Browse Source

Small init changes (static state) (#663)

* Small init changes (static state)

* Hide vanity on empty accounts

* Require accounts for apps, cleanup needsApi/needsCalls

* Display InputAddress when disable (but no options)

* Filter toolbox based on accounts

* Accounts gated via app, don't display add

* Cleanup linting

* Validation display & clear on settings types

* Split save for developer & general

* Add Reset button to dev settings
Jaco Greeff 6 years ago
parent
commit
a8ed300dc4
48 changed files with 434 additions and 462 deletions
  1. 2 2
      packages/app-123code/src/Transfer.tsx
  2. 4 10
      packages/app-accounts/src/Backup.tsx
  3. 6 12
      packages/app-accounts/src/ChangePass.tsx
  4. 6 12
      packages/app-accounts/src/Restore.tsx
  5. 10 7
      packages/app-accounts/src/index.tsx
  6. 3 2
      packages/app-addresses/src/index.tsx
  7. 8 14
      packages/app-democracy/src/Referendum.tsx
  8. 4 8
      packages/app-explorer/src/EventsRecent.tsx
  9. 3 23
      packages/app-extrinsics/src/index.tsx
  10. 1 1
      packages/app-settings/src/index.css
  11. 183 129
      packages/app-settings/src/index.tsx
  12. 5 9
      packages/app-staking/src/StakeList/Account.tsx
  13. 5 11
      packages/app-staking/src/StakeList/Nominating.tsx
  14. 1 1
      packages/app-staking/src/index.tsx
  15. 4 10
      packages/app-storage/src/Selection/Raw.tsx
  16. 1 1
      packages/app-storage/src/Selection/index.tsx
  17. 20 5
      packages/app-toolbox/src/index.tsx
  18. 7 13
      packages/app-transfer/src/Transfer.tsx
  19. 5 33
      packages/app-transfer/src/index.tsx
  20. 8 5
      packages/apps/src/Content/index.tsx
  21. 16 7
      packages/apps/src/SideBar/Item.tsx
  22. 16 20
      packages/apps/src/SideBar/index.tsx
  23. 7 2
      packages/apps/src/routing/123code.ts
  24. 3 2
      packages/apps/src/routing/accounts.ts
  25. 3 2
      packages/apps/src/routing/addresses.ts
  26. 7 6
      packages/apps/src/routing/democracy.ts
  27. 3 2
      packages/apps/src/routing/explorer.ts
  28. 4 2
      packages/apps/src/routing/extrinsics.ts
  29. 3 2
      packages/apps/src/routing/nodeinfo.ts
  30. 3 2
      packages/apps/src/routing/settings.ts
  31. 7 6
      packages/apps/src/routing/staking.ts
  32. 3 2
      packages/apps/src/routing/storage.ts
  33. 3 2
      packages/apps/src/routing/toolbox.ts
  34. 7 6
      packages/apps/src/routing/transfer.ts
  35. 7 5
      packages/apps/src/types.ts
  36. 5 6
      packages/ui-api/src/with/call.tsx
  37. 6 12
      packages/ui-api/src/with/observable.tsx
  38. 4 7
      packages/ui-app/src/InputAddress/index.tsx
  39. 3 2
      packages/ui-app/src/InputFile.tsx
  40. 5 2
      packages/ui-app/src/Tabs.tsx
  41. 1 0
      packages/ui-app/src/types.ts
  42. 3 7
      packages/ui-params/src/Param/Bytes.tsx
  43. 6 10
      packages/ui-params/src/Param/Tuple.tsx
  44. 4 8
      packages/ui-params/src/Param/Vector.tsx
  45. 6 10
      packages/ui-signer/src/Checks/Proposal.tsx
  46. 7 11
      packages/ui-signer/src/Checks/Transfer.tsx
  47. 4 10
      packages/ui-signer/src/Modal.tsx
  48. 2 1
      types.json

+ 2 - 2
packages/app-123code/src/Transfer.tsx

@@ -27,7 +27,7 @@ export default class Transfer extends React.PureComponent<Props> {
       <section>
         <h1>transfer</h1>
         <div className='ui--row'>
-          <article className='medium'>
+          <div className='large'>
             <InputAddress
               label='recipient address for this transfer'
               onChange={this.onChangeRecipient}
@@ -45,7 +45,7 @@ export default class Transfer extends React.PureComponent<Props> {
                 tx='balances.transfer'
               />
             </Button.Group>
-          </article>
+          </div>
           <div className='template--summary small'>Make a transfer from any account you control to another account. Transfer fees and per-transaction fees apply and will be calculated upon submission.</div>
         </div>
       </section>

+ 4 - 10
packages/app-accounts/src/Backup.tsx

@@ -25,16 +25,10 @@ type State = {
 };
 
 class Backup extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isPassValid: false,
-      password: ''
-    };
-  }
+  state: State = {
+    isPassValid: false,
+    password: ''
+  };
 
   render () {
     return (

+ 6 - 12
packages/app-accounts/src/ChangePass.tsx

@@ -26,18 +26,12 @@ type State = {
 };
 
 class ChangePass extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isNewValid: false,
-      isOldValid: false,
-      newPass: '',
-      oldPass: ''
-    };
-  }
+  state: State = {
+    isNewValid: false,
+    isOldValid: false,
+    newPass: '',
+    oldPass: ''
+  };
 
   render () {
     return (

+ 6 - 12
packages/app-accounts/src/Restore.tsx

@@ -25,18 +25,12 @@ type State = {
 };
 
 class Restore extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isFileValid: false,
-      isPassValid: false,
-      json: null,
-      password: ''
-    };
-  }
+  state: State = {
+    isFileValid: false,
+    isPassValid: false,
+    json: null,
+    password: ''
+  };
 
   render () {
     const { t } = this.props;

+ 10 - 7
packages/app-accounts/src/index.tsx

@@ -27,7 +27,7 @@ type Props = AppProps & I18nProps & {
 
 type State = {
   hidden: Array<string>,
-  items: Array<TabItem>
+  tabs: Array<TabItem>
 };
 
 class AccountsApp extends React.PureComponent<Props, State> {
@@ -43,7 +43,7 @@ class AccountsApp extends React.PureComponent<Props, State> {
 
     this.state = {
       ...baseState,
-      items: [
+      tabs: [
         {
           name: 'edit',
           text: t('Edit account')
@@ -72,8 +72,10 @@ class AccountsApp extends React.PureComponent<Props, State> {
   }
 
   static hideEditState () {
+    // Hide vanity as well - since the route order and matching changes, the
+    // /create/:seed route become problematic, so don't allow that option
     return {
-      hidden: ['edit']
+      hidden: ['edit', 'vanity']
     };
   }
 
@@ -93,7 +95,7 @@ class AccountsApp extends React.PureComponent<Props, State> {
 
   render () {
     const { basePath } = this.props;
-    const { hidden, items } = this.state;
+    const { hidden, tabs } = this.state;
     const renderCreator = this.renderComponent(Creator);
 
     return (
@@ -102,7 +104,7 @@ class AccountsApp extends React.PureComponent<Props, State> {
           <Tabs
             basePath={basePath}
             hidden={hidden}
-            items={items}
+            items={tabs}
           />
         </header>
         <Switch>
@@ -122,13 +124,14 @@ class AccountsApp extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderComponent = (Component: React.ComponentType<ComponentProps>) => {
+  private renderComponent (Component: React.ComponentType<ComponentProps>) {
     return ({ match }: LocationProps) => {
-      const { basePath, onStatusChange } = this.props;
+      const { basePath, location, onStatusChange } = this.props;
 
       return (
         <Component
           basePath={basePath}
+          location={location}
           match={match}
           onStatusChange={onStatusChange}
         />

+ 3 - 2
packages/app-addresses/src/index.tsx

@@ -107,13 +107,14 @@ class AddressesApp extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderComponent = (Component: React.ComponentType<ComponentProps>) => {
+  private renderComponent (Component: React.ComponentType<ComponentProps>) {
     return () => {
-      const { basePath, onStatusChange } = this.props;
+      const { basePath, location, onStatusChange } = this.props;
 
       return (
         <Component
           basePath={basePath}
+          location={location}
           onStatusChange={onStatusChange}
         />
       );

+ 8 - 14
packages/app-democracy/src/Referendum.tsx

@@ -44,20 +44,14 @@ type State = {
 };
 
 class Referendum extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      voteCount: 0,
-      voteCountYay: 0,
-      voteCountNay: 0,
-      votedTotal: new BN(0),
-      votedYay: new BN(0),
-      votedNay: new BN(0)
-    };
-  }
+  state: State = {
+    voteCount: 0,
+    voteCountYay: 0,
+    voteCountNay: 0,
+    votedTotal: new BN(0),
+    votedYay: new BN(0),
+    votedNay: new BN(0)
+  };
 
   static getDerivedStateFromProps ({ democracy_referendumVotesFor }: Props, prevState: State): State | null {
     if (!democracy_referendumVotesFor) {

+ 4 - 8
packages/app-explorer/src/EventsRecent.tsx

@@ -24,14 +24,10 @@ type State = {
 };
 
 class EventsRecent extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      prevEventHash: '',
-      recentEvents: []
-    };
-  }
+  state: State = {
+    prevEventHash: '',
+    recentEvents: []
+  };
 
   static getDerivedStateFromProps ({ system_events = [] }: Props, prevState: State): State | null {
     const prevEventHash = xxhashAsHex(stringToU8a(JSON.stringify(system_events)));

+ 3 - 23
packages/app-extrinsics/src/index.tsx

@@ -8,33 +8,17 @@ import { AppProps, I18nProps } from '@polkadot/ui-app/types';
 import './index.css';
 
 import React from 'react';
-import { Link } from 'react-router-dom';
 import { QueueConsumer } from '@polkadot/ui-app/Status/Context';
 import { Tabs } from '@polkadot/ui-app/index';
-import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
-import { withMulti, withObservable } from '@polkadot/ui-api/index';
 
 import Selection from './Selection';
 import translate from './translate';
 
-type Props = AppProps & I18nProps & {
-  accountAll?: Array<any>
-};
+type Props = AppProps & I18nProps;
 
 class ExtrinsicsApp extends React.PureComponent<Props> {
   render () {
-    const { accountAll, basePath, t } = this.props;
-
-    if (!accountAll || !Object.keys(accountAll).length) {
-      return (
-        <main className='extrinsics--App'>
-          {t('There are no saved accounts. ')}
-          <Link to='/accounts'>
-            {t('Add Accounts.')}
-          </Link>
-        </main>
-      );
-    }
+    const { basePath, t } = this.props;
 
     return (
       <main className='extrinsics--App'>
@@ -59,8 +43,4 @@ class ExtrinsicsApp extends React.PureComponent<Props> {
 
 export { ExtrinsicsApp };
 
-export default withMulti(
-  ExtrinsicsApp,
-  translate,
-  withObservable(accountsObservable.subject, { propName: 'accountAll' })
-);
+export default translate(ExtrinsicsApp);

+ 1 - 1
packages/app-settings/src/index.css

@@ -3,7 +3,7 @@
 /* of the Apache-2.0 license. See the LICENSE file for details. */
 
 .sub-label {
+  cursor: pointer;
   padding: 0rem .5833rem;
-  font-size: .85rem;
   text-align: right;
 }

+ 183 - 129
packages/app-settings/src/index.tsx

@@ -3,12 +3,14 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { AppProps, I18nProps } from '@polkadot/ui-app/types';
+import { TabItem } from '@polkadot/ui-app/Tabs';
 import { SettingsStruct } from '@polkadot/ui-settings/types';
 
 import React from 'react';
+import { Route, Switch } from 'react-router';
 import store from 'store';
 import typeRegistry from '@polkadot/types/codec/typeRegistry';
-import { Button, Dropdown, Input, InputFile } from '@polkadot/ui-app/index';
+import { Button, Dropdown, Input, InputFile, Tabs } from '@polkadot/ui-app/index';
 import uiSettings from '@polkadot/ui-settings';
 import { u8aToString } from '@polkadot/util';
 
@@ -19,130 +21,193 @@ import translate from './translate';
 type Props = AppProps & I18nProps;
 
 type State = {
-  settings: SettingsStruct & {
-    types?: { [index: string]: any } | null,
-    typesError?: boolean,
-    typesPlaceholder?: string,
-    customNode: boolean
-  }
+  isCustomNode: boolean,
+  isTypesValid: boolean,
+  isUrlValid: boolean,
+  settings: SettingsStruct,
+  tabs: Array<TabItem>,
+  types?: { [index: string]: any } | null,
+  typesPlaceholder?: string
 };
 
 class App extends React.PureComponent<Props, State> {
   constructor (props: Props) {
     super(props);
 
+    const { t } = props;
     const types = store.get('types') || {};
     const names = Object.keys(types);
-    const presets = uiSettings.get();
+    const settings = uiSettings.get();
+    let isCustomNode = true;
 
     // check to see if user has saved a custom node by seeing if their URL is equal to any preset
-    let customNode = true;
     for (let i = 0; i < uiSettings.availableNodes.length; i++) {
-      if (uiSettings.availableNodes[i].value === presets.apiUrl) {
-        customNode = false;
+      if (uiSettings.availableNodes[i].value === settings.apiUrl) {
+        isCustomNode = false;
       }
     }
 
     this.state = {
-      settings: {
-        ...presets,
-        typesPlaceholder: names.length
-          ? names.join(', ')
-          : undefined,
-        customNode: customNode
-      }
+      isCustomNode,
+      isTypesValid: true,
+      isUrlValid: this.isValidUrl(settings.apiUrl),
+      tabs: [
+        {
+          name: 'general',
+          text: t('General')
+        },
+        {
+          name: 'developer',
+          text: t('Developer')
+        }
+      ],
+      typesPlaceholder: names.length
+        ? names.join(', ')
+        : undefined,
+      settings
     };
   }
 
   render () {
-    const { t } = this.props;
-    const { settings: { apiUrl, i18nLang, typesPlaceholder, typesError, uiMode, uiTheme, customNode } } = this.state;
+    const { basePath } = this.props;
+    const { tabs } = this.state;
 
     return (
       <main className='settings--App'>
-        <section>
-          <h1>{t('general')}</h1>
-          <div className='ui--row'>
-            <div className='full'>
-              <div className='sub-label'>
-                {
-                  customNode
-                    ? <><a onClick={this.toggleCustomNode}>{t('pre-set')}</a> | <b>{t('custom')}</b></>
-                    : <><b>{t('pre-set')}</b> | <a onClick={this.toggleCustomNode}>{t('custom')}</a></>
-                }
-              </div>
+        <header>
+          <Tabs
+            basePath={basePath}
+            items={tabs}
+          />
+        </header>
+        <Switch>
+          <Route path={`${basePath}/developer`} render={this.renderDeveloper} />
+          <Route render={this.renderGeneral} />
+        </Switch>
+      </main>
+    );
+  }
+
+  private renderDeveloper = () => {
+    const { t } = this.props;
+    const { isTypesValid, types, typesPlaceholder } = this.state;
+
+    return (
+      <>
+        <div className='ui--row'>
+          <div className='full'>
+            <InputFile
+              clearContent={!types && isTypesValid}
+              isError={!isTypesValid}
+              label={t('additional type definitions (JSON)')}
+              onChange={this.onChangeTypes}
+              placeholder={typesPlaceholder}
+            />
+          </div>
+        </div>
+        <Button.Group>
+          <Button
+            isDisabled={!types}
+            isNegative
+            onClick={this.clearTypes}
+            label={t('Reset')}
+          />
+          <Button.Or />
+          <Button
+            isDisabled={!isTypesValid}
+            isPrimary
+            onClick={this.saveDeveloper}
+            label={t('Save')}
+          />
+        </Button.Group>
+      </>
+    );
+  }
+
+  private renderGeneral = () => {
+    const { t } = this.props;
+    const { isCustomNode, isUrlValid, settings: { apiUrl, i18nLang, uiMode, uiTheme } } = this.state;
+
+    return (
+      <>
+        <div className='ui--row'>
+          <div className='full'>
+            <div className='sub-label'>
               {
-                customNode
-                  ? <Input
-                    defaultValue={apiUrl}
-                    label={t('remote node/endpoint to connect to')}
-                    onChange={this.onChangeApiUrl}
-                  />
-                  : <Dropdown
-                    defaultValue={apiUrl}
-                    label={t('remote node/endpoint to connect to')}
-                    onChange={this.onChangeApiUrl}
-                    options={uiSettings.availableNodes}
-                  />
+                isCustomNode
+                  ? <><a onClick={this.toggleCustomNode}>{t('pre-set')}</a> | <b>{t('custom')}</b></>
+                  : <><b>{t('pre-set')}</b> | <a onClick={this.toggleCustomNode}>{t('custom')}</a></>
               }
             </div>
+            {
+              isCustomNode
+                ? <Input
+                  defaultValue={apiUrl}
+                  isError={!isUrlValid}
+                  label={t('remote node/endpoint to connect to')}
+                  onChange={this.onChangeApiUrl}
+                />
+                : <Dropdown
+                  defaultValue={apiUrl}
+                  label={t('remote node/endpoint to connect to')}
+                  onChange={this.onChangeApiUrl}
+                  options={uiSettings.availableNodes}
+                />
+            }
           </div>
-          <div className='ui--row'>
-            <div className='medium'>
-              <Dropdown
-                defaultValue={uiTheme}
-                label={t('default interface theme')}
-                onChange={this.onChangeUiTheme}
-                options={uiSettings.availableUIThemes}
-              />
-            </div>
-            <div className='medium'>
-              <Dropdown
-                defaultValue={uiMode}
-                label={t('interface operation mode')}
-                onChange={this.onChangeUiMode}
-                options={uiSettings.availableUIModes}
-              />
-            </div>
+        </div>
+        <div className='ui--row'>
+          <div className='medium'>
+            <Dropdown
+              defaultValue={uiTheme}
+              label={t('default interface theme')}
+              onChange={this.onChangeUiTheme}
+              options={uiSettings.availableUIThemes}
+            />
           </div>
-          <div className='ui--row'>
-            <div className='full'>
-              <Dropdown
-                defaultValue={i18nLang}
-                isDisabled
-                label={t('default interface language')}
-                onChange={this.onChangeLang}
-                options={uiSettings.availableLanguages}
-              />
-            </div>
+          <div className='medium'>
+            <Dropdown
+              defaultValue={uiMode}
+              label={t('interface operation mode')}
+              onChange={this.onChangeUiMode}
+              options={uiSettings.availableUIModes}
+            />
           </div>
-        </section>
-        <section>
-          <h1>{t('developer')}</h1>
-          <div className='ui--row'>
-            <div className='full'>
-              <InputFile
-                isError={typesError}
-                label={t('additional type definitions (JSON)')}
-                onChange={this.onChangeTypes}
-                placeholder={typesPlaceholder}
-              />
-            </div>
+        </div>
+        <div className='ui--row'>
+          <div className='full'>
+            <Dropdown
+              defaultValue={i18nLang}
+              isDisabled
+              label={t('default interface language')}
+              onChange={this.onChangeLang}
+              options={uiSettings.availableLanguages}
+            />
           </div>
-        </section>
+        </div>
         <Button.Group>
           <Button
+            isDisabled={!isUrlValid}
             isPrimary
-            onClick={this.save}
+            onClick={this.saveAndReload}
             label={t('Save & Reload')}
           />
         </Button.Group>
-      </main>
+      </>
     );
   }
 
+  private clearTypes = (): void => {
+    this.setState({
+      isTypesValid: true,
+      types: null,
+      typesPlaceholder: ''
+    });
+  }
+
   private onChangeApiUrl = (apiUrl: string): void => {
     this.setState(({ settings }: State) => ({
+      isUrlValid: this.isValidUrl(apiUrl),
       settings: {
         ...settings,
         apiUrl
@@ -163,25 +228,19 @@ class App extends React.PureComponent<Props, State> {
 
       typeRegistry.register(types);
 
-      this.setState(({ settings }: State) => ({
-        settings: {
-          ...settings,
-          types,
-          typesError: false,
-          typesPlaceholder
-        }
-      }));
+      this.setState({
+        isTypesValid: true,
+        types,
+        typesPlaceholder
+      });
     } catch (error) {
       console.error('Registering types:', error);
 
-      this.setState(({ settings }: State) => ({
-        settings: {
-          ...settings,
-          types: null,
-          typesError: true,
-          typesPlaceholder: error.message
-        }
-      }));
+      this.setState({
+        isTypesValid: false,
+        types: null,
+        typesPlaceholder: error.message
+      });
     }
   }
 
@@ -204,49 +263,44 @@ class App extends React.PureComponent<Props, State> {
   }
 
   private toggleCustomNode = (): void => {
-    this.setState(({ settings }: State) => {
-      const customNode = !settings.customNode;
+    this.setState(({ isCustomNode, settings }: State) => {
       // reset URL to a preset when toggled to preset
-      const apiUrl = customNode
-        ? settings.apiUrl
-        : uiSettings.availableNodes[0].value;
+      const apiUrl = isCustomNode
+        ? uiSettings.availableNodes[0].value
+        : settings.apiUrl;
 
       return {
+        isCustomNode: !isCustomNode,
+        isUrlValid: true,
         settings: {
           ...settings,
-          apiUrl,
-          customNode
+          apiUrl
         }
       };
     });
   }
 
-  private save = (): void => {
-    const { onStatusChange, t } = this.props;
-    const { settings: { types, typesError } } = this.state;
-
-    // validate custom node url
-    const apiUrl = this.state.settings.apiUrl;
+  private isValidUrl (apiUrl: string): boolean {
+    return (
+      // some random length... we probably want to parse via some lib
+      (apiUrl.length >= 7) &&
+      // check that it starts with a valid ws identifier
+      (apiUrl.startsWith('ws://') || apiUrl.startsWith('wss://'))
+    );
+  }
 
-    if (this.state.settings.customNode) {
-      if (!(apiUrl.startsWith('ws://localhost') ||
-      apiUrl.startsWith('ws://127.0.0.1') ||
-      apiUrl.startsWith('wss://'))) {
-        onStatusChange({
-          action: '',
-          status: 'error',
-          message: t('Custom node URL is not valid')
-        });
+  private saveDeveloper = (): void => {
+    const { isTypesValid, types } = this.state;
 
-        return;
-      }
+    if (isTypesValid) {
+      store.set('types', types);
     }
+  }
 
-    uiSettings.set(this.state.settings);
+  private saveAndReload = (): void => {
+    const { settings } = this.state;
 
-    if (types && !typesError) {
-      store.set('types', types);
-    }
+    uiSettings.set(settings);
 
     // HACK This is terribe, but since the API needs to re-connect, but since
     // the API does not yet handle re-connections properly, it is what it is

+ 5 - 9
packages/app-staking/src/StakeList/Account.tsx

@@ -37,15 +37,11 @@ type State = {
 };
 
 class Account extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isNominateOpen: false,
-      isNominating: false,
-      isPrefsOpen: false
-    };
-  }
+  state: State = {
+    isNominateOpen: false,
+    isNominating: false,
+    isPrefsOpen: false
+  };
 
   static getDerivedStateFromProps ({ staking_nominating }: Props) {
     const isNominating = !!staking_nominating && !staking_nominating.isEmpty;

+ 5 - 11
packages/app-staking/src/StakeList/Nominating.tsx

@@ -24,17 +24,11 @@ type State = {
 };
 
 class Nominating extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isNomineeValid: false,
-      isAddressFormatValid: false,
-      nominee: ''
-    };
-  }
+  state: State = {
+    isNomineeValid: false,
+    isAddressFormatValid: false,
+    nominee: ''
+  };
 
   render () {
     const { isOpen, style } = this.props;

+ 1 - 1
packages/app-staking/src/index.tsx

@@ -86,7 +86,7 @@ class App extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderComponent = (Component: React.ComponentType<ComponentProps>) => {
+  private renderComponent (Component: React.ComponentType<ComponentProps>) {
     return (): React.ReactNode => {
       const { intentions, validators } = this.state;
       const { balances = {} } = this.props;

+ 4 - 10
packages/app-storage/src/Selection/Raw.tsx

@@ -20,16 +20,10 @@ type State = {
 };
 
 class Raw extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isValid: false,
-      key: new Uint8Array([])
-    };
-  }
+  state: State = {
+    isValid: false,
+    key: new Uint8Array([])
+  };
 
   render () {
     const { t } = this.props;

+ 1 - 1
packages/app-storage/src/Selection/index.tsx

@@ -65,7 +65,7 @@ class Selection extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderComponent = (Component: React.ComponentType<ComponentProps>) => {
+  private renderComponent (Component: React.ComponentType<ComponentProps>) {
     return (): React.ReactNode => {
       return (
         <Component onAdd={this.onAdd} />

+ 20 - 5
packages/app-toolbox/src/index.tsx

@@ -3,12 +3,15 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { AppProps, I18nProps } from '@polkadot/ui-app/types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 
 import './index.css';
 
 import React from 'react';
 import { Route, Switch } from 'react-router';
 import Tabs, { TabItem } from '@polkadot/ui-app/Tabs';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { withMulti, withObservable } from '@polkadot/ui-api/index';
 
 import Hash from './Hash';
 import Rpc from './Rpc';
@@ -16,7 +19,9 @@ import Sign from './Sign';
 import Verify from './Verify';
 import translate from './translate';
 
-type Props = AppProps & I18nProps;
+type Props = AppProps & I18nProps & {
+  allAccounts?: SubjectInfo
+};
 
 type State = {
   tabs: Array<TabItem>
@@ -32,7 +37,7 @@ class ToolboxApp extends React.PureComponent<Props, State> {
       tabs: [
         {
           name: 'rpc',
-          text: 'RPC calls'
+          text: t('RPC calls')
         },
         {
           name: 'hash',
@@ -50,15 +55,21 @@ class ToolboxApp extends React.PureComponent<Props, State> {
     };
   }
   render () {
-    const { basePath } = this.props;
+    const { allAccounts = {}, basePath } = this.props;
     const { tabs } = this.state;
+    const hasAccounts = Object.keys(allAccounts).length !== 0;
+    const filteredTabs = hasAccounts
+      ? tabs
+      : tabs.filter(({ name }) =>
+        !['sign', 'verify'].includes(name)
+      );
 
     return (
       <main className='toolbox--App'>
         <header>
           <Tabs
             basePath={basePath}
-            items={tabs}
+            items={filteredTabs}
           />
         </header>
         <Switch>
@@ -72,4 +83,8 @@ class ToolboxApp extends React.PureComponent<Props, State> {
   }
 }
 
-export default translate(ToolboxApp);
+export default withMulti(
+  ToolboxApp,
+  translate,
+  withObservable(accountObservable.subject, { propName: 'allAccounts' })
+);

+ 7 - 13
packages/app-transfer/src/Transfer.tsx

@@ -31,19 +31,13 @@ type State = {
 const ZERO = new BN(0);
 
 class Transfer extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      accountId: null,
-      amount: ZERO,
-      extrinsic: null,
-      hasAvailable: true,
-      recipientId: null
-    };
-  }
+  state: State = {
+    accountId: null,
+    amount: ZERO,
+    extrinsic: null,
+    hasAvailable: true,
+    recipientId: null
+  };
 
   render () {
     const { t } = this.props;

+ 5 - 33
packages/app-transfer/src/index.tsx

@@ -3,12 +3,8 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { AppProps, I18nProps } from '@polkadot/ui-app/types';
-import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 
 import React from 'react';
-import { Trans } from 'react-i18next';
-import accountObservable from '@polkadot/ui-keyring/observable/accounts';
-import { withMulti, withObservable } from '@polkadot/ui-api/index';
 import { Tabs } from '@polkadot/ui-app/index';
 
 import './index.css';
@@ -16,34 +12,14 @@ import './index.css';
 import Transfer from './Transfer';
 import translate from './translate';
 
-type Props = AppProps & I18nProps & {
-  allAccounts?: SubjectInfo
-};
+type Props = AppProps & I18nProps;
 
 class App extends React.PureComponent<Props> {
   render () {
-    return (
-      <main className='transfer--App'>
-        {this.renderBody()}
-      </main>
-    );
-  }
-
-  private renderBody () {
-    const { allAccounts, basePath, t } = this.props;
-
-    if (!allAccounts || !Object.keys(allAccounts).length) {
-      return (
-        <div className='transfer--App-no-accounts'>
-          <Trans i18nKey='no-accounts'>
-            You currently have no active accounts. Before you are able to transfer, add <a href='#/accounts'>an account</a>.
-          </Trans>
-        </div>
-      );
-    }
+    const { basePath, t } = this.props;
 
     return (
-      <>
+      <main className='transfer--App'>
         <header>
           <Tabs
             basePath={basePath}
@@ -54,13 +30,9 @@ class App extends React.PureComponent<Props> {
           />
         </header>
         <Transfer />
-      </>
+      </main>
     );
   }
 }
 
-export default withMulti(
-  App,
-  translate,
-  withObservable(accountObservable.subject, { propName: 'allAccounts' })
-);
+export default translate(App);

+ 8 - 5
packages/apps/src/Content/index.tsx

@@ -23,7 +23,9 @@ type Props = I18nProps & ApiProps & {
 };
 
 const unknown = {
-  isApiGated: false,
+  display: {
+    needsApi: undefined
+  },
   Component: NotFound,
   name: ''
 };
@@ -32,11 +34,11 @@ class Content extends React.Component<Props> {
   render () {
     const { isApiConnected, isApiReady, location, t } = this.props;
     const app = location.pathname.slice(1) || '';
-    const { Component, isApiGated, name } = routing.routes.find((route) =>
+    const { Component, display: { needsApi }, name } = routing.routes.find((route) =>
       !!(route && app.indexOf(route.name) === 0)
     ) || unknown;
 
-    if (isApiGated && (!isApiReady || !isApiConnected)) {
+    if (needsApi && (!isApiReady || !isApiConnected)) {
       return (
         <div className='apps--Content-body'>
           <main>{t('Waiting for API to be connected and ready.')}</main>
@@ -51,6 +53,7 @@ class Content extends React.Component<Props> {
             <Component
               key='content-content'
               basePath={`/${name}`}
+              location={location}
               onStatusChange={queueAction}
             />,
             <Status
@@ -67,12 +70,12 @@ class Content extends React.Component<Props> {
 }
 
 // React-router needs to be first, otherwise we have blocked updates
-// These API queries are used in a number of places, warm them up as
-// to avoid constant un/resubscriptions on these
 export default withMulti(
   Content,
   withRouter,
   translate,
+  // These API queries are used in a number of places, warm them up
+  // to avoid constant un-/re-subscribe on these
   withCalls<Props>(
     'query.session.validators',
     'derive.accounts.indexes',

+ 16 - 7
packages/apps/src/SideBar/Item.tsx

@@ -4,16 +4,19 @@
 
 import { I18nProps } from '@polkadot/ui-app/types';
 import { ApiProps } from '@polkadot/ui-api/types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { Route } from '../types';
 
 import React from 'react';
 import { withRouter } from 'react-router';
 import { NavLink } from 'react-router-dom';
 import { Icon, Menu } from '@polkadot/ui-app/index';
-import { withApi, withMulti } from '@polkadot/ui-api/index';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { withApi, withMulti, withObservable } from '@polkadot/ui-api/index';
 import { isFunction } from '@polkadot/util';
 
 type Props = I18nProps & ApiProps & {
+  allAccounts?: SubjectInfo,
   route: Route
 };
 
@@ -21,7 +24,7 @@ class Item extends React.PureComponent<Props> {
   render () {
     const { route: { i18n, icon, name }, t } = this.props;
 
-    if (!this.isApiAvailable()) {
+    if (!this.isVisible()) {
       return null;
     }
 
@@ -38,13 +41,18 @@ class Item extends React.PureComponent<Props> {
     );
   }
 
-  private isApiAvailable () {
-    const { api, isApiConnected, isApiReady, route: { isApiGated, name, needsApi } } = this.props;
+  private isVisible () {
+    const { allAccounts = {}, api, isApiConnected, isApiReady, route: { display: { isHidden, needsAccounts, needsApi }, name } } = this.props;
+    const hasAccounts = Object.keys(allAccounts).length !== 0;
 
-    if (isApiGated && (!isApiReady || !isApiConnected)) {
+    if (isHidden) {
       return false;
-    } else if (!needsApi || !needsApi.length) {
+    } else if (needsAccounts && !hasAccounts) {
+      return false;
+    } else if (!needsApi) {
       return true;
+    } else if (!isApiReady || !isApiConnected) {
+      return false;
     }
 
     const notFound = needsApi.filter((endpoint) => {
@@ -68,5 +76,6 @@ class Item extends React.PureComponent<Props> {
 export default withMulti(
   Item,
   withRouter,
-  withApi
+  withApi,
+  withObservable(accountObservable.subject, { propName: 'allAccounts' })
 );

+ 16 - 20
packages/apps/src/SideBar/index.tsx

@@ -62,26 +62,22 @@ class SideBar extends React.PureComponent<Props> {
   private renderRoutes () {
     const { t } = this.props;
 
-    return routing.routes
-      .filter((route) =>
-        !route || !route.isHidden
-      )
-      .map((route, index) => (
-        route
-          ? (
-            <Item
-              key={route.name}
-              t={t}
-              route={route}
-            />
-          )
-          : (
-            <Menu.Divider
-              hidden
-              key={index}
-            />
-          )
-      ));
+    return routing.routes.map((route, index) => (
+      route
+        ? (
+          <Item
+            key={route.name}
+            t={t}
+            route={route}
+          />
+        )
+        : (
+          <Menu.Divider
+            hidden
+            key={index}
+          />
+        )
+    ));
   }
 
   private renderGithub () {

+ 7 - 2
packages/apps/src/routing/123code.ts

@@ -9,12 +9,17 @@ import Template from '@polkadot/app-123code/index';
 export default ([
   {
     Component: Template,
+    display: {
+      isHidden: true,
+      needsAccounts: true,
+      needsApi: [
+        'tx.balances.transfer'
+      ]
+    },
     i18n: {
       defaultValue: 'Template'
     },
     icon: 'th',
-    isApiGated: true,
-    isHidden: true,
     name: '123code'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/accounts.ts

@@ -9,12 +9,13 @@ import Accounts from '@polkadot/app-accounts/index';
 export default ([
   {
     Component: Accounts,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Accounts'
     },
     icon: 'users',
-    isApiGated: true,
-    isHidden: false,
     name: 'accounts'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/addresses.ts

@@ -9,12 +9,13 @@ import Addresses from '@polkadot/app-addresses/index';
 export default ([
   {
     Component: Addresses,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Addresses'
     },
     icon: 'address book',
-    isApiGated: true,
-    isHidden: false,
     name: 'addresses'
   }
 ] as Routes);

+ 7 - 6
packages/apps/src/routing/democracy.ts

@@ -9,15 +9,16 @@ import Democracy from '@polkadot/app-democracy/index';
 export default ([
   {
     Component: Democracy,
+    display: {
+      needsAccounts: true,
+      needsApi: [
+        'query.democracy.nextTally'
+      ]
+    },
     i18n: {
       defaultValue: 'Democracy'
     },
     icon: 'calendar check',
-    isApiGated: true,
-    isHidden: false,
-    name: 'democracy',
-    needsApi: [
-      'query.democracy.nextTally'
-    ]
+    name: 'democracy'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/explorer.ts

@@ -9,12 +9,13 @@ import Explorer from '@polkadot/app-explorer/index';
 export default ([
   {
     Component: Explorer,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Explorer'
     },
     icon: 'braille',
-    isApiGated: true,
-    isHidden: false,
     name: 'explorer'
   }
 ] as Routes);

+ 4 - 2
packages/apps/src/routing/extrinsics.ts

@@ -9,12 +9,14 @@ import Extrinsics from '@polkadot/app-extrinsics/index';
 export default ([
   {
     Component: Extrinsics,
+    display: {
+      needsAccounts: true,
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Extrinsics'
     },
     icon: 'send',
-    isApiGated: true,
-    isHidden: false,
     name: 'extrinsics'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/nodeinfo.ts

@@ -9,12 +9,13 @@ import Nodeinfo from '@polkadot/app-nodeinfo/index';
 export default ([
   {
     Component: Nodeinfo,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Node info'
     },
     icon: 'tty',
-    isApiGated: true,
-    isHidden: false,
     name: 'nodeinfo'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/settings.ts

@@ -9,12 +9,13 @@ import Settings from '@polkadot/app-settings/index';
 export default ([
   {
     Component: Settings,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Settings'
     },
     icon: 'settings',
-    isApiGated: false,
-    isHidden: false,
     name: 'settings'
   }
 ] as Routes);

+ 7 - 6
packages/apps/src/routing/staking.ts

@@ -9,15 +9,16 @@ import Staking from '@polkadot/app-staking/index';
 export default ([
   {
     Component: Staking,
+    display: {
+      needsAccounts: true,
+      needsApi: [
+        'tx.staking.stake'
+      ]
+    },
     i18n: {
       defaultValue: 'Staking'
     },
     icon: 'certificate',
-    isApiGated: true,
-    isHidden: false,
-    name: 'staking',
-    needsApi: [
-      'tx.staking.stake'
-    ]
+    name: 'staking'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/storage.ts

@@ -9,12 +9,13 @@ import Storage from '@polkadot/app-storage/index';
 export default ([
   {
     Component: Storage,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Chain state'
     },
     icon: 'database',
-    isApiGated: true,
-    isHidden: false,
     name: 'chainstate'
   }
 ] as Routes);

+ 3 - 2
packages/apps/src/routing/toolbox.ts

@@ -9,12 +9,13 @@ import Toolbox from '@polkadot/app-toolbox/index';
 export default ([
   {
     Component: Toolbox,
+    display: {
+      needsApi: []
+    },
     i18n: {
       defaultValue: 'Toolbox'
     },
     icon: 'configure',
-    isApiGated: true,
-    isHidden: false,
     name: 'toolbox'
   }
 ] as Routes);

+ 7 - 6
packages/apps/src/routing/transfer.ts

@@ -9,15 +9,16 @@ import Transfer from '@polkadot/app-transfer/index';
 export default ([
   {
     Component: Transfer,
+    display: {
+      needsAccounts: true,
+      needsApi: [
+        'tx.balances.transfer'
+      ]
+    },
     i18n: {
       defaultValue: 'Transfer'
     },
     icon: 'angle double right',
-    isApiGated: true,
-    isHidden: false,
-    name: 'transfer',
-    needsApi: [
-      'tx.balances.transfer'
-    ]
+    name: 'transfer'
   }
 ] as Routes);

+ 7 - 5
packages/apps/src/types.ts

@@ -9,12 +9,14 @@ export type RouteProps = AppProps & BareProps;
 
 export type Route = {
   Component: React.ComponentType<RouteProps>,
-  i18n: any, // I18Next$Translate$Config,
+  display: {
+    isHidden?: boolean,
+    needsAccounts?: boolean,
+    needsApi?: Array<string>
+  },
+  i18n: { defaultValue: string },
   icon: SemanticICONS,
-  isApiGated: boolean,
-  isHidden: boolean,
-  name: string,
-  needsApi?: Array<string>
+  name: string
 };
 
 export type Routes = Array<Route | null>;

+ 5 - 6
packages/ui-api/src/with/call.tsx

@@ -26,7 +26,11 @@ const NOOP = () => {
 export default function withCall<P extends ApiProps> (endpoint: string, { at, atProp, callOnResult, params = [], paramName = 'params', propName, transform = echoTransform }: Options = {}): (Inner: React.ComponentType<ApiProps>) => React.ComponentType<any> {
   return (Inner: React.ComponentType<ApiProps>): React.ComponentType<Subtract<P, ApiProps>> => {
     class WithPromise extends React.Component<P, State> {
-      state: State;
+      state: State = {
+        callResult: void 0,
+        callUpdated: false,
+        callUpdatedAt: 0
+      };
       private destroy?: () => void;
       private isActive: boolean = false;
       private propName: string;
@@ -38,11 +42,6 @@ export default function withCall<P extends ApiProps> (endpoint: string, { at, at
         const [, section, method] = endpoint.split('.');
 
         this.propName = `${section}_${method}`;
-        this.state = {
-          callResult: void 0,
-          callUpdated: false,
-          callUpdatedAt: 0
-        };
       }
 
       componentDidUpdate (prevProps: any) {

+ 6 - 12
packages/ui-api/src/with/observable.tsx

@@ -23,18 +23,12 @@ type State = CallState & {
 export default function withObservable<T, P> (observable: Observable<P>, { callOnResult, propName = 'value', transform = echoTransform }: Options = {}): HOC {
   return (Inner: React.ComponentType<any>, defaultProps: DefaultProps = {}, render?: RenderFn): React.ComponentType<any> => {
     return class WithObservable extends React.Component<any, State> {
-      state: State;
-
-      constructor (props: any) {
-        super(props);
-
-        this.state = {
-          callResult: void 0,
-          callUpdated: false,
-          callUpdatedAt: 0,
-          subscriptions: []
-        };
-      }
+      state: State = {
+        callResult: void 0,
+        callUpdated: false,
+        callUpdatedAt: 0,
+        subscriptions: []
+      };
 
       componentDidMount () {
         this.setState({

+ 4 - 7
packages/ui-app/src/InputAddress/index.tsx

@@ -76,11 +76,7 @@ const createOption = (address: string) => {
 };
 
 class InputAddress extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {};
-  }
+  state: State = {};
 
   static getDerivedStateFromProps ({ value }: Props): State | null {
     try {
@@ -112,8 +108,9 @@ class InputAddress extends React.PureComponent<Props, State> {
   render () {
     const { className, defaultValue, hideAddress = false, isDisabled = false, isError, label, optionsAll, type = DEFAULT_TYPE, style, withLabel } = this.props;
     const { value } = this.state;
+    const hasOptions = optionsAll && Object.keys(optionsAll[type]).length !== 0;
 
-    if (!optionsAll || !Object.keys(optionsAll[type]).length) {
+    if (!hasOptions && !isDisabled) {
       return null;
     }
 
@@ -143,7 +140,7 @@ class InputAddress extends React.PureComponent<Props, State> {
         options={
           isDisabled && actualValue
             ? [createOption(actualValue)]
-            : optionsAll[type]
+            : (optionsAll ? optionsAll[type] : [])
         }
         style={style}
         value={value}

+ 3 - 2
packages/ui-app/src/InputFile.tsx

@@ -16,6 +16,7 @@ type Props = BareProps & WithNamespaces & {
   // Reference Example Usage: https://github.com/react-dropzone/react-dropzone/tree/master/examples/Accept
   // i.e. MIME types: 'application/json, text/plain', or '.json, .txt'
   accept?: string,
+  clearContent?: boolean,
   isDisabled?: boolean,
   isError?: boolean,
   label: string,
@@ -41,7 +42,7 @@ class InputFile extends React.PureComponent<Props, State> {
   state: State = {};
 
   render () {
-    const { accept, className, isDisabled, isError = false, label, placeholder, t, withLabel } = this.props;
+    const { accept, className, clearContent, isDisabled, isError = false, label, placeholder, t, withLabel } = this.props;
     const { file } = this.state;
 
     return (
@@ -58,7 +59,7 @@ class InputFile extends React.PureComponent<Props, State> {
         >
           <div className='label'>
             {
-              !file
+              !file || clearContent
                 ? placeholder || t('drag and drop the file here')
                 : placeholder || t('{{name}} ({{size}} bytes)', {
                   replace: file

+ 5 - 2
packages/ui-app/src/Tabs.tsx

@@ -45,14 +45,17 @@ class Tabs extends React.PureComponent<Props> {
     const to = index === 0
       ? basePath
       : `${basePath}/${name}`;
+    // only do exact matching when not the fallback (first position tab),
+    // params are problematic for dynamic hidden such as app-accounts
+    const isExact = !hasParams || index === 0;
 
     return (
       <NavLink
         activeClassName='active'
         className='item'
-        exact={!hasParams}
+        exact={isExact}
         key={to}
-        strict={!hasParams}
+        strict={isExact}
         to={to}
       >
         {text}

+ 1 - 0
packages/ui-app/src/types.ts

@@ -15,6 +15,7 @@ export type BareProps = {
 
 export type AppProps = {
   basePath: string,
+  location: Location,
   onStatusChange: (status: ActionStatus) => void
 };
 

+ 3 - 7
packages/ui-params/src/Param/Bytes.tsx

@@ -16,13 +16,9 @@ type State = {
 };
 
 export default class Bytes extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      isFileDrop: false
-    };
-  }
+  state: State = {
+    isFileDrop: false
+  };
 
   render () {
     const { isDisabled } = this.props;

+ 6 - 10
packages/ui-params/src/Param/Tuple.tsx

@@ -20,16 +20,12 @@ type State = {
 };
 
 export default class Tuple extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      Components: [],
-      sub: [],
-      subTypes: [],
-      values: []
-    };
-  }
+  state: State = {
+    Components: [],
+    sub: [],
+    subTypes: [],
+    values: []
+  };
 
   static getDerivedStateFromProps ({ defaultValue: { value }, type: { sub, type } }: Props, prevState: State): Partial<State> | null {
     if (type === prevState.type) {

+ 4 - 8
packages/ui-params/src/Param/Vector.tsx

@@ -24,14 +24,10 @@ type State = {
 };
 
 class Vector extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      Component: null,
-      values: []
-    };
-  }
+  state: State = {
+    Component: null,
+    values: []
+  };
 
   static getDerivedStateFromProps ({ defaultValue: { value = [] }, isDisabled, type: { sub, type } }: Props, prevState: State): Partial<State> | null {
     if (type === prevState.type) {

+ 6 - 10
packages/ui-signer/src/Checks/Proposal.tsx

@@ -26,16 +26,12 @@ type State = ExtraFees & {
 };
 
 class Proposal extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      extraFees: new BN(0),
-      extraAmount: new BN(0),
-      extraWarn: false,
-      isBelowMinimum: false
-    };
-  }
+  state: State = {
+    extraFees: new BN(0),
+    extraAmount: new BN(0),
+    extraWarn: false,
+    isBelowMinimum: false
+  };
 
   static getDerivedStateFromProps ({ deposit, democracy_minimumDeposit = new BN(0), onChange }: Props): State {
     const extraAmount = deposit instanceof Compact

+ 7 - 11
packages/ui-signer/src/Checks/Transfer.tsx

@@ -30,17 +30,13 @@ type State = ExtraFees & {
 };
 
 class Transfer extends React.PureComponent<Props, State> {
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      extraFees: new BN(0),
-      extraAmount: new BN(0),
-      extraWarn: false,
-      isCreation: false,
-      isNoEffect: false
-    };
-  }
+  state: State = {
+    extraFees: new BN(0),
+    extraAmount: new BN(0),
+    extraWarn: false,
+    isCreation: false,
+    isNoEffect: false
+  };
 
   static getDerivedStateFromProps ({ amount, balances_votingBalance = ZERO_BALANCE, fees, onChange }: Props): State {
     let extraFees = fees.transferFee;

+ 4 - 10
packages/ui-signer/src/Modal.tsx

@@ -39,16 +39,10 @@ type State = {
 };
 
 class Signer extends React.PureComponent<Props, State> {
-  state: State;
-
-  constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      password: '',
-      unlockError: null
-    };
-  }
+  state: State = {
+    password: '',
+    unlockError: null
+  };
 
   static getDerivedStateFromProps ({ queue }: Props, { currentItem, password, unlockError }: State): State {
     const nextItem = queue.find(({ status }) =>

+ 2 - 1
types.json

@@ -11,5 +11,6 @@
   "Transaction": {
     "inputs": "Vec<TransactionInput>",
     "outputs": "Vec<TransactionOutput>"
-  }
+  },
+  "blah": "u32"
 }