Browse Source

Merge branch 'master' into joystream-update-upstream

Mokhtar Naamani 5 years ago
parent
commit
e1af8589a1
100 changed files with 2517 additions and 3146 deletions
  1. 54 0
      .github/workflows/pr.yml
  2. 6 0
      BOUNTIES.md
  3. 14 0
      CHANGELOG.md
  4. 1 1
      lerna.json
  5. 11 11
      package.json
  6. 3 3
      packages/app-123code/package.json
  7. 2 2
      packages/app-123code/src/AccountSelector.tsx
  8. 2 2
      packages/app-123code/src/SummaryBar.tsx
  9. 33 52
      packages/app-123code/src/Transfer.tsx
  10. 1 1
      packages/app-123code/src/index.tsx
  11. 4 4
      packages/app-accounts/package.json
  12. 8 6
      packages/app-accounts/src/Account.tsx
  13. 3 3
      packages/app-accounts/src/Overview.tsx
  14. 197 285
      packages/app-accounts/src/modals/Create.tsx
  15. 3 3
      packages/app-accounts/src/modals/CreateConfirmation.tsx
  16. 1 2
      packages/app-accounts/src/modals/Import.tsx
  17. 1 2
      packages/app-accounts/src/modals/Qr.tsx
  18. 2 2
      packages/app-accounts/src/modals/Transfer.tsx
  19. 3 3
      packages/app-address-book/package.json
  20. 103 136
      packages/app-address-book/src/Address.tsx
  21. 88 141
      packages/app-address-book/src/modals/Create.tsx
  22. 3 3
      packages/app-claims/package.json
  23. 4 4
      packages/app-contracts/package.json
  24. 1 1
      packages/app-contracts/src/Contracts/Call.tsx
  25. 26 39
      packages/app-contracts/src/Params.tsx
  26. 4 4
      packages/app-council/package.json
  27. 1 1
      packages/app-council/src/Motions/Propose.tsx
  28. 2 2
      packages/app-council/src/Motions/index.tsx
  29. 3 1
      packages/app-council/src/Overview/Vote.tsx
  30. 4 4
      packages/app-dashboard/package.json
  31. 4 4
      packages/app-democracy/package.json
  32. 8 10
      packages/app-democracy/src/Overview/Proposals.tsx
  33. 0 1
      packages/app-democracy/src/Overview/Referendum.tsx
  34. 11 12
      packages/app-democracy/src/Overview/Referendums.tsx
  35. 3 3
      packages/app-democracy/src/Propose.tsx
  36. 3 3
      packages/app-explorer/package.json
  37. 85 4
      packages/app-explorer/src/BlockHeader.tsx
  38. 0 90
      packages/app-explorer/src/BlockHeader/BlockHeader.css
  39. 11 4
      packages/app-explorer/src/BlockInfo/ByHash.tsx
  40. 138 165
      packages/app-explorer/src/Forks.tsx
  41. 47 3
      packages/app-explorer/src/NodeInfo/Peers.tsx
  42. 0 46
      packages/app-explorer/src/NodeInfo/index.css
  43. 0 2
      packages/app-explorer/src/NodeInfo/index.tsx
  44. 34 55
      packages/app-explorer/src/Query.tsx
  45. 0 5
      packages/app-explorer/src/index.css
  46. 22 30
      packages/app-explorer/src/index.tsx
  47. 5 5
      packages/app-extrinsics/package.json
  48. 2 2
      packages/app-extrinsics/src/Selection.tsx
  49. 3 3
      packages/app-generic-asset/package.json
  50. 2 2
      packages/app-generic-asset/src/Transfer.tsx
  51. 3 3
      packages/app-js/package.json
  52. 4 4
      packages/app-parachains/package.json
  53. 4 8
      packages/app-parachains/src/Overview/Parachains.tsx
  54. 4 4
      packages/app-settings/package.json
  55. 92 115
      packages/app-settings/src/Developer.tsx
  56. 1 6
      packages/app-settings/src/General.tsx
  57. 5 5
      packages/app-staking/package.json
  58. 138 0
      packages/app-staking/src/Actions/Account/InjectKeys.tsx
  59. 1 1
      packages/app-staking/src/Actions/Account/SetControllerAccount.tsx
  60. 90 151
      packages/app-staking/src/Actions/Account/SetSessionAccount.tsx
  61. 89 111
      packages/app-staking/src/Actions/Account/index.tsx
  62. 2 2
      packages/app-staking/src/Actions/NewStake.tsx
  63. 61 82
      packages/app-staking/src/Overview/Address.tsx
  64. 39 66
      packages/app-staking/src/Overview/CurrentList.tsx
  65. 3 5
      packages/app-staking/src/Overview/Summary.tsx
  66. 7 7
      packages/app-staking/src/Overview/index.tsx
  67. 0 31
      packages/app-staking/src/index.css
  68. 85 89
      packages/app-staking/src/index.tsx
  69. 1 1
      packages/app-staking/src/types.ts
  70. 4 4
      packages/app-storage/package.json
  71. 102 107
      packages/app-storage/src/Query.tsx
  72. 36 66
      packages/app-storage/src/Selection/Consts.tsx
  73. 94 151
      packages/app-storage/src/Selection/Modules.tsx
  74. 30 48
      packages/app-storage/src/Selection/Raw.tsx
  75. 1 1
      packages/app-storage/src/index.tsx
  76. 3 1
      packages/app-storage/src/types.ts
  77. 3 3
      packages/app-sudo/package.json
  78. 74 100
      packages/app-sudo/src/SetKey.tsx
  79. 1 1
      packages/app-sudo/src/Sudo.tsx
  80. 51 62
      packages/app-sudo/src/index.tsx
  81. 3 3
      packages/app-toolbox/package.json
  82. 43 62
      packages/app-toolbox/src/Hash.tsx
  83. 1 1
      packages/app-toolbox/src/Rpc/Account.tsx
  84. 6 5
      packages/app-toolbox/src/Rpc/Selection.tsx
  85. 112 172
      packages/app-toolbox/src/Sign.tsx
  86. 31 49
      packages/app-toolbox/src/Verify.tsx
  87. 4 4
      packages/app-transfer/package.json
  88. 4 4
      packages/app-treasury/package.json
  89. 43 75
      packages/app-treasury/src/Overview/Proposal.tsx
  90. 40 70
      packages/app-treasury/src/Overview/Proposals.tsx
  91. 4 6
      packages/app-treasury/src/Overview/Propose.tsx
  92. 7 10
      packages/app-treasury/src/Overview/Summary.tsx
  93. 2 2
      packages/app-treasury/src/Settings.tsx
  94. 2 2
      packages/apps-routing/package.json
  95. 5 5
      packages/apps/package.json
  96. 3 3
      packages/apps/src/Content/Status.tsx
  97. 80 87
      packages/apps/src/SideBar/Item.tsx
  98. 127 178
      packages/apps/src/SideBar/index.tsx
  99. 2 2
      packages/joy-election/package.json
  100. 4 4
      packages/joy-election/src/Reveals.tsx

+ 54 - 0
.github/workflows/pr.yml

@@ -0,0 +1,54 @@
+name: CI
+on: [pull_request]
+
+jobs:
+  lint:
+    name: Linting
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: lint
+      run: |
+        yarn install --frozen-lockfile
+        yarn lint
+
+  test:
+    name: Testing
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: test
+      run: |
+        yarn install --frozen-lockfile
+        yarn test
+
+  build_code:
+    name: Build Code
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: build
+      run: |
+        yarn install --frozen-lockfile
+        yarn build

+ 6 - 0
BOUNTIES.md

@@ -11,3 +11,9 @@ Current bounties are tracked by the [!bounty](https://github.com/polkadot-js/app
 ## Process
 
 Once listed, the normal [Gitcoin](https://gitcoin.co/) process kicks in. This means application, work and payment is managed by this tool. The values for bounties are determined by the size estimation done by the team.
+
+## Some small requests
+
+Please don't start work on an issue until you have been approved via the gitcoin interface. We generally love enthusiasm and code in the repo, however short-cutting the process does create some issues for the management of the bounties. We certainly don't want to be playing favorites if 2 PRs for the same issue are created at the same time. And in cases where somebody else has been approved and an unapproved PR comes in... well, it gets really murky.
+
+When making changes, please do not force push in your PRs, especially not after a review has been started. We will clone your repo and work from that, doing a simple `pull` on a force-pushed branch ends up being, well, less than simple. We squash merge all PRs, so you do not clutter up the history by using stock-standard pushes to your branch.

+ 14 - 0
CHANGELOG.md

@@ -1,3 +1,17 @@
+# 0.36.0-beta.x
+
+- Support for Kusama CC2
+- Support for Edgeware mainnet
+- Experimental Ledger support
+- Display forks on explorer (limited to Babe)
+- Change settings to have Save as well as Save & Reload (depending on hanges made)
+- Updates to struct & enum rendering (as per extrinsic app)
+- Bakc, Password change & Delete don't show for built-in dev accounts
+- UI theme update
+- Migrate all buttons to have icons (via bounty)
+- Make the network selection clickable on network name (via bounty)
+- A large number of components refactored for React functional components
+
 # 0.35.1
 
 - Api 0.91.1, Util 1.2.1, Extension 0.10.1

+ 1 - 1
lerna.json

@@ -10,5 +10,5 @@
   "packages": [
     "packages/*"
   ],
-  "version": "0.36.0-beta.30"
+  "version": "0.36.0-beta.65"
 }

+ 11 - 11
package.json

@@ -1,5 +1,5 @@
 {
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "private": true,
   "engines": {
     "node": ">=10.13.0",
@@ -10,12 +10,12 @@
     "packages/*"
   ],
   "resolutions": {
-    "@polkadot/api": "^0.93.0-beta.7",
-    "@polkadot/api-contract": "^0.93.0-beta.7",
-    "@polkadot/keyring": "^1.4.1",
-    "@polkadot/types": "^0.93.0-beta.7",
-    "@polkadot/util": "^1.4.1",
-    "@polkadot/util-crypto": "^1.4.1",
+    "@polkadot/api": "^0.94.0-beta.11",
+    "@polkadot/api-contract": "^0.94.0-beta.11",
+    "@polkadot/keyring": "^1.5.1",
+    "@polkadot/types": "^0.94.0-beta.11",
+    "@polkadot/util": "^1.5.1",
+    "@polkadot/util-crypto": "^1.5.1",
     "@types/styled-components": "4.1.8",
     "babel-core": "^7.0.0-bridge.0",
     "rxjs": "^6.4.0",
@@ -33,10 +33,10 @@
     "start": "cd packages/apps && webpack --config webpack.config.js"
   },
   "devDependencies": {
-    "@babel/core": "^7.6.0",
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/dev-react": "^0.31.0-beta.8",
-    "@polkadot/ts": "^0.1.72",
+    "@babel/core": "^7.6.2",
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/dev-react": "^0.31.1",
+    "@polkadot/ts": "^0.1.73",
     "autoprefixer": "^9.6.1",
     "empty": "^0.10.1",
     "gh-pages": "^2.1.1",

+ 3 - 3
packages/app-123code/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-123code",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A basic app that shows the ropes on customisation",
   "main": "index.js",
   "scripts": {},
@@ -10,7 +10,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

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

@@ -9,11 +9,11 @@ import { AccountIndex, Balance, Nonce } from '@polkadot/react-query';
 
 interface Props {
   className?: string;
-  onChange: (accountId?: string) => void;
+  onChange: (accountId: string | null) => void;
 }
 
 function AccountSelector ({ className, onChange }: Props): React.ReactElement<Props> {
-  const [accountId, setAccountId] = useState<string | undefined>();
+  const [accountId, setAccountId] = useState<string | null>(null);
 
   useEffect((): void => onChange(accountId), [accountId]);
 

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

@@ -22,7 +22,7 @@ interface Props extends BareProps, I18nProps {
   staking_intentions?: AccountId[];
 }
 
-function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_intentions = [], session_validators = [] }: Props): React.ReactElement<Props> {
+function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_intentions, session_validators }: Props): React.ReactElement<Props> {
   const { api, systemChain, systemName, systemVersion } = useContext(ApiContext);
   const [nextUp, setNextUp] = useState<AccountId[]>([]);
 
@@ -52,7 +52,7 @@ function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumbe
           {formatNumber(chain_bestNumber)} ({formatNumber(chain_bestNumberLag)} lag)
         </Bubble>
         <Bubble icon='chess queen' label='validators'>{
-          session_validators.map((accountId, index): React.ReactNode => (
+          (session_validators || []).map((accountId, index): React.ReactNode => (
             <IdentityIcon key={index} value={accountId} size={20} />
           ))
         }</Bubble>

+ 33 - 52
packages/app-123code/src/Transfer.tsx

@@ -3,64 +3,45 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import BN from 'bn.js';
-import React from 'react';
-import { Button, InputAddress, InputBalance, TxButton, TxComponent } from '@polkadot/react-components';
+import React, { useState } from 'react';
+import { Button, InputAddress, InputBalance, TxButton } from '@polkadot/react-components';
 
 import Summary from './Summary';
 
 interface Props {
-  accountId?: string;
+  accountId?: string | null;
 }
-interface State {
-  amount?: BN;
-  recipientId?: string;
-}
-
-export default class Transfer extends TxComponent<Props, State> {
-  public state: State = {};
-
-  public render (): React.ReactNode {
-    const { accountId } = this.props;
-    const { amount, recipientId } = this.state;
 
-    return (
-      <section>
-        <h1>transfer</h1>
-        <div className='ui--row'>
-          <div className='large'>
-            <InputAddress
-              label='recipient address for this transfer'
-              onChange={this.onChangeRecipient}
-              onEnter={this.sendTx}
-              type='all'
+export default function Transfer ({ accountId }: Props): React.ReactElement<Props> {
+  const [amount, setAmount] = useState<BN | undefined | null>(null);
+  const [recipientId, setRecipientId] = useState<string | null>(null);
+
+  return (
+    <section>
+      <h1>transfer</h1>
+      <div className='ui--row'>
+        <div className='large'>
+          <InputAddress
+            label='recipient address for this transfer'
+            onChange={setRecipientId}
+            type='all'
+          />
+          <InputBalance
+            label='amount to transfer'
+            onChange={setAmount}
+          />
+          <Button.Group>
+            <TxButton
+              accountId={accountId}
+              icon='send'
+              label='make transfer'
+              params={[recipientId, amount]}
+              tx='balances.transfer'
             />
-            <InputBalance
-              label='amount to transfer'
-              onChange={this.onChangeAmount}
-              onEnter={this.sendTx}
-            />
-            <Button.Group>
-              <TxButton
-                accountId={accountId}
-                icon='send'
-                label='make transfer'
-                params={[recipientId, amount]}
-                tx='balances.transfer'
-                ref={this.button}
-              />
-            </Button.Group>
-          </div>
-          <Summary className='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.</Summary>
+          </Button.Group>
         </div>
-      </section>
-    );
-  }
-
-  private onChangeAmount = (amount?: BN): void => {
-    this.setState({ amount });
-  }
-
-  private onChangeRecipient = (recipientId?: string): void => {
-    this.setState({ recipientId });
-  }
+        <Summary className='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.</Summary>
+      </div>
+    </section>
+  );
 }

+ 1 - 1
packages/app-123code/src/index.tsx

@@ -22,7 +22,7 @@ import translate from './translate';
 interface Props extends AppProps, I18nProps {}
 
 function App ({ className }: Props): React.ReactElement<Props> {
-  const [accountId, setAccountId] = useState<string | undefined>();
+  const [accountId, setAccountId] = useState<string | null>(null);
 
   return (
     // in all apps, the main wrapper is setup to allow the padding

+ 4 - 4
packages/app-accounts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-accounts",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,9 +10,9 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-qr": "^0.45.0-beta.13",
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-qr": "^0.46.0-beta.0",
     "@types/file-saver": "^2.0.0",
     "@types/yargs": "^13.0.2",
     "babel-plugin-module-resolver": "^3.1.1",

+ 8 - 6
packages/app-accounts/src/Account.tsx

@@ -23,8 +23,7 @@ interface Props extends I18nProps {
 function Account ({ address, className, t }: Props): React.ReactElement<Props> {
   const [genesisHash, setGenesisHash] = useState<string | null>(null);
   const [isBackupOpen, setIsBackupOpen] = useState(false);
-  const [isEditable, setIsEditable] = useState(false);
-  const [isExternal, setIsExternal] = useState(false);
+  const [{ isDevelopment, isEditable, isExternal }, setFlags] = useState({ isDevelopment: false, isEditable: false, isExternal: false });
   const [isForgetOpen, setIsForgetOpen] = useState(false);
   const [isPasswordOpen, setIsPasswordOpen] = useState(false);
   const [isTransferOpen, setIsTransferOpen] = useState(false);
@@ -33,8 +32,11 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
     const account = keyring.getAccount(address);
 
     setGenesisHash((account && account.meta.genesisHash) || null);
-    setIsEditable((account && !(account.meta.isInjected || account.meta.isHardware)) || false);
-    setIsExternal((account && account.meta.isExternal) || false);
+    setFlags({
+      isDevelopment: (account && account.meta.isTesting) || false,
+      isEditable: (account && !(account.meta.isInjected || account.meta.isHardware)) || false,
+      isExternal: (account && account.meta.isExternal) || false
+    });
   }, [address]);
 
   const _toggleBackup = (): void => setIsBackupOpen(!isBackupOpen);
@@ -73,7 +75,7 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
       buttons={
         <div className='accounts--Account-buttons buttons'>
           <div className='actions'>
-            {isEditable && (
+            {isEditable && !isDevelopment && (
               <Button
                 isNegative
                 onClick={_toggleForget}
@@ -82,7 +84,7 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
                 tooltip={t('Forget this account')}
               />
             )}
-            {isEditable && !isExternal && (
+            {isEditable && !isExternal && !isDevelopment && (
               <>
                 <Button
                   icon='cloud download'

+ 3 - 3
packages/app-accounts/src/Overview.tsx

@@ -36,11 +36,11 @@ async function queryLedger (): Promise<void> {
   }
 }
 
-function Overview ({ accounts = [], onStatusChange, t }: Props): React.ReactElement<Props> {
+function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<Props> {
   const [isCreateOpen, setIsCreateOpen] = useState(false);
   const [isImportOpen, setIsImportOpen] = useState(false);
   const [isQrOpen, setIsQrOpen] = useState(false);
-  const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && (Object.keys(accounts).length === 0);
+  const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && accounts && (Object.keys(accounts).length === 0);
 
   const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
   const _toggleImport = (): void => setIsImportOpen(!isImportOpen);
@@ -105,7 +105,7 @@ function Overview ({ accounts = [], onStatusChange, t }: Props): React.ReactElem
           onStatusChange={onStatusChange}
         />
       )}
-      {Object.keys(accounts).map((address): React.ReactNode => (
+      {accounts && Object.keys(accounts).map((address): React.ReactNode => (
         <Account
           address={address}
           key={address}

+ 197 - 285
packages/app-accounts/src/modals/Create.tsx

@@ -3,18 +3,16 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { I18nProps } from '@polkadot/react-components/types';
-import { ApiProps } from '@polkadot/react-api/types';
 import { ActionStatus } from '@polkadot/react-components/Status/types';
 import { KeypairType } from '@polkadot/util-crypto/types';
 import { ModalProps } from '../types';
 
 import FileSaver from 'file-saver';
-import React from 'react';
+import React, { useContext, useState } from 'react';
 import styled from 'styled-components';
 import { DEV_PHRASE } from '@polkadot/keyring/defaults';
-import { withApi, withMulti } from '@polkadot/react-api';
-import { AddressRow, Button, Dropdown, Input, Labelled, Modal, Password } from '@polkadot/react-components';
-import { InputAddress } from '@polkadot/react-components/InputAddress';
+import { ApiContext } from '@polkadot/react-api';
+import { AddressRow, Button, Dropdown, Input, InputAddress, Modal, Password } from '@polkadot/react-components';
 import keyring from '@polkadot/ui-keyring';
 import uiSettings from '@polkadot/ui-settings';
 import { isHex, u8aToHex } from '@polkadot/util';
@@ -23,20 +21,15 @@ import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from
 import translate from '../translate';
 import CreateConfirmation from './CreateConfirmation';
 
-interface Props extends ModalProps, ApiProps, I18nProps {
+interface Props extends ModalProps, I18nProps {
   seed?: string;
   type?: KeypairType;
 }
 
 type SeedType = 'bip' | 'raw' | 'dev';
 
-interface SeedOption {
-  text: string;
-  value: SeedType;
-}
-
 interface AddressState {
-  address: string;
+  address: string | null;
   deriveError: string | null;
   derivePath: string;
   isSeedValid: boolean;
@@ -45,30 +38,15 @@ interface AddressState {
   seedType: SeedType;
 }
 
-interface State extends AddressState {
-  isNameValid: boolean;
-  isPassValid: boolean;
-  isValid: boolean;
-  name: string;
-  password: string;
-  seedOptions: SeedOption[];
-  showWarning: boolean;
-  tags: string[];
-}
-
-const DEFAULT_TYPE = 'ed25519'; // 'sr25519';
+const DEFAULT_PAIR_TYPE = 'ed25519'; // 'sr25519';
 
 function deriveValidate (seed: string, derivePath: string, pairType: KeypairType): string | null {
   try {
     const { path } = keyExtractSuri(`${seed}${derivePath}`);
 
     // we don't allow soft for ed25519
-    if (pairType === 'ed25519') {
-      const firstSoft = path.find(({ isSoft }): boolean => isSoft);
-
-      if (firstSoft) {
-        return 'Soft derivation paths are not allowed on ed25519';
-      }
+    if (pairType === 'ed25519' && path.some(({ isSoft }): boolean => isSoft)) {
+      return 'Soft derivation paths are not allowed on ed25519';
     }
   } catch (error) {
     return error.message;
@@ -91,17 +69,19 @@ function addressFromSeed (phrase: string, derivePath: string, pairType: KeypairT
     .address;
 }
 
-function generateSeed (_seed: string | null, derivePath: string, seedType: SeedType, pairType: KeypairType): AddressState {
-  const seed = ((): string => {
-    switch (seedType) {
-      case 'bip':
-        return mnemonicGenerate();
-      case 'dev':
-        return DEV_PHRASE;
-      default:
-        return _seed || u8aToHex(randomAsU8a());
-    }
-  })();
+function newSeed (seed: string | undefined | null, seedType: SeedType): string {
+  switch (seedType) {
+    case 'bip':
+      return mnemonicGenerate();
+    case 'dev':
+      return DEV_PHRASE;
+    default:
+      return seed || u8aToHex(randomAsU8a());
+  }
+}
+
+function generateSeed (_seed: string | undefined | null, derivePath: string, seedType: SeedType, pairType: KeypairType = DEFAULT_PAIR_TYPE): AddressState {
+  const seed = newSeed(_seed, seedType);
   const address = addressFromSeed(seed, derivePath, pairType);
 
   return {
@@ -115,239 +95,70 @@ function generateSeed (_seed: string | null, derivePath: string, seedType: SeedT
   };
 }
 
-class Create extends React.PureComponent<Props, State> {
-  public state: State;
-
-  public constructor (props: Props) {
-    super(props);
+function updateAddress (seed: string, derivePath: string, seedType: SeedType, pairType: KeypairType): AddressState {
+  const deriveError = deriveValidate(seed, derivePath, pairType);
+  let isSeedValid = seedType === 'raw'
+    ? rawValidate(seed)
+    : mnemonicValidate(seed);
+  let address: string | null = null;
 
-    const { isDevelopment, seed, t, type } = this.props;
-    const seedOptions: SeedOption[] = [
-      { value: 'bip', text: t('Mnemonic') },
-      { value: 'raw', text: t('Raw seed') }
-    ];
-
-    if (isDevelopment) {
-      seedOptions.push({ value: 'dev', text: t('Development') });
+  if (!deriveError && isSeedValid) {
+    try {
+      address = addressFromSeed(seed, derivePath, pairType);
+    } catch (error) {
+      isSeedValid = false;
     }
-
-    const pairType = type || DEFAULT_TYPE;
-    const seedType = seed ? 'raw' : 'bip';
-
-    this.state = {
-      ...generateSeed(seed || null, '', seedType, pairType),
-      isNameValid: true,
-      isPassValid: false,
-      isValid: false,
-      name: 'new account',
-      password: '',
-      seedOptions,
-      showWarning: false,
-      tags: []
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { className, t } = this.props;
-    const { address, deriveError, derivePath, isNameValid, isPassValid, isSeedValid, isValid, name, pairType, password, seed, seedOptions, seedType, showWarning } = this.state;
-    const isDevSeed = seedType === 'dev';
-    const seedLabel = ((): string => {
-      switch (seedType) {
-        case 'bip':
-          return t('mnemonic seed');
-        case 'dev':
-          return t('development seed');
-        default:
-          return t('seed (hex or string)');
-      }
-    })();
-
-    return (
-      <Modal
-        className={className}
-        dimmer='inverted'
-        open
-      >
-        <Modal.Header>{t('Add an account via seed')}</Modal.Header>
-        {showWarning && (
-          <CreateConfirmation
-            address={address}
-            name={name}
-            onCommit={this.onCommit}
-            onHideWarning={this.onHideWarning}
-          />
-        )}
-        <Modal.Content>
-          <AddressRow
-            defaultName={name}
-            value={isSeedValid ? address : ''}
-          >
-            <Input
-              autoFocus
-              className='full'
-              help={t('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
-              isError={!isNameValid}
-              label={t('name')}
-              onChange={this.onChangeName}
-              onEnter={this.onCommit}
-              value={name}
-            />
-            <Input
-              className='full'
-              help={t('The private key for your account is derived from this seed. This seed must be kept secret as anyone in its possession has access to the funds of this account. If you validate, use the seed of the session account as the "--key" parameter of your node.')}
-              isAction
-              isError={!isSeedValid}
-              isReadOnly={isDevSeed}
-              label={seedLabel}
-              onChange={this.onChangeSeed}
-              onEnter={this.onCommit}
-              value={seed}
-            >
-              <Dropdown
-                isButton
-                defaultValue={seedType}
-                onChange={this.selectSeedType}
-                options={seedOptions}
-              />
-            </Input>
-            <Password
-              className='full'
-              help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
-              isError={!isPassValid}
-              label={t('password')}
-              onChange={this.onChangePass}
-              onEnter={this.onCommit}
-              value={password}
-            />
-            <details
-              className='accounts--Creator-advanced'
-              open
-            >
-              <summary>{t('Advanced creation options')}</summary>
-              <Dropdown
-                defaultValue={pairType}
-                help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
-                label={t('keypair crypto type')}
-                onChange={this.onChangePairType}
-                options={uiSettings.availableCryptos}
-              />
-              <Input
-                className='full'
-                help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`.')}
-                isError={!!deriveError}
-                label={t('secret derivation path')}
-                onChange={this.onChangeDerive}
-                onEnter={this.onCommit}
-                value={derivePath}
-              />
-              {deriveError && (
-                <Labelled label=''><article className='error'>{deriveError}</article></Labelled>
-              )}
-            </details>
-          </AddressRow>
-        </Modal.Content>
-        <Modal.Actions>
-          <Button.Group>
-            <Button
-              icon='cancel'
-              isNegative
-              label={t('Cancel')}
-              onClick={this.onDiscard}
-            />
-            <Button.Or />
-            <Button
-              icon='plus'
-              isDisabled={!isValid}
-              isPrimary
-              label={t('Save')}
-              onClick={this.onShowWarning}
-            />
-          </Button.Group>
-        </Modal.Actions>
-      </Modal>
-    );
-  }
-
-  private nextState (newState: Partial<State>): void {
-    this.setState(
-      (prevState: State): State => {
-        const { derivePath = prevState.derivePath, name = prevState.name, pairType = prevState.pairType, password = prevState.password, seed = prevState.seed, seedOptions = prevState.seedOptions, seedType = prevState.seedType, showWarning = prevState.showWarning, tags = prevState.tags } = newState;
-        let address = prevState.address;
-        const deriveError = deriveValidate(seed, derivePath, pairType);
-        const isNameValid = !!name;
-        const isPassValid = keyring.isPassValid(password);
-        let isSeedValid = seedType === 'raw'
-          ? rawValidate(seed)
-          : mnemonicValidate(seed);
-
-        if (!deriveError && isSeedValid && (seed !== prevState.seed || derivePath !== prevState.derivePath || pairType !== prevState.pairType)) {
-          try {
-            address = addressFromSeed(seed, derivePath, pairType);
-          } catch (error) {
-            isSeedValid = false;
-          }
-        }
-
-        return {
-          address,
-          deriveError,
-          derivePath,
-          isNameValid,
-          isPassValid,
-          isSeedValid,
-          isValid: isNameValid && isPassValid && isSeedValid,
-          name,
-          pairType,
-          password,
-          seed,
-          seedOptions,
-          seedType,
-          showWarning,
-          tags
-        };
-      }
-    );
-  }
-
-  private onChangeDerive = (derivePath: string): void => {
-    this.nextState({ derivePath });
   }
 
-  private onChangeName = (name: string): void => {
-    this.nextState({ name: name.trim() });
-  }
-
-  private onChangePairType = (pairType: KeypairType): void => {
-    this.nextState({ pairType });
-  }
-
-  private onChangePass = (password: string): void => {
-    this.nextState({ password });
-  }
-
-  private onChangeSeed = (seed: string): void => {
-    this.nextState({ seed });
-  }
-
-  private onShowWarning = (): void => {
-    this.nextState({ showWarning: true });
-  }
+  return {
+    address,
+    deriveError,
+    derivePath,
+    isSeedValid,
+    pairType,
+    seedType,
+    seed
+  };
+}
 
-  private onHideWarning = (): void => {
-    this.nextState({ showWarning: false });
-  }
+function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type: propsType }: Props): React.ReactElement<Props> {
+  const { isDevelopment } = useContext(ApiContext);
+  const [{ address, deriveError, derivePath, isSeedValid, pairType, seed, seedType }, setAddress] = useState<AddressState>(generateSeed(propsSeed, '', propsSeed ? 'raw' : 'bip', propsType));
+  const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: true, name: 'new account' });
+  const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
+  const isValid = !!address && !deriveError && isNameValid && isPassValid && isSeedValid;
+
+  const _onChangePass = (password: string): void =>
+    setPassword({ isPassValid: keyring.isPassValid(password), password });
+  const _onChangeDerive = (newDerivePath: string): void =>
+    setAddress(updateAddress(seed, newDerivePath, seedType, pairType));
+  const _onChangeSeed = (newSeed: string): void =>
+    setAddress(updateAddress(newSeed, derivePath, seedType, pairType));
+  const _onChangePairType = (newPairType: KeypairType): void =>
+    setAddress(updateAddress(seed, derivePath, seedType, newPairType));
+  const _selectSeedType = (newSeedType: SeedType): void => {
+    if (newSeedType !== seedType) {
+      setAddress(generateSeed(null, derivePath, newSeedType, pairType));
+    }
+  };
+  const _onChangeName = (_name: string): void => {
+    const name = _name.trim();
 
-  private onCommit = (): void => {
-    const { onClose, onStatusChange, t } = this.props;
-    const { derivePath, isValid, name, pairType, password, seed, tags } = this.state;
-    const status: Partial<ActionStatus> = { action: 'create' };
+    setName({ isNameValid: !!name, name });
+  };
+  const _toggleConfirmation = (): void => setIsConfirmationOpen(!isConfirmationOpen);
 
+  const _onCommit = (): void => {
     if (!isValid) {
       return;
     }
 
+    // we will fill in all the details below
+    const status = { action: 'create' } as ActionStatus;
+
     try {
-      const { json, pair } = keyring.addUri(`${seed}${derivePath}`, password, { name, tags }, pairType);
+      const { json, pair } = keyring.addUri(`${seed}${derivePath}`, password, { name, tags: [] }, pairType);
       const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
       const { address } = pair;
 
@@ -363,35 +174,136 @@ class Create extends React.PureComponent<Props, State> {
       status.message = error.message;
     }
 
-    this.onHideWarning();
-
-    onStatusChange(status as ActionStatus);
+    _toggleConfirmation();
+    onStatusChange(status);
     onClose();
-  }
-
-  private onDiscard = (): void => {
-    const { onClose } = this.props;
-
-    onClose();
-  }
-
-  private selectSeedType = (seedType: SeedType): void => {
-    if (seedType === this.state.seedType) {
-      return;
-    }
+  };
 
-    this.setState(({ derivePath, pairType }: State): State =>
-      (generateSeed(null, derivePath, seedType, pairType) as State)
-    );
-  }
+  return (
+    <Modal
+      className={className}
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Add an account via seed')}</Modal.Header>
+      {address && isConfirmationOpen && (
+        <CreateConfirmation
+          address={address}
+          name={name}
+          onCommit={_onCommit}
+          onClose={_toggleConfirmation}
+        />
+      )}
+      <Modal.Content>
+        <AddressRow
+          defaultName={name}
+          value={isSeedValid ? address : ''}
+        >
+          <Input
+            autoFocus
+            className='full'
+            help={t('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
+            isError={!isNameValid}
+            label={t('name')}
+            onChange={_onChangeName}
+            onEnter={_onCommit}
+            value={name}
+          />
+          <Input
+            className='full'
+            help={t('The private key for your account is derived from this seed. This seed must be kept secret as anyone in its possession has access to the funds of this account. If you validate, use the seed of the session account as the "--key" parameter of your node.')}
+            isAction
+            isError={!isSeedValid}
+            isReadOnly={seedType === 'dev'}
+            label={
+              seedType === 'bip'
+                ? t('mnemonic seed')
+                : seedType === 'dev'
+                  ? t('development seed')
+                  : t('seed (hex or string)')
+            }
+            onChange={_onChangeSeed}
+            onEnter={_onCommit}
+            value={seed}
+          >
+            <Dropdown
+              isButton
+              defaultValue={seedType}
+              onChange={_selectSeedType}
+              options={
+                (
+                  isDevelopment
+                    ? [{ value: 'dev', text: t('Development') }]
+                    : []
+                ).concat(
+                  { value: 'bip', text: t('Mnemonic') },
+                  { value: 'raw', text: t('Raw seed') }
+                )
+              }
+            />
+          </Input>
+          <Password
+            className='full'
+            help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
+            isError={!isPassValid}
+            label={t('password')}
+            onChange={_onChangePass}
+            onEnter={_onCommit}
+            value={password}
+          />
+          <details
+            className='accounts--Creator-advanced'
+            open
+          >
+            <summary>{t('Advanced creation options')}</summary>
+            <Dropdown
+              defaultValue={pairType}
+              help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
+              label={t('keypair crypto type')}
+              onChange={_onChangePairType}
+              options={uiSettings.availableCryptos}
+            />
+            <Input
+              className='full'
+              help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`.')}
+              isError={!!deriveError}
+              label={t('secret derivation path')}
+              onChange={_onChangeDerive}
+              onEnter={_onCommit}
+              value={derivePath}
+            />
+            {deriveError && (
+              <article className='error'>{deriveError}</article>
+            )}
+          </details>
+        </AddressRow>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='plus'
+            isDisabled={!isValid}
+            isPrimary
+            label={t('Save')}
+            onClick={_toggleConfirmation}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
 }
 
-export default withMulti(
+export default translate(
   styled(Create)`
     .accounts--Creator-advanced {
       margin-top: 1rem;
     }
-  `,
-  translate,
-  withApi
+  `
 );

+ 3 - 3
packages/app-accounts/src/modals/CreateConfirmation.tsx

@@ -12,11 +12,11 @@ import translate from '../translate';
 interface Props extends I18nProps {
   address: string;
   name: string;
+  onClose: () => void;
   onCommit: () => void;
-  onHideWarning: () => void;
 }
 
-function CreateConfirmation ({ address, name, onCommit, onHideWarning, t }: Props): React.ReactElement<Props> | null {
+function CreateConfirmation ({ address, name, onClose, onCommit, t }: Props): React.ReactElement<Props> | null {
   return (
     <Modal
       dimmer='inverted'
@@ -41,7 +41,7 @@ function CreateConfirmation ({ address, name, onCommit, onHideWarning, t }: Prop
             icon='cancel'
             isNegative
             label={t('Cancel')}
-            onClick={onHideWarning}
+            onClick={onClose}
           />
           <Button.Or />
           <Button

+ 1 - 2
packages/app-accounts/src/modals/Import.tsx

@@ -8,8 +8,7 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
 import { ModalProps } from '../types';
 
 import React from 'react';
-import { AddressRow, Button, InputFile, Modal, Password, TxComponent } from '@polkadot/react-components';
-import { InputAddress } from '@polkadot/react-components/InputAddress';
+import { AddressRow, Button, InputAddress, InputFile, Modal, Password, TxComponent } from '@polkadot/react-components';
 import { isHex, isObject, u8aToString } from '@polkadot/util';
 import keyring from '@polkadot/ui-keyring';
 

+ 1 - 2
packages/app-accounts/src/modals/Qr.tsx

@@ -7,8 +7,7 @@ import { ModalProps } from '../types';
 
 import React, { useState } from 'react';
 import styled from 'styled-components';
-import { AddressRow, Button, Input, Modal } from '@polkadot/react-components';
-import { InputAddress } from '@polkadot/react-components/InputAddress';
+import { AddressRow, Button, Input, InputAddress, Modal } from '@polkadot/react-components';
 import { QrScanAddress } from '@polkadot/react-qr';
 import keyring from '@polkadot/ui-keyring';
 

+ 2 - 2
packages/app-accounts/src/modals/Transfer.tsx

@@ -166,11 +166,11 @@ class Transfer extends React.PureComponent<Props, State> {
     this.nextState({ amount });
   }
 
-  private onChangeFrom = (senderId: string): void => {
+  private onChangeFrom = (senderId: string | null): void => {
     this.nextState({ senderId });
   }
 
-  private onChangeTo = (recipientId: string): void => {
+  private onChangeTo = (recipientId: string | null): void => {
     this.nextState({ recipientId });
   }
 

+ 3 - 3
packages/app-address-book/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-address-book",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,7 +10,7 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 103 - 136
packages/app-address-book/src/Address.tsx

@@ -6,7 +6,7 @@ import { KeyringAddress } from '@polkadot/ui-keyring/types';
 import { ActionStatus } from '@polkadot/react-components/Status/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import styled from 'styled-components';
 import { AddressCard, AddressInfo, Button, ChainLock, Forget } from '@polkadot/react-components';
 import keyring from '@polkadot/ui-keyring';
@@ -20,154 +20,121 @@ interface Props extends I18nProps {
   className?: string;
 }
 
-interface State {
-  current?: KeyringAddress;
-  genesisHash: string | null;
-  isEditable: boolean;
-  isForgetOpen: boolean;
-  isTransferOpen: boolean;
-}
-
 const WITH_BALANCE = { available: true, free: true, total: true };
 const WITH_EXTENDED = { nonce: true };
 
-class Address extends React.PureComponent<Props, State> {
-  public state: State;
+const isEditable = true;
 
-  public constructor (props: Props) {
-    super(props);
+function Address ({ address, className, t }: Props): React.ReactElement<Props> {
+  const [current, setCurrent] = useState<KeyringAddress | null>(null);
+  const [genesisHash, setGenesisHash] = useState<string | null>(null);
+  const [isForgetOpen, setIsForgetOpen] = useState(false);
+  const [isTransferOpen, setIsTransferOpen] = useState(false);
 
-    const { address } = this.props;
+  useEffect((): void => {
     const current = keyring.getAddress(address);
 
-    this.state = {
-      current,
-      genesisHash: (current && current.meta.genesisHash) || null,
-      isEditable: true,
-      isForgetOpen: false,
-      isTransferOpen: false
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { address, className, t } = this.props;
-    const { current, genesisHash, isEditable, isForgetOpen, isTransferOpen } = this.state;
-
-    return (
-      <AddressCard
-        buttons={
-          <div className='addresses--Address-buttons buttons'>
-            <div className='actions'>
-              {isEditable && (
-                <Button
-                  isNegative
-                  onClick={this.toggleForget}
-                  icon='trash'
-                  key='forget'
-                  size='small'
-                  tooltip={t('Forget this address')}
-                />
-              )}
+    setCurrent(current || null);
+    setGenesisHash((current && current.meta.genesisHash) || null);
+  }, []);
+
+  const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
+  const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
+  const _onForget = (): void => {
+    if (address) {
+      const status: Partial<ActionStatus> = {
+        account: address,
+        action: 'forget'
+      };
+
+      try {
+        keyring.forgetAddress(address);
+        status.status = 'success';
+        status.message = t('address forgotten');
+      } catch (error) {
+        status.status = 'error';
+        status.message = error.message;
+      }
+    }
+  };
+  const _onGenesisChange = (genesisHash: string | null): void => {
+    setGenesisHash(genesisHash);
+
+    const account = keyring.getAddress(address);
+
+    account && keyring.saveAddress(address, { ...account.meta, genesisHash });
+  };
+
+  return (
+    <AddressCard
+      buttons={
+        <div className='addresses--Address-buttons buttons'>
+          <div className='actions'>
+            {isEditable && (
               <Button
-                icon='paper plane'
-                isPrimary
-                key='deposit'
-                label={t('deposit')}
-                onClick={this.toggleTransfer}
+                isNegative
+                onClick={_toggleForget}
+                icon='trash'
+                key='forget'
                 size='small'
-                tooltip={t('Send funds to this address')}
+                tooltip={t('Forget this address')}
               />
-            </div>
-            {isEditable && (
-              <div className='others'>
-                <ChainLock
-                  genesisHash={genesisHash}
-                  onChange={this.onGenesisChange}
-                />
-              </div>
             )}
+            <Button
+              icon='paper plane'
+              isPrimary
+              key='deposit'
+              label={t('deposit')}
+              onClick={_toggleTransfer}
+              size='small'
+              tooltip={t('Send funds to this address')}
+            />
           </div>
-        }
-        className={className}
-        isEditable={isEditable}
-        type='address'
-        value={address}
-        withExplorer
-        withIndex
-        withTags
-      >
-        {address && current && (
-          <>
-            {isForgetOpen && (
-              <Forget
-                address={current.address}
-                onForget={this.onForget}
-                key='modal-forget-account'
-                mode='address'
-                onClose={this.toggleForget}
+          {isEditable && (
+            <div className='others'>
+              <ChainLock
+                genesisHash={genesisHash}
+                onChange={_onGenesisChange}
               />
-            )}
-            {isTransferOpen && (
-              <Transfer
-                key='modal-transfer'
-                onClose={this.toggleTransfer}
-                recipientId={address}
-              />
-            )}
-          </>
-        )}
-        <AddressInfo
-          address={address}
-          withBalance={WITH_BALANCE}
-          withExtended={WITH_EXTENDED}
-        />
-      </AddressCard>
-    );
-  }
-
-  private toggleForget = (): void => {
-    this.setState(({ isForgetOpen }): Pick<State, never> => ({
-      isForgetOpen: !isForgetOpen
-    }));
-  }
-
-  private toggleTransfer = (): void => {
-    this.setState(({ isTransferOpen }): Pick<State, never> => ({
-      isTransferOpen: !isTransferOpen
-    }));
-  }
-
-  private onForget = (): void => {
-    const { address, t } = this.props;
-
-    if (!address) {
-      return;
-    }
-
-    const status: Partial<ActionStatus> = {
-      account: address,
-      action: 'forget'
-    };
-
-    try {
-      keyring.forgetAddress(address);
-      status.status = 'success';
-      status.message = t('address forgotten');
-    } catch (error) {
-      status.status = 'error';
-      status.message = error.message;
-    }
-  }
-
-  private onGenesisChange = (genesisHash: string | null): void => {
-    const { address } = this.props;
-
-    this.setState({ genesisHash }, (): void => {
-      const account = keyring.getAddress(address);
-
-      account && keyring.saveAddress(address, { ...account.meta, genesisHash });
-    });
-  }
+            </div>
+          )}
+        </div>
+      }
+      className={className}
+      isEditable={isEditable}
+      type='address'
+      value={address}
+      withExplorer
+      withIndex
+      withTags
+    >
+      {address && current && (
+        <>
+          {isForgetOpen && (
+            <Forget
+              address={current.address}
+              onForget={_onForget}
+              key='modal-forget-account'
+              mode='address'
+              onClose={_toggleForget}
+            />
+          )}
+          {isTransferOpen && (
+            <Transfer
+              key='modal-transfer'
+              onClose={_toggleTransfer}
+              recipientId={address}
+            />
+          )}
+        </>
+      )}
+      <AddressInfo
+        address={address}
+        withBalance={WITH_BALANCE}
+        withExtended={WITH_EXTENDED}
+      />
+    </AddressCard>
+  );
 }
 
 export default translate(

+ 88 - 141
packages/app-address-book/src/modals/Create.tsx

@@ -6,159 +6,59 @@ import { I18nProps } from '@polkadot/react-components/types';
 import { ActionStatus } from '@polkadot/react-components/Status/types';
 import { ModalProps } from '../types';
 
-import React from 'react';
+import React, { useState } from 'react';
 
-import { AddressRow, Button, Input, Modal } from '@polkadot/react-components';
-import { InputAddress } from '@polkadot/react-components/InputAddress';
+import { AddressRow, Button, Input, InputAddress, Modal } from '@polkadot/react-components';
 import keyring from '@polkadot/ui-keyring';
 
 import translate from '../translate';
 
 interface Props extends ModalProps, I18nProps {}
 
-interface State {
-  address: string;
-  isAddressExisting: boolean;
-  isAddressValid: boolean;
-  isNameValid: boolean;
-  isValid: boolean;
-  name: string;
-  tags: string[];
-}
+function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [{ isNameValid, name }, setName] = useState<{ isNameValid: boolean; name: string }>({ isNameValid: true, name: 'new address' });
+  const [{ address, isAddressExisting, isAddressValid }, setAddress] = useState<{ address: string; isAddressExisting: boolean; isAddressValid: boolean }>({ address: '', isAddressExisting: false, isAddressValid: false });
+  const isValid = isAddressValid && isNameValid;
 
-class Create extends React.PureComponent<Props, State> {
-  public state: State = {
-    address: '',
-    isAddressExisting: false,
-    isAddressValid: false,
-    isNameValid: true,
-    isValid: false,
-    name: 'new address',
-    tags: []
-  };
+  const _onChangeAddress = (input: string): void => {
+    let address = '';
+    let isAddressValid = true;
+    let isAddressExisting = false;
 
-  public render (): React.ReactNode {
-    const { t } = this.props;
-    const { address, isAddressValid, isNameValid, isValid, name } = this.state;
-
-    return (
-      <Modal
-        dimmer='inverted'
-        open
-      >
-        <Modal.Header>{t('Add an address')}</Modal.Header>
-        <Modal.Content>
-          <AddressRow
-            defaultName={name}
-            value={address}
-          >
-            <Input
-              autoFocus
-              className='full'
-              help={t('Paste here the address of the contact you want to add to your address book.')}
-              isError={!isAddressValid}
-              label={t('address')}
-              onChange={this.onChangeAddress}
-              onEnter={this.onCommit}
-              value={address}
-            />
-            <Input
-              className='full'
-              help={t('Type the name of your contact. This name will be used across all the apps. It can be edited later on.')}
-              isError={!isNameValid}
-              label={t('name')}
-              onChange={this.onChangeName}
-              onEnter={this.onCommit}
-              value={name}
-            />
-          </AddressRow>
-        </Modal.Content>
-        <Modal.Actions>
-          <Button.Group>
-            <Button
-              icon='cancel'
-              isNegative
-              onClick={this.onDiscard}
-              label={t('Cancel')}
-            />
-            <Button.Or />
-            <Button
-              icon='save'
-              isDisabled={!isValid}
-              isPrimary
-              onClick={this.onCommit}
-              label={t('Save')}
-            />
-          </Button.Group>
-        </Modal.Actions>
-      </Modal>
-    );
-  }
-
-  private nextState (newState: Partial<State>, allowEdit = false): void {
-    this.setState(
-      (prevState: State): State => {
-        let { address = prevState.address, name = prevState.name, tags = prevState.tags } = newState;
-        let isAddressValid = true;
-        let isAddressExisting = false;
-        let newAddress = address;
-
-        try {
-          newAddress = keyring.encodeAddress(
-            keyring.decodeAddress(address)
-          );
-          isAddressValid = keyring.isAvailable(newAddress);
-
-          if (!isAddressValid) {
-            const old = keyring.getAddress(newAddress);
-
-            if (old) {
-              if (!allowEdit) {
-                name = old.meta.name || name;
-              }
-
-              isAddressExisting = true;
-              isAddressValid = true;
-            }
-          }
-        } catch (error) {
-          isAddressValid = false;
-        }
+    try {
+      address = keyring.encodeAddress(
+        keyring.decodeAddress(input)
+      );
+      isAddressValid = keyring.isAvailable(address);
 
-        const isNameValid = !!name;
-
-        return {
-          address: newAddress,
-          isAddressExisting,
-          isAddressValid,
-          isNameValid,
-          isValid: isAddressValid && isNameValid,
-          name,
-          tags
-        };
-      }
-    );
-  }
+      if (!isAddressValid) {
+        const old = keyring.getAddress(address);
 
-  private onChangeAddress = (address: string): void => {
-    this.nextState({ address });
-  }
+        if (old) {
+          const newName = old.meta.name || name;
 
-  private onChangeName = (name: string): void => {
-    this.nextState({ name }, true);
-  }
+          isAddressExisting = true;
+          isAddressValid = true;
+
+          setName({ isNameValid: !!newName, name: newName });
+        }
+      }
+    } catch (error) {
+      isAddressValid = false;
+    }
 
-  private onCommit = (): void => {
-    const { onClose, onStatusChange, t } = this.props;
-    const { address, isAddressExisting, isValid, name, tags } = this.state;
-    const status: Partial<ActionStatus> = { action: 'create' };
+    setAddress({ address: address || input, isAddressExisting, isAddressValid });
+  };
+  const _onChangeName = (name: string): void => setName({ isNameValid: !!name, name });
+  const _onCommit = (): void => {
+    const status = { action: 'create' } as ActionStatus;
 
     if (!isValid) {
       return;
     }
 
     try {
-      keyring.saveAddress(address, { name, genesisHash: keyring.genesisHash, tags });
+      keyring.saveAddress(address, { name, genesisHash: keyring.genesisHash, tags: [] });
 
       status.account = address;
       status.status = address ? 'success' : 'error';
@@ -172,15 +72,62 @@ class Create extends React.PureComponent<Props, State> {
       status.message = error.message;
     }
 
-    onStatusChange(status as ActionStatus);
+    onStatusChange(status);
     onClose();
-  }
-
-  private onDiscard = (): void => {
-    const { onClose } = this.props;
+  };
 
-    onClose();
-  }
+  return (
+    <Modal
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Add an address')}</Modal.Header>
+      <Modal.Content>
+        <AddressRow
+          defaultName={name}
+          value={address}
+        >
+          <Input
+            autoFocus
+            className='full'
+            help={t('Paste here the address of the contact you want to add to your address book.')}
+            isError={!isAddressValid}
+            label={t('address')}
+            onChange={_onChangeAddress}
+            onEnter={_onCommit}
+            value={address}
+          />
+          <Input
+            className='full'
+            help={t('Type the name of your contact. This name will be used across all the apps. It can be edited later on.')}
+            isError={!isNameValid}
+            label={t('name')}
+            onChange={_onChangeName}
+            onEnter={_onCommit}
+            value={name}
+          />
+        </AddressRow>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            onClick={onClose}
+            label={t('Cancel')}
+          />
+          <Button.Or />
+          <Button
+            icon='save'
+            isDisabled={!isValid}
+            isPrimary
+            onClick={_onCommit}
+            label={t('Save')}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
 }
 
 export default translate(Create);

+ 3 - 3
packages/app-claims/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-claims",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "An app for claiming Polkadot tokens",
   "main": "index.js",
   "scripts": {},
@@ -11,7 +11,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 4 - 4
packages/app-contracts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-contracts",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "Deployment and management of substrate contracts",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/api-contract": "^0.93.0-beta.7",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/api-contract": "^0.94.0-beta.11",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 1 - 1
packages/app-contracts/src/Contracts/Call.tsx

@@ -256,7 +256,7 @@ class Call extends TxComponent<Props, State> {
     this.setState({ accountId });
   }
 
-  private onChangeAddress = (address: string): void => {
+  private onChangeAddress = (address: string | null): void => {
     const contractAbi = getContractAbi(address);
 
     this.setState({ address, contractAbi, isAddressValid: !!contractAbi });

+ 26 - 39
packages/app-contracts/src/Params.tsx

@@ -6,7 +6,7 @@ import { ContractABIFnArg } from '@polkadot/api-contract/types';
 import { TypeDef } from '@polkadot/types/types';
 import { RawParams } from '@polkadot/react-params/types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import UIParams from '@polkadot/react-params';
 import { getTypeDef } from '@polkadot/types';
 
@@ -22,47 +22,34 @@ interface ParamDef {
   type: TypeDef;
 }
 
-interface State {
-  params: ParamDef[];
-}
-
-export default class Params extends React.PureComponent<Props, State> {
-  public state: State = { params: [] };
-
-  public static getDerivedStateFromProps ({ params }: Props): State | null {
-    if (!params) {
-      return { params: [] };
-    }
-
-    return {
-      params: params.map(({ name, type }): ParamDef => ({
-        name,
-        type: getTypeDef(type, name)
-      }))
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { isDisabled, onEnter } = this.props;
-    const { params } = this.state;
-
-    if (!params.length) {
-      return null;
+export default function Params ({ isDisabled, onChange, onEnter, params: propParams }: Props): React.ReactElement<Props> | null {
+  const [params, setParams] = useState<ParamDef[]>([]);
+
+  useEffect((): void => {
+    if (propParams) {
+      setParams(
+        propParams.map(({ name, type }): ParamDef => ({
+          name,
+          type: getTypeDef(type, name)
+        }))
+      );
     }
+  }, [propParams]);
 
-    return (
-      <UIParams
-        isDisabled={isDisabled}
-        onChange={this.onChange}
-        onEnter={onEnter}
-        params={params}
-      />
-    );
+  if (!params.length) {
+    return null;
   }
 
-  private onChange = (values: RawParams): void => {
-    const { onChange } = this.props;
-
+  const _onChange = (values: RawParams): void => {
     onChange(values.map(({ value }): any => value));
-  }
+  };
+
+  return (
+    <UIParams
+      isDisabled={isDisabled}
+      onChange={_onChange}
+      onEnter={onEnter}
+      params={params}
+    />
+  );
 }

+ 4 - 4
packages/app-council/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-council",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "Council",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65"
   }
 }

+ 1 - 1
packages/app-council/src/Motions/Propose.tsx

@@ -124,7 +124,7 @@ class Propose extends TxModal<Props, State> {
     this.setState({ threshold });
   }
 
-  private onChangeExtrinsic = (method: Call): void => {
+  private onChangeExtrinsic = (method?: Call): void => {
     if (!method) {
       return;
     }

+ 2 - 2
packages/app-council/src/Motions/index.tsx

@@ -18,7 +18,7 @@ interface Props extends I18nProps {
   council_proposals?: Hash[];
 }
 
-function Proposals ({ council_proposals = [], t }: Props): React.ReactElement<Props> {
+function Proposals ({ council_proposals, t }: Props): React.ReactElement<Props> {
   return (
     <CardGrid
       emptyText={t('No council motions')}
@@ -27,7 +27,7 @@ function Proposals ({ council_proposals = [], t }: Props): React.ReactElement<Pr
         <Propose />
       }
     >
-      {council_proposals.map((hash: Hash): React.ReactNode => (
+      {council_proposals && council_proposals.map((hash: Hash): React.ReactNode => (
         <Motion
           hash={hash.toHex()}
           key={hash.toHex()}

+ 3 - 1
packages/app-council/src/Overview/Vote.tsx

@@ -267,7 +267,9 @@ class Vote extends TxModal<Props, State> {
       return;
     }
 
-    api.derive.elections.approvalsOfAt(accountId, voteCount)
+    // FIXME This any is a mismatch in api-derive
+    api.derive.elections
+      .approvalsOfAt(accountId as any, voteCount)
       .then((approvals: boolean[]): void => {
         if ((voterPositions && voterPositions[accountId.toString()]) && approvals && approvals.length && approvals !== this.state.approvals) {
           this.setState({

+ 4 - 4
packages/app-dashboard/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-dashboard",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "Dashboard for all apps, allowing for an overview and quick navigation",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/apps-routing": "^0.36.0-beta.30",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/apps-routing": "^0.36.0-beta.65",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 4 - 4
packages/app-democracy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-democracy",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A referendum & proposal app",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65"
   }
 }

+ 8 - 10
packages/app-democracy/src/Overview/Proposals.tsx

@@ -18,21 +18,19 @@ interface Props extends I18nProps {
   democracy_publicProps?: [BN, Proposal][];
 }
 
-function Proposals ({ democracy_publicProps = [], t }: Props): React.ReactElement<Props> {
+function Proposals ({ democracy_publicProps, t }: Props): React.ReactElement<Props> {
   return (
     <Column
       emptyText={t('No available proposals')}
       headerText={t('proposals')}
     >
-      {
-        democracy_publicProps.map(([idNumber, proposal]): React.ReactNode => (
-          <ProposalDisplay
-            idNumber={idNumber}
-            key={idNumber.toString()}
-            value={proposal}
-          />
-        ))
-      }
+      {democracy_publicProps && democracy_publicProps.map(([idNumber, proposal]): React.ReactNode => (
+        <ProposalDisplay
+          idNumber={idNumber}
+          key={idNumber.toString()}
+          value={proposal}
+        />
+      ))}
     </Column>
   );
 }

+ 0 - 1
packages/app-democracy/src/Overview/Referendum.tsx

@@ -133,7 +133,6 @@ function Referendum ({ chain_bestNumber, className, democracy_enactmentPeriod, d
         </Static>
         <VoteThreshold
           isDisabled
-          isOptional={false}
           defaultValue={{ isValid: true, value: value.threshold }}
           label={t('vote threshold')}
           name='voteThreshold'

+ 11 - 12
packages/app-democracy/src/Overview/Referendums.tsx

@@ -18,23 +18,22 @@ interface Props extends I18nProps {
   democracy_referendums?: Option<ReferendumInfoExtended>[];
 }
 
-function Referendums ({ democracy_referendums = [], t }: Props): React.ReactElement<Props> {
+function Referendums ({ democracy_referendums, t }: Props): React.ReactElement<Props> {
   return (
     <Column
       emptyText={t('No available referendums')}
       headerText={t('referendum')}
     >
-      {
-        democracy_referendums
-          .filter((opt): boolean => opt.isSome)
-          .map((opt): ReferendumInfoExtended => opt.unwrap())
-          .map((referendum): React.ReactNode => (
-            <Referendum
-              idNumber={referendum.index}
-              key={referendum.index.toString()}
-              value={referendum}
-            />
-          ))
+      {democracy_referendums && democracy_referendums
+        .filter((opt): boolean => opt.isSome)
+        .map((opt): ReferendumInfoExtended => opt.unwrap())
+        .map((referendum): React.ReactNode => (
+          <Referendum
+            idNumber={referendum.index}
+            key={referendum.index.toString()}
+            value={referendum}
+          />
+        ))
       }
     </Column>
   );

+ 3 - 3
packages/app-democracy/src/Propose.tsx

@@ -21,7 +21,7 @@ interface Props extends I18nProps, ApiProps, RouteComponentProps {
 }
 
 interface State {
-  accountId?: string;
+  accountId?: string | null;
   method: Call | null;
   value: BN;
   isValid: boolean;
@@ -97,11 +97,11 @@ class Propose extends TxComponent<Props, State> {
     );
   }
 
-  private onChangeAccount = (accountId: string): void => {
+  private onChangeAccount = (accountId: string | null): void => {
     this.nextState({ accountId });
   }
 
-  private onChangeExtrinsic = (method: Call): void => {
+  private onChangeExtrinsic = (method?: Call): void => {
     if (!method) {
       return;
     }

+ 3 - 3
packages/app-explorer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-explorer",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,7 +10,7 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 85 - 4
packages/app-explorer/src/BlockHeader/index.tsx → packages/app-explorer/src/BlockHeader.tsx

@@ -4,10 +4,9 @@
 
 import { BareProps } from '@polkadot/react-components/types';
 
-import './BlockHeader.css';
-
 import React from 'react';
 import { Link } from 'react-router-dom';
+import styled from 'styled-components';
 import { HeaderExtended } from '@polkadot/api-derive';
 import { AddressMini, LinkPolkascan } from '@polkadot/react-components';
 import { formatNumber } from '@polkadot/util';
@@ -44,7 +43,7 @@ const renderDetails = ({ number: blockNumber, extrinsicsRoot, parentHash, stateR
   );
 };
 
-export default function BlockHeader ({ isSummary, value, withExplorer, withLink }: Props): React.ReactElement<Props> | null {
+function BlockHeader ({ className, isSummary, value, withExplorer, withLink }: Props): React.ReactElement<Props> | null {
   if (!value) {
     return null;
   }
@@ -53,7 +52,7 @@ export default function BlockHeader ({ isSummary, value, withExplorer, withLink
   const textNumber = formatNumber(value.number);
 
   return (
-    <article className='explorer--BlockHeader'>
+    <article className={`explorer--BlockHeader ${className}`}>
       <div className='header-outer'>
         <div className='header'>
           <div className='number'>{
@@ -82,3 +81,85 @@ export default function BlockHeader ({ isSummary, value, withExplorer, withLink
     </article>
   );
 }
+
+export default styled(BlockHeader)`
+  .author {
+    font-size: 1rem;
+    text-align: right;
+    vertical-align: middle;
+
+    > .ui--AddressMini.padded {
+      padding: 0;
+    }
+  }
+
+  .header {
+    color: rgba(0, 0, 0, 0.6);
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    text-align: left;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    vertical-align: middle;
+
+    > div {
+      display: inline-block;
+      font-weight: 100;
+      vertical-align: middle;
+      white-space: nowrap;
+    }
+
+    > .number {
+      font-size: 2.25rem;
+    }
+
+    .hash {
+      font-size: 1.5rem;
+      font-family: sans-serif;
+    }
+  }
+
+  .hash {
+    font-family: monospace;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .number {
+    align-items: center;
+    box-sizing: border-box;
+  }
+
+  .contains {
+    border: 0;
+    margin-top: 0.5rem;
+    text-align: center;
+
+    > .info {
+      margin-bottom: 0.125em;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+
+      .ui--Labelled {
+        text-align: center;
+      }
+
+      > label {
+        display: inline-block;
+      }
+
+      > span,
+      > label {
+        vertical-align: middle;
+      }
+
+      label {
+        margin-right: 0.5rem;
+        min-width: 10rem;
+        text-align: right;
+      }
+    }
+  }
+`;

+ 0 - 90
packages/app-explorer/src/BlockHeader/BlockHeader.css

@@ -1,90 +0,0 @@
-/* Copyright 2017-2019 @polkadot/app-explorer authors & contributors
-/* This software may be modified and distributed under the terms
-/* of the Apache-2.0 license. See the LICENSE file for details. */
-
-header > .explorer--BlockHeader {
-  border: none;
-  box-shadow: none;
-}
-
-.explorer--BlockHeader {
-  .author {
-    font-size: 1rem;
-    text-align: right;
-    vertical-align: middle;
-
-    > .ui--AddressMini.padded {
-      padding: 0;
-    }
-  }
-
-  .header {
-    color: rgba(0, 0, 0, 0.6);
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    text-align: left;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    vertical-align: middle;
-
-    > div {
-      display: inline-block;
-      font-weight: 100;
-      vertical-align: middle;
-      white-space: nowrap;
-    }
-
-    > .number {
-      font-size: 2.25rem;
-    }
-
-    .hash {
-      font-size: 1.5rem;
-      font-family: sans-serif;
-    }
-  }
-
-  .hash {
-    font-family: monospace;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-
-  .number {
-    align-items: center;
-    box-sizing: border-box;
-  }
-
-  .contains {
-    border: 0;
-    margin-top: 0.5rem;
-    text-align: center;
-
-    > .info {
-      margin-bottom: 0.125em;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-
-      .ui--Labelled {
-        text-align: center;
-      }
-
-      > label {
-        display: inline-block;
-      }
-
-      > span,
-      > label {
-        vertical-align: middle;
-      }
-
-      label {
-        margin-right: 0.5rem;
-        min-width: 10rem;
-        text-align: right;
-      }
-    }
-  }
-}

+ 11 - 4
packages/app-explorer/src/BlockInfo/ByHash.tsx

@@ -7,6 +7,7 @@ import { EventRecord, SignedBlock } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
 
 import React from 'react';
+import styled from 'styled-components';
 import { HeaderExtended } from '@polkadot/api-derive';
 import { withCalls } from '@polkadot/react-api';
 import { Columar } from '@polkadot/react-components';
@@ -24,15 +25,16 @@ interface Props extends I18nProps {
   value: string;
 }
 
-function BlockByHash ({ system_events, chain_getBlock, chain_getHeader }: Props): React.ReactElement<Props> | null {
+function BlockByHash ({ className, system_events, chain_getBlock, chain_getHeader }: Props): React.ReactElement<Props> | null {
   if (!chain_getBlock || chain_getBlock.isEmpty || !chain_getHeader || chain_getHeader.isEmpty) {
     return null;
   }
 
   return (
-    <>
+    <div className={className}>
       <header>
         <BlockHeader
+          className='exporer--BlockByHash-BlockHeader'
           value={chain_getHeader}
           withExplorer
         />
@@ -45,7 +47,7 @@ function BlockByHash ({ system_events, chain_getBlock, chain_getHeader }: Props)
         <Events value={system_events} />
         <Logs value={chain_getHeader.digest.logs} />
       </Columar>
-    </>
+    </div>
   );
 }
 
@@ -54,5 +56,10 @@ export default translate(
     ['rpc.chain.getBlock', { paramName: 'value' }],
     ['derive.chain.getHeader', { paramName: 'value' }],
     ['query.system.events', { atProp: 'value' }]
-  )(BlockByHash)
+  )(styled(BlockByHash)`
+    .exporer--BlockByHash-BlockHeader {
+      border: none;
+      box-shadow: none;
+    }
+  `)
 );

+ 138 - 165
packages/app-explorer/src/Forks.tsx

@@ -7,9 +7,9 @@ import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { Header } from '@polkadot/types/interfaces';
 
-import React from 'react';
+import React, { useContext, useEffect, useRef, useState } from 'react';
 import styled from 'styled-components';
-import { withApi, withMulti } from '@polkadot/react-api';
+import { ApiContext } from '@polkadot/react-api';
 import { CardSummary, IdentityIcon, SummaryBox } from '@polkadot/react-components';
 import { formatNumber } from '@polkadot/util';
 
@@ -39,12 +39,6 @@ interface Props extends ApiProps, I18nProps {
   newHead?: Header;
 }
 
-interface State {
-  numBlocks: number;
-  numForks: number;
-  tree?: Link;
-}
-
 type UnsubFn = () => void;
 
 interface Col {
@@ -151,24 +145,23 @@ function renderCol ({ author, hash, isEmpty, isFinalized, parent, width }: Col,
       colSpan={width}
       key={`${hash}:${index}:${width}`}
     >
-      {
-        isEmpty
-          ? <div className='empty' />
-          : (
-            <>
-              {author && (
-                <IdentityIcon
-                  className='author'
-                  size={28}
-                  value={author}
-                />
-              )}
-              <div className='contents'>
-                <div className='hash'>{hash}</div>
-                <div className='parent'>{parent}</div>
-              </div>
-            </>
-          )
+      {isEmpty
+        ? <div className='empty' />
+        : (
+          <>
+            {author && (
+              <IdentityIcon
+                className='author'
+                size={28}
+                value={author}
+              />
+            )}
+            <div className='contents'>
+              <div className='hash'>{hash}</div>
+              <div className='parent'>{parent}</div>
+            </div>
+          </>
+        )
       }
     </td>
   );
@@ -210,146 +203,32 @@ function renderRows (rows: Row[]): React.ReactNode[] {
   });
 }
 
-class Forks extends React.PureComponent<Props, State> {
-  public state: State = {
-    numBlocks: 0,
-    numForks: 0
-  };
-
-  private _children: Map<string, string[]> = new Map([['root', []]]);
-
-  private _headers: Map<string, LinkHeader> = new Map();
+function Forks ({ className, t }: Props): React.ReactElement<Props> | null {
+  const { api } = useContext(ApiContext);
+  const [tree, setTree] = useState<Link | null>(null);
+  const childrenRef = useRef<Map<string, string[]>>(new Map([['root', []]]));
+  const countRef = useRef({ numBlocks: 0, numForks: 0 });
+  const headersRef = useRef<Map<string, LinkHeader>>(new Map());
+  const firstNumRef = useRef('');
 
-  private _firstNum = '';
-
-  private _subFinHead: UnsubFn | null = null;
-
-  private _subNewHead: UnsubFn | null = null;
-
-  // mark as finalized, working down for parents as well
-  private finalize (hash: string): void {
-    const hdr = this._headers.get(hash);
+  const _finalize = (hash: string): void => {
+    const hdr = headersRef.current.get(hash);
 
     if (hdr && !hdr.isFinalized) {
       hdr.isFinalized = true;
 
-      this.finalize(hdr.parent);
-    }
-  }
-
-  // callback for the subscribe finalized sub
-  private addFinalized = (header: Header): void => {
-    this.finalize(header.hash.toHex());
-  }
-
-  // callback for the subscribe headers sub
-  private addHeader = (header: Header): void => {
-    const { api } = this.props;
-
-    // formatted block info
-    const bn = formatNumber(header.number);
-    const hash = header.hash.toHex();
-    const parent = header.parentHash.toHex();
-    let isFork = false;
-
-    // if this the first one?
-    if (!this._firstNum) {
-      this._firstNum = bn;
-    }
-
-    if (!this._headers.has(hash)) {
-      // if this is the first, add to the root entry
-      if (this._firstNum === bn) {
-        (this._children.get('root') as any[]).push(hash);
-      }
-
-      // add to the header map
-      // also for HeaderExtended header.author ? header.author.toString() : null
-      this._headers.set(hash, createHdr(bn, hash, parent, null));
-
-      // check to see if the children already has a entry
-      if (this._children.has(parent)) {
-        isFork = true;
-        (this._children.get(parent) as any[]).push(hash);
-      } else {
-        this._children.set(parent, [hash]);
-      }
-
-      // if we don't have the parent of this one, retrieve it
-      if (!this._headers.has(parent)) {
-        // just make sure we are not first in the list, we don't want to full chain
-        if (this._firstNum !== bn) {
-          console.warn(`Retrieving missing header ${header.parentHash.toHex()}`);
-
-          api.rpc.chain.getHeader(header.parentHash).then(this.addHeader);
-
-          // catch the refresh on the result
-          return;
-        }
-      }
-
-      // do the magic, extract the info into something useful and add to state
-      this.setState(({ numBlocks, numForks }): State => ({
-        numBlocks: numBlocks + 1,
-        numForks: numForks + (isFork ? 1 : 0),
-        tree: this.generateTree()
-      }));
-    }
-  }
-
-  // on mount, create the subscriptions for heads & finalized
-  public async componentDidMount (): Promise<void> {
-    const { api } = this.props;
-
-    this._subFinHead = await api.rpc.chain.subscribeFinalizedHeads(this.addFinalized);
-    this._subNewHead = await api.rpc.chain.subscribeNewHeads(this.addHeader);
-  }
-
-  // unsubscribe when we are unmounting
-  public componentWillUnmount (): void {
-    if (this._subFinHead) {
-      this._subFinHead();
-    }
-
-    if (this._subNewHead) {
-      this._subNewHead();
+      _finalize(hdr.parent);
     }
-  }
-
-  // render the acual component
-  public render (): React.ReactNode {
-    const { className, t } = this.props;
-    const { numBlocks, numForks, tree } = this.state;
-
-    if (!tree) {
-      return null;
-    }
-
-    return (
-      <div className={className}>
-        <SummaryBox>
-          <section>
-            <CardSummary label={t('blocks')}>{formatNumber(numBlocks)}</CardSummary>
-            <CardSummary label={t('forks')}>{formatNumber(numForks)}</CardSummary>
-          </section>
-        </SummaryBox>
-        <table>
-          <tbody>
-            {renderRows(createRows(tree.arr))}
-          </tbody>
-        </table>
-      </div>
-    );
-  }
+  };
 
   // adds children for a specific header, retrieving based on matching parent
-  private addChildren (base: LinkHeader, children: LinkArray): LinkArray {
-    const hdrs = (this._children.get(base.hash) || [])
-      .map((hash): LinkHeader | null => this._headers.get(hash) || null)
+  const _addChildren = (base: LinkHeader, children: LinkArray): LinkArray => {
+    const hdrs = (childrenRef.current.get(base.hash) || [])
+      .map((hash): LinkHeader | null => headersRef.current.get(hash) || null)
       .filter((hdr): boolean => !!hdr) as LinkHeader[];
 
     hdrs.forEach((hdr): void => {
-      children.push({ arr: this.addChildren(hdr, []), hdr });
+      children.push({ arr: _addChildren(hdr, []), hdr });
     });
 
     // caclulate the max height/width for this entry
@@ -368,16 +247,16 @@ class Forks extends React.PureComponent<Props, State> {
     });
 
     return children;
-  }
+  };
 
   // create a tree list from the available headers
-  private generateTree (): Link {
+  const _generateTree = (): Link => {
     const root = createLink();
 
     // add all the root entries first, we iterate from these
     // We add the root entry explicitly, it exists as per init
-    (this._children.get('root') as string[]).forEach((hash): void => {
-      const hdr = this._headers.get(hash);
+    (childrenRef.current.get('root') as string[]).forEach((hash): void => {
+      const hdr = headersRef.current.get(hash);
 
       // if this fails, well, we have a bigger issue :(
       if (hdr) {
@@ -387,7 +266,7 @@ class Forks extends React.PureComponent<Props, State> {
 
     // iterate through, adding the children for each of the root nodes
     root.arr.forEach(({ arr, hdr }): void => {
-      this.addChildren(hdr, arr);
+      _addChildren(hdr, arr);
     });
 
     // align the columns with empty spacers - this aids in display
@@ -397,11 +276,107 @@ class Forks extends React.PureComponent<Props, State> {
     root.hdr.width = calcWidth(root.arr);
 
     return root;
+  };
+
+  useEffect((): () => void => {
+    let _subFinHead: UnsubFn | null = null;
+    let _subNewHead: UnsubFn | null = null;
+
+    // callback when finalized
+    const _newFinalized = (header: Header): void => {
+      _finalize(header.hash.toHex());
+    };
+
+    // callback for the subscribe headers sub
+    const _newHeader = (header: Header): void => {
+      // formatted block info
+      const bn = formatNumber(header.number);
+      const hash = header.hash.toHex();
+      const parent = header.parentHash.toHex();
+      let isFork = false;
+
+      // if this the first one?
+      if (!firstNumRef.current) {
+        firstNumRef.current = bn;
+      }
+
+      if (!headersRef.current.has(hash)) {
+        // if this is the first, add to the root entry
+        if (firstNumRef.current === bn) {
+          (childrenRef.current.get('root') as any[]).push(hash);
+        }
+
+        // add to the header map
+        // also for HeaderExtended header.author ? header.author.toString() : null
+        headersRef.current.set(hash, createHdr(bn, hash, parent, null));
+
+        // check to see if the children already has a entry
+        if (childrenRef.current.has(parent)) {
+          isFork = true;
+          (childrenRef.current.get(parent) as any[]).push(hash);
+        } else {
+          childrenRef.current.set(parent, [hash]);
+        }
+
+        // if we don't have the parent of this one, retrieve it
+        if (!headersRef.current.has(parent)) {
+          // just make sure we are not first in the list, we don't want to full chain
+          if (firstNumRef.current !== bn) {
+            console.warn(`Retrieving missing header ${header.parentHash.toHex()}`);
+
+            api.rpc.chain.getHeader(header.parentHash).then(_newHeader);
+
+            // catch the refresh on the result
+            return;
+          }
+        }
+
+        // update our counters
+        countRef.current.numBlocks++;
+
+        if (isFork) {
+          countRef.current.numForks++;
+        }
+
+        // do the magic, extract the info into something useful and add to state
+        setTree(_generateTree());
+      }
+    };
+
+    (async (): Promise<void> => {
+      _subFinHead = await api.rpc.chain.subscribeFinalizedHeads(_newFinalized);
+      _subNewHead = await api.rpc.chain.subscribeNewHeads(_newHeader);
+    })();
+
+    return (): void => {
+      _subFinHead && _subFinHead();
+      _subNewHead && _subNewHead();
+    };
+  }, []);
+
+  if (!tree) {
+    return null;
   }
+
+  return (
+    <div className={className}>
+      <SummaryBox>
+        <section>
+          <CardSummary label={t('blocks')}>{formatNumber(countRef.current.numBlocks)}</CardSummary>
+          <CardSummary label={t('forks')}>{formatNumber(countRef.current.numForks)}</CardSummary>
+        </section>
+      </SummaryBox>
+      <table>
+        <tbody>
+          {renderRows(createRows(tree.arr))}
+        </tbody>
+      </table>
+    </div>
+  );
 }
 
-export default withMulti(
-  styled(Forks as React.ComponentClass<Props>)`
+export default translate(
+  styled(Forks)`
     margin-bottom: 1.5rem;
 
     table {
@@ -498,7 +473,5 @@ export default withMulti(
         }
       }
     }
-  `,
-  translate,
-  withApi
+  `
 );

+ 47 - 3
packages/app-explorer/src/NodeInfo/Peers.tsx

@@ -6,6 +6,7 @@ import { PeerInfo } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
 
 import React from 'react';
+import styled from 'styled-components';
 import { formatNumber } from '@polkadot/util';
 
 import translate from './translate';
@@ -27,9 +28,9 @@ const renderPeer = (peer: PeerInfo): React.ReactNode => {
   );
 };
 
-function Peers ({ peers, t }: Props): React.ReactElement<Props> {
+function Peers ({ className, peers, t }: Props): React.ReactElement<Props> {
   return (
-    <section className='status--Peers'>
+    <section className={`status--Peers ${className}`}>
       <h1>{t('connected peers')}</h1>
       {peers && peers.length
         ? (
@@ -62,4 +63,47 @@ function Peers ({ peers, t }: Props): React.ReactElement<Props> {
   );
 }
 
-export default translate(Peers);
+export default translate(
+  styled(Peers)`
+    table {
+      width: 100%;
+
+      td, th {
+        padding: 0.25rem 0.5rem;
+        text-align: left;
+        white-space: nowrap;
+
+        &.hash {
+          font-family: monospace;
+          max-width: 0;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          width: 100%;
+        }
+
+        &.number {
+          text-align: right;
+        }
+
+        &.peerid {
+          font-style: italic;
+          text-align: left;
+        }
+
+        &.roles {
+          text-align: center;
+        }
+      }
+    }
+
+    tbody {
+      tr {
+        width: 100%;
+
+        &:nth-child(odd) {
+          background-color: #f2f2f2;
+        }
+      }
+    }
+  `
+);

+ 0 - 46
packages/app-explorer/src/NodeInfo/index.css

@@ -1,46 +0,0 @@
-/* Copyright 2017-2019 @polkadot/app-nodeinfo authors & contributors
-/* This software may be modified and distributed under the terms
-/* of the Apache-2.0 license. See the LICENSE file for details. */
-
-.status--Peers {
-  table {
-    width: 100%;
-
-    td, th {
-      padding: 0.25rem 0.5rem;
-      text-align: left;
-      white-space: nowrap;
-
-      &.hash {
-        font-family: monospace;
-        max-width: 0;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        width: 100%;
-      }
-
-      &.number {
-        text-align: right;
-      }
-
-      &.peerid {
-        font-style: italic;
-        text-align: left;
-      }
-
-      &.roles {
-        text-align: center;
-      }
-    }
-  }
-
-  tbody {
-    tr {
-      width: 100%;
-
-      &:nth-child(odd) {
-        background-color: #f2f2f2;
-      }
-    }
-  }
-}

+ 0 - 2
packages/app-explorer/src/NodeInfo/index.tsx

@@ -9,8 +9,6 @@ import React, { useContext, useEffect, useState } from 'react';
 import { ApiPromise } from '@polkadot/api';
 import { ApiContext } from '@polkadot/react-api';
 
-import './index.css';
-
 import Extrinsics from '../BlockInfo/Extrinsics';
 import Peers from './Peers';
 import Summary from './Summary';

+ 34 - 55
packages/app-explorer/src/Query.tsx

@@ -4,9 +4,9 @@
 
 import { I18nProps } from '@polkadot/react-components/types';
 
-import React from 'react';
+import React, { useState } from 'react';
 import styled from 'styled-components';
-import { Button, FilterOverlay, Input, TxComponent } from '@polkadot/react-components';
+import { Button, FilterOverlay, Input } from '@polkadot/react-components';
 import { isHex } from '@polkadot/util';
 
 import translate from './translate';
@@ -17,68 +17,47 @@ interface Props extends I18nProps {
 
 interface State {
   value: string;
-  isNumber: boolean;
   isValid: boolean;
 }
 
-class Query extends TxComponent<Props, State> {
-  public constructor (props: Props) {
-    super(props);
+function stateFromValue (value: string): State {
+  const isValidHex = isHex(value, 256);
+  const isNumber = !isValidHex && /^\d+$/.test(value);
 
-    const { value } = this.props;
-
-    this.state = this.stateFromValue(value || '');
-  }
-
-  public render (): React.ReactNode {
-    const { className, t } = this.props;
-    const { value, isValid } = this.state;
-
-    return (
-      <FilterOverlay className={className}>
-        <Input
-          className='explorer--query'
-          defaultValue={this.props.value}
-          isError={!isValid && value.length !== 0}
-          placeholder={t('block hash or number to query')}
-          onChange={this.setHash}
-          onEnter={this.submit}
-          withLabel={false}
-        >
-          <Button
-            icon='play'
-            onClick={this.onQuery}
-            ref={this.button}
-          />
-        </Input>
-      </FilterOverlay>
-    );
-  }
-
-  private setHash = (value: string): void => {
-    this.setState(
-      this.stateFromValue(value)
-    );
-  }
+  return {
+    value,
+    isValid: isValidHex || isNumber
+  };
+}
 
-  private onQuery = (): void => {
-    const { isValid, value } = this.state;
+function Query ({ className, t, value: propsValue }: Props): React.ReactElement<Props> {
+  const [{ value, isValid }, setState] = useState(stateFromValue(propsValue || ''));
 
+  const _setHash = (value: string): void => setState(stateFromValue(value));
+  const _onQuery = (): void => {
     if (isValid && value.length !== 0) {
       window.location.hash = `/explorer/query/${value}`;
     }
-  }
-
-  private stateFromValue (value: string): State {
-    const isValidHex = isHex(value, 256);
-    const isNumber = !isValidHex && /^\d+$/.test(value);
-
-    return {
-      value,
-      isNumber,
-      isValid: isValidHex || isNumber
-    };
-  }
+  };
+
+  return (
+    <FilterOverlay className={className}>
+      <Input
+        className='explorer--query'
+        defaultValue={propsValue}
+        isError={!isValid && value.length !== 0}
+        placeholder={t('block hash or number to query')}
+        onChange={_setHash}
+        onEnter={_onQuery}
+        withLabel={false}
+      >
+        <Button
+          icon='play'
+          onClick={_onQuery}
+        />
+      </Input>
+    </FilterOverlay>
+  );
 }
 
 export default translate(styled(Query)`

+ 0 - 5
packages/app-explorer/src/index.css

@@ -2,11 +2,6 @@
 /* This software may be modified and distributed under the terms
 /* of the Apache-2.0 license. See the LICENSE file for details. */
 
-.explorer--BlockByHash-block {
-  flex: 0 0 50%;
-  min-width: 0;
-}
-
 .explorer--Container {
   color: inherit;
 

+ 22 - 30
packages/app-explorer/src/index.tsx

@@ -8,8 +8,6 @@ import { AppProps, BareProps, I18nProps } from '@polkadot/react-components/types
 import { EventRecord } from '@polkadot/types/interfaces';
 import { KeyedEvent } from './types';
 
-import './index.css';
-
 import React, { useContext, useEffect, useState } from 'react';
 import { Route, Switch } from 'react-router';
 import styled from 'styled-components';
@@ -41,31 +39,27 @@ function ExplorerApp ({ basePath, className, newEvents, newHeader, t }: Props):
   useEffect((): void => {
     const newEventHash = xxhashAsHex(stringToU8a(JSON.stringify(newEvents)));
 
-    if (newEventHash === prevEventHash || !newEvents) {
-      return;
+    if (newEventHash !== prevEventHash && newEvents) {
+      setEvents({
+        prevEventHash: newEventHash,
+        events: newEvents.concat(events).filter((_, index): boolean => index < MAX_ITEMS)
+      });
     }
-
-    setEvents({
-      prevEventHash: newEventHash,
-      events: newEvents.concat(events).filter((_, index): boolean => index < MAX_ITEMS)
-    });
   }, [newEvents]);
 
   useEffect((): void => {
-    if (!newHeader) {
-      return;
+    if (newHeader) {
+      setHeaders(
+        headers
+          .filter((old, index): boolean => index < MAX_ITEMS && old.number.unwrap().lt(newHeader.number.unwrap()))
+          .reduce((next, header): HeaderExtended[] => {
+            next.push(header);
+
+            return next;
+          }, [newHeader])
+          .sort((a, b): number => b.number.unwrap().cmp(a.number.unwrap()))
+      );
     }
-
-    setHeaders(
-      headers
-        .filter((old, index): boolean => index < MAX_ITEMS && old.number.unwrap().lt(newHeader.number.unwrap()))
-        .reduce((next, header): HeaderExtended[] => {
-          next.push(header);
-
-          return next;
-        }, [newHeader])
-        .sort((a, b): number => b.number.unwrap().cmp(a.number.unwrap()))
-    );
   }, [newHeader]);
 
   return (
@@ -105,14 +99,12 @@ function ExplorerApp ({ basePath, className, newEvents, newHeader, t }: Props):
         <Route path={`${basePath}/query/:value`} component={BlockInfo} />
         <Route path={`${basePath}/query`} component={BlockInfo} />
         <Route path={`${basePath}/node`} component={NodeInfo} />
-        <Route
-          render={(): React.ReactElement<{}> =>
-            <Main
-              events={events}
-              headers={headers}
-            />
-          }
-        />
+        <Route render={(): React.ReactElement<{}> => (
+          <Main
+            events={events}
+            headers={headers}
+          />
+        )} />
       </Switch>
     </main>
   );

+ 5 - 5
packages/app-extrinsics/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-extrinsics",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,9 +10,9 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-params": "^0.36.0-beta.30",
-    "@polkadot/react-signer": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-params": "^0.36.0-beta.65",
+    "@polkadot/react-signer": "^0.36.0-beta.65"
   }
 }

+ 2 - 2
packages/app-extrinsics/src/Selection.tsx

@@ -26,7 +26,7 @@ interface State {
   isValidUnsigned: boolean;
   method: Call | null;
   accountNonce?: BN;
-  accountId?: string;
+  accountId?: string | null;
 }
 
 class Selection extends TxComponent<Props, State> {
@@ -126,7 +126,7 @@ class Selection extends TxComponent<Props, State> {
     this.nextState({ accountNonce });
   }
 
-  private onChangeSender = (accountId: string): void => {
+  private onChangeSender = (accountId: string | null): void => {
     this.nextState({ accountId, accountNonce: new BN(0) });
   }
 

+ 3 - 3
packages/app-generic-asset/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-generic-asset",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A basic GenericAsset transfer app",
   "main": "index.js",
   "scripts": {},
@@ -10,7 +10,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 2 - 2
packages/app-generic-asset/src/Transfer.tsx

@@ -152,11 +152,11 @@ class Transfer extends React.PureComponent<Props> {
     this.nextState({ amount });
   }
 
-  private onChangeFrom = (senderId: string): void => {
+  private onChangeFrom = (senderId: string | null): void => {
     this.nextState({ senderId });
   }
 
-  private onChangeTo = (recipientId: string): void => {
+  private onChangeTo = (recipientId: string | null): void => {
     this.nextState({ recipientId });
   }
 

+ 3 - 3
packages/app-js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-js",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A simple JavaScript console for playing with the API",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
     "snappyjs": "^0.6.0"
   }
 }

+ 4 - 4
packages/app-parachains/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-parachains",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "Parachains",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65"
   }
 }

+ 4 - 8
packages/app-parachains/src/Overview/Parachains.tsx

@@ -17,19 +17,15 @@ interface Props extends I18nProps {
   parachains_parachains?: BN[];
 }
 
-function Parachains ({ parachains_parachains = [], t }: Props): React.ReactElement<Props> {
+function Parachains ({ parachains_parachains, t }: Props): React.ReactElement<Props> {
   return (
     <Column
       emptyText={t('no deployed parachains')}
       headerText={t('parachains')}
     >
-      {
-        parachains_parachains.length
-          ? parachains_parachains.map((paraId): React.ReactNode =>
-            <Parachain key={paraId.toString()} paraId={paraId} />
-          )
-          : null
-      }
+      {parachains_parachains && parachains_parachains.map((paraId): React.ReactNode =>
+        <Parachain key={paraId.toString()} paraId={paraId} />
+      )}
     </Column>
   );
 }

+ 4 - 4
packages/app-settings/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-settings",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "Settings management",
   "main": "index.js",
   "scripts": {},
@@ -10,9 +10,9 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30",
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65",
     "query-string": "^6.8.3"
   }
 }

+ 92 - 115
packages/app-settings/src/Developer.tsx

@@ -4,7 +4,7 @@
 
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import store from 'store';
 import styled from 'styled-components';
 import { getTypeRegistry } from '@polkadot/types';
@@ -15,96 +15,45 @@ import translate from './translate';
 
 interface Props extends AppProps, I18nProps {}
 
-interface State {
-  code: string;
-  isJsonValid: boolean;
-  isTypesValid: boolean;
-  types: Record<string, any> | {};
-  typesPlaceholder?: string;
-}
-
-class Developer extends React.PureComponent<Props, State> {
-  private defaultCode = '{\n\n}';
+const EMPTY_CODE = '{\n\n}';
+const EMPTY_TYPES = {};
 
-  public constructor (props: Props) {
-    super(props);
+function Developer ({ className, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [code, setCode] = useState(EMPTY_CODE);
+  const [isJsonValid, setIsJsonValid] = useState(true);
+  const [isTypesValid, setIsTypesValid] = useState(true);
+  const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
+  const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
 
+  useEffect((): void => {
     const types = store.get('types') || {};
-    const names = Object.keys(types);
 
-    this.state = {
-      code: Object.keys(types).length ? JSON.stringify(types, null, 2) : this.defaultCode,
-      isJsonValid: true,
-      isTypesValid: true,
-      types: names.length ? types : {},
-      typesPlaceholder: names.length
-        ? names.join(', ')
-        : undefined
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { className, t } = this.props;
-    const { code, isJsonValid, isTypesValid, types, typesPlaceholder } = this.state;
-    const typesHasNoEntries = Object.keys(types).length === 0;
-
-    return (
-      <div className={className}>
-        <div className='ui--row'>
-          <div className='full'>
-            <InputFile
-              clearContent={typesHasNoEntries && isTypesValid}
-              help={t('Save the type definitions for your custom structures as key-value pairs in a valid JSON file. The key should be the name of your custom structure and the value an object containing your type definitions.')}
-              isError={!isTypesValid}
-              label={t('Additional types as a JSON file (or edit below)')}
-              onChange={this.onChangeTypes}
-              placeholder={typesPlaceholder}
-            />
-          </div>
-        </div>
-        <div className='ui--row'>
-          <div className='full'>
-            <Editor
-              className='editor'
-              code={code}
-              isValid={isJsonValid}
-              onEdit={this.onEditTypes}
-            />
-          </div>
-        </div>
-        <Button.Group>
-          <Button
-            isDisabled={typesHasNoEntries}
-            isNegative
-            onClick={this.clearTypes}
-            label={t('Reset')}
-            icon='sync'
-          />
-          <Button.Or />
-          <Button
-            isDisabled={!isTypesValid || !isJsonValid || typesHasNoEntries}
-            isPrimary
-            onClick={this.saveDeveloper}
-            label={t('Save')}
-            icon='save'
-          />
-        </Button.Group>
-      </div>
-    );
-  }
-
-  private clearTypes = (): void => {
+    if (Object.keys(types).length) {
+      setCode(JSON.stringify(types, null, 2));
+      setTypes({});
+      setTypesPlaceholder(Object.keys(types).join(', '));
+    }
+  }, []);
+
+  const _setState = ({ code, isJsonValid, isTypesValid, types, typesPlaceholder }: { code: string; isJsonValid: boolean; isTypesValid: boolean; types: Record<string, any>; typesPlaceholder: string | null }): void => {
+    setCode(code);
+    setIsJsonValid(isJsonValid);
+    setIsTypesValid(isTypesValid);
+    setTypes(types);
+    setTypesPlaceholder(typesPlaceholder);
+  };
+  const _clearTypes = (): void => {
     store.remove('types');
-    this.setState({
-      code: this.defaultCode,
+
+    _setState({
+      code: EMPTY_CODE,
       isJsonValid: true,
       isTypesValid: true,
-      types: {},
-      typesPlaceholder: undefined
+      types: EMPTY_TYPES,
+      typesPlaceholder: null
     });
-  }
-
-  private onChangeTypes = (data: Uint8Array): void => {
+  };
+  const _onChangeTypes = (data: Uint8Array): void => {
     const code = u8aToString(data);
 
     try {
@@ -113,7 +62,7 @@ class Developer extends React.PureComponent<Props, State> {
 
       console.log('Detected types:', typesPlaceholder);
 
-      this.setState({
+      _setState({
         code,
         isJsonValid: true,
         isTypesValid: true,
@@ -123,7 +72,7 @@ class Developer extends React.PureComponent<Props, State> {
     } catch (error) {
       console.error('Error registering types:', error);
 
-      this.setState({
+      _setState({
         code,
         isJsonValid: false,
         isTypesValid: false,
@@ -131,56 +80,84 @@ class Developer extends React.PureComponent<Props, State> {
         typesPlaceholder: error.message
       });
     }
-  }
-
-  private onEditTypes = (code: string): void => {
+  };
+  const _onEditTypes = (code: string): void => {
     try {
       if (!isJsonObject(code)) {
-        throw Error(this.props.t('This is not a valid JSON object.'));
+        throw Error(t('This is not a valid JSON object.'));
       }
 
-      this.setState((prevState: State): Pick<State, never> => ({
-        ...prevState,
-        code,
-        isJsonValid: true
-      }));
-
-      this.onChangeTypes(stringToU8a(code));
+      _onChangeTypes(stringToU8a(code));
     } catch (e) {
-      this.setState((prevState: State): Pick<State, never> => ({
-        ...prevState,
-        code,
-        isJsonValid: false,
-        typesPlaceholder: e.message
-      }));
+      setCode(code);
+      setIsJsonValid(false);
+      setTypesPlaceholder(e.message);
     }
-  }
-
-  private saveDeveloper = (): void => {
-    const { onStatusChange, t } = this.props;
-    const { types } = this.state;
-
+  };
+  const _saveDeveloper = (): void => {
     try {
       getTypeRegistry().register(types);
-
       store.set('types', types);
-
-      this.setState({ isTypesValid: true });
-
+      setIsTypesValid(true);
       onStatusChange({
         status: 'success',
         action: t('Your custom types have been added')
       });
     } catch (error) {
       console.error(error);
-      this.setState({ isTypesValid: false });
-
+      setIsTypesValid(false);
       onStatusChange({
         status: 'error',
         action: t(`Error saving your custom types. ${error.message}`)
       });
     }
-  }
+  };
+
+  const typesHasNoEntries = Object.keys(types).length === 0;
+
+  return (
+    <div className={className}>
+      <div className='ui--row'>
+        <div className='full'>
+          <InputFile
+            clearContent={typesHasNoEntries && isTypesValid}
+            help={t('Save the type definitions for your custom structures as key-value pairs in a valid JSON file. The key should be the name of your custom structure and the value an object containing your type definitions.')}
+            isError={!isTypesValid}
+            label={t('Additional types as a JSON file (or edit below)')}
+            onChange={_onChangeTypes}
+            placeholder={typesPlaceholder}
+          />
+        </div>
+      </div>
+      <div className='ui--row'>
+        <div className='full'>
+          <Editor
+            className='editor'
+            code={code}
+            isValid={isJsonValid}
+            onEdit={_onEditTypes}
+          />
+        </div>
+      </div>
+      <Button.Group>
+        <Button
+          isDisabled={typesHasNoEntries}
+          isNegative
+          onClick={_clearTypes}
+          label={t('Reset')}
+          icon='sync'
+        />
+        <Button.Or />
+        <Button
+          isDisabled={!isTypesValid || !isJsonValid || typesHasNoEntries}
+          isPrimary
+          onClick={_saveDeveloper}
+          label={t('Save')}
+          icon='save'
+        />
+      </Button.Group>
+    </div>
+  );
 }
 
 export default translate(

+ 1 - 6
packages/app-settings/src/General.tsx

@@ -5,7 +5,6 @@
 import { I18nProps } from '@polkadot/react-components/types';
 import { Option } from './types';
 
-import queryString from 'query-string';
 import React, { useEffect, useState } from 'react';
 import { isLedgerCapable } from '@polkadot/react-api';
 import { Button, Dropdown } from '@polkadot/react-components';
@@ -20,10 +19,6 @@ interface Props extends I18nProps{
   onClose?: () => void;
 }
 
-// check for a ledger=... string, acivate
-const urlOptions = queryString.parse(location.href.split('?')[1]);
-const WITH_LEDGER = !!urlOptions.ledger;
-
 const prefixOptions = uiSettings.availablePrefixes.map((o): Option => createOption(o, ['default']));
 const iconOptions = uiSettings.availableIcons.map((o): Option => createIdenticon(o, ['default']));
 const ledgerConnOptions = uiSettings.availableLedgerConn;
@@ -90,7 +85,7 @@ function General ({ className, isModalContent, onClose, t }: Props): React.React
               options={uiSettings.availableUIModes}
             />
           </div>
-          {WITH_LEDGER && isLedgerCapable() && (
+          {isLedgerCapable() && (
             <div className='ui--row'>
               <Dropdown
                 defaultValue={ledgerConn}

+ 5 - 5
packages/app-staking/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-staking",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A basic staking app",
   "main": "index.js",
   "scripts": {},
@@ -10,9 +10,9 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/app-explorer": "^0.36.0-beta.30",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/app-explorer": "^0.36.0-beta.65",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65"
   }
 }

+ 138 - 0
packages/app-staking/src/Actions/Account/InjectKeys.tsx

@@ -0,0 +1,138 @@
+// Copyright 2017-2019 @polkadot/app-staking authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { I18nProps } from '@polkadot/react-components/types';
+import { KeypairType } from '@polkadot/util-crypto/types';
+
+import React, { useContext, useEffect, useState } from 'react';
+import { Button, Dropdown, Icon, Input, Modal } from '@polkadot/react-components';
+import { QueueContext } from '@polkadot/react-components/Status';
+import keyring from '@polkadot/ui-keyring';
+import { assert, u8aToHex } from '@polkadot/util';
+import { keyExtractSuri, mnemonicValidate } from '@polkadot/util-crypto';
+
+import translate from '../../translate';
+
+interface Props extends I18nProps {
+  isOpen?: boolean;
+  onClose: () => void;
+}
+
+const CRYPTO_MAP: Record<string, KeypairType[]> = {
+  aura: ['ed25519', 'sr25519'],
+  babe: ['sr25519'],
+  gran: ['ed25519'],
+  imon: ['ed25519', 'sr25519'],
+  para: ['sr25519']
+};
+
+const EMPTY_KEY = '0x';
+
+function InjectKeys ({ isOpen = true, onClose, t }: Props): React.ReactElement<Props> | null {
+  const { queueRpc } = useContext(QueueContext);
+  // this needs to align with what is set as the first value in `type`
+  const [crypto, setCrypto] = useState<KeypairType>('sr25519');
+  const [publicKey, setPublicKey] = useState(EMPTY_KEY);
+  const [suri, setSuri] = useState('');
+  const [type, setType] = useState('babe');
+
+  useEffect((): void => {
+    setCrypto(CRYPTO_MAP[type][0]);
+  }, [type]);
+
+  useEffect((): void => {
+    try {
+      const { phrase } = keyExtractSuri(suri);
+
+      assert(mnemonicValidate(phrase), 'Invalid mnemonic phrase');
+
+      setPublicKey(u8aToHex(keyring.createFromUri(suri, {}, crypto).publicKey));
+    } catch (error) {
+      setPublicKey(EMPTY_KEY);
+    }
+  }, [crypto, suri]);
+
+  if (!isOpen) {
+    return null;
+  }
+
+  const _onSubmit = (): void => {
+    queueRpc({
+      rpc: { section: 'author', method: 'insertKey' } as any,
+      values: [type, suri, publicKey]
+    });
+  };
+  const _cryptoOptions = CRYPTO_MAP[type].map((value): { text: string; value: KeypairType } => ({
+    text: value === 'ed25519'
+      ? t('ed25519, Edwards')
+      : t('sr15519, Schnorrkel'),
+    value
+  }));
+
+  return (
+    <Modal
+      dimmer='inverted'
+      open
+      size='small'
+    >
+      <Modal.Header>
+        {t('Inject Keys')}
+      </Modal.Header>
+      <Modal.Content>
+        <Input
+          isError={publicKey.length !== 66}
+          label={t('suri (seed & derivation)')}
+          onChange={setSuri}
+          value={suri}
+        />
+        <Dropdown
+          label={t('key type to set')}
+          onChange={setType}
+          options={[
+            { text: t('Aura'), value: 'aura' },
+            { text: t('Babe'), value: 'babe' },
+            { text: t('Grandpa'), value: 'gran' },
+            { text: t('I\'m Online'), value: 'imon' },
+            { text: t('Parachains'), value: 'para' }
+          ]}
+          value={type}
+        />
+        <Dropdown
+          isDisabled={_cryptoOptions.length === 1}
+          label={t('crypto type to use')}
+          onChange={setCrypto}
+          options={_cryptoOptions}
+          value={crypto}
+        />
+        <Input
+          isDisabled
+          label={t('generated public key')}
+          value={publicKey}
+        />
+        <article className='warning'>
+          <div><Icon name='warning sign' />{t('This operation will submit the seed via an RPC call. Do not perform this operation on a public RPC node, but ensure that the node is local, connected to your validator and secure.')}</div>
+        </article>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='sign-in'
+            isPrimary
+            label={t('Submit key')}
+            onClick={_onSubmit}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(InjectKeys);

+ 1 - 1
packages/app-staking/src/Actions/Account/SetControllerAccount.tsx

@@ -110,7 +110,7 @@ class SetControllerAccount extends TxComponent<Props, State> {
     );
   }
 
-  private onChangeController = (controllerId: string): void => {
+  private onChangeController = (controllerId: string | null): void => {
     this.setState({ controllerId });
   }
 

+ 90 - 151
packages/app-staking/src/Actions/Account/SetSessionAccount.tsx

@@ -2,17 +2,16 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
-import React from 'react';
+import React, { useContext, useState } from 'react';
 import { Button, InputAddress, Input, Modal, TxButton } from '@polkadot/react-components';
-import { withApi, withMulti } from '@polkadot/react-api';
+import { ApiContext } from '@polkadot/react-api';
 
 import ValidationSessionKey from './InputValidationSessionKey';
 import translate from '../../translate';
 
-interface Props extends I18nProps, ApiProps {
+interface Props extends I18nProps {
   controllerId: string;
   isOpen: boolean;
   onClose: () => void;
@@ -20,159 +19,99 @@ interface Props extends I18nProps, ApiProps {
   stashId: string;
 }
 
-interface State {
-  // TODO remove once we drop v1 support
-  ed25519: string | null;
-  ed25519Error: string | null;
-  keys: string | null;
-}
-
-class SetSessionKey extends React.PureComponent<Props, State> {
-  public state: State;
+const EMPTY_PROOF = new Uint8Array();
 
-  public constructor (props: Props) {
-    super(props);
+function SetSessionKey ({ controllerId, isOpen, onClose, sessionIds, stashId, t }: Props): React.ReactElement<Props> | null {
+  const { isSubstrateV2 } = useContext(ApiContext);
+  const [ed25519, setEd25519] = useState<string | null>(null);
+  const [ed25519Error, setEd25519Error] = useState<string | null>(sessionIds[0] || controllerId);
+  const [keys, setKeys] = useState<string | null>(null);
 
-    this.state = {
-      ed25519: props.sessionIds[0] || props.controllerId,
-      ed25519Error: null,
-      keys: null
-    };
+  if (!isOpen) {
+    return null;
   }
 
-  public render (): React.ReactNode {
-    const { controllerId, isOpen, isSubstrateV2, onClose, t } = this.props;
-    const { ed25519, ed25519Error, keys } = this.state;
-
-    if (!isOpen) {
-      return null;
-    }
-
-    const hasError = isSubstrateV2
-      ? !keys
-      : (!ed25519 || !!ed25519Error);
-
-    return (
-      <Modal
-        className='staking--SetSessionAccount'
-        dimmer='inverted'
-        open
-        size='small'
-      >
-        {this.renderContent()}
-        <Modal.Actions>
-          <Button.Group>
-            <Button
-              isNegative
-              onClick={onClose}
-              label={t('Cancel')}
-              icon='cancel'
-            />
-            <Button.Or />
-            <TxButton
-              accountId={controllerId}
-              isDisabled={hasError}
-              isPrimary
-              label={t('Set Session Key')}
-              icon='sign-in'
-              onClick={onClose}
-              params={
-                isSubstrateV2
-                  ? [keys, new Uint8Array()]
-                  : [ed25519]
-              }
-              tx={
-                isSubstrateV2
-                  ? 'session.setKeys'
-                  : 'session.setKey'
-              }
-            />
-          </Button.Group>
-        </Modal.Actions>
-      </Modal>
-    );
-  }
-
-  private renderContent (): React.ReactNode {
-    const { controllerId, isSubstrateV2, t } = this.props;
-
-    return (
-      <>
-        <Modal.Header>
-          {t('Set Session Key')}
-        </Modal.Header>
-        <Modal.Content className='ui--signer-Signer-Content'>
-          <InputAddress
-            className='medium'
-            defaultValue={controllerId}
-            isDisabled
-            label={t('controller account')}
-          />
-          {
-            isSubstrateV2
-              ? this.renderV2Keys()
-              : this.renderV1Keys()
-          }
-        </Modal.Content>
-      </>
-    );
-  }
-
-  private renderV1Keys (): React.ReactNode {
-    const { controllerId, stashId, t } = this.props;
-    const { ed25519 } = this.state;
-
-    return (
-      <>
+  const hasError = isSubstrateV2
+    ? !keys
+    : (!ed25519 || !!ed25519Error);
+
+  return (
+    <Modal
+      className='staking--SetSessionAccount'
+      dimmer='inverted'
+      open
+      size='small'
+    >
+      <Modal.Header>
+        {t('Set Session Key')}
+      </Modal.Header>
+      <Modal.Content className='ui--signer-Signer-Content'>
         <InputAddress
           className='medium'
-          help={t('Changing the key only takes effect at the start of the next session. If validating, it must be an ed25519 key.')}
-          label={t('Session key (ed25519)')}
-          onChange={this.onChangeEd25519}
-          value={ed25519}
-        />
-        <ValidationSessionKey
-          controllerId={controllerId}
-          onError={this.onSessionErrorEd25519}
-          sessionId={ed25519}
-          stashId={stashId}
-        />
-      </>
-    );
-  }
-
-  private renderV2Keys (): React.ReactNode {
-    const { t } = this.props;
-    const { keys } = this.state;
-
-    return (
-      <>
-        <Input
-          className='medium'
-          help={t('Changing the key only takes effect at the start of the next session. The input here is generates from the author_rotateKeys command')}
-          isError={!keys}
-          label={t('Keys from rotateKeys')}
-          onChange={this.onChangeKeys}
+          defaultValue={controllerId}
+          isDisabled
+          label={t('controller account')}
         />
-      </>
-    );
-  }
-
-  private onChangeEd25519 = (ed25519: string | null): void => {
-    this.setState({ ed25519 });
-  }
-
-  private onChangeKeys = (keys: string): void => {
-    this.setState({ keys: keys || null });
-  }
-
-  private onSessionErrorEd25519 = (ed25519Error: string | null): void => {
-    this.setState({ ed25519Error });
-  }
+        {isSubstrateV2
+          ? (
+            <Input
+              className='medium'
+              help={t('Changing the key only takes effect at the start of the next session. The input here is generates from the author_rotateKeys command')}
+              isError={!keys}
+              label={t('Keys from rotateKeys')}
+              onChange={setKeys}
+            />
+          )
+          : (
+            <>
+              <InputAddress
+                className='medium'
+                help={t('Changing the key only takes effect at the start of the next session. If validating, it must be an ed25519 key.')}
+                label={t('Session key (ed25519)')}
+                onChange={setEd25519}
+                value={ed25519}
+              />
+              <ValidationSessionKey
+                controllerId={controllerId}
+                onError={setEd25519Error}
+                sessionId={ed25519}
+                stashId={stashId}
+              />
+            </>
+          )
+        }
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            isNegative
+            onClick={onClose}
+            label={t('Cancel')}
+            icon='cancel'
+          />
+          <Button.Or />
+          <TxButton
+            accountId={controllerId}
+            isDisabled={hasError}
+            isPrimary
+            label={t('Set Session Key')}
+            icon='sign-in'
+            onClick={onClose}
+            params={
+              isSubstrateV2
+                ? [keys, EMPTY_PROOF]
+                : [ed25519]
+            }
+            tx={
+              isSubstrateV2
+                ? 'session.setKeys'
+                : 'session.setKey'
+            }
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
 }
 
-export default withMulti(
-  SetSessionKey,
-  translate,
-  withApi
-);
+export default translate(SetSessionKey);

+ 89 - 111
packages/app-staking/src/Actions/Account/index.tsx

@@ -16,6 +16,7 @@ import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, Online
 import { withCalls, withMulti } from '@polkadot/react-api';
 
 import BondExtra from './BondExtra';
+import InjectKeys from './InjectKeys';
 import Nominate from './Nominate';
 import SetControllerAccount from './SetControllerAccount';
 import SetRewardDestination from './SetRewardDestination';
@@ -42,6 +43,7 @@ interface State {
   destination: number;
   hexSessionId: string | null;
   isBondExtraOpen: boolean;
+  isInjectOpen: boolean;
   isNominateOpen: boolean;
   isSetControllerAccountOpen: boolean;
   isSetRewardDestinationOpen: boolean;
@@ -72,6 +74,7 @@ class Account extends React.PureComponent<Props, State> {
     destination: 0,
     hexSessionId: null,
     isBondExtraOpen: false,
+    isInjectOpen: false,
     isNominateOpen: false,
     isSetControllerAccountOpen: false,
     isSettingPopupOpen: false,
@@ -122,7 +125,7 @@ class Account extends React.PureComponent<Props, State> {
 
   public render (): React.ReactNode {
     const { className, isSubstrateV2, t } = this.props;
-    const { stashId } = this.state;
+    const { controllerId, hexSessionId, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, sessionIds, stashId } = this.state;
 
     if (!stashId) {
       return null;
@@ -148,100 +151,82 @@ class Account extends React.PureComponent<Props, State> {
           unlocking: false
         }}
       >
-        {this.renderBondExtra()}
+        <BondExtra
+          controllerId={controllerId}
+          isOpen={isBondExtraOpen}
+          onClose={this.toggleBondExtra}
+          stashId={stashId}
+        />
+        <Unbond
+          controllerId={controllerId}
+          isOpen={isUnbondOpen}
+          onClose={this.toggleUnbond}
+          stashId={stashId}
+        />
+        {isInjectOpen && (
+          <InjectKeys onClose={this.toggleInject} />
+        )}
         {this.renderSetValidatorPrefs()}
         {this.renderNominate()}
         {this.renderSetControllerAccount()}
         {this.renderSetRewardDestination()}
         {this.renderSetSessionAccount()}
-        {this.renderUnbond()}
         {this.renderValidate()}
         <div className={className}>
           <div className='staking--Accounts'>
             {this.renderControllerAccount()}
-            {!isSubstrateV2 && this.renderSessionAccount()}
+            {!isSubstrateV2 && sessionIds.length && (
+              <div className='staking--Account-detail actions'>
+                <AddressRow
+                  label={t('session')}
+                  value={sessionIds[0]}
+                  withAddressOrName
+                  withBalance={{
+                    available: true,
+                    bonded: false,
+                    free: false,
+                    redeemable: false,
+                    unlocking: false
+                  }}
+                />
+              </div>
+            )}
           </div>
           <div className='staking--Infos'>
             <div className='staking--balances'>
-              {this.renderInfos()}
+              <AddressInfo
+                address={stashId}
+                withBalance={{
+                  available: false,
+                  bonded: true,
+                  free: false,
+                  redeemable: true,
+                  unlocking: true
+                }}
+                withRewardDestination
+                withHexSessionId={ isSubstrateV2 && hexSessionId !== '0x' && hexSessionId}
+                withValidatorPrefs={isStashValidating}
+              />
             </div>
-            {this.renderNominee()}
+            {nominees && nominees.length !== 0 && (
+              <div className='staking--Account-Nominee'>
+                <label className='staking--label'>{t('nominating')}</label>
+                {nominees.map((nomineeId, index): React.ReactNode => (
+                  <AddressMini
+                    key={index}
+                    value={nomineeId}
+                    withBalance={false}
+                    withBonded
+                  />
+                ))}
+              </div>
+            )}
           </div>
         </div>
       </AddressCard>
     );
   }
 
-  private renderBondExtra (): React.ReactNode {
-    const { controllerId, isBondExtraOpen, stashId } = this.state;
-
-    return (
-      <BondExtra
-        controllerId={controllerId}
-        isOpen={isBondExtraOpen}
-        onClose={this.toggleBondExtra}
-        stashId={stashId}
-      />
-    );
-  }
-
-  private renderUnbond (): React.ReactNode {
-    const { controllerId, isUnbondOpen, stashId } = this.state;
-
-    return (
-      <Unbond
-        controllerId={controllerId}
-        isOpen={isUnbondOpen}
-        onClose={this.toggleUnbond}
-        stashId={stashId}
-      />
-    );
-  }
-
-  private renderInfos (): React.ReactNode {
-    const { isSubstrateV2 } = this.props;
-    const { hexSessionId, isStashValidating, stashId } = this.state;
-
-    return (
-      <AddressInfo
-        address={stashId}
-        withBalance={{
-          available: false,
-          bonded: true,
-          free: false,
-          redeemable: true,
-          unlocking: true
-        }}
-        withRewardDestination
-        withHexSessionId={ isSubstrateV2 && hexSessionId !== '0x' && hexSessionId}
-        withValidatorPrefs={isStashValidating}
-      />
-    );
-  }
-
-  private renderNominee (): React.ReactNode {
-    const { t } = this.props;
-    const { nominees } = this.state;
-
-    if (!nominees || !nominees.length) {
-      return null;
-    }
-
-    return (
-      <div className='staking--Account-Nominee'>
-        <label className='staking--label'>{t('nominating')}</label>
-        {nominees.map((nomineeId, index): React.ReactNode => (
-          <AddressMini
-            key={index}
-            value={nomineeId}
-            withBalance={false}
-            withBonded
-          />
-        ))}
-      </div>
-    );
-  }
-
   private renderOnlineStatus (): React.ReactNode {
     const { onlineStatus, controllerId } = this.state;
 
@@ -285,32 +270,6 @@ class Account extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderSessionAccount (): React.ReactNode {
-    const { t } = this.props;
-    const { sessionIds } = this.state;
-
-    if (!sessionIds.length) {
-      return null;
-    }
-
-    return (
-      <div className='staking--Account-detail actions'>
-        <AddressRow
-          label={t('session')}
-          value={sessionIds[0]}
-          withAddressOrName
-          withBalance={{
-            available: true,
-            bonded: false,
-            free: false,
-            redeemable: false,
-            unlocking: false
-          }}
-        />
-      </div>
-    );
-  }
-
   private renderNominate (): React.ReactNode {
     const { stashOptions } = this.props;
     const { controllerId, isNominateOpen, nominees, stashId } = this.state;
@@ -465,9 +424,9 @@ class Account extends React.PureComponent<Props, State> {
             {t('Change validator preferences')}
           </Menu.Item>
         }
-        {(!!sessionIds.length || (isSubstrateV2 && hexSessionId !== '0x')) &&
+        {!isStashNominating && (!!sessionIds.length || (isSubstrateV2 && hexSessionId !== '0x')) &&
           <Menu.Item onClick={this.toggleSetSessionAccount}>
-            {isSubstrateV2 ? t('Change session keys') : t('Change session account')}
+            {isSubstrateV2 ? t('Rotate session keys') : t('Change session account')}
           </Menu.Item>
         }
         {isStashNominating &&
@@ -475,6 +434,11 @@ class Account extends React.PureComponent<Props, State> {
             {t('Change nominee(s)')}
           </Menu.Item>
         }
+        {!isStashNominating && isSubstrateV2 &&
+          <Menu.Item onClick={this.toggleInject}>
+            {t('Inject session keys (advanced)')}
+          </Menu.Item>
+        }
       </Menu>
     );
   }
@@ -554,6 +518,12 @@ class Account extends React.PureComponent<Props, State> {
     }));
   }
 
+  private toggleInject = (): void => {
+    this.setState(({ isInjectOpen }): Pick<State, never> => ({
+      isInjectOpen: !isInjectOpen
+    }));
+  }
+
   private toggleNominate = (): void => {
     this.setState(({ isNominateOpen }): Pick<State, never> => ({
       isNominateOpen: !isNominateOpen
@@ -608,14 +578,22 @@ export default withMulti(
       width: 0px;
     }
 
-    .staking--Account-detail.actions{
-      display: inline-block;
-      vertical-align: top;
-      margin-top: .5rem;
-      margin-bottom: 1.5rem;
+    .staking--Account-detail {
+      text-align: right;
+
+      &.actions{
+        display: inline-block;
+        vertical-align: top;
+        margin-top: .5rem;
+        margin-bottom: 1.5rem;
 
-      &:last-child {
-        margin: 0;
+        &:last-child {
+          margin: 0;
+        }
+      }
+
+      .staking--label {
+        margin: 0 1.75rem -0.75rem 0;
       }
     }
 

+ 2 - 2
packages/app-staking/src/Actions/NewStake.tsx

@@ -153,7 +153,7 @@ class NewStake extends TxComponent<Props, State> {
     });
   }
 
-  private onChangeController = (controllerId: string): void => {
+  private onChangeController = (controllerId: string | null): void => {
     this.nextState({ controllerId });
   }
 
@@ -161,7 +161,7 @@ class NewStake extends TxComponent<Props, State> {
     this.nextState({ destination });
   }
 
-  private onChangeStash = (stashId: string): void => {
+  private onChangeStash = (stashId: string | null): void => {
     this.nextState({ stashId });
   }
 

+ 61 - 82
packages/app-staking/src/Overview/Address.tsx

@@ -82,8 +82,8 @@ class Address extends React.PureComponent<Props, State> {
   }
 
   public render (): React.ReactNode {
-    const { address, className, defaultName, filter } = this.props;
-    const { controllerId, stakers, stashId } = this.state;
+    const { address, className, defaultName, filter, isSubstrateV2, lastAuthor, lastBlock, t } = this.props;
+    const { controllerId, onlineStatus, sessionId, stakers, stashId } = this.state;
     const bonded = stakers && !stakers.own.isEmpty
       ? [stakers.own.unwrap(), stakers.total.unwrap().sub(stakers.own.unwrap())]
       : true;
@@ -96,54 +96,67 @@ class Address extends React.PureComponent<Props, State> {
       return null;
     }
 
+    const isAuthor = !!lastBlock && !!lastAuthor && [address, controllerId, stashId].includes(lastAuthor);
+    const nominators = this.getNominators();
+
     return (
       <AddressCard
-        buttons={this.renderKeys()}
+        buttons={
+          <div className='staking--Address-info'>
+            {isAuthor && (
+              <div className={classes(isSubstrateV2 ? 'blockNumberV2' : 'blockNumberV1')}>#{lastBlock}</div>
+            )}
+            {controllerId && (
+              <div>
+                <label className={classes('staking--label', isSubstrateV2 && !isAuthor && 'controllerSpacer')}>{t('controller')}</label>
+                <AddressMini value={controllerId} />
+              </div>
+            )}
+            {!isSubstrateV2 && sessionId && (
+              <div>
+                <label className='staking--label'>{t('session')}</label>
+                <AddressMini value={sessionId} />
+              </div>
+            )}
+          </div>
+        }
         className={className}
         defaultName={defaultName}
-        iconInfo={this.renderOnlineStatus()}
+        iconInfo={controllerId && onlineStatus && (
+          <OnlineStatus
+            accountId={controllerId}
+            value={onlineStatus}
+            tooltip
+          />
+        )}
         key={stashId || controllerId || undefined}
         value={stashId || address}
         withBalance={{ bonded }}
+        withValidatorPrefs={{ validatorPayment: true }}
       >
-        {this.renderNominators()}
+        {nominators.length !== 0 && (
+          <details>
+            <summary>
+              {t('Nominators ({{count}})', {
+                replace: {
+                  count: nominators.length
+                }
+              })}
+            </summary>
+            {nominators.map(([who, bonded]): React.ReactNode =>
+              <AddressMini
+                bonded={bonded}
+                key={who.toString()}
+                value={who}
+                withBonded
+              />
+            )}
+          </details>
+        )}
       </AddressCard>
     );
   }
 
-  private renderKeys (): React.ReactNode {
-    const { address, isSubstrateV2, lastAuthor, lastBlock, t } = this.props;
-    const { controllerId, sessionId, stashId } = this.state;
-    const isAuthor = !!lastBlock && !!lastAuthor && [address, controllerId, stashId].includes(lastAuthor);
-
-    return (
-      <div className='staking--Address-info'>
-        {isAuthor
-          ? <div className={classes(isSubstrateV2 ? 'blockNumberV2' : 'blockNumberV1')}>#{lastBlock}</div>
-          : null
-        }
-        {controllerId
-          ? (
-            <div>
-              <label className={classes('staking--label', isSubstrateV2 && !isAuthor && 'controllerSpacer')}>{t('controller')}</label>
-              <AddressMini value={controllerId} />
-            </div>
-          )
-          : null
-        }
-        {!isSubstrateV2 && sessionId
-          ? (
-            <div>
-              <label className='staking--label'>{t('session')}</label>
-              <AddressMini value={sessionId} />
-            </div>
-          )
-          : null
-        }
-      </div>
-    );
-  }
-
   private getNominators (): [AccountId, Balance][] {
     const { stakers } = this.state;
 
@@ -176,50 +189,6 @@ class Address extends React.PureComponent<Props, State> {
 
     return true;
   }
-
-  private renderNominators (): React.ReactNode {
-    const { t } = this.props;
-    const nominators = this.getNominators();
-
-    if (!nominators.length) {
-      return null;
-    }
-
-    return (
-      <details>
-        <summary>
-          {t('Nominators ({{count}})', {
-            replace: {
-              count: nominators.length
-            }
-          })}
-        </summary>
-        {nominators.map(([who, bonded]): React.ReactNode =>
-          <AddressMini
-            bonded={bonded}
-            key={who.toString()}
-            value={who}
-            withBonded
-          />
-        )}
-      </details>
-    );
-  }
-
-  private renderOnlineStatus (): React.ReactNode {
-    const { controllerId, onlineStatus } = this.state;
-    if (!controllerId || !onlineStatus) {
-      return null;
-    }
-
-    return (
-      <OnlineStatus
-        accountId={controllerId}
-        value={onlineStatus}
-        tooltip
-      />
-    );
-  }
 }
 
 export default withMulti(
@@ -250,6 +219,16 @@ export default withMulti(
       right: 0;
     }
 
+    .staking--Address-info {
+      /* Small additional margin to take care of validator highlights */
+      margin-right: 0.25rem;
+      text-align: right;
+
+      .staking--label {
+        margin: 0 2.25rem -0.75rem 0;
+      }
+    }
+
     .staking--label.controllerSpacer {
       margin-top: 2.75rem;
     }

+ 39 - 66
packages/app-staking/src/Overview/CurrentList.tsx

@@ -6,81 +6,24 @@ import { BlockNumber } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ValidatorFilter } from '../types';
 
-import React from 'react';
+import React, { useState } from 'react';
 import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-components';
 
 import translate from '../translate';
 import Address from './Address';
 
 interface Props extends I18nProps {
-  currentValidatorsControllersV1OrStashesV2: string[];
+  currentValidators: string[];
   lastAuthor?: string;
   lastBlock: string;
   next: string[];
   recentlyOnline: Record<string, BlockNumber>;
 }
 
-interface State {
-  filter: ValidatorFilter;
-  filterOptions: { text: React.ReactNode; value: ValidatorFilter }[];
-}
-
-class CurrentList extends React.PureComponent<Props, State> {
-  public state: State;
-
-  public constructor (props: Props) {
-    super(props);
-
-    const { t } = props;
-
-    this.state = {
-      filter: 'all',
-      filterOptions: [
-        { text: t('Show all validators and intentions'), value: 'all' },
-        { text: t('Show only my nominations'), value: 'iNominated' },
-        { text: t('Show only with nominators'), value: 'hasNominators' },
-        { text: t('Show only without nominators'), value: 'noNominators' },
-        { text: t('Show only with warnings'), value: 'hasWarnings' },
-        { text: t('Show only without warnings'), value: 'noWarnings' }
-      ]
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { currentValidatorsControllersV1OrStashesV2, next, t } = this.props;
-    const { filter, filterOptions } = this.state;
-    return (
-      <div>
-        <FilterOverlay>
-          <Dropdown
-            onChange={this.onChangeFilter}
-            options={filterOptions}
-            value={filter}
-            withLabel={false}
-          />
-        </FilterOverlay>
-        <Columar className='validator--ValidatorsList'>
-          <Column
-            emptyText={t('No addresses found')}
-            headerText={t('validators')}
-          >
-            {this.renderColumn(currentValidatorsControllersV1OrStashesV2, t('validator'))}
-          </Column>
-          <Column
-            emptyText={t('No addresses found')}
-            headerText={t('next up')}
-          >
-            {this.renderColumn(next, t('intention'))}
-          </Column>
-        </Columar>
-      </div>
-    );
-  }
-
-  private renderColumn (addresses: string[], defaultName: string): React.ReactNode {
-    const { lastAuthor, lastBlock, recentlyOnline } = this.props;
-    const { filter } = this.state;
+function CurrentList ({ currentValidators, lastAuthor, lastBlock, next, recentlyOnline, t }: Props): React.ReactElement<Props> {
+  const [filter, setFilter] = useState<ValidatorFilter>('all');
 
+  const _renderColumn = (addresses: string[], defaultName: string): React.ReactNode => {
     return addresses.map((address): React.ReactNode => (
       <Address
         address={address}
@@ -92,11 +35,41 @@ class CurrentList extends React.PureComponent<Props, State> {
         recentlyOnline={recentlyOnline}
       />
     ));
-  }
+  };
 
-  private onChangeFilter = (filter: ValidatorFilter): void => {
-    this.setState({ filter });
-  }
+  return (
+    <div>
+      <FilterOverlay>
+        <Dropdown
+          onChange={setFilter}
+          options={[
+            { text: t('Show all validators and intentions'), value: 'all' },
+            { text: t('Show only my nominations'), value: 'iNominated' },
+            { text: t('Show only with nominators'), value: 'hasNominators' },
+            { text: t('Show only without nominators'), value: 'noNominators' },
+            { text: t('Show only with warnings'), value: 'hasWarnings' },
+            { text: t('Show only without warnings'), value: 'noWarnings' }
+          ]}
+          value={filter}
+          withLabel={false}
+        />
+      </FilterOverlay>
+      <Columar className='validator--ValidatorsList'>
+        <Column
+          emptyText={t('No addresses found')}
+          headerText={t('validators')}
+        >
+          {_renderColumn(currentValidators, t('validator'))}
+        </Column>
+        <Column
+          emptyText={t('No addresses found')}
+          headerText={t('next up')}
+        >
+          {_renderColumn(next, t('intention'))}
+        </Column>
+      </Columar>
+    </div>
+  );
 }
 
 export default translate(CurrentList);

+ 3 - 5
packages/app-staking/src/Overview/Summary.tsx

@@ -17,16 +17,14 @@ import translate from '../translate';
 interface Props extends I18nProps {
   allControllers: string[];
   className?: string;
-  currentValidatorsControllersV1OrStashesV2: string[];
+  currentValidators: string[];
   lastAuthor?: string;
   lastBlock: string;
   staking_validatorCount?: BN;
   next: string[];
 }
 
-function Summary (props: Props): React.ReactElement<Props> {
-  const { className, currentValidatorsControllersV1OrStashesV2, lastAuthor, lastBlock, next, style, staking_validatorCount, t } = props;
-
+function Summary ({ className, currentValidators, lastAuthor, lastBlock, next, style, staking_validatorCount, t }: Props): React.ReactElement<Props> {
   return (
     <SummaryBox
       className={className}
@@ -34,7 +32,7 @@ function Summary (props: Props): React.ReactElement<Props> {
     >
       <section>
         <CardSummary label={t('validators')}>
-          {currentValidatorsControllersV1OrStashesV2.length}/{staking_validatorCount ? staking_validatorCount.toString() : '-'}
+          {currentValidators.length}/{staking_validatorCount ? staking_validatorCount.toString() : '-'}
         </CardSummary>
         <CardSummary label={t('waiting')}>
           {next.length}

+ 7 - 7
packages/app-staking/src/Overview/index.tsx

@@ -22,18 +22,18 @@ interface Props extends BareProps, ComponentProps {
 // TODO: Switch to useState
 function Overview (props: Props): React.ReactElement<Props> {
   const { isSubstrateV2 } = useContext(ApiContext);
-  const { chain_subscribeNewHeads, allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOnline } = props;
+  const { chain_subscribeNewHeads, allControllers, allStashes, currentValidators, recentlyOnline } = props;
   let nextSorted: string[];
 
   if (isSubstrateV2) {
-    // this is a V2 node currentValidatorsControllersV1OrStashesV2 is a list of stashes
+    // this is a V2 node currentValidators is a list of stashes
     nextSorted = allStashes.filter((address): boolean =>
-      !currentValidatorsControllersV1OrStashesV2.includes(address)
+      !currentValidators.includes(address)
     );
   } else {
-    // this is a V1 node currentValidatorsControllersV1OrStashesV2 is a list of controllers
+    // this is a V1 node currentValidators is a list of controllers
     nextSorted = allControllers.filter((address): boolean =>
-      !currentValidatorsControllersV1OrStashesV2.includes(address)
+      !currentValidators.includes(address)
     );
   }
 
@@ -49,13 +49,13 @@ function Overview (props: Props): React.ReactElement<Props> {
     <div className='staking--Overview'>
       <Summary
         allControllers={allControllers}
-        currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
+        currentValidators={currentValidators}
         lastBlock={lastBlock}
         lastAuthor={lastAuthor}
         next={nextSorted}
       />
       <CurrentList
-        currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
+        currentValidators={currentValidators}
         lastBlock={lastBlock}
         lastAuthor={lastAuthor}
         next={nextSorted}

+ 0 - 31
packages/app-staking/src/index.css

@@ -1,31 +0,0 @@
-/* Copyright 2017-2019 @polkadot/app-staking authors & contributors
-/* This software may be modified and distributed under the terms
-/* of the Apache-2.0 license. See the LICENSE file for details. */
-
-.staking--App .rx--updated {
-  background: transparent !important;
-}
-
-.staking--Account-links {
-  display: flex;
-  align-items: flex-end;
-  flex-direction: column;
-}
-
-.staking--Account-detail {
-  text-align: right;
-
-  .staking--label {
-    margin: 0 1.75rem -0.75rem 0;
-  }
-}
-
-.staking--Address-info {
-  /* Small additional margin to take care of validator highlights */
-  margin-right: 0.25rem;
-  text-align: right;
-
-  .staking--label {
-    margin: 0 2.25rem -0.75rem 0;
-  }
-}

+ 85 - 89
packages/app-staking/src/index.tsx

@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 // Copyright 2017-2019 @polkadot/app-staking authors & contributors
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
@@ -10,16 +9,15 @@ import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { ComponentProps } from './types';
 
 import BN from 'bn.js';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { Route, Switch } from 'react-router';
+import styled from 'styled-components';
 import { createType, Option } from '@polkadot/types';
 import { HelpOverlay } from '@polkadot/react-components';
 import Tabs from '@polkadot/react-components/Tabs';
 import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
 import accountObservable from '@polkadot/ui-keyring/observable/accounts';
 
-import './index.css';
-
 import Accounts from './Actions/Accounts';
 import basicMd from './md/basic.md';
 import Overview from './Overview';
@@ -28,7 +26,7 @@ import translate from './translate';
 interface Props extends AppProps, ApiProps, I18nProps {
   allAccounts?: SubjectInfo;
   allStashesAndControllers?: [AccountId[], Option<AccountId>[]];
-  chain_bestNumber?: BlockNumber;
+  bestNumber?: BlockNumber;
   currentValidatorsControllersV1OrStashesV2?: AccountId[];
   recentlyOnline?: AuthorityId[];
 }
@@ -36,84 +34,50 @@ interface Props extends AppProps, ApiProps, I18nProps {
 interface State {
   allControllers: string[];
   allStashes: string[];
-  currentValidatorsControllersV1OrStashesV2: string[];
-  recentlyOnline: Record<string, BlockNumber>;
+  currentValidators: string[];
 }
 
-class App extends React.PureComponent<Props, State> {
-  public state: State = {
+function App ({ allAccounts, allStashesAndControllers, bestNumber, className, currentValidatorsControllersV1OrStashesV2, basePath, recentlyOnline: propsRecentlyOnline, t }: Props): React.ReactElement<Props> {
+  const [{ allControllers, allStashes, currentValidators }, setState] = useState<State>({
     allControllers: [],
     allStashes: [],
-    currentValidatorsControllersV1OrStashesV2: [],
-    recentlyOnline: {}
-  };
-
-  public static getDerivedStateFromProps (props: Props, state: State): Pick<State, never> {
-    const { allStashesAndControllers = [[], []], chain_bestNumber, currentValidatorsControllersV1OrStashesV2 = [] } = props;
+    currentValidators: []
+  });
+  const [recentlyOnline, setRecentlyOnline] = useState<Record<string, BlockNumber>>({});
+
+  useEffect((): void => {
+    const [_stashes, _controllers] = (allStashesAndControllers || [[], []]);
+    const _validators = currentValidatorsControllersV1OrStashesV2 || [];
+
+    setState({
+      allControllers: _controllers
+        .filter((optId): boolean => optId.isSome)
+        .map((accountId): string => accountId.unwrap().toString()),
+      allStashes: _stashes
+        .filter((): boolean => true)
+        .map((accountId): string => accountId.toString()),
+      currentValidators: _validators.map((authorityId): string =>
+        authorityId.toString()
+      )
+    });
+  }, [allStashesAndControllers, currentValidatorsControllersV1OrStashesV2]);
 
-    const recentlyOnline: Record<string, BlockNumber> = {
-      ...(state.recentlyOnline || {}),
-      ...(props.recentlyOnline || []).reduce(
+  useEffect((): void => {
+    setRecentlyOnline({
+      ...(recentlyOnline || {}),
+      ...(propsRecentlyOnline || []).reduce(
         (result: Record<string, BlockNumber>, authorityId): Record<string, BlockNumber> => ({
           ...result,
-          [authorityId.toString()]: chain_bestNumber || createType('BlockNumber', new BN(0))
+          [authorityId.toString()]: bestNumber || createType('BlockNumber', new BN(0))
         }),
         {}
       )
-    };
-
-    return {
-      allControllers: allStashesAndControllers[1].filter((optId): boolean => optId.isSome).map((accountId): string =>
-        accountId.unwrap().toString()
-      ),
-      allStashes: allStashesAndControllers[0].filter((): boolean => true).map((accountId): string => accountId.toString()),
-      currentValidatorsControllersV1OrStashesV2: currentValidatorsControllersV1OrStashesV2.map((authorityId): string =>
-        authorityId.toString()
-      ),
-      recentlyOnline
-    };
-  }
+    });
+  }, [bestNumber, propsRecentlyOnline]);
 
-  public render (): React.ReactNode {
-    const { allAccounts, basePath, t } = this.props;
-
-    return (
-      <main className='staking--App'>
-        <HelpOverlay md={basicMd} />
-        <header>
-          <Tabs
-            basePath={basePath}
-            hidden={
-              !allAccounts || Object.keys(allAccounts).length === 0
-                ? ['actions']
-                : []
-            }
-            items={[
-              {
-                isRoot: true,
-                name: 'overview',
-                text: t('Staking overview')
-              },
-              {
-                name: 'actions',
-                text: t('Account actions')
-              }
-            ]}
-          />
-        </header>
-        <Switch>
-          <Route path={`${basePath}/actions`} render={this.renderComponent(Accounts)} />
-          <Route render={this.renderComponent(Overview)} />
-        </Switch>
-      </main>
-    );
-  }
-
-  private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
+  const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
+    // eslint-disable-next-line react/display-name
     return (): React.ReactNode => {
-      const { allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOnline } = this.state;
-      const { allAccounts } = this.props;
-
       if (!allAccounts) {
         return null;
       }
@@ -123,35 +87,67 @@ class App extends React.PureComponent<Props, State> {
           allAccounts={allAccounts}
           allControllers={allControllers}
           allStashes={allStashes}
-          currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
+          currentValidators={currentValidators}
           recentlyOnline={recentlyOnline}
         />
       );
     };
-  }
+  };
+
+  return (
+    <main className={`staking--App ${className}`}>
+      <HelpOverlay md={basicMd} />
+      <header>
+        <Tabs
+          basePath={basePath}
+          hidden={
+            !allAccounts || Object.keys(allAccounts).length === 0
+              ? ['actions']
+              : []
+          }
+          items={[
+            {
+              isRoot: true,
+              name: 'overview',
+              text: t('Staking overview')
+            },
+            {
+              name: 'actions',
+              text: t('Account actions')
+            }
+          ]}
+        />
+      </header>
+      <Switch>
+        <Route path={`${basePath}/actions`} render={_renderComponent(Accounts)} />
+        <Route render={_renderComponent(Overview)} />
+      </Switch>
+    </main>
+  );
 }
 
 export default withMulti(
-  App,
+  styled(App)`
+    .rx--updated {
+      background: transparent !important;
+    }
+  `,
   translate,
   withCalls<Props>(
-    'derive.chain.bestNumber',
+    ['derive.chain.bestNumber', { propName: 'bestNumber' }],
     ['derive.staking.controllers', { propName: 'allStashesAndControllers' }],
     ['query.session.validators', { propName: 'currentValidatorsControllersV1OrStashesV2' }],
-    [
-      'query.system.events',
-      {
-        propName: 'recentlyOnline',
-        transform: (value?: EventRecord[]): AuthorityId[] =>
-          (value || [])
-            .filter(({ event: { method, section } }): boolean =>
-              section === 'imOnline' && method === 'HeartbeatReceived'
-            )
-            .map(
-              ({ event: { data: [authorityId] } }): AuthorityId => authorityId as AuthorityId
-            )
-      }
-    ]
+    ['query.system.events', {
+      propName: 'recentlyOnline',
+      transform: (value?: EventRecord[]): AuthorityId[] =>
+        (value || [])
+          .filter(({ event: { method, section } }): boolean =>
+            section === 'imOnline' && method === 'HeartbeatReceived'
+          )
+          .map(({ event: { data: [authorityId] } }): AuthorityId =>
+            authorityId as AuthorityId
+          )
+    }]
   ),
   withObservable(accountObservable.subject, { propName: 'allAccounts' })
 );

+ 1 - 1
packages/app-staking/src/types.ts

@@ -14,7 +14,7 @@ export interface ComponentProps {
   allAccounts?: SubjectInfo;
   allControllers: string[];
   allStashes: string[];
-  currentValidatorsControllersV1OrStashesV2: string[];
+  currentValidators: string[];
   recentlyOnline: Record<string, BlockNumber>;
 }
 

+ 4 - 4
packages/app-storage/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-storage",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,8 +10,8 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-params": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-params": "^0.36.0-beta.65"
   }
 }

+ 102 - 107
packages/app-storage/src/Query.tsx

@@ -2,33 +2,26 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { StorageEntryPromise } from '@polkadot/api/types';
+import { RenderFn, DefaultProps, ComponentRenderer } from '@polkadot/react-api/with/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { QueryTypes, StorageModuleQuery } from './types';
+import { ConstValue } from '@polkadot/react-components/InputConsts/types';
+import { QueryTypes, StorageEntryPromise, StorageModuleQuery } from './types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import styled from 'styled-components';
-import { Compact } from '@polkadot/types';
 import { Button, Labelled } from '@polkadot/react-components';
 import { withCallDiv } from '@polkadot/react-api';
 import valueToText from '@polkadot/react-params/valueToText';
+import { Compact, Data, Option } from '@polkadot/types';
 import { isU8a, u8aToHex, u8aToString } from '@polkadot/util';
 
 import translate from './translate';
-import { RenderFn, DefaultProps, ComponentRenderer } from '@polkadot/react-api/with/types';
-import { ConstValue } from '@polkadot/react-components/InputConsts/types';
 
 interface Props extends I18nProps {
   onRemove: (id: number) => void;
   value: QueryTypes;
 }
 
-interface State {
-  inputs?: React.ReactNode[];
-  Component?: React.ComponentType<{}>;
-  spread: Record<number, boolean>;
-}
-
 interface CacheInstance {
   Component: React.ComponentType<any>;
   render: RenderFn;
@@ -72,16 +65,14 @@ function createComponent (type: string, Component: React.ComponentType<any>, def
   return {
     Component,
     // In order to replace the default component during runtime we can provide a RenderFn to create a new 'plugged' component
-    render: (createComponent: RenderFn): React.ComponentType<any> => {
-      return renderHelper(createComponent, defaultProps);
-    },
+    render: (createComponent: RenderFn): React.ComponentType<any> =>
+      renderHelper(createComponent, defaultProps),
     // In order to modify the parameters which are used to render the default component, we can use this method
-    refresh: (swallowErrors: boolean, contentShorten: boolean): React.ComponentType<any> => {
-      return renderHelper(
+    refresh: (swallowErrors: boolean, contentShorten: boolean): React.ComponentType<any> =>
+      renderHelper(
         (value: any): React.ReactNode => valueToText(type, value, swallowErrors, contentShorten),
         defaultProps
-      );
-    }
+      )
   };
 }
 
@@ -100,12 +91,23 @@ function getCachedComponent (query: QueryTypes): CacheInstance {
     } else {
       const values: any[] = params.map(({ value }): any => value);
 
-      // render function to create an element for the query results which is plugged to the api
-      renderHelper = withCallDiv('subscribe', {
-        paramName: 'params',
-        paramValid: true,
-        params: [key, ...values]
-      });
+      if (isU8a(key)) {
+        // subscribe to the raw key here
+        renderHelper = withCallDiv('rpc.state.subscribeStorage', {
+          paramName: 'params',
+          paramValid: true,
+          params: [[key]],
+          transform: ([data]: Option<Data>[]): Option<Data> => data
+        });
+      } else {
+        // render function to create an element for the query results which is plugged to the api
+        renderHelper = withCallDiv('subscribe', {
+          paramName: 'params',
+          paramValid: true,
+          params: [key, ...values]
+        });
+      }
+
       type = key.creator && key.creator.meta
         ? typeToString(key)
         : 'Data';
@@ -124,102 +126,95 @@ function getCachedComponent (query: QueryTypes): CacheInstance {
   return cache[id];
 }
 
-class Query extends React.PureComponent<Props, State> {
-  public state: State = { spread: {} };
-
-  public static getDerivedStateFromProps ({ value }: Props): Pick<State, never> {
-    const Component = getCachedComponent(value).Component;
-    const inputs: React.ReactNode[] = isU8a(value.key)
-      ? []
-      // FIXME We need to render the actual key params
-      // const { key, params } = value;
-      // const inputs = key.params.map(({ name, type }, index) => (
-      //   <span key={`param_${name}_${index}`}>
-      //     {name}={valueToText(type, params[index].value)}
-      //   </span>
-      // ));
-      : [];
-
-    return {
-      Component,
-      inputs
-    };
-  }
+function Query ({ className, onRemove, value }: Props): React.ReactElement<Props> | null {
+  // const [inputs, setInputs] = useState<React.ReactNode[]>([]);
+  const [{ Component }, setComponent] = useState<Partial<CacheInstance>>({});
+  const [isSpreadable, setIsSpreadable] = useState(false);
+  const [spread, setSpread] = useState<Record<number, boolean>>({});
+
+  useEffect((): void => {
+    setComponent(getCachedComponent(value));
+    // setInputs(
+    //   isU8a(value.key)
+    //     ? []
+    //     // FIXME We need to render the actual key params
+    //     // const { key, params } = value;
+    //     // const inputs = key.params.map(({ name, type }, index) => (
+    //     //   <span key={`param_${name}_${index}`}>
+    //     //     {name}={valueToText(type, params[index].value)}
+    //     //   </span>
+    //     // ));
+    //     : []
+    // );
+    setIsSpreadable(
+      (value.key as StorageEntryPromise).creator &&
+      (value.key as StorageEntryPromise).creator.meta &&
+      ['Bytes', 'Data'].includes((value.key as StorageEntryPromise).creator.meta.type.toString())
+    );
+  }, [value]);
 
-  public render (): React.ReactNode {
-    const { className, value } = this.props;
-    const { Component } = this.state;
-    const { id, isConst, key } = value;
-    const type = isConst
-      ? (key as unknown as ConstValue).meta.type.toString()
-      : isU8a(key)
-        ? 'Data'
-        : typeToString(key as StorageEntryPromise);
-
-    if (!Component) {
-      return null;
-    }
+  const { id, isConst, key } = value;
+  const type = isConst
+    ? (key as unknown as ConstValue).meta.type.toString()
+    : isU8a(key)
+      ? 'Data'
+      : typeToString(key as StorageEntryPromise);
 
-    return (
-      <div className={`storage--Query storage--actionrow ${className}`}>
-        <div className='storage--actionrow-value'>
-          <Labelled
-            label={
-              <div className='ui--Param-text'>
-                {keyToName(isConst, key)}: {type}
-              </div>
-            }
-          >
-            <Component />
-          </Labelled>
-        </div>
-        <div className='storage--actionrow-buttons'>
-          <div className='container'>
-            {(key as StorageEntryPromise).creator && (key as StorageEntryPromise).creator.meta && ['Bytes', 'Data'].includes((key as StorageEntryPromise).creator.meta.type.toString()) && (
-              <Button
-                icon='ellipsis horizontal'
-                key='spread'
-                onClick={this.spreadHandler(id)}
-              />
-            )}
-            <Button
-              icon='close'
-              isNegative
-              key='close'
-              onClick={this.onRemove}
-            />
-          </div>
-        </div>
-      </div>
-    );
+  if (!Component) {
+    return null;
   }
 
-  private spreadHandler (id: number): () => void {
+  const _spreadHandler = (id: number): () => void => {
     return (): void => {
-      const { spread } = this.state;
-
       cache[id].Component = cache[id].refresh(true, !!spread[id]);
       spread[id] = !spread[id];
 
-      this.setState({
-        ...this.state,
-        ...spread,
-        Component: cache[id].Component
-      });
+      setComponent(cache[id]);
+      setSpread({ ...spread });
     };
-  }
-
-  private onRemove = (): void => {
-    const { onRemove, value: { id } } = this.props;
+  };
+  const _onRemove = (): void => {
+    delete cache[value.id];
 
-    delete cache[id];
+    onRemove(value.id);
+  };
 
-    onRemove(id);
-  }
+  return (
+    <div className={`storage--Query storage--actionrow ${className}`}>
+      <div className='storage--actionrow-value'>
+        <Labelled
+          label={
+            <div className='ui--Param-text'>
+              {keyToName(isConst, key)}: {type}
+            </div>
+          }
+        >
+          <Component />
+        </Labelled>
+      </div>
+      <div className='storage--actionrow-buttons'>
+        <div className='container'>
+          {isSpreadable && (
+            <Button
+              icon='ellipsis horizontal'
+              key='spread'
+              onClick={_spreadHandler(id)}
+            />
+          )}
+          <Button
+            icon='close'
+            isNegative
+            key='close'
+            onClick={_onRemove}
+          />
+        </div>
+      </div>
+    </div>
+  );
 }
 
 export default translate(
-  styled(Query as React.ComponentClass<Props>)`
+  styled(Query)`
     margin-bottom: 0.25em;
 
     label {

+ 36 - 66
packages/app-storage/src/Selection/Consts.tsx

@@ -5,84 +5,54 @@
 import { ConstantCodec } from '@polkadot/api-metadata/consts/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ConstValue } from '@polkadot/react-components/InputConsts/types';
-import { ApiProps } from '@polkadot/react-api/types';
 import { ComponentProps } from '../types';
 
-import React from 'react';
-import { Button, InputConsts, TxComponent } from '@polkadot/react-components';
-import { withApi, withMulti } from '@polkadot/react-api';
+import React, { useContext, useState } from 'react';
+import { ApiContext } from '@polkadot/react-api';
+import { Button, InputConsts } from '@polkadot/react-components';
 
 import translate from '../translate';
 
-interface Props extends ComponentProps, ApiProps, I18nProps {}
+interface Props extends ComponentProps, I18nProps {}
 
-interface State {
-  value: ConstValue;
-}
-
-class Consts extends TxComponent<Props, State> {
-  private defaultValue: ConstValue;
-
-  public state: State;
-
-  public constructor (props: Props) {
-    super(props);
-
-    const { api } = this.props;
+function Consts ({ onAdd, t }: Props): React.ReactElement<Props> {
+  const { api } = useContext(ApiContext);
+  const [defaultValue] = useState<ConstValue>((): ConstValue => {
     const section = Object.keys(api.consts)[0];
     const method = Object.keys(api.consts[section])[0];
 
-    this.defaultValue = {
+    return {
       meta: (api.consts[section][method] as ConstantCodec).meta,
       method,
       section
     };
-    this.state = {
-      value: this.defaultValue
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { api, t } = this.props;
-    const { value: { method, section } } = this.state;
-    const meta = (api.consts[section][method] as ConstantCodec).meta;
-
-    return (
-      <section className='storage--actionrow'>
-        <div className='storage--actionrow-value'>
-          <InputConsts
-            defaultValue={this.defaultValue}
-            label={t('selected constant query')}
-            onChange={this.onChangeConst}
-            help={meta && meta.documentation && meta.documentation.join(' ')}
-          />
-        </div>
-        <div className='storage--actionrow-buttons'>
-          <Button
-            icon='plus'
-            isPrimary
-            onClick={this.onAdd}
-            ref={this.button}
-          />
-        </div>
-      </section>
-    );
-  }
-
-  private onAdd = (): void => {
-    const { onAdd } = this.props;
-    const { value } = this.state;
-
-    onAdd({ isConst: true, key: value });
-  }
-
-  private onChangeConst = (value: ConstValue): void => {
-    this.setState({ value });
-  }
+  });
+  const [value, setValue] = useState(defaultValue);
+
+  const _onAdd = (): void => onAdd({ isConst: true, key: value });
+
+  const { method, section } = value;
+  const meta = (api.consts[section][method] as ConstantCodec).meta;
+
+  return (
+    <section className='storage--actionrow'>
+      <div className='storage--actionrow-value'>
+        <InputConsts
+          defaultValue={defaultValue}
+          label={t('selected constant query')}
+          onChange={setValue}
+          help={meta && meta.documentation && meta.documentation.join(' ')}
+        />
+      </div>
+      <div className='storage--actionrow-buttons'>
+        <Button
+          icon='plus'
+          isPrimary
+          onClick={_onAdd}
+        />
+      </div>
+    </section>
+  );
 }
 
-export default withMulti(
-  Consts,
-  translate,
-  withApi
-);
+export default translate(Consts);

+ 94 - 151
packages/app-storage/src/Selection/Modules.tsx

@@ -2,175 +2,118 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { StorageEntryPromise } from '@polkadot/api/types';
 import { TypeDef } from '@polkadot/types/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { RawParams } from '@polkadot/react-params/types';
-import { ApiProps } from '@polkadot/react-api/types';
-import { ComponentProps } from '../types';
+import { ComponentProps, StorageEntryPromise } from '../types';
 
-import React from 'react';
+import React, { useContext, useState } from 'react';
 import { getTypeDef } from '@polkadot/types';
-import { Button, InputStorage, TxComponent } from '@polkadot/react-components';
+import { Button, InputStorage } from '@polkadot/react-components';
 import Params from '@polkadot/react-params';
-import { withApi, withMulti } from '@polkadot/react-api';
-import { isUndefined } from '@polkadot/util';
+import { ApiContext } from '@polkadot/react-api';
+import { isNull, isUndefined } from '@polkadot/util';
 
 import translate from '../translate';
 
-interface Props extends ComponentProps, ApiProps, I18nProps {}
+interface Props extends ComponentProps, I18nProps {}
 
-interface State {
-  isValid: boolean;
-  key: StorageEntryPromise;
-  defaultValues?: RawParams | null;
-  values: RawParams;
-  params: { type: TypeDef }[];
-}
-
-class Modules extends TxComponent<Props, State> {
-  private defaultValue: any;
-
-  public state: State;
-
-  public constructor (props: Props) {
-    super(props);
-
-    const { api } = this.props;
-
-    this.defaultValue = api.query.timestamp.now;
-    this.state = {
-      isValid: true,
-      key: this.defaultValue,
-      values: [],
-      params: []
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { t } = this.props;
-    const { isValid, key: { creator: { method, section, meta } }, defaultValues, params } = this.state;
-
-    return (
-      <section className='storage--actionrow'>
-        <div className='storage--actionrow-value'>
-          <InputStorage
-            defaultValue={this.defaultValue}
-            label={t('selected state query')}
-            onChange={this.onChangeKey}
-            help={meta && meta.documentation && meta.documentation.join(' ')}
-          />
-          <Params
-            key={`${section}.${method}:params` /* force re-render on change */}
-            onChange={this.onChangeParams}
-            onEnter={this.submit}
-            params={params}
-            values={defaultValues}
-          />
-        </div>
-        <div className='storage--actionrow-buttons'>
-          <Button
-            icon='plus'
-            isDisabled={!isValid}
-            isPrimary
-            onClick={this.onAdd}
-            ref={this.button}
-          />
-        </div>
-      </section>
-    );
-  }
-
-  private nextState (newState: Partial<State>): void {
-    this.setState(
-      (prevState: State): Pick<State, never> => {
-        const { key = prevState.key, values = prevState.values } = newState;
-
-        const areParamsValid = (): boolean => {
-          return values.reduce(
-            (isValid: boolean, value): boolean => (
-              isValid &&
-              !isUndefined(value) &&
-              !isUndefined(value.value) &&
-              value.isValid),
-            true
-          );
-        };
-
-        if (key.creator.meta.type.isDoubleMap) {
-          const key1 = key.creator.meta.type.asDoubleMap.key1.toString();
-          const key2 = key.creator.meta.type.asDoubleMap.key2.toString();
+type ParamsType = { type: TypeDef }[];
 
-          return {
-            defaultValues: this.getDefaultValues(),
-            isValid: values.length === 2 && areParamsValid(),
-            key,
-            params: [
-              { type: getTypeDef(key1) },
-              { type: getTypeDef(key2) }
-            ],
-            values
-          };
-        }
-
-        const hasParam = key.creator.meta.type.isMap;
-        const isValid = values.length === (hasParam ? 1 : 0) && areParamsValid();
-
-        return {
-          defaultValues: null,
-          isValid,
-          key,
-          params: hasParam
-            ? [{ type: getTypeDef(key.creator.meta.type.asMap.key.toString()) }]
-            : [],
-          values
-        };
-      }
-    );
-  }
-
-  private getDefaultValues = (): RawParams | null => {
-    const { api } = this.props;
-    const { key } = this.state;
-
-    if (key.creator.section === 'session') {
-      return [
-        {
-          isValid: true,
-          value: api.consts.session.dedupKeyPrefix.toHex()
-        }
-      ];
-    }
-    return null;
-  }
+function areParamsValid (values: RawParams): boolean {
+  return values.reduce(
+    (isValid: boolean, value): boolean => (
+      isValid &&
+      !isUndefined(value) &&
+      !isUndefined(value.value) &&
+      value.isValid),
+    true
+  );
+}
 
-  private onAdd = (): void => {
-    const { onAdd } = this.props;
-    const { key, values } = this.state;
+function Modules ({ onAdd, t }: Props): React.ReactElement<Props> {
+  const { api } = useContext(ApiContext);
+  const [{ defaultValues, isLinked, key, params }, setKey] = useState<{ defaultValues: RawParams | undefined | null; isLinked: boolean; key: StorageEntryPromise; params: ParamsType }>({ defaultValues: undefined, isLinked: false, key: api.query.timestamp.now, params: [] });
+  const [{ isValid, values }, setValues] = useState<{ isValid: boolean; values: RawParams }>({ isValid: true, values: [] });
 
-    onAdd({
+  const _onAdd = (): void => {
+    isValid && onAdd({
       isConst: false,
       key,
-      params: values
+      params: values.filter(({ value }): boolean => !isLinked || !isNull(value))
     });
-  }
-
-  private onChangeKey = (key: StorageEntryPromise): void => {
-    this.nextState({
-      isValid: false,
+  };
+  const _onChangeValues = (values: RawParams): void => {
+    setValues({
+      isValid: (
+        key.creator.meta.type.isDoubleMap
+          ? values.length === 2
+          : values.length === (key.creator.meta.type.isMap ? 1 : 0)
+      ) && areParamsValid(values),
+      values
+    });
+  };
+  const _onChangeKey = (key: StorageEntryPromise): void => {
+    const isMap = key.creator.meta.type.isMap;
+    const isLinked = isMap && key.creator.meta.type.asMap.linked.isTrue;
+
+    setKey({
+      defaultValues: key.creator.section === 'session'
+        ? [{
+          isValid: true,
+          value: api.consts.session.dedupKeyPrefix.toHex()
+        }]
+        : null,
+      isLinked,
       key,
-      values: [],
-      params: []
+      params: key.creator.meta.type.isDoubleMap
+        ? [
+          { type: getTypeDef(key.creator.meta.type.asDoubleMap.key1.toString()) },
+          { type: getTypeDef(key.creator.meta.type.asDoubleMap.key2.toString()) }
+        ]
+        : isMap
+          ? [{
+            type: getTypeDef(
+              isLinked
+                ? `Option<${key.creator.meta.type.asMap.key.toString()}>`
+                : key.creator.meta.type.asMap.key.toString()
+            )
+          }]
+          : []
     });
-  }
 
-  private onChangeParams = (values: RawParams = []): void => {
-    this.nextState({ values });
-  }
+    _onChangeValues([]);
+  };
+
+  const { creator: { method, section, meta } } = key;
+
+  return (
+    <section className='storage--actionrow'>
+      <div className='storage--actionrow-value'>
+        <InputStorage
+          defaultValue={api.query.timestamp.now}
+          label={t('selected state query')}
+          onChange={_onChangeKey}
+          help={meta && meta.documentation && meta.documentation.join(' ')}
+        />
+        <Params
+          key={`${section}.${method}:params` /* force re-render on change */}
+          onChange={_onChangeValues}
+          onEnter={_onAdd}
+          params={params}
+          values={defaultValues}
+        />
+      </div>
+      <div className='storage--actionrow-buttons'>
+        <Button
+          icon='plus'
+          isDisabled={!isValid}
+          isPrimary
+          onClick={_onAdd}
+        />
+      </div>
+    </section>
+  );
 }
 
-export default withMulti(
-  Modules,
-  translate,
-  withApi
-);
+export default translate(Modules);

+ 30 - 48
packages/app-storage/src/Selection/Raw.tsx

@@ -5,8 +5,8 @@
 import { I18nProps } from '@polkadot/react-components/types';
 import { ComponentProps } from '../types';
 
-import React from 'react';
-import { Button, Input, TxComponent } from '@polkadot/react-components';
+import React, { useState } from 'react';
+import { Button, Input } from '@polkadot/react-components';
 
 import translate from '../translate';
 import { u8aToU8a } from '@polkadot/util';
@@ -14,60 +14,42 @@ import { Compact } from '@polkadot/types';
 
 interface Props extends ComponentProps, I18nProps {}
 
-interface State {
-  isValid: boolean;
-  key: Uint8Array;
-}
+function Raw ({ onAdd, t }: Props): React.ReactElement<Props> {
+  const [{ isValid, key }, setValue] = useState<{ isValid: boolean; key: Uint8Array }>({ isValid: false, key: new Uint8Array([]) });
 
-class Raw extends TxComponent<Props, State> {
-  public state: State = {
-    isValid: false,
-    key: new Uint8Array([])
+  const _onAdd = (): void => {
+    isValid && onAdd({ isConst: false, key });
   };
-
-  public render (): React.ReactNode {
-    const { t } = this.props;
-    const { isValid } = this.state;
-
-    return (
-      <section className='storage--actionrow'>
-        <div className='storage--actionrow-value'>
-          <Input
-            autoFocus
-            label={t('hex-encoded storage key')}
-            onChange={this.onChangeKey}
-            onEnter={this.submit}
-          />
-        </div>
-        <div className='storage--actionrow-buttons'>
-          <Button
-            icon='plus'
-            isDisabled={!isValid}
-            isPrimary
-            onClick={this.onAdd}
-            ref={this.button}
-          />
-        </div>
-      </section>
-    );
-  }
-
-  private onAdd = (): void => {
-    const { onAdd } = this.props;
-    const { key } = this.state;
-
-    onAdd({ isConst: false, key });
-  }
-
-  private onChangeKey = (key: string): void => {
+  const _onChangeKey = (key: string): void => {
     const u8a = u8aToU8a(key);
     const isValid = u8a.length !== 0;
 
-    this.setState({
+    setValue({
       isValid,
       key: Compact.addLengthPrefix(u8a)
     });
-  }
+  };
+
+  return (
+    <section className='storage--actionrow'>
+      <div className='storage--actionrow-value'>
+        <Input
+          autoFocus
+          label={t('hex-encoded storage key')}
+          onChange={_onChangeKey}
+          onEnter={_onAdd}
+        />
+      </div>
+      <div className='storage--actionrow-buttons'>
+        <Button
+          icon='plus'
+          isDisabled={!isValid}
+          isPrimary
+          onClick={_onAdd}
+        />
+      </div>
+    </section>
+  );
 }
 
 export default translate(Raw);

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

@@ -18,7 +18,7 @@ interface Props extends AppProps, I18nProps {}
 function StorageApp ({ basePath }: Props): React.ReactElement<Props> {
   const [queue, setQueue] = useState<QueryTypes[]>([]);
 
-  const _onAdd = (query: QueryTypes): void => setQueue([query].concat(queue));
+  const _onAdd = (query: QueryTypes): void => setQueue([query, ...queue]);
   const _onRemove = (id: number): void => setQueue(queue.filter((item): boolean => item.id !== id));
 
   return (

+ 3 - 1
packages/app-storage/src/types.ts

@@ -2,10 +2,12 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { StorageEntryPromise } from '@polkadot/api/types';
+import { StorageEntryBase } from '@polkadot/api/types';
 import { ConstValue } from '@polkadot/react-components/InputConsts/types';
 import { RawParams } from '@polkadot/react-params/types';
 
+export type StorageEntryPromise = StorageEntryBase<'promise', any>;
+
 interface Base {
   isConst: boolean;
 }

+ 3 - 3
packages/app-sudo/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-sudo",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A basic app that shows the ropes on customisation",
   "main": "index.js",
   "scripts": {},
@@ -11,7 +11,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 74 - 100
packages/app-sudo/src/SetKey.tsx

@@ -3,7 +3,7 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 import { I18nProps } from '@polkadot/react-components/types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { AddressMini, Icon, InputAddress, Labelled, TxButton } from '@polkadot/react-components';
 import { ComponentProps } from './types';
 
@@ -13,110 +13,84 @@ import translate from './translate';
 
 interface Props extends I18nProps, ComponentProps {}
 
-interface State {
-  selected?: string;
-}
-
-const SudoInputAddress = styled(InputAddress)`
-  margin: -0.25rem 0.5rem -0.25rem 0;
-`;
-
-const SudoLabelled = styled(Labelled)`
-  align-items: center;
-`;
-
-class SetKey extends React.PureComponent<Props, State> {
-  public state: State = {};
+function SetKey ({ allAccounts, className, isMine, sudoKey, t }: Props): React.ReactElement<Props> {
+  const [selected, setSelected] = useState<string | null>(null);
 
-  public constructor (props: Props) {
-    super(props);
-
-    this.state = {
-      selected: props.sudoKey
-    };
-  }
-
-  public static getDerivedStateFromProps ({ sudoKey }: Props, state: State): Pick<State, never> {
-    if (sudoKey && (!state.selected || sudoKey !== state.selected)) {
-      return { selected: sudoKey };
+  useEffect((): void => {
+    if (sudoKey && !selected) {
+      setSelected(sudoKey);
     }
-    return {};
-  }
-
-  public render (): React.ReactNode {
-    const { className, isMine, sudoKey, t } = this.props;
-    const { selected } = this.state;
-
-    return (
-      <section>
-        <section className={`${className} ui--row`}>
-          {isMine
-            ? (
-              <>
-                <SudoInputAddress
-                  value={selected}
-                  label={t('sudo key')}
-                  isInput={true}
-                  onChange={this.onChange}
-                  type='all'
-                />
-                <TxButton
-                  accountId={sudoKey}
-                  isDisabled={!isMine || sudoKey === selected}
-                  isPrimary
-                  label={t('Reassign')}
-                  icon='sign-in'
-                  params={[selected]}
-                  tx='sudo.setKey'
-                />
-              </>
-            )
-            : (
-              <SudoLabelled
-                className='ui--Dropdown'
+  }, [selected, sudoKey]);
+
+  const willLose = isMine &&
+    !!Object.keys(allAccounts).length &&
+    !!selected &&
+    selected !== sudoKey &&
+    !Object.keys(allAccounts).find((s): boolean => s === selected);
+
+  return (
+    <section>
+      <section className={`${className} ui--row`}>
+        {isMine
+          ? (
+            <>
+              <InputAddress
+                className='sudoInputAddress'
+                value={selected}
                 label={t('sudo key')}
-                withLabel
-              >
-                <AddressMini value={sudoKey} />
-              </SudoLabelled>
-            )
-          }
-        </section>
-        {this.willLose() && (
-          <article className='warning padded'>
-            <div>
-              <Icon name='warning' />
-              {t('You will no longer have sudo access')}
-            </div>
-          </article>
-        )}
+                isInput={true}
+                onChange={setSelected}
+                type='all'
+              />
+              <TxButton
+                accountId={sudoKey}
+                isDisabled={!isMine || sudoKey === selected}
+                isPrimary
+                label={t('Reassign')}
+                icon='sign-in'
+                params={[selected]}
+                tx='sudo.setKey'
+              />
+            </>
+          )
+          : (
+            <Labelled
+              className='ui--Dropdown sudoLabelled'
+              label={t('sudo key')}
+              withLabel
+            >
+              <AddressMini value={sudoKey} />
+            </Labelled>
+          )
+        }
       </section>
-    );
-  }
-
-  private onChange = (selected?: string): void => {
-    this.setState({ selected });
-  }
+      {willLose && (
+        <article className='warning padded'>
+          <div>
+            <Icon name='warning' />
+            {t('You will no longer have sudo access')}
+          </div>
+        </article>
+      )}
+    </section>
+  );
+}
 
-  private willLose = (): boolean => {
-    const { allAccounts, isMine, sudoKey } = this.props;
-    const { selected } = this.state;
+export default translate(
+  styled(SetKey)`
+    align-items: flex-end;
+    justify-content: center;
 
-    return (
-      isMine &&
-      !!Object.keys(allAccounts).length &&
-      !!selected &&
-      selected !== sudoKey &&
-      !Object.keys(allAccounts).find((s): boolean => s === selected)
-    );
-  }
-}
+    .summary {
+      text-align: center;
+    }
 
-export default translate(styled(SetKey as React.ComponentClass<Props, State>)`
-  align-items: flex-end;
-  justify-content: center;
+    .sudoInputAddress {
+      margin: -0.25rem 0.5rem -0.25rem 0;
+    }
 
-  .summary {
-    text-align: center;
-  }
-`);
+    .sudoLabelled {
+      align-items: center;
+    }
+  `
+);

+ 1 - 1
packages/app-sudo/src/Sudo.tsx

@@ -80,7 +80,7 @@ class Propose extends TxComponent<Props, State> {
     );
   }
 
-  private onChangeExtrinsic = (method: Call): void => {
+  private onChangeExtrinsic = (method?: Call): void => {
     if (!method) {
       return;
     }

+ 51 - 62
packages/app-sudo/src/index.tsx

@@ -4,14 +4,13 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
-import { ApiProps } from '@polkadot/react-api/types';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { ComponentProps } from './types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { Route, Switch } from 'react-router';
 import { Icon, Tabs } from '@polkadot/react-components';
-import { withApi, withCalls, withMulti, withObservable } from '@polkadot/react-api';
+import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
 import accountObservable from '@polkadot/ui-keyring/observable/accounts';
 
 import SetKey from './SetKey';
@@ -19,70 +18,23 @@ import Sudo from './Sudo';
 
 import translate from './translate';
 
-interface Props extends AppProps, ApiProps, I18nProps {
+interface Props extends AppProps, I18nProps {
   allAccounts: SubjectInfo;
-  sudo_key?: string;
+  sudoKey?: string;
 }
 
-interface State {
-  isMine: boolean;
-}
-
-class App extends React.PureComponent<Props, State> {
-  public state: State = {
-    isMine: false
-  };
+function App ({ allAccounts, basePath, sudoKey, t }: Props): React.ReactElement<Props> {
+  const [isMine, setIsMine] = useState(false);
 
-  public static getDerivedStateFromProps ({ allAccounts = {}, sudo_key }: Props): State | null {
-    return {
-      isMine: !!sudo_key && !!Object.keys(allAccounts).find((key): boolean => key === sudo_key.toString())
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { basePath, t } = this.props;
-    const { isMine } = this.state;
-
-    return (
-      <main>
-        <header>
-          <Tabs
-            basePath={basePath}
-            items={[
-              {
-                isRoot: true,
-                name: 'index',
-                text: t('Sudo access')
-              },
-              {
-                name: 'key',
-                text: t('Set sudo key')
-              }
-            ]}
-          />
-        </header>
-        {isMine ? (
-          <Switch>
-            <Route path={`${basePath}/key`} render={this.renderComponent(SetKey)} />
-            <Route render={this.renderComponent(Sudo)} />
-          </Switch>
-        ) : (
-          <article className='error padded'>
-            <div>
-              <Icon name='ban' />
-              {t('You do not have access to the current sudo key')}
-            </div>
-          </article>
-        )}
-      </main>
+  useEffect((): void => {
+    setIsMine(
+      !!sudoKey && !!allAccounts && Object.keys(allAccounts).some((key): boolean => key === sudoKey)
     );
-  }
+  }, [allAccounts, sudoKey]);
 
-  private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
+  const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
+    // eslint-disable-next-line react/display-name
     return (): React.ReactNode => {
-      const { allAccounts = {}, sudo_key: sudoKey = '' } = this.props;
-      const { isMine } = this.state;
-
       return (
         <Component
           allAccounts={allAccounts}
@@ -91,15 +43,52 @@ class App extends React.PureComponent<Props, State> {
         />
       );
     };
-  }
+  };
+
+  return (
+    <main>
+      <header>
+        <Tabs
+          basePath={basePath}
+          items={[
+            {
+              isRoot: true,
+              name: 'index',
+              text: t('Sudo access')
+            },
+            {
+              name: 'key',
+              text: t('Set sudo key')
+            }
+          ]}
+        />
+      </header>
+      {isMine
+        ? (
+          <Switch>
+            <Route path={`${basePath}/key`} render={_renderComponent(SetKey)} />
+            <Route render={_renderComponent(Sudo)} />
+          </Switch>
+        )
+        : (
+          <article className='error padded'>
+            <div>
+              <Icon name='ban' />
+              {t('You do not have access to the current sudo key')}
+            </div>
+          </article>
+        )
+      }
+    </main>
+  );
 }
 
 export default withMulti(
   App,
   translate,
-  withApi,
   withCalls<Props>(
     ['query.sudo.key', {
+      propName: 'sudoKey',
       transform: (key): string =>
         key.toString()
     }]

+ 3 - 3
packages/app-toolbox/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-toolbox",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,7 +10,7 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65"
   }
 }

+ 43 - 62
packages/app-toolbox/src/Hash.tsx

@@ -4,7 +4,7 @@
 
 import { I18nProps as Props } from '@polkadot/react-components/types';
 
-import React from 'react';
+import React, { useState } from 'react';
 import { Input, Output, Static } from '@polkadot/react-components';
 import { hexToU8a, isHex, stringToU8a } from '@polkadot/util';
 import { blake2AsHex } from '@polkadot/util-crypto';
@@ -17,59 +17,52 @@ interface State {
   isHexData: boolean;
 }
 
-class Hash extends React.PureComponent<Props, State> {
-  public state: State = {
+function Hash ({ t }: Props): React.ReactElement<Props> {
+  const [{ data, hash, isHexData }, setState] = useState<State>({
     data: '',
     hash: blake2AsHex(stringToU8a(''), 256),
     isHexData: false
-  };
-
-  public render (): React.ReactNode {
-    return (
-      <div className='toolbox--Hash'>
-        {this.renderInput()}
-        {this.renderOutput()}
-      </div>
-    );
-  }
+  });
 
-  public renderInput (): React.ReactNode {
-    const { t } = this.props;
-    const { data, isHexData } = this.state;
-
-    return (
-      <>
-        <div className='ui--row'>
-          <Input
-            autoFocus
-            className='full'
-            help={t('The input data to hash. This can be either specified as a hex value (0x-prefix) or as a string.')}
-            label={t('from the following data')}
-            onChange={this.onChangeData}
-            value={data}
-          />
-        </div>
-        <div className='ui--row'>
-          <Static
-            className='medium'
-            help={t('Detection on the input string to determine if it is hex or non-hex.')}
-            label={t('hex input data')}
-            value={
-              isHexData
-                ? t('Yes')
-                : t('No')
-            }
-          />
-        </div>
-      </>
-    );
-  }
+  const _onChangeData = (data: string): void => {
+    const isHexData = isHex(data);
 
-  public renderOutput (): React.ReactNode {
-    const { t } = this.props;
-    const { hash } = this.state;
+    setState({
+      data,
+      hash: blake2AsHex(
+        isHexData
+          ? hexToU8a(data)
+          : stringToU8a(data),
+        256
+      ),
+      isHexData
+    });
+  };
 
-    return (
+  return (
+    <div className='toolbox--Hash'>
+      <div className='ui--row'>
+        <Input
+          autoFocus
+          className='full'
+          help={t('The input data to hash. This can be either specified as a hex value (0x-prefix) or as a string.')}
+          label={t('from the following data')}
+          onChange={_onChangeData}
+          value={data}
+        />
+      </div>
+      <div className='ui--row'>
+        <Static
+          className='medium'
+          help={t('Detection on the input string to determine if it is hex or non-hex.')}
+          label={t('hex input data')}
+          value={
+            isHexData
+              ? t('Yes')
+              : t('No')
+          }
+        />
+      </div>
       <div className='ui--row'>
         <Output
           className='full'
@@ -81,20 +74,8 @@ class Hash extends React.PureComponent<Props, State> {
           withCopy
         />
       </div>
-    );
-  }
-
-  private onChangeData = (data: string): void => {
-    const isHexData = isHex(data);
-    const hash = blake2AsHex(
-      isHexData
-        ? hexToU8a(data)
-        : stringToU8a(data),
-      256
-    );
-
-    this.setState({ data, hash, isHexData });
-  }
+    </div>
+  );
 }
 
 export default translate(Hash);

+ 1 - 1
packages/app-toolbox/src/Rpc/Account.tsx

@@ -22,7 +22,7 @@ function Account ({ className, defaultValue, isError, onChange, t }: Props): Rea
   const [accountId, setAccountId] = useState<string | null | undefined>(defaultValue);
   const [accountNonce, setAccountNonce] = useState(new BN(0));
 
-  const _onChangeAccountId = (accountId: string): void => {
+  const _onChangeAccountId = (accountId: string | null): void => {
     setAccountId(accountId);
     onChange(accountId, accountNonce);
   };

+ 6 - 5
packages/app-toolbox/src/Rpc/Selection.tsx

@@ -9,9 +9,10 @@ import { QueueTxRpcAdd } from '@polkadot/react-components/Status/types';
 
 import React from 'react';
 import rpc from '@polkadot/jsonrpc';
-import { getTypeDef } from '@polkadot/types';
 import { Button, InputRpc, TxComponent } from '@polkadot/react-components';
 import Params from '@polkadot/react-params';
+import { getTypeDef } from '@polkadot/types';
+import { isNull } from '@polkadot/util';
 
 import translate from './translate';
 
@@ -40,9 +41,8 @@ class Selection extends TxComponent<Props, State> {
     const { t } = this.props;
     const { isValid, rpc } = this.state;
     const params = rpc.params.map(({ isOptional, name, type }): ParamDef => ({
-      isOptional,
       name,
-      type: getTypeDef(type)
+      type: getTypeDef(isOptional ? `Option<${type}>` : type)
     }));
 
     return (
@@ -64,7 +64,6 @@ class Selection extends TxComponent<Props, State> {
             isDisabled={!isValid}
             isPrimary
             onClick={this.onSubmit}
-
             label={t('Submit RPC call')}
             icon='sign-in'
             ref={this.button}
@@ -111,7 +110,9 @@ class Selection extends TxComponent<Props, State> {
     queueRpc({
       accountId,
       rpc,
-      values: values.map(({ value }): any => value)
+      values: values
+        .filter(({ value }): boolean => !isNull(value))
+        .map(({ value }): any => value)
     });
   }
 }

+ 112 - 172
packages/app-toolbox/src/Sign.tsx

@@ -5,7 +5,7 @@
 import { I18nProps } from '@polkadot/react-components/types';
 import { KeyringPair } from '@polkadot/keyring/types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import styled from 'styled-components';
 import { withMulti } from '@polkadot/react-api';
 import { Button, Input, InputAddress, Output, Static } from '@polkadot/react-components';
@@ -28,16 +28,21 @@ interface State {
   signature: string;
 }
 
-class Sign extends React.PureComponent<Props, State> {
-  public state: State;
-
-  public constructor (props: Props) {
-    super(props);
-
+function Sign ({ className, t }: Props): React.ReactElement<Props> {
+  const [state, setState] = useState<State>({
+    currentPair: null,
+    data: '',
+    isHexData: false,
+    isLocked: false,
+    isUnlockVisible: false,
+    signature: ''
+  });
+
+  useEffect((): void => {
     const pairs = keyring.getPairs();
     const currentPair = pairs[0] || null;
 
-    this.state = {
+    setState({
       currentPair,
       data: '',
       isHexData: false,
@@ -46,83 +51,62 @@ class Sign extends React.PureComponent<Props, State> {
         : false,
       isUnlockVisible: false,
       signature: ''
-    };
-  }
+    });
+  }, []);
+
+  const _nextState = ({ currentPair = state.currentPair, data = state.data, isHexData = state.isHexData, isUnlockVisible = state.isUnlockVisible }: Partial<State>): void => {
+    const isLocked = !currentPair || currentPair.isLocked;
+    let signature = '';
+
+    if (!isLocked && currentPair) {
+      signature = u8aToHex(
+        currentPair.sign(
+          isHexData
+            ? hexToU8a(data)
+            : stringToU8a(data)
+        )
+      );
+    }
 
-  public render (): React.ReactNode {
-    const { className } = this.props;
-    const { isLocked } = this.state;
+    setState({
+      currentPair,
+      data,
+      isHexData,
+      isLocked,
+      isUnlockVisible,
+      signature
+    });
+  };
 
-    return (
-      <div className='toolbox--Sign'>
-        {this.renderAccount()}
-        <div className={className}>
-          {this.renderInput()}
-          {this.renderSignature()}
-          <div className='unlock-overlay' hidden={!isLocked}>
-            {this.renderUnlockWarning()}
-          </div>
-          {this.renderUnlock()}
-        </div>
-      </div>
-    );
-  }
+  const _toggleUnlock = (): void =>
+    _nextState({ isUnlockVisible: !state.isUnlockVisible });
+  const _onChangeAccount = (accountId: string | null): void =>
+    _nextState({ currentPair: keyring.getPair(accountId || '') });
+  const _onChangeData = (data: string): void =>
+    _nextState({ data, isHexData: isHex(data) });
 
-  public renderAccount (): React.ReactNode {
-    const { t } = this.props;
+  const { currentPair, data, isHexData, isLocked, isUnlockVisible, signature } = state;
 
-    return (
+  return (
+    <div className={`toolbox--Sign ${className}`}>
       <div className='ui--row'>
         <InputAddress
           className='full'
           help={t('select the account you wish to sign data with')}
           isInput={false}
           label={t('account')}
-          onChange={this.onChangeAccount}
+          onChange={_onChangeAccount}
           type='account'
         />
       </div>
-    );
-  }
-
-  public renderUnlockWarning (): React.ReactNode {
-    const { t } = this.props;
-    const { isLocked } = this.state;
-
-    if (!isLocked) {
-      return null;
-    }
-
-    return (
-      <div className='unlock-overlay-warning'>
-        <div className='unlock-overlay-content'>
-          {t('You need to unlock this account to be able to sign data.')}<br/>
-          <Button.Group>
-            <Button
-              isPrimary
-              onClick={this.toggleUnlock}
-              label={t('Unlock account')}
-              icon='unlock'
-            />
-          </Button.Group>
-        </div>
-      </div>
-    );
-  }
-
-  public renderInput (): React.ReactNode {
-    const { t } = this.props;
-    const { data, isHexData } = this.state;
-
-    return (
-      <>
+      <div className='toolbox--Sign-input'>
         <div className='ui--row'>
           <Input
             autoFocus
             className='full'
             help={t('The input data to sign. This can be either specified as a hex value (0x-prefix) or as a string.')}
             label={t('sign the following data')}
-            onChange={this.onChangeData}
+            onChange={_onChangeData}
             value={data}
           />
         </div>
@@ -138,122 +122,78 @@ class Sign extends React.PureComponent<Props, State> {
             }
           />
         </div>
-      </>
-    );
-  }
-
-  public renderSignature (): React.ReactNode {
-    const { t } = this.props;
-    const { signature } = this.state;
-
-    return (
-      <div className='ui--row'>
-        <Output
-          className='full'
-          help={t('The resulting signature of the input data, as done with the crypto algorithm from the account. (This could be non-deterministic for some types such as sr25519).')}
-          isHidden={signature.length === 0}
-          isMonospace
-          label={t('signature of supplied data')}
-          value={signature}
-          withCopy
-        />
+        <div className='ui--row'>
+          <Output
+            className='full'
+            help={t('The resulting signature of the input data, as done with the crypto algorithm from the account. (This could be non-deterministic for some types such as sr25519).')}
+            isHidden={signature.length === 0}
+            isMonospace
+            label={t('signature of supplied data')}
+            value={signature}
+            withCopy
+          />
+        </div>
+        <div
+          className='unlock-overlay'
+          hidden={!isLocked}
+        >
+          {isLocked && (
+            <div className='unlock-overlay-warning'>
+              <div className='unlock-overlay-content'>
+                {t('You need to unlock this account to be able to sign data.')}<br/>
+                <Button.Group>
+                  <Button
+                    isPrimary
+                    onClick={_toggleUnlock}
+                    label={t('Unlock account')}
+                    icon='unlock'
+                  />
+                </Button.Group>
+              </div>
+            </div>
+          )}
+        </div>
+        {isUnlockVisible && (
+          <Unlock
+            onClose={_toggleUnlock}
+            pair={currentPair}
+          />
+        )}
       </div>
-    );
-  }
-
-  public renderUnlock (): React.ReactNode {
-    const { currentPair, isUnlockVisible } = this.state;
-
-    if (!isUnlockVisible) {
-      return null;
-    }
-
-    return (
-      <Unlock
-        onClose={this.toggleUnlock}
-        pair={currentPair}
-      />
-    );
-  }
-
-  private nextState = (newState: Partial<State>): void => {
-    this.setState(
-      (prevState: State): State => {
-        const { currentPair = prevState.currentPair, data = prevState.data, isHexData = prevState.isHexData, isUnlockVisible = prevState.isUnlockVisible } = newState;
-        const isLocked = !currentPair || currentPair.isLocked;
-        let signature = '';
-
-        if (!isLocked && currentPair) {
-          signature = u8aToHex(
-            currentPair.sign(
-              isHexData
-                ? hexToU8a(data)
-                : stringToU8a(data)
-            )
-          );
-        }
-
-        return {
-          currentPair,
-          data,
-          isHexData,
-          isLocked,
-          isUnlockVisible,
-          signature
-        };
-      }
-    );
-  }
-
-  private toggleUnlock = (): void => {
-    const { isUnlockVisible } = this.state;
-
-    this.nextState({
-      isUnlockVisible: !isUnlockVisible
-    });
-  }
-
-  private onChangeAccount = (accountId: string): void => {
-    const currentPair = keyring.getPair(accountId);
-
-    this.nextState({ currentPair });
-  }
-
-  private onChangeData = (data: string): void => {
-    const isHexData = isHex(data);
-
-    this.nextState({ data, isHexData });
-  }
+    </div>
+  );
 }
 
 export default withMulti(
   styled(Sign)`
-    position: relative;
-    width: 100%;
-    height: 100%;
-
-    .unlock-overlay {
-      position: absolute;
+    .toolbox--Sign-input {
+      position: relative;
       width: 100%;
       height: 100%;
-      top:0;
-      left:0;
-      background-color: #0f0e0e7a;
-    }
 
-    .unlock-overlay-warning {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      height:100%;
-    }
+      .unlock-overlay {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        top:0;
+        left:0;
+        background-color: #0f0e0e7a;
+      }
 
-    .unlock-overlay-content {
-      color:#fff;
-      text-align:center;
+      .unlock-overlay-warning {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height:100%;
+      }
+
+      .unlock-overlay-content {
+        color:#fff;
+        text-align:center;
 
-      .ui--Button-Group {
-        text-align: center;
+        .ui--Button-Group {
+          text-align: center;
+        }
       }
     }
   `,

+ 31 - 49
packages/app-toolbox/src/Verify.tsx

@@ -63,11 +63,21 @@ class Verify extends React.PureComponent<Props, State> {
 
   public render (): React.ReactNode {
     const { t } = this.props;
-    const { cryptoOptions, cryptoType, data, isHexData } = this.state;
+    const { cryptoOptions, cryptoType, data, defaultPublicKey, isHexData, isValid, isValidAddress, isValidSignature, signature } = this.state;
 
     return (
       <div className='toolbox--Verify'>
-        {this.renderAddress()}
+        <div className='ui--row'>
+          <InputAddress
+            className='full'
+            defaultValue={defaultPublicKey}
+            help={t('The account that signed the input')}
+            isError={!isValidAddress}
+            isInput
+            label={t('verify using address')}
+            onChange={this.onChangeAddress}
+          />
+        </div>
         <div className='ui--row'>
           <Input
             autoFocus
@@ -78,7 +88,23 @@ class Verify extends React.PureComponent<Props, State> {
             value={data}
           />
         </div>
-        {this.renderSignature()}
+        <div className='ui--row'>
+          <Input
+            className='full'
+            icon={
+              <Icon
+                color={isValid ? 'green' : (isValidSignature ? 'red' : undefined)}
+                name={isValid ? 'check circle' : (isValidSignature ? 'exclamation circle' : 'help circle')}
+                size='big'
+              />
+            }
+            isError={!isValidSignature}
+            help={t('The signature as by the account being checked, supplied as a hex-formatted string.')}
+            label={t('the supplied signature')}
+            onChange={this.onChangeSignature}
+            value={signature}
+          />
+        </div>
         <div className='ui--row'>
           <Dropdown
             defaultValue={cryptoType}
@@ -102,50 +128,6 @@ class Verify extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderAddress (): React.ReactNode {
-    const { t } = this.props;
-    const { defaultPublicKey, isValidAddress } = this.state;
-
-    return (
-      <div className='ui--row'>
-        <InputAddress
-          className='full'
-          defaultValue={defaultPublicKey}
-          help={t('The account that signed the input')}
-          isError={!isValidAddress}
-          isInput
-          label={t('verify using address')}
-          onChange={this.onChangeAddress}
-        />
-      </div>
-    );
-  }
-
-  private renderSignature (): React.ReactNode {
-    const { t } = this.props;
-    const { isValid, isValidSignature, signature } = this.state;
-
-    return (
-      <div className='ui--row'>
-        <Input
-          className='full'
-          icon={
-            <Icon
-              color={isValid ? 'green' : (isValidSignature ? 'red' : undefined)}
-              name={isValid ? 'check circle' : (isValidSignature ? 'exclamation circle' : 'help circle')}
-              size='big'
-            />
-          }
-          isError={!isValidSignature}
-          help={t('The signature as by the account being checked, supplied as a hex-formatted string.')}
-          label={t('the supplied signature')}
-          onChange={this.onChangeSignature}
-          value={signature}
-        />
-      </div>
-    );
-  }
-
   private nextState (newState: Partial<State>): void {
     this.setState(
       (prevState: State): Pick<State, never> => {
@@ -209,11 +191,11 @@ class Verify extends React.PureComponent<Props, State> {
     this.nextState({ signature, isValidSignature });
   }
 
-  private onChangeAddress = (accountId: string): void => {
+  private onChangeAddress = (accountId: string | null): void => {
     let currentPublicKey;
 
     try {
-      currentPublicKey = keyring.decodeAddress(accountId);
+      currentPublicKey = keyring.decodeAddress(accountId || '');
     } catch (err) {
       console.error(err);
     }

+ 4 - 4
packages/app-transfer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-transfer",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A basic transfer app",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65"
   }
 }

+ 4 - 4
packages/app-treasury/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-treasury",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "A referendum & proposal app",
   "main": "index.js",
   "scripts": {},
@@ -11,8 +11,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30"
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65"
   }
 }

+ 43 - 75
packages/app-treasury/src/Overview/Proposal.tsx

@@ -6,7 +6,7 @@ import { TreasuryProposal as TreasuryProposalType } from '@polkadot/types/interf
 import { I18nProps } from '@polkadot/react-components/types';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import styled from 'styled-components';
 import { Option } from '@polkadot/types';
 import { ActionItem, Icon, TreasuryProposal } from '@polkadot/react-components';
@@ -30,83 +30,51 @@ interface Props extends I18nProps {
   onRespond: () => void;
 }
 
-interface State {
-  isApproveOpen: boolean;
-}
-
-class ProposalDisplay extends React.PureComponent<Props, State> {
-  public constructor (props: Props) {
-    super(props);
-
-    const { proposal, onPopulate } = props;
-    if (proposal) {
-      onPopulate();
-    }
-  }
-
-  public componentDidUpdate ({ proposal }: Props): void {
-    const { onPopulate } = this.props;
-
-    if (this.props.proposal && !proposal) {
-      onPopulate();
-    }
-  }
+function ProposalDisplay ({ allAccounts, isApproved, onPopulate, onRespond, proposal, proposalId, t }: Props): React.ReactElement<Props> | null {
+  useEffect((): void => {
+    onPopulate();
+  }, [proposal]);
 
-  public state: State = {
-    isApproveOpen: false
-  };
-
-  public render (): React.ReactNode {
-    const { proposal, proposalId } = this.props;
-
-    if (!proposal) {
-      return null;
-    }
-
-    return (
-      <ActionItem
-        accessory={this.renderAccessory()}
-        idNumber={proposalId}
-      >
-        <TreasuryProposal proposal={proposal} />
-      </ActionItem>
-    );
+  if (!proposal) {
+    return null;
   }
 
-  private renderAccessory (): React.ReactNode {
-    const { allAccounts, isApproved, onRespond, proposal, proposalId, t } = this.props;
-
-    if (isApproved) {
-      return (
-        <Approved>
-          <Icon name='check' />
-          {'  '}
-          {t('Approved')}
-        </Approved>
-      );
-    }
-
-    const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
-    if (!hasAccounts) {
-      return null;
-    }
-
-    return (
-      <Approve
-        proposalInfo={
-          <>
-            <h3>Proposal #{proposalId}</h3>
-            <details>
-              <TreasuryProposal proposal={proposal} />
-            </details>
-            <br />
-          </>
-        }
-        proposalId={proposalId}
-        onSuccess={onRespond}
-      />
-    );
-  }
+  const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
+
+  return (
+    <ActionItem
+      accessory={
+        isApproved
+          ? (
+            <Approved>
+              <Icon name='check' />
+              {'  '}
+              {t('Approved')}
+            </Approved>
+          )
+          : hasAccounts
+            ? (
+              <Approve
+                proposalInfo={
+                  <>
+                    <h3>Proposal #{proposalId}</h3>
+                    <details>
+                      <TreasuryProposal proposal={proposal} />
+                    </details>
+                    <br />
+                  </>
+                }
+                proposalId={proposalId}
+                onSuccess={onRespond}
+              />
+            )
+            : null
+      }
+      idNumber={proposalId}
+    >
+      <TreasuryProposal proposal={proposal} />
+    </ActionItem>
+  );
 }
 
 export default withMulti(

+ 40 - 70
packages/app-treasury/src/Overview/Proposals.tsx

@@ -7,7 +7,7 @@ import { ProposalIndex } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
 
 import BN from 'bn.js';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import { RouteComponentProps } from 'react-router';
 import { withRouter } from 'react-router-dom';
 import { withCalls, withMulti } from '@polkadot/react-api';
@@ -22,98 +22,68 @@ interface Props extends I18nProps, RouteComponentProps<{}> {
   treasury_proposalCount?: BN;
 }
 
-interface State {
-  isEmpty: boolean;
-  isProposeOpen: boolean;
-  proposalIndices: BN[];
-}
-
-class ProposalsBase extends React.PureComponent<Props, State> {
-  public state: State = {
-    isEmpty: true,
-    isProposeOpen: false,
-    proposalIndices: [] as BN[]
-  };
+function ProposalsBase ({ history, isApprovals = false, treasury_approvals, treasury_proposalCount, t }: Props): React.ReactElement<Props> {
+  const [isEmpty, setIsEmpty] = useState(true);
+  const [proposalIndices, setProposalIndices] = useState<BN[]>([]);
 
-  public static getDerivedStateFromProps ({ isApprovals = false, treasury_approvals = [] as BN[], treasury_proposalCount = new BN(0) }: Props): Pick<State, never> {
+  useEffect((): void => {
     let proposalIndices: BN[] = [];
 
     if (isApprovals) {
-      proposalIndices = treasury_approvals;
-    } else {
+      proposalIndices = treasury_approvals || [];
+    } else if (treasury_proposalCount && treasury_approvals) {
       for (let i = 0; i < treasury_proposalCount.toNumber(); i++) {
         if (!treasury_approvals.find((index): boolean => index.eqn(i))) {
           proposalIndices.push(new BN(i));
         }
       }
     }
-    return { proposalIndices };
-  }
-
-  public render (): React.ReactNode {
-    const { isApprovals, t } = this.props;
-    const { isEmpty } = this.state;
-
-    return (
-      <>
-        <Column
-          emptyText={t(isApprovals ? 'No approved proposals' : 'No pending proposals')}
-          headerText={t(isApprovals ? 'Approved' : 'Proposals')}
-          isEmpty={isEmpty}
-        >
-          {this.renderProposals()}
-        </Column>
-      </>
-    );
-  }
-
-  private renderProposals (): React.ReactNode {
-    const { isApprovals } = this.props;
-    const { proposalIndices } = this.state;
-
-    return proposalIndices.map((proposalId): React.ReactNode => (
-      <Proposal
-        isApproved={isApprovals}
-        onPopulate={this.onPopulateProposal}
-        onRespond={this.onRespond}
-        proposalId={proposalId.toString()}
-        key={proposalId.toString()}
-      />
-    ));
-  }
 
-  protected onRespond = (): void => {
-    const { history } = this.props;
+    setProposalIndices(proposalIndices);
+  }, [isApprovals, treasury_approvals, treasury_approvals]);
 
+  const _onRespond = (): void => {
     history.push('/council/motions');
-  }
-
-  private onPopulateProposal = (): void => {
-    this.setState(({ isEmpty }: State): Pick<State, never> | null => {
-      if (isEmpty) {
-        return { isEmpty: false };
-      }
+  };
+  const _onPopulateProposal = (): void => {
+    isEmpty && setIsEmpty(false);
+  };
 
-      return null;
-    });
-  }
+  return (
+    <>
+      <Column
+        emptyText={isApprovals ? t('No approved proposals') : t('No pending proposals')}
+        headerText={isApprovals ? t('Approved') : t('Proposals')}
+        isEmpty={isEmpty}
+      >
+        {proposalIndices.map((proposalId): React.ReactNode => (
+          <Proposal
+            isApproved={isApprovals}
+            onPopulate={_onPopulateProposal}
+            onRespond={_onRespond}
+            proposalId={proposalId.toString()}
+            key={proposalId.toString()}
+          />
+        ))}
+      </Column>
+    </>
+  );
 }
 
 const Proposals = withMulti(
   withRouter(ProposalsBase),
   translate,
   withCalls<Props>(
-    [
-      'query.treasury.approvals',
-      {
-        transform: (value: ProposalIndex[]): BN[] =>
-          value.map((proposalId): BN => new BN(proposalId))
-      }
-    ],
+    ['query.treasury.approvals', {
+      transform: (value: ProposalIndex[]): BN[] =>
+        value.map((proposalId): BN => new BN(proposalId))
+    }],
     'query.treasury.proposalCount'
   )
 );
 
 export default Proposals;
 
-export const Approvals = (): JSX.Element => <Proposals isApprovals />;
+export function Approvals (): React.ReactElement<{}> {
+  return <Proposals isApprovals />;
+}

+ 4 - 6
packages/app-treasury/src/Overview/Propose.tsx

@@ -11,7 +11,7 @@ import TxModal, { TxModalState, TxModalProps as Props } from '@polkadot/react-co
 import translate from '../translate';
 
 interface State extends TxModalState {
-  beneficiary?: string;
+  beneficiary?: string | null;
   value: BN;
 }
 
@@ -25,12 +25,10 @@ class Propose extends TxModal<Props, State> {
 
   protected txMethod = (): string => 'treasury.proposeSpend';
 
-  protected txParams = (): (string | BN | undefined)[] => {
+  protected txParams = (): [BN, string | null | undefined] => {
     const { beneficiary, value } = this.state;
 
-    return [
-      value, beneficiary
-    ];
+    return [value, beneficiary];
   }
 
   protected isDisabled = (): boolean => {
@@ -96,7 +94,7 @@ class Propose extends TxModal<Props, State> {
     );
   }
 
-  private onChangeBeneficiary = (beneficiary: string): void => {
+  private onChangeBeneficiary = (beneficiary: string | null): void => {
     this.nextState({ beneficiary });
   }
 

+ 7 - 10
packages/app-treasury/src/Overview/Summary.tsx

@@ -19,9 +19,8 @@ interface Props extends I18nProps {
   treasury_pot?: BN;
 }
 
-function Summary (props: Props): React.ReactElement<Props> {
-  const { treasury_proposalCount = new BN(0), treasury_approvals = [] as BN[], treasury_pot = new BN(0), t } = props;
-  const value = treasury_pot
+function Summary ({ treasury_proposalCount, treasury_approvals, treasury_pot, t }: Props): React.ReactElement<Props> {
+  const value = treasury_pot && treasury_pot.gtn(0)
     ? treasury_pot.toString()
     : null;
 
@@ -36,13 +35,11 @@ function Summary (props: Props): React.ReactElement<Props> {
         </CardSummary>
       </section>
       <section>
-        <CardSummary label={t('pot')}>
-          {
-            value
-              ? `${formatBalance(value, false)}${treasury_pot.gtn(0) ? formatBalance.calcSi(value).value : ''}`
-              : '-'
-          }
-        </CardSummary>
+        {value && (
+          <CardSummary label={t('pot')}>
+            {formatBalance(value, false)}{formatBalance.calcSi(value).value}
+          </CardSummary>
+        )}
       </section>
     </SummaryBox>
   );

+ 2 - 2
packages/app-treasury/src/Settings.tsx

@@ -25,7 +25,7 @@ interface Props extends I18nProps, ApiProps, RouteComponentProps {
 }
 
 interface State {
-  accountId?: string;
+  accountId?: string | null;
   proposalBond?: BN;
   proposalBondMinimum?: BN;
   spendPeriod?: BN;
@@ -174,7 +174,7 @@ class Settings extends TxComponent<Props, State> {
     );
   }
 
-  private onChangeAccount = (accountId: string): void => {
+  private onChangeAccount = (accountId: string | null): void => {
     this.nextState({ accountId });
   }
 

+ 2 - 2
packages/apps-routing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/apps-routing",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,6 +10,6 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.0"
+    "@babel/runtime": "^7.6.2"
   }
 }

+ 5 - 5
packages/apps/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/apps",
-  "version": "0.36.0-beta.30",
+  "version": "0.36.0-beta.65",
   "description": "An Apps portal into the Polkadot network",
   "main": "index.js",
   "homepage": ".",
@@ -13,10 +13,10 @@
   "license": "Apache-2.0",
   "dependencies": {
     "@babel/polyfill": "^7.6.0",
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-signer": "^0.36.0-beta.30",
-    "@polkadot/ui-assets": "^0.45.0-beta.13",
+    "@babel/runtime": "^7.6.2",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-signer": "^0.36.0-beta.65",
+    "@polkadot/ui-assets": "^0.46.0-beta.0",
     "query-string": "^6.8.3"
   }
 }

+ 3 - 3
packages/apps/src/Content/Status.tsx

@@ -27,7 +27,7 @@ interface Props extends I18nProps {
 
 let prevEventHash: string;
 
-function Status ({ optionsAll, queueAction, stqueue, system_events = [], t, txqueue }: Props): React.ReactElement<Props> {
+function Status ({ optionsAll, queueAction, stqueue, system_events, t, txqueue }: Props): React.ReactElement<Props> {
   useEffect((): void => {
     const eventHash = xxhashAsHex(stringToU8a(JSON.stringify(system_events)));
 
@@ -38,7 +38,7 @@ function Status ({ optionsAll, queueAction, stqueue, system_events = [], t, txqu
     prevEventHash = eventHash;
 
     const addresses = optionsAll.account.map((account): string | null => account.value);
-    const statusses = system_events
+    const statusses = system_events && system_events
       .map(({ event: { data, method, section } }): ActionStatus | null => {
         if (section === 'balances' && method === 'Transfer') {
           const account = data[1].toString();
@@ -69,7 +69,7 @@ function Status ({ optionsAll, queueAction, stqueue, system_events = [], t, txqu
       })
       .filter((item): boolean => !!item) as ActionStatus[];
 
-    statusses.length && queueAction(statusses);
+    statusses && statusses.length && queueAction(statusses);
   }, [system_events]);
 
   return (

+ 80 - 87
packages/apps/src/SideBar/Item.tsx

@@ -3,15 +3,14 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { I18nProps } from '@polkadot/react-components/types';
-import { ApiProps } from '@polkadot/react-api/types';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { Route } from '@polkadot/apps-routing/types';
 
-import React from 'react';
+import React, { useContext } from 'react';
 import { NavLink } from 'react-router-dom';
 import { Icon, Menu, Tooltip } from '@polkadot/react-components';
 import accountObservable from '@polkadot/ui-keyring/observable/accounts';
-import { withApi, withCalls, withMulti, withObservable } from '@polkadot/react-api';
+import { ApiContext, withCalls, withMulti, withObservable } from '@polkadot/react-api';
 import { isFunction } from '@polkadot/util';
 import { Option } from '@polkadot/types';
 
@@ -22,12 +21,12 @@ import { ElectionStage } from '@joystream/types/';
 import { councilSidebarName } from '@polkadot/apps-routing/joy-election';
 
 
-interface Props extends I18nProps, ApiProps {
+interface Props extends I18nProps {
   isCollapsed: boolean;
   onClick: () => void;
   allAccounts?: SubjectInfo;
   route: Route;
-  sudo_key: string;
+  sudoKey: string;
   electionStage: Option<ElectionStage>;
 }
 
@@ -36,75 +35,27 @@ type Subtitle = {
   classes: string[]
 };
 
-class Item extends React.PureComponent<Props> {
-  private _disabledLog: Map<string, string> = new Map();
 
-  public render (): React.ReactNode {
-    const { route: { Modal, i18n, icon, name }, t, isCollapsed, onClick } = this.props;
+const disabledLog: Map<string, string> = new Map();
 
-    if (!this.isVisible()) {
-      return null;
-    }
-
-    const subtitle = this.getSubtitle(name);
+function logDisabled (route: string, message: string): void {
+  if (!disabledLog.get(route)) {
+    disabledLog.set(route, message);
 
-    const body = (
-      <>
-        <Icon name={icon} />
-        <div className='text SidebarItem'>
-            <div>{t(`sidebar.${name}`, i18n)}</div>
-            {subtitle && <div className={`SidebarSubtitle ${subtitle.classes.join(' ')}`}>{subtitle.text}</div>}
-        </div>
-        <Tooltip
-          offset={{ right: -4 }}
-          place='right'
-          text={t(`sidebar.${name}`, i18n)}
-          trigger={`nav-${name}`}
-        />
-      </>
-    );
-
-    return (
-      <Menu.Item className='apps--SideBar-Item'>
-        {
-          Modal
-            ? (
-              <a
-                className='apps--SideBar-Item-NavLink'
-                data-for={`nav-${name}`}
-                data-tip
-                data-tip-disable={!isCollapsed}
-                onClick={onClick}
-              >
-                {body}
-              </a>
-            )
-            : (
-              <NavLink
-                activeClassName='apps--SideBar-Item-NavLink-active'
-                className='apps--SideBar-Item-NavLink'
-                data-for={`nav-${name}`}
-                data-tip
-                data-tip-disable={!isCollapsed}
-                onClick={onClick}
-                to={`/${name}`}
-              >
-                {body}
-              </NavLink>
-            )
-        }
-      </Menu.Item>
-    );
+    console.warn(`Disabling ${route}: ${message}`);
   }
+}
+
+function Item ({ allAccounts, route: { Modal, display: { isHidden, needsAccounts, needsApi, needsSudo }, i18n, icon, name }, t, isCollapsed, onClick, sudoKey, electionStage }: Props): React.ReactElement<Props> | null {
+  const { api, isApiConnected, isApiReady } = useContext(ApiContext);
 
-  private getSubtitle (name: string): Subtitle | undefined {
+  const _getSubtitle = (name: string): Subtitle | undefined => {
     if (name === councilSidebarName) {
-      const { electionStage: stage } = this.props;
-      if (stage && stage.isSome) {
+      if (electionStage && electionStage.isSome) {
         const classes: string[] = [];
         let text = 'No active election';
-        if (stage.isSome) {
-          const stageValue = stage.value as ElectionStage;
+        if (electionStage.isSome) {
+          const stageValue = electionStage.value as ElectionStage;
           const stageName = stageValue.type;
           text = `${stageName} stage`;
           classes.push(stageName);
@@ -115,16 +66,7 @@ class Item extends React.PureComponent<Props> {
     return undefined;
   }
 
-  private logDisabled (route: string, message: string): void {
-    if (!this._disabledLog.get(route)) {
-      this._disabledLog.set(route, message);
-
-      console.warn(`Disabling ${route}: ${message}`);
-    }
-  }
-
-  private hasApi (endpoint: string): boolean {
-    const { api } = this.props;
+  const _hasApi = (endpoint: string): boolean => {
     const [area, section, method] = endpoint.split('.');
 
     try {
@@ -132,12 +74,10 @@ class Item extends React.PureComponent<Props> {
     } catch (error) {
       return false;
     }
-  }
-
-  private isVisible (): boolean {
-    const { allAccounts = {}, isApiConnected, isApiReady, route: { display: { isHidden, needsAccounts, needsApi, needsSudo }, name }, sudo_key: sudoKey } = this.props;
-    const hasAccounts = Object.keys(allAccounts).length !== 0;
-    const hasSudo = !!Object.keys(allAccounts).find((address): boolean => address === sudoKey);
+  };
+  const _isVisible = (): boolean => {
+    const hasAccounts = !!allAccounts && Object.keys(allAccounts).length !== 0;
+    const hasSudo = !!allAccounts && Object.keys(allAccounts).some((address): boolean => address === sudoKey);
 
     if (isHidden) {
       return false;
@@ -149,34 +89,87 @@ class Item extends React.PureComponent<Props> {
       return false;
     } else if (needsSudo) {
       if (!hasSudo) {
-        this.logDisabled('sudo', 'Sudo key not available');
+        logDisabled(name, 'Sudo key not available');
         return false;
       }
     }
 
     const notFound = needsApi.filter((endpoint: string | string[]): boolean => {
       const hasApi = Array.isArray(endpoint)
-        ? endpoint.reduce((hasApi, endpoint): boolean => hasApi || this.hasApi(endpoint), false)
-        : this.hasApi(endpoint);
+        ? endpoint.reduce((hasApi, endpoint): boolean => hasApi || _hasApi(endpoint), false)
+        : _hasApi(endpoint);
 
       return !hasApi;
     });
 
     if (notFound.length !== 0) {
-      this.logDisabled(name, `API not available: ${notFound}`);
+      logDisabled(name, `API not available: ${notFound}`);
     }
 
     return notFound.length === 0;
+  };
+
+  if (!_isVisible()) {
+    return null;
   }
+
+  const subtitle = _getSubtitle(name);
+
+  const body = (
+    <>
+      <Icon name={icon} />
+      <div className='text SidebarItem'>
+            <div>{t(`sidebar.${name}`, i18n)}</div>
+            {subtitle && <div className={`SidebarSubtitle ${subtitle.classes.join(' ')}`}>{subtitle.text}</div>}
+      </div>
+      <Tooltip
+        offset={{ right: -4 }}
+        place='right'
+        text={t(`sidebar.${name}`, i18n)}
+        trigger={`nav-${name}`}
+      />
+    </>
+  );
+
+  return (
+    <Menu.Item className='apps--SideBar-Item'>
+      {Modal
+        ? (
+          <a
+            className='apps--SideBar-Item-NavLink'
+            data-for={`nav-${name}`}
+            data-tip
+            data-tip-disable={!isCollapsed}
+            onClick={onClick}
+          >
+            {body}
+          </a>
+        )
+        : (
+          <NavLink
+            activeClassName='apps--SideBar-Item-NavLink-active'
+            className='apps--SideBar-Item-NavLink'
+            data-for={`nav-${name}`}
+            data-tip
+            data-tip-disable={!isCollapsed}
+            onClick={onClick}
+            to={`/${name}`}
+          >
+            {body}
+          </NavLink>
+        )
+      }
+    </Menu.Item>
+  );
 }
 
 export default withMulti(
   Item,
   translate,
-  withApi,
   withCalls(queryToProp('query.councilElection.stage', { propName: 'electionStage' })),
   withCalls<Props>(
     ['query.sudo.key', {
+      propName: 'sudoKey',
       transform: (key): string =>
         key.toString()
     }]

+ 127 - 178
packages/apps/src/SideBar/index.tsx

@@ -1,23 +1,20 @@
-/* eslint-disable @typescript-eslint/camelcase */
 // Copyright 2017-2019 @polkadot/apps authors & contributors
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { Route } from '@polkadot/apps-routing/types';
-import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { SIDEBAR_MENU_THRESHOLD } from '../constants';
 
 import './SideBar.css';
 
-import React from 'react';
+import React, { useContext, useState } from 'react';
 import styled from 'styled-components';
 import { Responsive } from 'semantic-ui-react';
 import routing from '@polkadot/apps-routing';
-import { withApi, withMulti } from '@polkadot/react-api';
+import { ApiContext } from '@polkadot/react-api';
 import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
 import { classes } from '@polkadot/react-components/util';
-// import { BestNumber, Chain } from '@polkadot/react-query';
+import { BestNumber, Chain } from '@polkadot/react-query';
 
 import translate from '../translate';
 import Item from './Item';
@@ -26,7 +23,7 @@ import NetworkModal from '../modals/Network';
 
 import { SemanticICONS } from 'semantic-ui-react';
 
-interface Props extends ApiProps, I18nProps {
+interface Props extends I18nProps /*ApiProps,*/ {
   className?: string;
   collapse: () => void;
   handleResize: () => void;
@@ -52,6 +49,7 @@ function OuterLink ({ url, title, icon = 'external alternate' }: OuterLinkProps)
   );
 }
 
+/*
 interface State {
   modals: Record<string, boolean>;
 }
@@ -112,182 +110,121 @@ class SideBar extends React.PureComponent<Props, State> {
             {this.renderCollapse()}
           </Menu>
           <Responsive minWidth={SIDEBAR_MENU_THRESHOLD}>
-            <div
-              className='apps--SideBar-toggle'
-              onClick={this.props.collapse}
-            />
-          </Responsive>
-        </div>
-      </Responsive>
-    );
-  }
-
-  private renderCollapse (): React.ReactNode {
-    const { isCollapsed } = this.props;
-
-    return (
-      <Responsive
-        minWidth={SIDEBAR_MENU_THRESHOLD}
-        className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}
-      >
-        <Button
-          icon={`angle double ${isCollapsed ? 'right' : 'left'}`}
-          isBasic
-          isCircular
-          onClick={this.props.collapse}
-        />
-      </Responsive>
-    );
-  }
-
-  /*
-  private renderLogo (): React.ReactNode {
-    const { api, isApiReady } = this.props;
+*/
+function SideBar ({ className, collapse, handleResize, isCollapsed, toggleMenu, menuOpen }: Props): React.ReactElement<Props> {
+  const { api, isApiReady } = useContext(ApiContext);
+  const [modals, setModals] = useState<Record<string, boolean>>(
+    routing.routes.reduce((result: Record<string, boolean>, route): Record<string, boolean> => {
+      if (route && route.Modal) {
+        result[route.name] = false;
+      }
 
-    return (
-      <div
-        className='apps--SideBar-logo'
-        onClick={this.toggleNetworkModal}
-      >
-        <ChainImg />
-        <div className='info'>
-          <Chain className='chain' />
-          {isApiReady &&
-            <div className='runtimeVersion'>version {api.runtimeVersion.specVersion.toNumber()}</div>
-          }
-          <BestNumber label='#' />
-        </div>
-      </div>
-    );
-  }
-  */
+      return result;
+    }, { network: false })
+  );
 
-  private renderJoystreamLogo () {
-    const { isCollapsed } = this.props;
-    const logo = isCollapsed
-      ? 'images/logo-j.svg'
-      : 'images/logo-joytream.svg';
+  const _toggleModal = (name: string): () => void =>
+    (): void => setModals({ ...modals, [name]: !modals[name] });
 
-    return (
-      <img
-        alt='Joystream'
-        className='apps--SideBar-logo'
-        src={logo}
+  return (
+    <Responsive
+      onUpdate={handleResize}
+      className={classes(className, 'apps-SideBar-Wrapper', isCollapsed ? 'collapsed' : 'expanded')}
+    >
+      <ChainImg
+        className={`toggleImg ${menuOpen ? 'closed' : 'open delayed'}`}
+        onClick={toggleMenu}
       />
-    );
-  }
-
-  private renderModals (): React.ReactNode {
-    const { modals } = this.state;
-    const filtered = routing.routes.filter((route): any => route && route.Modal) as Route[];
-
-    return filtered.map(({ name, Modal }): React.ReactNode => (
-      Modal && modals[name]
-        ? (
-          <Modal
-            key={name}
-            onClose={this.closeModal(name)}
-          />
-        )
-        : <div key={name} />
-    ));
-  }
-
-  private renderRoutes (): React.ReactNode {
-    const { handleResize, isCollapsed } = this.props;
-
-    return routing.routes.map((route, index): React.ReactNode => (
-      route
-        ? (
-          <Item
-            isCollapsed={isCollapsed}
-            key={route.name}
-            route={route}
-            onClick={
-              route.Modal
-                ? this.openModal(route.name)
-                : handleResize
+      {routing.routes.map((route): React.ReactNode => (
+        route && route.Modal
+          ? route.Modal && modals[route.name]
+            ? (
+              <route.Modal
+                key={route.name}
+                onClose={_toggleModal(route.name)}
+              />
+            )
+            : <div key={route.name} />
+          : null
+      ))}
+      {modals.network && (
+        <NetworkModal onClose={_toggleModal('network')}/>
+      )}
+      <div className='apps--SideBar'>
+        <Menu
+          secondary
+          vertical
+        >
+          <div className='apps-SideBar-Scroll'>
+          {JoystreamLogo(isCollapsed)}
+            <div
+              className='apps--SideBar-logo'
+              onClick={_toggleModal('network')}
+            >
+              <ChainImg />
+              <div className='info'>
+                <Chain className='chain' />
+                {isApiReady && (
+                  <div className='runtimeVersion'>version {api.runtimeVersion.specVersion.toNumber()}</div>
+                )}
+                <BestNumber label='#' />
+              </div>
+            </div>
+            {routing.routes.map((route, index): React.ReactNode => (
+              route
+                ? (
+                  <Item
+                    isCollapsed={isCollapsed}
+                    key={route.name}
+                    route={route}
+                    onClick={
+                      route.Modal
+                        ? _toggleModal(route.name)
+                        : handleResize
+                    }
+                  />
+                )
+                : (
+                  <Menu.Divider
+                    hidden
+                    key={index}
+                  />
+                )
+            ))}
+            <Menu.Divider hidden />
+            <OuterLink url='https://testnet.joystream.org/faucet' title='Free Tokens' />
+            <OuterLink url='https://blog.joystream.org/acropolis-incentives/' title='Earn Monero' />
+            <Menu.Divider hidden />
+            {
+              isCollapsed
+                ? undefined
+                : <NodeInfo />
             }
+          </div>
+          <Responsive
+            minWidth={SIDEBAR_MENU_THRESHOLD}
+            className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}
+          >
+            <Button
+              icon={`angle double ${isCollapsed ? 'right' : 'left'}`}
+              isBasic
+              isCircular
+              onClick={collapse}
+            />
+          </Responsive>
+        </Menu>
+        <Responsive minWidth={SIDEBAR_MENU_THRESHOLD}>
+          <div
+            className='apps--SideBar-toggle'
+            onClick={collapse}
           />
-        )
-        : (
-          <Menu.Divider
-            hidden
-            key={index}
-          />
-        )
-    ));
-  }
-
-  /*
-  private renderGithub (): React.ReactNode {
-    return (
-      <Menu.Item className='apps--SideBar-Item'>
-        <a
-          className='apps--SideBar-Item-NavLink'
-          href='https://github.com/polkadot-js/apps'
-          rel='noopener noreferrer'
-          target='_blank'
-        >
-          <Icon name='github' /><span className='text'>GitHub</span>
-        </a>
-      </Menu.Item>
-    );
-  }
-
-  private renderWiki (): React.ReactNode {
-    return (
-      <Menu.Item className='apps--SideBar-Item'>
-        <a
-          className='apps--SideBar-Item-NavLink'
-          href='https://wiki.polkadot.network'
-          rel='noopener noreferrer'
-          target='_blank'
-        >
-          <Icon name='book' /><span className='text'>Wiki</span>
-        </a>
-      </Menu.Item>
-    );
-  }
-  */
-
-  private closeModal = (name: string): () => void => {
-    return (): void => {
-      this.setState(({ modals }): State => ({
-        modals: {
-          ...modals,
-          [name]: false
-        }
-      }));
-    };
-  }
-
-  private openModal = (name: string): () => void => {
-    return (): void => {
-      this.setState(({ modals }): State => {
-        return {
-          modals: {
-            ...modals,
-            [name]: true
-          }
-        };
-      });
-    };
-  }
-
-  private toggleNetworkModal = (): void => {
-    this.setState(({ modals }): State => {
-      return {
-        modals: {
-          ...modals,
-          network: !modals.network
-        }
-      };
-    });
-  }
+        </Responsive>
+      </div>
+    </Responsive>
+  );
 }
 
-export default withMulti(
+export default translate(
   styled(SideBar)`
     .toggleImg {
       cursor: pointer;
@@ -313,7 +250,19 @@ export default withMulti(
         top: -2.9rem !important;
       `}
     }
-  `,
-  translate,
-  withApi
+  `
 );
+
+
+function JoystreamLogo (isCollapsed: boolean) {
+  const logo = isCollapsed
+  ? 'images/logo-j.svg'
+  : 'images/logo-joytream.svg';
+  return (
+  <img
+    alt='Joystream'
+    className='apps--SideBar-logo'
+    src={logo}
+  />
+  );
+}

+ 2 - 2
packages/joy-election/package.json

@@ -8,8 +8,8 @@
   "maintainers": [],
   "dependencies": {
     "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.30",
-    "@polkadot/react-query": "^0.36.0-beta.30",
+    "@polkadot/react-components": "^0.36.0-beta.65",
+    "@polkadot/react-query": "^0.36.0-beta.65",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 4 - 4
packages/joy-election/src/Reveals.tsx

@@ -15,15 +15,15 @@ import { withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
 
 // AppsProps is needed to get a location from the route.
 type Props = AppProps & ApiProps & I18nProps & {
-  applicantId?: string;
+  applicantId?: string | null;
   applicants?: AccountId[];
   location: any;
 };
 
 type State = {
-  applicantId?: string,
+  applicantId?: string | null,
   salt?: string,
-  hashedVote?: string
+  hashedVote?: string | null
 };
 
 class RevealVoteForm extends React.PureComponent<Props, State> {
@@ -95,7 +95,7 @@ class RevealVoteForm extends React.PureComponent<Props, State> {
     );
   }
 
-  private onChangeApplicant = (applicantId?: string) => {
+  private onChangeApplicant = (applicantId: string | null) => {
     this.setState({ applicantId });
   }
 

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