Browse Source

Remaining issues

Leszek Wiesner 4 years ago
parent
commit
2e67b25e16
31 changed files with 162 additions and 155 deletions
  1. 2 1
      pioneer/.eslintrc.js
  2. 4 4
      pioneer/packages/app-staking/src/Overview/index.tsx
  3. 1 1
      pioneer/packages/apps/src/SideBar/index.tsx
  4. 1 1
      pioneer/packages/joy-help/src/Help.tsx
  5. 8 5
      pioneer/packages/joy-media/src/DiscoveryProvider.tsx
  6. 4 7
      pioneer/packages/joy-media/src/TransportContext.tsx
  7. 5 4
      pioneer/packages/joy-media/src/Upload.tsx
  8. 1 1
      pioneer/packages/joy-media/src/channels/ChannelPreview.tsx
  9. 2 0
      pioneer/packages/joy-media/src/music/ReorderableTracks.tsx
  10. 1 1
      pioneer/packages/joy-media/src/transport.ts
  11. 1 1
      pioneer/packages/joy-media/src/video/PlayVideo.tsx
  12. 1 1
      pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx
  13. 1 1
      pioneer/packages/joy-proposals/src/forms/SetContentWorkingGroupMintCapForm.tsx
  14. 1 1
      pioneer/packages/joy-proposals/src/forms/errorHandling.ts
  15. 1 1
      pioneer/packages/joy-proposals/src/validationSchema.ts
  16. 1 0
      pioneer/packages/joy-roles/jest.config.js
  17. 8 5
      pioneer/packages/joy-roles/src/classifiers.spec.ts
  18. 1 0
      pioneer/packages/joy-roles/src/flows/apply.controller.tsx
  19. 6 9
      pioneer/packages/joy-roles/src/flows/apply.tsx
  20. 3 3
      pioneer/packages/joy-roles/src/tabs/MyRoles.tsx
  21. 2 2
      pioneer/packages/joy-roles/src/tabs/Opportunities.tsx
  22. 3 0
      pioneer/packages/joy-roles/src/transport.mock.ts
  23. 83 88
      pioneer/packages/joy-roles/src/transport.substrate.ts
  24. 0 4
      pioneer/packages/joy-utils/src/Controller.tsx
  25. 1 1
      pioneer/packages/joy-utils/src/Loadable.tsx
  26. 6 3
      pioneer/packages/joy-utils/src/MyAccountContext.tsx
  27. 5 4
      pioneer/packages/joy-utils/src/Transport.ts
  28. 1 0
      pioneer/packages/joy-utils/src/index.ts
  29. 2 0
      pioneer/packages/joy-utils/src/memo/MemoView.tsx
  30. 2 2
      pioneer/packages/joy-utils/src/memoize.ts
  31. 4 4
      pioneer/packages/joy-utils/src/transport/proposals.ts

+ 2 - 1
pioneer/.eslintrc.js

@@ -14,6 +14,7 @@ module.exports = {
     '@typescript-eslint/no-explicit-any': 'off',
     '@typescript-eslint/camelcase': 'off',
     'react/prop-types': 'off',
-    'new-cap': 'off'
+    'new-cap': 'off',
+    '@typescript-eslint/interface-name-prefix': 'off'
   }
 };

+ 4 - 4
pioneer/packages/app-staking/src/Overview/index.tsx

@@ -29,10 +29,10 @@ export default function Overview ({
     validators &&
       setNext(
         isSubstrateV2
-          ? // this is a V2 node currentValidators is a list of stashes
-          allStashes.filter((address): boolean => !validators.includes(address as any))
-          : // this is a V1 node currentValidators is a list of controllers
-          allControllers.filter((address): boolean => !validators.includes(address as any))
+          // this is a V2 node currentValidators is a list of stashes
+          ? allStashes.filter((address): boolean => !validators.includes(address as any))
+          // this is a V1 node currentValidators is a list of controllers
+          : allControllers.filter((address): boolean => !validators.includes(address as any))
       );
   }, [allControllers, allStashes, validators]);
 

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

@@ -36,7 +36,7 @@ type OuterLinkProps = {
 export function OuterLink ({ url, title, icon = 'external alternate' }: OuterLinkProps) {
   return (
     <Menu.Item className="apps--SideBar-Item">
-      <a className="apps--SideBar-Item-NavLink" href={url} target="_blank">
+      <a className="apps--SideBar-Item-NavLink" href={url} target="_blank" rel="noopener noreferrer">
         <Icon name={icon} />
         <span className="text">{title}</span>
       </a>

+ 1 - 1
pioneer/packages/joy-help/src/Help.tsx

@@ -32,7 +32,7 @@ const renderMemo = (accId: string) => {
 export const Component = (_props: Props) => {
   return (<>
     <div style={{ marginBottom: '1rem' }}>
-      Visit our <a href='https://github.com/Joystream/helpdesk' target='_blank'>helpdesk</a>{' '}
+      Visit our <a href='https://github.com/Joystream/helpdesk' target='_blank' rel="noopener noreferrer">helpdesk</a>{' '}
       for instructions and guides to get started!
     </div>
     <Grid divided='vertically'>

+ 8 - 5
pioneer/packages/joy-media/src/DiscoveryProvider.tsx

@@ -1,7 +1,6 @@
 import React, { useState, useEffect, useContext, createContext } from 'react';
 import { Message } from 'semantic-ui-react';
 import axios, { CancelToken } from 'axios';
-import { parse as parseUrl } from 'url';
 
 import { AccountId } from '@polkadot/types/interfaces';
 import { Vec } from '@polkadot/types';
@@ -26,7 +25,7 @@ export type DiscoveryProviderProps = {
 
 // return string Url with last `/` removed
 function normalizeUrl (url: string | Url): string {
-  const st = new String(url);
+  const st: string = url.toString();
   if (st.endsWith('/')) {
     return st.substring(0, st.length - 1);
   }
@@ -47,12 +46,16 @@ function newDiscoveryProvider ({ bootstrapNodes }: BootstrapNodes): DiscoveryPro
 
     let stat = stats.get(providerKey);
 
-    if (!stat || (stat && (Date.now() > (stat.resolvedAt + (10 * 60 * 1000))))) {
-      for (let n = 0; bootstrapNodes && n < bootstrapNodes.length; n++) {
+    if (
+      (!stat || (stat && (Date.now() > (stat.resolvedAt + (10 * 60 * 1000))))) &&
+      bootstrapNodes
+    ) {
+      for (let n = 0; n < bootstrapNodes.length; n++) {
         const discoveryUrl = normalizeUrl(bootstrapNodes[n]);
 
         try {
-          parseUrl(discoveryUrl);
+          // eslint-disable-next-line no-new
+          new URL(discoveryUrl);
         } catch (err) {
           continue;
         }

+ 4 - 7
pioneer/packages/joy-media/src/TransportContext.tsx

@@ -21,13 +21,10 @@ export const SubstrateTransportProvider = (props: React.PropsWithChildren<{}>) =
   const [loaded, setLoaded] = useState<boolean>();
 
   useEffect(() => {
-    const load = async () => {
-      if (!loaded && api && api.isApiReady) {
-        setTransport(new SubstrateTransport(api));
-        setLoaded(true);
-      }
-    };
-    load();
+    if (!loaded && api && api.isApiReady) {
+      setTransport(new SubstrateTransport(api));
+      setLoaded(true);
+    }
   }, [loaded]);
 
   if (!transport) {

+ 5 - 4
pioneer/packages/joy-media/src/Upload.tsx

@@ -195,7 +195,7 @@ class Component extends React.PureComponent<Props, State> {
     </div>;
   }
 
-  private onFileSelected = async (file: File) => {
+  private onFileSelected = (file: File) => {
     if (!file.size) {
       this.setState({ error: 'You cannot upload an empty file.' });
     } else if (file.size > MAX_FILE_SIZE_BYTES) {
@@ -257,8 +257,9 @@ class Component extends React.PureComponent<Props, State> {
 
     const { api } = this.props;
     const { newContentId } = this.state;
+    let dataObject: Option<DataObject>;
     try {
-      var dataObject = await api.query.dataDirectory.dataObjectByContentId(newContentId) as Option<DataObject>;
+      dataObject = await api.query.dataDirectory.dataObjectByContentId(newContentId) as Option<DataObject>;
     } catch (err) {
       this.setState({
         error: err,
@@ -312,9 +313,9 @@ class Component extends React.PureComponent<Props, State> {
     };
 
     const { discoveryProvider } = this.props;
-
+    let url: string;
     try {
-      var url = await discoveryProvider.resolveAssetEndpoint(storageProvider, contentId, cancelSource.token);
+      url = await discoveryProvider.resolveAssetEndpoint(storageProvider, contentId, cancelSource.token);
     } catch (err) {
       return this.setState({
         error: new Error(`Failed to contact storage provider: ${err.message}`),

+ 1 - 1
pioneer/packages/joy-media/src/channels/ChannelPreview.tsx

@@ -26,7 +26,7 @@ export const ChannelPreview = (props: ChannelPreviewProps) => {
   let icon: 'music' | 'film' | undefined;
 
   if (isMusicChannel(channel)) {
-    subtitle = 'Music channel',
+    subtitle = 'Music channel';
     icon = 'music';
   } else if (isVideoChannel(channel)) {
     subtitle = 'Video channel';

+ 2 - 0
pioneer/packages/joy-media/src/music/ReorderableTracks.tsx

@@ -51,6 +51,7 @@ export const ReorderableTracks = (props: Props) => {
         {(provided, _snapshot) => (
           <div
             {...provided.droppableProps}
+            // eslint-disable-next-line @typescript-eslint/unbound-method
             ref={provided.innerRef}
             className='JoyListOfPreviews'
           >
@@ -58,6 +59,7 @@ export const ReorderableTracks = (props: Props) => {
               <Draggable key={item.id} draggableId={item.id} index={index}>
                 {(provided, snapshot) => (
                   <div
+                    // eslint-disable-next-line @typescript-eslint/unbound-method
                     ref={provided.innerRef}
                     {...provided.draggableProps}
                     {...provided.dragHandleProps}

+ 1 - 1
pioneer/packages/joy-media/src/transport.ts

@@ -79,7 +79,7 @@ export abstract class MediaTransport extends Transport {
     console.info(`Close transport session no. ${this.sessionId}`);
   }
 
-  async session<R> (operation: () => R): Promise<R> {
+  async session<R> (operation: () => R | Promise<R>): Promise<R> {
     if (typeof operation !== 'function') {
       throw new Error('Operation is not a function');
     }

+ 1 - 1
pioneer/packages/joy-media/src/video/PlayVideo.tsx

@@ -56,7 +56,7 @@ export function PlayVideo (props: PlayVideoProps) {
   const printLinks = (links?: string[]) => {
     return (links || []).map((x, i) =>
       <div key={`EntityLink-${i}`}>
-        <a href={encodeURI(x)} target='_blank' rel='nofollow'>{x}</a>
+        <a href={encodeURI(x)} target='_blank' rel='nofollow noopener noreferrer'>{x}</a>
       </div>
     );
   };

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

@@ -67,7 +67,7 @@ export function getExtendedStatus (proposal: ParsedProposal, bestNumber: BlockNu
         executedAtBlock = finalizedAtBlock + gracePeriod;
         if (approvedStatus === 'ExecutionFailed') {
           const executionFailedStatus = proposalStatus.Approved.ExecutionFailed as ExecutionFailedStatus;
-          executionFailReason = new Buffer(executionFailedStatus.error.toString().replace('0x', ''), 'hex').toString();
+          executionFailReason = Buffer.from(executionFailedStatus.error.toString().replace('0x', ''), 'hex').toString();
         }
       }
     }

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

@@ -1,5 +1,5 @@
 import React from 'react';
-import { default as MintCapacityForm } from './MintCapacityForm';
+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';

+ 1 - 1
pioneer/packages/joy-proposals/src/forms/errorHandling.ts

@@ -31,5 +31,5 @@ export function getFormErrorLabelsProps<ValuesT> (
     errorStates[fieldName] = getErrorLabelProps<ValuesT>(errors, touched, fieldName);
   }
 
-  return <FormErrorLabelsProps<ValuesT>> errorStates;
+  return errorStates as FormErrorLabelsProps<ValuesT>;
 }

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

@@ -242,7 +242,7 @@ const Validation: ValidationType = {
       .required('You need to specify an amount of tokens.'),
     destinationAccount: Yup.string()
       .required('Select a destination account!')
-      .test('address-test', '${account} is not a valid address.', account => !!checkAddress(account, 5))
+      .test('address-test', 'Invalid account address.', account => checkAddress(account, 5)[0])
   },
   SetLead: {
     workingGroupLead: Yup.string().required('Select a proposed lead!')

+ 1 - 0
pioneer/packages/joy-roles/jest.config.js

@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
 const { pathsToModuleNameMapper } = require('ts-jest/utils');
 // In the following statement, replace `./tsconfig` with the path to your `tsconfig` file
 // which contains the path mapping (ie the `compilerOptions.paths` option):

+ 8 - 5
pioneer/packages/joy-roles/src/classifiers.spec.ts

@@ -14,7 +14,7 @@ import {
   classifyOpeningStage, OpeningStageClassification
 } from './classifiers';
 
-class mockBlockQueryer {
+class MockBlockQueryer {
   hash: string
   timestamp: Date
 
@@ -26,14 +26,17 @@ class mockBlockQueryer {
     this.timestamp = timestamp;
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async blockHash (height: number): Promise<string> {
     return this.hash;
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async blockTimestamp (height: number): Promise<Date> {
     return this.timestamp;
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async expectedBlockTime (): Promise<number> {
     return 6;
   }
@@ -68,7 +71,7 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
           role_staking_policy: new Option(StakingPolicy),
           human_readable_text: new Text()
         }),
-        queryer: new mockBlockQueryer('somehash', now)
+        queryer: new MockBlockQueryer('somehash', now)
       },
       output: {
         state: OpeningState.WaitingToBegin,
@@ -97,7 +100,7 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
           role_staking_policy: new Option(StakingPolicy),
           human_readable_text: new Text()
         }),
-        queryer: new mockBlockQueryer('somehash', now)
+        queryer: new MockBlockQueryer('somehash', now)
       },
       output: {
         state: OpeningState.AcceptingApplications,
@@ -127,7 +130,7 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
           role_staking_policy: new Option(StakingPolicy),
           human_readable_text: new Text()
         }),
-        queryer: new mockBlockQueryer('somehash', now)
+        queryer: new MockBlockQueryer('somehash', now)
       },
       output: {
         state: OpeningState.InReview,
@@ -165,7 +168,7 @@ describe('hiring.Opening-> OpeningStageClassification', (): void => {
           role_staking_policy: new Option(StakingPolicy),
           human_readable_text: new Text(),
         }),
-        queryer: new mockBlockQueryer("somehash", now),
+        queryer: new MockBlockQueryer("somehash", now),
       },
       output: {
         state: OpeningState.Cancelled,

+ 1 - 0
pioneer/packages/joy-roles/src/flows/apply.controller.tsx

@@ -162,6 +162,7 @@ export class ApplyController extends Controller<State, ITransport> {
     this.dispatch();
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async prepareApplicationTransaction (
     applicationStake: Balance,
     roleStake: Balance,

+ 6 - 9
pioneer/packages/joy-roles/src/flows/apply.tsx

@@ -27,9 +27,6 @@ import {
   Table
 } from 'semantic-ui-react';
 
-// @ts-ignore
-import { Slider } from 'react-semantic-ui-range';
-
 import Identicon from '@polkadot/react-identicon';
 import AccountId from '@polkadot/types/primitive/Generic/AccountId';
 
@@ -750,11 +747,11 @@ function questionHash (section: QuestionSection, question: QuestionField): strin
   return section.title + '|' + question.title;
 }
 
-interface finalDataMap {
-  [k: string]: finalDataMap;
+interface FinalDataMap {
+  [k: string]: FinalDataMap;
 }
 
-function applicationDetailsToObject (input: ApplicationDetails, data: finalDataMap): any {
+function applicationDetailsToObject (input: ApplicationDetails, data: FinalDataMap): any {
   const output: any = {};
   if (!input.sections) {
     return {};
@@ -771,11 +768,11 @@ function applicationDetailsToObject (input: ApplicationDetails, data: finalDataM
   return output;
 }
 
-interface questionDataMap {
+interface QuestionDataMap {
   [k: string]: any;
 }
 
-function applicationDetailsToDataObject (input: ApplicationDetails, data: questionDataMap): any {
+function applicationDetailsToDataObject (input: ApplicationDetails, data: QuestionDataMap): any {
   const output: any = {};
   if (!input.sections) {
     return {};
@@ -811,7 +808,7 @@ export type ApplicationDetailsStageProps = {
 }
 
 export function ApplicationDetailsStage (props: ApplicationDetailsStageProps & StageTransitionProps) {
-  const initialForm = applicationDetailsToObject(props.applicationDetails, props.data as finalDataMap);
+  const initialForm = applicationDetailsToObject(props.applicationDetails, props.data as FinalDataMap);
 
   const [data, setData] = useReducer(questionReducer, initialForm);
   const [completed, setCompleted] = useState(false);

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

@@ -89,19 +89,19 @@ function CTAButton (props: CTA) {
   );
 }
 
-interface nameAndURL {
+interface NameAndURL {
   name: string;
   url?: string;
 }
 
-function RoleName (props: nameAndURL) {
+function RoleName (props: NameAndURL) {
   if (typeof props.url !== 'undefined') {
     return <Link to={props.url}>{props.name}</Link>;
   }
   return <span>{props.name}</span>;
 }
 
-export interface ActiveRole extends nameAndURL {
+export interface ActiveRole extends NameAndURL {
   curatorId: CuratorId;
   reward: Balance;
   stake: Balance;

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

@@ -115,7 +115,7 @@ function hasAnyStake (props: StakeRequirementProps): boolean {
   return props.requiredApplicationStake.anyRequirement() || props.requiredRoleStake.anyRequirement();
 }
 
-class messageState {
+class MessageState {
   positive = true
   warning = false
   negative = false
@@ -280,7 +280,7 @@ function OpeningBodyCTAView (props: OpeningBodyCTAProps) {
 
 export function OpeningBodyApplicationsStatus (props: OpeningStakeAndApplicationStatus) {
   const impossible = applicationImpossible(props);
-  const msg = new messageState();
+  const msg = new MessageState();
   if (impossible) {
     msg.setNegative();
   } else if (applicationPossibleWithIncresedStake(props)) {

+ 3 - 0
pioneer/packages/joy-roles/src/transport.mock.ts

@@ -350,6 +350,7 @@ export class Transport extends TransportBase implements ITransport {
     });
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async openingApplications (): Promise<OpeningApplication[]> {
     return [{
       id: 1,
@@ -422,6 +423,7 @@ export class Transport extends TransportBase implements ITransport {
     }];
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async myCurationGroupRoles (): Promise<ActiveRole[]> {
     return [
       {
@@ -438,6 +440,7 @@ export class Transport extends TransportBase implements ITransport {
     return new Observable<ActiveRole[]>(observer => { /* do nothing */ });
   }
 
+  // eslint-disable-next-line @typescript-eslint/require-await
   async applyToCuratorOpening (
     id: number,
     roleAccountName: string,

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

@@ -141,33 +141,31 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   protected async groupMember (id: CuratorId, curator: IRoleAccounter): Promise<GroupMember> {
-    return new Promise<GroupMember>(async (resolve, reject) => {
-      const roleAccount = curator.role_account;
-      const memberId = await this.memberIdFromCuratorId(id);
+    const roleAccount = curator.role_account;
+    const memberId = await this.memberIdFromCuratorId(id);
 
-      const profile = await this.cachedApi.query.members.memberProfile(memberId) as Option<Profile>;
-      if (profile.isNone) {
-        reject('no profile found');
-      }
+    const profile = await this.cachedApi.query.members.memberProfile(memberId) as Option<Profile>;
+    if (profile.isNone) {
+      throw new Error('no profile found');
+    }
 
-      let stakeValue: Balance = new u128(0);
-      if (curator.role_stake_profile && curator.role_stake_profile.isSome) {
-        stakeValue = await this.curatorStake(curator.role_stake_profile.unwrap());
-      }
+    let stakeValue: Balance = new u128(0);
+    if (curator.role_stake_profile && curator.role_stake_profile.isSome) {
+      stakeValue = await this.curatorStake(curator.role_stake_profile.unwrap());
+    }
 
-      let earnedValue: Balance = new u128(0);
-      if (curator.reward_relationship && curator.reward_relationship.isSome) {
-        earnedValue = await this.curatorTotalReward(curator.reward_relationship.unwrap());
-      }
+    let earnedValue: Balance = new u128(0);
+    if (curator.reward_relationship && curator.reward_relationship.isSome) {
+      earnedValue = await this.curatorTotalReward(curator.reward_relationship.unwrap());
+    }
 
-      resolve({
-        roleAccount,
-        memberId,
-        profile: profile.unwrap(),
-        title: 'Content curator',
-        stake: stakeValue,
-        earned: earnedValue
-      });
+    return ({
+      roleAccount,
+      memberId,
+      profile: profile.unwrap(),
+      title: 'Content curator',
+      stake: stakeValue,
+      earned: earnedValue
     });
   }
 
@@ -322,40 +320,38 @@ export class Transport extends TransportBase implements ITransport {
   }
 
   async curationGroupOpening (id: number): Promise<WorkingGroupOpening> {
-    return new Promise<WorkingGroupOpening>(async (resolve, reject) => {
-      const nextId = (await this.cachedApi.query.contentWorkingGroup.nextCuratorOpeningId() as u32).toNumber();
-      if (id < 0 || id >= nextId) {
-        reject('invalid id');
-      }
-
-      const curatorOpening = new SingleLinkedMapEntry<CuratorOpening>(
-        CuratorOpening,
-        await this.cachedApi.query.contentWorkingGroup.curatorOpeningById(id)
-      );
+    const nextId = (await this.cachedApi.query.contentWorkingGroup.nextCuratorOpeningId() as u32).toNumber();
+    if (id < 0 || id >= nextId) {
+      throw new Error('invalid id');
+    }
 
-      const opening = await this.opening(
-        curatorOpening.value.getField<OpeningId>('opening_id').toNumber()
-      );
+    const curatorOpening = new SingleLinkedMapEntry<CuratorOpening>(
+      CuratorOpening,
+      await this.cachedApi.query.contentWorkingGroup.curatorOpeningById(id)
+    );
 
-      const applications = await this.curatorOpeningApplications(id);
-      const stakes = classifyOpeningStakes(opening);
+    const opening = await this.opening(
+      curatorOpening.value.getField<OpeningId>('opening_id').toNumber()
+    );
 
-      resolve({
-        opening: opening,
-        meta: {
-          id: id.toString(),
-          group: WorkingGroups.ContentCurators
-        },
-        stage: await classifyOpeningStage(this, opening),
-        applications: {
-          numberOfApplications: applications.length,
-          maxNumberOfApplications: opening.max_applicants,
-          requiredApplicationStake: stakes.application,
-          requiredRoleStake: stakes.role,
-          defactoMinimumStake: new u128(0)
-        },
+    const applications = await this.curatorOpeningApplications(id);
+    const stakes = classifyOpeningStakes(opening);
+
+    return ({
+      opening: opening,
+      meta: {
+        id: id.toString(),
+        group: WorkingGroups.ContentCurators
+      },
+      stage: await classifyOpeningStage(this, opening),
+      applications: {
+        numberOfApplications: applications.length,
+        maxNumberOfApplications: opening.max_applicants,
+        requiredApplicationStake: stakes.application,
+        requiredRoleStake: stakes.role,
         defactoMinimumStake: new u128(0)
-      });
+      },
+      defactoMinimumStake: new u128(0)
     });
   }
 
@@ -385,8 +381,7 @@ export class Transport extends TransportBase implements ITransport {
 
   expectedBlockTime (): Promise<number> {
     return this.promise<number>(
-      // @ts-ignore
-      this.api.consts.babe.expectedBlockTime.toNumber() / 1000
+      (this.api.consts.babe.expectedBlockTime as Moment).toNumber() / 1000
     );
   }
 
@@ -576,48 +571,48 @@ export class Transport extends TransportBase implements ITransport {
     return status.account as string;
   }
 
-  async applyToCuratorOpening (
+  applyToCuratorOpening (
     id: number,
     roleAccountName: string,
     sourceAccount: string,
     appStake: Balance,
     roleStake: Balance,
     applicationText: string): Promise<number> {
-    return new Promise<number>(async (resolve, reject) => {
-      const membershipIds = (
-        await this.cachedApi.query.members.memberIdsByControllerAccountId(sourceAccount)
-      ) as Vec<MemberId>;
-      if (membershipIds.length === 0) {
-        reject('No membship ID associated with this address');
-      }
+    return new Promise<number>((resolve, reject) => {
+      (this.cachedApi.query.members.memberIdsByControllerAccountId(sourceAccount) as Promise<Vec<MemberId>>)
+        .then(membershipIds => {
+          if (membershipIds.length === 0) {
+            reject(new Error('No membship ID associated with this address'));
+          }
 
-      const roleAccount = this.generateRoleAccount(roleAccountName);
-      if (!roleAccount) {
-        reject('failed to create role account');
-      }
-      const tx = this.api.tx.contentWorkingGroup.applyOnCuratorOpening(
-        membershipIds[0],
-        new u32(id),
-        new GenericAccountId(roleAccount as string),
-        roleStake.eq(Zero) ? null : roleStake,
-        appStake.eq(Zero) ? null : appStake,
-        applicationText
-      ) as unknown as SubmittableExtrinsic;
-
-      const txFailedCb = () => {
-        reject('transaction failed');
-      };
+          const roleAccount = this.generateRoleAccount(roleAccountName);
+          if (!roleAccount) {
+            reject(new Error('failed to create role account'));
+          }
+          const tx = this.api.tx.contentWorkingGroup.applyOnCuratorOpening(
+            membershipIds[0],
+            new u32(id),
+            new GenericAccountId(roleAccount as string),
+            roleStake.eq(Zero) ? null : roleStake,
+            appStake.eq(Zero) ? null : appStake,
+            applicationText
+          ) as unknown as SubmittableExtrinsic;
+
+          const txFailedCb = () => {
+            reject(new Error('transaction failed'));
+          };
 
-      const txSuccessCb = () => {
-        resolve(1);
-      };
+          const txSuccessCb = () => {
+            resolve(1);
+          };
 
-      this.queueExtrinsic({
-        accountId: sourceAccount,
-        extrinsic: tx,
-        txFailedCb,
-        txSuccessCb
-      });
+          this.queueExtrinsic({
+            accountId: sourceAccount,
+            extrinsic: tx,
+            txFailedCb,
+            txSuccessCb
+          });
+        });
     });
   }
 

+ 0 - 4
pioneer/packages/joy-utils/src/Controller.tsx

@@ -5,10 +5,6 @@ type errorProps = {
 }
 
 export class Controller<S, T> extends Observable<S & errorProps, T> {
-  constructor (transport: T, initialState: S & { hasError?: boolean }) {
-    super(transport, initialState);
-  }
-
   onError (desc: any) {
     this.state._hasError = true;
     console.log(desc);

+ 1 - 1
pioneer/packages/joy-utils/src/Loadable.tsx

@@ -9,7 +9,7 @@ export function Loadable<P extends {[index: string]: any}> (required: string[],
     }
 
     for (const requirement of required) {
-      if (!props.hasOwnProperty(requirement) || typeof props[requirement] === 'undefined') {
+      if (typeof props[requirement] === 'undefined') {
         return loading;
       }
     }

+ 6 - 3
pioneer/packages/joy-utils/src/MyAccountContext.tsx

@@ -29,12 +29,13 @@ function reducer (state: MyAccountState, action: MyAccountAction): MyAccountStat
   let address: string | undefined;
 
   switch (action.type) {
-    case 'reload':
+    case 'reload': {
       address = readMyAddress();
       console.log('Reload my address:', address);
       return { ...state, address, inited: true };
+    }
 
-    case 'set':
+    case 'set': {
       address = action.address;
       if (address !== state.address) {
         if (address) {
@@ -46,14 +47,16 @@ function reducer (state: MyAccountState, action: MyAccountAction): MyAccountStat
         }
       }
       return state;
+    }
 
-    case 'forget':
+    case 'forget': {
       address = action.address;
       const isMyAddress = address && address === readMyAddress();
       if (!address || isMyAddress) {
         return forget();
       }
       return state;
+    }
 
     default:
       throw new Error('No action type provided');

+ 5 - 4
pioneer/packages/joy-utils/src/Transport.ts

@@ -1,10 +1,11 @@
 export abstract class Transport {
-  protected async promise<T> (value: T, timeout?: number): Promise<T> {
-    return new Promise<T>(async (resolve, reject) => {
+  protected promise<T> (value: T, timeout?: number): Promise<T> {
+    return new Promise<T>((resolve, reject) => {
       if (timeout) {
-        await new Promise(r => setTimeout(r, timeout));
+        (new Promise(resolve => setTimeout(resolve, timeout))).then(() => resolve(value));
+      } else {
+        resolve(value);
       }
-      resolve(value);
     });
   }
 }

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

@@ -102,6 +102,7 @@ export function findNameByAddress (address: string): string | undefined {
     try {
       keyring_address = keyring.getAddress(address);
     } catch (error) {
+      // do nothing
     }
   }
   return keyring_address ? keyring_address.meta.name : undefined;

+ 2 - 0
pioneer/packages/joy-utils/src/memo/MemoView.tsx

@@ -8,7 +8,9 @@ import { nonEmptyStr } from '../index';
 import './Memo.css';
 import { Link } from 'react-router-dom';
 
+// eslint-disable-next-line @typescript-eslint/no-var-requires
 const remark = require('remark');
+// eslint-disable-next-line @typescript-eslint/no-var-requires
 const strip = require('strip-markdown');
 const mdStripper = remark().use(strip);
 

+ 2 - 2
pioneer/packages/joy-utils/src/memoize.ts

@@ -10,7 +10,7 @@ function getNewFunction (originalMethod: () => void) {
     let returnedValue: any;
 
     if (args.length > 0) {
-      if (!this.hasOwnProperty(propMapName)) {
+      if (!Object.prototype.hasOwnProperty.call(this, propMapName)) {
         Object.defineProperty(this, propMapName, {
           configurable: false,
           enumerable: false,
@@ -28,7 +28,7 @@ function getNewFunction (originalMethod: () => void) {
         myMap.set(hashKey, returnedValue);
       }
     } else {
-      if (this.hasOwnProperty(propValName)) {
+      if (Object.prototype.hasOwnProperty.call(this, propValName)) {
         returnedValue = this[propValName];
       } else {
         returnedValue = originalMethod.apply(this, args as []);

+ 4 - 4
pioneer/packages/joy-utils/src/transport/proposals.ts

@@ -50,8 +50,8 @@ export default class ProposalsTransport extends BaseTransport {
     return this.proposalsCodex.proposalDetailsByProposalId(id);
   }
 
-  async cancellationFee (): Promise<number> {
-    return ((await this.api.consts.proposalsEngine.cancellationFee) as BalanceOf).toNumber();
+  cancellationFee (): number {
+    return (this.api.consts.proposalsEngine.cancellationFee as BalanceOf).toNumber();
   }
 
   async proposalById (id: ProposalId): Promise<ParsedProposal> {
@@ -70,7 +70,7 @@ export default class ProposalsTransport extends BaseTransport {
     };
     const createdAtBlock = rawProposal.createdAt;
     const createdAt = await this.chainT.blockTimestamp(createdAtBlock.toNumber());
-    const cancellationFee = await this.cancellationFee();
+    const cancellationFee = this.cancellationFee();
 
     return {
       id,
@@ -158,7 +158,7 @@ export default class ProposalsTransport extends BaseTransport {
     const stake = calculateStake(type);
     const meta = calculateMetaFromType(type);
     // Currently it's same for all types, but this will change soon
-    const cancellationFee = await this.cancellationFee();
+    const cancellationFee = this.cancellationFee();
     return {
       type,
       votingPeriod,