浏览代码

Adjust polkadot-js/apps and joy-utils, reactivate joy-members

Leszek Wiesner 4 年之前
父节点
当前提交
ea711c9e77
共有 65 个文件被更改,包括 1138 次插入463 次删除
  1. 4 1
      package.json
  2. 0 3
      pioneer/.eslintignore
  3. 3 1
      pioneer/packages/apps-config/src/api/spec/index.ts
  4. 3 0
      pioneer/packages/apps-config/src/api/spec/joystream-node.ts
  5. 5 0
      pioneer/packages/apps-config/src/settings/endpoints.ts
  6. 3 1
      pioneer/packages/apps-config/src/ui/logos/index.ts
  7. 15 0
      pioneer/packages/apps-config/src/ui/logos/nodes/joystream-node.svg
  8. 16 42
      pioneer/packages/apps-routing/src/index.ts
  9. 15 0
      pioneer/packages/apps-routing/src/joy-members.ts
  10. 27 0
      pioneer/packages/apps-routing/src/joy-pages.ts
  11. 14 1
      pioneer/packages/apps/src/Apps.tsx
  12. 2 6
      pioneer/packages/apps/src/Content/index.tsx
  13. 58 0
      pioneer/packages/apps/src/JoyTopBar/TopBar.tsx
  14. 1 1
      pioneer/packages/apps/src/SideBar/ChainInfo.tsx
  15. 3 24
      pioneer/packages/apps/src/SideBar/index.tsx
  16. 20 13
      pioneer/packages/apps/src/index.tsx
  17. 0 0
      pioneer/packages/joy-members/.skip-build
  18. 3 3
      pioneer/packages/joy-members/package.json
  19. 47 29
      pioneer/packages/joy-members/src/Dashboard.tsx
  20. 4 4
      pioneer/packages/joy-members/src/Details.tsx
  21. 3 1
      pioneer/packages/joy-members/src/DetailsByHandle.tsx
  22. 34 29
      pioneer/packages/joy-members/src/EditForm.tsx
  23. 6 4
      pioneer/packages/joy-members/src/List.tsx
  24. 3 4
      pioneer/packages/joy-members/src/MemberPreview.tsx
  25. 0 58
      pioneer/packages/joy-members/src/index.css
  26. 12 8
      pioneer/packages/joy-members/src/index.tsx
  27. 62 0
      pioneer/packages/joy-members/src/style.ts
  28. 3 2
      pioneer/packages/joy-members/src/utils.ts
  29. 0 0
      pioneer/packages/joy-pages/.skip-build
  30. 3 3
      pioneer/packages/joy-pages/package.json
  31. 0 14
      pioneer/packages/joy-utils-old/src/functions/misc.ts
  32. 0 1
      pioneer/packages/joy-utils-old/src/react/components/index.tsx
  33. 0 1
      pioneer/packages/joy-utils-old/src/react/context/index.tsx
  34. 0 0
      pioneer/packages/joy-utils/README.md
  35. 3 3
      pioneer/packages/joy-utils/package.json
  36. 0 0
      pioneer/packages/joy-utils/src/functions/date.ts
  37. 0 0
      pioneer/packages/joy-utils/src/functions/format.ts
  38. 157 0
      pioneer/packages/joy-utils/src/functions/misc.ts
  39. 0 0
      pioneer/packages/joy-utils/src/react/components/FlexCenter.tsx
  40. 0 0
      pioneer/packages/joy-utils/src/react/components/MutedText.tsx
  41. 0 0
      pioneer/packages/joy-utils/src/react/components/Section.tsx
  42. 45 49
      pioneer/packages/joy-utils/src/react/components/TxButton.tsx
  43. 1 2
      pioneer/packages/joy-utils/src/react/components/forms.tsx
  44. 4 0
      pioneer/packages/joy-utils/src/react/components/index.tsx
  45. 18 9
      pioneer/packages/joy-utils/src/react/context/account.tsx
  46. 2 0
      pioneer/packages/joy-utils/src/react/context/index.tsx
  47. 2 6
      pioneer/packages/joy-utils/src/react/context/membership.tsx
  48. 0 0
      pioneer/packages/joy-utils/src/react/helpers/index.ts
  49. 113 0
      pioneer/packages/joy-utils/src/react/hocs/accounts.tsx
  50. 100 0
      pioneer/packages/joy-utils/src/react/hocs/guards.tsx
  51. 2 0
      pioneer/packages/joy-utils/src/react/hooks/index.ts
  52. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyAccount.tsx
  53. 6 0
      pioneer/packages/joy-utils/src/react/hooks/useMyMembership.tsx
  54. 0 28
      pioneer/packages/old-apps/apps-routing/src/joy-pages.ts
  55. 0 17
      pioneer/packages/old-apps/apps/src/TopBar.css
  56. 0 47
      pioneer/packages/old-apps/apps/src/TopBar.tsx
  57. 0 11
      pioneer/packages/old-apps/react-components/src/styles/old-theme.ts-unused
  58. 7 0
      pioneer/packages/react-components/src/InputAddress/index.tsx
  59. 5 2
      pioneer/packages/react-components/src/Tabs/Tab.tsx
  60. 1 0
      pioneer/packages/react-components/src/Tabs/types.ts
  61. 3 1
      pioneer/packages/react-components/src/styles/index.ts
  62. 16 8
      pioneer/packages/react-components/src/styles/joystream.ts
  63. 1 1
      pioneer/packages/react-components/src/styles/theme.ts
  64. 7 9
      pioneer/tsconfig.json
  65. 270 16
      yarn.lock

+ 4 - 1
package.json

@@ -22,7 +22,10 @@
     "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"
   ],
   "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

+ 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

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

@@ -0,0 +1,15 @@
+<svg width="250" height="250" xmlns="http://www.w3.org/2000/svg">
+ <g>
+  <title>background</title>
+  <rect fill="#000000" id="canvas_background" height="252" width="252" y="-1" x="-1"/>
+  <g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
+   <rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
+  </g>
+ </g>
+ <g>
+  <title>Layer 1</title>
+  <g id="svg_5">
+   <path id="svg_4" fill="white" d="m165.30201,24.50002l-80.60401,0l0,27.86133l49.16845,0l0,114.62931c0,9.15519 -5.64228,17.1156 -14.1057,20.69789l-19.74798,7.56147l8.46342,30.24997l29.82348,-11.5427c10.0755,-3.98021 18.53892,-11.5427 22.97214,-21.09478c2.82114,-5.5729 4.0302,-11.94062 4.0302,-17.91144l0,-150.45105z"/>
+  </g>
+ </g>
+</svg>

+ 16 - 42
pioneer/packages/apps-routing/src/index.ts

@@ -9,69 +9,43 @@ 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,
-      staking(t),
-      democracy(t),
-      council(t),
-      // TODO Not sure about the inclusion of treasury, parachains & society here
+      members(t),
       null,
+      transfer(t),
+      accounts(t),
       settings(t)
     ]
     : [
-      // dashboard(t),
-      explorer(t),
-      accounts(t),
-      claims(t),
-      poll(t),
-      transfer(t),
-      genericAsset(t),
+      members(t),
       null,
-      staking(t),
-      democracy(t),
-      council(t),
-      treasury(t),
-      techcomm(t),
-      parachains(t),
-      society(t),
+      transfer(t),
+      accounts(t),
+      settings(t),
       null,
-      contracts(t),
+      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'
+  };
+}

+ 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;
+  }
 `);

+ 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;

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

@@ -0,0 +1,58 @@
+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';
+
+type Props = {};
+
+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 (_props: Props) {
+  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

+ 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 () {

+ 4 - 4
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;

+ 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} />
     );

+ 34 - 29
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,6 +78,8 @@ const InnerForm = (props: FormProps) => {
     memberId
   } = props;
 
+  const { api } = useContext(ApiContext)
+
   const onSubmit = (sendTx: () => void) => {
     if (isValid) sendTx();
   };
@@ -102,7 +103,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 = () => {
@@ -165,24 +168,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>
@@ -228,7 +233,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');
@@ -280,9 +287,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 {

+ 6 - 4
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;
@@ -55,7 +56,8 @@ class Component extends React.PureComponent<Props, State> {
     const {
       firstMemberId,
       membersCreated,
-      match: { params: { page } }
+      match: { params: { page } },
+      api
     } = this.props;
 
     const membersCount = membersCreated.toNumber();
@@ -71,7 +73,7 @@ class Component extends React.PureComponent<Props, State> {
       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 +97,4 @@ class Component extends React.PureComponent<Props, State> {
   }
 }
 
-export default translate(Component);
+export default translate(withApi(Component));

+ 3 - 4
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;

+ 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;
-    }
-  }
-}

+ 12 - 8
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;
@@ -33,7 +37,7 @@ class App extends React.PureComponent<Props> {
       {
         name: 'list',
         text: t('All members') + ` (${memberCount})`,
-        forcedExact: false
+        forceMatchParams: true
       },
       {
         name: 'edit',
@@ -58,7 +62,7 @@ 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>
@@ -69,7 +73,7 @@ class App extends React.PureComponent<Props> {
           <Route exact={true} path={`${basePath}/:handle`} component={DetailsByHandle} />
           <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"
   }
 }

+ 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';

+ 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


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


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

@@ -0,0 +1,157 @@
+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.trim().length === 0);
+
+export const nonEmptyStr = (x?: any) =>
+  isStr(x) && x.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();
+}

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


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


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


+ 45 - 49
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 = "paper-plane", 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();
@@ -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;

+ 1 - 2
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> = {

+ 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';

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

@@ -1,10 +1,11 @@
-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: string | undefined = store.get(MY_ADDRESS_STORAGE_KEY);
   console.log('Read my address from the local storage:', myAddress);
   return myAddress;
 }
@@ -22,7 +23,7 @@ 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 };
   }
 
@@ -40,7 +41,7 @@ function reducer (state: MyAccountState, action: MyAccountAction): MyAccountStat
       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();
@@ -91,6 +92,18 @@ export const MyAccountContext = createContext<MyAccountContextProps>(contextStub
 export function MyAccountProvider (props: React.PropsWithChildren<{}>) {
   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 +123,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


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

@@ -0,0 +1,113 @@
+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) {
+      let [ 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
+  );

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

@@ -0,0 +1,100 @@
+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 {}> (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 {}> (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 {}> (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'
-});

+ 7 - 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,11 @@ 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/*" ],

+ 270 - 16
yarn.lock

@@ -1374,6 +1374,13 @@
     pirates "^4.0.0"
     source-map-support "^0.5.16"
 
+"@babel/runtime@7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c"
+  integrity sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==
+  dependencies:
+    regenerator-runtime "^0.12.0"
+
 "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6":
   version "7.11.2"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
@@ -4450,6 +4457,13 @@
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
   integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
 
+"@types/query-string@^6.2.0":
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-6.3.0.tgz#b6fa172a01405abcaedac681118e78429d62ea39"
+  integrity sha512-yuIv/WRffRzL7cBW+sla4HwBZrEXRNf1MKQ5SklPEadth+BKbDxiVG8A3iISN5B3yC4EeSCzMZP8llHTcUhOzQ==
+  dependencies:
+    query-string "*"
+
 "@types/reach__router@^1.2.3":
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.5.tgz#14e1e981cccd3a5e50dc9e969a72de0b9d472f6d"
@@ -4648,16 +4662,37 @@
   dependencies:
     source-map "^0.6.1"
 
-"@types/unist@^2.0.0", "@types/unist@^2.0.2":
+"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
   integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
 
+"@types/uuid@^3.4.4":
+  version "3.4.9"
+  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.9.tgz#fcf01997bbc9f7c09ae5f91383af076d466594e1"
+  integrity sha512-XDwyIlt/47l2kWLTzw/mtrpLdB+GPSskR2n/PIcPn+VYhVO77rGhRncIR5GPU0KRzXuqkDO+J5qqrG0Y8P6jzQ==
+
 "@types/uuid@^7.0.2":
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.4.tgz#00a5749810b4ad80bff73a61f9cc9d0d521feb3c"
   integrity sha512-WGZCqBZZ0mXN2RxvLHL6/7RCu+OWs28jgQMP04LWfpyJlQUMTR6YU9CNJAKDgbw+EV/u687INXuLUc7FuML/4g==
 
+"@types/vfile-message@*":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5"
+  integrity sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==
+  dependencies:
+    vfile-message "*"
+
+"@types/vfile@^3.0.0":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-3.0.2.tgz#19c18cd232df11ce6fa6ad80259bc86c366b09b9"
+  integrity sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==
+  dependencies:
+    "@types/node" "*"
+    "@types/unist" "*"
+    "@types/vfile-message" "*"
+
 "@types/vfile@^4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/vfile/-/vfile-4.0.0.tgz#c32d13cbda319bc9f4ab3cacc0263b4ba1dd1ea3"
@@ -4708,6 +4743,11 @@
   resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a"
   integrity sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==
 
+"@types/yup@^0.26.10":
+  version "0.26.37"
+  resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.37.tgz#7a52854ac602ba0dc969bebc960559f7464a1686"
+  integrity sha512-cDqR/ez4+iAVQYOwadXjKX4Dq1frtnDGV2GNVKj3aUVKVCKRvsr8esFk66j+LgeeJGmrMcBkkfCf3zk13MjV7A==
+
 "@typescript-eslint/eslint-plugin@3.8.0":
   version "3.8.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.8.0.tgz#f82947bcdd9a4e42be7ad80dfd61f1dc411dd1df"
@@ -5895,7 +5935,7 @@ arrify@^2.0.1:
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
   integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
 
-asap@^2.0.0:
+asap@^2.0.0, asap@~2.0.3:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
   integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -8508,6 +8548,11 @@ core-js-pure@^3.0.1:
   resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.4.5.tgz#284474cb48d134e26e6e314cb89986c6c59480fb"
   integrity sha512-v3BoUOhmBvs4Z17jG/oM7qyv+tEEMvD1FYDDfxa6uD5W2rA/DpKvhvmyrBzxuMQTa/91UQKisaiqe0+0GuL2oA==
 
+core-js@^1.0.0:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
+  integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
+
 core-js@^2.4.0, core-js@^2.6.5:
   version "2.6.10"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.10.tgz#8a5b8391f8cc7013da703411ce5b585706300d7f"
@@ -8661,6 +8706,14 @@ create-react-context@0.3.0, create-react-context@^0.3.0:
     gud "^1.0.0"
     warning "^4.0.3"
 
+create-react-context@^0.2.2:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
+  integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
+  dependencies:
+    fbjs "^0.8.0"
+    gud "^1.0.0"
+
 cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -9223,6 +9276,11 @@ deepmerge@^1.5.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
   integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
 
+deepmerge@^2.1.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
+  integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
+
 deepmerge@^4.0.0, deepmerge@^4.2.2:
   version "4.2.2"
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@@ -11289,6 +11347,19 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "^2.0.0"
 
+fbjs@^0.8.0:
+  version "0.8.17"
+  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
+  integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
+  dependencies:
+    core-js "^1.0.0"
+    isomorphic-fetch "^2.1.1"
+    loose-envify "^1.0.0"
+    object-assign "^4.1.0"
+    promise "^7.1.1"
+    setimmediate "^1.0.5"
+    ua-parser-js "^0.7.18"
+
 fd-slicer@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
@@ -11636,6 +11707,11 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
+fn-name@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
+  integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=
+
 focus-lock@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a"
@@ -11737,6 +11813,21 @@ formidable@^1.2.0:
   resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
   integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
 
+formik@^1.5.0:
+  version "1.5.8"
+  resolved "https://registry.yarnpkg.com/formik/-/formik-1.5.8.tgz#eee8cd345effe46839bc748c7f920486f12f14b0"
+  integrity sha512-fNvPe+ddbh+7xiByT25vuso2p2hseG/Yvuj211fV1DbCjljUEG9OpgRpcb7g7O3kxHX/q31cbZDzMxJXPWSNwA==
+  dependencies:
+    create-react-context "^0.2.2"
+    deepmerge "^2.1.1"
+    hoist-non-react-statics "^3.3.0"
+    lodash "^4.17.14"
+    lodash-es "^4.17.14"
+    prop-types "^15.6.1"
+    react-fast-compare "^2.0.1"
+    tiny-warning "^1.0.2"
+    tslib "^1.9.3"
+
 forwarded@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -14739,7 +14830,7 @@ isobject@^4.0.0:
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
   integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
 
-isomorphic-fetch@^2.2.1:
+isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
   integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
@@ -16122,6 +16213,11 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+lodash-es@^4.17.14:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
+  integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
+
 lodash._reinterpolate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -16582,6 +16678,11 @@ markdown-loader@^5.1.0:
     loader-utils "^1.2.3"
     marked "^0.7.0"
 
+markdown-table@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60"
+  integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==
+
 markdown-table@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b"
@@ -16645,6 +16746,13 @@ mdast-add-list-metadata@1.0.1:
   dependencies:
     unist-util-visit-parents "1.1.2"
 
+mdast-util-compact@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz#d531bb7667b5123abf20859be086c4d06c894593"
+  integrity sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==
+  dependencies:
+    unist-util-visit "^1.1.0"
+
 mdast-util-compact@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz#cabc69a2f43103628326f35b1acf735d55c99490"
@@ -18723,7 +18831,7 @@ parse-asn1@^5.0.0:
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
 
-parse-entities@^1.1.0, parse-entities@^1.1.2:
+parse-entities@^1.0.2, parse-entities@^1.1.0, parse-entities@^1.1.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50"
   integrity sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==
@@ -19805,6 +19913,13 @@ promise.prototype.finally@^3.1.0:
     es-abstract "^1.17.0-next.0"
     function-bind "^1.1.1"
 
+promise@^7.1.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+  dependencies:
+    asap "~2.0.3"
+
 promise@~1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/promise/-/promise-1.3.0.tgz#e5cc9a4c8278e4664ffedc01c7da84842b040175"
@@ -19850,6 +19965,11 @@ proper-lockfile@^4.0.0, proper-lockfile@^4.1.1:
     retry "^0.12.0"
     signal-exit "^3.0.2"
 
+property-expr@^1.5.0:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f"
+  integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==
+
 property-information@^5.0.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943"
@@ -20119,6 +20239,15 @@ qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
+query-string@*, query-string@^6.13.1, query-string@^6.2.0:
+  version "6.13.1"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
+  integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
+  dependencies:
+    decode-uri-component "^0.2.0"
+    split-on-first "^1.0.0"
+    strict-uri-encode "^2.0.0"
+
 query-string@^4.1.0:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -20136,15 +20265,6 @@ query-string@^5.0.1:
     object-assign "^4.1.0"
     strict-uri-encode "^1.0.0"
 
-query-string@^6.13.1:
-  version "6.13.1"
-  resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
-  integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
-  dependencies:
-    decode-uri-component "^0.2.0"
-    split-on-first "^1.0.0"
-    strict-uri-encode "^2.0.0"
-
 querystring-es3@^0.2.0, querystring-es3@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -20380,6 +20500,11 @@ react-error-overlay@^6.0.3:
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
   integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
 
+react-fast-compare@^2.0.1:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
+  integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
+
 react-fast-compare@^3.0.1:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
@@ -20454,7 +20579,7 @@ react-lifecycles-compat@^3.0.4:
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
-react-markdown@^4.3.1:
+react-markdown@^4.0.6, react-markdown@^4.3.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.3.1.tgz#39f0633b94a027445b86c9811142d05381300f2f"
   integrity sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==
@@ -20980,6 +21105,11 @@ regenerator-runtime@^0.11.0:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
+regenerator-runtime@^0.12.0:
+  version "0.12.1"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
+  integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
+
 regenerator-runtime@^0.13.2:
   version "0.13.3"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
@@ -21121,6 +21251,27 @@ remark-parse@^5.0.0:
     vfile-location "^2.0.0"
     xtend "^4.0.1"
 
+remark-parse@^6.0.0:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a"
+  integrity sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==
+  dependencies:
+    collapse-white-space "^1.0.2"
+    is-alphabetical "^1.0.0"
+    is-decimal "^1.0.0"
+    is-whitespace-character "^1.0.0"
+    is-word-character "^1.0.0"
+    markdown-escapes "^1.0.0"
+    parse-entities "^1.1.0"
+    repeat-string "^1.5.4"
+    state-toggle "^1.0.0"
+    trim "0.0.1"
+    trim-trailing-lines "^1.0.0"
+    unherit "^1.0.4"
+    unist-util-remove-position "^1.0.0"
+    vfile-location "^2.0.0"
+    xtend "^4.0.1"
+
 remark-parse@^8.0.0:
   version "8.0.3"
   resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1"
@@ -21143,6 +21294,26 @@ remark-parse@^8.0.0:
     vfile-location "^3.0.0"
     xtend "^4.0.1"
 
+remark-stringify@^6.0.0:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088"
+  integrity sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==
+  dependencies:
+    ccount "^1.0.0"
+    is-alphanumeric "^1.0.0"
+    is-decimal "^1.0.0"
+    is-whitespace-character "^1.0.0"
+    longest-streak "^2.0.1"
+    markdown-escapes "^1.0.0"
+    markdown-table "^1.1.0"
+    mdast-util-compact "^1.0.0"
+    parse-entities "^1.0.2"
+    repeat-string "^1.5.4"
+    state-toggle "^1.0.0"
+    stringify-entities "^1.0.1"
+    unherit "^1.0.4"
+    xtend "^4.0.1"
+
 remark-stringify@^8.0.0:
   version "8.1.1"
   resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-8.1.1.tgz#e2a9dc7a7bf44e46a155ec78996db896780d8ce5"
@@ -21163,6 +21334,15 @@ remark-stringify@^8.0.0:
     unherit "^1.0.4"
     xtend "^4.0.1"
 
+remark@^10.0.1:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df"
+  integrity sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==
+  dependencies:
+    remark-parse "^6.0.0"
+    remark-stringify "^6.0.0"
+    unified "^7.0.0"
+
 remark@^12.0.0:
   version "12.0.1"
   resolved "https://registry.yarnpkg.com/remark/-/remark-12.0.1.tgz#f1ddf68db7be71ca2bad0a33cd3678b86b9c709f"
@@ -21959,7 +22139,7 @@ set-value@^2.0.0, set-value@^2.0.1:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
-setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
   integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -22858,6 +23038,16 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
+stringify-entities@^1.0.1:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7"
+  integrity sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==
+  dependencies:
+    character-entities-html4 "^1.0.0"
+    character-entities-legacy "^1.0.0"
+    is-alphanumerical "^1.0.0"
+    is-hexadecimal "^1.0.0"
+
 stringify-entities@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.1.tgz#32154b91286ab0869ab2c07696223bd23b6dbfc0"
@@ -22972,6 +23162,11 @@ strip-json-comments@~2.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
+strip-markdown@^3.0.3:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/strip-markdown/-/strip-markdown-3.1.2.tgz#172f6f89f9a98896e65a65422e0507f2bbac1667"
+  integrity sha512-NjwW6CEefesmHQPs7lof/lgnSriqUnRNOWpnrNPq9A7/yOCdnhaB7DcxlhYuN7WiiRUe349aitAsTQ/ajM9Dmw==
+
 strip-outer@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631"
@@ -23277,6 +23472,11 @@ symbol.prototype.description@^1.0.0:
     es-abstract "^1.17.0-next.1"
     has-symbols "^1.0.1"
 
+synchronous-promise@^2.0.5:
+  version "2.0.13"
+  resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
+  integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
+
 table@^5.2.3, table@^5.4.6:
   version "5.4.6"
   resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -23735,7 +23935,7 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
   integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
 
-tiny-warning@^1.0.0, tiny-warning@^1.0.3:
+tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -23863,6 +24063,11 @@ toposort@^1.0.0:
   resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
   integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
 
+toposort@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
+  integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
+
 touch@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
@@ -24220,6 +24425,11 @@ typescript@^3.0.3, typescript@^3.7.2, typescript@^3.7.5, typescript@^3.8.3, type
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
   integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
 
+ua-parser-js@^0.7.18:
+  version "0.7.21"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
+  integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
+
 uc.micro@^1.0.1, uc.micro@^1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
@@ -24324,6 +24534,20 @@ unified@^6.1.5:
     vfile "^2.0.0"
     x-is-string "^0.1.0"
 
+unified@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13"
+  integrity sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    "@types/vfile" "^3.0.0"
+    bail "^1.0.0"
+    extend "^3.0.0"
+    is-plain-obj "^1.1.0"
+    trough "^1.0.0"
+    vfile "^3.0.0"
+    x-is-string "^0.1.0"
+
 unified@^9.0.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/unified/-/unified-9.1.0.tgz#7ba82e5db4740c47a04e688a9ca8335980547410"
@@ -24842,6 +25066,14 @@ vfile-location@^3.0.0:
   resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.0.1.tgz#d78677c3546de0f7cd977544c367266764d31bb3"
   integrity sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==
 
+vfile-message@*:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
+  integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
+  dependencies:
+    "@types/unist" "^2.0.0"
+    unist-util-stringify-position "^2.0.0"
+
 vfile-message@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1"
@@ -24878,6 +25110,16 @@ vfile@^2.0.0:
     unist-util-stringify-position "^1.0.0"
     vfile-message "^1.0.0"
 
+vfile@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803"
+  integrity sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==
+  dependencies:
+    is-buffer "^2.0.0"
+    replace-ext "1.0.0"
+    unist-util-stringify-position "^1.0.0"
+    vfile-message "^1.0.0"
+
 vinyl-fs@^3.0.1:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7"
@@ -25881,6 +26123,18 @@ yoga-layout-prebuilt@^1.9.3:
   dependencies:
     "@types/yoga-layout" "1.9.2"
 
+yup@^0.26.10:
+  version "0.26.10"
+  resolved "https://registry.yarnpkg.com/yup/-/yup-0.26.10.tgz#3545839663289038faf25facfc07e11fd67c0cb1"
+  integrity sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw==
+  dependencies:
+    "@babel/runtime" "7.0.0"
+    fn-name "~2.0.1"
+    lodash "^4.17.10"
+    property-expr "^1.5.0"
+    synchronous-promise "^2.0.5"
+    toposort "^2.0.2"
+
 zepto@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/zepto/-/zepto-1.2.0.tgz#e127bd9e66fd846be5eab48c1394882f7c0e4f98"