Browse Source

Upgrade joy-roles + fix components flow

Leszek Wiesner 4 years ago
parent
commit
17933d4c03
47 changed files with 1415 additions and 998 deletions
  1. 2 1
      package.json
  2. 0 1
      pioneer/.eslintignore
  3. 4 2
      pioneer/package.json
  4. 3 0
      pioneer/packages/apps-routing/src/index.ts
  5. 6 8
      pioneer/packages/apps-routing/src/joy-roles.ts
  6. 11 0
      pioneer/packages/apps/webpack.base.config.js
  7. 0 0
      pioneer/packages/joy-roles/.skip-build
  8. 3 3
      pioneer/packages/joy-roles/package.json
  9. 6 6
      pioneer/packages/joy-roles/src/balances.spec.ts
  10. 7 7
      pioneer/packages/joy-roles/src/balances.ts
  11. 28 82
      pioneer/packages/joy-roles/src/classifiers.spec.ts
  12. 6 7
      pioneer/packages/joy-roles/src/classifiers.ts
  13. 39 40
      pioneer/packages/joy-roles/src/flows/apply.controller.tsx
  14. 45 55
      pioneer/packages/joy-roles/src/flows/apply.tsx
  15. 36 28
      pioneer/packages/joy-roles/src/index.tsx
  16. 15 34
      pioneer/packages/joy-roles/src/mocks.ts
  17. 127 167
      pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx
  18. 20 7
      pioneer/packages/joy-roles/src/tabs/MyRoles.controller.tsx
  19. 19 22
      pioneer/packages/joy-roles/src/tabs/MyRoles.elements.stories.tsx
  20. 2 3
      pioneer/packages/joy-roles/src/tabs/MyRoles.tsx
  21. 12 4
      pioneer/packages/joy-roles/src/tabs/Opportunities.controller.tsx
  22. 52 53
      pioneer/packages/joy-roles/src/tabs/Opportunities.elements.stories.tsx
  23. 9 17
      pioneer/packages/joy-roles/src/tabs/Opportunities.stories.tsx
  24. 3 3
      pioneer/packages/joy-roles/src/tabs/Opportunities.tsx
  25. 20 15
      pioneer/packages/joy-roles/src/tabs/Opportunity.controller.tsx
  26. 6 2
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx
  27. 16 18
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.stories.tsx
  28. 1 1
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx
  29. 53 80
      pioneer/packages/joy-roles/src/transport.mock.ts
  30. 103 179
      pioneer/packages/joy-roles/src/transport.substrate.ts
  31. 4 5
      pioneer/packages/joy-roles/src/transport.ts
  32. 0 18
      pioneer/packages/joy-utils-old/src/Loadable.tsx
  33. 0 76
      pioneer/packages/joy-utils-old/src/View.tsx
  34. 0 23
      pioneer/packages/joy-utils/src/functions/accounts.ts
  35. 7 0
      pioneer/packages/joy-utils/src/react/components/index.tsx
  36. 1 1
      pioneer/packages/joy-utils/src/react/helpers/Controller.tsx
  37. 5 1
      pioneer/packages/joy-utils/src/react/helpers/Observable.ts
  38. 0 0
      pioneer/packages/joy-utils/src/react/helpers/Subscribable.ts
  39. 5 0
      pioneer/packages/joy-utils/src/react/helpers/index.ts
  40. 0 0
      pioneer/packages/joy-utils/src/react/helpers/memoize.ts
  41. 22 0
      pioneer/packages/joy-utils/src/react/hocs/Loadable.tsx
  42. 71 0
      pioneer/packages/joy-utils/src/react/hocs/View.tsx
  43. 2 0
      pioneer/packages/joy-utils/src/react/hocs/index.ts
  44. 9 1
      pioneer/packages/joy-utils/src/transport/mock/base.ts
  45. 6 0
      pioneer/packages/react-components/src/styles/joystream.ts
  46. 3 14
      pioneer/tsconfig.json
  47. 626 14
      yarn.lock

+ 2 - 1
package.json

@@ -27,7 +27,8 @@
     "pioneer/packages/joy-members",
     "pioneer/packages/joy-pages",
     "pioneer/packages/joy-election",
-    "pioneer/packages/joy-proposals"
+    "pioneer/packages/joy-proposals",
+    "pioneer/packages/joy-roles"
   ],
   "resolutions": {
     "@polkadot/api": "1.26.1",

+ 0 - 1
pioneer/.eslintignore

@@ -5,7 +5,6 @@ packages/old-apps/*
 packages/joy-forum/*
 packages/joy-help/*
 packages/joy-media/*
-packages/joy-roles/*
 packages/joy-settings/*
 packages/joy-utils-old/*
 .eslintrc.js

+ 4 - 2
pioneer/package.json

@@ -40,7 +40,7 @@
     "@types/chart.js": "^2.9.23",
     "@types/file-saver": "^2.0.1",
     "@types/i18next": "^13.0.0",
-    "@types/jest": "^26.0.7",
+    "@types/jest": "^26.0.10",
     "@types/react-beautiful-dnd": "^13.0.0",
     "@types/react-copy-to-clipboard": "^4.3.0",
     "@types/react-dom": "^16.9.8",
@@ -78,7 +78,9 @@
     "storybook-react-router": "^1.0.8",
     "typescript": "^3.9.7",
     "eslint-plugin-header": "^3.0.0",
-    "eslint-plugin-sort-destructure-keys": "^1.3.5"
+    "eslint-plugin-sort-destructure-keys": "^1.3.5",
+    "jest": "^26.4.1",
+    "ts-jest": "^26.2.0"
   },
   "dependencies": {
     "@types/lodash": "^4.14.138",

+ 3 - 0
pioneer/packages/apps-routing/src/index.ts

@@ -22,11 +22,13 @@ import members from './joy-members';
 import { terms, privacyPolicy } from './joy-pages';
 import election from './joy-election';
 import proposals from './joy-proposals';
+import roles from './joy-roles';
 
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
   return appSettings.uiMode === 'light'
     ? [
       members(t),
+      roles(t),
       election(t),
       proposals(t),
       null,
@@ -36,6 +38,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
     ]
     : [
       members(t),
+      roles(t),
       election(t),
       proposals(t),
       null,

+ 6 - 8
pioneer/packages/old-apps/apps-routing/src/joy-roles.ts → pioneer/packages/apps-routing/src/joy-roles.ts

@@ -1,9 +1,9 @@
-import { Routes } from './types';
+import { Route } from './types';
 
 import Roles from '@polkadot/joy-roles/index';
 
-export default ([
-  {
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
     Component: Roles,
     display: {
       needsApi: [
@@ -11,10 +11,8 @@ export default ([
         'query.storageWorkingGroup.mint'
       ]
     },
-    i18n: {
-      defaultValue: 'Working groups'
-    },
+    text: t<string>('nav.roles', 'Working groups', { ns: 'apps-routing' }),
     icon: 'users',
     name: 'working-groups'
-  }
-] as Routes);
+  };
+}

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

@@ -69,6 +69,17 @@ function createWebpack (ENV, context) {
             }
           ]
         },
+        {
+          test: /\.s[ac]ss$/i,
+          use: [
+            // Creates `style` nodes from JS strings
+            'style-loader',
+            // Translates CSS into CommonJS
+            'css-loader',
+            // Compiles Sass to CSS
+            'sass-loader'
+          ]
+        },
         {
           include: /node_modules/,
           test: /\.css$/,

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


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

@@ -11,10 +11,10 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
+    "@babel/runtime": "^7.10.5",
     "@polkadot/joy-utils": "^0.1.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@types/faker": "^4.1.8",
     "faker": "^4.1.0",
     "marked": "^0.7.0",

+ 6 - 6
pioneer/packages/joy-roles/src/balances.spec.ts

@@ -1,12 +1,12 @@
 import { Balance } from '@polkadot/types/interfaces';
-import { u128 } from '@polkadot/types';
+import { createMock } from '@joystream/types';
 import { Avg, AvgDelta, Min, Step, Sum } from './balances';
 
 describe('Balance arithmetic', (): void => {
   it('Can calculate a sum', (): void => {
     const input: Balance[] = [];
     for (let i = 0; i < 10; i++) {
-      input.push(new u128(i));
+      input.push(createMock('u128', i));
     }
 
     expect(Sum(input).toNumber()).toEqual(45);
@@ -15,7 +15,7 @@ describe('Balance arithmetic', (): void => {
   it('Can calculate an average', (): void => {
     const input: Balance[] = [];
     for (let i = 0; i < 10; i++) {
-      input.push(new u128(i));
+      input.push(createMock('u128', i));
     }
 
     expect(Avg(input).toNumber()).toEqual(4);
@@ -24,7 +24,7 @@ describe('Balance arithmetic', (): void => {
   it('Can calculate an average delta', (): void => {
     const input: Balance[] = [];
     for (let i = 0; i < 10; i++) {
-      input.push(new u128(i));
+      input.push(createMock('u128', i));
     }
 
     expect(AvgDelta(input).toNumber()).toEqual(1);
@@ -33,7 +33,7 @@ describe('Balance arithmetic', (): void => {
   it('Can calculate a step value with large numbers', (): void => {
     const input: Balance[] = [];
     for (let i = 0; i < 10; i++) {
-      input.push(new u128(i * 10));
+      input.push(createMock('u128', i * 10));
     }
 
     expect(Step(input).toNumber()).toEqual(4);
@@ -42,7 +42,7 @@ describe('Balance arithmetic', (): void => {
   it('Can calculate a step value with small numbers', (): void => {
     const input: Balance[] = [];
     for (let i = 0; i < 10; i++) {
-      input.push(new u128(i));
+      input.push(createMock('u128', i));
     }
 
     expect(Min(Step(input)).toNumber()).toEqual(1);

+ 7 - 7
pioneer/packages/joy-roles/src/balances.ts

@@ -1,15 +1,15 @@
 import { Balance } from '@polkadot/types/interfaces';
-import { u128 } from '@polkadot/types';
+import { createMock } from '@joystream/types';
 
-export const Zero = new u128(0);
-export const One = new u128(1);
+export const Zero = createMock('Balance', 0);
+export const One = createMock('Balance', 1);
 
-export const Add = (x: Balance, y: Balance): Balance => new u128(x.add(y));
-export const Sub = (x: Balance, y: Balance): Balance => new u128(x.sub(y));
+export const Add = (x: Balance, y: Balance): Balance => createMock('Balance', x.add(y));
+export const Sub = (x: Balance, y: Balance): Balance => createMock('Balance', x.sub(y));
 export const Sum = (balances: Balance[]): Balance => balances.reduce(Add, Zero);
 
 export const Avg = (xs: Balance[]): Balance =>
-  xs[0] === undefined ? Zero : new u128(Sum(xs).divn(xs.length));
+  xs[0] === undefined ? Zero : createMock('Balance', Sum(xs).divn(xs.length));
 
 export const AvgDelta = (xs: Balance[]): Balance => {
   if (xs.length < 2) {
@@ -28,7 +28,7 @@ export const AvgDelta = (xs: Balance[]): Balance => {
 };
 
 // An average value to 'step' up balances, like on the nudge controls for a slider
-export const Step = (xs: Balance[], ticks = 10): Balance => new u128(Avg(xs).divn(ticks));
+export const Step = (xs: Balance[], ticks = 10): Balance => createMock('Balance', Avg(xs).divn(ticks));
 export const Min = (x: Balance, min: Balance = One): Balance => x.gte(min) ? x : min;
 export const Sort = (xs: Balance[]): Balance[] => {
   xs.sort((a, b): number => {

+ 28 - 82
pioneer/packages/joy-roles/src/classifiers.spec.ts

@@ -1,18 +1,11 @@
-import { Option, Text, u32 } from '@polkadot/types';
-import {
-  AcceptingApplications,
-  ActiveOpeningStage,
-  ApplicationRationingPolicy,
-  StakingPolicy,
-  Opening, OpeningStage,
-  ReviewPeriod
-} from '@joystream/types/hiring';
+import { Opening } from '@joystream/types/hiring';
 
 import {
   OpeningState,
   IBlockQueryer,
   classifyOpeningStage, OpeningStageClassification
 } from './classifiers';
+import { createMock } from '@joystream/types';
 
 class MockBlockQueryer {
   hash: string
@@ -36,8 +29,7 @@ class MockBlockQueryer {
     return this.timestamp;
   }
 
-  // eslint-disable-next-line @typescript-eslint/require-await
-  async expectedBlockTime (): Promise<number> {
+  expectedBlockTime (): number {
     return 6;
   }
 }
@@ -58,18 +50,15 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
     {
       description: 'WaitingToBegin',
       input: {
-        opening: new Opening({
-          created: new u32(100),
-          stage: new OpeningStage({
+        opening: createMock('Opening', {
+          created: 100,
+          stage: createMock('OpeningStage', {
             WaitingToBegin: {
-              begins_at_block: new u32(100)
+              begins_at_block: 100
             }
           }),
-          max_review_period_length: new u32(100),
-          application_rationing_policy: new Option(ApplicationRationingPolicy),
-          application_staking_policy: new Option(StakingPolicy),
-          role_staking_policy: new Option(StakingPolicy),
-          human_readable_text: new Text()
+          max_review_period_length: 100,
+          human_readable_text: ''
         }),
         queryer: new MockBlockQueryer('somehash', now)
       },
@@ -83,22 +72,19 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
     {
       description: 'Accepting applications',
       input: {
-        opening: new Opening({
-          created: new u32(100),
-          stage: new OpeningStage({
+        opening: createMock('Opening', {
+          created: 100,
+          stage: createMock('OpeningStage', {
             Active: {
-              stage: new ActiveOpeningStage({
-                acceptingApplications: new AcceptingApplications({
-                  started_accepting_applicants_at_block: new u32(100)
-                })
+              stage: createMock('ActiveOpeningStage', {
+                acceptingApplications: {
+                  started_accepting_applicants_at_block: 100
+                }
               })
             }
           }),
-          max_review_period_length: new u32(100),
-          application_rationing_policy: new Option(ApplicationRationingPolicy),
-          application_staking_policy: new Option(StakingPolicy),
-          role_staking_policy: new Option(StakingPolicy),
-          human_readable_text: new Text()
+          max_review_period_length: 100,
+          human_readable_text: ''
         }),
         queryer: new MockBlockQueryer('somehash', now)
       },
@@ -112,23 +98,20 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
     {
       description: 'In review period',
       input: {
-        opening: new Opening({
-          created: new u32(100),
-          stage: new OpeningStage({
+        opening: createMock('Opening', {
+          created: 100,
+          stage: createMock('OpeningStage', {
             Active: {
-              stage: new ActiveOpeningStage({
-                reviewPeriod: new ReviewPeriod({
-                  started_accepting_applicants_at_block: new u32(100),
-                  started_review_period_at_block: new u32(100)
-                })
+              stage: createMock('ActiveOpeningStage', {
+                reviewPeriod: {
+                  started_accepting_applicants_at_block: 100,
+                  started_review_period_at_block: 100
+                }
               })
             }
           }),
-          max_review_period_length: new u32(14400),
-          application_rationing_policy: new Option(ApplicationRationingPolicy),
-          application_staking_policy: new Option(StakingPolicy),
-          role_staking_policy: new Option(StakingPolicy),
-          human_readable_text: new Text()
+          max_review_period_length: 14400,
+          human_readable_text: ''
         }),
         queryer: new MockBlockQueryer('somehash', now)
       },
@@ -141,43 +124,6 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
         review_end_time: new Date('2020-01-24T11:47:04.433Z')
       }
     }
-    /*
-     * jest is having trouble with the enum type
-    {
-      description: "Deactivated: cancelled",
-      input: {
-        opening: new Opening({
-          created: new u32(100),
-          stage: new OpeningStage({
-            'Active': {
-              stage: new ActiveOpeningStage({
-                reviewPeriod: new Deactivated({
-                  cause: new OpeningDeactivationCause(
-        OpeningDeactivationCauseKeys.CancelledBeforeActivation,
-                  ),
-                  deactivated_at_block: new u32(123),
-                  started_accepting_applicants_at_block: new u32(100),
-                  started_review_period_at_block: new Option(u32, 100),
-                })
-              })
-            }
-          }),
-          max_review_period_length: new u32(100),
-          application_rationing_policy: new Option(ApplicationRationingPolicy),
-          application_staking_policy: new Option(StakingPolicy),
-          role_staking_policy: new Option(StakingPolicy),
-          human_readable_text: new Text(),
-        }),
-        queryer: new MockBlockQueryer("somehash", now),
-      },
-      output: {
-        state: OpeningState.Cancelled,
-        starting_block: 123,
-        starting_block_hash: "somehash",
-        starting_time: now,
-      },
-    },
-    */
   ];
 
   cases.forEach((test: Test) => {

+ 6 - 7
pioneer/packages/joy-roles/src/classifiers.ts

@@ -1,6 +1,6 @@
 import moment from 'moment';
 
-import { Option, u128 } from '@polkadot/types';
+import { Option } from '@polkadot/types';
 import { Balance } from '@polkadot/types/interfaces';
 
 import {
@@ -25,6 +25,7 @@ import {
   RoleStakeRequirement,
   StakeType
 } from './StakeRequirement';
+import { createMock } from '@joystream/types';
 
 export enum CancelledReason {
   ApplicantCancelled = 0,
@@ -53,7 +54,7 @@ export interface OpeningStageClassification {
 export interface IBlockQueryer {
   blockHash(height: number): Promise<string>;
   blockTimestamp(height: number): Promise<Date>;
-  expectedBlockTime: () => Promise<number>;
+  expectedBlockTime: () => number;
 }
 
 async function classifyActiveOpeningStageAcceptingApplications (
@@ -76,10 +77,8 @@ async function classifyActiveOpeningStageReviewPeriod (
 ): Promise<OpeningStageClassification> {
   const blockNumber = stage.started_review_period_at_block.toNumber();
   const maxReviewLengthInBlocks = opening.max_review_period_length.toNumber();
-  const [startDate, blockTime] = await Promise.all([
-    queryer.blockTimestamp(blockNumber),
-    queryer.expectedBlockTime()
-  ]);
+  const startDate = await queryer.blockTimestamp(blockNumber);
+  const blockTime = queryer.expectedBlockTime();
   const endDate = moment(startDate).add(maxReviewLengthInBlocks * blockTime, 's');
 
   return {
@@ -216,7 +215,7 @@ function classifyStakeRequirement<T extends StakeRequirement> (
   option: Option<StakingPolicy>
 ): T {
   if (option.isNone) {
-    return new constructor(new u128(0));
+    return new constructor(createMock('Balance', 0));
   }
 
   const policy = option.unwrap();

+ 39 - 40
pioneer/packages/joy-roles/src/flows/apply.controller.tsx

@@ -1,16 +1,14 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import { formatBalance } from '@polkadot/util';
-import { u128 } from '@polkadot/types';
 import { Balance } from '@polkadot/types/interfaces';
-import AccountId from '@polkadot/types/primitive/Generic/AccountId';
+import AccountId from '@polkadot/types/generic/AccountId';
 
-import { Controller, View } from '@polkadot/joy-utils/index';
+import { Controller } from '@polkadot/joy-utils/react/helpers';
+import { View } from '@polkadot/joy-utils/react/hocs';
 
 import { GenericJoyStreamRoleSchema } from '@joystream/types/hiring/schemas/role.schema.typings';
 
-import { Container } from 'semantic-ui-react';
-
 import { ITransport } from '../transport';
 
 import { keyPairDetails, FlowModal, ProgressSteps } from './apply';
@@ -18,6 +16,7 @@ import { keyPairDetails, FlowModal, ProgressSteps } from './apply';
 import { OpeningStakeAndApplicationStatus } from '../tabs/Opportunities';
 import { Min, Step, Sum } from '../balances';
 import { WorkingGroups, AvailableGroups } from '../working_groups';
+import { createMock } from '@joystream/types';
 
 type State = {
   // Input data from state
@@ -48,14 +47,14 @@ type State = {
 
 const newEmptyState = (): State => {
   return {
-    applicationStake: new u128(0),
-    roleStake: new u128(0),
+    applicationStake: createMock('u128', 0),
+    roleStake: createMock('u128', 0),
     appDetails: {},
     hasError: false,
     transactionDetails: new Map<string, string>(),
     roleKeyNameBase: '',
     roleKeyName: '',
-    txKeyAddress: new AccountId(),
+    txKeyAddress: createMock('AccountId', undefined),
     activeStep: 0,
     txInProgress: false,
     complete: false
@@ -123,6 +122,7 @@ export class ApplyController extends Controller<State, ITransport> {
           this.state.activeStep = this.state.hasConfirmStep
             ? ProgressSteps.ConfirmStakes
             : ProgressSteps.ApplicationDetails;
+          this.state.complete = false;
 
           this.state.roleKeyNameBase = hrt.job.title + ' role key';
 
@@ -224,38 +224,37 @@ export class ApplyController extends Controller<State, ITransport> {
 }
 
 export const ApplyView = View<ApplyController, State>(
-  (state, controller, params) => {
-    controller.findOpening(params.get('id'), params.get('group'));
+  ({ state, controller, params }) => {
+    useEffect(() => {
+      controller.findOpening(params.get('id'), params.get('group'));
+    }, [params.get('id'), params.get('group')])
     return (
-      <Container className="apply-flow">
-        <div className="dimmer"></div>
-        <FlowModal
-          role={state.role!}
-          applications={state.applications!}
-          keypairs={state.keypairs!}
-          hasConfirmStep={state.hasConfirmStep!}
-          step={state.step!}
-          slots={state.slots!}
-          transactionDetails={state.transactionDetails}
-          roleKeyName={state.roleKeyName}
-          prepareApplicationTransaction={(...args) => controller.prepareApplicationTransaction(...args)}
-          makeApplicationTransaction={() => controller.makeApplicationTransaction()}
-          applicationStake={state.applicationStake}
-          setApplicationStake={(v) => controller.setApplicationStake(v)}
-          roleStake={state.roleStake}
-          setRoleStake={(v) => controller.setRoleStake(v)}
-          appDetails={state.appDetails}
-          setAppDetails={(v) => controller.setAppDetails(v)}
-          txKeyAddress={state.txKeyAddress}
-          setTxKeyAddress={(v) => controller.setTxKeyAddress(v)}
-          activeStep={state.activeStep}
-          setActiveStep={(v) => controller.setActiveStep(v)}
-          txInProgress={state.txInProgress}
-          setTxInProgress={(v) => controller.setTxInProgress(v)}
-          complete={state.complete}
-          setComplete={(v) => controller.setComplete(v)}
-        />
-      </Container>
+      <FlowModal
+        role={state.role!}
+        applications={state.applications!}
+        keypairs={state.keypairs!}
+        hasConfirmStep={state.hasConfirmStep!}
+        step={state.step!}
+        slots={state.slots!}
+        transactionDetails={state.transactionDetails}
+        roleKeyName={state.roleKeyName}
+        prepareApplicationTransaction={controller.prepareApplicationTransaction.bind(controller)}
+        makeApplicationTransaction={controller.makeApplicationTransaction.bind(controller)}
+        applicationStake={state.applicationStake}
+        setApplicationStake={controller.setApplicationStake.bind(controller)}
+        roleStake={state.roleStake}
+        setRoleStake={controller.setRoleStake.bind(controller)}
+        appDetails={state.appDetails}
+        setAppDetails={controller.setAppDetails.bind(controller)}
+        txKeyAddress={state.txKeyAddress}
+        setTxKeyAddress={controller.setTxKeyAddress.bind(controller)}
+        activeStep={state.activeStep}
+        setActiveStep={controller.setActiveStep.bind(controller)}
+        txInProgress={state.txInProgress}
+        setTxInProgress={controller.setTxInProgress.bind(controller)}
+        complete={state.complete}
+        setComplete={controller.setComplete.bind(controller)}
+      />
     );
   }
 );

+ 45 - 55
pioneer/packages/joy-roles/src/flows/apply.tsx

@@ -3,12 +3,8 @@ import { useHistory, Link } from 'react-router-dom';
 
 import { formatBalance } from '@polkadot/util';
 import { Balance } from '@polkadot/types/interfaces';
-import {
-  GenericAccountId,
-  u128
-} from '@polkadot/types';
 
-import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
+import { useMyAccount } from '@polkadot/joy-utils/react/hooks';
 
 import {
   Accordion,
@@ -28,7 +24,7 @@ import {
 } from 'semantic-ui-react';
 
 import Identicon from '@polkadot/react-identicon';
-import AccountId from '@polkadot/types/primitive/Generic/AccountId';
+import AccountId from '@polkadot/types/generic/AccountId';
 
 import {
   GenericJoyStreamRoleSchema,
@@ -44,8 +40,9 @@ import {
 } from '../tabs/Opportunities';
 import { IStakeRequirement } from '../StakeRequirement';
 
-import { Loadable } from '@polkadot/joy-utils/index';
+import { Loadable } from '@polkadot/joy-utils/react/hocs';
 import { Add } from '../balances';
+import { createMock } from '@joystream/types';
 
 type accordionProps = {
   title: string;
@@ -92,6 +89,7 @@ export type keyPairDetails = {
 
 export type FundSourceSelectorProps = {
   keypairs: keyPairDetails[];
+  selected?: AccountId;
   totalStake?: Balance;
 }
 
@@ -105,7 +103,7 @@ export function FundSourceSelector (props: FundSourceSelectorProps & FundSourceC
 
   const onChangeDropdown = (e: any, { value }: any) => {
     if (typeof props.addressCallback !== 'undefined') {
-      props.addressCallback(new GenericAccountId(value));
+      props.addressCallback(createMock('AccountId', value));
     }
   };
 
@@ -115,7 +113,7 @@ export function FundSourceSelector (props: FundSourceSelectorProps & FundSourceC
     }
   };
 
-  props.keypairs.map((v) => {
+  props.keypairs.forEach((v) => {
     if (props.totalStake && v.balance.lt(props.totalStake)) {
       return;
     }
@@ -134,13 +132,6 @@ export function FundSourceSelector (props: FundSourceSelectorProps & FundSourceC
     });
   });
 
-  useEffect(() => {
-    if (pairs.length > 0 && typeof props.addressCallback !== 'undefined') {
-      props.addressCallback(new GenericAccountId(pairs[0].accountId));
-    }
-  }, []);
-
-  const accCtx = useMyAccount();
   let passphraseCallback = null;
   if (props.passphraseCallback) {
     passphraseCallback = (
@@ -164,7 +155,7 @@ export function FundSourceSelector (props: FundSourceSelectorProps & FundSourceC
           selection
           options={pairs}
           onChange={onChangeDropdown}
-          defaultValue={accCtx.state.inited ? accCtx.state.address : undefined}
+          value={props.selected && props.selected.toString()}
         />
       </Form.Field>
       {passphraseCallback}
@@ -211,7 +202,7 @@ export function StakeRankSelector (props: StakeRankSelectorProps) {
   props.slots.forEach(slotStake => props.stake.gt(slotStake.sub(props.otherStake)) && --estimatedSlot);
 
   const changeValue = (e: any, { value }: any) => {
-    const newStake = new u128(value);
+    const newStake = createMock('Balance', value);
     props.setStake(newStake);
   };
 
@@ -584,7 +575,7 @@ export function ConfirmStakes2Up (props: ConfirmStakes2UpProps) {
                 <Label color={valid ? 'green' : 'red'}>
                   <Icon name='times circle' />
                   Your current combined stake
-                  <Label.Detail>{formatBalance(new u128(props.selectedApplicationStake.add(props.selectedRoleStake)))}</Label.Detail>
+                  <Label.Detail>{formatBalance(props.selectedApplicationStake.add(props.selectedRoleStake))}</Label.Detail>
                 </Label>
                 { maxNumberOfApplications > 0 && (
                   <Label color='grey'>
@@ -613,10 +604,10 @@ type StakeRankMiniSelectorProps = {
 function StakeRankMiniSelector (props: StakeRankMiniSelectorProps) {
   const changeValue = (e: any, { value }: any) => {
     if (value < 0) {
-      props.setValue(new u128(0));
+      props.setValue(createMock('Balance', 0));
       return;
     }
-    const newStake = new u128(value);
+    const newStake = createMock('Balance', value);
     props.setValue(newStake);
   };
 
@@ -919,6 +910,7 @@ export const SubmitApplicationStage = (props: SubmitApplicationStageProps) => {
         <Label attached='top'>Source of funds</Label>
         <p>Please select the account that will be used as the source of funds.</p>
         <FundSourceSelector {...props}
+          selected={props.keyAddress}
           addressCallback={props.setKeyAddress}
         />
       </Segment>
@@ -1033,10 +1025,12 @@ export const FlowModal = Loadable<FlowModalProps>(
       complete, setComplete
     } = props;
 
-    const accCtx = useMyAccount();
-    if (txKeyAddress.isEmpty) {
-      setTxKeyAddress(new AccountId(accCtx.state.address));
-    }
+    const accContext = useMyAccount();
+    useEffect(() => {
+      if (txKeyAddress.isEmpty) {
+        setTxKeyAddress(createMock('AccountId', accContext.state.address));
+      }
+    }, [txKeyAddress]);
 
     const history = useHistory();
     const cancel = () => {
@@ -1095,35 +1089,6 @@ export const FlowModal = Loadable<FlowModalProps>(
       setSelectedRoleStake: setRoleStake
     };
 
-    const stages: { [k in ProgressSteps]: JSX.Element } = {
-      [ProgressSteps.ConfirmStakes]: (<ConfirmStakesStage
-        {...props}
-        nextTransition={enterApplicationDetailsState}
-        prevTransition={cancel}
-        {...setStakeProps}
-      />),
-
-      [ProgressSteps.ApplicationDetails]: (<ApplicationDetailsStage
-        setData={setAppDetails}
-        data={appDetails}
-        applicationDetails={props.role.application}
-        nextTransition={enterSubmitApplicationState}
-        prevTransition={() => { props.hasConfirmStep ? enterConfirmStakeState() : cancel(); }}
-      />),
-
-      [ProgressSteps.SubmitApplication]: (<SubmitApplicationStage
-        {...props}
-        nextTransition={enterDoneState}
-        prevTransition={enterApplicationDetailsState}
-        keyAddress={txKeyAddress}
-        setKeyAddress={setTxKeyAddress}
-        transactionDetails={props.transactionDetails}
-        totalStake={Add(applicationStake, roleStake)}
-      />),
-
-      [ProgressSteps.Done]: (<DoneStage {...props} roleKeyName={props.roleKeyName} />)
-    };
-
     const cancelText = complete ? 'Close' : 'Cancel application';
 
     return (
@@ -1147,7 +1112,32 @@ export const FlowModal = Loadable<FlowModalProps>(
           <Grid columns="equal">
             <Grid.Column width={11} className="main">
               <ProgressStepsView activeStep={activeStep} hasConfirmStep={props.hasConfirmStep} />
-              {stages[activeStep]}
+              { activeStep === ProgressSteps.ConfirmStakes && (<ConfirmStakesStage
+                  {...props}
+                  nextTransition={enterApplicationDetailsState}
+                  prevTransition={cancel}
+                  {...setStakeProps}
+                />
+              ) }
+              { activeStep === ProgressSteps.ApplicationDetails && (<ApplicationDetailsStage
+                  setData={setAppDetails}
+                  data={appDetails}
+                  applicationDetails={props.role.application}
+                  nextTransition={enterSubmitApplicationState}
+                  prevTransition={() => { props.hasConfirmStep ? enterConfirmStakeState() : cancel(); }}
+                />
+              ) }
+              { activeStep === ProgressSteps.SubmitApplication && (<SubmitApplicationStage
+                  {...props}
+                  nextTransition={enterDoneState}
+                  prevTransition={enterApplicationDetailsState}
+                  keyAddress={txKeyAddress}
+                  setKeyAddress={setTxKeyAddress}
+                  transactionDetails={props.transactionDetails}
+                  totalStake={Add(applicationStake, roleStake)}
+                />
+               ) }
+              { activeStep === ProgressSteps.Done && (<DoneStage {...props} roleKeyName={props.roleKeyName} />) }
             </Grid.Column>
             <Grid.Column width={5} className="summary">
               <Header as='h3'>{props.role.headline}</Header>

+ 36 - 28
pioneer/packages/joy-roles/src/index.tsx

@@ -4,13 +4,11 @@ import { ApiContext } from '@polkadot/react-api';
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 
-import { Route, Switch, RouteComponentProps } from 'react-router';
+import { Route, Switch } from 'react-router';
 import Tabs from '@polkadot/react-components/Tabs';
 import { withMulti } from '@polkadot/react-api/index';
 import QueueContext from '@polkadot/react-components/Status/Context';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
-
-import { ViewComponent } from '@polkadot/joy-utils/index';
+import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts';
 
 import { Transport } from './transport.substrate';
 
@@ -25,15 +23,6 @@ import './index.sass';
 
 import translate from './translate';
 
-const renderViewComponent = (Component: ViewComponent<any>, props?: RouteComponentProps) => {
-  let params = new Map<string, string>();
-  if (props && props.match.params) {
-    params = new Map<string, string>(Object.entries(props.match.params));
-  }
-
-  return <Component params={params} />;
-};
-
 type Props = AppProps & ApiProps & I18nProps & MyAccountProps
 
 export const App: React.FC<Props> = (props: Props) => {
@@ -53,20 +42,26 @@ export const App: React.FC<Props> = (props: Props) => {
 
   const { api } = useContext(ApiContext);
   const { queueExtrinsic } = useContext(QueueContext);
-  const transport = new Transport(api, queueExtrinsic);
+  const [transport] = useState(() => new Transport(api, queueExtrinsic));
 
-  const [wgCtrl] = useState(new WorkingGroupsController(transport));
-  const oppCtrl = new OpportunityController(transport, props.myMemberId);
-  const oppsCtrl = new OpportunitiesController(transport, props.myMemberId);
-  const [applyCtrl] = useState(new ApplyController(transport));
-  const myRolesCtrl = new MyRolesController(transport, props.myAddress);
-  const [adminCtrl] = useState(new AdminController(transport, api, queueExtrinsic));
+  const [wgCtrl] = useState(() => new WorkingGroupsController(transport));
+  const [oppCtrl] = useState(() => new OpportunityController(transport));
+  const [oppsCtrl] = useState(() => new OpportunitiesController(transport));
+  const [applyCtrl] = useState(() => new ApplyController(transport));
+  const [myRolesCtrl] = useState(() => new MyRolesController(transport));
+  const [adminCtrl] = useState(() => new AdminController(transport, api, queueExtrinsic));
 
   useEffect(() => {
     return () => {
       transport.unsubscribe();
     };
-  });
+  }, []);
+
+  useEffect(() => {
+    oppCtrl.setMemberId(props.myMemberId);
+    oppsCtrl.setMemberId(props.myMemberId);
+    myRolesCtrl.setMyAddress(props.myAddress);
+  }, [props.myMemberId, props.myAddress]);
 
   const { basePath } = props;
 
@@ -86,13 +81,26 @@ export const App: React.FC<Props> = (props: Props) => {
         />
       </header>
       <Switch>
-        <Route path={`${basePath}/opportunities/:group/:id([0-9]+)/apply`} render={(props) => renderViewComponent(ApplyView(applyCtrl), props)} />
-        <Route path={`${basePath}/opportunities/:group/:id([0-9]+)`} render={(props) => renderViewComponent(OpportunityView(oppCtrl), props)} />
-        <Route path={`${basePath}/opportunities/:group/:lead(lead)?`} render={(props) => renderViewComponent(OpportunitiesView(oppsCtrl), props)} />
-        <Route path={`${basePath}/opportunities`} render={() => renderViewComponent(OpportunitiesView(oppsCtrl))} />
-        <Route path={`${basePath}/my-roles`} render={() => renderViewComponent(MyRolesView(myRolesCtrl))} />
-        <Route path={`${basePath}/admin`} render={() => renderViewComponent(AdminView(adminCtrl))} />
-        <Route render={() => renderViewComponent(WorkingGroupsView(wgCtrl))} />
+        <Route
+          path={`${basePath}/opportunities/:group/:id([0-9]+)/apply`}
+          render={(props) => <ApplyView controller={applyCtrl} params={props.match.params}/>} />
+        <Route
+          path={`${basePath}/opportunities/:group/:id([0-9]+)`}
+          render={(props) => <OpportunityView controller={oppCtrl} params={props.match.params}/>} />
+        <Route
+          path={`${basePath}/opportunities/:group/:lead(lead)?`}
+          render={(props) => <OpportunitiesView controller={oppsCtrl} params={props.match.params}/>} />
+        <Route
+          path={`${basePath}/opportunities`}
+          render={(props) => <OpportunitiesView controller={oppsCtrl} params={props.match.params}/>} />
+        <Route
+          path={`${basePath}/my-roles`}
+          render={(props) => <MyRolesView controller={myRolesCtrl} params={props.match.params}/>} />
+        <Route
+          path={`${basePath}/admin`}
+          render={(props) => <AdminView controller={adminCtrl} params={props.match.params}/>} />
+        <Route
+          render={(props) => <WorkingGroupsView controller={wgCtrl} params={props.match.params}/> } />
       </Switch>
     </main>
   );

+ 15 - 34
pioneer/packages/joy-roles/src/mocks.ts

@@ -1,41 +1,22 @@
-import { bool, Option, Text, u32, u64, Vec } from '@polkadot/types';
-import AccountId from '@polkadot/types/primitive/Generic/AccountId';
+import { IMembership } from '@joystream/types/members';
 
-import { IMembership, EntryMethod } from '@joystream/types/members';
-
-import {
-  AcceptingApplications,
-  ActiveOpeningStage,
-  OpeningStage,
-  ActiveOpeningStageVariant,
-  ApplicationId
-} from '@joystream/types/hiring';
+import { createMock } from '@joystream/types';
 
 export function mockProfile (name: string, avatar_uri = ''): IMembership {
-  return {
-    handle: new Text(name),
-    avatar_uri: new Text(avatar_uri),
-    about: new Text(''),
-    registered_at_block: new u32(0),
-    registered_at_time: new u64(0),
-    entry: new EntryMethod(),
-    suspended: new bool(false),
-    subscription: new Option(u64),
-    root_account: new AccountId('5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
-    controller_account: new AccountId('5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp')
-  };
+  return createMock('Membership', {
+    handle: name,
+    avatar_uri: avatar_uri,
+    root_account: '5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp',
+    controller_account: '5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'
+  });
 }
 
-export const mockStage = new OpeningStage({
-  Active: new ActiveOpeningStageVariant({
-    applications_added: new (Vec.with(ApplicationId))([]),
-    active_application_count: new u32(0),
-    unstaking_application_count: new u32(0),
-    deactivated_application_count: new u32(0),
-    stage: new ActiveOpeningStage({
-      AcceptingApplications: new AcceptingApplications({
-        started_accepting_applicants_at_block: new u32(100)
-      })
+export const mockStage = createMock('OpeningStage', {
+  Active: {
+    stage: createMock('ActiveOpeningStage', {
+      AcceptingApplications: {
+        started_accepting_applicants_at_block: 100
+      }
     })
-  })
+  }
 });

+ 127 - 167
pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx

@@ -3,12 +3,12 @@ import { Link } from 'react-router-dom';
 import { formatBalance } from '@polkadot/util';
 
 import { ApiPromise } from '@polkadot/api';
-import { GenericAccountId, Option, Text, Vec, u32, u128 } from '@polkadot/types';
+import { Option, Text, Vec } from '@polkadot/types';
 import { Balance } from '@polkadot/types/interfaces';
 
-import { SingleLinkedMapEntry, Controller, View } from '@polkadot/joy-utils/index';
-import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
-import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
+import { Controller } from '@polkadot/joy-utils/react/helpers';
+import { View } from '@polkadot/joy-utils/react/hocs';
+import { useMyAccount } from '@polkadot/joy-utils/react/hooks';
 import { QueueTxExtrinsicAdd } from '@polkadot/react-components/Status/types';
 
 import {
@@ -35,8 +35,6 @@ import {
   Application,
   ApplicationStage,
   ActivateOpeningAt,
-  ApplicationRationingPolicy,
-  CurrentBlock,
   Opening,
   OpeningStage,
   StakingPolicy,
@@ -68,6 +66,7 @@ import {
 } from '../openingStateMarkup';
 
 import { Add, Zero } from '../balances';
+import { createMock } from '@joystream/types';
 
 type ids = {
   curatorId: number;
@@ -110,102 +109,94 @@ type State = {
 }
 
 function newHRT (title: string): Text {
-  return new Text(JSON.stringify({
-    version: 1,
-    headline: 'some headline',
-    job: {
-      title: title,
-      description: 'some job description'
-    },
-    application: {
-      sections: [
-        {
-          title: 'About you',
-          questions: [
-            {
-              title: 'your name',
-              type: 'text'
-            }
-          ]
-        },
-        {
-          title: 'Something else',
-          questions: [
-            {
-              title: 'another thing',
-              type: 'text area'
-            }
-          ]
+  return createMock('Text', JSON.stringify({
+      version: 1,
+      headline: 'some headline',
+      job: {
+        title: title,
+        description: 'some job description'
+      },
+      application: {
+        sections: [
+          {
+            title: 'About you',
+            questions: [
+              {
+                title: 'your name',
+                type: 'text'
+              }
+            ]
+          },
+          {
+            title: 'Something else',
+            questions: [
+              {
+                title: 'another thing',
+                type: 'text area'
+              }
+            ]
+          }
+        ]
+      },
+      reward: '10 JOY per block',
+      creator: {
+        membership: {
+          handle: 'ben'
         }
-      ]
-    },
-    reward: '10 JOY per block',
-    creator: {
-      membership: {
-        handle: 'ben'
+      },
+      process: {
+        details: [
+          'Some custom detail'
+        ]
       }
-    },
-    process: {
-      details: [
-        'Some custom detail'
-      ]
-    }
-  })
+    })
   );
 }
 
 const createRationingPolicyOpt = (maxApplicants: number) =>
-  new Option<ApplicationRationingPolicy>(
-    ApplicationRationingPolicy,
-    new ApplicationRationingPolicy({
-      max_active_applicants: new u32(maxApplicants)
-    })
-  );
+  createMock('Option<ApplicationRationingPolicy>', {
+    max_active_applicants: maxApplicants
+  });
 const createStakingPolicyOpt = (amount: number, amount_mode: StakingAmountLimitMode): Option<StakingPolicy> =>
-  new Option(
-    StakingPolicy,
-    new StakingPolicy({
-      amount: new u128(amount),
-      amount_mode,
-      crowded_out_unstaking_period_length: new Option('BlockNumber', null),
-      review_period_expired_unstaking_period_length: new Option('BlockNumber', null)
-    })
-  );
+  createMock('Option<StakingPolicy>', {
+    amount,
+    amount_mode
+  });
 
-const STAKING_MODE_EXACT = new StakingAmountLimitMode(StakingAmountLimitModeKeys.Exact);
-const STAKING_MODE_AT_LEAST = new StakingAmountLimitMode(StakingAmountLimitModeKeys.AtLeast);
+const STAKING_MODE_EXACT = createMock('StakingAmountLimitMode', StakingAmountLimitModeKeys.Exact);
+const STAKING_MODE_AT_LEAST = createMock('StakingAmountLimitMode', StakingAmountLimitModeKeys.AtLeast);
 
 const stockOpenings: openingDescriptor[] = [
   {
     title: 'Test config A: no application stake, no role stake, no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999)
+      max_review_period_length: createMock('u32', 99999)
     },
     text: newHRT('Test configuration A')
   },
   {
     title: 'Test config B: no application stake, no role stake, 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999)
+      max_review_period_length: createMock('u32', 99999)
     },
     text: newHRT('Test configuration B')
   },
   {
     title: 'Test config C: fixed application stake (100), no role stake, no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT)
     },
     text: newHRT('Test configuration C')
   },
   {
     title: 'Test config D: fixed application stake (100), no role stake, 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT)
     },
@@ -213,18 +204,18 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config E: no application stake, fixed role stake (100), no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       role_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT)
     },
     text: newHRT('Test configuration E')
   },
   {
     title: 'Test config F: no application stake, fixed role stake (100), 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       role_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT)
     },
@@ -232,18 +223,18 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config G: minimum application stake (100), no role stake, no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST)
     },
     text: newHRT('Test configuration G')
   },
   {
     title: 'Test config H: minimum application stake (100), no role stake, 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST)
     },
@@ -251,18 +242,18 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config I: no application stake, minimum role stake (100), no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       role_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST)
     },
     text: newHRT('Test configuration I')
   },
   {
     title: 'Test config J: no application stake, minimum role stake (100), 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       role_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST)
     },
@@ -270,9 +261,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config K: fixed application stake (100), fixed role stake (200), no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
     },
@@ -280,9 +271,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config L: fixed application stake (100), fixed role stake (200), 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
@@ -291,9 +282,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config M: Minimum application stake (100), minimum role stake (200), no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
     },
@@ -301,9 +292,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config N: Minimum application stake (100), minimum role stake (200), 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
@@ -312,9 +303,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config O: Fixed application stake (100), minimum role stake (200), no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
     },
@@ -322,15 +313,10 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config P: Fixed application stake (100), minimum role stake (200), 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
-      application_rationing_policy: new Option<ApplicationRationingPolicy>(
-        ApplicationRationingPolicy,
-        new ApplicationRationingPolicy({
-          max_active_applicants: new u32(10)
-        })
-      ),
+      max_review_period_length: createMock('u32', 99999),
+      application_rationing_policy: createRationingPolicyOpt(10),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
     },
@@ -338,9 +324,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config Q: Minimum application stake (100), fixed role stake (200), no applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
     },
@@ -348,9 +334,9 @@ const stockOpenings: openingDescriptor[] = [
   },
   {
     title: 'Test config R: Minimum application stake (100), fixed role stake (200), 10 applicant limit',
-    start: new ActivateOpeningAt(CurrentBlock),
+    start: createMock('ActivateOpeningAt', 'CurrentBlock'),
     policy: {
-      max_review_period_length: new u32(99999),
+      max_review_period_length: createMock('u32', 99999),
       application_rationing_policy: createRationingPolicyOpt(10),
       application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
       role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
@@ -376,21 +362,18 @@ export class AdminController extends Controller<State, ITransport> {
     this.api = api;
     this.queueExtrinsic = queueExtrinsic;
     this.state.currentDescriptor = stockOpenings[0];
-    this.updateState();
+    this.refreshState();
   }
 
-  onTxSuccess = () => { this.updateState(); }
+  onTxSuccess = () => { this.closeModal(); this.refreshState(); }
 
   newOpening (accountId: string, desc: openingDescriptor) {
     const tx = this.api.tx.contentWorkingGroup.addCuratorOpening(
       desc.start,
       desc.policy,
       desc.text
-    ) as unknown as SubmittableExtrinsic;
+    );
 
-    // FIXME: Normally we would keep it open in case of errror, but due to bad design
-    // the values in the form are reset at this point anyway, so there is no point
-    this.closeModal();
     this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId });
   }
 
@@ -408,11 +391,11 @@ export class AdminController extends Controller<State, ITransport> {
     const tx = this.api.tx.contentWorkingGroup.applyOnCuratorOpening(
       membershipIds[0],
       openingId,
-      new GenericAccountId(creatorAddress),
-      new Option(u128, 400),
-      new Option(u128, 400),
-      new Text('This is my application')
-    ) as unknown as SubmittableExtrinsic;
+      creatorAddress,
+      400,
+      400,
+      'This is my application'
+    );
     this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId: creatorAddress });
   }
 
@@ -426,26 +409,21 @@ export class AdminController extends Controller<State, ITransport> {
       openingId,
       applications,
       null
-    ) as unknown as SubmittableExtrinsic;
+    );
     this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId });
   }
 
   protected async profile (id: MemberId): Promise<Membership> {
     const member = (await this.api.query.members.membershipById(id)) as Membership;
-    if (member.handle.isEmpty) {
+    if (member.isEmpty) {
       throw new Error(`Expected member profile not found! (id: ${id.toString()}`);
     }
     return member;
   }
 
   protected async stakeValue (stakeId: StakeId): Promise<Balance> {
-    const stake = new SingleLinkedMapEntry<Stake>(
-      Stake,
-      await this.api.query.stake.stakes(
-        stakeId
-      )
-    );
-    return stake.value.value;
+    const stake = await this.api.query.stake.stakes(stakeId) as Stake;
+    return stake.value;
   }
 
   protected async roleStake (application: Application): Promise<Balance> {
@@ -464,67 +442,49 @@ export class AdminController extends Controller<State, ITransport> {
     return this.stakeValue(application.active_application_staking_id.unwrap());
   }
 
-  async updateState () {
+  async refreshState () {
     this.state.openings = new Map<number, opening>();
 
     const nextOpeningId = await this.api.query.contentWorkingGroup.nextCuratorOpeningId() as CuratorOpeningId;
     for (let i = nextOpeningId.toNumber() - 1; i >= 0; i--) {
-      const curatorOpening = new SingleLinkedMapEntry<CuratorOpening>(
-        CuratorOpening,
-        await this.api.query.contentWorkingGroup.curatorOpeningById(i)
-      );
+      const curatorOpening = await this.api.query.contentWorkingGroup.curatorOpeningById(i) as CuratorOpening;
 
-      const openingId = curatorOpening.value.opening_id;
+      const openingId = curatorOpening.opening_id;
 
-      const baseOpening = new SingleLinkedMapEntry<Opening>(
-        Opening,
-        await this.api.query.hiring.openingById(
-          openingId
-        )
-      );
+      const baseOpening = await this.api.query.hiring.openingById(openingId) as Opening;
 
-      const hrt = baseOpening.value.parse_human_readable_text_with_fallback();
+      const hrt = baseOpening.parse_human_readable_text_with_fallback();
       const title = hrt.job.title;
 
       this.state.openings.set(i, {
         openingId: openingId.toNumber(),
         curatorId: i,
         applications: new Array<application>(),
-        state: baseOpening.value.stage,
+        state: baseOpening.stage,
         title: title,
-        classification: await classifyOpeningStage(this.transport, baseOpening.value)
+        classification: await classifyOpeningStage(this.transport, baseOpening)
       });
     }
 
     const nextAppid = await this.api.query.contentWorkingGroup.nextCuratorApplicationId() as CuratorApplicationId;
     for (let i = 0; i < nextAppid.toNumber(); i++) {
-      const cApplication = new SingleLinkedMapEntry<CuratorApplication>(
-        CuratorApplication,
-        await this.api.query.contentWorkingGroup.curatorApplicationById(i)
-      );
+      const cApplication = await this.api.query.contentWorkingGroup.curatorApplicationById(i) as CuratorApplication;
 
-      const appId = cApplication.value.application_id;
-      const baseApplications = new SingleLinkedMapEntry<Application>(
-        Application,
-        await this.api.query.hiring.applicationById(
-          appId
-        )
-      );
+      const appId = cApplication.application_id;
+      const baseApplications = await this.api.query.hiring.applicationById(appId) as Application;
 
-      const curatorOpening = this.state.openings.get(
-        cApplication.value.curator_opening_id.toNumber()
-      ) as opening;
+      const curatorOpening = this.state.openings.get(cApplication.curator_opening_id.toNumber()) as opening;
 
       curatorOpening.applications.push({
         openingId: appId.toNumber(),
         curatorId: i,
-        stage: baseApplications.value.stage,
-        account: cApplication.value.role_account_id.toString(),
-        memberId: cApplication.value.member_id.toNumber(),
-        profile: (await this.profile(cApplication.value.member_id)),
-        applicationStake: await this.applicationStake(baseApplications.value),
-        roleStake: await this.roleStake(baseApplications.value),
-        application: baseApplications.value
+        stage: baseApplications.stage,
+        account: cApplication.role_account_id.toString(),
+        memberId: cApplication.member_id.toNumber(),
+        profile: (await this.profile(cApplication.member_id)),
+        applicationStake: await this.applicationStake(baseApplications),
+        roleStake: await this.roleStake(baseApplications),
+        application: baseApplications
       });
     }
 
@@ -573,7 +533,7 @@ const AdminContainer = ({ state, controller }: AdminContainerProps) => {
             <Modal
               open={state.modalOpen}
               onClose={() => controller.closeModal()}
-              mountNode={containerRef.current} // Prevent conflicts with tx-modal (after form values reset issue is fixed, see FIXME: above)
+              mountNode={containerRef.current} // Prevent conflicts with tx-modal
             >
               <Modal.Content image>
                 <Modal.Description>
@@ -593,7 +553,7 @@ const AdminContainer = ({ state, controller }: AdminContainerProps) => {
 };
 
 export const AdminView = View<AdminController, State>(
-  (state, controller) => {
+  ({ state, controller }) => {
     return (
       <AdminContainer state={state} controller={controller} />
     );
@@ -627,11 +587,11 @@ const NewOpening = (props: NewOpeningProps) => {
     switch (value) {
       case 'CurrentBlock':
         setShowExactBlock(false);
-        setStart(new ActivateOpeningAt(CurrentBlock));
+        setStart(createMock('ActivateOpeningAt', 'CurrentBlock'));
         break;
 
       case 'ExactBlock':
-        setStart(new ActivateOpeningAt({ ExactBlock: exactBlock }));
+        setStart(createMock('ActivateOpeningAt', { ExactBlock: exactBlock }));
         setShowExactBlock(true);
         break;
     }
@@ -639,7 +599,7 @@ const NewOpening = (props: NewOpeningProps) => {
 
   const onChangeExactBlock = (e: any, { value }: any) => {
     setExactBlock(value);
-    setStart(new ActivateOpeningAt({ ExactBlock: value }));
+    setStart(createMock('ActivateOpeningAt', { ExactBlock: value }));
   };
 
   const [policy, setPolicy] = useState(props.desc.policy);
@@ -709,7 +669,7 @@ const NewOpening = (props: NewOpeningProps) => {
     props.fn({
       start: start,
       policy: policy,
-      text: new Text(text),
+      text: createMock('Text', text),
       title: ''
     });
   };
@@ -738,7 +698,7 @@ const NewOpening = (props: NewOpeningProps) => {
         <Input
           type="number"
           value={policy.max_review_period_length.toNumber()}
-          onChange={(e: any, { value }: any) => onChangePolicyField('max_review_period_length', new u32(value))}
+          onChange={(e: any, { value }: any) => onChangePolicyField('max_review_period_length', createMock('u32', value))}
         />
       </Form.Field>
 

+ 20 - 7
pioneer/packages/joy-roles/src/tabs/MyRoles.controller.tsx

@@ -1,7 +1,8 @@
 import React from 'react';
 
 import { Container } from 'semantic-ui-react';
-import { Controller, View } from '@polkadot/joy-utils/index';
+import { Controller } from '@polkadot/joy-utils/react/helpers';
+import { View } from '@polkadot/joy-utils/react/hocs';
 import { ITransport } from '../transport';
 import {
   Applications, OpeningApplication,
@@ -23,13 +24,23 @@ const newEmptyState = (): State => {
 };
 
 export class MyRolesController extends Controller<State, ITransport> {
-  constructor (transport: ITransport, myAddress: string | undefined, initialState: State = newEmptyState()) {
+  constructor (transport: ITransport, initialState: State = newEmptyState()) {
     super(transport, initialState);
+  }
+
+  refreshState() {
+    if (!this.state.myAddress) {
+      return;
+    }
+    // Set actual data
+    this.updateCurationGroupRoles(this.state.myAddress);
+    this.updateApplications(this.state.myAddress);
+  }
 
+  setMyAddress (myAddress: string | undefined) {
     if (typeof myAddress === 'string') {
       this.state.myAddress = myAddress;
-      this.updateCurationGroupRoles(myAddress);
-      this.updateApplications(myAddress);
+      this.refreshState();
     }
   }
 
@@ -54,16 +65,18 @@ export class MyRolesController extends Controller<State, ITransport> {
   }
 
   leaveRole (role: ActiveRole, rationale: string) {
-    this.transport.leaveRole(role.group, this.state.myAddress, role.workerId.toNumber(), rationale);
+    const successCb = this.refreshState.bind(this);
+    this.transport.leaveRole(role.group, this.state.myAddress, role.workerId.toNumber(), rationale, successCb);
   }
 
   cancelApplication (application: OpeningApplication) {
-    this.transport.withdrawApplication(application.meta.group, this.state.myAddress, application.id);
+    const successCb = this.refreshState.bind(this);
+    this.transport.withdrawApplication(application.meta.group, this.state.myAddress, application.id, successCb);
   }
 }
 
 export const MyRolesView = View<MyRolesController, State>(
-  (state, controller) => (
+  ({ state, controller }) => (
     <Container className="my-roles">
       <CurrentRoles currentRoles={state.currentRoles} />
       <Applications applications={state.applications} cancelCallback={(a) => controller.cancelApplication(a)} />

+ 19 - 22
pioneer/packages/joy-roles/src/tabs/MyRoles.elements.stories.tsx

@@ -1,13 +1,11 @@
 import React from 'react';
 import { withKnobs } from '@storybook/addon-knobs';
+import { createMock } from '@joystream/types';
 
 import {
   Container
 } from 'semantic-ui-react';
 
-import {
-  u128
-} from '@polkadot/types';
 
 import {
   CurrentRoles, CurrentRolesProps,
@@ -30,7 +28,6 @@ import {
   yesterday
 } from './Opportunities.stories';
 
-import { CuratorId } from '@joystream/types/content-working-group';
 import { WorkingGroups, workerRoleNameByGroup } from '../working_groups';
 
 export default {
@@ -46,10 +43,10 @@ export function CurrentRolesFragment () {
   const props: CurrentRolesProps = {
     currentRoles: [
       {
-        workerId: new CuratorId(1),
+        workerId: createMock('CuratorId', 1),
         name: workerRoleNameByGroup[WorkingGroups.StorageProviders],
-        reward: new u128(321),
-        stake: new u128(100),
+        reward: createMock('u128', 321),
+        stake: createMock('u128', 100),
         group: WorkingGroups.StorageProviders,
         CTAs: [
           {
@@ -59,11 +56,11 @@ export function CurrentRolesFragment () {
         ]
       },
       {
-        workerId: new CuratorId(1),
+        workerId: createMock('CuratorId', 1),
         name: 'Some other role',
         url: 'some URL',
-        reward: new u128(321),
-        stake: new u128(12343200),
+        reward: createMock('u128', 321),
+        stake: createMock('u128', 12343200),
         group: WorkingGroups.ContentCurators,
         CTAs: [
           {
@@ -176,8 +173,8 @@ const permutations: (ApplicationProps & TestProps)[] = [
       starting_time: yesterday()
     },
     opening: opening,
-    applicationStake: new u128(5),
-    roleStake: new u128(15),
+    applicationStake: createMock('u128', 5),
+    roleStake: createMock('u128', 15),
     cancelCallback: () => { /* do nothing */ },
     rank: 15,
     capacity: 20
@@ -196,8 +193,8 @@ const permutations: (ApplicationProps & TestProps)[] = [
       starting_time: yesterday()
     },
     opening: opening,
-    applicationStake: new u128(5),
-    roleStake: new u128(15),
+    applicationStake: createMock('u128', 5),
+    roleStake: createMock('u128', 15),
     cancelCallback: () => { /* do nothing */ },
     rank: 21,
     capacity: 20
@@ -218,8 +215,8 @@ const permutations: (ApplicationProps & TestProps)[] = [
       review_end_block: 12345
     },
     opening: opening,
-    applicationStake: new u128(5),
-    roleStake: new u128(15),
+    applicationStake: createMock('u128', 5),
+    roleStake: createMock('u128', 15),
     cancelCallback: () => { /* do nothing */ },
     rank: 15,
     capacity: 20
@@ -240,8 +237,8 @@ const permutations: (ApplicationProps & TestProps)[] = [
       review_end_block: 12345
     },
     opening: opening,
-    applicationStake: new u128(5),
-    roleStake: new u128(15),
+    applicationStake: createMock('u128', 5),
+    roleStake: createMock('u128', 15),
     cancelCallback: () => { /* do nothing */ },
     rank: 21,
     capacity: 20
@@ -260,8 +257,8 @@ const permutations: (ApplicationProps & TestProps)[] = [
       starting_time: yesterday()
     },
     opening: opening,
-    applicationStake: new u128(5),
-    roleStake: new u128(15),
+    applicationStake: createMock('u128', 5),
+    roleStake: createMock('u128', 15),
     cancelCallback: () => { /* do nothing */ },
     rank: 21,
     capacity: 20
@@ -280,8 +277,8 @@ const permutations: (ApplicationProps & TestProps)[] = [
       starting_time: yesterday()
     },
     opening: opening,
-    applicationStake: new u128(5),
-    roleStake: new u128(15),
+    applicationStake: createMock('u128', 5),
+    roleStake: createMock('u128', 15),
     cancelCallback: () => { /* do nothing */ },
     rank: 21,
     capacity: 20,

+ 2 - 3
pioneer/packages/joy-roles/src/tabs/MyRoles.tsx

@@ -17,10 +17,9 @@ import {
 } from 'semantic-ui-react';
 
 import { formatBalance } from '@polkadot/util';
-import { u128 } from '@polkadot/types';
 import { Balance } from '@polkadot/types/interfaces';
 
-import { Loadable } from '@polkadot/joy-utils/index';
+import { Loadable } from '@polkadot/joy-utils/react/hocs';
 import { Opening } from '@joystream/types/hiring';
 
 import {
@@ -456,7 +455,7 @@ export function Application (props: ApplicationProps) {
                   Total stake
                 </Table.Cell>
                 <Table.Cell>
-                  {formatBalance(new u128(props.roleStake.add(props.applicationStake)))}
+                  {formatBalance(props.roleStake.add(props.applicationStake))}
                 </Table.Cell>
               </Table.Row>
             </Table.Body>

+ 12 - 4
pioneer/packages/joy-roles/src/tabs/Opportunities.controller.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
 
-import { Controller, View } from '@polkadot/joy-utils/index';
+import { Controller } from '@polkadot/joy-utils/react/helpers';
+import { View } from '@polkadot/joy-utils/react/hocs';
 
 import { ITransport } from '../transport';
 
@@ -20,13 +21,20 @@ type State = {
 }
 
 export class OpportunitiesController extends Controller<State, ITransport> {
-  constructor (transport: ITransport, memberId?: MemberId, initialState: State = {}) {
+  constructor (transport: ITransport, initialState: State = {}) {
     super(transport, initialState);
-    this.state.memberId = memberId;
+  }
+
+  refreshState() {
     this.getOpportunities();
     this.getBlocktime();
   }
 
+  async setMemberId(memberId?: MemberId) {
+    this.state.memberId = memberId;
+    this.dispatch();
+  }
+
   async getOpportunities () {
     this.state.opportunities = await this.transport.currentOpportunities();
     this.dispatch();
@@ -39,7 +47,7 @@ export class OpportunitiesController extends Controller<State, ITransport> {
 }
 
 export const OpportunitiesView = View<OpportunitiesController, State>(
-  (state, controller, params) => (
+  ({ state, controller, params }) => (
     <OpeningsView
       group={AvailableGroups.includes(params.get('group') as any) ? params.get('group') as WorkingGroups : undefined}
       lead={!!params.get('lead')}

+ 52 - 53
pioneer/packages/joy-roles/src/tabs/Opportunities.elements.stories.tsx

@@ -2,8 +2,6 @@ import React from 'react';
 import { withKnobs } from '@storybook/addon-knobs';
 import { Card, Container } from 'semantic-ui-react';
 
-import { u128 } from '@polkadot/types';
-
 import {
   OpeningBodyApplicationsStatus, OpeningStakeAndApplicationStatus,
   OpeningBodyReviewInProgress,
@@ -23,6 +21,7 @@ import { OpeningMetadata } from '../OpeningMetadata';
 import 'semantic-ui-css/semantic.min.css';
 import '@polkadot/joy-roles/index.sass';
 import { WorkingGroups } from '../working_groups';
+import { createMock } from '@joystream/types';
 
 export default {
   title: 'Roles / Components / Opportunities groups tab / Elements',
@@ -93,81 +92,81 @@ export function OpeningApplicationsStatusByState () {
       _description: 'No limit, no applications, no stake',
       numberOfApplications: 0,
       maxNumberOfApplications: 0,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'No limit, some applications, no stake',
       numberOfApplications: 15,
       maxNumberOfApplications: 0,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'Limit, no applications, no stake',
       numberOfApplications: 0,
       maxNumberOfApplications: 20,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'Limit, some applications, no stake',
       numberOfApplications: 10,
       maxNumberOfApplications: 20,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'Limit, full applications, no stake (application impossible)',
       numberOfApplications: 20,
       maxNumberOfApplications: 20,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'No limit, no applications, some stake',
       numberOfApplications: 0,
       maxNumberOfApplications: 0,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(10)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 10)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'No limit, some applications, some stake',
       numberOfApplications: 15,
       maxNumberOfApplications: 0,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(10)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 10)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'Limit, no applications, some stake',
       numberOfApplications: 0,
       maxNumberOfApplications: 20,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(10)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 10)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'Limit, some applications, some stake',
       numberOfApplications: 10,
       maxNumberOfApplications: 20,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(10)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 10)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     },
     {
       _description: 'Limit, full applications, some stake',
       numberOfApplications: 20,
       maxNumberOfApplications: 20,
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(10)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0)
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 10)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0)
     }
   ];
 
@@ -189,51 +188,51 @@ export function OpeningApplicationsStakeRequirementByStake () {
   const permutations: (StakeRequirementProps & TestProps)[] = [
     {
       _description: 'No stakes required (should be empty)',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0),
       maxNumberOfApplications: 0
     },
     {
       _description: 'App stake required; no role stake required',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(500)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 500)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0),
       maxNumberOfApplications: 0
     },
     {
       _description: 'App stake required >; no role stake required',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(500), StakeType.AtLeast),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(0),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 500), StakeType.AtLeast),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 0),
       maxNumberOfApplications: 0
     },
     {
       _description: 'No app stake required; role stake required',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(101)),
-      defactoMinimumStake: new u128(0),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 101)),
+      defactoMinimumStake: createMock('Balance', 0),
       maxNumberOfApplications: 0
     },
     {
       _description: 'No app stake required; role stake required',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(0), StakeType.AtLeast),
-      requiredRoleStake: new RoleStakeRequirement(new u128(102)),
-      defactoMinimumStake: new u128(0),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 0), StakeType.AtLeast),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 102)),
+      defactoMinimumStake: createMock('Balance', 0),
       maxNumberOfApplications: 0
     },
     {
       _description: '>= App stake required; role stake required',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(101), StakeType.AtLeast),
-      requiredRoleStake: new RoleStakeRequirement(new u128(102)),
-      defactoMinimumStake: new u128(0),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 101), StakeType.AtLeast),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 102)),
+      defactoMinimumStake: createMock('Balance', 0),
       maxNumberOfApplications: 0
     },
     {
       _description: 'App stake required; no role stake required; dynamic minimum > 0',
-      requiredApplicationStake: new ApplicationStakeRequirement(new u128(500)),
-      requiredRoleStake: new RoleStakeRequirement(new u128(0)),
-      defactoMinimumStake: new u128(1000),
+      requiredApplicationStake: new ApplicationStakeRequirement(createMock('Balance', 500)),
+      requiredRoleStake: new RoleStakeRequirement(createMock('Balance', 0)),
+      defactoMinimumStake: createMock('Balance', 1000),
       maxNumberOfApplications: 20
     }
   ];

+ 9 - 17
pioneer/packages/joy-roles/src/tabs/Opportunities.stories.tsx

@@ -2,14 +2,8 @@ import React from 'react';
 import { number, select, text, withKnobs } from '@storybook/addon-knobs';
 import * as faker from 'faker';
 
-import { Option, Text, u32, u128 } from '@polkadot/types';
 import { Balance } from '@polkadot/types/interfaces';
 
-import {
-  Opening,
-  ApplicationRationingPolicy,
-  StakingPolicy
-} from '@joystream/types/hiring';
 import { mockStage } from '../mocks';
 import {
   OpeningView,
@@ -27,6 +21,7 @@ import { OpeningMetadata } from '../OpeningMetadata';
 import 'semantic-ui-css/semantic.min.css';
 import '@polkadot/joy-roles/index.sass';
 import { WorkingGroups } from '../working_groups';
+import { createMock } from '@joystream/types';
 
 export default {
   title: 'Roles / Components / Opportunities groups tab',
@@ -53,16 +48,13 @@ export function yesterday (): Date {
 }
 
 export function newMockHumanReadableText (obj: any) {
-  return new Text(JSON.stringify(obj));
+  return createMock('Text', JSON.stringify(obj));
 }
 
-export const opening = new Opening({
-  created: new u32(100),
+export const opening = createMock('Opening', {
+  created: 100,
   stage: mockStage,
-  max_review_period_length: new u32(100),
-  application_rationing_policy: new Option(ApplicationRationingPolicy),
-  application_staking_policy: new Option(StakingPolicy),
-  role_staking_policy: new Option(StakingPolicy),
+  max_review_period_length: 100,
   human_readable_text: newMockHumanReadableText({
     version: 1,
     headline: text('Headline', 'Help us curate awesome content', 'Role'),
@@ -137,15 +129,15 @@ export function OpportunitySandbox () {
     numberOfApplications: number('Applications count', 0, applicationSliderOptions, 'Role rationing policy'),
     maxNumberOfApplications: number('Application max', 0, applicationSliderOptions, 'Role rationing policy'),
     requiredApplicationStake: new ApplicationStakeRequirement(
-      new u128(number('Application stake', 500, moneySliderOptions, 'Role stakes'))
+      createMock('Balance', number('Application stake', 500, moneySliderOptions, 'Role stakes'))
     ),
     requiredRoleStake: new RoleStakeRequirement(
-      new u128(number('Role stake', 0, moneySliderOptions, 'Role stakes'))
+      createMock('Balance', number('Role stake', 0, moneySliderOptions, 'Role stakes'))
     ),
-    defactoMinimumStake: new u128(0)
+    defactoMinimumStake: createMock('Balance', 0)
   };
 
-  const defactoMinimumStake: Balance = new u128(number('Dynamic min stake', 0, moneySliderOptions, 'Role stakes'));
+  const defactoMinimumStake: Balance = createMock('Balance', number('Dynamic min stake', 0, moneySliderOptions, 'Role stakes'));
 
   const meta: OpeningMetadata = {
     id: '1',

+ 3 - 3
pioneer/packages/joy-roles/src/tabs/Opportunities.tsx

@@ -22,7 +22,7 @@ import {
 import { formatBalance } from '@polkadot/util';
 import { Balance } from '@polkadot/types/interfaces';
 
-import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
+import { useMyAccount } from '@polkadot/joy-utils/react/hooks';
 
 import { Countdown } from '../elements';
 import { ApplicationStakeRequirement, RoleStakeRequirement } from '../StakeRequirement';
@@ -38,7 +38,7 @@ import {
   openingDescription
 } from '../openingStateMarkup';
 
-import { Loadable } from '@polkadot/joy-utils/index';
+import { Loadable } from '@polkadot/joy-utils/react/hocs';
 import styled from 'styled-components';
 import _ from 'lodash';
 import { WorkingGroups, AvailableGroups, workerRoleNameByGroup } from '../working_groups';
@@ -571,7 +571,7 @@ export const OpeningsView = Loadable<OpeningsViewProps>(
           filteredOpenings.length
             ? filteredOpenings.map((opening, key) => (
               <OpeningView
-                key={key}
+                key={`${opening.meta.group}-${opening.meta.id}`}
                 {...opening}
                 block_time_in_seconds={props.block_time_in_seconds as number}
                 member_id={props.member_id} />

+ 20 - 15
pioneer/packages/joy-roles/src/tabs/Opportunity.controller.tsx

@@ -1,6 +1,7 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
-import { Controller, memoize, View, Params } from '@polkadot/joy-utils/index';
+import { Controller, memoize } from '@polkadot/joy-utils/react/helpers';
+import { View } from '@polkadot/joy-utils/react/hocs';
 
 import { ITransport } from '../transport';
 
@@ -21,19 +22,23 @@ type State = {
 }
 
 export class OpportunityController extends Controller<State, ITransport> {
-  constructor (transport: ITransport, memberId?: MemberId, initialState: State = {}) {
+  constructor (transport: ITransport, initialState: State = {}) {
     super(transport, initialState);
-    this.state.memberId = memberId;
     this.getBlocktime();
   }
 
+  async setMemberId(memberId?: MemberId) {
+    this.state.memberId = memberId;
+    this.dispatch();
+  }
+
   @memoize()
   async getOpportunity (group: string | undefined, id: string | undefined) {
-    if (!id) {
-      return this.onError('OpportunityController: no ID provided in params');
+    if (!id || !group) {
+      return;
     }
 
-    if (!group || !AvailableGroups.includes(group as any)) {
+    if (!AvailableGroups.includes(group as any)) {
       return this.onError('OppportunityController: invalid group provided in params');
     }
 
@@ -47,14 +52,14 @@ export class OpportunityController extends Controller<State, ITransport> {
   }
 }
 
-const renderOpeningView = (state: State, controller: OpportunityController, params: Params) => {
-  controller.getOpportunity(params.get('group'), params.get('id'));
-  return (
-    <OpeningView {...state.opportunity!} block_time_in_seconds={state.blockTime!} member_id={state.memberId} />
-  );
-};
-
 export const OpportunityView = View<OpportunityController, State>({
   errorComponent: OpeningError,
-  render: renderOpeningView
+  renderComponent: ({ state, controller, params }) => {
+    useEffect(() => {
+      controller.getOpportunity(params.get('group'), params.get('id'));
+    }, [params.get('group'), params.get('id')]);
+    return (
+      <OpeningView {...state.opportunity!} block_time_in_seconds={state.blockTime!} member_id={state.memberId} />
+    );
+  }
 });

+ 6 - 2
pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
 
-import { Controller, View } from '@polkadot/joy-utils/index';
+import { Controller } from '@polkadot/joy-utils/react/helpers';
+import { View } from '@polkadot/joy-utils/react/hocs';
 
 import { ITransport } from '../transport';
 
@@ -20,6 +21,9 @@ type State = {
 export class WorkingGroupsController extends Controller<State, ITransport> {
   constructor (transport: ITransport, initialState: State = {}) {
     super(transport, {});
+  }
+
+  refreshState() {
     this.getCurationGroup();
     this.getStorageGroup();
   }
@@ -49,7 +53,7 @@ const WorkingGroupsOverview = styled.div`
 `;
 
 export const WorkingGroupsView = View<WorkingGroupsController, State>(
-  (state) => (
+  ({ state }) => (
     <WorkingGroupsOverview>
       <ContentCurators {...state.contentCurators}/>
       <StorageProviders {...state.storageProviders}/>

+ 16 - 18
pioneer/packages/joy-roles/src/tabs/WorkingGroup.stories.tsx

@@ -1,9 +1,7 @@
 import React from 'react';
 import { boolean, number, text, withKnobs } from '@storybook/addon-knobs';
 
-import { u128, GenericAccountId } from '@polkadot/types';
-
-import { MemberId } from '@joystream/types/members';
+import { createMock } from '@joystream/types';
 
 import { ContentCurators } from '@polkadot/joy-roles/tabs/WorkingGroup';
 import { GroupMember } from '../elements';
@@ -22,59 +20,59 @@ export default {
 export function ContentCuratorsSection () {
   const members: GroupMember[] = [
     {
-      memberId: new MemberId(1),
-      roleAccount: new GenericAccountId('5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
+      memberId: createMock('MemberId', 1),
+      roleAccount: createMock('AccountId', '5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
       profile: mockProfile(
         text('Handle', 'benholdencrowther', 'Ben'),
         text('Avatar URL', 'https://www.benholdencrowther.com/wp-content/uploads/2019/03/Hanging_Gardens_of_Babylon.jpg', 'Ben')
       ),
       title: text('Title', 'Curation lead', 'Ben'),
-      stake: new u128(number('Stake', 10101, {}, 'Ben')),
+      stake: createMock('Balance', number('Stake', 10101, {}, 'Ben')),
       workerId: 1,
       group: WorkingGroups.ContentCurators
     },
     {
-      memberId: new MemberId(2),
-      roleAccount: new GenericAccountId('5DfJWGbBAH8hLAg8rcRYZW5BEZbE4BJeCQKoxUeqoyewLSew'),
+      memberId: createMock('MemberId', 2),
+      roleAccount: createMock('AccountId', '5DfJWGbBAH8hLAg8rcRYZW5BEZbE4BJeCQKoxUeqoyewLSew'),
       profile: mockProfile(text('Handle', 'bwhm0', 'Martin')),
       title: text('Title', 'Content curator', 'Martin'),
-      stake: new u128(number('Stake', 10101, {}, 'Martin')),
+      stake: createMock('Balance', number('Stake', 10101, {}, 'Martin')),
       workerId: 2,
       group: WorkingGroups.ContentCurators
     },
     {
-      memberId: new MemberId(3),
-      roleAccount: new GenericAccountId('5DQqNWRFPruFs9YKheVMqxUbqoXeMzAWfVfcJgzuia7NA3D3'),
+      memberId: createMock('MemberId', 3),
+      roleAccount: createMock('AccountId', '5DQqNWRFPruFs9YKheVMqxUbqoXeMzAWfVfcJgzuia7NA3D3'),
       profile: mockProfile(
         'yourheropaul',
         'https://yhp.io/img/paul.svg'
       ),
       title: text('Title', 'Content curator', 'Paul'),
-      stake: new u128(number('Stake', 10101, {}, 'Paul')),
+      stake: createMock('Balance', number('Stake', 10101, {}, 'Paul')),
       workerId: 3,
       group: WorkingGroups.ContentCurators
     },
     {
-      memberId: new MemberId(4),
-      roleAccount: new GenericAccountId('5GSMNn8Sy8k64mGUWPDafjMZu9bQNX26GujbBQ1LeJpNbrfg'),
+      memberId: createMock('MemberId', 4),
+      roleAccount: createMock('AccountId', '5GSMNn8Sy8k64mGUWPDafjMZu9bQNX26GujbBQ1LeJpNbrfg'),
       profile: mockProfile(
         'alex_joystream',
         'https://avatars2.githubusercontent.com/u/153928?s=200&v=4'
       ),
       title: text('Title', 'Content curator', 'Alex'),
-      stake: new u128(number('Stake', 10101, {}, 'Alex')),
+      stake: createMock('Balance', number('Stake', 10101, {}, 'Alex')),
       workerId: 4,
       group: WorkingGroups.ContentCurators
     },
     {
-      memberId: new MemberId(5),
-      roleAccount: new GenericAccountId('5Gn9n7SDJ7VgHqHQWYzkSA4vX6DCmS5TFWdHxikTXp9b4L32'),
+      memberId: createMock('MemberId', 5),
+      roleAccount: createMock('AccountId', '5Gn9n7SDJ7VgHqHQWYzkSA4vX6DCmS5TFWdHxikTXp9b4L32'),
       profile: mockProfile(
         'mokhtar',
         'https://avatars2.githubusercontent.com/u/1621012?s=460&v=4'
       ),
       title: text('Title', 'Content curator', 'Mokhtar'),
-      stake: new u128(number('Stake', 10101, {}, 'Mokhtar')),
+      stake: createMock('Balance', number('Stake', 10101, {}, 'Mokhtar')),
       workerId: 5,
       group: WorkingGroups.ContentCurators
     }

+ 1 - 1
pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx

@@ -3,7 +3,7 @@ import { Button, Card, Icon, Message, SemanticICONS } from 'semantic-ui-react';
 import { Link } from 'react-router-dom';
 
 import { GroupLeadView, GroupMember, GroupMemberView, GroupLead } from '../elements';
-import { Loadable } from '@polkadot/joy-utils/index';
+import { Loadable } from '@polkadot/joy-utils/react/hocs';
 
 import { WorkingGroups } from '../working_groups';
 import styled from 'styled-components';

+ 53 - 80
pioneer/packages/joy-roles/src/transport.mock.ts

@@ -1,20 +1,14 @@
 import { Observable } from 'rxjs';
 import { Balance } from '@polkadot/types/interfaces';
-import { Option, u32, u128, GenericAccountId } from '@polkadot/types';
 
-import { Subscribable, Transport as TransportBase } from '@polkadot/joy-utils/index';
+import { Subscribable } from '@polkadot/joy-utils/react/helpers';
+import MockTransportBase from '@polkadot/joy-utils/transport/mock/base';
 
 import { ITransport } from './transport';
 
-import { MemberId } from '@joystream/types/members';
-import {
-  Opening,
-  ApplicationRationingPolicy,
-  StakingPolicy
-} from '@joystream/types/hiring';
+import { createMock } from '@joystream/types';
 
 import { WorkingGroupMembership, GroupLeadStatus } from './tabs/WorkingGroup';
-import { CuratorId } from '@joystream/types/content-working-group';
 import { WorkingGroupOpening } from './tabs/Opportunities';
 import { ActiveRole, OpeningApplication } from './tabs/MyRoles';
 import { ApplicationStakeRequirement, RoleStakeRequirement, StakeType } from './StakeRequirement';
@@ -27,15 +21,7 @@ import * as faker from 'faker';
 import { mockProfile, mockStage } from './mocks';
 import { WorkingGroups, workerRoleNameByGroup } from './working_groups';
 
-export class Transport extends TransportBase implements ITransport {
-  protected simulateApiResponse<T> (value: T): Promise<T> {
-    return this.promise<T>(value, this.randomTimeout());
-  }
-
-  protected randomTimeout (min = 1, max = 20): number {
-    return Math.random() * (max - min) + min;
-  }
-
+export class Transport extends MockTransportBase implements ITransport {
   groupLeadStatus (group: WorkingGroups = WorkingGroups.ContentCurators): Promise<GroupLeadStatus> {
     return this.simulateApiResponse<GroupLeadStatus>({
       loaded: true
@@ -49,59 +35,59 @@ export class Transport extends TransportBase implements ITransport {
       leadRolesAvailable: false,
       workers: [
         {
-          memberId: new MemberId(1),
-          roleAccount: new GenericAccountId('5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
+          memberId: createMock('MemberId', 1),
+          roleAccount: createMock('AccountId', '5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
           profile: mockProfile(
             'benholdencrowther',
             'https://www.benholdencrowther.com/wp-content/uploads/2019/03/Hanging_Gardens_of_Babylon.jpg'
           ),
           title: 'Content curator',
-          stake: new u128(10101),
+          stake: createMock('u128', 10101),
           workerId: 1,
           group: WorkingGroups.ContentCurators
         },
         {
-          memberId: new MemberId(2),
-          roleAccount: new GenericAccountId('5DfJWGbBAH8hLAg8rcRYZW5BEZbE4BJeCQKoxUeqoyewLSew'),
+          memberId: createMock('MemberId', 2),
+          roleAccount: createMock('AccountId', '5DfJWGbBAH8hLAg8rcRYZW5BEZbE4BJeCQKoxUeqoyewLSew'),
           profile: mockProfile('bwhm0'),
           title: 'Content curator',
-          stake: new u128(10101),
+          stake: createMock('u128', 10101),
           workerId: 2,
           group: WorkingGroups.ContentCurators
         },
         {
-          memberId: new MemberId(3),
-          roleAccount: new GenericAccountId('5DQqNWRFPruFs9YKheVMqxUbqoXeMzAWfVfcJgzuia7NA3D3'),
+          memberId: createMock('MemberId', 3),
+          roleAccount: createMock('AccountId', '5DQqNWRFPruFs9YKheVMqxUbqoXeMzAWfVfcJgzuia7NA3D3'),
           profile: mockProfile(
             'yourheropaul',
             'https://yhp.io/img/paul.svg'
           ),
           title: 'Content curator',
-          stake: new u128(10101),
+          stake: createMock('u128', 10101),
           workerId: 3,
           group: WorkingGroups.ContentCurators
         },
         {
-          memberId: new MemberId(4),
-          roleAccount: new GenericAccountId('5GSMNn8Sy8k64mGUWPDafjMZu9bQNX26GujbBQ1LeJpNbrfg'),
+          memberId: createMock('MemberId', 4),
+          roleAccount: createMock('AccountId', '5GSMNn8Sy8k64mGUWPDafjMZu9bQNX26GujbBQ1LeJpNbrfg'),
           profile: mockProfile(
             'alex_joystream',
             'https://avatars2.githubusercontent.com/u/153928?s=200&v=4'
           ),
           title: 'Content curator',
-          stake: new u128(10101),
+          stake: createMock('u128', 10101),
           workerId: 4,
           group: WorkingGroups.ContentCurators
         },
         {
-          memberId: new MemberId(3),
-          roleAccount: new GenericAccountId('5Gn9n7SDJ7VgHqHQWYzkSA4vX6DCmS5TFWdHxikTXp9b4L32'),
+          memberId: createMock('MemberId', 3),
+          roleAccount: createMock('AccountId', '5Gn9n7SDJ7VgHqHQWYzkSA4vX6DCmS5TFWdHxikTXp9b4L32'),
           profile: mockProfile(
             'mokhtar',
             'https://avatars2.githubusercontent.com/u/1621012?s=460&v=4'
           ),
           title: 'Content curator',
-          stake: new u128(10101),
+          stake: createMock('u128', 10101),
           workerId: 5,
           group: WorkingGroups.ContentCurators
         }
@@ -116,14 +102,14 @@ export class Transport extends TransportBase implements ITransport {
       leadRolesAvailable: true,
       workers: [
         {
-          memberId: new MemberId(1),
-          roleAccount: new GenericAccountId('5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
+          memberId: createMock('MemberId', 1),
+          roleAccount: createMock('AccountId', '5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
           profile: mockProfile(
             'benholdencrowther',
             'https://www.benholdencrowther.com/wp-content/uploads/2019/03/Hanging_Gardens_of_Babylon.jpg'
           ),
           title: 'Storage provider',
-          stake: new u128(10101),
+          stake: createMock('u128', 10101),
           workerId: 1,
           group: WorkingGroups.StorageProviders
         }
@@ -135,13 +121,10 @@ export class Transport extends TransportBase implements ITransport {
     return this.simulateApiResponse<Array<WorkingGroupOpening>>(
       [
         {
-          opening: new Opening({
-            created: new u32(50000),
+          opening: createMock('Opening', {
+            created: 50000,
             stage: mockStage,
-            max_review_period_length: new u32(100),
-            application_rationing_policy: new Option(ApplicationRationingPolicy),
-            application_staking_policy: new Option(StakingPolicy),
-            role_staking_policy: new Option(StakingPolicy),
+            max_review_period_length: 100,
             human_readable_text: newMockHumanReadableText({
               version: 1,
               headline: 'Help us curate awesome content',
@@ -195,14 +178,14 @@ export class Transport extends TransportBase implements ITransport {
             numberOfApplications: 0,
             maxNumberOfApplications: 0,
             requiredApplicationStake: new ApplicationStakeRequirement(
-              new u128(500)
+              createMock('u128', 500)
             ),
             requiredRoleStake: new RoleStakeRequirement(
-              new u128(0)
+              createMock('u128', 0)
             ),
-            defactoMinimumStake: new u128(0)
+            defactoMinimumStake: createMock('u128', 0)
           },
-          defactoMinimumStake: new u128(0)
+          defactoMinimumStake: createMock('u128', 0)
         }
       ]
     );
@@ -212,13 +195,10 @@ export class Transport extends TransportBase implements ITransport {
   async groupOpening (group: WorkingGroups, id: number): Promise<WorkingGroupOpening> {
     return this.simulateApiResponse<WorkingGroupOpening>(
       {
-        opening: new Opening({
-          created: new u32(50000),
+        opening: createMock('Opening', {
+          created: 50000,
           stage: mockStage,
-          max_review_period_length: new u32(100),
-          application_rationing_policy: new Option(ApplicationRationingPolicy),
-          application_staking_policy: new Option(StakingPolicy),
-          role_staking_policy: new Option(StakingPolicy),
+          max_review_period_length: 100,
           human_readable_text: newMockHumanReadableText({
             version: 1,
             headline: 'Help us curate awesome content',
@@ -276,15 +256,15 @@ export class Transport extends TransportBase implements ITransport {
           numberOfApplications: 0,
           maxNumberOfApplications: 0,
           requiredApplicationStake: new ApplicationStakeRequirement(
-            new u128(501),
+            createMock('u128', 501),
             StakeType.AtLeast
           ),
           requiredRoleStake: new RoleStakeRequirement(
-            new u128(502)
+            createMock('u128', 502)
           ),
-          defactoMinimumStake: new u128(0)
+          defactoMinimumStake: createMock('u128', 0)
         },
-        defactoMinimumStake: new u128(0)
+        defactoMinimumStake: createMock('u128', 0)
       }
     );
   }
@@ -292,14 +272,14 @@ export class Transport extends TransportBase implements ITransport {
   openingApplicationRanks (group: WorkingGroups, openingId: number): Promise<Balance[]> {
     const slots: Balance[] = [];
     for (let i = 0; i < 20; i++) {
-      slots.push(new u128((i * 100) + 10 + i + 1));
+      slots.push(createMock('u128', (i * 100) + 10 + i + 1));
     }
 
     return this.simulateApiResponse<Balance[]>(slots);
   }
 
-  expectedBlockTime (): Promise<number> {
-    return this.promise<number>(6);
+  expectedBlockTime (): number {
+    return 6;
   }
 
   blockHash (height: number): Promise<string> {
@@ -310,28 +290,24 @@ export class Transport extends TransportBase implements ITransport {
     return this.promise<Date>(new Date());
   }
 
-  transactionFee (): Promise<Balance> {
-    return this.simulateApiResponse<Balance>(new u128(5));
-  }
-
   accounts (): Subscribable<keyPairDetails[]> {
     return new Observable<keyPairDetails[]>(observer => {
       observer.next(
         [
           {
             shortName: 'KP1',
-            accountId: new GenericAccountId('5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
-            balance: new u128(23342)
+            accountId: createMock('AccountId', '5HZ6GtaeyxagLynPryM7ZnmLzoWFePKuDrkb4AT8rT4pU1fp'),
+            balance: createMock('u128', 23342)
           },
           {
             shortName: 'KP2',
-            accountId: new GenericAccountId('5DQqNWRFPruFs9YKheVMqxUbqoXeMzAWfVfcJgzuia7NA3D3'),
-            balance: new u128(993342)
+            accountId: createMock('AccountId', '5DQqNWRFPruFs9YKheVMqxUbqoXeMzAWfVfcJgzuia7NA3D3'),
+            balance: createMock('u128', 993342)
           },
           {
             shortName: 'KP3',
-            accountId: new GenericAccountId('5DBaczGTDhcHgwsZzNE5qW15GrQxxdyros4pYkcKrSUovFQ9'),
-            balance: new u128(242)
+            accountId: createMock('AccountId', '5DBaczGTDhcHgwsZzNE5qW15GrQxxdyros4pYkcKrSUovFQ9'),
+            balance: createMock('u128', 242)
           }
         ]
       );
@@ -352,13 +328,10 @@ export class Transport extends TransportBase implements ITransport {
         starting_block_hash: 'somehash',
         starting_time: yesterday()
       },
-      opening: new Opening({
-        created: new u32(50000),
+      opening: createMock('Opening', {
+        created: 50000,
         stage: mockStage,
-        max_review_period_length: new u32(100),
-        application_rationing_policy: new Option(ApplicationRationingPolicy),
-        application_staking_policy: new Option(StakingPolicy),
-        role_staking_policy: new Option(StakingPolicy),
+        max_review_period_length: 100,
         human_readable_text: newMockHumanReadableText({
           version: 1,
           headline: 'Help us curate awesome content',
@@ -400,8 +373,8 @@ export class Transport extends TransportBase implements ITransport {
           }
         })
       }),
-      applicationStake: new u128(5),
-      roleStake: new u128(15),
+      applicationStake: createMock('u128', 5),
+      roleStake: createMock('u128', 15),
       rank: 21,
       capacity: 20
     }];
@@ -411,12 +384,12 @@ export class Transport extends TransportBase implements ITransport {
   async myRoles (address: string): Promise<ActiveRole[]> {
     return [
       {
-        workerId: new CuratorId(1),
+        workerId: createMock('CuratorId', 1),
         name: workerRoleNameByGroup[WorkingGroups.ContentCurators],
         group: WorkingGroups.ContentCurators,
         url: 'some URL',
-        reward: new u128(321),
-        stake: new u128(12343200)
+        reward: createMock('u128', 321),
+        stake: createMock('u128', 12343200)
       }
     ];
   }

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

@@ -2,14 +2,15 @@ import { map, switchMap } from 'rxjs/operators';
 
 import ApiPromise from '@polkadot/api/promise';
 import { Balance } from '@polkadot/types/interfaces';
-import { GenericAccountId, Option, u128, Vec } from '@polkadot/types';
+import { Option, Vec } from '@polkadot/types';
 import { Constructor } from '@polkadot/types/types';
 import { Moment } from '@polkadot/types/interfaces/runtime';
 import { QueueTxExtrinsicAdd } from '@polkadot/react-components/Status/types';
-import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
 import keyringOption from '@polkadot/ui-keyring/options';
 
-import { APIQueryCache, MultipleLinkedMapEntry, SingleLinkedMapEntry, Subscribable, Transport as TransportBase } from '@polkadot/joy-utils/index';
+import { APIQueryCache } from '@polkadot/joy-utils/transport/APIQueryCache';
+import { Subscribable } from '@polkadot/joy-utils/react/helpers';
+import BaseTransport from '@polkadot/joy-utils/transport/base';
 
 import { ITransport } from './transport';
 import { GroupMember } from './elements';
@@ -32,8 +33,8 @@ import {
 import { Application, Opening, OpeningId, ApplicationId, ActiveApplicationStage } from '@joystream/types/hiring';
 import { Stake, StakeId } from '@joystream/types/stake';
 import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards';
-import { Membership, MemberId, ActorId } from '@joystream/types/members';
-import { createAccount, generateSeed } from '@polkadot/joy-utils/accounts';
+import { Membership, MemberId } from '@joystream/types/members';
+import { createAccount, generateSeed } from '@polkadot/joy-utils/functions/accounts';
 
 import { WorkingGroupMembership, GroupLeadStatus } from './tabs/WorkingGroup';
 import { WorkingGroupOpening } from './tabs/Opportunities';
@@ -146,23 +147,26 @@ const workingGroupsApiMapping: WGApiMapping = {
   }
 };
 
-export class Transport extends TransportBase implements ITransport {
-  protected api: ApiPromise
-  protected cachedApi: APIQueryCache
+export class Transport extends BaseTransport implements ITransport {
   protected queueExtrinsic: QueueTxExtrinsicAdd
 
   constructor (api: ApiPromise, queueExtrinsic: QueueTxExtrinsicAdd) {
-    super();
-    this.api = api;
-    this.cachedApi = new APIQueryCache(api);
+    super(api, new APIQueryCache(api));
     this.queueExtrinsic = queueExtrinsic;
   }
 
+  apiMethodByGroup(group: WorkingGroups, method: WGApiMethodType) {
+    const apiModule = workingGroupsApiMapping[group].module;
+    const apiMethod = workingGroupsApiMapping[group].methods.query[method];
+
+    return this.api.query[apiModule][apiMethod];
+  }
+
   cachedApiMethodByGroup (group: WorkingGroups, method: WGApiMethodType) {
     const apiModule = workingGroupsApiMapping[group].module;
     const apiMethod = workingGroupsApiMapping[group].methods.query[method];
 
-    return this.cachedApi.query[apiModule][apiMethod];
+    return this.cacheApi.query[apiModule][apiMethod];
   }
 
   apiExtrinsicByGroup (group: WorkingGroups, method: WGApiTxMethodType) {
@@ -173,17 +177,12 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   unsubscribe () {
-    this.cachedApi.unsubscribe();
+    this.cacheApi.unsubscribe();
   }
 
   protected async stakeValue (stakeId: StakeId): Promise<Balance> {
-    const stake = new SingleLinkedMapEntry<Stake>(
-      Stake,
-      await this.cachedApi.query.stake.stakes(
-        stakeId
-      )
-    );
-    return stake.value.value;
+    const stake = await this.cacheApi.query.stake.stakes(stakeId) as Stake;
+    return stake.value;
   }
 
   protected async workerStake (stakeProfile: GroupWorkerStakeProfile): Promise<Balance> {
@@ -191,25 +190,20 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   protected async rewardRelationshipById (id: RewardRelationshipId): Promise<RewardRelationship | undefined> {
-    const rewardRelationship = new SingleLinkedMapEntry<RewardRelationship>(
-      RewardRelationship,
-      await this.cachedApi.query.recurringRewards.rewardRelationships(id)
-    ).value;
+    const rewardRelationship = await this.cacheApi.query.recurringRewards.rewardRelationships(id) as RewardRelationship;
 
     return rewardRelationship.isEmpty ? undefined : rewardRelationship;
   }
 
   protected async workerTotalReward (relationshipId: RewardRelationshipId): Promise<Balance> {
     const relationship = await this.rewardRelationshipById(relationshipId);
-    return relationship?.total_reward_received || new u128(0);
+    return relationship?.total_reward_received || this.api.createType('Balance', 0);
   }
 
   protected async curatorMemberId (curator: Curator): Promise<MemberId> {
     const curatorApplicationId = curator.induction.curator_application_id;
-    const curatorApplication = new SingleLinkedMapEntry<CuratorApplication>(
-      CuratorApplication,
-      await this.cachedApi.query.contentWorkingGroup.curatorApplicationById(curatorApplicationId)
-    ).value;
+    const curatorApplication =
+      await this.cacheApi.query.contentWorkingGroup.curatorApplicationById(curatorApplicationId) as CuratorApplication;
 
     return curatorApplication.member_id;
   }
@@ -231,12 +225,12 @@ export class Transport extends TransportBase implements ITransport {
       ? await this.curatorMemberId(worker as Curator)
       : (worker as Worker).member_id;
 
-    const profile = await this.cachedApi.query.members.membershipById(memberId) as Membership;
+    const profile = await this.cacheApi.query.members.membershipById(memberId) as Membership;
     if (profile.handle.isEmpty) {
       throw new Error('No group member profile found!');
     }
 
-    let stakeValue: Balance = new u128(0);
+    let stakeValue: Balance = this.api.createType('Balance', 0);
     if (worker.role_stake_profile && worker.role_stake_profile.isSome) {
       stakeValue = await this.workerStake(worker.role_stake_profile.unwrap());
     }
@@ -256,27 +250,18 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   protected async areGroupRolesOpen (group: WorkingGroups, lead = false): Promise<boolean> {
-    const nextId = await this.cachedApiMethodByGroup(group, 'nextOpeningId')() as GroupOpeningId;
-
-    // This is chain specfic, but if next id is still 0, it means no openings have been added yet
-    if (nextId.eq(0)) {
-      return false;
-    }
-
-    const groupOpenings = new MultipleLinkedMapEntry<GroupOpeningId, GroupOpening>(
-      OpeningId,
-      workingGroupsApiMapping[group].openingType,
-      await this.cachedApiMethodByGroup(group, 'openingById')()
+    const groupOpenings = await this.entriesByIds<GroupOpeningId, GroupOpening>(
+      this.apiMethodByGroup(group, 'openingById')
     );
 
-    for (const groupOpening of groupOpenings.linked_values) {
+    for (const [/* id */, groupOpening] of groupOpenings) {
       const opening = await this.opening(groupOpening.hiring_opening_id.toNumber());
       if (
         opening.is_active &&
         (
           groupOpening instanceof WGOpening
             ? (lead === groupOpening.opening_type.isOfType('Leader'))
-            : !lead // Lead opening are never available for content working group currently
+            : !lead // Lead openings are never available for content working group currently
         )
       ) {
         return true;
@@ -287,41 +272,35 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   protected async currentCuratorLead (): Promise<GroupLeadWithMemberId | null> {
-    const optLeadId = (await this.cachedApi.query.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
+    const optLeadId = (await this.cacheApi.query.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
 
     if (!optLeadId.isSome) {
       return null;
     }
 
     const leadId = optLeadId.unwrap();
-    const lead = new SingleLinkedMapEntry<Lead>(
-      Lead,
-      await this.cachedApi.query.contentWorkingGroup.leadById(leadId)
-    );
+    const lead = await this.cacheApi.query.contentWorkingGroup.leadById(leadId) as Lead;
 
-    const memberId = lead.value.member_id;
+    if (lead.isEmpty || !lead.stage.isOfType('Active')) {
+      return null;
+    }
 
-    return {
-      lead: lead.value,
-      memberId
-    };
+    const memberId = lead.member_id;
+
+    return { lead, memberId };
   }
 
   protected async currentStorageLead (): Promise <GroupLeadWithMemberId | null> {
-    const optLeadId = (await this.cachedApi.query.storageWorkingGroup.currentLead()) as Option<WorkerId>;
+    const optLeadId = (await this.cacheApi.query.storageWorkingGroup.currentLead()) as Option<WorkerId>;
 
     if (!optLeadId.isSome) {
       return null;
     }
 
     const leadWorkerId = optLeadId.unwrap();
-    const leadWorkerLink = new SingleLinkedMapEntry(
-      Worker,
-      await this.cachedApi.query.storageWorkingGroup.workerById(leadWorkerId)
-    );
-    const leadWorker = leadWorkerLink.value;
+    const leadWorker = await this.cacheApi.query.storageWorkingGroup.workerById(leadWorkerId) as Worker;
 
-    if (!leadWorker.is_active) {
+    if (leadWorker.isEmpty) {
       return null;
     }
 
@@ -338,7 +317,7 @@ export class Transport extends TransportBase implements ITransport {
       : await this.currentStorageLead();
 
     if (currentLead !== null) {
-      const profile = await this.cachedApi.query.members.membershipById(currentLead.memberId) as Membership;
+      const profile = await this.cacheApi.query.members.membershipById(currentLead.memberId) as Membership;
 
       if (profile.handle.isEmpty) {
         throw new Error(`${group} lead profile not found!`);
@@ -377,29 +356,14 @@ export class Transport extends TransportBase implements ITransport {
     const leadRolesAvailable = await this.areGroupRolesOpen(group, true);
     const leadStatus = await this.groupLeadStatus(group);
 
-    const nextId = await this.cachedApiMethodByGroup(group, 'nextWorkerId')() as GroupWorkerId;
-
-    let workersWithIds: { worker: GroupWorker; id: GroupWorkerId }[] = [];
-    // This is chain specfic, but if next id is still 0, it means no workers have been added yet
-    if (!nextId.eq(0)) {
-      const values = new MultipleLinkedMapEntry<GroupWorkerId, GroupWorker>(
-        ActorId,
-        workingGroupsApiMapping[group].workerType,
-        await this.cachedApiMethodByGroup(group, 'workerById')() as GroupWorker
-      );
-
-      workersWithIds = values.linked_values
-        // First bind workers with ids
-        .map((worker, i) => ({ worker, id: values.linked_keys[i] }))
-        // Filter by: active and "not lead"
-        .filter(({ worker, id }) => worker.is_active && (!leadStatus.lead?.workerId || !id.eq(leadStatus.lead.workerId)));
-    }
+    const workers = (await this.entriesByIds<GroupWorkerId, GroupWorker>(
+        this.apiMethodByGroup(group, 'workerById')
+      ))
+      .filter(([id, worker]) => worker.is_active && (!leadStatus.lead?.workerId || !id.eq(leadStatus.lead.workerId)));
 
     return {
       leadStatus,
-      workers: await Promise.all(
-        workersWithIds.map(({ worker, id }) => this.groupMember(group, id, worker))
-      ),
+      workers: await Promise.all(workers.map(([id, worker]) => this.groupMember(group, id, worker))),
       workerRolesAvailable,
       leadRolesAvailable
     };
@@ -440,43 +404,29 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   protected async opening (id: number): Promise<Opening> {
-    const opening = new SingleLinkedMapEntry<Opening>(
-      Opening,
-      await this.cachedApi.query.hiring.openingById(id)
-    );
+    const opening = await this.cacheApi.query.hiring.openingById(id) as Opening;
 
-    return opening.value;
+    return opening;
   }
 
   protected async groupOpeningApplications (group: WorkingGroups, groupOpeningId: number): Promise<WorkingGroupPair<Application, GroupApplication>[]> {
-    const output = new Array<WorkingGroupPair<Application, GroupApplication>>();
-
-    const nextAppid = (await this.cachedApiMethodByGroup(group, 'nextApplicationId')()) as GroupApplicationId;
-    for (let i = 0; i < nextAppid.toNumber(); i++) {
-      const cApplication = new SingleLinkedMapEntry<GroupApplication>(
-        workingGroupsApiMapping[group].applicationType,
-        await this.cachedApiMethodByGroup(group, 'applicationById')(i)
-      );
-
-      if (cApplication.value.opening_id.toNumber() !== groupOpeningId) {
-        continue;
-      }
+    const groupApplications = await this.entriesByIds<GroupApplicationId, GroupApplication>(
+      this.apiMethodByGroup(group, 'applicationById')
+    );
 
-      const appId = cApplication.value.application_id;
-      const baseApplications = new SingleLinkedMapEntry<Application>(
-        Application,
-        await this.cachedApi.query.hiring.applicationById(
-          appId
-        )
-      );
+    const openingGroupApplications = groupApplications
+      .filter(([id, groupApplication]) => groupApplication.opening_id.toNumber() === groupOpeningId);
 
-      output.push({
-        hiringModule: baseApplications.value,
-        workingGroup: cApplication.value
-      });
-    }
+    const openingHiringApplications = (await Promise.all(
+      openingGroupApplications.map(
+        ([id, groupApplication]) => this.cacheApi.query.hiring.applicationById(groupApplication.application_id)
+      )
+    )) as Application[];
 
-    return output;
+    return openingHiringApplications.map((hiringApplication, index) => ({
+      hiringModule: hiringApplication,
+      workingGroup: openingGroupApplications[index][1]
+    }));
   }
 
   async groupOpening (group: WorkingGroups, id: number): Promise<WorkingGroupOpening> {
@@ -485,10 +435,7 @@ export class Transport extends TransportBase implements ITransport {
       throw new Error('invalid id');
     }
 
-    const groupOpening = new SingleLinkedMapEntry<GroupOpening>(
-      workingGroupsApiMapping[group].openingType,
-      await this.cachedApiMethodByGroup(group, 'openingById')(id)
-    ).value;
+    const groupOpening = await this.cachedApiMethodByGroup(group, 'openingById')(id) as GroupOpening;
 
     const opening = await this.opening(
       groupOpening.hiring_opening_id.toNumber()
@@ -510,9 +457,9 @@ export class Transport extends TransportBase implements ITransport {
         maxNumberOfApplications: opening.max_applicants,
         requiredApplicationStake: stakes.application,
         requiredRoleStake: stakes.role,
-        defactoMinimumStake: new u128(0)
+        defactoMinimumStake: this.api.createType('u128', 0)
       },
-      defactoMinimumStake: new u128(0)
+      defactoMinimumStake: this.api.createType('u128', 0)
     });
   }
 
@@ -541,10 +488,8 @@ export class Transport extends TransportBase implements ITransport {
     );
   }
 
-  expectedBlockTime (): Promise<number> {
-    return this.promise<number>(
-      (this.api.consts.babe.expectedBlockTime as Moment).toNumber() / 1000
-    );
+  expectedBlockTime (): number {
+    return (this.api.consts.babe.expectedBlockTime as Moment).toNumber() / 1000;
   }
 
   async blockHash (height: number): Promise<string> {
@@ -560,10 +505,6 @@ export class Transport extends TransportBase implements ITransport {
     return new Date(blockTime.toNumber());
   }
 
-  transactionFee (): Promise<Balance> {
-    return this.promise<Balance>(new u128(5));
-  }
-
   accounts (): Subscribable<keyPairDetails[]> {
     return keyringOption.optionsSubject.pipe(
       map(accounts => {
@@ -572,8 +513,8 @@ export class Transport extends TransportBase implements ITransport {
           .map(async (result, k) => {
             return {
               shortName: result.name,
-              accountId: new GenericAccountId(result.value as string),
-              balance: await this.cachedApi.query.balances.freeBalance(result.value as string)
+              accountId: this.api.createType('AccountId', result.value),
+              balance: (await this.api.derive.balances.account(result.value as string)).freeBalance
             };
           });
       }),
@@ -627,51 +568,40 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   async openingApplicationsByAddressAndGroup (group: WorkingGroups, roleKey: string): Promise<OpeningApplication[]> {
-    const applications = new MultipleLinkedMapEntry<GroupApplicationId, GroupApplication>(
-      ApplicationId,
-      workingGroupsApiMapping[group].applicationType,
-      await this.cachedApiMethodByGroup(group, 'applicationById')()
-    );
-
-    const myApps = applications.linked_values.filter(app => app.role_account_id.eq(roleKey));
-    const myAppIds = applications.linked_keys.filter((id, key) => applications.linked_values[key].role_account_id.eq(roleKey));
+    const myGroupApplications = (await this.entriesByIds<GroupApplicationId, GroupApplication>(
+        await this.apiMethodByGroup(group, 'applicationById')
+      ))
+      .filter(([id, groupApplication]) => groupApplication.role_account_id.eq(roleKey))
 
-    const hiringAppPairs = await Promise.all(
-      myApps.map(
-        async app => new SingleLinkedMapEntry<Application>(
-          Application,
-          await this.cachedApi.query.hiring.applicationById(
-            app.application_id
-          )
-        )
+    const myHiringApplications = await Promise.all(
+      myGroupApplications.map(
+        ([id, groupApplication]) => this.cacheApi.query.hiring.applicationById(groupApplication.application_id)
       )
-    );
-
-    const hiringApps = hiringAppPairs.map(app => app.value);
+    ) as Application[];
 
     const stakes = await Promise.all(
-      hiringApps.map(app => this.applicationStakes(app))
+      myHiringApplications.map(app => this.applicationStakes(app))
     );
 
     const openings = await Promise.all(
-      myApps.map(application => {
-        return this.groupOpening(group, application.opening_id.toNumber());
+      myGroupApplications.map(([id, groupApplication]) => {
+        return this.groupOpening(group, groupApplication.opening_id.toNumber());
       })
     );
 
     const allAppsByOpening = (await Promise.all(
-      myApps.map(application => {
-        return this.groupOpeningApplications(group, application.opening_id.toNumber());
-      })
+      myGroupApplications.map(([id, groupApplication]) => (
+        this.groupOpeningApplications(group, groupApplication.opening_id.toNumber())
+      ))
     ));
 
     return await Promise.all(
       openings.map(async (o, key) => {
         return {
-          id: myAppIds[key].toNumber(),
-          hired: isApplicationHired(hiringApps[key]),
-          cancelledReason: classifyApplicationCancellation(hiringApps[key]),
-          rank: await this.myApplicationRank(hiringApps[key], allAppsByOpening[key].map(a => a.hiringModule)),
+          id: myGroupApplications[key][0].toNumber(),
+          hired: isApplicationHired(myHiringApplications[key]),
+          cancelledReason: classifyApplicationCancellation(myHiringApplications[key]),
+          rank: await this.myApplicationRank(myHiringApplications[key], allAppsByOpening[key].map(a => a.hiringModule)),
           capacity: o.applications.maxNumberOfApplications,
           stage: o.stage,
           opening: o.opening,
@@ -696,30 +626,22 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   async myRolesByGroup (group: WorkingGroups, roleKeyId: string): Promise<ActiveRole[]> {
-    const workers = new MultipleLinkedMapEntry<GroupWorkerId, GroupWorker>(
-      ActorId,
-      workingGroupsApiMapping[group].workerType,
-      await this.cachedApiMethodByGroup(group, 'workerById')()
+    const workers = await this.entriesByIds<GroupWorkerId, GroupWorker>(
+      this.apiMethodByGroup(group, 'workerById')
     );
 
     const groupLead = (await this.groupLeadStatus(group)).lead;
 
     return Promise.all(
       workers
-        .linked_values
-        .toArray()
-        // We need to associate worker ids with workers BEFORE filtering the array
-        .map((worker, index) => ({ worker, id: workers.linked_keys[index] }))
-        .filter(({ worker }) => worker.role_account_id.eq(roleKeyId) && worker.is_active)
-        .map(async workerWithId => {
-          const { worker, id } = workerWithId;
-
-          let stakeValue: Balance = new u128(0);
+        .filter(([id, worker]) => worker.role_account_id.eq(roleKeyId) && worker.is_active)
+        .map(async ([id, worker]) => {
+          let stakeValue: Balance = this.api.createType('u128', 0);
           if (worker.role_stake_profile && worker.role_stake_profile.isSome) {
             stakeValue = await this.workerStake(worker.role_stake_profile.unwrap());
           }
 
-          let earnedValue: Balance = new u128(0);
+          let earnedValue: Balance = this.api.createType('u128', 0);
           if (worker.reward_relationship && worker.reward_relationship.isSome) {
             earnedValue = await this.workerTotalReward(worker.reward_relationship.unwrap());
           }
@@ -768,7 +690,7 @@ export class Transport extends TransportBase implements ITransport {
     roleStake: Balance,
     applicationText: string): Promise<number> {
     return new Promise<number>((resolve, reject) => {
-      (this.cachedApi.query.members.memberIdsByControllerAccountId(sourceAccount) as Promise<Vec<MemberId>>)
+      (this.cacheApi.query.members.memberIdsByControllerAccountId(sourceAccount) as Promise<Vec<MemberId>>)
         .then(membershipIds => {
           if (membershipIds.length === 0) {
             reject(new Error('No membship ID associated with this address'));
@@ -786,7 +708,7 @@ export class Transport extends TransportBase implements ITransport {
             roleStake.eq(Zero) ? null : roleStake, // Role stake
             appStake.eq(Zero) ? null : appStake, // Application stake
             applicationText // Human readable text
-          ) as unknown as SubmittableExtrinsic;
+          );
 
           const txFailedCb = () => {
             reject(new Error('transaction failed'));
@@ -806,26 +728,28 @@ export class Transport extends TransportBase implements ITransport {
     });
   }
 
-  leaveRole (group: WorkingGroups, sourceAccount: string, id: number, rationale: string) {
+  leaveRole (group: WorkingGroups, sourceAccount: string, id: number, rationale: string, txSuccessCb?: () => void) {
     const tx = this.apiExtrinsicByGroup(group, 'leaveRole')(
       id,
       rationale
-    ) as unknown as SubmittableExtrinsic;
+    );
 
     this.queueExtrinsic({
       accountId: sourceAccount,
-      extrinsic: tx
+      extrinsic: tx,
+      txSuccessCb
     });
   }
 
-  withdrawApplication (group: WorkingGroups, sourceAccount: string, id: number) {
+  withdrawApplication (group: WorkingGroups, sourceAccount: string, id: number, txSuccessCb?: () => void) {
     const tx = this.apiExtrinsicByGroup(group, 'withdrawApplication')(
       id
-    ) as unknown as SubmittableExtrinsic;
+    );
 
     this.queueExtrinsic({
       accountId: sourceAccount,
-      extrinsic: tx
+      extrinsic: tx,
+      txSuccessCb
     });
   }
 }

+ 4 - 5
pioneer/packages/joy-roles/src/transport.ts

@@ -1,4 +1,4 @@
-import { Subscribable } from '@polkadot/joy-utils/index';
+import { Subscribable } from '@polkadot/joy-utils/react/helpers';
 import { Balance } from '@polkadot/types/interfaces';
 
 import { WorkingGroupMembership, GroupLeadStatus } from './tabs/WorkingGroup';
@@ -14,10 +14,9 @@ export interface ITransport {
   currentOpportunities: () => Promise<Array<WorkingGroupOpening>>;
   groupOpening: (group: WorkingGroups, id: number) => Promise<WorkingGroupOpening>;
   openingApplicationRanks: (group: WorkingGroups, openingId: number) => Promise<Balance[]>;
-  expectedBlockTime: () => Promise<number>;
+  expectedBlockTime: () => number;
   blockHash: (height: number) => Promise<string>;
   blockTimestamp: (height: number) => Promise<Date>;
-  transactionFee: () => Promise<Balance>;
   accounts: () => Subscribable<keyPairDetails[]>;
   openingApplicationsByAddress: (address: string) => Promise<OpeningApplication[]>;
   myRoles: (address: string) => Promise<ActiveRole[]>;
@@ -30,6 +29,6 @@ export interface ITransport {
     roleStake: Balance,
     applicationText: string
   ) => Promise<number>;
-  leaveRole: (group: WorkingGroups, sourceAccount: string, id: number, rationale: string) => void;
-  withdrawApplication: (group: WorkingGroups, sourceAccount: string, id: number) => void;
+  leaveRole: (group: WorkingGroups, sourceAccount: string, id: number, rationale: string, successCb?: () => void) => void;
+  withdrawApplication: (group: WorkingGroups, sourceAccount: string, id: number, successCb?: () => void) => void;
 }

+ 0 - 18
pioneer/packages/joy-utils-old/src/Loadable.tsx

@@ -1,18 +0,0 @@
-import React from 'react';
-
-export function Loadable<P extends {[index: string]: any}> (required: string[], f: (props: P) => React.ReactNode | void): (props: P) => any {
-  const loading = <div className="spinner"></div>;
-
-  return (props: P) => {
-    if (!props) {
-      return loading;
-    }
-
-    for (const requirement of required) {
-      if (typeof props[requirement] === 'undefined') {
-        return loading;
-      }
-    }
-    return f(props);
-  };
-}

+ 0 - 76
pioneer/packages/joy-utils-old/src/View.tsx

@@ -1,76 +0,0 @@
-import React, { useState, useEffect, useMemo } from 'react';
-
-import { Controller } from './Controller';
-
-export type Params = Map<string, string>
-
-const newParams = (): Params => {
-  return new Map<string, string>();
-};
-
-type viewProps = {
-  params?: Params;
-}
-
-type renderFn<C, S> = (state: S, controller: C, params: Params) => React.ReactElement | null
-
-export type ViewComponent<C, P = any, S = any> = React.FC<viewProps>
-export type ViewComponentFactory<C, P = any, S = any> = (controller: C) => React.FC<viewProps>
-
-type ViewPropsExplicit<C, S> = {
-  errorComponent?: React.ComponentType;
-  render: renderFn<C, S>;
-}
-
-type ViewProps<C, S> = ViewPropsExplicit<C, S> | renderFn<C, S>
-
-function DefaultError () {
-  return <p>There has been an error</p>;
-}
-
-export function View<C extends Controller<S, any>, S> (args: ViewProps<C, S>): ViewComponentFactory<C, S> {
-  return (controller: C) => {
-    return (props: viewProps, ctx: any): React.ReactElement | null => {
-      const [state, setState] = useState<S>(controller.state);
-
-      const onUpdate = (newState: S) => {
-        setState({ ...newState });
-      };
-
-      useEffect(() => {
-        controller.subscribe(onUpdate);
-        controller.dispatch(); // Dispatch on first subscription (in case there's was a re-render of the View)
-
-        return () => {
-          controller.unsubscribe(onUpdate);
-        };
-      }, []);
-
-      let context: Params;
-      if (typeof props.params !== 'undefined') {
-        context = props.params;
-      } else {
-        context = newParams();
-      }
-
-      let renderFn: renderFn<C, S>;
-      let Err: React.ComponentType = DefaultError;
-      if (typeof args === 'function') {
-        renderFn = args;
-      } else {
-        renderFn = args.render;
-
-        if (typeof args.errorComponent !== 'undefined') {
-          Err = args.errorComponent;
-        }
-      }
-
-      return useMemo(() => {
-        if (controller.hasError()) {
-          return Err ? <Err /> : null;
-        }
-        return renderFn(state, controller, context);
-      }, [state]);
-    };
-  };
-}

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

@@ -4,16 +4,10 @@ import { KeypairType } from '@polkadot/util-crypto/types';
 
 import FileSaver from 'file-saver';
 import { DEV_PHRASE } from '@polkadot/keyring/defaults';
-import { InputAddress } from '@polkadot/react-components';
 import keyring from '@polkadot/ui-keyring';
 import { isHex, u8aToHex } from '@polkadot/util';
 import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
 
-import { ApiPromise } from '@polkadot/api';
-import { MemberId, Membership } from '@joystream/types/members';
-import { AccountId } from '@polkadot/types/interfaces';
-import { Vec } from '@polkadot/types/codec';
-
 export type SeedType = 'bip' | 'raw' | 'dev';
 
 export interface AddressState {
@@ -113,7 +107,6 @@ export function downloadAccount ({ json, pair }: CreateResult): void {
   const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
 
   FileSaver.saveAs(blob, `${pair.address}.json`);
-  InputAddress.setLastValue('account', pair.address);
 }
 
 export function createAccount (suri: string, pairType: KeypairType, name: string, password: string, success: string): ActionStatus {
@@ -140,19 +133,3 @@ export function createAccount (suri: string, pairType: KeypairType, name: string
 export function isPasswordValid (password: string): boolean {
   return password.length === 0 || keyring.isPassValid(password);
 }
-
-export type MemberFromAccount = { account: string; id: number; profile?: Membership };
-
-// TODO: Use transport instead (now that it's available in joy-utils)
-export async function memberFromAccount (api: ApiPromise, accountId: AccountId | string): Promise<MemberFromAccount> {
-  const [memberId] =
-    ((await api.query.members.memberIdsByRootAccountId(accountId)) as Vec<MemberId>)
-      .concat((await api.query.members.memberIdsByControllerAccountId(accountId)) as Vec<MemberId>);
-  const member = (await api.query.members.membershipById(memberId)) as Membership;
-
-  return {
-    account: accountId.toString(),
-    id: memberId.toNumber(),
-    profile: member.handle.isEmpty ? undefined : member
-  };
-}

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

@@ -2,3 +2,10 @@ export { default as Section } from './Section';
 export { default as TxButton } from './TxButton';
 export { MutedSpan, MutedDiv } from './MutedText';
 export { FlexCenter } from './FlexCenter';
+export { default as PromiseComponent } from './PromiseComponent';
+export { default as MemberByAccountPreview } from './MemberByAccountPreview';
+export { default as MemberProfilePreview } from './MemberProfilePreview';
+export { default as MembersDropdown } from './MembersDropdown';
+export { default as InputStake } from './InputStake';
+export { ApplicationsDetails, ApplicationsDetailsByIds } from './working-groups/ApplicationDetails';
+export { LeadInfo, LeadInfoFromId } from './working-groups/LeadInfo';

+ 1 - 1
pioneer/packages/joy-utils-old/src/Controller.tsx → pioneer/packages/joy-utils/src/react/helpers/Controller.tsx

@@ -7,7 +7,7 @@ type errorProps = {
 export class Controller<S, T> extends Observable<S & errorProps, T> {
   onError (desc: any) {
     this.state._hasError = true;
-    console.log(desc);
+    console.error(desc);
     this.dispatch();
   }
 

+ 5 - 1
pioneer/packages/joy-utils-old/src/Observable.ts → pioneer/packages/joy-utils/src/react/helpers/Observable.ts

@@ -4,9 +4,11 @@ export abstract class Observable<S extends { [key: string ]: any }, T> implement
   state: S
   protected transport: T
   protected observers: Observer<S>[] = []
+  protected initialState: S
 
   constructor (transport: T, initialState: S) {
-    this.state = initialState;
+    this.initialState = initialState;
+    this.state = { ...initialState };
     this.transport = transport;
   }
 
@@ -31,4 +33,6 @@ export abstract class Observable<S extends { [key: string ]: any }, T> implement
     }
     this.dispatch();
   }
+
+  public refreshState() { }
 }

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


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

@@ -3,3 +3,8 @@ import React from 'react';
 export function componentName (WrappedComponent: React.ComponentType<any>) {
   return WrappedComponent.displayName || WrappedComponent.name || 'Component';
 }
+
+export { Controller } from './Controller';
+export { Observable } from './Observable';
+export { memoize } from './memoize';
+export { Subscribable } from './Subscribable';

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


+ 22 - 0
pioneer/packages/joy-utils/src/react/hocs/Loadable.tsx

@@ -0,0 +1,22 @@
+import React from 'react';
+import { componentName } from '../helpers';
+
+export function Loadable<P extends {[index: string]: any}>
+  (required: string[], Component: React.FC<P>): React.FC<P> {
+
+  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
+    if (!props) {
+      return <div className="spinner"></div>;
+    }
+
+    for (const requirement of required) {
+      if (typeof props[requirement] === 'undefined') {
+        return <div className="spinner"></div>;
+      }
+    }
+    return <Component {...props} />;
+  };
+  ResultComponent.displayName = `Loadable(${componentName(Component)})`;
+
+  return ResultComponent;
+}

+ 71 - 0
pioneer/packages/joy-utils/src/react/hocs/View.tsx

@@ -0,0 +1,71 @@
+import React, { useState, useEffect } from 'react';
+
+import { Controller } from '../helpers';
+
+export type ParamsMap = Map<string, string | undefined>
+
+type ViewRendererProps<C> = {
+  params?: Record<string, string | undefined>;
+  controller: C;
+}
+
+type RenderComponentProps<C, S> = {
+  state: S,
+  controller: C,
+  params: ParamsMap
+}
+type RenderComponent<C, S> = React.ComponentType<RenderComponentProps<C, S>>
+
+export type ViewRenderer<C> = React.FC<ViewRendererProps<C>>
+
+type ViewArgsExplicit<C, S> = {
+  errorComponent?: React.ComponentType;
+  renderComponent: RenderComponent<C, S>;
+}
+
+type ViewArgs<C, S> = ViewArgsExplicit<C, S> | RenderComponent<C, S>
+
+function DefaultError () {
+  return <p>There has been an error</p>;
+}
+
+export function View<C extends Controller<S, any>, S> (args: ViewArgs<C, S>): ViewRenderer<C> {
+    return (props: ViewRendererProps<C>): React.ReactElement | null => {
+      const { controller } = props;
+      const [state, setState] = useState<S>(controller.state);
+
+      const onUpdate = (newState: S) => {
+        setState({ ...newState });
+      };
+
+      useEffect(() => {
+        controller.subscribe(onUpdate);
+        controller.refreshState(); // Refresh controller state on View mount
+
+        return () => {
+          controller.unsubscribe(onUpdate);
+        };
+      }, []);
+
+      const params = props.params ? new Map(Object.entries(props.params)) : new Map<string, string | undefined>();
+
+      let RenderComponent: RenderComponent<C, S>;
+      let Err: React.ComponentType = DefaultError;
+      if (typeof args === 'function') {
+        RenderComponent = args;
+      } else {
+        RenderComponent = args.renderComponent;
+
+        if (typeof args.errorComponent !== 'undefined') {
+          Err = args.errorComponent;
+        }
+      }
+
+      if (controller.hasError()) {
+        return Err ? <Err /> : null;
+      }
+      else {
+        return <RenderComponent { ...{state, controller, params} } />;
+      }
+    };
+}

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

@@ -0,0 +1,2 @@
+export { Loadable } from './Loadable';
+export { View } from './View';

+ 9 - 1
pioneer/packages/joy-utils-old/src/Transport.ts → pioneer/packages/joy-utils/src/transport/mock/base.ts

@@ -1,4 +1,4 @@
-export abstract class Transport {
+export default abstract class BaseTransport {
   protected promise<T> (value: T, timeout?: number): Promise<T> {
     return new Promise<T>((resolve, reject) => {
       if (timeout) {
@@ -8,4 +8,12 @@ export abstract class Transport {
       }
     });
   }
+
+  protected simulateApiResponse<T> (value: T): Promise<T> {
+    return this.promise<T>(value, this.randomTimeout());
+  }
+
+  protected randomTimeout (min = 1, max = 20): number {
+    return Math.random() * (max - min) + min;
+  }
 }

+ 6 - 0
pioneer/packages/react-components/src/styles/joystream.ts

@@ -177,4 +177,10 @@ export default css`
       max-width: 10em;
     }
   }
+
+  /* Fix options inside struct UI bug */
+  .ui--Params-Struct + .ui--Param-Option-toggle {
+    bottom: auto;
+    top: 0;
+  }
 `;

+ 3 - 14
pioneer/tsconfig.json

@@ -7,7 +7,6 @@
     "packages/joy-forum/**/*",
     "packages/joy-help/**/*",
     "packages/joy-media/**/*",
-    "packages/joy-roles/**/*",
     "packages/joy-settings/**/*",
     "packages/joy-utils-old/**/*"
   ],
@@ -18,27 +17,17 @@
     "resolveJsonModule": true,
     "baseUrl": ".",
     "paths": {
-      "@polkadot/types/augment": [ "../types/src/definitions/augment-types.ts" ],
-      // "@joystream/types/": [ "../types/src/" ],
-      // "@joystream/types/*": [ "../types/src/*" ],
+      "@polkadot/types/augment": [ "../types/augment-codec/augment-types.ts" ],
       "@polkadot/joy-election/": [ "packages/joy-election/src/" ],
       "@polkadot/joy-election/*": [ "packages/joy-election/src/*" ],
-      // "@polkadot/joy-forum/": [ "packages/joy-forum/src/" ],
-      // "@polkadot/joy-forum/*": [ "packages/joy-forum/src/*" ],
-      // "@polkadot/joy-help/": [ "packages/joy-help/src/" ],
-      // "@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-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-roles/": [ "packages/joy-roles/src/" ],
+      "@polkadot/joy-roles/*": [ "packages/joy-roles/src/*" ],
       "@polkadot/joy-utils/": [ "packages/joy-utils/src/" ],
       "@polkadot/joy-utils/*": [ "packages/joy-utils/src/*" ],
       "@polkadot/apps/*": ["packages/apps/src/*"],

File diff suppressed because it is too large
+ 626 - 14
yarn.lock


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