Browse Source

Merge branch 'iznik' into chainspec-import-members-forum-versioned-store

Mokhtar Naamani 4 years ago
parent
commit
7b28e2693e
100 changed files with 1674 additions and 654 deletions
  1. 5 6
      .travis.yml
  2. 5 1
      package.json
  3. 0 3
      pioneer/.eslintignore
  4. 8 2
      pioneer/.eslintrc.js
  5. 3 1
      pioneer/packages/apps-config/src/api/spec/index.ts
  6. 3 0
      pioneer/packages/apps-config/src/api/spec/joystream-node.ts
  7. 5 0
      pioneer/packages/apps-config/src/settings/endpoints.ts
  8. 3 1
      pioneer/packages/apps-config/src/ui/logos/index.ts
  9. 1 0
      pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg
  10. 21 41
      pioneer/packages/apps-routing/src/index.ts
  11. 15 0
      pioneer/packages/apps-routing/src/joy-members.ts
  12. 27 0
      pioneer/packages/apps-routing/src/joy-pages.ts
  13. BIN
      pioneer/packages/apps/public/favicon.ico
  14. 11 0
      pioneer/packages/apps/public/index.html
  15. 14 1
      pioneer/packages/apps/src/Apps.tsx
  16. 1 1
      pioneer/packages/apps/src/Content/NotFound.tsx
  17. 2 6
      pioneer/packages/apps/src/Content/index.tsx
  18. 56 0
      pioneer/packages/apps/src/JoyTopBar/TopBar.tsx
  19. 1 1
      pioneer/packages/apps/src/SideBar/ChainInfo.tsx
  20. 3 24
      pioneer/packages/apps/src/SideBar/index.tsx
  21. 20 13
      pioneer/packages/apps/src/index.tsx
  22. 3 11
      pioneer/packages/apps/webpack.base.config.js
  23. 2 1
      pioneer/packages/apps/webpack.config.js
  24. 0 0
      pioneer/packages/joy-members/.skip-build
  25. 3 3
      pioneer/packages/joy-members/package.json
  26. 47 29
      pioneer/packages/joy-members/src/Dashboard.tsx
  27. 27 23
      pioneer/packages/joy-members/src/Details.tsx
  28. 3 1
      pioneer/packages/joy-members/src/DetailsByHandle.tsx
  29. 53 45
      pioneer/packages/joy-members/src/EditForm.tsx
  30. 11 6
      pioneer/packages/joy-members/src/List.tsx
  31. 8 8
      pioneer/packages/joy-members/src/MemberPreview.tsx
  32. 0 58
      pioneer/packages/joy-members/src/index.css
  33. 16 11
      pioneer/packages/joy-members/src/index.tsx
  34. 62 0
      pioneer/packages/joy-members/src/style.ts
  35. 3 2
      pioneer/packages/joy-members/src/utils.ts
  36. 0 0
      pioneer/packages/joy-pages/.skip-build
  37. 3 3
      pioneer/packages/joy-pages/package.json
  38. 4 2
      pioneer/packages/joy-pages/src/index.tsx
  39. 3 11
      pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx
  40. 2 2
      pioneer/packages/joy-proposals/src/Proposal/ProposalFromId.tsx
  41. 53 36
      pioneer/packages/joy-proposals/src/Proposal/Votes.tsx
  42. 1 1
      pioneer/packages/joy-roles/src/transport.substrate.ts
  43. 0 14
      pioneer/packages/joy-utils-old/src/functions/misc.ts
  44. 0 1
      pioneer/packages/joy-utils-old/src/react/components/index.tsx
  45. 0 1
      pioneer/packages/joy-utils-old/src/react/context/index.tsx
  46. 27 26
      pioneer/packages/joy-utils-old/src/react/hooks/proposals/useProposalSubscription.tsx
  47. 0 0
      pioneer/packages/joy-utils/README.md
  48. 3 3
      pioneer/packages/joy-utils/package.json
  49. 0 0
      pioneer/packages/joy-utils/src/functions/date.ts
  50. 2 2
      pioneer/packages/joy-utils/src/functions/format.ts
  51. 167 0
      pioneer/packages/joy-utils/src/functions/misc.ts
  52. 1 1
      pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx
  53. 4 1
      pioneer/packages/joy-utils/src/react/components/MutedText.tsx
  54. 3 0
      pioneer/packages/joy-utils/src/react/components/Section.tsx
  55. 46 50
      pioneer/packages/joy-utils/src/react/components/TxButton.tsx
  56. 5 5
      pioneer/packages/joy-utils/src/react/components/forms.tsx
  57. 4 0
      pioneer/packages/joy-utils/src/react/components/index.tsx
  58. 30 10
      pioneer/packages/joy-utils/src/react/context/account.tsx
  59. 2 0
      pioneer/packages/joy-utils/src/react/context/index.tsx
  60. 2 6
      pioneer/packages/joy-utils/src/react/context/membership.tsx
  61. 0 0
      pioneer/packages/joy-utils/src/react/helpers/index.ts
  62. 124 0
      pioneer/packages/joy-utils/src/react/hocs/accounts.tsx
  63. 104 0
      pioneer/packages/joy-utils/src/react/hocs/guards.tsx
  64. 2 0
      pioneer/packages/joy-utils/src/react/hooks/index.ts
  65. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx
  66. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx
  67. 0 28
      pioneer/packages/old-apps/apps-routing/src/joy-pages.ts
  68. 0 17
      pioneer/packages/old-apps/apps/src/TopBar.css
  69. 0 47
      pioneer/packages/old-apps/apps/src/TopBar.tsx
  70. 0 11
      pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused
  71. 1 1
      pioneer/packages/page-staking/src/Targets/Summary.tsx
  72. 8 0
      pioneer/packages/react-components/src/InputAddress/index.tsx
  73. 5 2
      pioneer/packages/react-components/src/Tabs/Tab.tsx
  74. 1 0
      pioneer/packages/react-components/src/Tabs/types.ts
  75. 3 1
      pioneer/packages/react-components/src/styles/index.ts
  76. 16 8
      pioneer/packages/react-components/src/styles/joystream.ts
  77. 1 1
      pioneer/packages/react-components/src/styles/theme.ts
  78. 7 9
      pioneer/tsconfig.json
  79. 23 0
      types/src/common.ts
  80. 9 18
      types/src/content-working-group/index.ts
  81. 125 8
      types/src/definitions/augment-types.ts
  82. 11 0
      types/src/hiring/index.ts
  83. 4 0
      types/src/mint/index.ts
  84. 6 1
      types/src/proposals.ts
  85. 2 0
      types/src/scripts/updateAugmentTypes.ts
  86. 6 0
      types/src/stake/index.ts
  87. 25 1
      types/src/versioned-store/permissions/index.ts
  88. 5 25
      types/src/working-group/index.ts
  89. 2 2
      types/tsconfig-scripts.json
  90. 46 0
      utils/api-examples/README.md
  91. 25 0
      utils/api-examples/package.json
  92. 32 0
      utils/api-examples/scripts/example.js
  93. 49 0
      utils/api-examples/scripts/export-data-directory.js
  94. 9 0
      utils/api-examples/scripts/index.js
  95. 15 0
      utils/api-examples/scripts/inject-data-objects.js
  96. 33 0
      utils/api-examples/scripts/list-data-directory.js
  97. 31 0
      utils/api-examples/scripts/transfer.js
  98. 20 0
      utils/api-examples/src/get-code.ts
  99. 52 0
      utils/api-examples/src/script.ts
  100. 56 0
      utils/api-examples/src/status.ts

+ 5 - 6
.travis.yml

@@ -9,8 +9,7 @@ language: rust
 # one is embedded in the binary)
 # cache: cargo
 
-rust:
-  - stable
+rust: stable
 
 # Skip Rust build in a pull request if no rust project files were modified
 before_install:
@@ -25,8 +24,8 @@ before_install:
     fi
 
 install:
-  - rustup install nightly
-  - rustup target add wasm32-unknown-unknown --toolchain nightly
+  - rustup install nightly-2020-05-23
+  - rustup target add wasm32-unknown-unknown --toolchain nightly-2020-05-23
   # travis installs rust using rustup with the "minimal" profile so these tools are not installed by default
   - rustup component add rustfmt
   - rustup component add clippy
@@ -37,7 +36,7 @@ before_script:
 script:
   # we set release as build type for all steps to benefit from already compiled packages in prior steps
   - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release -- -D warnings
-  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release
-  - TRIGGER_WASM_BUILD=1 cargo build --release
+  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all
+  - TRIGGER_WASM_BUILD=1 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release -p joystream-node
   - ls -l ./target/release/wbuild/joystream-node-runtime/
   - ./target/release/joystream-node --version

+ 5 - 1
package.json

@@ -22,7 +22,11 @@
     "pioneer",
     "pioneer/packages/apps*",
     "pioneer/packages/page*",
-    "pioneer/packages/react*"
+    "pioneer/packages/react*",
+    "pioneer/packages/joy-utils",
+    "pioneer/packages/joy-members",
+    "pioneer/packages/joy-pages",
+    "utils/api-examples"
   ],
   "resolutions": {
     "@polkadot/api": "1.26.1",

+ 0 - 3
pioneer/.eslintignore

@@ -2,16 +2,13 @@
 **/coverage/*
 **/node_modules/*
 packages/old-apps/*
-packages/joy-members/*
 packages/joy-election/*
 packages/joy-forum/*
 packages/joy-help/*
 packages/joy-media/*
-packages/joy-pages/*
 packages/joy-proposals/*
 packages/joy-roles/*
 packages/joy-settings/*
-packages/joy-utils/*
 packages/joy-utils-old/*
 .eslintrc.js
 i18next-scanner.config.js

+ 8 - 2
pioneer/.eslintrc.js

@@ -13,14 +13,20 @@ module.exports = {
   rules: {
     ...base.rules,
     '@typescript-eslint/no-explicit-any': 'off',
-    'react/prop-types': 'off',
     'new-cap': 'off',
     '@typescript-eslint/interface-name-prefix': 'off',
     '@typescript-eslint/ban-ts-comment': 'error',
     // why only required in VSCode!?!? is eslint plugin not working like eslint commandline?
     // Or are we having to add this because of new versions of eslint-config-* ?
     'no-console': 'off',
-    'header/header': 'off' // Temporary disable polkadot's rule
+    // Override some extended config rules:
+    'camelcase': 'off',
+    'header/header': 'off',
+    'sort-keys': 'off',
+    'react/jsx-sort-props': 'off',
+    'react/jsx-max-props-per-line': 'off',
+    'sort-destructure-keys/sort-destructure-keys': 'off',
+    '@typescript-eslint/unbound-method': 'warn', // Doesn't work well with our version of Formik, see: https://github.com/formium/formik/issues/2589
   },
   // isolate pioneer from monorepo eslint rules
   root: true

+ 3 - 1
pioneer/packages/apps-config/src/api/spec/index.ts

@@ -10,6 +10,7 @@ import encointerNodeTeeproxy from './encointer-node-teeproxy';
 import kulupu from './kulupu';
 import nodeTemplate from './node-template';
 import stablePoc from './stable-poc';
+import joystreamNode from './joystream-node';
 
 export default {
   acala,
@@ -21,5 +22,6 @@ export default {
   kulupu,
   'node-template': nodeTemplate,
   'stable-poc': stablePoc,
-  stable_poc: stablePoc
+  stable_poc: stablePoc,
+  'joystream-node': joystreamNode
 };

+ 3 - 0
pioneer/packages/apps-config/src/api/spec/joystream-node.ts

@@ -0,0 +1,3 @@
+import { types } from '@joystream/types';
+
+export default types;

+ 5 - 0
pioneer/packages/apps-config/src/settings/endpoints.ts

@@ -29,6 +29,11 @@ function createDev (t: TFunction): LinkOption[] {
 
 function createLive (t: TFunction): LinkOption[] {
   return [
+    {
+      info: 'joystream',
+      text: t<string>('rpc.joystream', 'Joystream (Current Testnet, hosted by Jsgenesis)', { ns: 'apps-config' }),
+      value: 'wss://rome-rpc-endpoint.joystream.org:9944'
+    },
     {
       dnslink: 'polkadot',
       info: 'polkadot',

+ 3 - 1
pioneer/packages/apps-config/src/ui/logos/index.ts

@@ -17,6 +17,7 @@ import nodeNodle from './nodes/nodle.svg';
 import nodePolkadot from './nodes/polkadot-circle.svg';
 import nodePolkadotJs from './nodes/polkadot-js.svg';
 import nodeSubstrate from './nodes/substrate-hexagon.svg';
+import nodeJoystream from './nodes/joystream-node.svg';
 
 // extensions
 import extensionPolkadotJs from './extensions/polkadot-js.svg';
@@ -48,7 +49,8 @@ const nodeLogos: Record<string, any> = [
   ['Nodle Chain Node', nodeNodle],
   ['parity-polkadot', nodePolkadot],
   ['polkadot-js', nodePolkadotJs],
-  ['substrate-node', nodeSubstrate]
+  ['substrate-node', nodeSubstrate],
+  ['joystream-node', nodeJoystream]
 ].reduce((logos, [node, logo]): Record<string, any> => ({
   ...logos,
   [(node as string).toLowerCase().replace(/-/g, ' ')]: logo

+ 1 - 0
pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240"><defs><style>.cls-1{fill:#4038ff;}.cls-2{fill:#fff;}</style></defs><title>Icon-mono-white-1bg-blue</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" width="240" height="240"/><path class="cls-2" d="M135.28,49.73l12.7,0-.15,59.67a57,57,0,0,1-14.49,37.86,67.76,67.76,0,0,0,1.72-15Z"/><path class="cls-2" d="M94.28,153.78v0a34.19,34.19,0,0,1-26.15,12.61L72,153.73Z"/><path class="cls-2" d="M102,130.94v1.28a34,34,0,0,1-2,11.41l-25-.06,3.83-12.69Z"/><path class="cls-2" d="M158.14,49.78l12.7,0-.09,36.84a57,57,0,0,1-14.49,37.86,67.76,67.76,0,0,0,1.72-15Z"/><path class="cls-2" d="M125.11,49.69l-.21,82.59a57.22,57.22,0,0,1-57.32,57H61.23l3.83-12.69h2.56a44.5,44.5,0,0,0,44.57-44.35l.22-82.58Z"/></g></g></svg>

+ 21 - 41
pioneer/packages/apps-routing/src/index.ts

@@ -9,69 +9,49 @@ import appSettings from '@polkadot/ui-settings';
 // When adding here, also ensure to add to Dummy.tsx
 
 import accounts from './accounts';
-import claims from './claims';
-import contracts from './contracts';
-import council from './council';
-// import dashboard from './dashboard';
-import democracy from './democracy';
 import explorer from './explorer';
 import extrinsics from './extrinsics';
-import genericAsset from './generic-asset';
 import js from './js';
-import parachains from './parachains';
-import poll from './poll';
 import settings from './settings';
-import society from './society';
 import staking from './staking';
 import storage from './storage';
 import sudo from './sudo';
-import techcomm from './techcomm';
 import toolbox from './toolbox';
 import transfer from './transfer';
-import treasury from './treasury';
+// Joy packages
+import members from './joy-members';
+import { terms, privacyPolicy } from './joy-pages';
 
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
   return appSettings.uiMode === 'light'
     ? [
-      // dashboard,
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
-      null,
+      members(t),
       staking(t),
-      democracy(t),
-      council(t),
-      // TODO Not sure about the inclusion of treasury, parachains & society here
       null,
-      settings(t)
+      transfer(t),
+      accounts(t),
+      settings(t),
+      // Those are hidden
+      terms(t),
+      privacyPolicy(t)
     ]
     : [
-      // dashboard(t),
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
-      null,
+      members(t),
       staking(t),
-      democracy(t),
-      council(t),
-      treasury(t),
-      techcomm(t),
-      parachains(t),
-      society(t),
       null,
-      contracts(t),
+      transfer(t),
+      accounts(t),
+      settings(t),
+      null,
+      explorer(t),
       storage(t),
       extrinsics(t),
+      js(t),
+      toolbox(t),
       sudo(t),
       null,
-      settings(t),
-      toolbox(t),
-      js(t)
+      // Those are hidden
+      terms(t),
+      privacyPolicy(t)
     ];
 }

+ 15 - 0
pioneer/packages/apps-routing/src/joy-members.ts

@@ -0,0 +1,15 @@
+import { Route } from './types';
+
+import Members from '@polkadot/joy-members/index';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Members,
+    display: {
+      needsApi: ['query.members.nextMemberId']
+    },
+    icon: 'users',
+    name: 'members',
+    text: t<string>('nav.membership', 'Membership', { ns: 'apps-routing' })
+  };
+}

+ 27 - 0
pioneer/packages/apps-routing/src/joy-pages.ts

@@ -0,0 +1,27 @@
+import { Route } from './types';
+
+import { ToS, Privacy } from '@polkadot/joy-pages/index';
+
+export function terms (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: ToS,
+    display: {
+      isHidden: true
+    },
+    text: t<string>('nav.terms', 'Terms of Service', { ns: 'apps-routing' }),
+    icon: 'file',
+    name: 'pages/tos'
+  };
+}
+
+export function privacyPolicy (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Privacy,
+    display: {
+      isHidden: true
+    },
+    text: t<string>('nav.privacy', 'Privacy Policy', { ns: 'apps-routing' }),
+    icon: 'file',
+    name: 'pages/privacy'
+  };
+}

BIN
pioneer/packages/apps/public/favicon.ico


+ 11 - 0
pioneer/packages/apps/public/index.html

@@ -6,6 +6,17 @@
     <link rel="manifest" href="manifest.json">
     <link rel="shortcut icon" href="favicon.ico">
     <title><%= htmlWebpackPlugin.options.PAGE_TITLE %></title>
+    <% if (htmlWebpackPlugin.options.IS_PROD) { %>
+      <!-- Global site tag (gtag.js) - Google Analytics -->
+      <script async src="https://www.googletagmanager.com/gtag/js?id=UA-133429788-6"></script>
+      <script>
+        window.dataLayer = window.dataLayer || [];
+        function gtag(){dataLayer.push(arguments);}
+        gtag('js', new Date());
+
+        gtag('config', 'UA-133429788-6', { 'anonymize_ip': true });
+      </script>
+    <% } %>
     <script type="text/javascript" src="/env-config.js"></script>
   </head>
   <body>

+ 14 - 1
pioneer/packages/apps/src/Apps.tsx

@@ -21,6 +21,9 @@ import SideBar from './SideBar';
 import WarmUp from './WarmUp';
 import { WindowDimensionsCtx } from './WindowDimensions';
 
+/* Joystream-specific */
+import TopBar from './JoyTopBar/TopBar';
+
 interface SidebarState {
   isCollapsed: boolean;
   isMenu: boolean;
@@ -96,7 +99,10 @@ function Apps ({ className = '' }: Props): React.ReactElement<Props> {
             toggleMenu={_toggleMenu}
           />
           <Signer>
-            <Content />
+            <div className='apps--Main'>
+              <TopBar />
+              <Content />
+            </div>
           </Signer>
           <ConnectingOverlay />
           <div id={PORTAL_ID} />
@@ -222,4 +228,11 @@ export default React.memo(styled(Apps)`
       opacity: 1;
     }
   }
+
+  .apps--Main {
+    flex-grow: 1;
+    min-height: 100vh;
+    overflow-x: hidden;
+    overflow-y: auto;
+  }
 `);

+ 1 - 1
pioneer/packages/apps/src/Content/NotFound.tsx

@@ -7,7 +7,7 @@ import { Redirect } from 'react-router';
 
 function NotFound (): React.ReactElement {
   return (
-    <Redirect to='/explorer' />
+    <Redirect to='/staking' />
   );
 }
 

+ 2 - 6
pioneer/packages/apps/src/Content/index.tsx

@@ -78,15 +78,11 @@ function Content ({ className }: Props): React.ReactElement<Props> {
 }
 
 export default React.memo(styled(Content)`
-  background: #f5f4f3;
-  flex-grow: 1;
-  height: 100%;
-  min-height: 100vh;
-  overflow-x: hidden;
-  overflow-y: auto;
+  background: rgba(250, 250, 250);
   padding: 0 1.5rem;
   position: relative;
   width: 100%;
+  height: 100%;
 
   @media(max-width: 768px) {
     padding: 0 0.5rem;

+ 56 - 0
pioneer/packages/apps/src/JoyTopBar/TopBar.tsx

@@ -0,0 +1,56 @@
+import React from 'react';
+import { useMyMembership } from '@polkadot/joy-utils/react/hooks';
+import { InputAddress } from '@polkadot/react-components';
+import { Available } from '@polkadot/react-query';
+import styled from 'styled-components';
+import { useApi } from '@polkadot/react-hooks';
+
+const StyledTopBar = styled.div`
+  padding: 0.75rem;
+  background-color: #3f3f3f;
+  border-bottom: 1px solid #d4d4d5;
+  text-align: right;
+  margin: 0;
+
+  &.NoMyAddress {
+    background-color: #ffeb83;
+    color: #000;
+    text-align: center;
+  }
+
+  .ui--InputAddress {
+    display: inline-block;
+  }
+`;
+
+function JoyTopBar () {
+  const {
+    allAccounts,
+    myAddress
+  } = useMyMembership();
+
+  const { isApiReady } = useApi();
+
+  if (!isApiReady) {
+    return null;
+  }
+
+  const balance = <span className='label'>Balance: </span>;
+  const labelExtra = myAddress
+    ? <Available label={balance} params={myAddress} />
+    : 'No key selected';
+
+  return Object.keys(allAccounts || {}).length ? (
+    <StyledTopBar>
+      <InputAddress
+        defaultValue={myAddress}
+        help='My current key that signs transactions'
+        label='My key'
+        labelExtra={labelExtra}
+        type='account'
+      />
+    </StyledTopBar>
+  ) : null;
+}
+
+export default JoyTopBar;

+ 1 - 1
pioneer/packages/apps/src/SideBar/ChainInfo.tsx

@@ -24,7 +24,7 @@ function ChainInfo ({ className = '', onClick }: Props): React.ReactElement<Prop
 
   return (
     <div
-      className={`apps--SideBar-logo ${className} ui--highlight--border`}
+      className={`apps--SideBar-logo ${className}`}
       onClick={onClick}
     >
       <div className='apps--SideBar-logo-inner'>

+ 3 - 24
pioneer/packages/apps/src/SideBar/index.tsx

@@ -7,7 +7,7 @@ import { Routes } from '@polkadot/apps-routing/types';
 import React, { useCallback, useMemo, useState } from 'react';
 import styled from 'styled-components';
 import createRoutes from '@polkadot/apps-routing';
-import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
+import { Button, ChainImg, Menu, media } from '@polkadot/react-components';
 
 import { SIDEBAR_MENU_THRESHOLD } from '../constants';
 import NetworkModal from '../modals/Network';
@@ -101,27 +101,6 @@ function SideBar ({ className = '', collapse, handleResize, isCollapsed, isMenuO
                 )
             ))}
             <Menu.Divider hidden />
-            <Menu.Item className='apps--SideBar-Item'>
-              <a
-                className='apps--SideBar-Item-NavLink'
-                href='https://github.com/polkadot-js/apps'
-                rel='noopener noreferrer'
-                target='_blank'
-              >
-                <Icon icon='code-branch' /><span className='text'>{t<string>('nav.github', 'GitHub', { ns: 'apps-routing' })}</span>
-              </a>
-            </Menu.Item>
-            <Menu.Item className='apps--SideBar-Item'>
-              <a
-                className='apps--SideBar-Item-NavLink'
-                href='https://wiki.polkadot.network'
-                rel='noopener noreferrer'
-                target='_blank'
-              >
-                <Icon icon='book' /><span className='text'>{t<string>('nav.wiki', 'Wiki', { ns: 'apps-routing' })}</span>
-              </a>
-            </Menu.Item>
-            <Menu.Divider hidden />
             {!isCollapsed && <NodeInfo />}
           </div>
           <div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
@@ -159,7 +138,7 @@ export default React.memo(styled(SideBar)`
 
   .apps--SideBar {
     align-items: center;
-    background: #4f5255;
+    background: #3f3f3f;
     box-sizing: border-box;
     display: flex;
     flex-flow: column;
@@ -221,7 +200,7 @@ export default React.memo(styled(SideBar)`
     }
 
     .apps--SideBar-collapse {
-      background: #4f5255;
+      background: #3f3f3f;
       bottom: 0;
       left: 0;
       padding: 0.75rem 0 .75rem 0.65rem;

+ 20 - 13
pioneer/packages/apps/src/index.tsx

@@ -20,6 +20,9 @@ import settings from '@polkadot/ui-settings';
 import Apps from './Apps';
 import WindowDimensions from './WindowDimensions';
 
+/* Joystream-specific */
+import { MyMembershipProvider, MyAccountProvider } from '@polkadot/joy-utils/react/context';
+
 const rootId = 'root';
 const rootElement = document.getElementById(rootId);
 const theme = { theme: settings.uiTheme };
@@ -38,19 +41,23 @@ store.each((_, key): void => {
 ReactDOM.render(
   <Suspense fallback='...'>
     <ThemeProvider theme={theme}>
-      <Queue>
-        <Api url={settings.apiUrl}>
-          <BlockAuthors>
-            <Events>
-              <HashRouter>
-                <WindowDimensions>
-                  <Apps />
-                </WindowDimensions>
-              </HashRouter>
-            </Events>
-          </BlockAuthors>
-        </Api>
-      </Queue>
+      <MyAccountProvider>
+        <Queue>
+          <Api url={settings.apiUrl}>
+            <MyMembershipProvider>
+              <BlockAuthors>
+                <Events>
+                  <HashRouter>
+                    <WindowDimensions>
+                      <Apps />
+                    </WindowDimensions>
+                  </HashRouter>
+                </Events>
+              </BlockAuthors>
+            </MyMembershipProvider>
+          </Api>
+        </Queue>
+      </MyAccountProvider>
     </ThemeProvider>
   </Suspense>,
   rootElement

+ 3 - 11
pioneer/packages/apps/webpack.base.config.js

@@ -98,7 +98,7 @@ function createWebpack (ENV, context) {
           ]
         },
         {
-          exclude: [/semantic-ui-css/],
+          // Original config had "exclude: [/semantic-ui-css/]"
           test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
           use: [
             {
@@ -112,7 +112,8 @@ function createWebpack (ENV, context) {
           ]
         },
         {
-          exclude: [/semantic-ui-css/],
+          // Original config had "exclude: [/semantic-ui-css/]", because Semantic UI Icons
+          // are not used in polkadot-js/apps repository, but they are used in ours
           test: [/\.eot$/, /\.ttf$/, /\.svg$/, /\.woff$/, /\.woff2$/],
           use: [
             {
@@ -123,15 +124,6 @@ function createWebpack (ENV, context) {
               }
             }
           ]
-        },
-        {
-          include: [/semantic-ui-css/],
-          test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.eot$/, /\.ttf$/, /\.svg$/, /\.woff$/, /\.woff2$/],
-          use: [
-            {
-              loader: require.resolve('null-loader')
-            }
-          ]
         }
       ]
     },

+ 2 - 1
pioneer/packages/apps/webpack.config.js

@@ -20,7 +20,8 @@ module.exports = merge(
     devtool: process.env.BUILD_ANALYZE ? 'source-map' : false,
     plugins: [
       new HtmlWebpackPlugin({
-        PAGE_TITLE: 'Polkadot/Substrate Portal',
+        IS_PROD: ENV === 'production',
+        PAGE_TITLE: 'Joystream Network Portal',
         inject: true,
         template: path.join(context, `${hasPublic ? 'public/' : ''}index.html`)
       })

+ 0 - 0
pioneer/packages/joy-members/.skip-build


+ 3 - 3
pioneer/packages/joy-members/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 47 - 29
pioneer/packages/joy-members/src/Dashboard.tsx

@@ -3,12 +3,12 @@ import BN from 'bn.js';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
-import { Bubble } from '@polkadot/react-components/index';
+import { withCalls } from '@polkadot/react-api/hoc';
+import { Label } from 'semantic-ui-react';
 import { formatNumber } from '@polkadot/util';
 import { bool as Bool } from '@polkadot/types';
 
-import Section from '@polkadot/joy-utils/Section';
+import { Section } from '@polkadot/joy-utils/react/components';
 import translate from './translate';
 import { queryMembershipToProp } from './utils';
 
@@ -27,39 +27,57 @@ class Dashboard extends React.PureComponent<Props> {
   renderGeneral () {
     const p = this.props;
     const { newMembershipsAllowed: isAllowed } = p;
-    let isAllowedColor = '';
+    let isAllowedColor: 'grey' | 'green' | 'red' = 'grey';
+
     if (isAllowed) {
       isAllowedColor = isAllowed.eq(true) ? 'green' : 'red';
     }
-    return <Section title='General'>
-      <Bubble label='New memberships allowed?' className={isAllowedColor}>
-        {isAllowed && (isAllowed.eq(true) ? 'Yes' : 'No')}
-      </Bubble>
-      <Bubble label='Next member ID'>
-        {formatNumber(p.nextMemberId)}
-      </Bubble>
-      <Bubble label='First member ID'>
-        {formatNumber(FIRST_MEMBER_ID)}
-      </Bubble>
-    </Section>;
+
+    return (
+      <Section title='General'>
+        <Label.Group size='large'>
+          <Label color={isAllowedColor}>
+            New memberships allowed?
+            <Label.Detail>{isAllowed && (isAllowed.eq(true) ? 'Yes' : 'No')}</Label.Detail>
+          </Label>
+          <Label color='grey'>
+            Next member ID
+            <Label.Detail>{formatNumber(p.nextMemberId)}</Label.Detail>
+          </Label>
+          <Label color='grey'>
+            First member ID
+            <Label.Detail>{formatNumber(FIRST_MEMBER_ID)}</Label.Detail>
+          </Label>
+        </Label.Group>
+      </Section>
+    );
   }
 
   renderValidation () {
     const p = this.props;
-    return <Section title='Validation'>
-      <Bubble label='Min. length of handle'>
-        {formatNumber(p.minHandleLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of handle'>
-        {formatNumber(p.maxHandleLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of avatar URI'>
-        {formatNumber(p.maxAvatarUriLength)} chars
-      </Bubble>
-      <Bubble label='Max. length of about'>
-        {formatNumber(p.maxAboutTextLength)} chars
-      </Bubble>
-    </Section>;
+
+    return (
+      <Section title='Validation'>
+        <Label.Group color='grey' size='large'>
+          <Label>
+            Min. length of handle
+            <Label.Detail>{formatNumber(p.minHandleLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of handle
+            <Label.Detail>{formatNumber(p.maxHandleLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of avatar URI
+            <Label.Detail>{formatNumber(p.maxAvatarUriLength)} chars</Label.Detail>
+          </Label>
+          <Label>
+            Max. length of about
+            <Label.Detail>{formatNumber(p.maxAboutTextLength)} chars</Label.Detail>
+          </Label>
+        </Label.Group>
+      </Section>
+    );
   }
 
   render () {

+ 27 - 23
pioneer/packages/joy-members/src/Details.tsx

@@ -5,18 +5,18 @@ import ReactMarkdown from 'react-markdown';
 import { IdentityIcon } from '@polkadot/react-components';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { Option } from '@polkadot/types';
 import BalanceDisplay from '@polkadot/react-components/Balance';
-import AddressMini from '@polkadot/react-components/AddressMiniJoy';
+import AddressMini from '@polkadot/react-components/AddressMini';
 import { formatNumber } from '@polkadot/util';
 
 import translate from './translate';
 import { MemberId, Membership, EntryMethod, Paid, Screening, Genesis, SubscriptionId } from '@joystream/types/members';
 import { queryMembershipToProp } from './utils';
 import { Seat } from '@joystream/types/council';
-import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts';
 
 type Props = ApiProps & I18nProps & MyAccountProps & {
   preview?: boolean;
@@ -28,6 +28,7 @@ type Props = ApiProps & I18nProps & MyAccountProps & {
 class Component extends React.PureComponent<Props> {
   render () {
     const { membership } = this.props;
+
     return membership && !membership.handle.isEmpty
       ? this.renderProfile(membership)
       : (
@@ -54,34 +55,34 @@ class Component extends React.PureComponent<Props> {
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
     const isMyProfile = myAddress && (myAddress === root_account.toString() || myAddress === controller_account.toString());
     const isCouncilor: boolean = (
-      (activeCouncil.find(x => root_account.eq(x.member)) !== undefined) ||
-      (activeCouncil.find(x => controller_account.eq(x.member)) !== undefined)
+      (activeCouncil.find((x) => root_account.eq(x.member)) !== undefined) ||
+      (activeCouncil.find((x) => controller_account.eq(x.member)) !== undefined)
     );
 
     return (
       <>
-      <div className={`item ProfileDetails ${isMyProfile && 'MyProfile'}`}>
-        {hasAvatar
-          ? <img className='ui avatar image' src={avatar_uri.toString()} />
-          : <IdentityIcon className='image' value={root_account} size={40} />
-        }
-        <div className='content'>
-          <div className='header'>
-            <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
-            {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
-          </div>
-          <div className='description'>
-            {isCouncilor &&
-              <b className='muted text' style={{ color: '#607d8b' }}>
+        <div className={`item ProfileDetails${isMyProfile ? ' MyProfile' : ''}`}>
+          {hasAvatar
+            ? <img className='ui avatar image' src={avatar_uri.toString()} />
+            : <IdentityIcon className='image' value={root_account} size={40} />
+          }
+          <div className='content'>
+            <div className='header'>
+              <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
+              {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
+            </div>
+            <div className='description'>
+              {isCouncilor &&
+              <b className='muted text' style={{ color: '#607d8b', display: 'block' }}>
                 <i className='university icon'></i>
                 Council member
               </b>}
-            <BalanceDisplay label='Balance(root): ' params={root_account} />
-            <div>MemberId: {this.props.memberId.toString()}</div>
+              <BalanceDisplay label='Balance(root): ' params={root_account} />
+              <div>MemberId: {this.props.memberId.toString()}</div>
+            </div>
           </div>
         </div>
-      </div>
-      {!preview && this.renderDetails(membership, isCouncilor)}
+        {!preview && this.renderDetails(membership, isCouncilor)}
       </>
     );
   }
@@ -147,11 +148,14 @@ class Component extends React.PureComponent<Props> {
 
   private renderEntryMethod (entry: EntryMethod) {
     const etype = entry.type;
+
     if (etype === Paid.name) {
       const paid = entry.value as Paid;
+
       return <div>Paid, terms ID: {paid.toNumber()}</div>;
     } else if (etype === Screening.name) {
       const accountId = entry.value as Screening;
+
       return <div>Screened by <AddressMini value={accountId} isShort={false} isPadded={false} withBalance /></div>;
     } else if (etype === Genesis.name) {
       return <div>Created at Genesis</div>;

+ 3 - 1
pioneer/packages/joy-members/src/DetailsByHandle.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { stringToU8a, u8aToHex } from '@polkadot/util';
 
 import translate from './translate';
@@ -16,6 +16,7 @@ type DetailsByHandleProps = {
 
 function DetailsByHandleInner (p: DetailsByHandleProps) {
   const { memberIdByHandle: memberId } = p;
+
   return memberId !== undefined // here we can't make distinction value existing and loading
     ? <div className='ui massive relaxed middle aligned list FullProfile'>
       <Details memberId={memberId} />
@@ -39,6 +40,7 @@ class Component extends React.PureComponent<Props> {
   render () {
     const { match: { params: { handle } } } = this.props;
     const handleHex = u8aToHex(stringToU8a(handle));
+
     return (
       <DetailsByHandle handle={handleHex} />
     );

+ 53 - 45
pioneer/packages/joy-members/src/EditForm.tsx

@@ -1,19 +1,18 @@
 import BN from 'bn.js';
-import React from 'react';
+import React, { useContext } from 'react';
 import { Link } from 'react-router-dom';
 import { Form, Field, withFormik, FormikProps } from 'formik';
 import * as Yup from 'yup';
 
 import { Vec } from '@polkadot/types';
-import Section from '@polkadot/joy-utils/Section';
-import TxButton from '@polkadot/joy-utils/TxButton';
-import * as JoyForms from '@polkadot/joy-utils/forms';
+import { Section, TxButton } from '@polkadot/joy-utils/react/components';
+import * as JoyForms from '@polkadot/joy-utils/react/components/forms';
 import { SubmittableResult } from '@polkadot/api';
 import { MemberId, Membership, PaidTermId, PaidMembershipTerms } from '@joystream/types/members';
 import { OptionText } from '@joystream/types/common';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/react/hocs/accounts';
 import { queryMembershipToProp } from './utils';
-import { withCalls } from '@polkadot/react-api/index';
+import { withCalls, ApiContext } from '@polkadot/react-api/index';
 import { Button, Message } from 'semantic-ui-react';
 import { formatBalance } from '@polkadot/util';
 import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
@@ -79,12 +78,15 @@ const InnerForm = (props: FormProps) => {
     memberId
   } = props;
 
+  const { api } = useContext(ApiContext);
+
   const onSubmit = (sendTx: () => void) => {
     if (isValid) sendTx();
   };
 
   const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
     setSubmitting(false);
+
     if (txResult == null) {
       // Tx cancelled.
 
@@ -102,7 +104,9 @@ const InnerForm = (props: FormProps) => {
 
   // TODO extract to forms.tsx
   const fieldToTextOption = (field: FieldName): OptionText => {
-    return isFieldChanged(field) ? OptionText.some(values[field]) : OptionText.none();
+    return isFieldChanged(field)
+      ? api.createType('Option<Text>', values[field])
+      : api.createType('Option<Text>', null);
   };
 
   const buildTxParams = () => {
@@ -126,29 +130,29 @@ const InnerForm = (props: FormProps) => {
   // TODO show warning that you don't have enough balance to buy a membership
 
   return (
-    <Section title="My Membership Profile">
-      <Form className="ui form JoyForm">
+    <Section title='My Membership Profile'>
+      <Form className='ui form JoyForm'>
         <LabelledText
-          name="handle"
-          label="Handle/nickname"
+          name='handle'
+          label='Handle/nickname'
           placeholder={'You can use a-z, 0-9 and underscores.'}
           style={{ maxWidth: '30rem' }}
           {...props}
         />
         <LabelledText
-          name="avatar"
-          label="Avatar URL"
-          placeholder="Paste here an URL of your avatar image."
+          name='avatar'
+          label='Avatar URL'
+          placeholder='Paste here an URL of your avatar image.'
           {...props}
         />
-        <LabelledField name="about" label="About" {...props}>
+        <LabelledField name='about' label='About' {...props}>
           <Field
-            component="textarea"
-            id="about"
-            name="about"
+            component='textarea'
+            id='about'
+            name='about'
             disabled={isSubmitting}
             rows={3}
-            placeholder="Write here anything you would like to share about yourself with Joystream community."
+            placeholder='Write here anything you would like to share about yourself with Joystream community.'
           />
         </LabelledField>
         {!profile && paidTerms && (
@@ -165,24 +169,26 @@ const InnerForm = (props: FormProps) => {
           </Message>
         )}
         <LabelledField invisibleLabel {...props}>
-          <TxButton
-            type="submit"
-            size="large"
-            label={profile ? 'Update my profile' : 'Register'}
-            isDisabled={!dirty || isSubmitting}
-            params={buildTxParams()}
-            tx={profile ? 'members.updateMembership' : 'members.buyMembership'}
-            onClick={onSubmit}
-            txFailedCb={onTxFailed}
-            txSuccessCb={onTxSuccess}
-          />
-          <Button
-            type="button"
-            size="large"
-            disabled={!dirty || isSubmitting}
-            onClick={() => resetForm()}
-            content="Reset form"
-          />
+          <div style={{ display: 'flex' }}>
+            <TxButton
+              type='submit'
+              size='large'
+              label={profile ? 'Update my profile' : 'Register'}
+              isDisabled={!dirty || isSubmitting}
+              params={buildTxParams()}
+              tx={profile ? 'members.updateMembership' : 'members.buyMembership'}
+              onClick={onSubmit}
+              txFailedCb={onTxFailed}
+              txSuccessCb={onTxSuccess}
+            />
+            <Button
+              type='button'
+              size='large'
+              disabled={!dirty || isSubmitting}
+              onClick={() => resetForm()}
+              content='Reset form'
+            />
+          </div>
         </LabelledField>
       </Form>
     </Section>
@@ -191,8 +197,9 @@ const InnerForm = (props: FormProps) => {
 
 const EditForm = withFormik<OuterProps, FormValues>({
   // Transform outer props into form values
-  mapPropsToValues: props => {
+  mapPropsToValues: (props) => {
     const { profile: p } = props;
+
     return {
       handle: p ? p.handle.toString() : '',
       avatar: p ? p.avatar_uri.toString() : '',
@@ -202,7 +209,7 @@ const EditForm = withFormik<OuterProps, FormValues>({
 
   validationSchema: buildSchema,
 
-  handleSubmit: values => {
+  handleSubmit: (values) => {
     // do submitting things
   }
 })(InnerForm);
@@ -220,6 +227,7 @@ type WithMembershipDataProps = {
 
 function WithMembershipDataInner (p: WithMembershipDataProps) {
   const triedToFindProfile = !p.memberId || p.membership;
+
   if (
     triedToFindProfile &&
     p.paidTerms &&
@@ -228,7 +236,9 @@ function WithMembershipDataInner (p: WithMembershipDataProps) {
     p.maxAvatarUriLength &&
     p.maxAboutTextLength
   ) {
-    const membership = p.membership && !p.membership.handle.isEmpty ? p.membership : undefined;
+    const membership = (p.memberId && p.membership && !p.membership.handle.isEmpty)
+      ? p.membership
+      : undefined;
 
     if (!membership && p.paidTerms.isEmpty) {
       console.error('Could not find active paid membership terms');
@@ -267,10 +277,10 @@ type WithMembershipDataWrapperProps = MyAccountProps & {
 function WithMembershipDataWrapperInner (p: WithMembershipDataWrapperProps) {
   if (p.allAccounts && !Object.keys(p.allAccounts).length) {
     return (
-      <Message warning className="JoyMainStatus">
+      <Message warning className='JoyMainStatus'>
         <Message.Header>Please create a key to get started.</Message.Header>
         <div style={{ marginTop: '1rem' }}>
-          <Link to={'/accounts'} className="ui button orange">
+          <Link to={'/accounts'} className='ui button orange'>
             Create key
           </Link>
         </div>
@@ -280,9 +290,7 @@ function WithMembershipDataWrapperInner (p: WithMembershipDataWrapperProps) {
 
   if (p.memberIdsByRootAccountId && p.memberIdsByControllerAccountId && p.paidTermsIds) {
     if (p.paidTermsIds.length) {
-      // let member_ids = p.memberIdsByRootAccountId.slice(); // u8a.subarray is not a function!!
-      p.memberIdsByRootAccountId.concat(p.memberIdsByControllerAccountId);
-      const memberId = p.memberIdsByRootAccountId.length ? p.memberIdsByRootAccountId[0] : undefined;
+      const [memberId] = p.memberIdsByRootAccountId.toArray().concat(p.memberIdsByControllerAccountId.toArray());
 
       return <WithMembershipData memberId={memberId} paidTermsId={p.paidTermsIds[0]} />;
     } else {

+ 11 - 6
pioneer/packages/joy-members/src/List.tsx

@@ -4,13 +4,14 @@ import React from 'react';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
-import Section from '@polkadot/joy-utils/Section';
+import { Section } from '@polkadot/joy-utils/react/components';
 import translate from './translate';
 import Details from './Details';
 import { MemberId } from '@joystream/types/members';
 import { RouteComponentProps, Redirect } from 'react-router-dom';
 import { Pagination, Icon, PaginationProps } from 'semantic-ui-react';
 import styled from 'styled-components';
+import { withApi } from '@polkadot/react-api';
 
 const StyledPagination = styled(Pagination)`
   border-bottom: 1px solid #ddd !important;
@@ -22,7 +23,7 @@ type Props = ApiProps & I18nProps & RouteComponentProps & {
   match: { params: { page?: string } };
 };
 
-type State = {};
+type State = Record<any, never>;
 
 const MEMBERS_PER_PAGE = 20;
 
@@ -31,7 +32,8 @@ class Component extends React.PureComponent<Props, State> {
 
   onPageChange = (e: React.MouseEvent, data: PaginationProps) => {
     const { history } = this.props;
-    history.push(`/members/list/${data.activePage}`);
+
+    history.push(`/members/list/${data.activePage || 1}`);
   }
 
   renderPagination (currentPage: number, pagesCount: number) {
@@ -55,7 +57,8 @@ class Component extends React.PureComponent<Props, State> {
     const {
       firstMemberId,
       membersCreated,
-      match: { params: { page } }
+      match: { params: { page } },
+      api
     } = this.props;
 
     const membersCount = membersCreated.toNumber();
@@ -67,11 +70,13 @@ class Component extends React.PureComponent<Props, State> {
     }
 
     const ids: MemberId[] = [];
+
     if (membersCount > 0) {
       const firstId = firstMemberId.toNumber() + (currentPage - 1) * MEMBERS_PER_PAGE;
       const lastId = Math.min(firstId + MEMBERS_PER_PAGE, membersCount) - 1;
+
       for (let i = firstId; i <= lastId; i++) {
-        ids.push(new MemberId(i));
+        ids.push(api.createType('MemberId', i));
       }
     }
 
@@ -95,4 +100,4 @@ class Component extends React.PureComponent<Props, State> {
   }
 }
 
-export default translate(Component);
+export default translate(withApi(Component));

+ 8 - 8
pioneer/packages/joy-members/src/MemberPreview.tsx

@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
 import { Vec } from '@polkadot/types';
 import { AccountId } from '@polkadot/types/interfaces';
 import IdentityIcon from '@polkadot/react-components/IdentityIcon';
@@ -12,9 +12,8 @@ import translate from './translate';
 import { MemberId, Membership } from '@joystream/types/members';
 import { queryMembershipToProp } from './utils';
 import { Seat } from '@joystream/types/council';
-import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
-import { FlexCenter } from '@polkadot/joy-utils/FlexCenter';
-import { MutedSpan } from '@polkadot/joy-utils/MutedText';
+import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { FlexCenter, MutedSpan } from '@polkadot/joy-utils/react/components';
 
 const AvatarSizePx = 36;
 const InlineAvatarSizePx = 24;
@@ -33,6 +32,7 @@ type MemberPreviewProps = ApiProps & I18nProps & {
 class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
   render () {
     const { membership } = this.props;
+
     return membership && !membership.handle.isEmpty
       ? this.renderProfile(membership)
       : null;
@@ -43,19 +43,19 @@ class InnerMemberPreview extends React.PureComponent<MemberPreviewProps> {
     const { handle, avatar_uri } = membership;
 
     const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
-    const isCouncilor: boolean = accountId !== undefined && activeCouncil.find(x => accountId.eq(x.member)) !== undefined;
+    const isCouncilor: boolean = accountId !== undefined && activeCouncil.find((x) => accountId.eq(x.member)) !== undefined;
 
     const avatarSize = inline ? InlineAvatarSizePx : AvatarSizePx;
 
-    return <div className={`JoyMemberPreview ${className}`} style={style}>
+    return <div className={`JoyMemberPreview ${className || ''}`} style={style}>
       <FlexCenter>
         {prefixLabel &&
           <MutedSpan className='PrefixLabel'>{prefixLabel}</MutedSpan>
         }
         {hasAvatar ? (
-          <img className="Avatar" src={avatar_uri.toString()} width={avatarSize} height={avatarSize} />
+          <img className='Avatar' src={avatar_uri.toString()} width={avatarSize} height={avatarSize} />
         ) : (
-          <IdentityIcon className="Avatar" value={accountId} size={avatarSize} />
+          <IdentityIcon className='Avatar' value={accountId} size={avatarSize} />
         )
         }
         <div className='Content'>

+ 0 - 58
pioneer/packages/joy-members/src/index.css

@@ -1,58 +0,0 @@
-.ProfilePreviews,
-.FullProfile {
-  .item {
-    .image {
-      padding: 0 !important;
-    }
-    .description {
-      font-size: 1rem;
-    }
-  }
-}
-.ProfilePreviews {
-  &.ui.list>.item:first-child {
-    padding-top: .75rem;
-  }
-  &.ui.list>.item:last-child {
-    padding-bottom: .75rem;
-  }
-  .MyProfile {
-    background-color: #FFF8E1;
-  }
-}
-.ProfileDetails {
-  padding-left: 1rem !important;
-  .handle {
-    margin-right: 1rem;
-    .button {
-      padding: .5rem .75rem;
-    }
-  }
-}
-.ProfileDetailsTable {
-  font-size: 1rem !important;
-  tr td:first-child {
-    width: 1%;
-    white-space: nowrap;
-  }
-}
-
-.JoyMemberPreview {
-  margin-right: .5rem;
-  .PrefixLabel {
-    margin-right: .5rem;
-  }
-  .Avatar {
-    margin-right: .5rem;
-    border-radius: 100%;
-  }
-  .Content {
-    .Username {
-      font-weight: bold;
-    }
-    .Details {
-      font-weight: 100;
-      opacity: .75;
-    }
-  }
-}

+ 16 - 11
pioneer/packages/joy-members/src/index.tsx

@@ -5,10 +5,9 @@ import { Route, Switch } from 'react-router';
 
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
-import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
-
-import './index.css';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
+import Tabs from '@polkadot/react-components/Tabs';
+import { TabItem } from '@polkadot/react-components/Tabs/types';
 
 import { queryMembershipToProp } from './utils';
 import translate from './translate';
@@ -16,10 +15,15 @@ import Dashboard from './Dashboard';
 import List from './List';
 import DetailsByHandle from './DetailsByHandle';
 import EditForm from './EditForm';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
+import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts';
 import { FIRST_MEMBER_ID } from './constants';
 import { RouteComponentProps } from 'react-router-dom';
 
+import styled from 'styled-components';
+import style from './style';
+
+const MembersMain = styled.main`${style}`;
+
 // define out internal types
 type Props = AppProps & ApiProps & I18nProps & MyAccountProps & {
   nextMemberId?: BN;
@@ -32,8 +36,8 @@ class App extends React.PureComponent<Props> {
     return [
       {
         name: 'list',
-        text: t('All members') + ` (${memberCount})`,
-        forcedExact: false
+        text: t('All members') + ` (${memberCount?.toString() || '-'})`,
+        forceMatchParams: true
       },
       {
         name: 'edit',
@@ -48,6 +52,7 @@ class App extends React.PureComponent<Props> {
 
   private renderList (routeProps: RouteComponentProps) {
     const { nextMemberId, ...otherProps } = this.props;
+
     return nextMemberId
       ? <List firstMemberId={FIRST_MEMBER_ID} membersCreated={nextMemberId} {...otherProps} {...routeProps}/>
       : <em>Loading...</em>;
@@ -58,18 +63,18 @@ class App extends React.PureComponent<Props> {
     const tabs = this.buildTabs();
 
     return (
-      <main className='members--App'>
+      <MembersMain className='members--App'>
         <header>
           <Tabs basePath={basePath} items={tabs} />
         </header>
         <Switch>
           <Route path={`${basePath}/edit`} component={EditForm} />
           <Route path={`${basePath}/dashboard`} component={Dashboard} />
-          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ props => this.renderList(props) } />
+          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ (props) => this.renderList(props) } />
           <Route exact={true} path={`${basePath}/:handle`} component={DetailsByHandle} />
-          <Route render={ props => this.renderList(props) } />
+          <Route render={ (props) => this.renderList(props) } />
         </Switch>
-      </main>
+      </MembersMain>
     );
   }
 }

+ 62 - 0
pioneer/packages/joy-members/src/style.ts

@@ -0,0 +1,62 @@
+import { css } from 'styled-components';
+
+export default css`
+  .ProfilePreviews,
+  .FullProfile {
+    .item {
+      .image {
+        padding: 0 !important;
+      }
+      .description {
+        font-size: 1rem;
+      }
+    }
+  }
+  .ProfilePreviews {
+    &.ui.list>.item:first-child {
+      padding-top: .75rem;
+    }
+    &.ui.list>.item:last-child {
+      padding-bottom: .75rem;
+    }
+    .MyProfile {
+      background-color: #FFF8E1;
+    }
+  }
+  .ProfileDetails {
+    padding-left: 1rem !important;
+    .handle {
+      margin-right: 1rem;
+      .button {
+        padding: .5rem .75rem;
+      }
+    }
+  }
+  .ProfileDetailsTable {
+    font-size: 1rem !important;
+    tr td:first-child {
+      width: 1%;
+      white-space: nowrap;
+    }
+  }
+
+  .JoyMemberPreview {
+    margin-right: .5rem;
+    .PrefixLabel {
+      margin-right: .5rem;
+    }
+    .Avatar {
+      margin-right: .5rem;
+      border-radius: 100%;
+    }
+    .Content {
+      .Username {
+        font-weight: bold;
+      }
+      .Details {
+        font-weight: 100;
+        opacity: .75;
+      }
+    }
+  }
+`;

+ 3 - 2
pioneer/packages/joy-members/src/utils.ts

@@ -1,5 +1,6 @@
-import { queryToProp } from '@polkadot/joy-utils/index';
-import { Options as QueryOptions } from '@polkadot/react-api/with/types';
+// TODO: Move to joy-utils?
+import { queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { Options as QueryOptions } from '@polkadot/react-api/hoc/types';
 
 export const queryMembershipToProp = (storageItem: string, paramNameOrOpts?: string | QueryOptions) => {
   return queryToProp(`query.members.${storageItem}`, paramNameOrOpts);

+ 0 - 0
pioneer/packages/joy-pages/.skip-build


+ 3 - 3
pioneer/packages/joy-pages/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 4 - 2
pioneer/packages/joy-pages/src/index.tsx

@@ -4,9 +4,11 @@ import Page from './Page';
 import ToS_md from './md/ToS.md';
 
 import Privacy_md from './md/Privacy.md';
+
 export function ToS () {
-  return <Page md={ToS_md} />;
+  return <Page md={ToS_md as string} />;
 }
+
 export function Privacy () {
-  return <Page md={Privacy_md} />;
+  return <Page md={Privacy_md as string} />;
 }

+ 3 - 11
pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx

@@ -5,7 +5,7 @@ import Body from './Body';
 import VotingSection from './VotingSection';
 import Votes from './Votes';
 import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
-import { ParsedProposal, ProposalVotes } from '@polkadot/joy-utils/types/proposals';
+import { ParsedProposal } from '@polkadot/joy-utils/types/proposals';
 import { withCalls } from '@polkadot/react-api';
 import { withMulti } from '@polkadot/react-api/with';
 
@@ -14,7 +14,6 @@ import { ProposalId, ProposalDecisionStatuses, ApprovedProposalStatuses, Executi
 import { BlockNumber } from '@polkadot/types/interfaces';
 import { MemberId } from '@joystream/types/members';
 import { Seat } from '@joystream/types/council';
-import { PromiseComponent } from '@polkadot/joy-utils/react/components';
 import ProposalDiscussion from './discussion/ProposalDiscussion';
 
 import styled from 'styled-components';
@@ -115,7 +114,6 @@ export function getExtendedStatus (proposal: ParsedProposal, bestNumber: BlockNu
 type ProposalDetailsProps = MyAccountProps & {
   proposal: ParsedProposal;
   proposalId: ProposalId;
-  votesListState: { data: ProposalVotes | null; error: any; loading: boolean };
   bestNumber?: BlockNumber;
   council?: Seat[];
 };
@@ -127,8 +125,7 @@ function ProposalDetails ({
   myMemberId,
   iAmMember,
   council,
-  bestNumber,
-  votesListState
+  bestNumber
 }: ProposalDetailsProps) {
   const iAmCouncilMember = Boolean(iAmMember && council && council.some(seat => seat.member.toString() === myAddress));
   const iAmProposer = Boolean(iAmMember && myMemberId !== undefined && proposal.proposerId === myMemberId.toNumber());
@@ -156,12 +153,7 @@ function ProposalDetails ({
               memberId={ myMemberId as MemberId }
               isVotingPeriod={ isVotingPeriod }/>
           ) }
-          <PromiseComponent
-            error={votesListState.error}
-            loading={votesListState.loading}
-            message="Fetching the votes...">
-            <Votes votes={votesListState.data as ProposalVotes} />
-          </PromiseComponent>
+          <Votes proposal={proposal}/>
         </ProposalDetailsVoting>
       </ProposalDetailsMain>
       <ProposalDetailsDiscussion>

+ 2 - 2
pioneer/packages/joy-proposals/src/Proposal/ProposalFromId.tsx

@@ -12,14 +12,14 @@ export default function ProposalFromId (props: RouteComponentProps<any>) {
     }
   } = props;
 
-  const { proposal: proposalState, votes: votesState } = useProposalSubscription(new ProposalId(id));
+  const proposalState = useProposalSubscription(new ProposalId(id));
 
   return (
     <PromiseComponent
       error={proposalState.error}
       loading={proposalState.loading}
       message={'Fetching proposal...'}>
-      <ProposalDetails proposal={ proposalState.data } proposalId={ id } votesListState={ votesState }/>
+      <ProposalDetails proposal={ proposalState.data } proposalId={ id }/>
     </PromiseComponent>
   );
 }

+ 53 - 36
pioneer/packages/joy-proposals/src/Proposal/Votes.tsx

@@ -1,50 +1,67 @@
 import React from 'react';
 import { Header, Divider, Table, Icon } from 'semantic-ui-react';
 import useVoteStyles from './useVoteStyles';
-import { ProposalVotes } from '@polkadot/joy-utils/types/proposals';
 import { VoteKind } from '@joystream/types/proposals';
 import { VoteKindStr } from './VotingSection';
 import ProfilePreview from '@polkadot/joy-utils/MemberProfilePreview';
+import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks';
+import { ParsedProposal, ProposalVotes } from '@polkadot/joy-utils/types/proposals';
+import { PromiseComponent } from '@polkadot/joy-utils/react/components';
 
 type VotesProps = {
-  votes: ProposalVotes;
+  proposal: ParsedProposal;
 };
 
-export default function Votes ({ votes }: VotesProps) {
-  if (!votes.votes.length) {
-    return <Header as="h4">No votes have been submitted!</Header>;
-  }
+export default function Votes ({ proposal: { id, votingResults } }: VotesProps) {
+  const transport = useTransport();
+  const [votes, error, loading] = usePromise<ProposalVotes | null>(
+    () => transport.proposals.votes(id),
+    null,
+    [votingResults]
+  );
 
   return (
-    <>
-      <Header as="h3">
-        All Votes: ({votes.votes.length}/{votes.councilMembersLength})
-      </Header>
-      <Divider />
-      <Table basic="very">
-        <Table.Body>
-          {votes.votes.map((proposalVote, idx) => {
-            const { vote, member } = proposalVote;
-            const voteStr = (vote as VoteKind).type.toString() as VoteKindStr;
-            const { icon, textColor } = useVoteStyles(voteStr);
-            return (
-              <Table.Row key={`${member.handle}-${idx}`}>
-                <Table.Cell className={textColor}>
-                  <Icon name={icon} />
-                  {voteStr}
-                </Table.Cell>
-                <Table.Cell>
-                  <ProfilePreview
-                    handle={member.handle}
-                    avatar_uri={member.avatar_uri}
-                    root_account={member.root_account}
-                  />
-                </Table.Cell>
-              </Table.Row>
-            );
-          })}
-        </Table.Body>
-      </Table>
-    </>
+    <PromiseComponent
+      error={error}
+      loading={loading}
+      message="Fetching the votes...">
+      { (votes && votes.votes.length > 0)
+        ? (
+          <>
+            <Header as="h3">
+              All Votes: ({votes.votes.length}/{votes.councilMembersLength})
+            </Header>
+            <Divider />
+            <Table basic="very">
+              <Table.Body>
+                {votes.votes.map((proposalVote, idx) => {
+                  const { vote, member } = proposalVote;
+                  const voteStr = (vote as VoteKind).type.toString() as VoteKindStr;
+                  const { icon, textColor } = useVoteStyles(voteStr);
+                  return (
+                    <Table.Row key={`${member.handle}-${idx}`}>
+                      <Table.Cell className={textColor}>
+                        <Icon name={icon} />
+                        {voteStr}
+                      </Table.Cell>
+                      <Table.Cell>
+                        <ProfilePreview
+                          handle={member.handle}
+                          avatar_uri={member.avatar_uri}
+                          root_account={member.root_account}
+                        />
+                      </Table.Cell>
+                    </Table.Row>
+                  );
+                })}
+              </Table.Body>
+            </Table>
+          </>
+        )
+        : (
+          <Header as="h4">No votes have been submitted!</Header>
+        )
+      }
+    </PromiseComponent>
   );
 }

+ 1 - 1
pioneer/packages/joy-roles/src/transport.substrate.ts

@@ -548,7 +548,7 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   async blockHash (height: number): Promise<string> {
-    const blockHash = await this.cachedApi.query.system.blockHash(height);
+    const blockHash = await this.api.rpc.chain.getBlockHash(height);
     return blockHash.toString();
   }
 

+ 0 - 14
pioneer/packages/joy-utils-old/src/functions/misc.ts

@@ -1,14 +0,0 @@
-import { Bytes } from '@polkadot/types/primitive';
-
-export function includeKeys<T extends { [k: string]: any }> (obj: T, ...allowedKeys: string[]) {
-  return Object.keys(obj).filter(objKey => {
-    return allowedKeys.reduce(
-      (hasAllowed: boolean, allowedKey: string) => hasAllowed || objKey.includes(allowedKey),
-      false
-    );
-  });
-}
-
-export function bytesToString (bytes: Bytes) {
-  return Buffer.from(bytes.toString().substr(2), 'hex').toString();
-}

+ 0 - 1
pioneer/packages/joy-utils-old/src/react/components/index.tsx

@@ -1 +0,0 @@
-export { default as PromiseComponent } from './PromiseComponent';

+ 0 - 1
pioneer/packages/joy-utils-old/src/react/context/index.tsx

@@ -1 +0,0 @@
-export { TransportContext, TransportProvider } from './transport';

+ 27 - 26
pioneer/packages/joy-utils-old/src/react/hooks/proposals/useProposalSubscription.tsx

@@ -1,39 +1,41 @@
 import { useState, useEffect } from 'react';
-import { ParsedProposal, ProposalVotes } from '../../../types/proposals';
-import { useTransport, usePromise } from '../';
+import { useTransport } from '../';
 import { ProposalId } from '@joystream/types/proposals';
+import { ParsedProposal } from '@polkadot/joy-utils/types/proposals';
 
 // Take advantage of polkadot api subscriptions to re-fetch proposal data and votes
 // each time there is some runtime change in the proposal
 const useProposalSubscription = (id: ProposalId) => {
   const transport = useTransport();
-  // State holding an "unsubscribe method"
-  const [unsubscribeProposal, setUnsubscribeProposal] = useState<(() => void) | null>(null);
-
-  const [proposal, proposalError, proposalLoading, refreshProposal] = usePromise<ParsedProposal>(
-    () => transport.proposals.proposalById(id),
-    {} as ParsedProposal
-  );
-
-  const [votes, votesError, votesLoading, refreshVotes] = usePromise<ProposalVotes | null>(
-    () => transport.proposals.votes(id),
-    null
-  );
-
-  // Function to re-fetch the data using transport
-  const refreshProposalData = () => {
-    refreshProposal();
-    refreshVotes();
-  };
+  // State holding current proposal data
+  const [data, setData] = useState<ParsedProposal | null>(null);
+  const [error, setError] = useState<any>(null);
+  const [loading, setLoading] = useState<boolean>(true);
 
   useEffect(() => {
     // onMount...
     let unmounted = false;
+    let unsubscribeProposal: (() => void) | undefined;
+    const refreshProposalData = () => {
+      transport.proposals.proposalById(id)
+        .then(newData => {
+          if (!unmounted) {
+            setData(newData);
+            setLoading(false);
+          }
+        })
+        .catch(error => {
+          if (!unmounted) {
+            setError(error);
+            setLoading(false);
+          }
+        });
+    };
     // Create the subscription
     transport.proposals.subscribeProposal(id, refreshProposalData)
       .then(unsubscribe => {
         if (!unmounted) {
-          setUnsubscribeProposal(() => unsubscribe);
+          unsubscribeProposal = unsubscribe;
         } else {
           unsubscribe(); // If already unmounted - unsubscribe immedietally!
         }
@@ -42,14 +44,13 @@ const useProposalSubscription = (id: ProposalId) => {
       // onUnmount...
       // Clean the subscription
       unmounted = true;
-      if (unsubscribeProposal !== null) unsubscribeProposal();
+      if (unsubscribeProposal) {
+        unsubscribeProposal();
+      }
     };
   }, []);
 
-  return {
-    proposal: { data: proposal, error: proposalError, loading: proposalLoading },
-    votes: { data: votes, error: votesError, loading: votesLoading }
-  };
+  return { data, error, loading };
 };
 
 export default useProposalSubscription;

+ 0 - 0
pioneer/packages/joy-utils-old/README.md → pioneer/packages/joy-utils/README.md


+ 3 - 3
pioneer/packages/joy-utils-old/package.json → pioneer/packages/joy-utils/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@types/query-string": "^6.2.0",
     "@types/uuid": "^3.4.4",
     "@types/yup": "^0.26.10",

+ 0 - 0
pioneer/packages/joy-utils-old/src/functions/date.ts → pioneer/packages/joy-utils/src/functions/date.ts


+ 2 - 2
pioneer/packages/joy-utils-old/src/functions/format.ts → pioneer/packages/joy-utils/src/functions/format.ts

@@ -16,7 +16,7 @@ export const formatReward = (
     : next_payment_at_block;
 
   return (
-    `${formatBalance(amount)}${interval.isSome ? ` / ${interval.unwrap()} block(s)` : ''}` +
-    ((showNextPaymentBlock && nextPaymentBlock) ? ` (Next payment: #${nextPaymentBlock})` : '')
+    `${formatBalance(amount)}${interval.isSome ? ` / ${interval.unwrap().toString()} block(s)` : ''}` +
+    ((showNextPaymentBlock && nextPaymentBlock) ? ` (Next payment: #${nextPaymentBlock.toString()})` : '')
   );
 };

+ 167 - 0
pioneer/packages/joy-utils/src/functions/misc.ts

@@ -0,0 +1,167 @@
+import { Bytes } from '@polkadot/types/primitive';
+import BN from 'bn.js';
+import keyring from '@polkadot/ui-keyring';
+import { ElectionStake, Backer } from '@joystream/types/council';
+import { Options as QueryOptions } from '@polkadot/react-api/hoc/types';
+import queryString from 'query-string';
+import { SubmittableResult } from '@polkadot/api';
+import { Codec } from '@polkadot/types/types';
+
+export const ZERO = new BN(0);
+
+export function bnToStr (bn?: BN, dflt = ''): string {
+  return bn ? bn.toString() : dflt;
+}
+
+export const notDefined = (x: any): boolean =>
+  x === null || typeof x === 'undefined';
+
+export const isDefined = (x: any): boolean =>
+  !notDefined(x);
+
+export const isDef = isDefined;
+
+export const notDef = notDefined;
+
+export const isObj = (x: any): boolean =>
+  x !== null && typeof x === 'object';
+
+export const isStr = (x: any): boolean =>
+  typeof x === 'string';
+
+export const isNum = (x: any): boolean =>
+  typeof x === 'number';
+
+export const isEmptyStr = (x: any): boolean =>
+  notDefined(x) || (isStr(x) && (x as string).trim().length === 0);
+
+export const nonEmptyStr = (x?: any) =>
+  isStr(x) && (x as string).trim().length > 0;
+
+export const parseNumStr = (num: string): number | undefined => {
+  try {
+    return parseInt(num, undefined);
+  } catch (err) {
+    return undefined;
+  }
+};
+
+export const nonEmptyArr = (x: any): boolean =>
+  Array.isArray(x) && x.length > 0;
+
+export const isEmptyArr = (x: any): boolean =>
+  !nonEmptyArr(x);
+
+export function findNameByAddress (address: string): string | undefined {
+  let keyring_address;
+
+  try {
+    keyring_address = keyring.getAccount(address);
+  } catch (error) {
+    try {
+      keyring_address = keyring.getAddress(address);
+    } catch (error) {
+      // do nothing
+    }
+  }
+
+  return keyring_address ? keyring_address.meta.name : undefined;
+}
+
+export function isKnownAddress (address: string): boolean {
+  return isDefined(findNameByAddress(address));
+}
+
+export function calcTotalStake (stakes: ElectionStake | ElectionStake[] | undefined): BN {
+  if (typeof stakes === 'undefined') {
+    return ZERO;
+  }
+
+  const total = (stake: ElectionStake) => stake.new.add(stake.transferred);
+
+  try {
+    if (Array.isArray(stakes)) {
+      return stakes.reduce((accum, stake) => {
+        return accum.add(total(stake));
+      }, ZERO);
+    } else {
+      return total(stakes);
+    }
+  } catch (err) {
+    console.log('Failed to calculate a total stake', stakes, err);
+
+    return ZERO;
+  }
+}
+
+export function calcBackersStake (backers: Backer[]): BN {
+  return backers.map((b) => b.stake).reduce((accum, stake) => {
+    return accum.add(stake);
+  }, ZERO);
+}
+
+/** Example of apiQuery: 'query.councilElection.round' */
+export function queryToProp (
+  apiQuery: string,
+  paramNameOrOpts?: string | QueryOptions
+): [string, QueryOptions] {
+  let paramName: string | undefined;
+  let propName: string | undefined;
+
+  if (typeof paramNameOrOpts === 'string') {
+    paramName = paramNameOrOpts;
+  } else if (paramNameOrOpts) {
+    paramName = paramNameOrOpts.paramName;
+    propName = paramNameOrOpts.propName;
+  }
+
+  // If prop name is still undefined, derive it from the name of storage item:
+  if (!propName) {
+    propName = apiQuery.split('.').slice(-1)[0];
+  }
+
+  return [apiQuery, { paramName, propName }];
+}
+
+export function getUrlParam (location: Location, paramName: string, deflt: string | null = null): string | null {
+  const params = queryString.parse(location.search);
+
+  return params[paramName] ? params[paramName] as string : deflt;
+}
+
+export function filterSubstrateEventsAndExtractData (txResult: SubmittableResult, eventName: string): Codec[][] {
+  const res: Codec[][] = [];
+
+  txResult.events.forEach((event) => {
+    const { event: { method, data } } = event;
+
+    if (method === eventName) {
+      res.push(data.toArray());
+    }
+  });
+
+  return res;
+}
+
+export function findFirstParamOfSubstrateEvent<T extends Codec> (txResult: SubmittableResult, eventName: string): T | undefined {
+  const data = filterSubstrateEventsAndExtractData(txResult, eventName);
+
+  if (data && data.length) {
+    return data[0][0] as T;
+  }
+
+  return undefined;
+}
+
+export function includeKeys<T extends { [k: string]: any }> (obj: T, ...allowedKeys: string[]) {
+  return Object.keys(obj).filter((objKey) => {
+    return allowedKeys.reduce(
+      (hasAllowed: boolean, allowedKey: string) => hasAllowed || objKey.includes(allowedKey),
+      false
+    );
+  });
+}
+
+export function bytesToString (bytes: Bytes) {
+  return Buffer.from(bytes.toString().substr(2), 'hex').toString();
+}

+ 1 - 1
pioneer/packages/joy-utils-old/src/FlexCenter.tsx → pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
 
-export function FlexCenter (props: React.PropsWithChildren<{}>) {
+export function FlexCenter (props: React.PropsWithChildren<unknown>) {
   return <div className='FlexCenter'>{props.children}</div>;
 }

+ 4 - 1
pioneer/packages/joy-utils-old/src/MutedText.tsx → pioneer/packages/joy-utils/src/react/components/MutedText.tsx

@@ -8,15 +8,18 @@ type Props = React.PropsWithChildren<{
 
 function getClassNames (props: Props): string {
   const { smaller = false, className } = props;
-  return `grey text ${smaller ? 'smaller' : ''} ${className}`;
+
+  return `grey text ${smaller ? 'smaller' : ''} ${className || ''}`;
 }
 
 export const MutedSpan = (props: Props) => {
   const { style, children } = props;
+
   return <span className={getClassNames(props)} style={style}>{children}</span>;
 };
 
 export const MutedDiv = (props: Props) => {
   const { style, children } = props;
+
   return <div className={getClassNames(props)} style={style}>{children}</div>;
 };

+ 3 - 0
pioneer/packages/joy-utils-old/src/Section.tsx → pioneer/packages/joy-utils/src/react/components/Section.tsx

@@ -46,6 +46,7 @@ type Props = BareProps & {
 export default class Section extends React.PureComponent<Props> {
   render () {
     let { className, children, pagination } = this.props;
+
     className = (className || '') + ' JoySection';
 
     return (
@@ -62,10 +63,12 @@ export default class Section extends React.PureComponent<Props> {
 
   private renderTitle = () => {
     const { title, level = 2, pagination } = this.props;
+
     if (!title) return null;
 
     const className = 'JoySection-title';
     const style = pagination ? { margin: '0' } : {};
+
     return React.createElement(
       `h${level}`,
       { className, style },

+ 46 - 50
pioneer/packages/joy-utils-old/src/TxButton.tsx → pioneer/packages/joy-utils/src/react/components/TxButton.tsx

@@ -5,11 +5,8 @@ import { Button } from '@polkadot/react-components/index';
 import { QueueConsumer } from '@polkadot/react-components/Status/Context';
 import { withApi } from '@polkadot/react-api/index';
 import { assert } from '@polkadot/util';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
-import { useTransportContext } from '@polkadot/joy-media/TransportContext';
-import { MockTransport } from '@polkadot/joy-media/transport.mock';
-import { Button$Sizes } from '@polkadot/react-components/Button/types';
-import { SemanticShorthandItem, IconProps } from 'semantic-ui-react';
+import { withMyAccount, MyAccountProps } from '../hocs/accounts';
+import { IconName } from '@fortawesome/fontawesome-svg-core';
 
 type InjectedProps = {
   queueExtrinsic: QueueTxExtrinsicAdd;
@@ -20,9 +17,7 @@ export type OnTxButtonClick = (sendTx: () => void) => void;
 type BasicButtonProps = {
   accountId?: string;
   type?: 'submit' | 'button';
-  size?: Button$Sizes;
   isBasic?: boolean;
-  isPrimary?: boolean;
   isDisabled?: boolean;
   label?: React.ReactNode;
   params: Array<any>;
@@ -32,7 +27,7 @@ type BasicButtonProps = {
   style?: Record<string, string | number>;
   children?: React.ReactNode;
   compact?: boolean;
-  icon?: boolean | SemanticShorthandItem<IconProps>;
+  icon?: IconName;
 
   onClick?: OnTxButtonClick;
   txFailedCb?: TxFailedCallback;
@@ -45,15 +40,14 @@ type PropsWithApi = BareProps & ApiProps & MyAccountProps & PartialQueueTxExtrin
 
 class TxButtonInner extends React.PureComponent<PropsWithApi & InjectedProps> {
   render () {
-    const { myAddress, accountId, isPrimary = true, isDisabled, icon = '', onClick } = this.props;
+    const { myAddress, accountId, isDisabled, icon = 'check', onClick } = this.props;
     const origin = accountId || myAddress;
 
     return (
       <Button
         {...this.props}
         isDisabled={isDisabled || !origin}
-        isPrimary={isPrimary}
-        icon={icon as string}
+        icon={icon}
         onClick={() => {
           if (onClick) onClick(this.send);
           else this.send();
@@ -74,7 +68,7 @@ class TxButtonInner extends React.PureComponent<PropsWithApi & InjectedProps> {
 
     queueExtrinsic({
       accountId: origin,
-      extrinsic: api.tx[section][method](...params) as any, // ???
+      extrinsic: api.tx[section][method](...params),
       txFailedCb,
       txSuccessCb,
       txStartCb,
@@ -98,41 +92,43 @@ class TxButton extends React.PureComponent<PropsWithApi> {
   }
 }
 
-const SubstrateTxButton = withApi(withMyAccount(TxButton));
-
-const mockSendTx = () => {
-  const msg = 'Cannot send a Substrate tx in a mock mode';
-  if (typeof window !== 'undefined') {
-    window.alert(`WARN: ${msg}`);
-  } else if (typeof console.warn === 'function') {
-    console.warn(msg);
-  } else {
-    console.log(`WARN: ${msg}`);
-  }
-};
-
-function MockTxButton (props: BasicButtonProps) {
-  const { isPrimary = true, icon = '', onClick } = props;
-
-  return (
-    <Button
-      {...props}
-      isPrimary={isPrimary}
-      icon={icon as string}
-      onClick={() => {
-        if (onClick) onClick(mockSendTx);
-        else mockSendTx();
-      }}
-    />
-  );
-}
-
-function ResolvedButton (props: BasicButtonProps) {
-  const isMock = useTransportContext() instanceof MockTransport;
-
-  return isMock
-    ? <MockTxButton {...props} />
-    : <SubstrateTxButton {...props} />;
-}
-
-export default ResolvedButton;
+export default withApi(withMyAccount(TxButton));
+
+// const SubstrateTxButton = withApi(withMyAccount(TxButton));
+
+// const mockSendTx = () => {
+//   const msg = 'Cannot send a Substrate tx in a mock mode';
+//   if (typeof window !== 'undefined') {
+//     window.alert(`WARN: ${msg}`);
+//   } else if (typeof console.warn === 'function') {
+//     console.warn(msg);
+//   } else {
+//     console.log(`WARN: ${msg}`);
+//   }
+// };
+
+// function MockTxButton (props: BasicButtonProps) {
+//   const { isPrimary = true, icon = '', onClick } = props;
+
+//   return (
+//     <Button
+//       {...props}
+//       isPrimary={isPrimary}
+//       icon={icon as string}
+//       onClick={() => {
+//         if (onClick) onClick(mockSendTx);
+//         else mockSendTx();
+//       }}
+//     />
+//   );
+// }
+
+// function ResolvedButton (props: BasicButtonProps) {
+//   const isMock = useTransportContext() instanceof MockTransport;
+
+//   return isMock
+//     ? <MockTxButton {...props} />
+//     : <SubstrateTxButton {...props} />;
+// }
+
+// export default ResolvedButton;

+ 5 - 5
pioneer/packages/joy-utils-old/src/forms.tsx → pioneer/packages/joy-utils/src/react/components/forms.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { Field, FormikErrors, FormikTouched } from 'formik';
-
-import { nonEmptyStr } from '@polkadot/joy-utils/index';
+import { nonEmptyStr } from '../../functions/misc';
 import { Popup, Icon } from 'semantic-ui-react';
 
 export type LabelledProps<FormValues> = {
@@ -38,9 +37,9 @@ export function LabelledField<FormValues> () {
       const renderLabel = () =>
         nonEmptyStr(label)
           ? <>
-              {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
-              {label}
-            </>
+            {required && <b style={{ color: 'red' }} title='This field is required'>* </b>}
+            {label}
+          </>
           : null;
 
       return (label || invisibleLabel)
@@ -57,6 +56,7 @@ export function LabelledField<FormValues> () {
           {fieldWithError}
         </div>;
     };
+
   return LabelledFieldInner;
 }
 

+ 4 - 0
pioneer/packages/joy-utils/src/react/components/index.tsx

@@ -0,0 +1,4 @@
+export { default as Section } from './Section';
+export { default as TxButton } from './TxButton';
+export { MutedSpan, MutedDiv } from './MutedText';
+export { FlexCenter } from './FlexCenter';

+ 30 - 10
pioneer/packages/joy-utils-old/src/MyAccountContext.tsx → pioneer/packages/joy-utils/src/react/context/account.tsx

@@ -1,11 +1,14 @@
-import React, { useReducer, createContext, useContext, useEffect } from 'react';
+import React, { useReducer, createContext, useEffect } from 'react';
 import store from 'store';
 
-export const MY_ADDRESS = 'joy.myAddress';
+export const ACCOUNT_CHANGED_EVENT_NAME = 'account-changed';
+export const MY_ADDRESS_STORAGE_KEY = 'joy.myAddress';
 
 function readMyAddress (): string | undefined {
-  const myAddress: string | undefined = store.get(MY_ADDRESS);
+  const myAddress = store.get(MY_ADDRESS_STORAGE_KEY) as string | undefined;
+
   console.log('Read my address from the local storage:', myAddress);
+
   return myAddress;
 }
 
@@ -22,7 +25,8 @@ type MyAccountAction = {
 function reducer (state: MyAccountState, action: MyAccountAction): MyAccountState {
   function forget () {
     console.log('Forget my address');
-    store.remove(MY_ADDRESS);
+    store.remove(MY_ADDRESS_STORAGE_KEY);
+
     return { ...state, address: undefined };
   }
 
@@ -32,29 +36,35 @@ function reducer (state: MyAccountState, action: MyAccountAction): MyAccountStat
     case 'reload': {
       address = readMyAddress();
       console.log('Reload my address:', address);
+
       return { ...state, address, inited: true };
     }
 
     case 'set': {
       address = action.address;
+
       if (address !== state.address) {
         if (address) {
           console.log('Set my new address:', address);
-          store.set(MY_ADDRESS, address);
+          store.set(MY_ADDRESS_STORAGE_KEY, address);
+
           return { ...state, address, inited: true };
         } else {
           return forget();
         }
       }
+
       return state;
     }
 
     case 'forget': {
       address = action.address;
       const isMyAddress = address && address === readMyAddress();
+
       if (!address || isMyAddress) {
         return forget();
       }
+
       return state;
     }
 
@@ -88,9 +98,23 @@ const contextStub: MyAccountContextProps = {
 
 export const MyAccountContext = createContext<MyAccountContextProps>(contextStub);
 
-export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
+export function MyAccountProvider (props: React.PropsWithChildren<unknown>) {
   const [state, dispatch] = useReducer(reducer, initialState);
 
+  const handleAccountChangeEvent = (e: Event) => {
+    const { detail: address } = e as CustomEvent<string>;
+
+    dispatch({ type: 'set', address });
+  };
+
+  useEffect(() => {
+    window.addEventListener(ACCOUNT_CHANGED_EVENT_NAME, handleAccountChangeEvent);
+
+    return () => {
+      window.removeEventListener(ACCOUNT_CHANGED_EVENT_NAME, handleAccountChangeEvent);
+    };
+  });
+
   useEffect(() => {
     if (!state.inited) {
       dispatch({ type: 'reload' });
@@ -110,7 +134,3 @@ export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
     </MyAccountContext.Provider>
   );
 }
-
-export function useMyAccount () {
-  return useContext(MyAccountContext);
-}

+ 2 - 0
pioneer/packages/joy-utils/src/react/context/index.tsx

@@ -0,0 +1,2 @@
+export { MyAccountContext, MyAccountProvider } from './account';
+export { MyMembershipContext, MyMembershipProvider } from './membership';

+ 2 - 6
pioneer/packages/joy-utils-old/src/MyMembershipContext.tsx → pioneer/packages/joy-utils/src/react/context/membership.tsx

@@ -1,5 +1,5 @@
-import React, { createContext, useContext } from 'react';
-import { MyAccountProps, withMyAccount } from './MyAccount';
+import React, { createContext } from 'react';
+import { MyAccountProps, withMyAccount } from '../hocs/accounts';
 
 export const MyMembershipContext = createContext<MyAccountProps>({});
 
@@ -12,7 +12,3 @@ function InnerMyMembershipProvider (props: React.PropsWithChildren<MyAccountProp
 }
 
 export const MyMembershipProvider = withMyAccount(InnerMyMembershipProvider);
-
-export function useMyMembership () {
-  return useContext(MyMembershipContext);
-}

+ 0 - 0
pioneer/packages/joy-utils-old/src/react/helpers/index.ts → pioneer/packages/joy-utils/src/react/helpers/index.ts


+ 124 - 0
pioneer/packages/joy-utils/src/react/hocs/accounts.tsx

@@ -0,0 +1,124 @@
+import React, { useContext } from 'react';
+
+import { AccountId } from '@polkadot/types/interfaces';
+import { Vec, Option } from '@polkadot/types';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { withCalls, withMulti, withObservable, ApiContext } from '@polkadot/react-api/index';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import { MemberId, Membership } from '@joystream/types/members';
+import { LeadId } from '@joystream/types/content-working-group';
+
+import { queryMembershipToProp } from '@polkadot/joy-members/utils';
+import useMyAccount from '../hooks/useMyAccount';
+import { componentName } from '../helpers';
+
+export type MyAddressProps = {
+  myAddress?: string;
+};
+
+export type MyAccountProps = MyAddressProps & {
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  myMembership?: Membership | null;
+
+  // Content Working Group
+  curatorEntries?: any; // entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
+
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
+};
+
+function withMyAddress<P extends MyAccountProps> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const {
+      state: { address }
+    } = useMyAccount();
+    const { api } = useContext(ApiContext);
+    const myAccountId = (address && api.isReady)
+      ? api.createType('AccountId', address)
+      : undefined;
+
+    return <Component myAddress={address} myAccountId={myAccountId} {...props} />;
+  };
+
+  ResultComponent.displayName = `withMyAddress(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+const withMyMemberIds = withCalls<MyAccountProps>(
+  queryMembershipToProp('memberIdsByRootAccountId', 'myAddress'),
+  queryMembershipToProp('memberIdsByControllerAccountId', 'myAddress')
+);
+
+function withMyMembership<P extends MyAccountProps> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { memberIdsByRootAccountId, memberIdsByControllerAccountId } = props;
+
+    const myMemberIdChecked = memberIdsByRootAccountId && memberIdsByControllerAccountId;
+
+    let myMemberId: MemberId | undefined;
+
+    if (memberIdsByRootAccountId && memberIdsByControllerAccountId) {
+      const [memberIdByAccount] = memberIdsByRootAccountId.toArray().concat(memberIdsByControllerAccountId.toArray());
+
+      myMemberId = memberIdByAccount;
+    }
+
+    const iAmMember = myMemberId !== undefined;
+
+    const newProps = {
+      myMemberIdChecked,
+      myMemberId,
+      iAmMember
+    };
+
+    return <Component {...props} {...newProps} />;
+  };
+
+  ResultComponent.displayName = `withMyMembership(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+function resolveMyProfile<P extends { myMembership?: Membership | null }> (Component: React.ComponentType<P>) {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    let { myMembership } = props;
+
+    myMembership = (!myMembership || myMembership.handle.isEmpty) ? null : myMembership;
+
+    return <Component {...props} myMembership={ myMembership } />;
+  };
+
+  ResultComponent.displayName = `resolveMyProfile(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+const withMyProfileCall = withCalls<MyAccountProps>(queryMembershipToProp('membershipById', {
+  paramName: 'myMemberId',
+  propName: 'myMembership'
+}));
+
+const withMyProfile = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(Component, withMyProfileCall, resolveMyProfile);
+
+export const withMyAccount = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(
+    Component,
+    withObservable(accountObservable.subject, { propName: 'allAccounts' }),
+    withMyAddress,
+    withMyMemberIds,
+    withMyMembership,
+    withMyProfile
+    // withContentWorkingGroup,
+    // withCurationActor
+  );

+ 104 - 0
pioneer/packages/joy-utils/src/react/hocs/guards.tsx

@@ -0,0 +1,104 @@
+import React from 'react';
+import { Message } from 'semantic-ui-react';
+import { Link } from 'react-router-dom';
+
+import { AccountId } from '@polkadot/types/interfaces';
+import { Vec, Option } from '@polkadot/types';
+import { withMulti } from '@polkadot/react-api/index';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import { MemberId, Membership } from '@joystream/types/members';
+import { LeadId } from '@joystream/types/content-working-group';
+import { useMyMembership } from '../hooks';
+import { componentName } from '../helpers';
+import { withMyAccount } from './accounts';
+
+export type MyAddressProps = {
+  myAddress?: string;
+};
+
+export type MyAccountProps = MyAddressProps & {
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  myMembership?: Membership | null;
+
+  // Content Working Group
+  curatorEntries?: any; // entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
+
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
+};
+
+export function MembershipRequired<P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { myMemberIdChecked, iAmMember } = useMyMembership();
+
+    if (!myMemberIdChecked) {
+      return <em>Loading...</em>;
+    } else if (iAmMember) {
+      return <Component {...props} />;
+    }
+
+    return (
+      <Message warning className='JoyMainStatus'>
+        <Message.Header>Only members can access this functionality.</Message.Header>
+        <div style={{ marginTop: '1rem' }}>
+          <Link to={'/members/edit'} className='ui button orange'>
+            Register here
+          </Link>
+          <span style={{ margin: '0 .5rem' }}> or </span>
+          <Link to={'/accounts'} className='ui button'>
+            Change key
+          </Link>
+        </div>
+      </Message>
+    );
+  };
+
+  ResultComponent.displayName = `MembershipRequired(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+export function AccountRequired<P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> {
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    const { allAccounts } = useMyMembership();
+
+    if (allAccounts && !Object.keys(allAccounts).length) {
+      return (
+        <Message warning className='JoyMainStatus'>
+          <Message.Header>Please create a key to get started.</Message.Header>
+          <div style={{ marginTop: '1rem' }}>
+            <Link to={'/accounts'} className='ui button orange'>
+              Create key
+            </Link>
+          </div>
+        </Message>
+      );
+    }
+
+    return <Component {...props} />;
+  };
+
+  ResultComponent.displayName = `AccountRequired(${componentName(Component)})`;
+
+  return ResultComponent;
+}
+
+// TODO: We could probably use withAccountRequired, which wouldn't pass any addiotional props, just like withMembershipRequired.
+// Just need to make sure those passed props are not used in the extended components (they probably aren't).
+export const withOnlyAccounts = <P extends MyAccountProps>(Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, withMyAccount, AccountRequired);
+
+export const withMembershipRequired = <P extends Record<string, unknown>> (Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, AccountRequired, MembershipRequired);
+
+export const withOnlyMembers = <P extends MyAccountProps>(Component: React.ComponentType<P>): React.ComponentType<P> =>
+  withMulti(Component, withMyAccount, withMembershipRequired);

+ 2 - 0
pioneer/packages/joy-utils/src/react/hooks/index.ts

@@ -0,0 +1,2 @@
+export { default as useMyAccount } from './useMyAccount';
+export { default as useMyMembership } from './useMyMembership';

+ 6 - 0
pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx

@@ -0,0 +1,6 @@
+import { useContext } from 'react';
+import { MyAccountContext } from '../context/account';
+
+export default function useMyAccount () {
+  return useContext(MyAccountContext);
+}

+ 6 - 0
pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx

@@ -0,0 +1,6 @@
+import { useContext } from 'react';
+import { MyMembershipContext } from '../context';
+
+export default function useMyMembership () {
+  return useContext(MyMembershipContext);
+}

+ 0 - 28
pioneer/packages/old-apps/apps-routing/src/joy-pages.ts

@@ -1,28 +0,0 @@
-import { Routes } from './types';
-
-import { ToS, Privacy } from '@polkadot/joy-pages/index';
-
-export default ([
-  {
-    Component: ToS,
-    display: {
-      isHidden: true
-    },
-    i18n: {
-      defaultValue: 'Terms of Service'
-    },
-    icon: 'file outline',
-    name: 'pages/tos'
-  },
-  {
-    Component: Privacy,
-    display: {
-      isHidden: true
-    },
-    i18n: {
-      defaultValue: 'Privacy Policy'
-    },
-    icon: 'file outline',
-    name: 'pages/privacy'
-  }
-] as Routes);

+ 0 - 17
pioneer/packages/old-apps/apps/src/TopBar.css

@@ -1,17 +0,0 @@
-.JoyTopBar {
-  padding: .5rem .5rem;
-  background-color: #3f3f3f;
-  border-bottom: 1px solid #d4d4d5;
-  text-align: right;
-  margin: 0 -2rem;
-
-  &.NoMyAddress {
-    background-color: #ffeb83;
-    color: #000;
-    text-align: center;
-  }
-
-  .ui--InputAddress {
-    display: inline-block;
-  }
-}

+ 0 - 47
pioneer/packages/old-apps/apps/src/TopBar.tsx

@@ -1,47 +0,0 @@
-import React from 'react';
-// import { Link } from 'react-router-dom';
-import { I18nProps } from '@polkadot/react-components/types';
-import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
-import { InputAddress } from '@polkadot/react-components';
-import { Available } from '@polkadot/react-query';
-import translate from './translate';
-import './TopBar.css';
-
-type Props = I18nProps & {};
-
-function renderAddress (address: string) {
-  const balance = <span className="label">Balance: </span>;
-
-  return (
-    <div className="JoyTopBar">
-      <InputAddress
-        defaultValue={address}
-        help="My current key that signs transactions"
-        label="My key"
-        labelExtra={<Available label={balance} params={address} />}
-        type="account"
-      />
-    </div>
-  );
-}
-
-// function renderNoAddress() {
-//   return (
-//     <div className="JoyTopBar NoMyAddress">
-//       <i className="warning sign icon"></i>
-//       <span style={{ marginRight: '1rem' }}>You need to create a key if you want to use all features.</span>
-//       <Link className="ui small button orange" to="/accounts">
-//         Create key
-//       </Link>
-//     </div>
-//   );
-// }
-
-function Component (_props: Props) {
-  const {
-    state: { address }
-  } = useMyAccount();
-  return address ? renderAddress(address) : null;
-}
-
-export default translate(Component);

+ 0 - 11
pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused

@@ -1,11 +0,0 @@
-/* Copyright 2017-2019 @polkadot/react-components authors & contributors
-/* This software may be modified and distributed under the terms
-/* of the Apache-2.0 license. See the LICENSE file for details. */
-
-import theme from 'styled-theming';
-
-export const primaryColor = theme('theme', {
-  substrate: '#DB2828',
-  polkadot: '#E6007A',
-  default: 'darkorange'
-});

+ 1 - 1
pioneer/packages/page-staking/src/Targets/Summary.tsx

@@ -66,7 +66,7 @@ function Summary ({ lastReward, numNominators, numValidators, totalStaked }: Pro
           </CardSummary>
         )}
       </section>
-      {numValidators && numNominators && (
+      {numValidators !== undefined && numNominators !== undefined && (
         <CardSummary label={`${t<string>('validators')} / ${t<string>('nominators')}`}>
           {numValidators}&nbsp;/&nbsp;{numNominators}
         </CardSummary>

+ 8 - 0
pioneer/packages/react-components/src/InputAddress/index.tsx

@@ -20,6 +20,8 @@ import Dropdown from '../Dropdown';
 import createHeader from './createHeader';
 import createItem from './createItem';
 
+import { ACCOUNT_CHANGED_EVENT_NAME } from '@polkadot/joy-utils/react/context/account';
+
 interface Props {
   className?: string;
   defaultValue?: Uint8Array | string | null;
@@ -115,6 +117,12 @@ function setLastValue (type: KeyringOption$Type = DEFAULT_TYPE, value: string):
 
   options.defaults[type] = value;
   store.set(STORAGE_KEY, options);
+
+  if (type === 'account') {
+    // This lets us update joy-utils account context in order to always be in sync
+    // with options:InputAddress: { defaults: { account } }) from local storage
+    window.dispatchEvent(new CustomEvent<string>(ACCOUNT_CHANGED_EVENT_NAME, { detail: value }));
+  }
 }
 
 class InputAddress extends React.PureComponent<Props, State> {

+ 5 - 2
pioneer/packages/react-components/src/Tabs/Tab.tsx

@@ -16,16 +16,19 @@ interface Props extends TabItem {
   index: number;
   isSequence?: boolean;
   num: number;
+  forceMatchParams?: boolean;
 }
 
-function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text }: Props): React.ReactElement<Props> {
+function Tab ({ basePath, className = '', hasParams, index, isExact, isRoot, isSequence, name, num, text, forceMatchParams }: Props): React.ReactElement<Props> {
   const to = isRoot
     ? basePath
     : `${basePath}/${name}`;
 
   // only do exact matching when not the fallback (first position tab),
   // params are problematic for dynamic hidden such as app-accounts
-  const tabIsExact = isExact || !hasParams || (!isSequence && index === 0);
+  const tabIsExact = forceMatchParams
+    ? false
+    : (isExact || !hasParams || (!isSequence && index === 0));
 
   return (
     <NavLink

+ 1 - 0
pioneer/packages/react-components/src/Tabs/types.ts

@@ -11,4 +11,5 @@ export interface TabItem {
   isRoot?: boolean;
   name: string;
   text: React.ReactNode;
+  forceMatchParams?: boolean;
 }

+ 3 - 1
pioneer/packages/react-components/src/styles/index.ts

@@ -10,12 +10,13 @@ import cssMedia from './media';
 import cssRx from './rx';
 import cssSemantic from './semantic';
 import cssTheme from './theme';
+import cssJoystream from './joystream';
 
 interface Props {
   uiHighlight?: string;
 }
 
-const defaultHighlight = '#f19135'; // #999
+const defaultHighlight = '#4038FF'; // #999
 
 const getHighlight = (props: Props): string =>
   (props.uiHighlight || defaultHighlight);
@@ -281,4 +282,5 @@ export default createGlobalStyle<Props>`
   ${cssMedia}
   ${cssRx}
   ${cssComponents}
+  ${cssJoystream}
 `;

+ 16 - 8
pioneer/packages/old-apps/react-components/src/styles/joystream.ts → pioneer/packages/react-components/src/styles/joystream.ts

@@ -94,14 +94,6 @@ export default css`
     }
   }
 
-  .apps--SideBar-logo {
-    max-height: 26px !important;
-    margin: 1rem 1.5rem 2.5rem 0.75rem !important;
-  }
-  .collapsed .apps--SideBar-logo {
-    margin: 1rem 0.75rem 2.5rem 0.5rem !important;
-  }
-
   .JoyForm {
     margin-bottom: 1.5rem;
 
@@ -192,4 +184,20 @@ export default css`
   .text-blue {
     color: #3b83c0;
   }
+
+  /* Overrides */
+  .ui--IdentityIcon {
+    border: none !important;
+  }
+  /* Normalize SideBar icons width */
+  .apps--SideBar-Item-NavLink svg {
+    width: 20px !important;
+  }
+  /* Fix "collapsed" sidebar on mobile */
+  .apps--Wrapper:not(.menu-open) .apps--SideBar-Scroll {
+    padding: 0 !important;
+  }
+  h1 {
+    text-transform: none;
+  }
 `;

+ 1 - 1
pioneer/packages/react-components/src/styles/theme.ts

@@ -10,7 +10,7 @@ export const colorBtnDefault = '#767778';
 export const colorBtnShadow = '#98999a';
 
 /* highlighted buttons, orange */
-export const colorBtnHighlight = '#f19135';
+export const colorBtnHighlight = '#4038FF';
 
 /* primary buttons, blue */
 export const colorBtnPrimary = colorBtnDefault; // '#2e86ab';

+ 7 - 9
pioneer/tsconfig.json

@@ -4,16 +4,13 @@
     "build/**/*",
     "**/build/**/*",
     "packages/old-apps/**",
-    "packages/joy-members/**/*",
     "packages/joy-election/**/*",
     "packages/joy-forum/**/*",
     "packages/joy-help/**/*",
     "packages/joy-media/**/*",
-    "packages/joy-pages/**/*",
     "packages/joy-proposals/**/*",
     "packages/joy-roles/**/*",
     "packages/joy-settings/**/*",
-    "packages/joy-utils/**/*",
     "packages/joy-utils-old/**/*"
   ],
   "compilerOptions": {
@@ -23,6 +20,7 @@
     "resolveJsonModule": true,
     "baseUrl": ".",
     "paths": {
+      "@polkadot/types/augment": [ "../types/src/definitions/augment-types.ts" ],
       // "@joystream/types/": [ "../types/src/" ],
       // "@joystream/types/*": [ "../types/src/*" ],
       // "@polkadot/joy-election/": [ "packages/joy-election/src/" ],
@@ -33,18 +31,18 @@
       // "@polkadot/joy-help/*": [ "packages/joy-help/src/*" ],
       // "@polkadot/joy-media/": [ "packages/joy-media/src/" ],
       // "@polkadot/joy-media/*": [ "packages/joy-media/src/*" ],
-      // "@polkadot/joy-members/": [ "packages/joy-members/src/" ],
-      // "@polkadot/joy-members/*": [ "packages/joy-members/src/*" ],
-      // "@polkadot/joy-pages/": [ "packages/joy-pages/src/" ],
-      // "@polkadot/joy-pages/*": [ "packages/joy-pages/src/*" ],
+      "@polkadot/joy-members/": [ "packages/joy-members/src/" ],
+      "@polkadot/joy-members/*": [ "packages/joy-members/src/*" ],
+      "@polkadot/joy-pages/": [ "packages/joy-pages/src/" ],
+      "@polkadot/joy-pages/*": [ "packages/joy-pages/src/*" ],
       // "@polkadot/joy-proposals/": [ "packages/joy-proposals/src/" ],
       // "@polkadot/joy-proposals/*": [ "packages/joy-proposals/src/*" ],
       // "@polkadot/joy-roles/": [ "packages/joy-roles/src/" ],
       // "@polkadot/joy-roles/*": [ "packages/joy-roles/src/*" ],
       // "@polkadot/joy-settings/": [ "packages/joy-settings/src/" ],
       // "@polkadot/joy-settings/*": [ "packages/joy-settings/src/*" ],
-      // "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
-      // "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
+      "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
+      "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
       "@polkadot/apps/*": ["packages/apps/src/*"],
       "@polkadot/apps": ["packages/apps/src"],
       "@polkadot/apps-config/*": [ "packages/apps-config/src/*" ],

+ 23 - 0
types/src/common.ts

@@ -71,6 +71,26 @@ export const WorkingGroupDef = {
 export type WorkingGroupKey = keyof typeof WorkingGroupDef
 export class WorkingGroup extends JoyEnum(WorkingGroupDef) {}
 
+// Temporarly in "common", because used both by /working-group and /content-working-group:
+export type ISlashableTerms = {
+  max_count: u16
+  max_percent_pts_per_time: u16
+}
+
+export class SlashableTerms
+  extends JoyStructDecorated({
+    max_count: u16,
+    max_percent_pts_per_time: u16,
+  })
+  implements ISlashableTerms {}
+
+export class UnslashableTerms extends Null {}
+
+export class SlashingTerms extends JoyEnum({
+  Unslashable: UnslashableTerms,
+  Slashable: SlashableTerms,
+} as const) {}
+
 export const commonTypes: RegistryTypes = {
   Credential,
   CredentialSet,
@@ -79,6 +99,9 @@ export const commonTypes: RegistryTypes = {
   PostId,
   InputValidationLengthConstraint,
   WorkingGroup,
+  // Expose in registry for api.createType purposes:
+  SlashingTerms,
+  SlashableTerms,
 }
 
 export default commonTypes

+ 9 - 18
types/src/content-working-group/index.ts

@@ -1,6 +1,6 @@
-import { BTreeMap, BTreeSet, bool, u32, Text, Null, Option, Vec, u16 } from '@polkadot/types'
+import { BTreeMap, BTreeSet, bool, u32, Text, Null, Option, Vec } from '@polkadot/types'
 import { BlockNumber } from '@polkadot/types/interfaces'
-import { OptionText, Credential, JoyEnum, JoyStructDecorated } from '../common'
+import { OptionText, Credential, JoyEnum, JoyStructDecorated, SlashingTerms } from '../common'
 import { ActorId, MemberId } from '../members'
 import { StakeId } from '../stake'
 import { OpeningId, ApplicationId, ApplicationRationingPolicy, StakingPolicy } from '../hiring/index'
@@ -203,22 +203,6 @@ export class CuratorApplication
   }
 }
 
-export type ISlashableTerms = {
-  max_count: u16
-  max_percent_pts_per_time: u16
-}
-export class SlashableTerms
-  extends JoyStructDecorated({
-    max_count: u16,
-    max_percent_pts_per_time: u16,
-  })
-  implements ISlashableTerms {}
-
-export class SlashingTerms extends JoyEnum({
-  Unslashable: Null,
-  Slashable: SlashableTerms,
-} as const) {}
-
 export type IOpeningPolicyCommitment = {
   application_rationing_policy: Option<ApplicationRationingPolicy>
   max_review_period_length: BlockNumber
@@ -336,6 +320,13 @@ export const contentWorkingGroupTypes = {
   WorkingGroupUnstaker,
   CuratorApplicationIdToCuratorIdMap,
   CuratorApplicationIdSet,
+  // Expose in registry for api.createType purposes:
+  CuratorRoleStakeProfile,
+  CuratorRoleStage,
+  CuratorExitSummary,
+  CuratorExitInitiationOrigin,
+  ExitedLeadRole,
+  CuratorInduction,
 }
 
 export default contentWorkingGroupTypes

+ 125 - 8
types/src/definitions/augment-types.ts

@@ -46,22 +46,22 @@ import { Multiplier } from '@polkadot/types/interfaces/txpayment';
 import { CallHash, Multisig, Timepoint } from '@polkadot/types/interfaces/utility';
 import { VestingInfo } from '@polkadot/types/interfaces/vesting';
 /** CUSTOMIMPORTS **/
-import { Credential as Credential, CredentialSet as CredentialSet, BlockAndTime as BlockAndTime, ThreadId as ThreadId, PostId as PostId, InputValidationLengthConstraint as InputValidationLengthConstraint, WorkingGroup as WorkingGroup } from '../common'
+import { Credential as Credential, CredentialSet as CredentialSet, BlockAndTime as BlockAndTime, ThreadId as ThreadId, PostId as PostId, InputValidationLengthConstraint as InputValidationLengthConstraint, WorkingGroup as WorkingGroup, SlashingTerms as SlashingTerms, SlashableTerms as SlashableTerms } from '../common'
 import { EntryMethod as EntryMethod, MemberId as MemberId, PaidTermId as PaidTermId, SubscriptionId as SubscriptionId, Membership as Membership, PaidMembershipTerms as PaidMembershipTerms, ActorId as ActorId } from '../members'
 import { ElectionStage as ElectionStage, ElectionStake as ElectionStake, SealedVote as SealedVote, TransferableStake as TransferableStake, ElectionParameters as ElectionParameters, Seat as Seat, Seats as Seats, Backer as Backer, Backers as Backers } from '../council'
 import { RoleParameters as RoleParameters } from '../roles'
 import { PostTextChange as PostTextChange, ModerationAction as ModerationAction, ChildPositionInParentCategory as ChildPositionInParentCategory, CategoryId as CategoryId, Category as Category, Thread as Thread, Post as Post, ReplyId as ReplyId, Reply as Reply } from '../forum'
-import { Stake as Stake } from '../stake'
-import { Mint as Mint } from '../mint'
+import { Stake as Stake, StakingStatus as StakingStatus, Staked as Staked, StakedStatus as StakedStatus, Unstaking as Unstaking, Slash as Slash } from '../stake'
+import { Mint as Mint, NextAdjustment as NextAdjustment, AdjustOnInterval as AdjustOnInterval, AdjustCapacityBy as AdjustCapacityBy } from '../mint'
 import { Recipient as Recipient, RewardRelationship as RewardRelationship } from '../recurring-rewards'
-import { Application as Application, ApplicationStage as ApplicationStage, ActivateOpeningAt as ActivateOpeningAt, ApplicationRationingPolicy as ApplicationRationingPolicy, OpeningStage as OpeningStage, StakingPolicy as StakingPolicy, Opening as Opening } from '../hiring'
+import { Application as Application, ApplicationStage as ApplicationStage, ActivateOpeningAt as ActivateOpeningAt, ApplicationRationingPolicy as ApplicationRationingPolicy, OpeningStage as OpeningStage, StakingPolicy as StakingPolicy, Opening as Opening, WaitingToBeingOpeningStageVariant as WaitingToBeingOpeningStageVariant, ActiveOpeningStageVariant as ActiveOpeningStageVariant, ActiveOpeningStage as ActiveOpeningStage, AcceptingApplications as AcceptingApplications, ReviewPeriod as ReviewPeriod, Deactivated as Deactivated, OpeningDeactivationCause as OpeningDeactivationCause, InactiveApplicationStage as InactiveApplicationStage, UnstakingApplicationStage as UnstakingApplicationStage, ApplicationDeactivationCause as ApplicationDeactivationCause } from '../hiring'
 import { Class as Class, Entity as Entity, ClassSchema as ClassSchema, Property as Property, PropertyType as PropertyType, PropertyValue as PropertyValue, ClassPropertyValue as ClassPropertyValue } from '../versioned-store'
-import { EntityPermissions as EntityPermissions, ReferenceConstraint as ReferenceConstraint, ClassPermissionsType as ClassPermissionsType, Operation as Operation } from '../versioned-store/permissions'
-import { OptionalText as OptionalText, Channel as Channel, ChannelContentType as ChannelContentType, ChannelCurationStatus as ChannelCurationStatus, ChannelPublicationStatus as ChannelPublicationStatus, CurationActor as CurationActor, Curator as Curator, CuratorApplication as CuratorApplication, CuratorOpening as CuratorOpening, Lead as Lead, OpeningPolicyCommitment as OpeningPolicyCommitment, Principal as Principal, WorkingGroupUnstaker as WorkingGroupUnstaker, CuratorApplicationIdToCuratorIdMap as CuratorApplicationIdToCuratorIdMap, CuratorApplicationIdSet as CuratorApplicationIdSet } from '../content-working-group'
-import { RationaleText as RationaleText, Application as ApplicationOf, ApplicationIdSet as ApplicationIdSet, ApplicationIdToWorkerIdMap as ApplicationIdToWorkerIdMap, WorkerId as WorkerId, Worker as WorkerOf, Opening as OpeningOf, StorageProviderId as StorageProviderId, OpeningType as OpeningType, ApplicationId as HiringApplicationId, RewardPolicy as RewardPolicy, OpeningId as working_group__OpeningId, WorkerId as working_group__WorkerId } from '../working-group'
+import { EntityPermissions as EntityPermissions, ReferenceConstraint as ReferenceConstraint, ClassPermissionsType as ClassPermissionsType, Operation as Operation, OperationType as OperationType, CreateEntity as CreateEntity, UpdatePropertyValues as UpdatePropertyValues, AddSchemaSupportToEntity as AddSchemaSupportToEntity, ParametrizedEntity as ParametrizedEntity, ParametrizedClassPropertyValue as ParametrizedClassPropertyValue, ParametrizedPropertyValue as ParametrizedPropertyValue } from '../versioned-store/permissions'
+import { OptionalText as OptionalText, Channel as Channel, ChannelContentType as ChannelContentType, ChannelCurationStatus as ChannelCurationStatus, ChannelPublicationStatus as ChannelPublicationStatus, CurationActor as CurationActor, Curator as Curator, CuratorApplication as CuratorApplication, CuratorOpening as CuratorOpening, Lead as Lead, OpeningPolicyCommitment as OpeningPolicyCommitment, Principal as Principal, WorkingGroupUnstaker as WorkingGroupUnstaker, CuratorApplicationIdToCuratorIdMap as CuratorApplicationIdToCuratorIdMap, CuratorApplicationIdSet as CuratorApplicationIdSet, CuratorRoleStakeProfile as CuratorRoleStakeProfile, CuratorRoleStage as CuratorRoleStage, CuratorExitSummary as CuratorExitSummary, CuratorExitInitiationOrigin as CuratorExitInitiationOrigin, ExitedLeadRole as ExitedLeadRole, CuratorInduction as CuratorInduction } from '../content-working-group'
+import { RationaleText as RationaleText, Application as ApplicationOf, ApplicationIdSet as ApplicationIdSet, ApplicationIdToWorkerIdMap as ApplicationIdToWorkerIdMap, WorkerId as WorkerId, Worker as WorkerOf, Opening as OpeningOf, StorageProviderId as StorageProviderId, OpeningType as OpeningType, ApplicationId as HiringApplicationId, RewardPolicy as RewardPolicy, OpeningId as working_group__OpeningId, WorkerId as working_group__WorkerId, WorkingGroupOpeningPolicyCommitment as WorkingGroupOpeningPolicyCommitment, RoleStakeProfile as RoleStakeProfile } from '../working-group'
 import { Url as Url, IPNSIdentity as IPNSIdentity, ServiceProviderRecord as ServiceProviderRecord } from '../discovery'
 import { ContentId as ContentId, LiaisonJudgement as LiaisonJudgement, DataObject as DataObject, DataObjectStorageRelationshipId as DataObjectStorageRelationshipId, DataObjectStorageRelationship as DataObjectStorageRelationship, DataObjectTypeId as DataObjectTypeId, DataObjectType as DataObjectType, DataObjectsMap as DataObjectsMap } from '../media'
-import { ProposalId as ProposalId, ProposalStatus as ProposalStatus, Proposal as ProposalOf, ProposalDetails as ProposalDetails, ProposalDetails as ProposalDetailsOf, VotingResults as VotingResults, ProposalParameters as ProposalParameters, VoteKind as VoteKind, ThreadCounter as ThreadCounter, DiscussionThread as DiscussionThread, DiscussionPost as DiscussionPost, AddOpeningParameters as AddOpeningParameters, FillOpeningParameters as FillOpeningParameters, TerminateRoleParameters as TerminateRoleParameters } from '../proposals'
+import { ProposalId as ProposalId, ProposalStatus as ProposalStatus, Proposal as ProposalOf, ProposalDetails as ProposalDetails, ProposalDetails as ProposalDetailsOf, VotingResults as VotingResults, ProposalParameters as ProposalParameters, VoteKind as VoteKind, ThreadCounter as ThreadCounter, DiscussionThread as DiscussionThread, DiscussionPost as DiscussionPost, AddOpeningParameters as AddOpeningParameters, FillOpeningParameters as FillOpeningParameters, TerminateRoleParameters as TerminateRoleParameters, ActiveStake as ActiveStake, FinalizationData as FinalizationData, ProposalDecisionStatus as ProposalDecisionStatus, ExecutionFailed as ExecutionFailed } from '../proposals'
 /** /CUSTOMIMPORTS **/
 
 declare module '@polkadot/types/types/registry' {
@@ -1892,6 +1892,12 @@ declare module '@polkadot/types/types/registry' {
     "WorkingGroup": WorkingGroup;
     "Option<WorkingGroup>": Option<WorkingGroup>;
     "Vec<WorkingGroup>": Vec<WorkingGroup>;
+    "SlashingTerms": SlashingTerms;
+    "Option<SlashingTerms>": Option<SlashingTerms>;
+    "Vec<SlashingTerms>": Vec<SlashingTerms>;
+    "SlashableTerms": SlashableTerms;
+    "Option<SlashableTerms>": Option<SlashableTerms>;
+    "Vec<SlashableTerms>": Vec<SlashableTerms>;
     "EntryMethod": EntryMethod;
     "Option<EntryMethod>": Option<EntryMethod>;
     "Vec<EntryMethod>": Vec<EntryMethod>;
@@ -1976,6 +1982,21 @@ declare module '@polkadot/types/types/registry' {
     "Stake": Stake;
     "Option<Stake>": Option<Stake>;
     "Vec<Stake>": Vec<Stake>;
+    "StakingStatus": StakingStatus;
+    "Option<StakingStatus>": Option<StakingStatus>;
+    "Vec<StakingStatus>": Vec<StakingStatus>;
+    "Staked": Staked;
+    "Option<Staked>": Option<Staked>;
+    "Vec<Staked>": Vec<Staked>;
+    "StakedStatus": StakedStatus;
+    "Option<StakedStatus>": Option<StakedStatus>;
+    "Vec<StakedStatus>": Vec<StakedStatus>;
+    "Unstaking": Unstaking;
+    "Option<Unstaking>": Option<Unstaking>;
+    "Vec<Unstaking>": Vec<Unstaking>;
+    "Slash": Slash;
+    "Option<Slash>": Option<Slash>;
+    "Vec<Slash>": Vec<Slash>;
     "MintId": u64;
     "Option<MintId>": Option<u64>;
     "Vec<MintId>": Vec<u64>;
@@ -1991,6 +2012,15 @@ declare module '@polkadot/types/types/registry' {
     "minting::BalanceOf": Balance;
     "Option<minting::BalanceOf>": Option<Balance>;
     "Vec<minting::BalanceOf>": Vec<Balance>;
+    "NextAdjustment": NextAdjustment;
+    "Option<NextAdjustment>": Option<NextAdjustment>;
+    "Vec<NextAdjustment>": Vec<NextAdjustment>;
+    "AdjustOnInterval": AdjustOnInterval;
+    "Option<AdjustOnInterval>": Option<AdjustOnInterval>;
+    "Vec<AdjustOnInterval>": Vec<AdjustOnInterval>;
+    "AdjustCapacityBy": AdjustCapacityBy;
+    "Option<AdjustCapacityBy>": Option<AdjustCapacityBy>;
+    "Vec<AdjustCapacityBy>": Vec<AdjustCapacityBy>;
     "RecipientId": u64;
     "Option<RecipientId>": Option<u64>;
     "Vec<RecipientId>": Vec<u64>;
@@ -2030,6 +2060,36 @@ declare module '@polkadot/types/types/registry' {
     "Opening": Opening;
     "Option<Opening>": Option<Opening>;
     "Vec<Opening>": Vec<Opening>;
+    "WaitingToBeingOpeningStageVariant": WaitingToBeingOpeningStageVariant;
+    "Option<WaitingToBeingOpeningStageVariant>": Option<WaitingToBeingOpeningStageVariant>;
+    "Vec<WaitingToBeingOpeningStageVariant>": Vec<WaitingToBeingOpeningStageVariant>;
+    "ActiveOpeningStageVariant": ActiveOpeningStageVariant;
+    "Option<ActiveOpeningStageVariant>": Option<ActiveOpeningStageVariant>;
+    "Vec<ActiveOpeningStageVariant>": Vec<ActiveOpeningStageVariant>;
+    "ActiveOpeningStage": ActiveOpeningStage;
+    "Option<ActiveOpeningStage>": Option<ActiveOpeningStage>;
+    "Vec<ActiveOpeningStage>": Vec<ActiveOpeningStage>;
+    "AcceptingApplications": AcceptingApplications;
+    "Option<AcceptingApplications>": Option<AcceptingApplications>;
+    "Vec<AcceptingApplications>": Vec<AcceptingApplications>;
+    "ReviewPeriod": ReviewPeriod;
+    "Option<ReviewPeriod>": Option<ReviewPeriod>;
+    "Vec<ReviewPeriod>": Vec<ReviewPeriod>;
+    "Deactivated": Deactivated;
+    "Option<Deactivated>": Option<Deactivated>;
+    "Vec<Deactivated>": Vec<Deactivated>;
+    "OpeningDeactivationCause": OpeningDeactivationCause;
+    "Option<OpeningDeactivationCause>": Option<OpeningDeactivationCause>;
+    "Vec<OpeningDeactivationCause>": Vec<OpeningDeactivationCause>;
+    "InactiveApplicationStage": InactiveApplicationStage;
+    "Option<InactiveApplicationStage>": Option<InactiveApplicationStage>;
+    "Vec<InactiveApplicationStage>": Vec<InactiveApplicationStage>;
+    "UnstakingApplicationStage": UnstakingApplicationStage;
+    "Option<UnstakingApplicationStage>": Option<UnstakingApplicationStage>;
+    "Vec<UnstakingApplicationStage>": Vec<UnstakingApplicationStage>;
+    "ApplicationDeactivationCause": ApplicationDeactivationCause;
+    "Option<ApplicationDeactivationCause>": Option<ApplicationDeactivationCause>;
+    "Vec<ApplicationDeactivationCause>": Vec<ApplicationDeactivationCause>;
     "ClassId": u64;
     "Option<ClassId>": Option<u64>;
     "Vec<ClassId>": Vec<u64>;
@@ -2069,6 +2129,27 @@ declare module '@polkadot/types/types/registry' {
     "Operation": Operation;
     "Option<Operation>": Option<Operation>;
     "Vec<Operation>": Vec<Operation>;
+    "OperationType": OperationType;
+    "Option<OperationType>": Option<OperationType>;
+    "Vec<OperationType>": Vec<OperationType>;
+    "CreateEntity": CreateEntity;
+    "Option<CreateEntity>": Option<CreateEntity>;
+    "Vec<CreateEntity>": Vec<CreateEntity>;
+    "UpdatePropertyValues": UpdatePropertyValues;
+    "Option<UpdatePropertyValues>": Option<UpdatePropertyValues>;
+    "Vec<UpdatePropertyValues>": Vec<UpdatePropertyValues>;
+    "AddSchemaSupportToEntity": AddSchemaSupportToEntity;
+    "Option<AddSchemaSupportToEntity>": Option<AddSchemaSupportToEntity>;
+    "Vec<AddSchemaSupportToEntity>": Vec<AddSchemaSupportToEntity>;
+    "ParametrizedEntity": ParametrizedEntity;
+    "Option<ParametrizedEntity>": Option<ParametrizedEntity>;
+    "Vec<ParametrizedEntity>": Vec<ParametrizedEntity>;
+    "ParametrizedClassPropertyValue": ParametrizedClassPropertyValue;
+    "Option<ParametrizedClassPropertyValue>": Option<ParametrizedClassPropertyValue>;
+    "Vec<ParametrizedClassPropertyValue>": Vec<ParametrizedClassPropertyValue>;
+    "ParametrizedPropertyValue": ParametrizedPropertyValue;
+    "Option<ParametrizedPropertyValue>": Option<ParametrizedPropertyValue>;
+    "Vec<ParametrizedPropertyValue>": Vec<ParametrizedPropertyValue>;
     "ChannelId": u64;
     "Option<ChannelId>": Option<u64>;
     "Vec<ChannelId>": Vec<u64>;
@@ -2132,6 +2213,24 @@ declare module '@polkadot/types/types/registry' {
     "CuratorApplicationIdSet": CuratorApplicationIdSet;
     "Option<CuratorApplicationIdSet>": Option<CuratorApplicationIdSet>;
     "Vec<CuratorApplicationIdSet>": Vec<CuratorApplicationIdSet>;
+    "CuratorRoleStakeProfile": CuratorRoleStakeProfile;
+    "Option<CuratorRoleStakeProfile>": Option<CuratorRoleStakeProfile>;
+    "Vec<CuratorRoleStakeProfile>": Vec<CuratorRoleStakeProfile>;
+    "CuratorRoleStage": CuratorRoleStage;
+    "Option<CuratorRoleStage>": Option<CuratorRoleStage>;
+    "Vec<CuratorRoleStage>": Vec<CuratorRoleStage>;
+    "CuratorExitSummary": CuratorExitSummary;
+    "Option<CuratorExitSummary>": Option<CuratorExitSummary>;
+    "Vec<CuratorExitSummary>": Vec<CuratorExitSummary>;
+    "CuratorExitInitiationOrigin": CuratorExitInitiationOrigin;
+    "Option<CuratorExitInitiationOrigin>": Option<CuratorExitInitiationOrigin>;
+    "Vec<CuratorExitInitiationOrigin>": Vec<CuratorExitInitiationOrigin>;
+    "ExitedLeadRole": ExitedLeadRole;
+    "Option<ExitedLeadRole>": Option<ExitedLeadRole>;
+    "Vec<ExitedLeadRole>": Vec<ExitedLeadRole>;
+    "CuratorInduction": CuratorInduction;
+    "Option<CuratorInduction>": Option<CuratorInduction>;
+    "Vec<CuratorInduction>": Vec<CuratorInduction>;
     "RationaleText": RationaleText;
     "Option<RationaleText>": Option<RationaleText>;
     "Vec<RationaleText>": Vec<RationaleText>;
@@ -2171,6 +2270,12 @@ declare module '@polkadot/types/types/registry' {
     "working_group::WorkerId": working_group__WorkerId;
     "Option<working_group::WorkerId>": Option<working_group__WorkerId>;
     "Vec<working_group::WorkerId>": Vec<working_group__WorkerId>;
+    "WorkingGroupOpeningPolicyCommitment": WorkingGroupOpeningPolicyCommitment;
+    "Option<WorkingGroupOpeningPolicyCommitment>": Option<WorkingGroupOpeningPolicyCommitment>;
+    "Vec<WorkingGroupOpeningPolicyCommitment>": Vec<WorkingGroupOpeningPolicyCommitment>;
+    "RoleStakeProfile": RoleStakeProfile;
+    "Option<RoleStakeProfile>": Option<RoleStakeProfile>;
+    "Vec<RoleStakeProfile>": Vec<RoleStakeProfile>;
     "Url": Url;
     "Option<Url>": Option<Url>;
     "Vec<Url>": Vec<Url>;
@@ -2246,6 +2351,18 @@ declare module '@polkadot/types/types/registry' {
     "TerminateRoleParameters": TerminateRoleParameters;
     "Option<TerminateRoleParameters>": Option<TerminateRoleParameters>;
     "Vec<TerminateRoleParameters>": Vec<TerminateRoleParameters>;
+    "ActiveStake": ActiveStake;
+    "Option<ActiveStake>": Option<ActiveStake>;
+    "Vec<ActiveStake>": Vec<ActiveStake>;
+    "FinalizationData": FinalizationData;
+    "Option<FinalizationData>": Option<FinalizationData>;
+    "Vec<FinalizationData>": Vec<FinalizationData>;
+    "ProposalDecisionStatus": ProposalDecisionStatus;
+    "Option<ProposalDecisionStatus>": Option<ProposalDecisionStatus>;
+    "Vec<ProposalDecisionStatus>": Vec<ProposalDecisionStatus>;
+    "ExecutionFailed": ExecutionFailed;
+    "Option<ExecutionFailed>": Option<ExecutionFailed>;
+    "Vec<ExecutionFailed>": Vec<ExecutionFailed>;
     /** /CUSTOMTYPES **/
   }
 }

+ 11 - 0
types/src/hiring/index.ts

@@ -344,6 +344,17 @@ export const hiringTypes: RegistryTypes = {
   OpeningStage,
   StakingPolicy,
   Opening,
+  // Expose in registry for api.createType purposes:
+  WaitingToBeingOpeningStageVariant,
+  ActiveOpeningStageVariant,
+  ActiveOpeningStage,
+  AcceptingApplications,
+  ReviewPeriod,
+  Deactivated,
+  OpeningDeactivationCause,
+  InactiveApplicationStage,
+  UnstakingApplicationStage,
+  ApplicationDeactivationCause,
 }
 
 export default hiringTypes

+ 4 - 0
types/src/mint/index.ts

@@ -54,5 +54,9 @@ export const mintTypes: RegistryTypes = {
   MintBalanceOf: 'Balance',
   BalanceOfMint: 'Balance',
   'minting::BalanceOf': 'Balance',
+  // Expose in registry for api.createType purposes:
+  NextAdjustment,
+  AdjustOnInterval,
+  AdjustCapacityBy,
 }
 export default mintTypes

+ 6 - 1
types/src/proposals.ts

@@ -89,7 +89,7 @@ export class ExecutionFailedStatus extends JoyStructDecorated({
   error: Vec.with(u8),
 }) {}
 
-class ExecutionFailed extends ExecutionFailedStatus {}
+export class ExecutionFailed extends ExecutionFailedStatus {}
 
 export const ApprovedProposalDef = {
   PendingExecution: Null,
@@ -285,6 +285,11 @@ export const proposalsTypes = {
   AddOpeningParameters,
   FillOpeningParameters,
   TerminateRoleParameters,
+  // Expose in registry for api.createType purposes:
+  ActiveStake,
+  FinalizationData,
+  ProposalDecisionStatus,
+  ExecutionFailed,
 }
 
 export default proposalsTypes

+ 2 - 0
types/src/scripts/updateAugmentTypes.ts

@@ -88,6 +88,8 @@ const addAugmentTypes = (typeName: string, constructorName: string) => {
 }
 
 Object.entries(typesByModule).forEach(([moduleName, types]) => {
+  console.log('Module: ', moduleName)
+  console.log('Types found:', Object.keys(types))
   Object.entries(types).forEach(([typeName, codecOrName]) => {
     if (typeof codecOrName === 'function') {
       const constructorName = codecOrName.name

+ 6 - 0
types/src/stake/index.ts

@@ -87,5 +87,11 @@ export class Stake
 export const stakeTypes: RegistryTypes = {
   StakeId: 'u64',
   Stake,
+  // Expose in registry for api.createType purposes:
+  StakingStatus,
+  Staked,
+  StakedStatus,
+  Unstaking,
+  Slash,
 }
 export default stakeTypes

+ 25 - 1
types/src/versioned-store/permissions/index.ts

@@ -2,15 +2,39 @@ import EntityPermissions from './EntityPermissions'
 import { ReferenceConstraint } from './reference-constraint'
 import ClassPermissionsType from './ClassPermissions'
 import { Operation } from './batching/'
+import { OperationType, CreateEntity, UpdatePropertyValues, AddSchemaSupportToEntity } from './batching/operation-types'
+import { ParametrizedEntity } from './batching/parametrized-entity'
 import { RegistryTypes } from '@polkadot/types/types'
+import ParametrizedClassPropertyValue from './batching/ParametrizedClassPropertyValue'
+import { ParametrizedPropertyValue } from './batching/parametrized-property-value'
 
-export { EntityPermissions, ReferenceConstraint, ClassPermissionsType, Operation }
+export {
+  EntityPermissions,
+  ReferenceConstraint,
+  ClassPermissionsType,
+  Operation,
+  OperationType,
+  CreateEntity,
+  UpdatePropertyValues,
+  AddSchemaSupportToEntity,
+  ParametrizedEntity,
+  ParametrizedClassPropertyValue,
+  ParametrizedPropertyValue,
+}
 
 export const versionedStorePermissionsTypes: RegistryTypes = {
   EntityPermissions,
   ReferenceConstraint,
   ClassPermissionsType,
   Operation,
+  // Expose in registry for api.createType purposes:
+  OperationType,
+  CreateEntity,
+  UpdatePropertyValues,
+  AddSchemaSupportToEntity,
+  ParametrizedEntity,
+  ParametrizedClassPropertyValue,
+  ParametrizedPropertyValue,
 }
 
 export default versionedStorePermissionsTypes

+ 5 - 25
types/src/working-group/index.ts

@@ -1,12 +1,12 @@
 import { Bytes, BTreeMap, BTreeSet, Option } from '@polkadot/types'
-import { u16, Null, u32, u128 } from '@polkadot/types/primitive'
+import { Null, u32, u128 } from '@polkadot/types/primitive'
 import AccountId from '@polkadot/types/generic/AccountId'
 import { BlockNumber, Balance } from '@polkadot/types/interfaces'
 import { MemberId, ActorId } from '../members'
 import { RewardRelationshipId } from '../recurring-rewards'
 import { StakeId } from '../stake'
 import { ApplicationId, OpeningId, ApplicationRationingPolicy, StakingPolicy } from '../hiring'
-import { JoyEnum, JoyStructDecorated } from '../common'
+import { JoyEnum, JoyStructDecorated, SlashingTerms } from '../common'
 import { RegistryTypes } from '@polkadot/types/types'
 
 export class RationaleText extends Bytes {}
@@ -73,29 +73,6 @@ export class Worker
   }
 }
 
-export type ISlashableTerms = {
-  max_count: u16
-  max_percent_pts_per_time: u16
-}
-
-// This type is also defined in /content-working-group, but currently both those definitions are identical
-// (I added this defininition here too, because techinicaly those are 2 different types in the runtime.
-// Later the definition in /content-working-group will be removed and we can just register this type here)
-export class SlashableTerms
-  extends JoyStructDecorated({
-    max_count: u16,
-    max_percent_pts_per_time: u16,
-  })
-  implements ISlashableTerms {}
-
-export class UnslashableTerms extends Null {}
-
-// This type is also defined in /content-working-group (as above)
-export class SlashingTerms extends JoyEnum({
-  Unslashable: UnslashableTerms,
-  Slashable: SlashableTerms,
-} as const) {}
-
 export type IWorkingGroupOpeningPolicyCommitment = {
   application_rationing_policy: Option<ApplicationRationingPolicy>
   max_review_period_length: BlockNumber
@@ -200,6 +177,9 @@ export const workingGroupTypes: RegistryTypes = {
   RewardPolicy,
   'working_group::OpeningId': OpeningId,
   'working_group::WorkerId': WorkerId,
+  // Expose in registry for api.createType purposes:
+  WorkingGroupOpeningPolicyCommitment,
+  RoleStakeProfile,
 }
 
 export default workingGroupTypes

+ 2 - 2
types/tsconfig-scripts.json

@@ -1,6 +1,6 @@
 {
   "extends": "./tsconfig-base.json",
   "include": [
-    "src/scripts/*.ts"
-  ]
+    "src/**/*.ts" // Include all files to make sure they're always up-to-date when running scripts
+  ],
 }

+ 46 - 0
utils/api-examples/README.md

@@ -0,0 +1,46 @@
+# Joystream API Examples
+
+Repo with examples on how to use the @joystream/types package along with @polkadot/api to communicate with a joystream full node.
+
+## Examples
+
+```
+yarn
+yarn run status
+```
+
+## Example code
+
+```javascript
+import { types } from '@joystream/types'
+import { ApiPromise, WsProvider } from '@polkadot/api'
+
+async function main() {
+  // Initialise the provider to connect to the local node
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+
+  // Create the API and wait until ready
+  const api = await ApiPromise.create({ provider, types })
+
+  await api.isReady
+
+  // Retrieve the chain & node information information via rpc calls
+  const [chain, nodeName, nodeVersion] = await Promise.all([
+    api.rpc.system.chain(),
+    api.rpc.system.name(),
+    api.rpc.system.version(),
+  ])
+
+  console.log(`Chain ${chain} using ${nodeName} v${nodeVersion}`)
+}
+
+main()
+```
+
+### Scripts
+
+You can run scripts that are found in the [./scripts/](./scripts) folder:
+
+```sh
+yarn script example
+```

+ 25 - 0
utils/api-examples/package.json

@@ -0,0 +1,25 @@
+{
+  "name": "api-examples",
+  "private": true,
+  "version": "0.1.0",
+  "license": "GPL-3.0-only",
+  "scripts": {
+    "status": "ts-node src/status",
+    "script": "ts-node src/script"
+  },
+  "dependencies": {
+    "@joystream/types": "^0.13.0",
+    "@polkadot/api": "^1.26.1",
+    "@polkadot/types": "^1.26.1",
+    "@polkadot/keyring": "^3.0.1",
+    "@polkadot/util": "^3.0.1",
+    "@polkadot/util-crypto": "^3.0.1",
+    "@types/bn.js": "^4.11.5",
+    "bn.js": "^4.11.8"
+  },
+  "devDependencies": {
+    "@polkadot/ts": "^0.1.56",
+    "typescript": "^3.9.7",
+    "ts-node": "^8.6.2"
+  }
+}

+ 32 - 0
utils/api-examples/scripts/example.js

@@ -0,0 +1,32 @@
+/* global api, hashing, keyring, types, util, joy */
+
+// run this script with:
+// yarn script example
+//
+// or copy and paste the code into the pioneer javascript toolbox at:
+// https://testnet.joystream.org/#/js
+//
+// Example works on nicaea release+
+
+const script = async ({ api, hashing, keyring, types, util, joy }) => {
+  // Retrieve the chain & node information information via rpc calls
+  const [chain, nodeName, nodeVersion, runtimeVersion] = await Promise.all([
+    api.rpc.system.chain(),
+    api.rpc.system.name(),
+    api.rpc.system.version(),
+    api.runtimeVersion,
+  ])
+
+  console.log(`Chain: ${chain}`)
+  console.log(`Software: ${nodeName} v${nodeVersion}`)
+  console.log(`Runtime specVersion: ${runtimeVersion.specVersion}`)
+}
+
+if (typeof module === 'undefined') {
+  // Pioneer js-toolbox
+  // Available globals [api, hashing, keyring, types, util]
+  script({ api, hashing, keyring, types, util, joy })
+} else {
+  // Node
+  module.exports = script
+}

+ 49 - 0
utils/api-examples/scripts/export-data-directory.js

@@ -0,0 +1,49 @@
+/* global api, hashing, keyring, types, util */
+
+// run this script with:
+// yarn script exportDataDirectory
+//
+// or copy and paste the code into the pioneer javascript toolbox at:
+// https://testnet.joystream.org/#/js
+
+const script = async ({ api }) => {
+  const ids = await api.query.dataDirectory.knownContentIds()
+
+  // When a BTreeMap is constructed for injection the node will fail to decode
+  // it if its not sorted.
+  ids.sort()
+
+  const transformed = await Promise.all(
+    ids.map(async (id) => {
+      let obj = await api.query.dataDirectory.dataObjectByContentId(id)
+      if (obj.isNone) {
+        return null
+      }
+      obj = obj.unwrap()
+
+      return [
+        id,
+        {
+          owner: obj.owner,
+          added_at: obj.added_at,
+          type_id: obj.type_id,
+          size: obj.size_in_bytes,
+          liaison: obj.liaison,
+          liaison_judgement: obj.liaison_judgement,
+          ipfs_content_id: obj.ipfs_content_id,
+        },
+      ]
+    })
+  )
+
+  console.log(JSON.stringify(transformed))
+  console.error(`Exported ${transformed.length} objects`)
+}
+
+if (typeof module === 'undefined') {
+  // Pioneer js-toolbox
+  script({ api, hashing, keyring, types, util })
+} else {
+  // Node
+  module.exports = script
+}

+ 9 - 0
utils/api-examples/scripts/index.js

@@ -0,0 +1,9 @@
+const exportedScripts = {}
+
+exportedScripts.example = require('./example.js')
+exportedScripts.exportDataDirectory = require('./export-data-directory.js')
+exportedScripts.injectDataObjects = require('./inject-data-objects.js')
+exportedScripts.listDataDirectory = require('./list-data-directory.js')
+exportedScripts.testTransfer = require('./transfer.js')
+
+module.exports = exportedScripts

File diff suppressed because it is too large
+ 15 - 0
utils/api-examples/scripts/inject-data-objects.js


+ 33 - 0
utils/api-examples/scripts/list-data-directory.js

@@ -0,0 +1,33 @@
+/* global api, hashing, keyring, types, util, joy */
+
+// run this script with:
+// yarn script listDataDirectory
+//
+// or copy and paste the code into the pioneer javascript toolbox at:
+// https://testnet.joystream.org/#/js
+// requires nicaea release+
+
+const script = async ({ api, joy }) => {
+  const ids = await api.query.dataDirectory.knownContentIds()
+
+  await Promise.all(
+    ids.map(async (id) => {
+      let obj = await api.query.dataDirectory.dataObjectByContentId(id)
+      if (obj.isNone) {
+        return
+      }
+      obj = obj.unwrap()
+      console.log(`contentId: ${api.createType('ContentId', id).encode()}, ipfs: ${obj.ipfs_content_id}`)
+    })
+  )
+
+  console.error(`Data Directory contains ${ids.length} objects`)
+}
+
+if (typeof module === 'undefined') {
+  // Pioneer js-toolbox
+  script({ api, hashing, keyring, types, util, joy })
+} else {
+  // Node
+  module.exports = script
+}

+ 31 - 0
utils/api-examples/scripts/transfer.js

@@ -0,0 +1,31 @@
+/* global api, hashing, keyring, types, util, window */
+
+// run this script with:
+// yarn script testTransfer
+//
+// or copy and paste the code into the pioneer javascript toolbox at:
+// https://testnet.joystream.org/#/js
+//
+
+const script = async ({ api, keyring }) => {
+  const sudoAddress = (await api.query.sudo.key()).toString()
+  let sudo
+  if (typeof window === 'undefined') {
+    // In node, get the keyPair if the keyring was provided
+    sudo = keyring.getPair(sudoAddress)
+  } else {
+    // Pioneer: let the UI Signer handle it
+    sudo = sudoAddress
+  }
+
+  const transfer = api.tx.balances.transfer(sudoAddress, 100)
+  await transfer.signAndSend(sudo)
+}
+
+if (typeof module === 'undefined') {
+  // Pioneer js-toolbox
+  script({ api, hashing, keyring, types, util })
+} else {
+  // Node
+  module.exports = script
+}

+ 20 - 0
utils/api-examples/src/get-code.ts

@@ -0,0 +1,20 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types } from '@joystream/types'
+
+async function main() {
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+
+  const api = await ApiPromise.create({ provider, types })
+
+  const currentBlockHash = await api.rpc.chain.getBlockHash(1)
+
+  console.log('getting code as of block hash', currentBlockHash.toString())
+
+  const substrateWasm = await api.query.substrate.code.at(currentBlockHash)
+
+  console.log(substrateWasm.toHex())
+
+  api.disconnect()
+}
+
+main()

+ 52 - 0
utils/api-examples/src/script.ts

@@ -0,0 +1,52 @@
+// @ts-check
+
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import * as types from '@polkadot/types'
+import * as util from '@polkadot/util'
+import joy, { types as joyTypes } from '@joystream/types'
+import * as hashing from '@polkadot/util-crypto'
+import { Keyring } from '@polkadot/keyring'
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const scripts = require('../scripts')
+
+async function main() {
+  const scriptArg = process.argv[2]
+  const script = scripts[scriptArg]
+
+  if (!scriptArg || !script) {
+    console.error('Please specify valid script name.')
+    console.error('Available scripts:', Object.keys(scripts))
+    return
+  }
+
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+
+  const api = await ApiPromise.create({ provider, types: joyTypes })
+
+  // We don't pass a custom signer to the api so we must use a keyPair
+  // when calling signAndSend on transactions
+  const keyring = new Keyring()
+
+  // Optional last argument is a SURI for account to use for signing transactions
+  const suri = process.argv[3]
+  if (suri) {
+    keyring.addFromUri(suri, undefined, 'sr25519')
+  }
+
+  // Add development well known keys to keyring
+  if ((await api.rpc.system.chain()).toString() === 'Development') {
+    keyring.addFromUri('//Alice', undefined, 'sr25519')
+    keyring.addFromUri('//Bob', undefined, 'sr25519')
+  }
+
+  try {
+    await script({ api, types, util, hashing, keyring, joy })
+  } catch (err) {
+    console.error(err)
+  }
+
+  api.disconnect()
+}
+
+main()

+ 56 - 0
utils/api-examples/src/status.ts

@@ -0,0 +1,56 @@
+// @ts-check
+
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { types } from '@joystream/types'
+import { Seat } from '@joystream/types/council'
+import { ValidatorId } from '@polkadot/types/interfaces'
+
+import BN from 'bn.js'
+
+async function main() {
+  // Initialise the provider to connect to the local node
+  const provider = new WsProvider('ws://127.0.0.1:9944')
+
+  // Create the API and wait until ready
+  const api = await ApiPromise.create({ provider, types })
+
+  // Retrieve the chain & node information information via rpc calls
+  const [chain, nodeName, nodeVersion] = await Promise.all([
+    api.rpc.system.chain(),
+    api.rpc.system.name(),
+    api.rpc.system.version(),
+  ])
+
+  console.log(`Chain ${chain} using ${nodeName} v${nodeVersion}`)
+
+  const council = ((await api.query.council.activeCouncil()) as unknown) as Seat[]
+  const validators = ((await api.query.session.validators()) as unknown) as ValidatorId[]
+  const version = (await api.rpc.state.getRuntimeVersion()) as any
+
+  console.log(`Runtime Version: ${version.authoringVersion}.${version.specVersion}.${version.implVersion}`)
+
+  // let council: QueryableStorageFunction<Seat[], SubscriptionResult> = (await api.query.council.activeCouncil()) as unknown as Seat[]
+  // let council = (await api.query.council.activeCouncil()) as unknown as Seat[];
+
+  // number of council members
+  console.log('Council size:', council.length)
+
+  console.log('Validator count:', validators.length)
+
+  if (validators && validators.length > 0) {
+    // Retrieve the free balances for all validators
+    const validatorBalances = await Promise.all(
+      validators.map((authorityId) => api.query.balances.account(authorityId))
+    )
+
+    const totalValidatorBalances = validatorBalances.reduce((total, value) => total.add(value.free), new BN(0))
+
+    // TODO: to get the staked amounts we need to read the account lock information.
+
+    console.log('Total Validator Free Balance:', totalValidatorBalances.toString())
+  }
+
+  api.disconnect()
+}
+
+main()

Some files were not shown because too many files changed in this diff