Browse Source

Update transports and compontents

Leszek Wiesner 4 years ago
parent
commit
2103b896ee
27 changed files with 234 additions and 2016 deletions
  1. 1 1
      pioneer/packages/apps-routing/src/joy-roles.ts
  2. 1 1
      pioneer/packages/apps/public/locales/en/index.json
  3. 0 76
      pioneer/packages/joy-proposals/src/forms/MintCapacityForm.tsx
  4. 0 185
      pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupLeadForm.tsx
  5. 0 23
      pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx
  6. 0 3
      pioneer/packages/joy-proposals/src/forms/index.ts
  7. 0 8
      pioneer/packages/joy-proposals/src/index.tsx
  8. 0 6
      pioneer/packages/joy-proposals/src/stories/ProposalForms.stories.tsx
  9. 1 20
      pioneer/packages/joy-proposals/src/validationSchema.ts
  10. 0 2
      pioneer/packages/joy-roles/src/elements.tsx
  11. 0 5
      pioneer/packages/joy-roles/src/index.tsx
  12. 0 907
      pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx
  13. 1 2
      pioneer/packages/joy-roles/src/tabs/MyRoles.tsx
  14. 45 161
      pioneer/packages/joy-roles/src/transport.substrate.ts
  15. 31 9
      pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx
  16. 9 0
      pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx
  17. 6 12
      pioneer/packages/joy-utils/src/consts/proposals.ts
  18. 2 124
      pioneer/packages/joy-utils/src/react/hocs/accounts.tsx
  19. 0 41
      pioneer/packages/joy-utils/src/transport/contentWorkingGroup.ts
  20. 0 3
      pioneer/packages/joy-utils/src/transport/index.ts
  21. 85 132
      pioneer/packages/joy-utils/src/transport/tokenomics.ts
  22. 1 1
      pioneer/packages/joy-utils/src/transport/workingGroups.ts
  23. 0 1
      pioneer/packages/joy-utils/src/types/proposals.ts
  24. 17 22
      pioneer/packages/joy-utils/src/types/tokenomics.ts
  25. 0 1
      pioneer/src/@types/ipfs-only-hash/index.d.ts
  26. 17 15
      types/src/hiring/index.ts
  27. 17 255
      yarn.lock

+ 1 - 1
pioneer/packages/apps-routing/src/joy-roles.ts

@@ -7,7 +7,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
     Component: Roles,
     display: {
       needsApi: [
-        'query.contentWorkingGroup.mint',
+        'query.contentDirectoryWorkingGroup.mint',
         'query.storageWorkingGroup.mint'
       ]
     },

+ 1 - 1
pioneer/packages/apps/public/locales/en/index.json

@@ -31,4 +31,4 @@
   "react-params.json",
   "react-query.json",
   "react-signer.json"
-]
+]

+ 0 - 76
pioneer/packages/joy-proposals/src/forms/MintCapacityForm.tsx

@@ -1,76 +0,0 @@
-import React from 'react';
-import * as Yup from 'yup';
-import { getFormErrorLabelsProps } from './errorHandling';
-import { GenericProposalForm,
-  GenericFormValues,
-  genericFormDefaultValues,
-  withProposalFormData,
-  ProposalFormExportProps,
-  ProposalFormContainerProps,
-  ProposalFormInnerProps } from './GenericProposalForm';
-import Validation from '../validationSchema';
-import { InputFormField } from './FormFields';
-import { withFormContainer } from './FormContainer';
-import { ProposalType } from '@polkadot/joy-utils/types/proposals';
-import { formatBalance } from '@polkadot/util';
-
-export type FormValues = GenericFormValues & {
-  capacity: string;
-};
-
-const defaultValues: FormValues = {
-  ...genericFormDefaultValues,
-  capacity: ''
-};
-
-type MintCapacityGroup = 'Council' | 'Content Working Group';
-
-// Aditional props coming all the way from export comonent into the inner form.
-type FormAdditionalProps = {
-  mintCapacityGroup: MintCapacityGroup;
-  txMethod: string;
-  proposalType: ProposalType;
-};
-type ExportComponentProps = ProposalFormExportProps<FormAdditionalProps, FormValues>;
-type FormContainerProps = ProposalFormContainerProps<ExportComponentProps>;
-type FormInnerProps = ProposalFormInnerProps<FormContainerProps, FormValues>;
-
-const MintCapacityForm: React.FunctionComponent<FormInnerProps> = (props) => {
-  const { handleChange, errors, touched, mintCapacityGroup, values, txMethod, initialData, proposalType } = props;
-  const errorLabelsProps = getFormErrorLabelsProps<FormValues>(errors, touched);
-
-  return (
-    <GenericProposalForm
-      {...props}
-      txMethod={txMethod}
-      proposalType={proposalType}
-      submitParams={[values.capacity]}
-    >
-      <InputFormField
-        error={errorLabelsProps.capacity}
-        onChange={handleChange}
-        name='capacity'
-        placeholder={ (initialData && initialData.capacity) }
-        label={`${mintCapacityGroup} Mint Capacity`}
-        help={`The new mint capacity you propse for ${mintCapacityGroup}`}
-        unit={ formatBalance.getDefaults().unit }
-        value={values.capacity}
-      />
-    </GenericProposalForm>
-  );
-};
-
-const FormContainer = withFormContainer<FormContainerProps, FormValues>({
-  mapPropsToValues: (props: FormContainerProps) => ({
-    ...defaultValues,
-    ...(props.initialData || {})
-  }),
-  validationSchema: Yup.object().shape({
-    ...Validation.All(),
-    ...Validation.SetContentWorkingGroupMintCapacity()
-  }),
-  handleSubmit: () => null,
-  displayName: 'MintCapacityForm'
-})(MintCapacityForm);
-
-export default withProposalFormData<FormContainerProps, ExportComponentProps>(FormContainer);

+ 0 - 185
pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupLeadForm.tsx

@@ -1,185 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { Dropdown, Label, Loader, Message, Icon, DropdownItemProps, DropdownOnSearchChangeData, DropdownProps } from 'semantic-ui-react';
-import { getFormErrorLabelsProps } from './errorHandling';
-import * as Yup from 'yup';
-import { GenericProposalForm,
-  GenericFormValues,
-  genericFormDefaultValues,
-  withProposalFormData,
-  ProposalFormExportProps,
-  ProposalFormContainerProps,
-  ProposalFormInnerProps } from './GenericProposalForm';
-import Validation from '../validationSchema';
-import { FormField } from './FormFields';
-import { withFormContainer } from './FormContainer';
-import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks';
-import { Membership } from '@joystream/types/members';
-import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent';
-import _ from 'lodash';
-
-export type FormValues = GenericFormValues & {
-  workingGroupLead: string;
-};
-
-const defaultValues: FormValues = {
-  ...genericFormDefaultValues,
-  workingGroupLead: ''
-};
-
-type FormAdditionalProps = Record<any, never>; // Aditional props coming all the way from export comonent into the inner form.
-type ExportComponentProps = ProposalFormExportProps<FormAdditionalProps, FormValues>;
-type FormContainerProps = ProposalFormContainerProps<ExportComponentProps>;
-type FormInnerProps = ProposalFormInnerProps<FormContainerProps, FormValues>;
-
-function memberOptionKey (id: number, profile: Membership) {
-  return `${id}:${profile.root_account.toString()}`;
-}
-
-const MEMBERS_QUERY_MIN_LENGTH = 4;
-const MEMBERS_NONE_OPTION: DropdownItemProps = {
-  key: '- NONE -',
-  text: '- NONE -',
-  value: 'none'
-};
-
-function membersToOptions (members: { id: number; profile: Membership }[]) {
-  return [MEMBERS_NONE_OPTION].concat(
-    members
-      .map(({ id, profile }) => ({
-        key: profile.handle,
-        text: `${profile.handle.toString()} (id:${id})`,
-        value: memberOptionKey(id, profile),
-        image: profile.avatar_uri.toString() ? { avatar: true, src: profile.avatar_uri } : null
-      }))
-  );
-}
-
-function filterMembers (options: DropdownItemProps[], query: string) {
-  if (query.length < MEMBERS_QUERY_MIN_LENGTH) {
-    return [MEMBERS_NONE_OPTION];
-  }
-
-  const regexp = new RegExp(_.escapeRegExp(query));
-
-  return options.filter((opt) => regexp.test((opt.text || '').toString()));
-}
-
-type MemberWithId = { id: number; profile: Membership };
-
-const SetContentWorkingGroupsLeadForm: React.FunctionComponent<FormInnerProps> = (props) => {
-  const { handleChange, errors, touched, values } = props;
-  const errorLabelsProps = getFormErrorLabelsProps<FormValues>(errors, touched);
-  // State
-  const [membersOptions, setMembersOptions] = useState([] as DropdownItemProps[]);
-  const [filteredOptions, setFilteredOptions] = useState([] as DropdownItemProps[]);
-  const [membersSearchQuery, setMembersSearchQuery] = useState('');
-  // Transport
-  const transport = useTransport();
-  const [members, /* error */, loading] = usePromise<MemberWithId[]>(
-    () => transport.council.membersExceptCouncil(),
-    []
-  );
-  const [currentLead, clError, clLoading] = usePromise<MemberWithId | null>(
-    () => transport.contentWorkingGroup.currentLead(),
-    null
-  );
-
-  // Generate members options array on load
-  useEffect(() => {
-    if (members.length) {
-      setMembersOptions(membersToOptions(members));
-    }
-  }, [members]);
-  // Filter options on search query change (we "pulled-out" this logic here to avoid lags)
-  useEffect(() => {
-    setFilteredOptions(filterMembers(membersOptions, membersSearchQuery));
-  }, [membersSearchQuery, membersOptions]);
-
-  return (
-    <PromiseComponent error={clError} loading={clLoading} message='Fetching current lead...'>
-      <GenericProposalForm
-        {...props}
-        txMethod='createSetLeadProposal'
-        proposalType='SetLead'
-        submitParams={[
-          values.workingGroupLead !== MEMBERS_NONE_OPTION.value ? values.workingGroupLead.split(':') : undefined
-        ]}
-      >
-        {loading ? (
-          <>
-            <Loader active inline style={{ marginRight: '5px' }} /> Fetching members...
-          </>
-        ) : (<>
-          <FormField
-            error={errorLabelsProps.workingGroupLead}
-            label='New Content Working Group Lead'
-            help={
-              'The member you propose to set as a new Content Working Group Lead. ' +
-              'Start typing handle or use "id:[ID]" query. ' +
-              'Current council members are not allowed to be selected and are excluded from the list.'
-            }
-          >
-            {
-              (!values.workingGroupLead || membersSearchQuery.length > 0) &&
-              (MEMBERS_QUERY_MIN_LENGTH - membersSearchQuery.length) > 0 && (
-                <Label>
-                  Type at least { MEMBERS_QUERY_MIN_LENGTH - membersSearchQuery.length } more characters
-                </Label>
-              )
-            }
-            <Dropdown
-              clearable
-              // Here we just ignore search query and return all options, since we pulled-out this logic
-              // to our component to avoid lags
-              search={ (options: DropdownItemProps[], query: string) => options }
-              // On search change we update it in our state
-              onSearchChange={ (e: React.SyntheticEvent, data: DropdownOnSearchChangeData) => {
-                setMembersSearchQuery(data.searchQuery);
-              } }
-              name='workingGroupLead'
-              placeholder={ 'Start typing member handle or "id:[ID]" query...' }
-              fluid
-              selection
-              options={filteredOptions}
-              onChange={
-                (e: React.ChangeEvent<any>, data: DropdownProps) => {
-                  // Fix TypeScript issue
-                  const originalHandler = handleChange as (e: React.ChangeEvent<any>, data: DropdownProps) => void;
-
-                  originalHandler(e, data);
-
-                  if (!data.value) {
-                    setMembersSearchQuery('');
-                  }
-                }
-              }
-              value={values.workingGroupLead}
-            />
-            {errorLabelsProps.workingGroupLead && <Label {...errorLabelsProps.workingGroupLead} prompt />}
-          </FormField>
-          <Message info active={1}>
-            <Message.Content>
-              <Icon name='info circle'/>
-              Current Content Working Group lead: <b>{ (currentLead && currentLead.profile.handle) || 'NONE' }</b>
-            </Message.Content>
-          </Message>
-        </>)}
-      </GenericProposalForm>
-    </PromiseComponent>
-  );
-};
-
-const FormContainer = withFormContainer<FormContainerProps, FormValues>({
-  mapPropsToValues: (props: FormContainerProps) => ({
-    ...defaultValues,
-    ...(props.initialData || {})
-  }),
-  validationSchema: Yup.object().shape({
-    ...Validation.All(),
-    ...Validation.SetLead()
-  }),
-  handleSubmit: () => null,
-  displayName: 'SetContentWorkingGroupLeadForm'
-})(SetContentWorkingGroupsLeadForm);
-
-export default withProposalFormData<FormContainerProps, ExportComponentProps>(FormContainer);

+ 0 - 23
pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx

@@ -1,23 +0,0 @@
-import React from 'react';
-import MintCapacityForm from './MintCapacityForm';
-import { RouteComponentProps } from 'react-router';
-import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks';
-import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent';
-
-const ContentWorkingGroupMintCapForm = (props: RouteComponentProps) => {
-  const transport = useTransport();
-  const [mintCapacity, error, loading] = usePromise<number>(() => transport.contentWorkingGroup.currentMintCap(), 0);
-
-  return (
-    <PromiseComponent error={error} loading={loading} message='Fetching current mint capacity...'>
-      <MintCapacityForm
-        mintCapacityGroup='Content Working Group'
-        txMethod='createSetContentWorkingGroupMintCapacityProposal'
-        proposalType='SetContentWorkingGroupMintCapacity'
-        initialData={{ capacity: mintCapacity.toString() }}
-        {...props} />
-    </PromiseComponent>
-  );
-};
-
-export default ContentWorkingGroupMintCapForm;

+ 0 - 3
pioneer/packages/joy-proposals/src/forms/index.ts

@@ -1,10 +1,7 @@
 export { default as SignalForm } from './SignalForm';
 export { default as SpendingProposalForm } from './SpendingProposalForm';
-export { default as MintCapacityForm } from './MintCapacityForm';
 export { default as SetCouncilParamsForm } from './SetCouncilParamsForm';
-export { default as SetContentWorkingGroupLeadForm } from './SetContentWorkingGroupLeadForm';
 export { default as RuntimeUpgradeForm } from './RuntimeUpgradeForm';
-export { default as SetContentWorkingGroupMintCapForm } from './SetContentWorkingGroupMintCapForm';
 export { default as SetCouncilMintCapForm } from './SetCouncilMintCapForm';
 export { default as SetMaxValidatorCountForm } from './SetMaxValidatorCountForm';
 export { default as AddWorkingGroupOpeningForm } from './AddWorkingGroupOpeningForm';

+ 0 - 8
pioneer/packages/joy-proposals/src/index.tsx

@@ -13,8 +13,6 @@ import translate from './translate';
 import NotDone from './NotDone';
 import { SignalForm,
   SpendingProposalForm,
-  SetContentWorkingGroupLeadForm,
-  SetContentWorkingGroupMintCapForm,
   SetCouncilParamsForm,
   SetMaxValidatorCountForm,
   RuntimeUpgradeForm,
@@ -107,12 +105,6 @@ function App (props: Props): React.ReactElement<Props> {
         <Route exact path={`${basePath}/new/runtime-upgrade`} component={RuntimeUpgradeForm} />
         <Route exact path={`${basePath}/new/set-election-parameters`} component={SetCouncilParamsForm} />
         <Route exact path={`${basePath}/new/spending`} component={SpendingProposalForm} />
-        <Route exact path={`${basePath}/new/set-lead`} component={SetContentWorkingGroupLeadForm} />
-        <Route
-          exact
-          path={`${basePath}/new/set-content-working-group-mint-capacity`}
-          component={SetContentWorkingGroupMintCapForm}
-        />
         <Route exact path={`${basePath}/new/set-validator-count`} component={SetMaxValidatorCountForm} />
         <Route exact path={`${basePath}/new/add-working-group-leader-opening`} component={AddWorkingGroupOpeningForm} />
         <Route exact path={`${basePath}/new/set-working-group-mint-capacity`} component={SetWorkingGroupMintCapacityForm} />

+ 0 - 6
pioneer/packages/joy-proposals/src/stories/ProposalForms.stories.tsx

@@ -1,9 +1,7 @@
 import { SignalForm,
   SpendingProposalForm,
   SetCouncilParamsForm,
-  SetContentWorkingGroupLeadForm,
   RuntimeUpgradeForm,
-  SetContentWorkingGroupMintCapForm,
   SetCouncilMintCapForm,
   SetMaxValidatorCountForm } from '../forms';
 import withMock from './withMock';
@@ -18,12 +16,8 @@ export const SpendingProposal = () => withMock(SpendingProposalForm);
 
 export const SetCouncilParams = () => withMock(SetCouncilParamsForm);
 
-export const SetContentWorkingGroupLead = () => withMock(SetContentWorkingGroupLeadForm);
-
 export const RuntimeUpgrade = () => withMock(RuntimeUpgradeForm);
 
-export const ContentWorkingGroupMintCap = () => withMock(SetContentWorkingGroupMintCapForm);
-
 export const CouncilMintCap = () => withMock(SetCouncilMintCapForm);
 
 export const SetMaxValidatorCount = () => withMock(SetMaxValidatorCountForm);

+ 1 - 20
pioneer/packages/joy-proposals/src/validationSchema.ts

@@ -7,8 +7,6 @@ import { FormValues as SignalFormValues } from './forms/SignalForm';
 import { FormValues as RuntimeUpgradeFormValues } from './forms/RuntimeUpgradeForm';
 import { FormValues as SetCouncilParamsFormValues } from './forms/SetCouncilParamsForm';
 import { FormValues as SpendingProposalFormValues } from './forms/SpendingProposalForm';
-import { FormValues as SetContentWorkingGroupLeadFormValues } from './forms/SetContentWorkingGroupLeadForm';
-import { FormValues as SetContentWorkingGroupMintCapacityFormValues } from './forms/MintCapacityForm';
 import { FormValues as SetMaxValidatorCountFormValues } from './forms/SetMaxValidatorCountForm';
 import { FormValues as AddWorkingGroupLeaderOpeningFormValues } from './forms/AddWorkingGroupOpeningForm';
 import { FormValues as SetWorkingGroupMintCapacityFormValues } from './forms/SetWorkingGroupMintCapacityForm';
@@ -59,10 +57,6 @@ const TOKENS_MAX = 5000000;
 const MAX_VALIDATOR_COUNT_MIN = 4;
 const MAX_VALIDATOR_COUNT_MAX = 100;
 
-// Content Working Group Mint Capacity
-const MINT_CAPACITY_MIN = 0;
-const MINT_CAPACITY_MAX = 1000000;
-
 // Add Working Group Leader Opening Parameters
 // TODO: Discuss the actual values
 const MIN_EXACT_BLOCK_MINUS_CURRENT = 14400 * 5; // ~5 days
@@ -122,7 +116,7 @@ import Validation from 'path/to/validationSchema'
 */
 
 type ProposalTypeKeys = typeof ProposalTypes[number];
-type OutdatedProposals = 'EvictStorageProvider' | 'SetStorageRoleParameters';
+type OutdatedProposals = 'EvictStorageProvider' | 'SetStorageRoleParameters' | 'SetLead' | 'SetContentWorkingGroupMintCapacity';
 type ValidationTypeKeys = Exclude<ProposalTypeKeys, OutdatedProposals> | 'All';
 
 /* eslint-disable @typescript-eslint/indent */
@@ -134,8 +128,6 @@ type FormValuesByType<T extends ValidationTypeKeys> =
   T extends 'RuntimeUpgrade' ? Omit<RuntimeUpgradeFormValues, keyof GenericFormValues> :
   T extends 'SetElectionParameters' ? Omit<SetCouncilParamsFormValues, keyof GenericFormValues> :
   T extends 'Spending' ? Omit<SpendingProposalFormValues, keyof GenericFormValues> :
-  T extends 'SetLead' ? Omit<SetContentWorkingGroupLeadFormValues, keyof GenericFormValues> :
-  T extends 'SetContentWorkingGroupMintCapacity' ? Omit<SetContentWorkingGroupMintCapacityFormValues, keyof GenericFormValues> :
   T extends 'SetValidatorCount' ? Omit<SetMaxValidatorCountFormValues, keyof GenericFormValues> :
   T extends 'AddWorkingGroupLeaderOpening' ? Omit<AddWorkingGroupLeaderOpeningFormValues, keyof GenericFormValues> :
   T extends 'SetWorkingGroupMintCapacity' ? Omit<SetWorkingGroupMintCapacityFormValues, keyof GenericFormValues> :
@@ -295,17 +287,6 @@ const Validation: ValidationType = {
     destinationAccount: Yup.string()
       .required('Select a destination account!')
   }),
-  SetLead: () => ({
-    workingGroupLead: Yup.string().required('Select a proposed lead!')
-  }),
-  SetContentWorkingGroupMintCapacity: () => ({
-    capacity: Yup.number()
-      .positive('Mint capacity should be positive.')
-      .integer('This field must be an integer.')
-      .min(MINT_CAPACITY_MIN, errorMessage('Mint capacity', MINT_CAPACITY_MIN, MINT_CAPACITY_MAX, CURRENCY_UNIT))
-      .max(MINT_CAPACITY_MAX, errorMessage('Mint capacity', MINT_CAPACITY_MIN, MINT_CAPACITY_MAX, CURRENCY_UNIT))
-      .required('You need to specify a mint capacity.')
-  }),
   SetValidatorCount: () => ({
     maxValidatorCount: Yup.number()
       .required('Enter the max validator count')

+ 0 - 2
pioneer/packages/joy-roles/src/elements.tsx

@@ -8,7 +8,6 @@ import { formatBalance } from '@polkadot/util';
 import Identicon from '@polkadot/react-identicon';
 import { IMembership, MemberId } from '@joystream/types/members';
 import { GenericAccountId } from '@polkadot/types';
-import { LeadRoleState } from '@joystream/types/content-working-group';
 import { WorkerId } from '@joystream/types/working-group';
 import { WorkingGroups } from './working_groups';
 import { RewardRelationship } from '@joystream/types/recurring-rewards';
@@ -58,7 +57,6 @@ export type GroupLead = {
   roleAccount: GenericAccountId;
   profile: IMembership;
   title: string;
-  stage?: LeadRoleState;
   stake?: Balance;
   rewardRelationship?: RewardRelationship;
 }

+ 0 - 5
pioneer/packages/joy-roles/src/index.tsx

@@ -17,7 +17,6 @@ import { OpportunityController, OpportunityView } from './tabs/Opportunity.contr
 import { OpportunitiesController, OpportunitiesView } from './tabs/Opportunities.controller';
 import { ApplyController, ApplyView } from './flows/apply.controller';
 import { MyRolesController, MyRolesView } from './tabs/MyRoles.controller';
-import { AdminController, AdminView } from './tabs/Admin.controller';
 
 import './index.sass';
 
@@ -51,7 +50,6 @@ export const App: React.FC<Props> = (props: Props) => {
   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 () => {
@@ -98,9 +96,6 @@ export const App: React.FC<Props> = (props: Props) => {
         <Route
           path={`${basePath}/my-roles`}
           render={(props: DefaultRouteProps) => <MyRolesView controller={myRolesCtrl} params={props.match.params}/>} />
-        <Route
-          path={`${basePath}/admin`}
-          render={(props: DefaultRouteProps) => <AdminView controller={adminCtrl} params={props.match.params}/>} />
         <Route
           render={(props: DefaultRouteProps) => <WorkingGroupsView controller={wgCtrl} params={props.match.params}/> } />
       </Switch>

+ 0 - 907
pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx

@@ -1,907 +0,0 @@
-import React, { useState, useRef } from 'react';
-import { Link } from 'react-router-dom';
-import { formatBalance } from '@polkadot/util';
-
-import { ApiPromise } from '@polkadot/api';
-import { Option, Text, Vec } from '@polkadot/types';
-import { Balance } from '@polkadot/types/interfaces';
-
-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 { Accordion,
-  Button,
-  Card,
-  Checkbox,
-  Container,
-  Dropdown,
-  Form,
-  Grid,
-  Icon,
-  Input,
-  Label,
-  Message,
-  Modal,
-  Table,
-  TextArea,
-  InputOnChangeData } from 'semantic-ui-react';
-
-import { ITransport } from '../transport';
-
-import { Application,
-  ApplicationStage,
-  ActivateOpeningAt,
-  Opening,
-  OpeningStage,
-  StakingPolicy,
-  StakingAmountLimitModeKeys,
-  StakingAmountLimitMode } from '@joystream/types/hiring';
-
-import { Membership,
-  MemberId } from '@joystream/types/members';
-
-import { Stake, StakeId } from '@joystream/types/stake';
-
-import { CuratorApplication, CuratorApplicationId,
-  CuratorOpening,
-  IOpeningPolicyCommitment, CuratorOpeningId } from '@joystream/types/content-working-group';
-
-import { classifyOpeningStage,
-  OpeningStageClassification,
-  OpeningState } from '../classifiers';
-
-import { openingDescription } from '../openingStateMarkup';
-
-import { Add, Zero } from '../balances';
-import { createType } from '@joystream/types';
-
-type ids = {
-  curatorId: number;
-  openingId: number;
-}
-
-type application = ids & {
-  account: string;
-  memberId: number;
-  profile: Membership;
-  stage: ApplicationStage;
-  applicationStake: Balance;
-  roleStake: Balance;
-  application: Application;
-}
-
-type opening = ids & {
-  title: string;
-  state: OpeningStage;
-  applications: Array<application>;
-  classification: OpeningStageClassification;
-}
-
-// Only max_review_period_length is not optional, so other fields can be "undefined"
-type policyDescriptor = Pick<IOpeningPolicyCommitment, 'max_review_period_length'> & Partial<IOpeningPolicyCommitment>;
-
-type stakingFieldName = 'application_staking_policy' | 'role_staking_policy';
-
-type openingDescriptor = {
-  title: string;
-  start: ActivateOpeningAt;
-  policy: policyDescriptor;
-  text: Text;
-}
-
-type State = {
-  openings: Map<number, opening>;
-  currentDescriptor: openingDescriptor;
-  modalOpen: boolean;
-}
-
-function newHRT (title: string): Text {
-  return createType('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'
-      }
-    },
-    process: {
-      details: [
-        'Some custom detail'
-      ]
-    }
-  })
-  );
-}
-
-const createRationingPolicyOpt = (maxApplicants: number) =>
-  createType('Option<ApplicationRationingPolicy>', {
-    max_active_applicants: maxApplicants
-  });
-const createStakingPolicyOpt = (amount: number, amount_mode: StakingAmountLimitMode): Option<StakingPolicy> =>
-  createType('Option<StakingPolicy>', {
-    amount,
-    amount_mode
-  });
-
-const STAKING_MODE_EXACT = createType('StakingAmountLimitMode', StakingAmountLimitModeKeys.Exact);
-const STAKING_MODE_AT_LEAST = createType('StakingAmountLimitMode', StakingAmountLimitModeKeys.AtLeast);
-
-const stockOpenings: openingDescriptor[] = [
-  {
-    title: 'Test config A: no application stake, no role stake, no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999)
-    },
-    text: newHRT('Test configuration A')
-  },
-  {
-    title: 'Test config B: no application stake, no role stake, 10 applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999)
-    },
-    text: newHRT('Test configuration B')
-  },
-  {
-    title: 'Test config C: fixed application stake (100), no role stake, no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('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: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT)
-    },
-    text: newHRT('Test configuration D')
-  },
-  {
-    title: 'Test config E: no application stake, fixed role stake (100), no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('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: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      role_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT)
-    },
-    text: newHRT('Test configuration F')
-  },
-  {
-    title: 'Test config G: minimum application stake (100), no role stake, no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('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: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST)
-    },
-    text: newHRT('Test configuration H')
-  },
-  {
-    title: 'Test config I: no application stake, minimum role stake (100), no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('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: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      role_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST)
-    },
-    text: newHRT('Test configuration J')
-  },
-  {
-    title: 'Test config K: fixed application stake (100), fixed role stake (200), no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
-    },
-    text: newHRT('Test configuration K')
-  },
-  {
-    title: 'Test config L: fixed application stake (100), fixed role stake (200), 10 applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
-    },
-    text: newHRT('Test configuration L')
-  },
-  {
-    title: 'Test config M: Minimum application stake (100), minimum role stake (200), no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
-    },
-    text: newHRT('Test configuration M')
-  },
-  {
-    title: 'Test config N: Minimum application stake (100), minimum role stake (200), 10 applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('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)
-    },
-    text: newHRT('Test configuration N')
-  },
-  {
-    title: 'Test config O: Fixed application stake (100), minimum role stake (200), no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
-    },
-    text: newHRT('Test configuration O')
-  },
-  {
-    title: 'Test config P: Fixed application stake (100), minimum role stake (200), 10 applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_EXACT),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_AT_LEAST)
-    },
-    text: newHRT('Test configuration P')
-  },
-  {
-    title: 'Test config Q: Minimum application stake (100), fixed role stake (200), no applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
-    },
-    text: newHRT('Test configuration Q')
-  },
-  {
-    title: 'Test config R: Minimum application stake (100), fixed role stake (200), 10 applicant limit',
-    start: createType('ActivateOpeningAt', 'CurrentBlock'),
-    policy: {
-      max_review_period_length: createType('u32', 99999),
-      application_rationing_policy: createRationingPolicyOpt(10),
-      application_staking_policy: createStakingPolicyOpt(100, STAKING_MODE_AT_LEAST),
-      role_staking_policy: createStakingPolicyOpt(200, STAKING_MODE_EXACT)
-    },
-    text: newHRT('Test configuration R')
-  }
-];
-
-const newEmptyState = (): State => {
-  return {
-    openings: new Map<number, opening>(),
-    currentDescriptor: stockOpenings[0],
-    modalOpen: false
-  };
-};
-
-export class AdminController extends Controller<State, ITransport> {
-  api: ApiPromise;
-  queueExtrinsic: QueueTxExtrinsicAdd;
-
-  constructor (transport: ITransport, api: ApiPromise, queueExtrinsic: QueueTxExtrinsicAdd, initialState: State = newEmptyState()) {
-    super(transport, initialState);
-    this.api = api;
-    this.queueExtrinsic = queueExtrinsic;
-    this.state.currentDescriptor = stockOpenings[0];
-    void this.refreshState();
-  }
-
-  onTxSuccess = () => { this.closeModal(); void this.refreshState(); }
-
-  newOpening (accountId: string, desc: openingDescriptor) {
-    const tx = this.api.tx.contentWorkingGroup.addCuratorOpening(
-      desc.start,
-      desc.policy,
-      desc.text
-    );
-
-    this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId });
-  }
-
-  startAcceptingApplications (accountId: string, id = 0) {
-    const tx = this.api.tx.contentWorkingGroup.acceptCuratorApplications(id);
-
-    this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId });
-  }
-
-  async applyAsACurator (creatorAddress: string, openingId: number) {
-    const membershipIds = (await this.api.query.members.memberIdsByControllerAccountId(creatorAddress)) as Vec<MemberId>;
-
-    if (membershipIds.length === 0) {
-      console.error('No membship ID associated with this address');
-
-      return;
-    }
-
-    const tx = this.api.tx.contentWorkingGroup.applyOnCuratorOpening(
-      membershipIds[0],
-      openingId,
-      creatorAddress,
-      400,
-      400,
-      'This is my application'
-    );
-
-    this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId: creatorAddress });
-  }
-
-  beginApplicantReview (accountId: string, openingId: number) {
-    const tx = this.api.tx.contentWorkingGroup.beginCuratorApplicantReview(openingId);
-
-    this.queueExtrinsic({ extrinsic: tx, txSuccessCb: this.onTxSuccess, accountId });
-  }
-
-  acceptCuratorApplications (accountId: string, openingId: number, applications: Array<number>) {
-    const tx = this.api.tx.contentWorkingGroup.fillCuratorOpening(
-      openingId,
-      applications,
-      null
-    );
-
-    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.isEmpty) {
-      throw new Error(`Expected member profile not found! (id: ${id.toString()}`);
-    }
-
-    return member;
-  }
-
-  protected async stakeValue (stakeId: StakeId): Promise<Balance> {
-    const stake = await this.api.query.stake.stakes(stakeId) as Stake;
-
-    return stake.value;
-  }
-
-  protected async roleStake (application: Application): Promise<Balance> {
-    if (application.active_role_staking_id.isNone) {
-      return Zero;
-    }
-
-    return this.stakeValue(application.active_role_staking_id.unwrap());
-  }
-
-  protected async applicationStake (application: Application): Promise<Balance> {
-    if (application.active_application_staking_id.isNone) {
-      return Zero;
-    }
-
-    return this.stakeValue(application.active_application_staking_id.unwrap());
-  }
-
-  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 = await this.api.query.contentWorkingGroup.curatorOpeningById(i) as CuratorOpening;
-
-      const openingId = curatorOpening.opening_id;
-
-      const baseOpening = await this.api.query.hiring.openingById(openingId) as Opening;
-
-      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.stage,
-        title: title,
-        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 = await this.api.query.contentWorkingGroup.curatorApplicationById(i) as CuratorApplication;
-
-      const appId = cApplication.application_id;
-      const baseApplications = await this.api.query.hiring.applicationById(appId) as Application;
-
-      const curatorOpening = this.state.openings.get(cApplication.curator_opening_id.toNumber()) as opening;
-
-      curatorOpening.applications.push({
-        openingId: appId.toNumber(),
-        curatorId: i,
-        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
-      });
-    }
-
-    this.dispatch();
-  }
-
-  showNewOpeningModal (desc: openingDescriptor) {
-    this.state.modalOpen = true;
-    this.state.currentDescriptor = desc;
-    this.dispatch();
-  }
-
-  closeModal () {
-    this.state.modalOpen = false;
-    this.dispatch();
-  }
-}
-
-type AdminContainerProps = {
-  state: State;
-  controller: AdminController;
-}
-
-const AdminContainer = ({ state, controller }: AdminContainerProps) => {
-  const address = useMyAccount().state.address;
-  const containerRef = useRef<HTMLDivElement>(null);
-
-  return (
-    <div ref={containerRef}>
-      <Container className='admin'>
-        <Card fluid color='orange'>
-          <Card.Content>
-            <Dropdown text='Create new opening...'>
-              <Dropdown.Menu>
-                {
-                  stockOpenings.map((value, key) => {
-                    return (
-                      <Dropdown.Item
-                        key={value.title}
-                        text={value.title}
-                        onClick={() => controller.showNewOpeningModal(value)}
-                      />
-                    );
-                  })
-                }
-              </Dropdown.Menu>
-            </Dropdown>
-            <Modal
-              open={state.modalOpen}
-              onClose={() => controller.closeModal()}
-              mountNode={containerRef.current} // Prevent conflicts with tx-modal
-            >
-              <Modal.Content image>
-                <Modal.Description>
-                  <NewOpening desc={state.currentDescriptor} fn={(desc) => address && controller.newOpening(address, desc)} />
-                </Modal.Description>
-              </Modal.Content>
-            </Modal>
-          </Card.Content>
-        </Card>
-        {
-          [...state.openings.keys()].map((key) => <OpeningView key={key} opening={state.openings.get(key) as opening} controller={controller} />)
-        }
-        <br />
-      </Container>
-    </div>
-  );
-};
-
-export const AdminView = View<AdminController, State>(
-  ({ state, controller }) => {
-    return (
-      <AdminContainer state={state} controller={controller} />
-    );
-  }
-);
-
-type NewOpeningProps = {
-  desc: openingDescriptor;
-  fn: (desc: openingDescriptor) => void;
-}
-
-const NewOpening = (props: NewOpeningProps) => {
-  const [start, setStart] = useState(props.desc.start);
-  const openingAtOptions = [
-    {
-      key: 'CurrentBlock',
-      text: 'Current Block',
-      value: 'CurrentBlock'
-    },
-    {
-      key: 'ExactBlock',
-      text: 'Exact Block',
-      value: 'ExactBlock'
-    }
-  ];
-
-  const [exactBlock, setExactBlock] = useState(0);
-  const [showExactBlock, setShowExactBlock] = useState(false);
-
-  const onChangeActivateAt = (e: any, { value }: any) => {
-    switch (value) {
-      case 'CurrentBlock':
-        setShowExactBlock(false);
-        setStart(createType('ActivateOpeningAt', 'CurrentBlock'));
-        break;
-
-      case 'ExactBlock':
-        setStart(createType('ActivateOpeningAt', { ExactBlock: exactBlock }));
-        setShowExactBlock(true);
-        break;
-    }
-  };
-
-  const onChangeExactBlock = (e: any, { value }: InputOnChangeData) => {
-    setExactBlock(typeof value === 'number' ? value : (parseInt(value) || 0));
-    setStart(createType('ActivateOpeningAt', { ExactBlock: value }));
-  };
-
-  const [policy, setPolicy] = useState(props.desc.policy);
-
-  const onChangePolicyField = <PolicyKey extends keyof policyDescriptor>(fieldName: PolicyKey, value: policyDescriptor[PolicyKey]) => {
-    const newState = { ...policy };
-
-    newState[fieldName] = value;
-    setPolicy(newState);
-  };
-
-  const [requireAppStakingPolicy, setRequireAppStakingPolicy] = useState(
-    !!(props.desc.policy &&
-      props.desc.policy.application_staking_policy &&
-      props.desc.policy.application_staking_policy.isSome)
-  );
-
-  const [requireRoleStakingPolicy, setRequireRoleStakingPolicy] = useState(
-    !!(props.desc.policy &&
-      props.desc.policy.role_staking_policy &&
-      props.desc.policy.role_staking_policy.isSome)
-  );
-
-  const stakeLimitOptions = [
-    {
-      key: StakingAmountLimitModeKeys.AtLeast,
-      text: StakingAmountLimitModeKeys.AtLeast,
-      value: StakingAmountLimitModeKeys.AtLeast
-    },
-    {
-      key: StakingAmountLimitModeKeys.Exact,
-      text: StakingAmountLimitModeKeys.Exact,
-      value: StakingAmountLimitModeKeys.Exact
-    }
-  ];
-
-  const changeStakingMode = (
-    fieldName: stakingFieldName,
-    mode: StakingAmountLimitModeKeys | '',
-    stakeValue: number
-  ) => {
-    if (mode === '') {
-      const policyField = policy[fieldName];
-
-      mode = policyField && policyField.isSome
-        ? (policyField.unwrap().amount_mode.type as StakingAmountLimitModeKeys)
-        : StakingAmountLimitModeKeys.Exact; // Default
-    }
-
-    const value = createStakingPolicyOpt(
-      stakeValue,
-      mode === StakingAmountLimitModeKeys.Exact ? STAKING_MODE_EXACT : STAKING_MODE_AT_LEAST
-    );
-
-    onChangePolicyField(fieldName, value);
-  };
-
-  const onStakeModeCheckboxChange = (fn: (v: boolean) => void, fieldName: stakingFieldName, checked: boolean, stakeValue: number) => {
-    fn(checked);
-
-    if (checked) {
-      changeStakingMode(fieldName, StakingAmountLimitModeKeys.AtLeast, stakeValue);
-    } else {
-      onChangePolicyField(fieldName, undefined);
-    }
-  };
-
-  const [text, setText] = useState(JSON.stringify(JSON.parse(props.desc.text.toString()), null, 2));
-
-  const submit = () => {
-    props.fn({
-      start: start,
-      policy: policy,
-      text: createType('Text', text),
-      title: ''
-    });
-  };
-
-  return (
-    <Form>
-      <Form.Field>
-        <label>Activate opening at</label>
-        <Form.Dropdown
-          selection
-          onChange={onChangeActivateAt}
-          options={openingAtOptions}
-          value={start.type}
-        />
-        {showExactBlock === true &&
-          <Input
-            type='number'
-            value={exactBlock}
-            onChange={onChangeExactBlock}
-          />
-        }
-      </Form.Field>
-
-      <Form.Field>
-        <label>Max review period length (in blocks)</label>
-        <Input
-          type='number'
-          value={policy.max_review_period_length.toNumber()}
-          onChange={(e: any, { value }: any) => onChangePolicyField('max_review_period_length', createType('u32', value))}
-        />
-      </Form.Field>
-
-      <Form.Field>
-        <label>Application staking policy</label>
-        <Checkbox label='Require an application stake' checked={requireAppStakingPolicy} onChange={(e, { checked }: any) => onStakeModeCheckboxChange(setRequireAppStakingPolicy, 'application_staking_policy', checked, 0)} />
-        {requireAppStakingPolicy && (
-          <Message>
-            <label>Stake mode</label>
-            <Form.Dropdown
-              selection
-              onChange={(e, { value }: any) => changeStakingMode('application_staking_policy', value, 0)}
-              options={stakeLimitOptions}
-              value={policy.application_staking_policy?.unwrap().amount_mode.type}
-            />
-
-            <label>Stake value</label>
-            <Input
-              type='number'
-              value={policy.application_staking_policy?.unwrap().amount.toNumber()}
-              onChange={(e: any, { value }: any) => changeStakingMode('application_staking_policy', '', value)}
-            />
-          </Message>
-        )}
-      </Form.Field>
-
-      <Form.Field>
-        <label>Role staking policy</label>
-        <Checkbox label='Require a role stake' checked={requireRoleStakingPolicy} onChange={(e, { checked }: any) => onStakeModeCheckboxChange(setRequireRoleStakingPolicy, 'role_staking_policy', checked, 0)} />
-        {requireRoleStakingPolicy && (
-          <Message>
-            <label>Stake mode</label>
-            <Form.Dropdown
-              selection
-              onChange={(e, { value }: any) => changeStakingMode('role_staking_policy', value, 0)}
-              options={stakeLimitOptions}
-              value={policy.role_staking_policy?.unwrap().amount_mode.type}
-            />
-
-            <label>Stake value</label>
-            <Input
-              type='number'
-              value={policy.role_staking_policy?.unwrap().amount.toNumber()}
-              onChange={(e: any, { value }: any) => changeStakingMode('role_staking_policy', '', value)}
-            />
-          </Message>
-        )}
-      </Form.Field>
-      <Form.Field>
-        <label>Role staking policy</label>
-        <TextArea value={text} rows={10} onChange={(e: any, { value }: any) => setText(value)} />
-      </Form.Field>
-
-      <Form.Field align='right'>
-        <Button positive onClick={() => submit()}>Create opening</Button>
-      </Form.Field>
-    </Form>
-  );
-};
-
-type OpeningViewProps = {
-  controller: AdminController;
-  opening: opening;
-}
-
-const OpeningView = (props: OpeningViewProps) => {
-  const address = useMyAccount().state.address as string;
-  const [applicationsOpen, setApplicationsOpen] = useState(true);
-  const [selected, setSelected] = useState<number[]>([]);
-
-  const toggleApplication = (id: number) => {
-    if (selected.includes(id)) {
-      setSelected(selected.filter((v) => v !== id));
-    } else {
-      setSelected([...selected, id]);
-    }
-  };
-
-  let CTAs = null;
-
-  switch (props.opening.classification.state) {
-    case OpeningState.InReview:
-      CTAs = (
-        <Container align='right'>
-          <Button onClick={() => { props.controller.acceptCuratorApplications(address, props.opening.curatorId, selected); }}>Accept curator applications</Button>
-        </Container>
-      );
-  }
-
-  return (
-    <Card fluid>
-      <Card.Content>
-        <Card.Header>
-          <Label attached='top right'>Opening</Label>
-          <Link to={`/working-groups/opportunities/curators/${props.opening.curatorId}`}>
-            {props.opening.title}
-          </Link>
-
-        </Card.Header>
-        <Card.Meta>
-          Working group module ID #{props.opening.curatorId}, hiring module ID #{props.opening.openingId}
-        </Card.Meta>
-        <Label ribbon>
-          {openingDescription(props.opening.classification.state)}
-        </Label>
-      </Card.Content>
-      <Card.Content extra>
-        <Accordion>
-          <Accordion.Title
-            active={applicationsOpen}
-            index={0}
-            onClick={() => setApplicationsOpen(!applicationsOpen)}
-          >
-            <Icon name='dropdown' />
-            Applications
-          </Accordion.Title>
-          <Accordion.Content active={applicationsOpen}>
-            <Table striped>
-              <Table.Header>
-                <Table.Row>
-                  <Table.HeaderCell>WG ID</Table.HeaderCell>
-                  <Table.HeaderCell>Hiring ID</Table.HeaderCell>
-                  <Table.HeaderCell>Member</Table.HeaderCell>
-                  <Table.HeaderCell>Stage</Table.HeaderCell>
-                  <Table.HeaderCell>App stake</Table.HeaderCell>
-                  <Table.HeaderCell>Role stake</Table.HeaderCell>
-                  <Table.HeaderCell>Total stake</Table.HeaderCell>
-                  <Table.HeaderCell>Details</Table.HeaderCell>
-                  <Table.HeaderCell></Table.HeaderCell>
-                </Table.Row>
-              </Table.Header>
-              <Table.Body>
-                {props.opening.applications.map((app, id) => (
-                  <Table.Row key={app.openingId}>
-                    <Table.Cell>{app.curatorId}</Table.Cell>
-                    <Table.Cell>{app.openingId}</Table.Cell>
-                    <Table.Cell>
-                      <Link to={`/members/${app.profile.handle.toString()}`}>
-                        {app.profile.handle}
-                      </Link>
-                    </Table.Cell>
-                    <Table.Cell>{app.stage.type}</Table.Cell>
-                    <Table.Cell>{formatBalance(app.applicationStake)}</Table.Cell>
-                    <Table.Cell>{formatBalance(app.roleStake)}</Table.Cell>
-                    <Table.Cell>{formatBalance(Add(app.applicationStake, app.roleStake))}</Table.Cell>
-                    <Table.Cell>
-                      <Modal trigger={
-                        <Button>view</Button>
-                      }>
-                        <Modal.Header>Application details</Modal.Header>
-                        <Modal.Content>
-                          <h3>Raw JSON</h3>
-                          <Message info>
-                            {JSON.stringify(app.application.toJSON())}
-                          </Message>
-
-                          <h3>Application form</h3>
-                          <Message info>
-                            {app.application.human_readable_text.toString()}
-                          </Message>
-                        </Modal.Content>
-                      </Modal>
-                    </Table.Cell>
-                    <Table.Cell>
-                      <Checkbox onChange={() => toggleApplication(app.curatorId)} checked={selected.includes(app.curatorId)} />
-                    </Table.Cell>
-                  </Table.Row>
-                ))}
-              </Table.Body>
-            </Table>
-            {CTAs}
-          </Accordion.Content>
-        </Accordion>
-      </Card.Content>
-      <Card.Content extra>
-        <Grid>
-          <Grid.Row columns={2}>
-            <Grid.Column>
-              <Dropdown text='Set stage'>
-                <Dropdown.Menu>
-                  <Dropdown.Item
-                    text='Start accepting applications'
-                    onClick={() => { props.controller.startAcceptingApplications(address, props.opening.curatorId); }}
-                  />
-                  <Dropdown.Item
-                    text='Begin applicant review'
-                    onClick={() => { props.controller.beginApplicantReview(address, props.opening.curatorId); }}
-                  />
-                </Dropdown.Menu>
-              </Dropdown>
-            </Grid.Column>
-            <Grid.Column align='right'>
-            </Grid.Column>
-          </Grid.Row>
-        </Grid>
-      </Card.Content>
-    </Card>
-  );
-};

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

@@ -26,7 +26,6 @@ import { openingIcon,
   openingDescription } from '../openingStateMarkup';
 import { CancelledReason, OpeningStageClassification, OpeningState } from '../classifiers';
 import { OpeningMetadata } from '../OpeningMetadata';
-import { CuratorId } from '@joystream/types/content-working-group';
 import { WorkerId } from '@joystream/types/working-group';
 import _ from 'lodash';
 import styled from 'styled-components';
@@ -101,7 +100,7 @@ function RoleName (props: NameAndURL) {
 }
 
 export interface ActiveRole extends NameAndURL {
-  workerId: CuratorId | WorkerId;
+  workerId: WorkerId;
   reward: Balance;
   stake: Balance;
   group: WorkingGroups;

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

@@ -3,7 +3,6 @@ import { map, switchMap } from 'rxjs/operators';
 import ApiPromise from '@polkadot/api/promise';
 import { Balance } from '@polkadot/types/interfaces';
 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 keyringOption from '@polkadot/ui-keyring/options';
@@ -15,12 +14,6 @@ import BaseTransport from '@polkadot/joy-utils/transport/base';
 import { ITransport } from './transport';
 import { GroupMember } from './elements';
 
-import { Curator, CuratorId,
-  CuratorApplication, CuratorApplicationId,
-  CuratorRoleStakeProfile,
-  CuratorOpening, CuratorOpeningId,
-  Lead, LeadId } from '@joystream/types/content-working-group';
-
 import { Application as WGApplication,
   Opening as WGOpening,
   Worker, WorkerId,
@@ -56,90 +49,16 @@ type StakePair<T = Balance> = {
   role: T;
 }
 
-type WGApiMethodType =
-  'nextOpeningId'
-  | 'openingById'
-  | 'nextApplicationId'
-  | 'applicationById'
-  | 'nextWorkerId'
-  | 'workerById';
-type WGApiTxMethodType =
-  'applyOnOpening'
-  | 'withdrawApplication'
-  | 'leaveRole';
-type WGApiMethodsMapping = {
-  query: { [key in WGApiMethodType]: string };
-  tx: { [key in WGApiTxMethodType]: string };
-};
-
-type GroupApplication = CuratorApplication | WGApplication;
-type GroupApplicationId = CuratorApplicationId | ApplicationId;
-type GroupOpening = CuratorOpening | WGOpening;
-type GroupOpeningId = CuratorOpeningId | OpeningId;
-type GroupWorker = Worker | Curator;
-type GroupWorkerId = CuratorId | WorkerId;
-type GroupWorkerStakeProfile = RoleStakeProfile | CuratorRoleStakeProfile;
-type GroupLead = Lead | Worker;
 type GroupLeadWithMemberId = {
-  lead: GroupLead;
+  lead: Worker;
   memberId: MemberId;
-  workerId?: WorkerId; // Only when it's `working-groups` module lead
+  workerId: WorkerId;
 }
 
-type WGApiMapping = {
-  [key in WorkingGroups]: {
-    module: string;
-    methods: WGApiMethodsMapping;
-    openingType: Constructor<GroupOpening>;
-    applicationType: Constructor<GroupApplication>;
-    workerType: Constructor<GroupWorker>;
-  }
-};
-
-const workingGroupsApiMapping: WGApiMapping = {
-  [WorkingGroups.StorageProviders]: {
-    module: 'storageWorkingGroup',
-    methods: {
-      query: {
-        nextOpeningId: 'nextOpeningId',
-        openingById: 'openingById',
-        nextApplicationId: 'nextApplicationId',
-        applicationById: 'applicationById',
-        nextWorkerId: 'nextWorkerId',
-        workerById: 'workerById'
-      },
-      tx: {
-        applyOnOpening: 'applyOnOpening',
-        withdrawApplication: 'withdrawApplication',
-        leaveRole: 'leaveRole'
-      }
-    },
-    openingType: WGOpening,
-    applicationType: WGApplication,
-    workerType: Worker
-  },
-  [WorkingGroups.ContentCurators]: {
-    module: 'contentWorkingGroup',
-    methods: {
-      query: {
-        nextOpeningId: 'nextCuratorOpeningId',
-        openingById: 'curatorOpeningById',
-        nextApplicationId: 'nextCuratorApplicationId',
-        applicationById: 'curatorApplicationById',
-        nextWorkerId: 'nextCuratorId',
-        workerById: 'curatorById'
-      },
-      tx: {
-        applyOnOpening: 'applyOnCuratorOpening',
-        withdrawApplication: 'withdrawCuratorApplication',
-        leaveRole: 'leaveCuratorRole'
-      }
-    },
-    openingType: CuratorOpening,
-    applicationType: CuratorApplication,
-    workerType: Curator
-  }
-};
+const apiModuleByGroup = {
+  [WorkingGroups.StorageProviders]: 'storageWorkingGroup',
+  [WorkingGroups.ContentCurators]: 'contentDirectoryWorkingGroup'
+} as const;
 
 export class Transport extends BaseTransport implements ITransport {
   protected queueExtrinsic: QueueTxExtrinsicAdd
@@ -149,25 +68,22 @@ export class Transport extends BaseTransport implements ITransport {
     this.queueExtrinsic = queueExtrinsic;
   }
 
-  apiMethodByGroup (group: WorkingGroups, method: WGApiMethodType) {
-    const apiModule = workingGroupsApiMapping[group].module;
-    const apiMethod = workingGroupsApiMapping[group].methods.query[method];
+  queryByGroup (group: WorkingGroups) {
+    const apiModule = apiModuleByGroup[group];
 
-    return this.api.query[apiModule][apiMethod];
+    return this.api.query[apiModule];
   }
 
-  cachedApiMethodByGroup (group: WorkingGroups, method: WGApiMethodType) {
-    const apiModule = workingGroupsApiMapping[group].module;
-    const apiMethod = workingGroupsApiMapping[group].methods.query[method];
+  queryCachedByGroup (group: WorkingGroups) {
+    const apiModule = apiModuleByGroup[group];
 
-    return this.cacheApi.query[apiModule][apiMethod];
+    return this.cacheApi.query[apiModule];
   }
 
-  apiExtrinsicByGroup (group: WorkingGroups, method: WGApiTxMethodType) {
-    const apiModule = workingGroupsApiMapping[group].module;
-    const apiMethod = workingGroupsApiMapping[group].methods.tx[method];
+  txByGroup (group: WorkingGroups) {
+    const apiModule = apiModuleByGroup[group];
 
-    return this.api.tx[apiModule][apiMethod];
+    return this.api.tx[apiModule];
   }
 
   unsubscribe () {
@@ -180,7 +96,7 @@ export class Transport extends BaseTransport implements ITransport {
     return stake.value;
   }
 
-  protected async workerStake (stakeProfile: GroupWorkerStakeProfile): Promise<Balance> {
+  protected async workerStake (stakeProfile: RoleStakeProfile): Promise<Balance> {
     return this.stakeValue(stakeProfile.stake_id);
   }
 
@@ -196,15 +112,7 @@ export class Transport extends BaseTransport implements ITransport {
     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 =
-      await this.cacheApi.query.contentWorkingGroup.curatorApplicationById(curatorApplicationId) as CuratorApplication;
-
-    return curatorApplication.member_id;
-  }
-
-  protected async workerRewardRelationship (worker: GroupWorker): Promise<RewardRelationship | undefined> {
+  protected async workerRewardRelationship (worker: Worker): Promise<RewardRelationship | undefined> {
     const rewardRelationship = worker.reward_relationship.isSome
       ? await this.rewardRelationshipById(worker.reward_relationship.unwrap())
       : undefined;
@@ -214,13 +122,11 @@ export class Transport extends BaseTransport implements ITransport {
 
   protected async groupMember (
     group: WorkingGroups,
-    id: GroupWorkerId,
-    worker: GroupWorker
+    id: WorkerId,
+    worker: Worker
   ): Promise<GroupMember> {
     const roleAccount = worker.role_account_id;
-    const memberId = group === WorkingGroups.ContentCurators
-      ? await this.curatorMemberId(worker as Curator)
-      : (worker as Worker).member_id;
+    const memberId = worker.member_id;
 
     const profile = await this.cacheApi.query.members.membershipById(memberId) as Membership;
 
@@ -249,8 +155,8 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   protected async areGroupRolesOpen (group: WorkingGroups, lead = false): Promise<boolean> {
-    const groupOpenings = await this.entriesByIds<GroupOpeningId, GroupOpening>(
-      this.apiMethodByGroup(group, 'openingById')
+    const groupOpenings = await this.entriesByIds<OpeningId, WGOpening>(
+      this.queryByGroup(group).openingById
     );
 
     for (const [/* id */, groupOpening] of groupOpenings) {
@@ -271,34 +177,15 @@ export class Transport extends BaseTransport implements ITransport {
     return false;
   }
 
-  protected async currentCuratorLead (): Promise<GroupLeadWithMemberId | null> {
-    const optLeadId = (await this.cacheApi.query.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
-
-    if (!optLeadId.isSome) {
-      return null;
-    }
-
-    const leadId = optLeadId.unwrap();
-    const lead = await this.cacheApi.query.contentWorkingGroup.leadById(leadId) as Lead;
-
-    if (lead.isEmpty || !lead.stage.isOfType('Active')) {
-      return null;
-    }
-
-    const memberId = lead.member_id;
-
-    return { lead, memberId };
-  }
-
-  protected async currentStorageLead (): Promise <GroupLeadWithMemberId | null> {
-    const optLeadId = (await this.cacheApi.query.storageWorkingGroup.currentLead()) as Option<WorkerId>;
+  protected async groupLead (group: WorkingGroups): Promise <GroupLeadWithMemberId | null> {
+    const optLeadId = (await this.queryCachedByGroup(group).currentLead()) as Option<WorkerId>;
 
     if (!optLeadId.isSome) {
       return null;
     }
 
     const leadWorkerId = optLeadId.unwrap();
-    const leadWorker = await this.cacheApi.query.storageWorkingGroup.workerById(leadWorkerId) as Worker;
+    const leadWorker = await this.queryCachedByGroup(group).workerById(leadWorkerId) as Worker;
 
     if (leadWorker.isEmpty) {
       return null;
@@ -312,9 +199,7 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   async groupLeadStatus (group: WorkingGroups = WorkingGroups.ContentCurators): Promise<GroupLeadStatus> {
-    const currentLead = group === WorkingGroups.ContentCurators
-      ? await this.currentCuratorLead()
-      : await this.currentStorageLead();
+    const currentLead = await this.groupLead(group);
 
     if (currentLead !== null) {
       const profile = await this.cacheApi.query.members.membershipById(currentLead.memberId) as Membership;
@@ -327,8 +212,8 @@ export class Transport extends BaseTransport implements ITransport {
       const rewardRelationship = rewardRelationshipId.isSome
         ? await this.rewardRelationshipById(rewardRelationshipId.unwrap())
         : undefined;
-      const stake = group === WorkingGroups.StorageProviders && (currentLead.lead as Worker).role_stake_profile.isSome
-        ? await this.workerStake((currentLead.lead as Worker).role_stake_profile.unwrap())
+      const stake = currentLead.lead.role_stake_profile.isSome
+        ? await this.workerStake(currentLead.lead.role_stake_profile.unwrap())
         : undefined;
 
       return {
@@ -338,7 +223,6 @@ export class Transport extends BaseTransport implements ITransport {
           roleAccount: currentLead.lead.role_account_id,
           profile,
           title: _.startCase(group) + ' Lead',
-          stage: group === WorkingGroups.ContentCurators ? (currentLead.lead as Lead).stage : undefined,
           stake,
           rewardRelationship
         },
@@ -356,8 +240,8 @@ export class Transport extends BaseTransport implements ITransport {
     const leadRolesAvailable = await this.areGroupRolesOpen(group, true);
     const leadStatus = await this.groupLeadStatus(group);
 
-    const workers = (await this.entriesByIds<GroupWorkerId, GroupWorker>(
-      this.apiMethodByGroup(group, 'workerById')
+    const workers = (await this.entriesByIds<WorkerId, Worker>(
+      this.queryByGroup(group).workerById
     ))
       .filter(([id, worker]) => worker.is_active && (!leadStatus.lead?.workerId || !id.eq(leadStatus.lead.workerId)));
 
@@ -379,9 +263,9 @@ export class Transport extends BaseTransport implements ITransport {
 
   async opportunitiesByGroup (group: WorkingGroups): Promise<WorkingGroupOpening[]> {
     const output = new Array<WorkingGroupOpening>();
-    const nextId = (await this.cachedApiMethodByGroup(group, 'nextOpeningId')()) as GroupOpeningId;
+    const nextId = (await this.queryCachedByGroup(group).nextOpeningId()) as OpeningId;
 
-    // This is chain specfic, but if next id is still 0, it means no curator openings have been added yet
+    // This is chain specfic, but if next id is still 0, it means no openings have been added yet
     if (!nextId.eq(0)) {
       const highestId = nextId.toNumber() - 1;
 
@@ -409,9 +293,9 @@ export class Transport extends BaseTransport implements ITransport {
     return opening;
   }
 
-  protected async groupOpeningApplications (group: WorkingGroups, groupOpeningId: number): Promise<WorkingGroupPair<Application, GroupApplication>[]> {
-    const groupApplications = await this.entriesByIds<GroupApplicationId, GroupApplication>(
-      this.apiMethodByGroup(group, 'applicationById')
+  protected async groupOpeningApplications (group: WorkingGroups, groupOpeningId: number): Promise<WorkingGroupPair<Application, WGApplication>[]> {
+    const groupApplications = await this.entriesByIds<ApplicationId, WGApplication>(
+      this.queryByGroup(group).applicationById
     );
 
     const openingGroupApplications = groupApplications
@@ -430,13 +314,13 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   async groupOpening (group: WorkingGroups, id: number): Promise<WorkingGroupOpening> {
-    const nextId = (await this.cachedApiMethodByGroup(group, 'nextOpeningId')() as GroupOpeningId).toNumber();
+    const nextId = (await this.queryCachedByGroup(group).nextOpeningId() as OpeningId).toNumber();
 
     if (id < 0 || id >= nextId) {
       throw new Error('invalid id');
     }
 
-    const groupOpening = await this.cachedApiMethodByGroup(group, 'openingById')(id) as GroupOpening;
+    const groupOpening = await this.queryCachedByGroup(group).openingById(id) as WGOpening;
 
     const opening = await this.opening(
       groupOpening.hiring_opening_id.toNumber()
@@ -573,8 +457,8 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   async openingApplicationsByAddressAndGroup (group: WorkingGroups, roleKey: string): Promise<OpeningApplication[]> {
-    const myGroupApplications = (await this.entriesByIds<GroupApplicationId, GroupApplication>(
-      this.apiMethodByGroup(group, 'applicationById')
+    const myGroupApplications = (await this.entriesByIds<ApplicationId, WGApplication>(
+      this.queryByGroup(group).applicationById
     ))
       .filter(([id, groupApplication]) => groupApplication.role_account_id.eq(roleKey));
 
@@ -632,8 +516,8 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   async myRolesByGroup (group: WorkingGroups, roleKeyId: string): Promise<ActiveRole[]> {
-    const workers = await this.entriesByIds<GroupWorkerId, GroupWorker>(
-      this.apiMethodByGroup(group, 'workerById')
+    const workers = await this.entriesByIds<WorkerId, Worker>(
+      this.queryByGroup(group).workerById
     );
 
     const groupLead = (await this.groupLeadStatus(group)).lead;
@@ -713,9 +597,9 @@ export class Transport extends BaseTransport implements ITransport {
             reject(new Error('failed to create role account'));
           }
 
-          const tx = this.apiExtrinsicByGroup(group, 'applyOnOpening')(
+          const tx = this.txByGroup(group).applyOnOpening(
             membershipIds[0], // Member id
-            id, // Worker/Curator opening id
+            id, // Worker opening id
             roleAccount, // Role account
             // TODO: Will need to be adjusted if AtLeast Zero stakes become possible
             roleStake.eq(Zero) ? null : roleStake, // Role stake
@@ -743,7 +627,7 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   leaveRole (group: WorkingGroups, sourceAccount: string, id: number, rationale: string, txSuccessCb?: () => void) {
-    const tx = this.apiExtrinsicByGroup(group, 'leaveRole')(
+    const tx = this.txByGroup(group).leaveRole(
       id,
       rationale
     );
@@ -756,7 +640,7 @@ export class Transport extends BaseTransport implements ITransport {
   }
 
   withdrawApplication (group: WorkingGroups, sourceAccount: string, id: number, txSuccessCb?: () => void) {
-    const tx = this.apiExtrinsicByGroup(group, 'withdrawApplication')(
+    const tx = this.txByGroup(group).withdrawApplication(
       id
     );
 

+ 31 - 9
pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx

@@ -119,14 +119,24 @@ const SpendingAndStakeTableRow: React.FC<{
   );
 };
 
+type TokenomicsGroup =
+  'validators' |
+  'council' |
+  'storageProviders' |
+  'contentCurators'
+
 const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
   const { width } = useWindowDimensions();
 
-  const displayStatusData = (group: 'validators' | 'council' | 'storageProviders' | 'storageProviderLead' | 'contentCurators', action: 'rewardsPerWeek' | 'totalStake'): string | undefined => {
-    if (group === 'storageProviderLead') {
-      return statusData === null ? 'Data currently unavailable...' : (data && statusData) && `${(data.storageProviders.lead[action] * Number(statusData.price)).toFixed(2)}`;
+  const displayStatusData = (group: TokenomicsGroup, dataType: 'rewardsPerWeek' | 'totalStake', lead = false): string | undefined => {
+    if ((group === 'storageProviders' || group === 'contentCurators') && lead) {
+      return statusData === null
+        ? 'Data currently unavailable...'
+        : (data && statusData) && `${(data[group].lead[dataType] * Number(statusData.price)).toFixed(2)}`;
     } else {
-      return statusData === null ? 'Data currently unavailable...' : (data && statusData) && `${(data[group][action] * Number(statusData.price)).toFixed(2)}`;
+      return statusData === null
+        ? 'Data currently unavailable...'
+        : (data && statusData) && `${(data[group][dataType] * Number(statusData.price)).toFixed(2)}`;
     }
   };
 
@@ -188,17 +198,17 @@ const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; status
           helpContent='Current Storage Provider Lead, and their projected reward and stake.'
           numberOfActors={data && `${data.storageProviders.lead.number}`}
           groupEarning={data && `${Math.round(data.storageProviders.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('storageProviderLead', 'rewardsPerWeek')}
+          groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek', true)}
           earningShare={data && `${round(data.storageProviders.lead.rewardsShare * 100)}`}
           groupStake={data && `${data.storageProviders.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('storageProviderLead', 'totalStake')}
+          groupStakeDollar={displayStatusData('storageProviders', 'totalStake', true)}
           stakeShare={data && `${round(data.storageProviders.lead.stakeShare * 100)}`}
           color='rgb(170, 222, 167)'
         />
         <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Content' : 'Content Curators'}
-          helpContent='The current Content Curators (and their Lead), and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.contentCurators.number} (${data.contentCurators.contentCuratorLead})`}
+          role={width <= 1015 ? 'Curators' : 'Content Curators'}
+          helpContent='The current Content Curators, and the sum of their projected rewards and stakes.'
+          numberOfActors={data && `${data.contentCurators.number}`}
           groupEarning={data && `${Math.round(data.contentCurators.rewardsPerWeek)}`}
           groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek')}
           earningShare={data && `${round(data.contentCurators.rewardsShare * 100)}`}
@@ -207,6 +217,18 @@ const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; status
           stakeShare={data && `${round(data.contentCurators.stakeShare * 100)}`}
           color='rgb(100, 194, 166)'
         />
+        <SpendingAndStakeTableRow
+          role={width <= 1015 ? 'C. Lead' : 'Curators Lead'}
+          helpContent='Current Content Curators Lead, and their projected reward and stake.'
+          numberOfActors={data && `${data.contentCurators.lead.number}`}
+          groupEarning={data && `${Math.round(data.contentCurators.lead.rewardsPerWeek)}`}
+          groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek', true)}
+          earningShare={data && `${round(data.contentCurators.lead.rewardsShare * 100)}`}
+          groupStake={data && `${data.contentCurators.lead.totalStake}`}
+          groupStakeDollar={displayStatusData('contentCurators', 'totalStake', true)}
+          stakeShare={data && `${round(data.contentCurators.lead.stakeShare * 100)}`}
+          color='rgb(100, 160, 190)'
+        />
         <SpendingAndStakeTableRow
           role='TOTAL'
           active={true}

+ 9 - 0
pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx

@@ -50,6 +50,10 @@ const TokenomicsCharts: React.FC<{data?: TokenomicsData; className?: string}> =
             colors: ['rgb(100, 194, 166)'],
             label: 'Content Curators',
             value: data.contentCurators.rewardsShare * 100
+          }, {
+            colors: ['rgb(100, 160, 190)'],
+            label: 'Content Curators Lead',
+            value: data.contentCurators.lead.rewardsShare * 100
           }
           ]} />
         <Label as='div'>
@@ -79,6 +83,11 @@ const TokenomicsCharts: React.FC<{data?: TokenomicsData; className?: string}> =
             colors: ['rgb(100, 194, 166)'],
             label: 'Content Curators',
             value: data.contentCurators.stakeShare * 100
+          },
+          {
+            colors: ['rgb(100, 160, 190)'],
+            label: 'Content Curators Lead',
+            value: data.contentCurators.lead.stakeShare * 100
           }
           ]} />
         <Label as='div'>

+ 6 - 12
pioneer/packages/joy-utils/src/consts/proposals.ts

@@ -41,21 +41,23 @@ export const metadata: { [k in ProposalType]: ProposalMeta } = {
   },
   SetLead: {
     description: 'Set Lead Proposal',
-    category: 'Content Working Group',
+    category: 'Other',
     stake: 50000,
     approvalQuorum: 60,
     approvalThreshold: 75,
     slashingQuorum: 60,
-    slashingThreshold: 80
+    slashingThreshold: 80,
+    outdated: true
   },
   SetContentWorkingGroupMintCapacity: {
     description: 'Set WG Mint Capacity Proposal',
-    category: 'Content Working Group',
+    category: 'Other',
     stake: 50000,
     approvalQuorum: 60,
     approvalThreshold: 75,
     slashingQuorum: 60,
-    slashingThreshold: 80
+    slashingThreshold: 80,
+    outdated: true
   },
   Spending: {
     description: 'Spending Proposal',
@@ -171,14 +173,6 @@ export const apiMethods: { [k in ProposalType]?: ProposalsApiMethodNames } = {
     votingPeriod: 'setValidatorCountProposalVotingPeriod',
     gracePeriod: 'setValidatorCountProposalGracePeriod'
   },
-  SetLead: {
-    votingPeriod: 'setLeadProposalVotingPeriod',
-    gracePeriod: 'setLeadProposalGracePeriod'
-  },
-  SetContentWorkingGroupMintCapacity: {
-    votingPeriod: 'setContentWorkingGroupMintCapacityProposalVotingPeriod',
-    gracePeriod: 'setContentWorkingGroupMintCapacityProposalGracePeriod'
-  },
   Spending: {
     votingPeriod: 'spendingProposalVotingPeriod',
     gracePeriod: 'spendingProposalGracePeriod'

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

@@ -1,23 +1,16 @@
 import React, { useContext } from 'react';
 
 import { AccountId } from '@polkadot/types/interfaces';
-import { Vec, Option } from '@polkadot/types';
+import { Vec } from '@polkadot/types';
 import accountObservable from '@polkadot/ui-keyring/observable/accounts';
 import { withCalls, withMulti, withObservable, ApiContext } from '@polkadot/react-api/index';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 
 import { MemberId, Membership } from '@joystream/types/members';
-import { LeadId, Lead, CuratorId, Curator, CurationActor } from '@joystream/types/content-working-group';
 
 import { queryMembershipToProp } from '@polkadot/joy-members/utils';
 import useMyAccount from '../hooks/useMyAccount';
 import { componentName } from '../helpers';
-import { queryToProp } from '@polkadot/joy-utils/functions/misc';
-import { entriesByIds } from '@polkadot/joy-utils/transport/base';
-
-import { useApi } from '@polkadot/react-hooks';
-import usePromise from '../hooks/usePromise';
-import { Error } from '../components/PromiseComponent';
 
 export type MyAddressProps = {
   myAddress?: string;
@@ -31,13 +24,6 @@ export type MyAccountProps = MyAddressProps & {
   myMemberIdChecked?: boolean;
   iAmMember?: boolean;
   myMembership?: Membership | null;
-
-  // Content Working Group
-  isLeadSet?: Option<LeadId>;
-  contentLeadId?: LeadId;
-  contentLead?: Lead;
-
-  curationActor?: [CurationActor, AccountId];
   allAccounts?: SubjectInfo;
 };
 
@@ -108,112 +94,6 @@ function resolveMyProfile<P extends { myMembership?: Membership | null }> (Compo
   return ResultComponent;
 }
 
-/* Content Working Group */
-function resolveLead<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
-    const { isLeadSet } = props;
-
-    const newProps = {
-      contentLeadId: isLeadSet?.unwrapOr(undefined)
-    };
-
-    return <Component {...props} {...newProps} />;
-  };
-
-  ResultComponent.displayName = `resolveLead(${componentName(Component)})`;
-
-  return ResultComponent;
-}
-
-const resolveLeadEntry = withCalls<MyAccountProps>(
-  queryToProp('query.contentWorkingGroup.leadById', { propName: 'contentLead', paramName: 'contentLeadId' })
-);
-
-const withContentWorkingGroupDetails = withCalls<MyAccountProps>(
-  queryToProp('query.contentWorkingGroup.currentLeadId', { propName: 'isLeadSet' })
-);
-
-const withContentWorkingGroup = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
-  withMulti(Component, withContentWorkingGroupDetails, resolveLead, resolveLeadEntry);
-
-function withCurationActor<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  const ResultComponent: React.FunctionComponent<P> = (props: P) => {
-    const {
-      myAccountId,
-      isLeadSet,
-      contentLead,
-      allAccounts
-    } = props;
-    const { isApiReady, api } = useApi();
-    const [curatorEntries, curatorsError, curatorsLoading] = usePromise<[CuratorId, Curator][]>(
-      () => isApiReady
-        ? entriesByIds<CuratorId, Curator>(api.query.contentWorkingGroup.curatorById)
-        : new Promise((resolve) => resolve([])),
-      [],
-      [isApiReady]
-    );
-
-    if (curatorsError) {
-      return <Error error={curatorsError} />;
-    }
-
-    if (!isApiReady || curatorsLoading || !myAccountId || !isLeadSet || !allAccounts) {
-      return <Component {...props} />;
-    }
-
-    const lead = isLeadSet.isSome && contentLead?.stage.isOfType('Active') ? contentLead : null;
-
-    const curationActorByAccount = (accountId: AccountId | string) => {
-      if (lead && lead.role_account.toString() === accountId.toString()) {
-        return api.createType('CurationActor', { Lead: null });
-      }
-
-      const matchingCuratorEntry = curatorEntries.find(([id, curator]) =>
-        curator.is_active && accountId.toString() === curator.role_account.toString()
-      );
-
-      return matchingCuratorEntry
-        ? api.createType('CurationActor', {
-          Curator: matchingCuratorEntry[0]
-        })
-        : null;
-    };
-
-    // First priority - currently selected account
-    let actor = curationActorByAccount(myAccountId);
-    let actorKey: AccountId | null = myAccountId;
-
-    // Second priority - check other keys and find best role
-    // TODO: Prioritize current member?
-    // TODO: Perhaps just don't do that at all and force the user to select the correct key to avoid confision?
-    if (!actor) {
-      const allActorsWithKeys = Object.keys(allAccounts).map((accKey) => ({
-        actor: curationActorByAccount(allAccounts[accKey].json.address),
-        key: api.createType('AccountId', allAccounts[accKey].json.address)
-      }));
-      let actorWithKey = allActorsWithKeys.find(({ actor }) => actor?.isOfType('Lead'));
-
-      if (!actorWithKey) {
-        actorWithKey = allActorsWithKeys.find(({ actor }) => actor?.isOfType('Curator'));
-      }
-
-      actor = actorWithKey?.actor || null;
-      actorKey = actorWithKey?.key || null;
-    }
-
-    if (actor && actorKey) {
-      return <Component {...props} curationActor={[actor, actorKey]} />;
-    } else {
-      // we don't have any key that can fulfill a curation action
-      return <Component {...props} />;
-    }
-  };
-
-  ResultComponent.displayName = `withCurationActor(${componentName(Component)})`;
-
-  return ResultComponent;
-}
-
 const withMyProfileCall = withCalls<MyAccountProps>(queryMembershipToProp('membershipById', {
   paramName: 'myMemberId',
   propName: 'myMembership'
@@ -229,7 +109,5 @@ export const withMyAccount = <P extends MyAccountProps>(Component: React.Compone
     withMyAddress,
     withMyMemberIds,
     withMyMembership,
-    withMyProfile,
-    withContentWorkingGroup,
-    withCurationActor
+    withMyProfile
   );

+ 0 - 41
pioneer/packages/joy-utils/src/transport/contentWorkingGroup.ts

@@ -1,41 +0,0 @@
-import { Membership } from '@joystream/types/members';
-import { u128, Option } from '@polkadot/types/';
-import BaseTransport from './base';
-import { MintId, Mint } from '@joystream/types/mint';
-import { LeadId, Lead } from '@joystream/types/content-working-group';
-import { ApiPromise } from '@polkadot/api';
-import MembersTransport from './members';
-import { APIQueryCache } from './APIQueryCache';
-
-export default class ContentWorkingGroupTransport extends BaseTransport {
-  private membersT: MembersTransport;
-
-  constructor (api: ApiPromise, cacheApi: APIQueryCache, membersTransport: MembersTransport) {
-    super(api, cacheApi);
-    this.membersT = membersTransport;
-  }
-
-  async currentMintCap (): Promise<number> {
-    const WGMintId = (await this.contentWorkingGroup.mint()) as MintId;
-    const WGMint = (await this.minting.mints(WGMintId)) as Mint;
-
-    return (WGMint.get('capacity') as u128).toNumber();
-  }
-
-  async currentLead (): Promise<{ id: number; profile: Membership } | null> {
-    const optLeadId = (await this.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
-    const leadId = optLeadId.unwrapOr(null);
-
-    if (!leadId) return null;
-
-    const lead = await this.contentWorkingGroup.leadById(leadId) as Lead;
-
-    if (lead.isEmpty || !lead.stage.isOfType('Active')) {
-      return null;
-    }
-
-    const profile = await this.membersT.expectedMembership(lead.member_id);
-
-    return profile && { id: lead.member_id.toNumber(), profile };
-  }
-}

+ 0 - 3
pioneer/packages/joy-utils/src/transport/index.ts

@@ -1,6 +1,5 @@
 import { ApiPromise } from '@polkadot/api';
 import ChainTransport from './chain';
-import ContentWorkingGroupTransport from './contentWorkingGroup';
 import ProposalsTransport from './proposals';
 import MembersTransport from './members';
 import CouncilTransport from './council';
@@ -17,7 +16,6 @@ export default class Transport {
   public members: MembersTransport;
   public council: CouncilTransport;
   public proposals: ProposalsTransport;
-  public contentWorkingGroup: ContentWorkingGroupTransport;
   public validators: ValidatorsTransport;
   public workingGroups: WorkingGroupsTransport;
   public tokenomics: TokenomicsTransport
@@ -29,7 +27,6 @@ export default class Transport {
     this.members = new MembersTransport(api, this.cacheApi);
     this.validators = new ValidatorsTransport(api, this.cacheApi);
     this.council = new CouncilTransport(api, this.cacheApi, this.members, this.chain);
-    this.contentWorkingGroup = new ContentWorkingGroupTransport(api, this.cacheApi, this.members);
     this.proposals = new ProposalsTransport(api, this.cacheApi, this.members, this.chain, this.council);
     this.workingGroups = new WorkingGroupsTransport(api, this.cacheApi, this.members);
     this.tokenomics = new TokenomicsTransport(api, this.cacheApi, this.council, this.workingGroups);

+ 85 - 132
pioneer/packages/joy-utils/src/transport/tokenomics.ts

@@ -9,9 +9,9 @@ import { BlockNumber, BalanceOf, Exposure } from '@polkadot/types/interfaces';
 import { WorkerId } from '@joystream/types/working-group';
 import { RewardRelationshipId, RewardRelationship } from '@joystream/types/recurring-rewards';
 import { StakeId, Stake } from '@joystream/types/stake';
-import { CuratorId, Curator, LeadId } from '@joystream/types/content-working-group';
 import { TokenomicsData } from '@polkadot/joy-utils/src/types/tokenomics';
 import { calculateValidatorsRewardsPerEra } from '../functions/staking';
+import { WorkingGroupKey } from '@joystream/types/common';
 
 export default class TokenomicsTransport extends BaseTransport {
   private councilT: CouncilTransport;
@@ -75,15 +75,16 @@ export default class TokenomicsTransport extends BaseTransport {
     };
   }
 
-  private async storageProviderSizeAndIds () {
-    const stakeIds: StakeId[] = [];
-    const rewardIds: RewardRelationshipId[] = [];
+  private async workingGroupSizeAndIds (group: WorkingGroupKey) {
+    const workerStakeIds: StakeId[] = [];
+    const workerRewardIds: RewardRelationshipId[] = [];
     let leadStakeId: StakeId | null = null;
     let leadRewardId: RewardRelationshipId | null = null;
-    let numberOfStorageProviders = 0;
+    let numberOfWorkers = 0;
     let leadNumber = 0;
-    const allWorkers = await this.workingGroupT.allWorkers('Storage');
-    const currentLeadId = (await this.api.query.storageWorkingGroup.currentLead() as Option<WorkerId>).unwrapOr(null)?.toNumber();
+    const allWorkers = await this.workingGroupT.allWorkers(group);
+    const currentLeadId = (await this.workingGroupT.queryByGroup(group).currentLead() as Option<WorkerId>)
+      .unwrapOr(undefined)?.toNumber();
 
     allWorkers.forEach(([workerId, worker]) => {
       const stakeId = worker.role_stake_profile.isSome ? worker.role_stake_profile.unwrap().stake_id : null;
@@ -94,50 +95,50 @@ export default class TokenomicsTransport extends BaseTransport {
         leadRewardId = rewardId;
         leadNumber += 1;
       } else {
-        numberOfStorageProviders += 1;
+        numberOfWorkers += 1;
 
         if (stakeId) {
-          stakeIds.push(stakeId);
+          workerStakeIds.push(stakeId);
         }
 
         if (rewardId) {
-          rewardIds.push(rewardId);
+          workerRewardIds.push(rewardId);
         }
       }
     });
 
     return {
-      numberOfStorageProviders,
-      stakeIds,
-      rewardIds,
+      numberOfWorkers,
+      workerStakeIds,
+      workerRewardIds,
       leadNumber,
       leadRewardId,
       leadStakeId
     };
   }
 
-  private async storageProviderStakeAndRewards (
-    stakeIds: StakeId[],
+  private async resolveGroupStakeAndRewards (
+    workerStakeIds: StakeId[],
     leadStakeId: StakeId | null,
-    rewardIds: RewardRelationshipId[],
+    workerRewardIds: RewardRelationshipId[],
     leadRewardId: RewardRelationshipId | null
   ) {
-    let totalStorageProviderStake = 0;
+    let workersStake = 0;
     let leadStake = 0;
-    let storageProviderRewardsPerBlock = 0;
-    let storageLeadRewardsPerBlock = 0;
+    let workersRewardsPerBlock = 0;
+    let leadRewardsPerBlock = 0;
 
-    (await this.api.query.stake.stakes.multi<Stake>(stakeIds)).forEach((stake) => {
-      totalStorageProviderStake += stake.value.toNumber();
+    (await this.api.query.stake.stakes.multi<Stake>(workerStakeIds)).forEach((stake) => {
+      workersStake += stake.value.toNumber();
     });
-    (await this.api.query.recurringRewards.rewardRelationships.multi<RewardRelationship>(rewardIds)).map((rewardRelationship) => {
+    (await this.api.query.recurringRewards.rewardRelationships.multi<RewardRelationship>(workerRewardIds)).map((rewardRelationship) => {
       const amount = rewardRelationship.amount_per_payout.toNumber();
       const payoutInterval = rewardRelationship.payout_interval.isSome
         ? rewardRelationship.payout_interval.unwrap().toNumber()
         : null;
 
       if (amount && payoutInterval) {
-        storageProviderRewardsPerBlock += amount / payoutInterval;
+        workersRewardsPerBlock += amount / payoutInterval;
       }
     });
 
@@ -151,96 +152,31 @@ export default class TokenomicsTransport extends BaseTransport {
       const leadRewardInterval = leadRewardData.payout_interval.isSome ? leadRewardData.payout_interval.unwrap().toNumber() : null;
 
       if (leadAmount && leadRewardInterval) {
-        storageLeadRewardsPerBlock += leadAmount / leadRewardInterval;
+        leadRewardsPerBlock += leadAmount / leadRewardInterval;
       }
     }
 
     return {
-      totalStorageProviderStake,
+      workersStake,
       leadStake,
-      storageProviderRewardsPerWeek: storageProviderRewardsPerBlock * 100800,
-      storageProviderLeadRewardsPerWeek: storageLeadRewardsPerBlock * 100800
+      workersRewardsPerWeek: workersRewardsPerBlock * 100800,
+      leadRewardsPerWeek: leadRewardsPerBlock * 100800
     };
   }
 
-  async getStorageProviderData () {
-    const { numberOfStorageProviders, leadNumber, stakeIds, rewardIds, leadRewardId, leadStakeId } = await this.storageProviderSizeAndIds();
-    const { totalStorageProviderStake, leadStake, storageProviderRewardsPerWeek, storageProviderLeadRewardsPerWeek } =
-      await this.storageProviderStakeAndRewards(stakeIds, leadStakeId, rewardIds, leadRewardId);
+  async getWorkingGroupData (group: WorkingGroupKey) {
+    const { numberOfWorkers, leadNumber, workerStakeIds, workerRewardIds, leadRewardId, leadStakeId } =
+      await this.workingGroupSizeAndIds(group);
+    const { workersStake, leadStake, workersRewardsPerWeek, leadRewardsPerWeek } =
+      await this.resolveGroupStakeAndRewards(workerStakeIds, leadStakeId, workerRewardIds, leadRewardId);
 
     return {
-      numberOfStorageProviders,
-      storageProviderLeadNumber: leadNumber,
-      totalStorageProviderStake,
-      totalStorageProviderLeadStake: leadStake,
-      storageProviderRewardsPerWeek,
-      storageProviderLeadRewardsPerWeek
-    };
-  }
-
-  private async contentCuratorSizeAndIds () {
-    const stakeIds: StakeId[] = []; const rewardIds: RewardRelationshipId[] = []; let numberOfContentCurators = 0;
-    const contentCurators = await this.entriesByIds<CuratorId, Curator>(this.api.query.contentWorkingGroup.curatorById);
-    const currentLeadId = (await this.api.query.contentWorkingGroup.currentLeadId() as Option<LeadId>).unwrapOr(null)?.toNumber();
-
-    contentCurators.forEach(([curatorId, curator]) => {
-      const stakeId = curator.role_stake_profile.isSome ? curator.role_stake_profile.unwrap().stake_id : null;
-      const rewardId = curator.reward_relationship.unwrapOr(null);
-
-      if (curator.is_active) {
-        numberOfContentCurators += 1;
-
-        if (stakeId) {
-          stakeIds.push(stakeId);
-        }
-
-        if (rewardId) {
-          rewardIds.push(rewardId);
-        }
-      }
-    });
-
-    return {
-      stakeIds,
-      rewardIds,
-      numberOfContentCurators,
-      contentCuratorLeadNumber: currentLeadId ? 1 : 0
-    };
-  }
-
-  private async contentCuratorStakeAndRewards (stakeIds: StakeId[], rewardIds: RewardRelationshipId[]) {
-    let totalContentCuratorStake = 0;
-    let contentCuratorRewardsPerBlock = 0;
-
-    (await this.api.query.stake.stakes.multi<Stake>(stakeIds)).forEach((stake) => {
-      totalContentCuratorStake += stake.value.toNumber();
-    });
-    (await this.api.query.recurringRewards.rewardRelationships.multi<RewardRelationship>(rewardIds)).map((rewardRelationship) => {
-      const amount = rewardRelationship.amount_per_payout.toNumber();
-      const payoutInterval = rewardRelationship.payout_interval.isSome
-        ? rewardRelationship.payout_interval.unwrap().toNumber()
-        : null;
-
-      if (amount && payoutInterval) {
-        contentCuratorRewardsPerBlock += amount / payoutInterval;
-      }
-    });
-
-    return {
-      totalContentCuratorStake,
-      contentCuratorRewardsPerBlock
-    };
-  }
-
-  async getContentCuratorData () {
-    const { stakeIds, rewardIds, numberOfContentCurators, contentCuratorLeadNumber } = await this.contentCuratorSizeAndIds();
-    const { totalContentCuratorStake, contentCuratorRewardsPerBlock } = await this.contentCuratorStakeAndRewards(stakeIds, rewardIds);
-
-    return {
-      numberOfContentCurators,
-      contentCuratorLeadNumber,
-      totalContentCuratorStake,
-      contentCuratorRewardsPerWeek: contentCuratorRewardsPerBlock * 100800
+      numberOfWorkers,
+      leadNumber,
+      workersStake,
+      leadStake,
+      workersRewardsPerWeek,
+      leadRewardsPerWeek
     };
   }
 
@@ -287,13 +223,50 @@ export default class TokenomicsTransport extends BaseTransport {
   }
 
   async getTokenomicsData (): Promise<TokenomicsData> {
-    const { numberOfCouncilMembers, totalCouncilRewardsInOneWeek, totalCouncilStake } = await this.getCouncilData();
-    const { numberOfStorageProviders, storageProviderLeadNumber, totalStorageProviderStake, totalStorageProviderLeadStake, storageProviderLeadRewardsPerWeek, storageProviderRewardsPerWeek } = await this.getStorageProviderData();
-    const { numberOfContentCurators, contentCuratorLeadNumber, totalContentCuratorStake, contentCuratorRewardsPerWeek } = await this.getContentCuratorData();
-    const { numberOfValidators, numberOfNominators, totalValidatorStake, validatorRewardsPerWeek, totalIssuance } = await this.getValidatorData();
-    const currentlyStakedTokens = totalCouncilStake + totalStorageProviderStake + totalStorageProviderLeadStake + totalContentCuratorStake + totalValidatorStake;
-    const totalWeeklySpending = totalCouncilRewardsInOneWeek + storageProviderRewardsPerWeek + storageProviderLeadRewardsPerWeek + contentCuratorRewardsPerWeek + validatorRewardsPerWeek;
-    const totalNumberOfActors = numberOfCouncilMembers + numberOfStorageProviders + storageProviderLeadNumber + numberOfContentCurators + contentCuratorLeadNumber + numberOfValidators;
+    const { numberOfCouncilMembers, totalCouncilRewardsInOneWeek, totalCouncilStake } =
+      await this.getCouncilData();
+    const workingGroupsData = {
+      storageProviders: await this.getWorkingGroupData('Storage'),
+      curators: await this.getWorkingGroupData('Content')
+    };
+    const { numberOfValidators, numberOfNominators, totalValidatorStake, validatorRewardsPerWeek, totalIssuance } =
+      await this.getValidatorData();
+
+    const currentlyStakedTokens =
+      totalCouncilStake +
+      Object.values(workingGroupsData).reduce(
+        (sum, { workersStake, leadStake }) => sum + workersStake + leadStake,
+        0) +
+      totalValidatorStake;
+
+    const totalWeeklySpending =
+      totalCouncilRewardsInOneWeek +
+      Object.values(workingGroupsData).reduce(
+        (sum, { workersRewardsPerWeek, leadRewardsPerWeek }) => sum + workersRewardsPerWeek + leadRewardsPerWeek,
+        0) +
+      validatorRewardsPerWeek;
+
+    const totalNumberOfActors =
+      numberOfCouncilMembers +
+      Object.values(workingGroupsData).reduce(
+        (sum, { numberOfWorkers, leadNumber }) => sum + numberOfWorkers + leadNumber,
+        0) +
+      numberOfValidators;
+
+    const resolveGroupData = (data: typeof workingGroupsData[keyof typeof workingGroupsData]) => ({
+      number: data.numberOfWorkers,
+      totalStake: data.workersStake,
+      stakeShare: data.workersStake / currentlyStakedTokens,
+      rewardsPerWeek: data.workersRewardsPerWeek,
+      rewardsShare: data.workersRewardsPerWeek / totalWeeklySpending,
+      lead: {
+        number: data.leadNumber,
+        totalStake: data.leadStake,
+        stakeShare: data.leadStake / currentlyStakedTokens,
+        rewardsPerWeek: data.leadRewardsPerWeek,
+        rewardsShare: data.leadRewardsPerWeek / totalWeeklySpending
+      }
+    });
 
     return {
       totalIssuance,
@@ -317,28 +290,8 @@ export default class TokenomicsTransport extends BaseTransport {
         totalStake: totalCouncilStake,
         stakeShare: totalCouncilStake / currentlyStakedTokens
       },
-      storageProviders: {
-        number: numberOfStorageProviders,
-        totalStake: totalStorageProviderStake,
-        stakeShare: totalStorageProviderStake / currentlyStakedTokens,
-        rewardsPerWeek: storageProviderRewardsPerWeek,
-        rewardsShare: storageProviderRewardsPerWeek / totalWeeklySpending,
-        lead: {
-          number: storageProviderLeadNumber,
-          totalStake: totalStorageProviderLeadStake,
-          stakeShare: totalStorageProviderLeadStake / currentlyStakedTokens,
-          rewardsPerWeek: storageProviderLeadRewardsPerWeek,
-          rewardsShare: storageProviderLeadRewardsPerWeek / totalWeeklySpending
-        }
-      },
-      contentCurators: {
-        number: numberOfContentCurators,
-        contentCuratorLead: contentCuratorLeadNumber,
-        rewardsPerWeek: contentCuratorRewardsPerWeek,
-        rewardsShare: contentCuratorRewardsPerWeek / totalWeeklySpending,
-        totalStake: totalContentCuratorStake,
-        stakeShare: totalContentCuratorStake / currentlyStakedTokens
-      }
+      storageProviders: resolveGroupData(workingGroupsData.storageProviders),
+      contentCurators: resolveGroupData(workingGroupsData.curators)
     };
   }
 }

+ 1 - 1
pioneer/packages/joy-utils/src/transport/workingGroups.ts

@@ -26,7 +26,7 @@ export default class WorkingGroupsTransport extends BaseTransport {
     return this.api.query[module];
   }
 
-  protected queryByGroup (group: WorkingGroupKey) {
+  queryByGroup (group: WorkingGroupKey) {
     const module = apiModuleByGroup[group];
 
     return this.cacheApi.query[module];

+ 0 - 1
pioneer/packages/joy-utils/src/types/proposals.ts

@@ -77,7 +77,6 @@ export type ProposalVotes = {
 export const Categories = {
   council: 'Council',
   validators: 'Validators',
-  cwg: 'Content Working Group',
   wg: 'Working Groups',
   other: 'Other'
 } as const;

+ 17 - 22
pioneer/packages/joy-utils/src/types/tokenomics.ts

@@ -1,3 +1,18 @@
+export type WorkingGroupTokenomicsData = {
+  number: number;
+  totalStake: number;
+  stakeShare: number;
+  rewardsPerWeek: number;
+  rewardsShare: number;
+  lead: {
+    number: number;
+    totalStake: number;
+    stakeShare: number;
+    rewardsPerWeek: number;
+    rewardsShare: number;
+  };
+}
+
 export type TokenomicsData = {
   totalIssuance: number;
   currentlyStakedTokens: number;
@@ -20,28 +35,8 @@ export type TokenomicsData = {
     totalStake: number;
     stakeShare: number;
   };
-  storageProviders: {
-    number: number;
-    totalStake: number;
-    stakeShare: number;
-    rewardsPerWeek: number;
-    rewardsShare: number;
-    lead: {
-      number: number;
-      totalStake: number;
-      stakeShare: number;
-      rewardsPerWeek: number;
-      rewardsShare: number;
-    };
-  };
-  contentCurators: {
-    number: number;
-    contentCuratorLead: number;
-    rewardsPerWeek: number;
-    rewardsShare: number;
-    totalStake: number;
-    stakeShare: number;
-  };
+  storageProviders: WorkingGroupTokenomicsData;
+  contentCurators: WorkingGroupTokenomicsData;
 }
 
 export type StatusServerData = {

+ 0 - 1
pioneer/src/@types/ipfs-only-hash/index.d.ts

@@ -1 +0,0 @@
-declare module 'ipfs-only-hash';

+ 17 - 15
types/src/hiring/index.ts

@@ -221,20 +221,22 @@ export class StakingPolicy
   implements IStakingPolicy {}
 export const schemaValidator: Ajv.ValidateFunction = new Ajv({ allErrors: true }).compile(role_schema_json)
 
-const OpeningHRTFallback: GenericJoyStreamRoleSchema = {
-  version: 1,
-  headline: 'Unknown',
-  job: {
-    title: 'Unknown',
-    description: 'Unknown',
-  },
-  application: {},
-  reward: 'Unknown',
-  creator: {
-    membership: {
-      handle: 'Unknown',
+function openingHRTFallback(title = 'Working Group Opening', description = ''): GenericJoyStreamRoleSchema {
+  return {
+    version: 1,
+    headline: title,
+    job: {
+      title: title,
+      description,
     },
-  },
+    application: {},
+    reward: '? JOY',
+    creator: {
+      membership: {
+        handle: 'Unknown',
+      },
+    },
+  }
 }
 
 export type IOpening = {
@@ -280,11 +282,11 @@ export class Opening
     return str
   }
 
-  parse_human_readable_text_with_fallback(): GenericJoyStreamRoleSchema {
+  parse_human_readable_text_with_fallback(fallbackTitle?: string): GenericJoyStreamRoleSchema {
     const hrt = this.parse_human_readable_text()
 
     if (typeof hrt !== 'object') {
-      return OpeningHRTFallback
+      return openingHRTFallback(fallbackTitle, hrt)
     }
 
     return hrt

+ 17 - 255
yarn.lock

@@ -1379,14 +1379,6 @@
     pirates "^4.0.0"
     source-map-support "^0.5.16"
 
-"@babel/runtime-corejs2@^7.6.3":
-  version "7.11.2"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.11.2.tgz#700a03945ebad0d31ba6690fc8a6bcc9040faa47"
-  integrity sha512-AC/ciV28adSSpEkBglONBWq4/Lvm6GAZuxIoyVtsnUpZMl0bxLtoChEnYAkP+47KyOCayZanojtflUEUJtR/6Q==
-  dependencies:
-    core-js "^2.6.5"
-    regenerator-runtime "^0.13.4"
-
 "@babel/runtime@7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c"
@@ -4559,11 +4551,6 @@
   resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.4.tgz#a8a5e917ef70c79523b8b8d57f529e49616a760c"
   integrity sha512-c9+1g6+6vEqcw5UuM0RbfQV0mssmZcoG9+hNC5ptDCsv4G+XJW1Z4pE13wV5zbc9e0+YrDydALBTiD3nWG1a3g==
 
-"@types/mime-types@^2.1.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
-  integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
-
 "@types/minimatch@*":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -4661,13 +4648,6 @@
     "@types/history" "*"
     "@types/react" "*"
 
-"@types/react-beautiful-dnd@^11.0.3":
-  version "11.0.5"
-  resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-11.0.5.tgz#2f5bc733dd46da28312c8ee0c126ab7202b90247"
-  integrity sha512-idvycolYX3IxwIln8yahzQj3EBgYq4KimPHSP0a2lNHiz2OZSskGg2dLmq1VxTGvvTtdG+Za8p596D/dalRlrA==
-  dependencies:
-    "@types/react" "*"
-
 "@types/react-beautiful-dnd@^13.0.0":
   version "13.0.0"
   resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4"
@@ -5898,15 +5878,6 @@ anymatch@^3.0.3, anymatch@~3.1.1:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-aplayer@^1.10.0, aplayer@^1.10.1:
-  version "1.10.1"
-  resolved "https://registry.yarnpkg.com/aplayer/-/aplayer-1.10.1.tgz#318289206107452cc39e8f552fa6cc6cb459a90c"
-  integrity sha512-HAfyxgCUTLAqtYlxzzK9Fyqg6y+kZ9CqT1WfeWE8FSzwspT6oBqWOZHANPHF3RGTtC33IsyEgrfthPDzU5r9kQ==
-  dependencies:
-    balloon-css "^0.5.0"
-    promise-polyfill "7.1.0"
-    smoothscroll "0.4.0"
-
 app-builder-bin@3.5.9:
   version "3.5.9"
   resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.9.tgz#a3ac0c25286bac68357321cb2eaf7128b0bc0a4f"
@@ -6356,21 +6327,6 @@ aws4@^1.8.0:
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
   integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==
 
-axios@0.18.0:
-  version "0.18.0"
-  resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
-  integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=
-  dependencies:
-    follow-redirects "^1.3.0"
-    is-buffer "^1.1.5"
-
-axios@0.19.2, axios@^0.19.0, axios@^0.19.2:
-  version "0.19.2"
-  resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
-  integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
-  dependencies:
-    follow-redirects "1.5.10"
-
 axios@^0.18.0, axios@^0.18.1:
   version "0.18.1"
   resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
@@ -6379,6 +6335,13 @@ axios@^0.18.0, axios@^0.18.1:
     follow-redirects "1.5.10"
     is-buffer "^2.0.2"
 
+axios@^0.19.0, axios@^0.19.2:
+  version "0.19.2"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
+  integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
+  dependencies:
+    follow-redirects "1.5.10"
+
 babel-code-frame@^6.22.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@@ -6792,16 +6755,6 @@ balanced-match@^1.0.0:
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
   integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
 
-balloon-css@^0.5.0:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/balloon-css/-/balloon-css-0.5.2.tgz#9e2163565a136c9d4aa20e8400772ce3b738d3ff"
-  integrity sha512-zheJpzwyNrG4t39vusA67v3BYg1HTVXOF8cErPEHzWK88PEOFwgo6Ea9VHOgOWNMgeuOtFVtB73NE2NWl9uDyQ==
-
-balloon-css@^1.0.3:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/balloon-css/-/balloon-css-1.2.0.tgz#53d3fb4051264a278a58713bed6865845dbcaf4b"
-  integrity sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==
-
 base-path-converter@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/base-path-converter/-/base-path-converter-1.0.2.tgz#e80b4b4f31c7b1561e632158e00774b6f2f27978"
@@ -10155,26 +10108,6 @@ dotenv@^8.0.0, dotenv@^8.2.0:
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
   integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
 
-dplayer@1.25.0:
-  version "1.25.0"
-  resolved "https://registry.yarnpkg.com/dplayer/-/dplayer-1.25.0.tgz#10afb3416b42125b8926d9b157aae594c84945fb"
-  integrity sha512-TG2IKT4IXH5trE3DKeeOqmGlewheHT5R30ya4jhMrjysc1rrCPMJYnmsBfiTS8h6613YZeriFIDHZHEEi/rs3Q==
-  dependencies:
-    axios "0.18.0"
-    balloon-css "^0.5.0"
-    mini-css-extract-plugin "0.4.0"
-    promise-polyfill "8.0.0"
-    webpack-cli "3.0.4"
-
-dplayer@^1.24.0:
-  version "1.26.0"
-  resolved "https://registry.yarnpkg.com/dplayer/-/dplayer-1.26.0.tgz#d366200b1962bd09e91226066f2aadea6f35c71b"
-  integrity sha512-uOE0w/WdlX7N9d0ppIEcAYrcnUjY52TMX+MBL4lj9Mj+JMljVuaEc5w88HkZp5Q11VqvN/jxnM8ktx2Dr7/MgA==
-  dependencies:
-    axios "0.19.2"
-    balloon-css "^1.0.3"
-    promise-polyfill "8.1.3"
-
 drbg.js@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b"
@@ -12050,11 +11983,6 @@ follow-redirects@^1.0.0:
   dependencies:
     debug "^3.0.0"
 
-follow-redirects@^1.3.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
-  integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
-
 for-in@^0.1.3:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
@@ -12720,11 +12648,6 @@ global-dirs@^2.0.1:
   dependencies:
     ini "^1.3.5"
 
-global-modules-path@^2.1.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.1.tgz#e541f4c800a1a8514a990477b267ac67525b9931"
-  integrity sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==
-
 global-modules@2.0.0, global-modules@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
@@ -13877,14 +13800,6 @@ import-lazy@^4.0.0:
   resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
   integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
 
-import-local@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
-  integrity sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==
-  dependencies:
-    pkg-dir "^2.0.0"
-    resolve-cwd "^2.0.0"
-
 import-local@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
@@ -14028,7 +13943,7 @@ inquirer@6.5.0:
     strip-ansi "^5.1.0"
     through "^2.3.6"
 
-inquirer@^6.0.0, inquirer@^6.2.0:
+inquirer@^6.2.0:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
   integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
@@ -14119,7 +14034,7 @@ interpret@^1.0.0:
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
   integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
 
-interpret@^1.1.0, interpret@^1.4.0:
+interpret@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
   integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
@@ -14141,11 +14056,6 @@ invert-kv@^1.0.0:
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
   integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
 
-invert-kv@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
-  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
-
 ip-regex@^2.0.0, ip-regex@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -15123,11 +15033,6 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
 
-iso-639-1@^2.1.0:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/iso-639-1/-/iso-639-1-2.1.4.tgz#c08b3d43a1c18945b05e26a257991ae6e36693ee"
-  integrity sha512-pwJRHnpz1sCR5saQ+Hm1E2YESw2eLGKP5TzsYKXuQ7SIfvKWMRb9CHhptqunYpCIcRCpq3LgLuhYG5hiLPRbFQ==
-
 iso-random-stream@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/iso-random-stream/-/iso-random-stream-1.1.1.tgz#83824bba77fbb3480dd6b35fbb06de7f9e93e80f"
@@ -16579,13 +16484,6 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-lcid@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
-  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
-  dependencies:
-    invert-kv "^2.0.0"
-
 lcov-parse@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0"
@@ -17312,13 +17210,6 @@ makeerror@1.0.x:
   dependencies:
     tmpl "1.0.x"
 
-map-age-cleaner@^0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
-  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
-  dependencies:
-    p-defer "^1.0.0"
-
 map-cache@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@@ -17506,15 +17397,6 @@ mem@^1.1.0:
   dependencies:
     mimic-fn "^1.0.0"
 
-mem@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
-  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
-  dependencies:
-    map-age-cleaner "^0.1.1"
-    mimic-fn "^2.0.0"
-    p-is-promise "^2.0.0"
-
 memoize-one@^5.0.0, memoize-one@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
@@ -17741,7 +17623,7 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
   dependencies:
     mime-db "1.42.0"
 
-mime-types@^2.1.18, mime-types@^2.1.22, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17:
+mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17:
   version "2.1.27"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
   integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
@@ -17763,7 +17645,7 @@ mimic-fn@^1.0.0:
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
   integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
 
-mimic-fn@^2.0.0, mimic-fn@^2.1.0:
+mimic-fn@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
@@ -17798,14 +17680,6 @@ mini-create-react-context@^0.4.0:
     "@babel/runtime" "^7.5.5"
     tiny-warning "^1.0.3"
 
-mini-css-extract-plugin@0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.0.tgz#ff3bf08bee96e618e177c16ca6131bfecef707f9"
-  integrity sha512-2Zik6PhUZ/MbiboG6SDS9UTPL4XXy4qnyGjSdCIWRrr8xb6PwLtHE+AYOjkXJWdF0OG8vo/yrJ8CgS5WbMpzIg==
-  dependencies:
-    loader-utils "^1.1.0"
-    webpack-sources "^1.1.0"
-
 mini-css-extract-plugin@0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz#a3f13372d6fcde912f3ee4cd039665704801e3b9"
@@ -19325,15 +19199,6 @@ os-locale@^2.0.0:
     lcid "^1.0.0"
     mem "^1.1.0"
 
-os-locale@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
-  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
-  dependencies:
-    execa "^1.0.0"
-    lcid "^2.0.0"
-    mem "^4.0.0"
-
 os-name@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801"
@@ -19372,11 +19237,6 @@ p-cancelable@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
   integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
 
-p-defer@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
-  integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
-
 p-defer@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83"
@@ -19397,11 +19257,6 @@ p-finally@^2.0.0:
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
   integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==
 
-p-is-promise@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
-  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
-
 p-limit@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@@ -20660,21 +20515,6 @@ promise-inflight@^1.0.1:
   resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
   integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
 
-promise-polyfill@7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.0.tgz#4d749485b44577c14137591c6f36e5d7e2dd3378"
-  integrity sha512-P6NJ2wU/8fac44ENORsuqT8TiolKGB2u0fEClPtXezn7w5cmLIjM/7mhPlTebke2EPr6tmqZbXvnX0TxwykGrg==
-
-promise-polyfill@8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.0.0.tgz#b47c7fc74052cc5b2132b703cca144f1a5eb56ef"
-  integrity sha512-QGmPnw2hDEaRS6freHynJ7nfS1nDg0/P0c/CGglA43utoJjYQMiY9ojEpK0HaJ4wbUztdmwqQRlEfGWdsEQ5uQ==
-
-promise-polyfill@8.1.3:
-  version "8.1.3"
-  resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116"
-  integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==
-
 promise-retry@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
@@ -21165,28 +21005,6 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-react-aplayer@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/react-aplayer/-/react-aplayer-1.0.0.tgz#ff8bae803c01d479126b072980783242294fae94"
-  integrity sha512-jGoNhjT8cEieakK5fwXVn2J849RAa4JAx+subEtrAr/34DWpEF8KZohedceyAzsJygSmBWVjX3plNoOMBbPlhA==
-  dependencies:
-    aplayer "^1.10.0"
-    prop-types "^15.6.1"
-    react "^16.2.0"
-
-react-beautiful-dnd@^12.0.0:
-  version "12.2.0"
-  resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-12.2.0.tgz#e5f6222f9e7934c6ed4ee09024547f9e353ae423"
-  integrity sha512-s5UrOXNDgeEC+sx65IgbeFlqKKgK3c0UfbrJLWufP34WBheyu5kJ741DtJbsSgPKyNLkqfswpMYr0P8lRj42cA==
-  dependencies:
-    "@babel/runtime-corejs2" "^7.6.3"
-    css-box-model "^1.2.0"
-    memoize-one "^5.1.1"
-    raf-schd "^4.0.2"
-    react-redux "^7.1.1"
-    redux "^4.0.4"
-    use-memo-one "^1.1.1"
-
 react-beautiful-dnd@^13.0.0:
   version "13.0.0"
   resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40"
@@ -21300,15 +21118,6 @@ react-dom@^16.13.1, react-dom@^16.8.3:
     prop-types "^15.6.2"
     scheduler "^0.19.1"
 
-react-dplayer@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/react-dplayer/-/react-dplayer-0.2.3.tgz#45d46ef256df4e69f642a900e8f607c98cb1b8cf"
-  integrity sha512-CcZCeolaGqxfiKF+Fnb8sUYt2gauxXpivZkr3X0Um9l5c/qMsZ2K0fQEV5nGBdTWMQEz2dh01c9vHk5pN3klGg==
-  dependencies:
-    classnames "^2.2.6"
-    dplayer "^1.24.0"
-    prop-types "^15.6.2"
-
 react-draggable@^4.0.3:
   version "4.4.3"
   resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3"
@@ -21591,7 +21400,7 @@ react@^16.12.0:
     object-assign "^4.1.1"
     prop-types "^15.6.2"
 
-react@^16.13.1, react@^16.2.0, react@^16.8.3:
+react@^16.13.1, react@^16.8.3:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
   integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
@@ -23253,11 +23062,6 @@ smoothscroll-polyfill@^0.4.3:
   resolved "https://registry.yarnpkg.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz#3a259131dc6930e6ca80003e1cb03b603b69abf8"
   integrity sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==
 
-smoothscroll@0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/smoothscroll/-/smoothscroll-0.4.0.tgz#40e507b46461408ba1b787d0081e1e883c4124a5"
-  integrity sha512-sggQ3U2Un38b3+q/j1P4Y4fCboCtoUIaBYoge+Lb6Xg1H8RTIif/hugVr+ErMtIDpvBbhQfTjtiTeYAfbw1ZGQ==
-
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -25887,16 +25691,16 @@ uuid@^8.2.0, uuid@^8.3.0:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
   integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
 
-v8-compile-cache@^2.0.0, v8-compile-cache@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
-  integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
-
 v8-compile-cache@^2.0.3:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
   integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
 
+v8-compile-cache@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
+  integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
+
 v8-to-istanbul@^4.1.3:
   version "4.1.4"
   resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6"
@@ -26294,23 +26098,6 @@ webpack-chain@^6.0.0:
     deepmerge "^1.5.2"
     javascript-stringify "^2.0.1"
 
-webpack-cli@3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.4.tgz#55d6ad2cdd608de8c0f757dde5bc4bf5bd2dec68"
-  integrity sha512-r5R0hMck4GxUS6a3TXClwi1KhQfnHZRtIfXqsSytQZG4kawKMhTtd5//uNZGoGks/h61Zv5jMnR6jwx15Qf8dA==
-  dependencies:
-    chalk "^2.4.1"
-    cross-spawn "^6.0.5"
-    enhanced-resolve "^4.0.0"
-    global-modules-path "^2.1.0"
-    import-local "^1.0.0"
-    inquirer "^6.0.0"
-    interpret "^1.1.0"
-    loader-utils "^1.1.0"
-    supports-color "^5.4.0"
-    v8-compile-cache "^2.0.0"
-    yargs "^11.1.0"
-
 webpack-cli@^3.3.12:
   version "3.3.12"
   resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a"
@@ -26939,13 +26726,6 @@ yargs-parser@^8.1.0:
   dependencies:
     camelcase "^4.1.0"
 
-yargs-parser@^9.0.2:
-  version "9.0.2"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
-  integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=
-  dependencies:
-    camelcase "^4.1.0"
-
 yargs@^10.0.3:
   version "10.1.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
@@ -26964,24 +26744,6 @@ yargs@^10.0.3:
     y18n "^3.2.1"
     yargs-parser "^8.1.0"
 
-yargs@^11.1.0:
-  version "11.1.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.1.tgz#5052efe3446a4df5ed669c995886cc0f13702766"
-  integrity sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==
-  dependencies:
-    cliui "^4.0.0"
-    decamelize "^1.1.1"
-    find-up "^2.1.0"
-    get-caller-file "^1.0.1"
-    os-locale "^3.1.0"
-    require-directory "^2.1.1"
-    require-main-filename "^1.0.1"
-    set-blocking "^2.0.0"
-    string-width "^2.0.0"
-    which-module "^2.0.0"
-    y18n "^3.2.1"
-    yargs-parser "^9.0.2"
-
 yargs@^13.2.2, yargs@^13.3.2:
   version "13.3.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"