Browse Source

Linter: Manual fixes

Leszek Wiesner 4 years ago
parent
commit
6d90afa998
32 changed files with 287 additions and 317 deletions
  1. 2 0
      pioneer/.eslintrc.js
  2. 0 2
      pioneer/package.json
  3. 2 4
      pioneer/packages/joy-roles/src/elements.tsx
  4. 5 4
      pioneer/packages/joy-roles/src/flows/apply.controller.tsx
  5. 2 2
      pioneer/packages/joy-roles/src/flows/apply.elements.stories.tsx
  6. 5 1
      pioneer/packages/joy-roles/src/flows/apply.stories.tsx
  7. 53 48
      pioneer/packages/joy-roles/src/flows/apply.tsx
  8. 10 8
      pioneer/packages/joy-roles/src/index.tsx
  9. 19 23
      pioneer/packages/joy-roles/src/openingStateMarkup.tsx
  10. 8 7
      pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx
  11. 2 2
      pioneer/packages/joy-roles/src/tabs/MyRoles.controller.tsx
  12. 11 8
      pioneer/packages/joy-roles/src/tabs/MyRoles.tsx
  13. 3 8
      pioneer/packages/joy-roles/src/tabs/Opportunities.controller.tsx
  14. 3 3
      pioneer/packages/joy-roles/src/tabs/Opportunities.stories.tsx
  15. 2 2
      pioneer/packages/joy-roles/src/tabs/Opportunities.tsx
  16. 14 18
      pioneer/packages/joy-roles/src/tabs/Opportunity.controller.tsx
  17. 13 8
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx
  18. 3 2
      pioneer/packages/joy-roles/src/transport.substrate.ts
  19. 3 2
      pioneer/packages/joy-utils/src/functions/accounts.ts
  20. 2 0
      pioneer/packages/joy-utils/src/functions/misc.ts
  21. 3 1
      pioneer/packages/joy-utils/src/react/helpers/Observable.ts
  22. 0 1
      pioneer/packages/joy-utils/src/react/helpers/index.ts
  23. 0 53
      pioneer/packages/joy-utils/src/react/helpers/memoize.ts
  24. 21 17
      pioneer/packages/joy-utils/src/react/hocs/View.tsx
  25. 3 1
      pioneer/packages/joy-utils/src/transport/mock/base.ts
  26. 3 0
      pioneer/packages/joy-utils/src/types/workingGroups.ts
  27. 1 0
      types/.prettierignore
  28. 4 2
      types/package.json
  29. 3 3
      types/src/hiring/schemas/role.schema.json
  30. 37 36
      types/src/hiring/schemas/role.schema.typings.ts
  31. 7 4
      types/src/index.ts
  32. 43 47
      yarn.lock

+ 2 - 0
pioneer/.eslintrc.js

@@ -27,6 +27,8 @@ module.exports = {
     'react/jsx-max-props-per-line': 'off',
     'sort-destructure-keys/sort-destructure-keys': 'off',
     '@typescript-eslint/unbound-method': 'warn', // Doesn't work well with our version of Formik, see: https://github.com/formium/formik/issues/2589
+    'react-hooks/exhaustive-deps': 'warn', // Causes more issues than it solves currently
+    'no-void': 'off' // Otherwise we cannot mark unhandles promises
   },
   // isolate pioneer from monorepo eslint rules
   root: true

+ 0 - 2
pioneer/package.json

@@ -25,7 +25,6 @@
     "test": "echo \"skipping tests\"",
     "vanitygen": "node packages/app-accounts/scripts/vanitygen.js",
     "start": "yarn clean && cd packages/apps && webpack --config webpack.config.js",
-    "generate-schemas": "json2ts -i packages/joy-types/src/schemas/role.schema.json -o packages/joy-types/src/schemas/role.schema.ts",
     "build-storybook": "build-storybook -c .storybook",
     "storybook": "start-storybook -s ./packages/apps/public -p 3001"
   },
@@ -74,7 +73,6 @@
     "@storybook/addon-actions": "^5.2.5",
     "@storybook/addon-console": "^1.2.1",
     "@storybook/react": "^5.2.5",
-    "json-schema-to-typescript": "^7.1.0",
     "storybook-react-router": "^1.0.8",
     "typescript": "^3.9.7",
     "eslint-plugin-header": "^3.0.0",

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

@@ -201,8 +201,6 @@ type CountdownProps = {
 }
 
 export function Countdown (props: CountdownProps) {
-  let interval = -1;
-
   const [days, setDays] = useState<number | undefined>(undefined);
   const [hours, setHours] = useState<number | undefined>(undefined);
   const [minutes, setMinutes] = useState<number | undefined>(undefined);
@@ -219,9 +217,9 @@ export function Countdown (props: CountdownProps) {
     setSeconds(d.seconds());
   };
 
-  interval = window.setInterval(update, 1000);
-
   useEffect(() => {
+    const interval = window.setInterval(update, 1000);
+
     update();
 
     return () => {

+ 5 - 4
pioneer/packages/joy-roles/src/flows/apply.controller.tsx

@@ -17,6 +17,7 @@ import { OpeningStakeAndApplicationStatus } from '../tabs/Opportunities';
 import { Min, Step, Sum } from '../balances';
 import { WorkingGroups, AvailableGroups } from '../working_groups';
 import { createMock } from '@joystream/types';
+import { ApplicationDetailsData } from '@polkadot/joy-utils/types/workingGroups';
 
 type State = {
   // Input data from state
@@ -30,7 +31,7 @@ type State = {
   // Data captured from form
   applicationStake: Balance;
   roleStake: Balance;
-  appDetails: any;
+  appDetails: ApplicationDetailsData;
   txKeyAddress: AccountId;
   activeStep: ProgressSteps;
   txInProgress: boolean;
@@ -153,12 +154,12 @@ export class ApplyController extends Controller<State, ITransport> {
     this.dispatch();
   }
 
-  setAppDetails (v: any): void {
+  setAppDetails (v: ApplicationDetailsData): void {
     this.state.appDetails = v;
     this.dispatch();
   }
 
-  setTxKeyAddress (v: any): void {
+  setTxKeyAddress (v: AccountId): void {
     this.state.txKeyAddress = v;
     this.dispatch();
   }
@@ -232,7 +233,7 @@ export const ApplyView = View<ApplyController, State>(
   ({ state, controller, params }) => {
     useEffect(() => {
       controller.findOpening(params.get('id'), params.get('group'));
-    }, [params.get('id'), params.get('group')]);
+    }, [params]);
 
     return (
       <FlowModal

+ 2 - 2
pioneer/packages/joy-roles/src/flows/apply.elements.stories.tsx

@@ -1,5 +1,5 @@
-// TODO: FIXME: Remove the ts-nocheck and fix errors!
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// TODO: FIXME: Remove those and fix errors!
+/* eslint-disable */
 // @ts-nocheck
 import React, { useState } from 'react';
 import { number, object, withKnobs } from '@storybook/addon-knobs';

+ 5 - 1
pioneer/packages/joy-roles/src/flows/apply.stories.tsx

@@ -112,7 +112,11 @@ export const ApplicationSandbox = () => {
       ),
       defactoMinimumStake: new u128(0)
     },
-    creator: creator,
+    creator: {
+      membership: {
+        handle: text('Creator handle', 'ben', 'Role')
+      }
+    },
     transactionFee: new u128(number('Transaction fee', 499, moneySliderOptions, 'Application Tx')),
     keypairs: [
       {

+ 53 - 48
pioneer/packages/joy-roles/src/flows/apply.tsx

@@ -19,7 +19,9 @@ import { Accordion,
   Segment,
   SemanticICONS,
   Step,
-  Table } from 'semantic-ui-react';
+  Table,
+  InputOnChangeData,
+  TextAreaProps } from 'semantic-ui-react';
 
 import Identicon from '@polkadot/react-identicon';
 import AccountId from '@polkadot/types/generic/AccountId';
@@ -37,6 +39,7 @@ import { IStakeRequirement } from '../StakeRequirement';
 import { Loadable } from '@polkadot/joy-utils/react/hocs';
 import { Add } from '../balances';
 import { createMock } from '@joystream/types';
+import { ApplicationDetailsData, ApplicationQuestionAnswers } from '@polkadot/joy-utils/types/workingGroups';
 
 type accordionProps = {
   title: string;
@@ -56,7 +59,16 @@ function ModalAccordion (props: React.PropsWithChildren<accordionProps>) {
   );
 }
 
-function KeyPair ({ address, className, style, isUppercase, name, balance }: any): any {
+type KeyPairProps = {
+  address: string;
+  className?: string;
+  style?: React.CSSProperties;
+  isUppercase: boolean;
+  name: string;
+  balance: Balance;
+}
+
+function KeyPair ({ address, className, style, isUppercase, name, balance }: KeyPairProps) {
   return (
     <div
       className={['keypair', className].join(' ')}
@@ -193,7 +205,7 @@ export function StakeRankSelector (props: StakeRankSelectorProps) {
   const ticks = [];
 
   for (let i = 0; i < slotCount; i++) {
-    ticks.push(<div key={i} className='tick' style={{ width: (100 / slotCount) + '%' }}>{slotCount - i}</div>);
+    ticks.push(<div key={i} className='tick' style={{ width: `${(100 / slotCount)}%` }}>{slotCount - i}</div>);
   }
 
   let estimatedSlot = slotCount + 1;
@@ -485,10 +497,10 @@ export function ConfirmStakes2Up (props: ConfirmStakes2UpProps) {
   const ticks = [];
 
   for (let i = 0; i < slotCount; i++) {
-    ticks.push(<div key={i} className='tick' style={{ width: (100 / slotCount) + '%' }}>{i + 1}</div>);
+    ticks.push(<div key={i} className='tick' style={{ width: `${(100 / slotCount)}%` }}>{i + 1}</div>);
   }
 
-  const tickLabel = <div className='ui pointing below label' style={{ left: ((100 / slotCount) * (estimatedSlot - 1)) + '%' }}>
+  const tickLabel = <div className='ui pointing below label' style={{ left: `${((100 / slotCount) * (estimatedSlot - 1))}%` }}>
     Your rank
     <div className='detail'>{estimatedSlot}/{props.applications.maxNumberOfApplications}</div>
   </div>;
@@ -708,12 +720,10 @@ function questionHash (section: QuestionSection, question: QuestionField): strin
   return section.title + '|' + question.title;
 }
 
-interface FinalDataMap {
-  [k: string]: FinalDataMap;
-}
+interface AnswersByHash { [hash: string]: string; }
 
-function applicationDetailsToObject (input: ApplicationDetails, data: FinalDataMap): any {
-  const output: any = {};
+function applicationDetailsDataToAnswersByHash (input: ApplicationDetails, data: ApplicationDetailsData): AnswersByHash {
+  const output: AnswersByHash = {};
 
   if (!input.sections) {
     return {};
@@ -721,47 +731,42 @@ function applicationDetailsToObject (input: ApplicationDetails, data: FinalDataM
 
   input.sections.map((section) => {
     section.questions.map((question) => {
-      let value: any = '';
-
-      if (data[section.title] && data[section.title][question.title]) {
-        value = data[section.title][question.title];
-      }
+      const sectionAnswers = data[section.title];
+      const answer = (sectionAnswers && sectionAnswers[question.title]) || '';
 
-      output[questionHash(section, question)] = value;
+      output[questionHash(section, question)] = answer;
     });
   });
 
   return output;
 }
 
-interface QuestionDataMap {
-  [k: string]: any;
-}
-
-function applicationDetailsToDataObject (input: ApplicationDetails, data: QuestionDataMap): any {
-  const output: any = {};
+function answersByHashToApplicationDetailsData (input: ApplicationDetails, data: AnswersByHash): ApplicationDetailsData {
+  const output: ApplicationDetailsData = {};
 
   if (!input.sections) {
     return {};
   }
 
   input.sections.map((section) => {
-    output[section.title] = {};
+    const sectionAnswers: ApplicationQuestionAnswers = {};
+
     section.questions.map((question) => {
       const hash = questionHash(section, question);
 
-      output[section.title][question.title] = data[hash];
+      sectionAnswers[question.title] = data[hash];
     });
+    output[section.title] = sectionAnswers;
   });
 
   return output;
 }
 
-function questionReducer (state: any, action: any) {
+function answersReducer (state: AnswersByHash, action: { key: string, value: string }) {
   return { ...state, [action.key]: action.value };
 }
 
-function questionFieldValueIsValid (question: QuestionField, value: any): boolean {
+function questionFieldValueIsValid (question: QuestionField, value: string): boolean {
   switch (question.type) {
     case 'text':
     case 'text area':
@@ -773,42 +778,42 @@ function questionFieldValueIsValid (question: QuestionField, value: any): boolea
 
 export type ApplicationDetailsStageProps = {
   applicationDetails: ApplicationDetails;
-  data: object;
-  setData: (o: object) => void;
+  data: ApplicationDetailsData;
+  setData: (o: ApplicationDetailsData) => void;
 }
 
 export function ApplicationDetailsStage (props: ApplicationDetailsStageProps & StageTransitionProps) {
-  const initialForm = applicationDetailsToObject(props.applicationDetails, props.data as FinalDataMap);
+  const initialForm = applicationDetailsDataToAnswersByHash(props.applicationDetails, props.data);
 
-  const [data, setData] = useReducer(questionReducer, initialForm);
+  const [answers, updateAnswers] = useReducer(answersReducer, initialForm);
   const [completed, setCompleted] = useState(false);
   const [valid, setValid] = useState(false);
 
-  const handleChange = (e: any, { name, value }: any) => {
+  const handleChange = (e: any, { name, value }: InputOnChangeData | TextAreaProps) => {
     setCompleted(false);
-    setData({ key: name, value: value });
+    updateAnswers({ key: name as string, value: value?.toString() || '' });
   };
 
-  const questionField = (section: QuestionSection, question: QuestionField, key: any) => {
+  const questionField = (section: QuestionSection, question: QuestionField) => {
     switch (question.type) {
       case 'text':
-        return <Form.Input value={data[questionHash(section, question)]}
+        return <Form.Input value={answers[questionHash(section, question)]}
           name={questionHash(section, question)}
           label={question.title}
           onChange={handleChange}
           required
-          error={completed && !questionFieldValueIsValid(question, data[questionHash(section, question)])}
-          key={key}
+          error={completed && !questionFieldValueIsValid(question, answers[questionHash(section, question)])}
+          key={questionHash(section, question)}
         />;
 
       case 'text area':
-        return <Form.TextArea value={data[questionHash(section, question)]}
+        return <Form.TextArea value={answers[questionHash(section, question)]}
           name={questionHash(section, question)}
           label={question.title}
           onChange={handleChange}
           required
-          error={completed && !questionFieldValueIsValid(question, data[questionHash(section, question)])}
-          key={key}
+          error={completed && !questionFieldValueIsValid(question, answers[questionHash(section, question)])}
+          key={questionHash(section, question)}
         />;
     }
 
@@ -824,7 +829,7 @@ export function ApplicationDetailsStage (props: ApplicationDetailsStageProps & S
 
     props.applicationDetails.sections.map((section) => {
       section.questions.map((question) => {
-        if (!questionFieldValueIsValid(question, data[questionHash(section, question)])) {
+        if (!questionFieldValueIsValid(question, answers[questionHash(section, question)])) {
           valid = false;
         }
       });
@@ -835,13 +840,13 @@ export function ApplicationDetailsStage (props: ApplicationDetailsStageProps & S
 
   useEffect(() => {
     setValid(isFormValid());
-  }, [data]);
+  }, [answers]);
 
   useEffect(() => {
     if (completed === true && valid === true) {
       props.nextTransition();
     }
-  }, [completed]);
+  }, [completed, valid]);
 
   const onSubmit = (): void => {
     setCompleted(true);
@@ -850,11 +855,11 @@ export function ApplicationDetailsStage (props: ApplicationDetailsStageProps & S
       return;
     }
 
-    props.setData(applicationDetailsToDataObject(props.applicationDetails, data));
+    props.setData(answersByHashToApplicationDetailsData(props.applicationDetails, answers));
   };
 
   const onCancel = () => {
-    props.setData(applicationDetailsToDataObject(props.applicationDetails, data));
+    props.setData(answersByHashToApplicationDetailsData(props.applicationDetails, answers));
     props.prevTransition();
   };
 
@@ -864,8 +869,8 @@ export function ApplicationDetailsStage (props: ApplicationDetailsStageProps & S
         {props.applicationDetails && props.applicationDetails.sections && props.applicationDetails.sections.map((section, key) => (
           <Segment padded className='section' key={key}>
             <h4><Label attached='top'>{section.title}</Label></h4>
-            {section.questions.map((question, key) =>
-              questionField(section, question, key)
+            {section.questions.map((question) =>
+              questionField(section, question)
             )}
           </Segment>
         ))}
@@ -1022,8 +1027,8 @@ export type FlowModalProps = Pick<StakeRankSelectorProps, 'slots' | 'step'> & Fu
   setApplicationStake: (b: Balance) => void;
   roleStake: Balance;
   setRoleStake: (b: Balance) => void;
-  appDetails: any;
-  setAppDetails: (v: any) => void;
+  appDetails: ApplicationDetailsData;
+  setAppDetails: (v: ApplicationDetailsData) => void;
   txKeyAddress: AccountId;
   setTxKeyAddress: (v: AccountId) => void;
   activeStep: ProgressSteps;

+ 10 - 8
pioneer/packages/joy-roles/src/index.tsx

@@ -4,7 +4,7 @@ import { ApiContext } from '@polkadot/react-api';
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 
-import { Route, Switch } from 'react-router';
+import { Route, Switch, RouteComponentProps } from 'react-router';
 import Tabs from '@polkadot/react-components/Tabs';
 import { withMulti } from '@polkadot/react-api/index';
 import QueueContext from '@polkadot/react-components/Status/Context';
@@ -25,6 +25,8 @@ import translate from './translate';
 
 type Props = AppProps & ApiProps & I18nProps & MyAccountProps
 
+type DefaultRouteProps = RouteComponentProps<Record<string, string | undefined>>;
+
 export const App: React.FC<Props> = (props: Props) => {
   const { t } = props;
   const tabs: Array<any> = [
@@ -83,24 +85,24 @@ export const App: React.FC<Props> = (props: Props) => {
       <Switch>
         <Route
           path={`${basePath}/opportunities/:group/:id([0-9]+)/apply`}
-          render={(props) => <ApplyView controller={applyCtrl} params={props.match.params}/>} />
+          render={(props: DefaultRouteProps) => <ApplyView controller={applyCtrl} params={props.match.params}/>} />
         <Route
           path={`${basePath}/opportunities/:group/:id([0-9]+)`}
-          render={(props) => <OpportunityView controller={oppCtrl} params={props.match.params}/>} />
+          render={(props: DefaultRouteProps) => <OpportunityView controller={oppCtrl} params={props.match.params}/>} />
         <Route
           path={`${basePath}/opportunities/:group/:lead(lead)?`}
-          render={(props) => <OpportunitiesView controller={oppsCtrl} params={props.match.params}/>} />
+          render={(props: DefaultRouteProps) => <OpportunitiesView controller={oppsCtrl} params={props.match.params}/>} />
         <Route
           path={`${basePath}/opportunities`}
-          render={(props) => <OpportunitiesView controller={oppsCtrl} params={props.match.params}/>} />
+          render={(props: DefaultRouteProps) => <OpportunitiesView controller={oppsCtrl} params={props.match.params}/>} />
         <Route
           path={`${basePath}/my-roles`}
-          render={(props) => <MyRolesView controller={myRolesCtrl} params={props.match.params}/>} />
+          render={(props: DefaultRouteProps) => <MyRolesView controller={myRolesCtrl} params={props.match.params}/>} />
         <Route
           path={`${basePath}/admin`}
-          render={(props) => <AdminView controller={adminCtrl} params={props.match.params}/>} />
+          render={(props: DefaultRouteProps) => <AdminView controller={adminCtrl} params={props.match.params}/>} />
         <Route
-          render={(props) => <WorkingGroupsView controller={wgCtrl} params={props.match.params}/> } />
+          render={(props: DefaultRouteProps) => <WorkingGroupsView controller={wgCtrl} params={props.match.params}/> } />
       </Switch>
     </main>
   );

+ 19 - 23
pioneer/packages/joy-roles/src/openingStateMarkup.tsx

@@ -10,56 +10,52 @@ export type headerMarkup = {
   iconSpin?: boolean;
 }
 
-export const stateMarkup = new Map<OpeningState, headerMarkup>([
-  [OpeningState.WaitingToBegin, {
+export const stateMarkup: Record<OpeningState, headerMarkup> = {
+  [OpeningState.WaitingToBegin]: {
     class: 'waiting-to-begin',
     description: 'Waiting to begin',
     icon: 'spinner',
     iconSpin: true
-  }],
-  [OpeningState.AcceptingApplications, {
+  },
+  [OpeningState.AcceptingApplications]: {
     class: 'active',
     description: 'Accepting applications',
     icon: 'heart'
-  }],
-  [OpeningState.InReview, {
+  },
+  [OpeningState.InReview]: {
     class: 'in-review',
     description: 'Applications in review',
     icon: 'hourglass half'
-  }],
-  [OpeningState.Complete, {
+  },
+  [OpeningState.Complete]: {
     class: 'complete',
     description: 'Hiring complete',
     icon: 'thumbs up'
-  }],
-  [OpeningState.Cancelled, {
+  },
+  [OpeningState.Cancelled]: {
     class: 'cancelled',
     description: 'Cancelled',
     icon: 'ban'
-  }]
-]);
-
-export function openingStateMarkup<T> (state: OpeningState, key: string): T {
-  const markup = stateMarkup.get(state);
-
-  if (typeof markup === 'undefined') {
-    return null as unknown as T;
   }
+};
+
+export function openingStateMarkup<K extends keyof headerMarkup> (state: OpeningState, key: K): headerMarkup[K] {
+  const markup = stateMarkup[state];
 
-  return (markup as any)[key];
+  return markup[key];
 }
 
 export function openingClass (state: OpeningState): string {
-  return 'status-' + openingStateMarkup<string>(state, 'class');
+  return `status-${openingStateMarkup(state, 'class') || ''}`;
 }
 
 export function openingDescription (state: OpeningState): string {
-  return openingStateMarkup<string>(state, 'description');
+  return openingStateMarkup(state, 'description') || '';
 }
 
 export function openingIcon (state: OpeningState) {
-  const icon = openingStateMarkup<SemanticICONS>(state, 'icon');
-  const spin = openingStateMarkup<boolean>(state, 'iconSpin');
+  const icon = openingStateMarkup(state, 'icon');
+  const spin = openingStateMarkup(state, 'iconSpin');
 
   return <Icon name={icon} loading={spin} />;
 }

+ 8 - 7
pioneer/packages/joy-roles/src/tabs/Admin.controller.tsx

@@ -25,7 +25,8 @@ import { Accordion,
   Message,
   Modal,
   Table,
-  TextArea } from 'semantic-ui-react';
+  TextArea,
+  InputOnChangeData } from 'semantic-ui-react';
 
 import { ITransport } from '../transport';
 
@@ -350,10 +351,10 @@ export class AdminController extends Controller<State, ITransport> {
     this.api = api;
     this.queueExtrinsic = queueExtrinsic;
     this.state.currentDescriptor = stockOpenings[0];
-    this.refreshState();
+    void this.refreshState();
   }
 
-  onTxSuccess = () => { this.closeModal(); this.refreshState(); }
+  onTxSuccess = () => { this.closeModal(); void this.refreshState(); }
 
   newOpening (accountId: string, desc: openingDescriptor) {
     const tx = this.api.tx.contentWorkingGroup.addCuratorOpening(
@@ -599,8 +600,8 @@ const NewOpening = (props: NewOpeningProps) => {
     }
   };
 
-  const onChangeExactBlock = (e: any, { value }: any) => {
-    setExactBlock(value);
+  const onChangeExactBlock = (e: any, { value }: InputOnChangeData) => {
+    setExactBlock(typeof value === 'number' ? value : (parseInt(value) || 0));
     setStart(createMock('ActivateOpeningAt', { ExactBlock: value }));
   };
 
@@ -799,7 +800,7 @@ const OpeningView = (props: OpeningViewProps) => {
       <Card.Content>
         <Card.Header>
           <Label attached='top right'>Opening</Label>
-          <Link to={'/working-groups/opportunities/curators/' + props.opening.curatorId}>
+          <Link to={`/working-groups/opportunities/curators/${props.opening.curatorId}`}>
             {props.opening.title}
           </Link>
 
@@ -842,7 +843,7 @@ const OpeningView = (props: OpeningViewProps) => {
                     <Table.Cell>{app.curatorId}</Table.Cell>
                     <Table.Cell>{app.openingId}</Table.Cell>
                     <Table.Cell>
-                      <Link to={'/members/' + app.profile.handle}>
+                      <Link to={`/members/${app.profile.handle.toString()}`}>
                         {app.profile.handle}
                       </Link>
                     </Table.Cell>

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

@@ -32,8 +32,8 @@ export class MyRolesController extends Controller<State, ITransport> {
     }
 
     // Set actual data
-    this.updateCurationGroupRoles(this.state.myAddress);
-    this.updateApplications(this.state.myAddress);
+    void this.updateCurationGroupRoles(this.state.myAddress);
+    void this.updateApplications(this.state.myAddress);
   }
 
   setMyAddress (myAddress: string | undefined) {

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

@@ -12,7 +12,8 @@ import { Button,
   Segment,
   Statistic,
   Table,
-  SemanticICONS } from 'semantic-ui-react';
+  SemanticICONS,
+  TextAreaProps } from 'semantic-ui-react';
 
 import { formatBalance } from '@polkadot/util';
 import { Balance } from '@polkadot/types/interfaces';
@@ -49,7 +50,7 @@ function CTAButton (props: CTA) {
     handleClose();
   };
 
-  const handleChange = (e: any, value: any) => setRationale(value.value);
+  const handleChange = (e: any, { value }: TextAreaProps) => setRationale(value?.toString() || '');
 
   return (
     <Modal trigger={
@@ -169,10 +170,10 @@ type RankAndCapacityProps = {
 }
 
 function RankAndCapacity (props: RankAndCapacityProps) {
-  let capacity = null;
+  let capacity = '';
 
   if (props.capacity > 0) {
-    capacity = '/ ' + props.capacity;
+    capacity = `/${props.capacity}`;
   }
 
   let iconName: SemanticICONS = 'check circle';
@@ -214,7 +215,7 @@ function ApplicationCancelledStatus (props: ApplicationStatusProps) {
   );
 }
 
-type statusRenderer = (p: ApplicationStatusProps) => any
+type statusRenderer = React.ComponentType<ApplicationStatusProps>
 
 function ApplicationStatusAcceptingApplications (props: ApplicationStatusProps): any {
   let positive = true;
@@ -299,7 +300,9 @@ export function ApplicationStatus (props: ApplicationStatusProps) {
     return ApplicationCancelledStatus(props);
   }
 
-  return (applicationStatusRenderers.get(props.openingStatus) as statusRenderer)(props);
+  const StatusRenderer = (applicationStatusRenderers.get(props.openingStatus) as statusRenderer);
+
+  return <StatusRenderer {...props} />;
 }
 
 enum ApplicationState {
@@ -413,7 +416,7 @@ export function Application (props: ApplicationProps) {
   }
 
   return (
-    <Segment className={'application status-' + applicationClass.get(appState)}>
+    <Segment className={`application status-${applicationClass.get(appState) || ''}`}>
       <Label attached='top'>
         {application.job.title}
         <Label.Detail className='right'>
@@ -469,7 +472,7 @@ export function Application (props: ApplicationProps) {
           </Table>
           <h4>Hiring process details</h4>
           <List bulleted>
-            {application.process && application.process.details.map((detail: string, key: any) => (
+            {application.process && application.process.details.map((detail, key) => (
               <List.Item key={key}>
                 <List.Icon name='info circle' />
                 <List.Content>{detail}</List.Content>

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

@@ -21,14 +21,14 @@ type State = {
 export class OpportunitiesController extends Controller<State, ITransport> {
   constructor (transport: ITransport, initialState: State = {}) {
     super(transport, initialState);
+    this.state.blockTime = this.transport.expectedBlockTime();
   }
 
   refreshState () {
-    this.getOpportunities();
-    this.getBlocktime();
+    void this.getOpportunities();
   }
 
-  async setMemberId (memberId?: MemberId) {
+  setMemberId (memberId?: MemberId) {
     this.state.memberId = memberId;
     this.dispatch();
   }
@@ -37,11 +37,6 @@ export class OpportunitiesController extends Controller<State, ITransport> {
     this.state.opportunities = await this.transport.currentOpportunities();
     this.dispatch();
   }
-
-  async getBlocktime () {
-    this.state.blockTime = await this.transport.expectedBlockTime();
-    this.dispatch();
-  }
 }
 
 export const OpportunitiesView = View<OpportunitiesController, State>(

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

@@ -94,10 +94,10 @@ export const opening = createMock('Opening', {
 });
 
 const stateOptions: any = (function () {
-  const options: any = {};
+  const options: Record<string, OpeningState> = {};
 
-  stateMarkup.forEach((value, key) => {
-    options[value.description] = key;
+  Object.entries(stateMarkup).forEach(([key, value]) => {
+    options[value.description] = parseInt(key) as OpeningState;
   });
 
   return options;

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

@@ -222,6 +222,8 @@ export function ApplicationCount (props: ApplicationCountProps) {
 type OpeningBodyCTAProps = OpeningStakeAndApplicationStatus & OpeningStage & OpeningBodyProps & MemberIdProps
 
 function OpeningBodyCTAView (props: OpeningBodyCTAProps) {
+  const accountCtx = useMyAccount();
+
   if (props.stage.state !== OpeningState.AcceptingApplications || applicationImpossible(props.applications)) {
     return null;
   }
@@ -255,8 +257,6 @@ function OpeningBodyCTAView (props: OpeningBodyCTAProps) {
     </Link>
   );
 
-  const accountCtx = useMyAccount();
-
   if (!accountCtx.state.address) {
     applyButton = (
       <Message error icon>

+ 14 - 18
pioneer/packages/joy-roles/src/tabs/Opportunity.controller.tsx

@@ -1,7 +1,7 @@
 import React, { useEffect } from 'react';
 
-import { Controller, memoize } from '@polkadot/joy-utils/react/helpers';
-import { View } from '@polkadot/joy-utils/react/hocs';
+import { Controller } from '@polkadot/joy-utils/react/helpers';
+import { View, RenderComponentProps } from '@polkadot/joy-utils/react/hocs/View';
 
 import { ITransport } from '../transport';
 
@@ -22,15 +22,14 @@ type State = {
 export class OpportunityController extends Controller<State, ITransport> {
   constructor (transport: ITransport, initialState: State = {}) {
     super(transport, initialState);
-    this.getBlocktime();
+    this.state.blockTime = this.transport.expectedBlockTime();
   }
 
-  async setMemberId (memberId?: MemberId) {
+  setMemberId (memberId?: MemberId) {
     this.state.memberId = memberId;
     this.dispatch();
   }
 
-  @memoize()
   async getOpportunity (group: string | undefined, id: string | undefined) {
     if (!id || !group) {
       return;
@@ -43,22 +42,19 @@ export class OpportunityController extends Controller<State, ITransport> {
     this.state.opportunity = await this.transport.groupOpening(group as WorkingGroups, parseInt(id));
     this.dispatch();
   }
+}
 
-  async getBlocktime () {
-    this.state.blockTime = await this.transport.expectedBlockTime();
-    this.dispatch();
-  }
+function OpportunityRenderer ({ state, controller, params }: RenderComponentProps<OpportunityController, State>) {
+  useEffect(() => {
+    void controller.getOpportunity(params.get('group'), params.get('id'));
+  }, [params]);
+
+  return (
+    <OpeningView {...state.opportunity!} block_time_in_seconds={state.blockTime!} member_id={state.memberId} />
+  );
 }
 
 export const OpportunityView = View<OpportunityController, State>({
   errorComponent: OpeningError,
-  renderComponent: ({ state, controller, params }) => {
-    useEffect(() => {
-      controller.getOpportunity(params.get('group'), params.get('id'));
-    }, [params.get('group'), params.get('id')]);
-
-    return (
-      <OpeningView {...state.opportunity!} block_time_in_seconds={state.blockTime!} member_id={state.memberId} />
-    );
-  }
+  renderComponent: OpportunityRenderer
 });

+ 13 - 8
pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx

@@ -10,6 +10,7 @@ import { ContentCurators,
   StorageProviders } from './WorkingGroup';
 
 import styled from 'styled-components';
+import { normalizeError } from '@polkadot/joy-utils/functions/misc';
 
 type State = {
   contentCurators?: WorkingGroupMembership;
@@ -27,17 +28,21 @@ export class WorkingGroupsController extends Controller<State, ITransport> {
   }
 
   getCurationGroup () {
-    this.transport.curationGroup().then((value: WorkingGroupMembership) => {
-      this.setState({ contentCurators: value });
-      this.dispatch();
-    });
+    this.transport.curationGroup()
+      .then((value: WorkingGroupMembership) => {
+        this.setState({ contentCurators: value });
+        this.dispatch();
+      })
+      .catch((e) => this.onError(normalizeError(e)));
   }
 
   getStorageGroup () {
-    this.transport.storageGroup().then((value: WorkingGroupMembership) => {
-      this.setState({ storageProviders: value });
-      this.dispatch();
-    });
+    this.transport.storageGroup()
+      .then((value: WorkingGroupMembership) => {
+        this.setState({ storageProviders: value });
+        this.dispatch();
+      })
+      .catch((e) => this.onError(normalizeError(e)));
   }
 }
 

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

@@ -574,7 +574,7 @@ export class Transport extends BaseTransport implements ITransport {
 
   async openingApplicationsByAddressAndGroup (group: WorkingGroups, roleKey: string): Promise<OpeningApplication[]> {
     const myGroupApplications = (await this.entriesByIds<GroupApplicationId, GroupApplication>(
-      await this.apiMethodByGroup(group, 'applicationById')
+      this.apiMethodByGroup(group, 'applicationById')
     ))
       .filter(([id, groupApplication]) => groupApplication.role_account_id.eq(roleKey));
 
@@ -737,7 +737,8 @@ export class Transport extends BaseTransport implements ITransport {
             txFailedCb,
             txSuccessCb
           });
-        });
+        })
+        .catch((e) => { reject(e); });
     });
   }
 

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

@@ -7,6 +7,7 @@ import { DEV_PHRASE } from '@polkadot/keyring/defaults';
 import keyring from '@polkadot/ui-keyring';
 import { isHex, u8aToHex } from '@polkadot/util';
 import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
+import { normalizeError } from './misc';
 
 export type SeedType = 'bip' | 'raw' | 'dev';
 
@@ -31,7 +32,7 @@ function deriveValidate (seed: string, derivePath: string, pairType: KeypairType
       return 'Soft derivation paths are not allowed on ed25519';
     }
   } catch (error) {
-    return error.message;
+    return normalizeError(error);
   }
 
   return null;
@@ -124,7 +125,7 @@ export function createAccount (suri: string, pairType: KeypairType, name: string
     downloadAccount(result);
   } catch (error) {
     status.status = 'error';
-    status.message = error.message;
+    status.message = normalizeError(error);
   }
 
   return status;

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

@@ -172,6 +172,8 @@ export function normalizeError (e: any): string {
 
   if (e instanceof Error) {
     message = e.message;
+  } else if (typeof e === 'string') {
+    return e;
   } else {
     message = JSON.stringify(e);
   }

+ 3 - 1
pioneer/packages/joy-utils/src/react/helpers/Observable.ts

@@ -36,5 +36,7 @@ export abstract class Observable<S extends { [key: string ]: any }, T> implement
     this.dispatch();
   }
 
-  public refreshState () { }
+  public refreshState () {
+    this.dispatch();
+  }
 }

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

@@ -6,5 +6,4 @@ export function componentName (WrappedComponent: React.ComponentType<any>) {
 
 export { Controller } from './Controller';
 export { Observable } from './Observable';
-export { memoize } from './memoize';
 export { Subscribable } from './Subscribable';

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

@@ -1,53 +0,0 @@
-let counter = 0;
-
-function getNewFunction (originalMethod: () => void) {
-  const identifier = ++counter;
-
-  return function (this: any, ...args: any[]) {
-    const propValName = `__memoized_value_${identifier}`;
-    const propMapName = `__memoized_map_${identifier}`;
-
-    let returnedValue: any;
-
-    if (args.length > 0) {
-      if (!Object.prototype.hasOwnProperty.call(this, propMapName)) {
-        Object.defineProperty(this, propMapName, {
-          configurable: false,
-          enumerable: false,
-          writable: false,
-          value: new Map<any, any>()
-        });
-      }
-
-      const myMap: Map<any, any> = this[propMapName];
-      const hashKey = args[0];
-
-      if (myMap.has(hashKey)) {
-        returnedValue = myMap.get(hashKey);
-      } else {
-        returnedValue = originalMethod.apply(this, args as []);
-        myMap.set(hashKey, returnedValue);
-      }
-    } else {
-      if (Object.prototype.hasOwnProperty.call(this, propValName)) {
-        returnedValue = this[propValName];
-      } else {
-        returnedValue = originalMethod.apply(this, args as []);
-        Object.defineProperty(this, propValName, {
-          configurable: false,
-          enumerable: false,
-          writable: false,
-          value: returnedValue
-        });
-      }
-    }
-
-    return returnedValue;
-  };
-}
-
-export function memoize () {
-  return (target: Record<string, any>, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
-    descriptor.value = getNewFunction(descriptor.value);
-  };
-}

+ 21 - 17
pioneer/packages/joy-utils/src/react/hocs/View.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useEffect } from 'react';
 
-import { Controller } from '../helpers';
+import { Controller, componentName } from '../helpers';
 
 export type ParamsMap = Map<string, string | undefined>
 
@@ -9,7 +9,7 @@ type ViewRendererProps<C> = {
   controller: C;
 }
 
-type RenderComponentProps<C, S> = {
+export type RenderComponentProps<C, S> = {
   state: S,
   controller: C,
   params: ParamsMap
@@ -30,7 +30,20 @@ function DefaultError () {
 }
 
 export function View<C extends Controller<S, any>, S> (args: ViewArgs<C, S>): ViewRenderer<C> {
-  return (props: ViewRendererProps<C>): React.ReactElement | null => {
+  let RenderComponent: RenderComponent<C, S>;
+  let ErrorComponent: React.ComponentType = DefaultError;
+
+  if (typeof args === 'function') {
+    RenderComponent = args;
+  } else {
+    RenderComponent = args.renderComponent;
+
+    if (typeof args.errorComponent !== 'undefined') {
+      ErrorComponent = args.errorComponent;
+    }
+  }
+
+  const ResultComponent: React.FunctionComponent<ViewRendererProps<C>> = (props) => {
     const { controller } = props;
     const [state, setState] = useState<S>(controller.state);
 
@@ -49,23 +62,14 @@ export function View<C extends Controller<S, any>, S> (args: ViewArgs<C, S>): Vi
 
     const params = props.params ? new Map(Object.entries(props.params)) : new Map<string, string | undefined>();
 
-    let RenderComponent: RenderComponent<C, S>;
-    let Err: React.ComponentType = DefaultError;
-
-    if (typeof args === 'function') {
-      RenderComponent = args;
-    } else {
-      RenderComponent = args.renderComponent;
-
-      if (typeof args.errorComponent !== 'undefined') {
-        Err = args.errorComponent;
-      }
-    }
-
     if (controller.hasError()) {
-      return Err ? <Err /> : null;
+      return ErrorComponent ? <ErrorComponent /> : null;
     } else {
       return <RenderComponent { ...{ state, controller, params } } />;
     }
   };
+
+  ResultComponent.displayName = `View(${componentName(RenderComponent)})`;
+
+  return ResultComponent;
 }

+ 3 - 1
pioneer/packages/joy-utils/src/transport/mock/base.ts

@@ -2,7 +2,9 @@ export default abstract class BaseTransport {
   protected promise<T> (value: T, timeout?: number): Promise<T> {
     return new Promise<T>((resolve, reject) => {
       if (timeout) {
-        (new Promise((resolve) => setTimeout(resolve, timeout))).then(() => resolve(value));
+        (new Promise((resolve) => setTimeout(resolve, timeout)))
+          .then(() => resolve(value))
+          .catch((e) => { throw e; });
       } else {
         resolve(value);
       }

+ 3 - 0
pioneer/packages/joy-utils/src/types/workingGroups.ts

@@ -32,3 +32,6 @@ export type ParsedApplication = {
   humanReadableText: string;
   stage: ApplicationStage;
 }
+
+export type ApplicationQuestionAnswers = { [questionTitle: string]: string | undefined };
+export type ApplicationDetailsData = { [sectionTitle: string]: ApplicationQuestionAnswers | undefined };

+ 1 - 0
types/.prettierignore

@@ -6,3 +6,4 @@ lib/
 build/
 augment/
 augment-codec/
+src/hiring/schemas/role.schema.typings.ts

+ 4 - 2
types/package.json

@@ -16,7 +16,8 @@
     "generate:codec-defs": "ts-node ./src/scripts/generateCodecDefs.ts",
     "generate:registry-json": "ts-node ./src/scripts/generateRegistryJson.ts",
     "generate:augment": "yarn generate:registry-json && yarn generate:defs && yarn generate:meta",
-    "generate:all": "yarn generate:augment && yarn generate:codec-defs"
+    "generate:all": "yarn generate:augment && yarn generate:codec-defs",
+    "generate:json-schemas": "json2ts -i ./src/hiring/schemas/role.schema.json -o ./src/hiring/schemas/role.schema.typings.ts"
   },
   "author": "Joystream contributors",
   "maintainers": [],
@@ -34,7 +35,8 @@
     "@polkadot/typegen": "1.26.1",
     "ts-node": "^8.6.2",
     "typescript": "^3.7.2",
-    "madge": "^3.9.2"
+    "madge": "^3.9.2",
+    "json-schema-to-typescript": "^9.1.1"
   },
   "engines": {
     "node": ">=12.18.0",

+ 3 - 3
types/src/hiring/schemas/role.schema.json

@@ -5,7 +5,7 @@
   "title": "Generic JoyStream role schema",
   "definitions": {
     "title": {
-      "$id": "#/properties/application/title",
+      "$id": "#/definitions/title",
       "type": "string",
       "title": "Field title",
       "default": "",
@@ -69,7 +69,7 @@
             "required": ["title", "questions"],
             "properties": {
               "title": {
-                "$ref": "#/properties/application/title"
+                "$ref": "#/definitions/title"
               },
               "questions": {
                 "$id": "#/properties/application/properties/sections/items/properties/questions",
@@ -82,7 +82,7 @@
                   "required": ["title", "type"],
                   "properties": {
                     "title": {
-                      "$ref": "#/properties/application/title"
+                      "$ref": "#/definitions/title"
                     },
                     "type": {
                       "$id": "#/properties/application/properties/questions/items/type",

+ 37 - 36
types/src/hiring/schemas/role.schema.typings.ts

@@ -5,56 +5,57 @@
  * and run json-schema-to-typescript to regenerate this file.
  */
 
-export type SchemaVersion = number
-export type Headline = string
-export type JobTitle = string
-export type JobDescriptionExpectsHTML = string
-export type QuestionFieldType = string
-export type QuestionsFields = QuestionField[]
-export type QuestionSections = QuestionSection[]
-export type TheRewardSchema = string
-export type HandleOrUsername = string
-export type TheItemsSchema = string
-export type AdditionalRolehiringProcessDetails = TheItemsSchema[]
+export type SchemaVersion = number;
+export type Headline = string;
+export type JobTitle = string;
+export type JobDescriptionExpectsHTML = string;
+export type FieldTitle = string;
+export type QuestionFieldType = string;
+export type QuestionsFields = QuestionField[];
+export type QuestionSections = QuestionSection[];
+export type TheRewardSchema = string;
+export type HandleOrUsername = string;
+export type TheItemsSchema = string;
+export type AdditionalRolehiringProcessDetails = TheItemsSchema[];
 
 export interface GenericJoyStreamRoleSchema {
-  version: SchemaVersion
-  headline: Headline
-  job: JobSpecifics
-  application: ApplicationDetails
-  reward: TheRewardSchema
-  creator: CreatorDetails
-  process?: HiringProcess
-  [k: string]: any
+  version: SchemaVersion;
+  headline: Headline;
+  job: JobSpecifics;
+  application: ApplicationDetails;
+  reward: TheRewardSchema;
+  creator: CreatorDetails;
+  process?: HiringProcess;
+  [k: string]: unknown;
 }
 export interface JobSpecifics {
-  title: JobTitle
-  description: JobDescriptionExpectsHTML
-  [k: string]: any
+  title: JobTitle;
+  description: JobDescriptionExpectsHTML;
+  [k: string]: unknown;
 }
 export interface ApplicationDetails {
-  sections?: QuestionSections
-  [k: string]: any
+  sections?: QuestionSections;
+  [k: string]: unknown;
 }
 export interface QuestionSection {
-  title: any
-  questions: QuestionsFields
-  [k: string]: any
+  title: FieldTitle;
+  questions: QuestionsFields;
+  [k: string]: unknown;
 }
 export interface QuestionField {
-  title: any
-  type: QuestionFieldType
-  [k: string]: any
+  title: FieldTitle;
+  type: QuestionFieldType;
+  [k: string]: unknown;
 }
 export interface CreatorDetails {
-  membership: EntryInMembershipModuke
-  [k: string]: any
+  membership: EntryInMembershipModuke;
+  [k: string]: unknown;
 }
 export interface EntryInMembershipModuke {
-  handle: HandleOrUsername
-  [k: string]: any
+  handle: HandleOrUsername;
+  [k: string]: unknown;
 }
 export interface HiringProcess {
-  details: AdditionalRolehiringProcessDetails
-  [k: string]: any
+  details: AdditionalRolehiringProcessDetails;
+  [k: string]: unknown;
 }

+ 7 - 4
types/src/index.ts

@@ -62,9 +62,12 @@ export const types: RegistryTypes = {
 }
 
 // Allows creating types without api instance (it's not a recommended way though, so should be used just for mocks)
-const mockRegistry = new TypeRegistry();
-mockRegistry.register(types);
+const mockRegistry = new TypeRegistry()
+mockRegistry.register(types)
 
-export function createMock<TypeName extends keyof InterfaceTypes>(type: TypeName, value: any): InterfaceTypes[TypeName] {
-  return createType(mockRegistry, type, value);
+export function createMock<TypeName extends keyof InterfaceTypes>(
+  type: TypeName,
+  value: any
+): InterfaceTypes[TypeName] {
+  return createType(mockRegistry, type, value)
 }

+ 43 - 47
yarn.lock

@@ -18,6 +18,15 @@
   version "0.0.0"
   uid ""
 
+"@apidevtools/json-schema-ref-parser@9.0.6":
+  version "9.0.6"
+  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c"
+  integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==
+  dependencies:
+    "@jsdevtools/ono" "^7.1.3"
+    call-me-maybe "^1.0.1"
+    js-yaml "^3.13.1"
+
 "@babel/cli@^7.10.5":
   version "7.10.5"
   resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.10.5.tgz#57df2987c8cf89d0fc7d4b157ec59d7619f1b77a"
@@ -2205,6 +2214,11 @@
     lodash "^4.17.15"
     moment "^2.24.0"
 
+"@jsdevtools/ono@^7.1.3":
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796"
+  integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
+
 "@ledgerhq/devices@^5.21.0":
   version "5.21.0"
   resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.21.0.tgz#b6dc274536e70513a3ae7df7a9f956ea87adcc49"
@@ -4573,7 +4587,7 @@
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
   integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
 
-"@types/node@*", "@types/node@>= 8", "@types/node@>=4.5.0":
+"@types/node@*", "@types/node@>= 8":
   version "12.12.14"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2"
   integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA==
@@ -4608,11 +4622,6 @@
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
 
-"@types/prettier@^1.16.1":
-  version "1.19.0"
-  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.0.tgz#a2502fb7ce9b6626fdbfc2e2a496f472de1bdd05"
-  integrity sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A==
-
 "@types/prettier@^2.0.0":
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.2.tgz#5bb52ee68d0f8efa9cc0099920e56be6cc4e37f3"
@@ -7994,17 +8003,17 @@ cli-boxes@^2.2.0:
   resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
   integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==
 
-cli-color@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f"
-  integrity sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==
+cli-color@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c"
+  integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==
   dependencies:
     ansi-regex "^2.1.1"
-    d "1"
-    es5-ext "^0.10.46"
+    d "^1.0.1"
+    es5-ext "^0.10.51"
     es6-iterator "^2.0.3"
     memoizee "^0.4.14"
-    timers-ext "^0.1.5"
+    timers-ext "^0.1.7"
 
 cli-cursor@^2.1.0:
   version "2.1.0"
@@ -10572,7 +10581,7 @@ es-to-primitive@^1.2.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
+es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
   version "0.10.53"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
   integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
@@ -12057,11 +12066,6 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
-format-util@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.3.tgz#032dca4a116262a12c43f4c3ec8566416c5b2d95"
-  integrity sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU=
-
 format@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
@@ -16033,7 +16037,7 @@ js-yaml@^3.10.0, js-yaml@^3.14.0:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
-js-yaml@^3.11.0, js-yaml@^3.12.1, js-yaml@^3.13.1:
+js-yaml@^3.11.0, js-yaml@^3.13.1:
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
   integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
@@ -16098,30 +16102,29 @@ json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-bet
   resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
   integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
-json-schema-ref-parser@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz#30af34aeab5bee0431da805dac0eb21b574bf63d"
-  integrity sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==
+json-schema-ref-parser@^9.0.1:
+  version "9.0.6"
+  resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#fc89a5e6b853f2abe8c0af30d3874196526adb60"
+  integrity sha512-z0JGv7rRD3CnJbZY/qCpscyArdtLJhr/wRBmFUdoZ8xMjsFyNdILSprG2degqRLjBjyhZHAEBpGOxniO9rKTxA==
   dependencies:
-    call-me-maybe "^1.0.1"
-    js-yaml "^3.12.1"
-    ono "^4.0.11"
+    "@apidevtools/json-schema-ref-parser" "9.0.6"
 
-json-schema-to-typescript@^7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/json-schema-to-typescript/-/json-schema-to-typescript-7.1.0.tgz#c98a8647718551a40f324448f53508d73c087911"
-  integrity sha512-7tQyQzR+0NyI2iPjkqLLe4ncaxov1oIAFRM+BI8DDs7wxVIXsd0GWoZJIPsNQePIDr7N/LCkYtkEEDvY7dhGzA==
+json-schema-to-typescript@^9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/json-schema-to-typescript/-/json-schema-to-typescript-9.1.1.tgz#572c1eb8b7ca82d6534c023c4651f3fe925171c0"
+  integrity sha512-VrdxmwQROjPBRlHxXwGUa2xzhOMPiNZIVsxZrZjMYtbI7suRFMiEktqaD/gqhfSya7Djy+x8dnJT+H0/0sZO0Q==
   dependencies:
-    "@types/json-schema" "^7.0.3"
-    "@types/node" ">=4.5.0"
-    "@types/prettier" "^1.16.1"
-    cli-color "^1.4.0"
-    json-schema-ref-parser "^6.1.0"
+    "@types/json-schema" "^7.0.4"
+    cli-color "^2.0.0"
+    glob "^7.1.6"
+    is-glob "^4.0.1"
+    json-schema-ref-parser "^9.0.1"
     json-stringify-safe "^5.0.1"
-    lodash "^4.17.11"
-    minimist "^1.2.0"
+    lodash "^4.17.15"
+    minimist "^1.2.5"
+    mkdirp "^1.0.4"
     mz "^2.7.0"
-    prettier "^1.18.2"
+    prettier "^2.0.5"
     stdin "0.0.1"
 
 json-schema-traverse@^0.4.1:
@@ -18984,13 +18987,6 @@ only@~0.0.2:
   resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
   integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
 
-ono@^4.0.11:
-  version "4.0.11"
-  resolved "https://registry.yarnpkg.com/ono/-/ono-4.0.11.tgz#c7f4209b3e396e8a44ef43b9cedc7f5d791d221d"
-  integrity sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==
-  dependencies:
-    format-util "^1.0.3"
-
 open@^6.3.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9"
@@ -24578,7 +24574,7 @@ timers-browserify@^2.0.4:
   dependencies:
     setimmediate "^1.0.4"
 
-timers-ext@^0.1.5:
+timers-ext@^0.1.5, timers-ext@^0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6"
   integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==