diff --git a/packages/light-apps/src/AddAccount/AddAccount.tsx b/packages/light-apps/src/AddAccount/AddAccount.tsx index 155f03617..90d3abe1b 100644 --- a/packages/light-apps/src/AddAccount/AddAccount.tsx +++ b/packages/light-apps/src/AddAccount/AddAccount.tsx @@ -2,11 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Menu, WalletCard } from '@substrate/ui-components'; import React from 'react'; import { RouteComponentProps, Link, Switch, Route, Redirect } from 'react-router-dom'; import { Container } from 'semantic-ui-react'; -import { AppContext } from '@substrate/ui-common'; -import { Menu, WalletCard } from '@substrate/ui-components'; import { Create } from './Create'; import { ImportWithJson } from './ImportWithJson'; @@ -18,47 +17,38 @@ interface MatchParams { interface Props extends RouteComponentProps { } -export class AddAccount extends React.PureComponent { - static contextType = AppContext; - - context!: React.ContextType; // http://bit.ly/typescript-and-react-context - - getActiveTab = () => this.props.location.pathname.split('/')[4]; +export function AddAccount (props: Props) { + const activeTab = props.location.pathname.split('/')[4]; + const { match: { params: { currentAccount } } } = props; - render () { - const { match: { params: { currentAccount } } } = this.props; - - const activeTab = this.getActiveTab(); - - return ( - - - - - Generate new account + return ( + + + + + Generate new account - - - - Import from JSON keyfile + + + + Import from JSON keyfile - - - - Import from mnemonic phrase + + + + Import from mnemonic phrase - - - - - - - - - - - - - ); - } + + + + + + + + + + + + + ); } diff --git a/packages/light-apps/src/AddAccount/Create.tsx b/packages/light-apps/src/AddAccount/Create.tsx index ca5b281f5..bc06f8f92 100644 --- a/packages/light-apps/src/AddAccount/Create.tsx +++ b/packages/light-apps/src/AddAccount/Create.tsx @@ -2,256 +2,208 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import FileSaver from 'file-saver'; +import { Keyring } from '@polkadot/ui-keyring'; import { mnemonicGenerate, mnemonicToSeed, naclKeypairFromSeed } from '@polkadot/util-crypto'; -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { AppContext } from '@substrate/ui-common'; import { AddressSummary, ErrorText, FadedText, Input, Margin, MnemonicSegment, NavButton, Stacked, StyledLinkButton, SubHeader, WrapperDiv, WithSpaceAround } from '@substrate/ui-components'; +import FileSaver from 'file-saver'; +import { Either, left, right } from 'fp-ts/lib/Either'; +import { none, Option, some } from 'fp-ts/lib/Option'; +import React, { useContext, useState } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; interface Props extends RouteComponentProps { } - -type Steps = 'create' | 'rewrite'; - -type State = { - address?: string; - error: string | null; +interface UserInput { mnemonic: string; name: string; password: string; - rewritePhrase?: string; - step: Steps; -}; - -export class Create extends React.PureComponent { - static contextType = AppContext; + rewritePhrase: string; +} +interface UserInputError extends Partial { } - context!: React.ContextType; // http://bit.ly/typescript-and-react-context +type Steps = 'create' | 'rewrite'; - state: State = { - error: null, - mnemonic: '', - name: '', - password: '', - step: 'create' - }; +/** + * Derive public address from mnemonic key + */ +function generateAddressFromMnemonic (keyring: Keyring, mnemonic: string): string { + const keypair = naclKeypairFromSeed(mnemonicToSeed(mnemonic)); - componentDidMount () { - this.newMnemonic(); - } + return keyring.encodeAddress( + keypair.publicKey + ); +} - clearFields = () => { - const mnemonic = mnemonicGenerate(); +/** + * Validate user inputs + */ +function validate (values: UserInput): Either { + const errors = {} as UserInputError; - this.setState({ - address: this.generateAddressFromMnemonic(mnemonic), - error: null, - mnemonic, - name: '', - password: '' + (['name', 'password', 'rewritePhrase'] as (keyof UserInput)[]) + .filter((key) => !values[key]) + .forEach((key) => { + errors[key] = `Field "${key}" cannot be empty`; }); - } - - createNewAccount = () => { - const { keyring } = this.context; - const { history } = this.props; - const { mnemonic, name, password } = this.state; - if (this.validateFields()) { - let pair = keyring.createAccountMnemonic(mnemonic, password, { name }); - - const address = pair.address(); - const json = pair.toJson(password); - const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' }); - - FileSaver.saveAs(blob, `${address}.json`); - - history.push(`/transfer/${address}`); - - this.clearFields(); - } else { - this.onError('Please make sure all the fields are set'); - } + if (values.mnemonic !== values.rewritePhrase) { + errors.rewritePhrase = 'Mnemonic does not match rewrite'; } - generateAddressFromMnemonic (mnemonic: string): string { - const { keyring } = this.context; - const keypair = naclKeypairFromSeed(mnemonicToSeed(mnemonic)); + return Object.keys(errors).length ? left(errors) : right(values); +} - return keyring.encodeAddress( - keypair.publicKey - ); - } +export function Create (props: Props) { + const { keyring } = useContext(AppContext); - newMnemonic = () => { - const mnemonic = mnemonicGenerate(); - const address = this.generateAddressFromMnemonic(mnemonic); + const [error, setError] = useState>(none); + const [mnemonic, setMnemonic] = useState(mnemonicGenerate()); + const [name, setName] = useState(''); + const [password, setPassword] = useState(''); + const [rewritePhrase, setRewritePhrase] = useState(''); + const [step, setStep] = useState('create'); - this.setState({ address, mnemonic }); - } + const address = generateAddressFromMnemonic(keyring, mnemonic); + const validation = validate({ mnemonic, name, password, rewritePhrase }); - onChangeName = ({ target: { value } }: React.ChangeEvent) => { - this.setState({ - name: value - }); - } + const createNewAccount = () => { + const { history } = props; - onChangePassword = ({ target: { value } }: React.ChangeEvent) => { - this.setState({ - password: value - }); - } + validation.fold( + (err) => { onError(err); }, + (values) => { + const pair = keyring.createAccountMnemonic(values.mnemonic, values.password, { name: values.name }); - onChangeRewritePhrase = ({ target: { value } }: React.ChangeEvent) => { - this.setState({ - rewritePhrase: value - }); + const json = pair.toJson(values.password); + const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' }); - if (value === this.state.mnemonic) { - this.onError(''); - } else { - this.onError('Mnemonic does not match rewrite'); - } - } + FileSaver.saveAs(blob, `${values.name}-${pair.address()}.json`); - onError = (value: string | null) => { - this.setState({ - error: value - }); - } - - toggleStep = () => { - const { address, password, step } = this.state; - - if (address && password) { - this.setState({ - step: step === 'create' ? 'rewrite' : 'create' - }); - } else { - this.onError('Please make sure all fields are set.'); - } - } + history.push(`/transfer/${pair.address()}`); + } + ); + }; - validateFields = () => { - const { mnemonic, name, password, rewritePhrase } = this.state; - return mnemonic.length && name && password.length && rewritePhrase === mnemonic; - } + const onError = (err: UserInputError) => { + setError(some(Object.values(err)[0])); + }; - render () { - const { address, name, step } = this.state; + const toggleStep = () => { + setError(none); - return ( - - - - { - step === 'create' - ? this.renderCreateStep() - : this.renderRewriteStep() - } - {this.renderError()} - + validation.fold( + (err) => (err.name || err.password) ? onError(err) : setStep(step === 'create' ? 'rewrite' : 'create'), + () => setStep(step === 'create' ? 'rewrite' : 'create') ); - } + }; - renderCreateStep () { - const { mnemonic } = this.state; + // FIXME: The two render functions below could go as independant components + const renderCreateStep = () => { return ( Create from the following mnemonic phrase - + setMnemonic(mnemonicGenerate())} mnemonic={mnemonic} /> - {this.renderSetName()} + {renderSetName(name, setName)} - {this.renderSetPassword()} + {renderSetPassword(password, setPassword)} - Next + Next ); - } - - renderError () { - const { error } = this.state; - - return ( - - {error || null} - - ); - } + }; - renderRewriteStep () { - const { mnemonic, rewritePhrase } = this.state; + const renderRewriteStep = () => { + const handler = ({ target: { value } }: React.ChangeEvent) => setRewritePhrase(value); return ( Copy Your Mnemonic Somewhere Safe If someone gets hold of this mnemonic they could drain your account - + Rewrite Mnemonic Below - Back + Back - Save + Save ); - } + }; - renderSetName () { - const { name } = this.state; + return ( + + + + {step === 'create' + ? renderCreateStep() + : renderRewriteStep() + } + {renderError(error)} + + ); - return ( - - Give it a name - - - - - ); - } +} - renderSetPassword () { - const { password } = this.state; +function renderError (error: Option) { + return error.fold( + null, + (err) => {err} + ); +} - return ( - - Encrypt it with a passphrase - - - - - ); - } +function renderSetName (name: string, setName: React.Dispatch>) { + const handler = ({ target: { value } }: React.ChangeEvent) => setName(value); + + return ( + + Give it a name + + + + + ); +} +function renderSetPassword (password: string, setPassword: React.Dispatch>) { + const handler = ({ target: { value } }: React.ChangeEvent) => setPassword(value); + + return ( + + Encrypt it with a passphrase + + + + + ); } diff --git a/packages/light-apps/src/AddAccount/ImportWithPhrase.tsx b/packages/light-apps/src/AddAccount/ImportWithPhrase.tsx index 1f66b79cc..c48b45459 100644 --- a/packages/light-apps/src/AddAccount/ImportWithPhrase.tsx +++ b/packages/light-apps/src/AddAccount/ImportWithPhrase.tsx @@ -2,10 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { AppContext } from '@substrate/ui-common'; import { ErrorText, Input, Margin, Modal, NavButton, Stacked, WrapperDiv } from '@substrate/ui-components'; +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; interface Props extends RouteComponentProps { } diff --git a/packages/light-apps/src/AddAccount/Restore.tsx b/packages/light-apps/src/AddAccount/Restore.tsx index 9f8ec442c..5801f958c 100644 --- a/packages/light-apps/src/AddAccount/Restore.tsx +++ b/packages/light-apps/src/AddAccount/Restore.tsx @@ -2,51 +2,29 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { Menu } from '@substrate/ui-components'; +import React, { useState } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; import { ImportWithJson } from './ImportWithJson'; import { ImportWithPhrase } from './ImportWithPhrase'; interface Props extends RouteComponentProps { } -type State = { - screen: 'JSON' | 'Phrase' -}; - -export class Restore extends React.PureComponent { - state: State = { - screen: 'JSON' - }; - - toJsonScreen = () => { - this.setState({ - screen: 'JSON' - }); - } - - toPhraseScreen = () => { - this.setState({ - screen: 'Phrase' - }); - } - - render () { - const { screen } = this.state; - - return ( - - - With JSON - With Phrase - - { - screen === 'JSON' - ? - : - } - - ); - } +export function Restore (props: Props) { + const [screen, setScreen] = useState('JSON'); + + return ( + + + setScreen('JSON')}> With JSON + setScreen('Phrase')}> With Phrase + + { + screen === 'JSON' + ? + : + } + + ); } diff --git a/packages/light-apps/src/Alerts/Alerts.tsx b/packages/light-apps/src/Alerts/Alerts.tsx index f28f4f500..1320ca75e 100644 --- a/packages/light-apps/src/Alerts/Alerts.tsx +++ b/packages/light-apps/src/Alerts/Alerts.tsx @@ -2,9 +2,9 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React, { useContext } from 'react'; import { Alert } from '@substrate/ui-components'; import { Alert as AlertType, AlertsContext } from '@substrate/ui-common'; +import React, { useContext } from 'react'; export function Alerts () { const { alerts, remove } = useContext(AlertsContext); diff --git a/packages/light-apps/src/App.tsx b/packages/light-apps/src/App.tsx index b8e0a1f47..a01e090a3 100644 --- a/packages/light-apps/src/App.tsx +++ b/packages/light-apps/src/App.tsx @@ -2,12 +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 React from 'react'; -import { BrowserRouter, MemoryRouter } from 'react-router-dom'; -import 'semantic-ui-css/semantic.min.css'; import { AppContext, ContextGate } from '@substrate/ui-common'; import { Container, GlobalStyle, Loading, substrateLightTheme } from '@substrate/ui-components'; +import React from 'react'; +import { BrowserRouter, MemoryRouter } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; +import 'semantic-ui-css/semantic.min.css'; import { Alerts } from './Alerts'; import { Content } from './Content'; @@ -21,30 +21,28 @@ import { TxQueueNotifier } from './TxQueueNotifier'; const Router: any = process.env.NODE_ENV === 'production' ? MemoryRouter : BrowserRouter; -export class App extends React.PureComponent { - render () { - return ( - - - - - - - {({ isReady }) => isReady - ? - - - - : - Connecting to the node... +export function App () { + return ( + + + + + + + {({ isReady }) => isReady + ? + + + + : + Connecting to the node... } - - - - - - - - ); - } + + + + + + + + ); } diff --git a/packages/light-apps/src/ManageAddresses/Add.tsx b/packages/light-apps/src/ManageAddresses/Add.tsx index 0afb4900b..ef0694826 100644 --- a/packages/light-apps/src/ManageAddresses/Add.tsx +++ b/packages/light-apps/src/ManageAddresses/Add.tsx @@ -2,22 +2,17 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { Margin, Stacked, SubHeader } from '@substrate/ui-components'; +import React from 'react'; import { SaveAddress } from './SaveAddress'; -interface Props extends RouteComponentProps<{}> { } - -export class Add extends React.PureComponent { - render () { - return ( - - Enter an Address And Save it with A Name for Later Use. - - - - ); - } +export function Add () { + return ( + + Enter an ddress and save it with a name for later use. + + + + ); } diff --git a/packages/light-apps/src/ManageAddresses/Edit.tsx b/packages/light-apps/src/ManageAddresses/Edit.tsx index 89e3f5043..50c230eee 100644 --- a/packages/light-apps/src/ManageAddresses/Edit.tsx +++ b/packages/light-apps/src/ManageAddresses/Edit.tsx @@ -2,9 +2,9 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Margin, NavLink, Stacked, SubHeader } from '@substrate/ui-components'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { Margin, NavLink, Stacked, SubHeader } from '@substrate/ui-components'; import { SaveAddress } from './SaveAddress'; @@ -15,16 +15,14 @@ interface MatchParams { interface Props extends RouteComponentProps { } -export class Edit extends React.PureComponent { - render () { - const { match: { params: { currentAccount, editAddress } } } = this.props; - return ( - - Rename this address in your address book. - - Add a New Address - - - ); - } +export function Edit (props: Props) { + const { match: { params: { currentAccount, editAddress } } } = props; + return ( + + Rename this address in your address book. + + Add a New Address + + + ); } diff --git a/packages/light-apps/src/ManageAddresses/ManageAddresses.tsx b/packages/light-apps/src/ManageAddresses/ManageAddresses.tsx index 57574100b..af089f0f0 100644 --- a/packages/light-apps/src/ManageAddresses/ManageAddresses.tsx +++ b/packages/light-apps/src/ManageAddresses/ManageAddresses.tsx @@ -2,35 +2,31 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { FlexItem, Margin, StackedHorizontal, WalletCard } from '@substrate/ui-components'; import React from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { Route, Switch } from 'react-router-dom'; import { Add } from './Add'; import { Edit } from './Edit'; -import { SavedAddresses } from '../SavedAddresses'; -import { FlexItem, Margin, StackedHorizontal, WalletCard } from '@substrate/ui-components'; - -interface Props extends RouteComponentProps<{}> { } +import { SavedAddresses } from './SavedAddresses'; -export class ManageAddresses extends React.PureComponent { - render () { - return ( - - - - - - - - - - - - - - - ); - } +export function ManageAddresses () { + return ( + + + + + + + + + + + + + + + ); } diff --git a/packages/light-apps/src/ManageAddresses/SaveAddress/SaveAddress.tsx b/packages/light-apps/src/ManageAddresses/SaveAddress/SaveAddress.tsx index af5da6def..0815b9ba3 100644 --- a/packages/light-apps/src/ManageAddresses/SaveAddress/SaveAddress.tsx +++ b/packages/light-apps/src/ManageAddresses/SaveAddress/SaveAddress.tsx @@ -2,10 +2,14 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Keyring } from '@polkadot/ui-keyring'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; import { isFunction } from '@polkadot/util'; -import React from 'react'; import { AppContext } from '@substrate/ui-common'; import { ErrorText, Form, Input, Margin, NavButton, Stacked, SuccessText, WrapperDiv } from '@substrate/ui-components'; +import { Either, fromOption, tryCatch2v } from 'fp-ts/lib/Either'; +import { fromNullable } from 'fp-ts/lib/Option'; +import React, { useContext, useEffect, useState } from 'react'; interface Props { addressDisabled?: boolean; @@ -13,153 +17,129 @@ interface Props { onSave?: () => void; } -type State = { - address: string, - error?: string, - name: string, - success?: string -}; - -export class SaveAddress extends React.PureComponent { - static contextType = AppContext; - - context!: React.ContextType; // http://bit.ly/typescript-and-react-context - - state: State = { - address: this.props.defaultAddress || '', - name: '' - }; +/** + * From an `address` string, check if it's in the keyring, and returns an Either + * of KeyringAddress. + */ +function getKeyringAddress (keyring: Keyring, address?: string): Either { + return fromOption(new Error('You need to specify an address'))(fromNullable(address)) + // `keyring.getAddress` might fail: catch and return None if it does + .chain((addr) => tryCatch2v(() => keyring.getAddress(addr), (e) => e as Error)) + .chain((keyringAddress) => tryCatch2v( + () => { + // If `.getMeta` doesn't throw, then it mean the address exists + // https://github.com/polkadot-js/ui/issues/133 + keyringAddress.getMeta(); + return keyringAddress; + }, + (e) => e as Error) + ); +} - componentDidMount () { - const name = this.getName(this.props.defaultAddress); - if (name) { - this.setState({ name: name }); - } - } - - componentDidUpdate (prevProps: Props) { - if (this.props.defaultAddress && prevProps.defaultAddress !== this.props.defaultAddress) { - const name = this.getName(this.props.defaultAddress) || ''; - this.setState({ - address: this.props.defaultAddress, - error: undefined, - name, - success: undefined - }); - } - } +export function SaveAddress (props: Props) { + const { addressDisabled, defaultAddress, onSave } = props; - getName = (address?: string) => { - if (!address) { - return; - } + const { keyring } = useContext(AppContext); - const { keyring } = this.context; + const [address, setAddress] = useState(defaultAddress || ''); + const keyringAddress = getKeyringAddress(keyring, address); + const [name, setName] = useState( + keyringAddress.map((keyringAddress) => keyringAddress.getMeta().name).getOrElse('') + ); - return keyring.getAddress(address).getMeta().name; - } + useEffect(() => { + setAddress(defaultAddress || ''); + setName(keyringAddress.map((keyringAddress) => keyringAddress.getMeta().name).getOrElse('')); + // eslint-disable-next-line + }, [defaultAddress]); // No need for keyringAddress dep, because it already depends on defaultAddress - handleInputAddress = ({ target: { value } }: React.ChangeEvent) => { - this.setState({ address: value }); - } + const [error, setError] = useState(undefined); + const [success, setSuccess] = useState(undefined); - handleInputName = ({ target: { value } }: React.ChangeEvent) => { - this.setState({ name: value }); - } + const handleInputAddress = ({ target: { value } }: React.ChangeEvent) => { + setAddress(value); + }; - handleSubmit = () => { - const { keyring } = this.context; - const { onSave } = this.props; - const { address, name } = this.state; + const handleInputName = ({ target: { value } }: React.ChangeEvent) => { + setName(value); + }; + const handleSubmit = () => { try { // if address already saved under this name: throw const lookupAddress = keyring.getAddress(address); - try { - if (lookupAddress && lookupAddress.getMeta().name === name) { - throw new Error('This address has already been saved under this name.'); - } - } catch (_error) { - /* Do nothing */ + if (lookupAddress && lookupAddress.getMeta().name === name) { + throw new Error('This address has already been saved under this name.'); } // If the address is already saved, just update the name keyring.saveAddress(address, { name }); - this.onSuccess(lookupAddress - ? 'Successfully edited existing address' - : 'Successfully saved address'); + onSuccess('Successfully saved address'); if (isFunction(onSave)) { onSave(); } } catch (e) { - this.onError(e.message); + onError(e.message); } - } - - onError = (value: string) => { - this.setState({ error: value, success: undefined }); - } - - onSuccess = (value: string) => { - this.setState({ error: undefined, success: value }); - } - - render () { - const { addressDisabled } = this.props; - const { address, name } = this.state; - - return ( -
- - - - - - - - {this.renderError()} - {this.renderSuccess()} - - -
- ); - } + }; - renderError () { - const { error } = this.state; + const onError = (value?: string) => { + setError(value); + setSuccess(undefined); + }; - return ( - - {error || null} - - ); - } + const onSuccess = (value?: string) => { + setError(undefined); + setSuccess(value); + }; - renderSuccess () { - const { success } = this.state; + return ( +
+ + + + + + + + {renderError(error)} + {renderSuccess(success)} + + +
+ ); +} - return ( - - {success || null} - - ); - } +function renderError (error?: string) { + return ( + + {error} + + ); +} + +function renderSuccess (success?: string) { + return ( + + {success} + + ); } diff --git a/packages/light-apps/src/SavedAddresses.tsx b/packages/light-apps/src/ManageAddresses/SavedAddresses.tsx similarity index 100% rename from packages/light-apps/src/SavedAddresses.tsx rename to packages/light-apps/src/ManageAddresses/SavedAddresses.tsx diff --git a/packages/light-apps/src/Onboarding/CreateNewAccountScreen.tsx b/packages/light-apps/src/Onboarding/CreateNewAccountScreen.tsx index b04c3fa02..33f97f793 100644 --- a/packages/light-apps/src/Onboarding/CreateNewAccountScreen.tsx +++ b/packages/light-apps/src/Onboarding/CreateNewAccountScreen.tsx @@ -2,12 +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 FileSaver from 'file-saver'; import { mnemonicGenerate, mnemonicToSeed, naclKeypairFromSeed } from '@polkadot/util-crypto'; -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { AppContext } from '@substrate/ui-common'; import { AddressSummary, ErrorText, FadedText, Input, Margin, MnemonicSegment, Modal, NavButton, Stacked, StackedHorizontal, StyledLinkButton, SubHeader, WithSpaceAround } from '@substrate/ui-components'; +import FileSaver from 'file-saver'; +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; interface Props extends RouteComponentProps { } @@ -173,7 +173,7 @@ export class CreateNewAccountScreen extends React.PureComponent { return ( - Next + Next ); diff --git a/packages/light-apps/src/Onboarding/ImportOptionsScreen.tsx b/packages/light-apps/src/Onboarding/ImportOptionsScreen.tsx index d22baa55d..42b7fd9c7 100644 --- a/packages/light-apps/src/Onboarding/ImportOptionsScreen.tsx +++ b/packages/light-apps/src/Onboarding/ImportOptionsScreen.tsx @@ -2,25 +2,23 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Modal } from '@substrate/ui-components'; import React from 'react'; import { Route, Switch } from 'react-router-dom'; -import { Modal } from '@substrate/ui-components'; import { ImportWithJson } from './ImportWithJson'; import { ImportWithPhrase } from './ImportWithPhrase'; -export class ImportOptionsScreen extends React.PureComponent { - render () { - return ( - - Unlock Account - - - - - - - - ); - } +export function ImportOptionsScreen () { + return ( + + Unlock Account + + + + + + + + ); } diff --git a/packages/light-apps/src/Onboarding/ImportWithJson.tsx b/packages/light-apps/src/Onboarding/ImportWithJson.tsx index 740aa135c..652e15a65 100644 --- a/packages/light-apps/src/Onboarding/ImportWithJson.tsx +++ b/packages/light-apps/src/Onboarding/ImportWithJson.tsx @@ -1,6 +1,7 @@ // Copyright 2018-2019 @paritytech/substrate-light-ui 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 { AlertsContext } from '@substrate/ui-common'; import { InputFile, Modal, NavLink, Stacked } from '@substrate/ui-components'; import React from 'react'; diff --git a/packages/light-apps/src/Onboarding/ImportWithPhrase.tsx b/packages/light-apps/src/Onboarding/ImportWithPhrase.tsx index 956abf333..91375b65c 100644 --- a/packages/light-apps/src/Onboarding/ImportWithPhrase.tsx +++ b/packages/light-apps/src/Onboarding/ImportWithPhrase.tsx @@ -2,9 +2,9 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { ErrorText, Input, Margin, Modal, NavButton, NavLink, Stacked } from '@substrate/ui-components'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { ErrorText, Input, Margin, Modal, NavButton, NavLink, Stacked } from '@substrate/ui-components'; interface Props extends RouteComponentProps { } diff --git a/packages/light-apps/src/Onboarding/Onboarding.tsx b/packages/light-apps/src/Onboarding/Onboarding.tsx index 1cbf26a0a..5250924fc 100644 --- a/packages/light-apps/src/Onboarding/Onboarding.tsx +++ b/packages/light-apps/src/Onboarding/Onboarding.tsx @@ -2,29 +2,27 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Container, Modal } from '@substrate/ui-components'; import React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import { Container, Modal } from '@substrate/ui-components'; import { CreateNewAccountScreen, ImportOptionsScreen, SaveScreen } from './index'; -export class Onboarding extends React.PureComponent { - render () { - return ( - - - - - - - - - - - ); - } +export function Onboarding () { + return ( + + + + + + + + + + + ); } diff --git a/packages/light-apps/src/Onboarding/SaveScreen.tsx b/packages/light-apps/src/Onboarding/SaveScreen.tsx index 034bf7fae..f57939775 100644 --- a/packages/light-apps/src/Onboarding/SaveScreen.tsx +++ b/packages/light-apps/src/Onboarding/SaveScreen.tsx @@ -2,10 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React from 'react'; -import { RouteComponentProps } from 'react-router-dom'; import { AppContext } from '@substrate/ui-common'; import { AddressSummary, ErrorText, Input, Modal, NavButton, NavLink, Stacked } from '@substrate/ui-components'; +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; interface MatchParams { importMethod: string; diff --git a/packages/light-apps/src/TopBar/TopBar.styles.tsx b/packages/light-apps/src/TopBar/TopBar.styles.tsx index d19c03388..312bcd08e 100644 --- a/packages/light-apps/src/TopBar/TopBar.styles.tsx +++ b/packages/light-apps/src/TopBar/TopBar.styles.tsx @@ -2,8 +2,8 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React from 'react'; import { Margin, StackedHorizontal } from '@substrate/ui-components'; +import React from 'react'; import { BlockCounterProps, NodeStatusProps } from './types'; diff --git a/packages/light-apps/src/index.tsx b/packages/light-apps/src/index.tsx index ea92e777e..440b3e7b6 100644 --- a/packages/light-apps/src/index.tsx +++ b/packages/light-apps/src/index.tsx @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import 'symbol-observable'; // https://github.com/mmiszy/react-with-observable#install +import 'symbol-observable'; // TO make es6 observables work import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/packages/transfer-app/src/SendBalance/Validation.tsx b/packages/transfer-app/src/SendBalance/Validation.tsx index 61ccebc74..6c459b424 100644 --- a/packages/transfer-app/src/SendBalance/Validation.tsx +++ b/packages/transfer-app/src/SendBalance/Validation.tsx @@ -14,46 +14,44 @@ interface Props { values: Either; } -export class Validation extends React.PureComponent { - render () { - const { values } = this.props; - const warnings = fromEither(values).chain(validateWarnings); - - return ( - - {values.fold(this.renderErrors, this.renderNull)} - {warnings.fold(null, this.renderWarnings)} - - ); - } - - renderErrors (errors: Errors) { - // For now we assume there's only one error, and show it. It should be - // relatively easy to extend to show multiple errors. - const error = Object.values(errors)[0]; - - return ( - - Errors - - {error} - - - ); - } - - renderNull () { - return null; - } - - renderWarnings (warnings: Warnings) { - return ( - - Warnings - - {warnings.map((warning) => {warning})} - - - ); - } +export function Validation (props: Props) { + const { values } = props; + const warnings = fromEither(values).chain(validateWarnings); + + return ( + + {values.fold(renderErrors, renderNull)} + {warnings.fold(null, renderWarnings)} + + ); +} + +function renderErrors (errors: Errors) { + // For now we assume there's only one error, and show it. It should be + // relatively easy to extend to show multiple errors. + const error = Object.values(errors)[0]; + + return ( + + Errors + + {error} + + + ); +} + +function renderNull () { + return null; +} + +function renderWarnings (warnings: Warnings) { + return ( + + Warnings + + {warnings.map((warning) => {warning})} + + + ); } diff --git a/packages/transfer-app/src/TxQueue.tsx b/packages/transfer-app/src/TxQueue.tsx index 495a576d5..cc938da4b 100644 --- a/packages/transfer-app/src/TxQueue.tsx +++ b/packages/transfer-app/src/TxQueue.tsx @@ -2,9 +2,9 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { TxQueueContext } from '@substrate/ui-common'; +import { PendingExtrinsic, TxQueueContext } from '@substrate/ui-common'; import { Icon, NavButton, Stacked, StackedHorizontal, SubHeader, TxDetails, TxSummary } from '@substrate/ui-components'; -import React from 'react'; +import React, { useContext } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { CenterDiv, LeftDiv, RightDiv } from './Transfer.styles'; @@ -13,93 +13,78 @@ import { AllExtrinsicData } from './SendBalance/types'; interface Props extends RouteComponentProps | undefined> { } -export class TxQueue extends React.PureComponent { - static contextType = TxQueueContext; - - context!: React.ContextType; // http://bit.ly/typescript-and-react-context - - handleNewTransfer = () => { - // Transaction was seen by the user, we can remove it - // Parent component will redirect to SendBalance - this.context.clear(); - } - - render () { - const { txQueue } = this.context; - - // The parent component will redirect to SendBalance if empty txQueue - if (!txQueue.length) return; - - return ( - - - - {this.renderTxStatus()} - - - - Summary: - {this.renderSummary()} - - - - {this.renderDetails()} - {txQueue[0].status.isFinalized ? ( - New Transfer - ) :

Please wait until the transaction is validated before making a new transfer..

} -
-
- ); - } - - renderDetails () { - const { match: { params: { currentAccount } } } = this.props; - - const { details: { allFees, allTotal, amount, recipientAddress } } = this.context.txQueue[0]; - - return ( - - ); - } - - renderSummary () { - const { match: { params: { currentAccount } } } = this.props; +export function TxQueue (props: Props) { + const { match: { params: { currentAccount } } } = props; + const { clear, txQueue } = useContext(TxQueueContext); + + // The parent component will redirect to SendBalance if empty txQueue + if (!txQueue.length) return null; + + return ( + + + + {renderTxStatus(txQueue)} + + + + Summary: + {renderSummary(currentAccount, txQueue)} + + + + {renderDetails(currentAccount, txQueue)} + {txQueue[0].status.isFinalized ? ( + New Transfer + ) :

Please wait until the transaction is validated before making a new transfer..

} +
+
+ ); +} - const { details: { amount, recipientAddress } } = this.context.txQueue[0]; +function renderDetails (currentAccount: string, txQueue: PendingExtrinsic[]) { + const { details: { allFees, allTotal, amount, recipientAddress } } = txQueue[0]; + + return ( + + ); +} - return ( - - ); - } +function renderSummary (currentAccount: string, txQueue: PendingExtrinsic[]) { + const { details: { amount, recipientAddress } } = txQueue[0]; - renderTxStatus () { - const { isFinalized, isDropped, isUsurped } = this.context.txQueue[0].status; + return ( + + ); +} - if (isFinalized) { - return - Transaction completed! - - ; - } else if (isDropped || isUsurped) { - return - Transaction error! - - ; - } else { - return - Sending... - - ; - } +function renderTxStatus (txQueue: PendingExtrinsic[]) { + const { isFinalized, isDropped, isUsurped } = txQueue[0].status; + + if (isFinalized) { + return + Transaction completed! + + ; + } else if (isDropped || isUsurped) { + return + Transaction error! + + ; + } else { + return + Sending... + + ; } } diff --git a/packages/ui-common/src/ContextGate.tsx b/packages/ui-common/src/ContextGate.tsx index 13f09b272..b6bf27641 100644 --- a/packages/ui-common/src/ContextGate.tsx +++ b/packages/ui-common/src/ContextGate.tsx @@ -7,7 +7,7 @@ import { ChainProperties, Health, Text } from '@polkadot/types'; import keyring from '@polkadot/ui-keyring'; import settings from '@polkadot/ui-settings'; import { logger } from '@polkadot/util'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { combineLatest, Observable } from 'rxjs'; import { filter, switchMap } from 'rxjs/operators'; @@ -16,7 +16,6 @@ import { isTestChain } from './util'; import { AlertsContextProvider } from './AlertsContext'; import { TxQueueContextProvider } from './TxQueueContext'; -// Holds the state for all the contexts interface State { isReady: boolean; system: System; @@ -51,49 +50,40 @@ let keyringInitialized = false; const l = logger('ui-common'); -// The reasons why we regroup all contexts in one big context is: -// 1. I don't like the render props syntax with the context consumer. -Amaury -// 2. We want to access Context in lifecycle methods like componentDidMount. -// It's either adding a wrapper and passing as props, like: -// https://github.com/facebook/react/issues/12397#issuecomment-375501574 -// or use one context for everything: -// https://github.com/facebook/react/issues/12397#issuecomment-462142714 -// FIXME we could probably split this out into small modular contexts once we -// use https://reactjs.org/docs/hooks-reference.html#usecontext -export class ContextGate extends React.PureComponent<{}, State> { - api = new ApiRx(new WsProvider(wsUrl)); +const api = new ApiRx(new WsProvider(wsUrl)); - state: State = { - ...DISCONNECTED_STATE_PROPERTIES - }; +export function ContextGate (props: { children: React.ReactNode }) { + const { children } = props; + const [state, setState] = useState(DISCONNECTED_STATE_PROPERTIES); + const { isReady, system } = state; - componentDidMount () { + useEffect(() => { // Block the UI when disconnected - this.api.isConnected.pipe( + api.isConnected.pipe( filter(isConnected => !isConnected) ).subscribe((_) => { - this.setState(DISCONNECTED_STATE_PROPERTIES); + setState(DISCONNECTED_STATE_PROPERTIES); }); // We want to fetch all the information again each time we reconnect. We // might be connecting to a different node, or the node might have changed // settings. - this.api.isConnected + api.isConnected .pipe( filter(isConnected => isConnected), // API needs to be ready to be able to use RPCs; connected isn't enough switchMap(_ => - this.api.isReady + api.isReady ), switchMap(_ => // Get info about the current chain // FIXME Correct types should come from @polkadot/api to avoid type assertion combineLatest([ - this.api.rpc.system.chain() as Observable, - this.api.rpc.system.health() as Observable, - this.api.rpc.system.name() as Observable, - this.api.rpc.system.properties() as Observable, - this.api.rpc.system.version() as Observable + api.rpc.system.chain() as Observable, + api.rpc.system.health() as Observable, + api.rpc.system.name() as Observable, + api.rpc.system.properties() as Observable, + api.rpc.system.version() as Observable ]) ) ) @@ -117,8 +107,7 @@ export class ContextGate extends React.PureComponent<{}, State> { l.log(`Api connected to ${wsUrl}`); l.log(`Api ready, connected to chain "${chain}" with properties ${JSON.stringify(properties)}`); - this.setState(state => ({ - ...state, + setState({ isReady: true, system: { chain: chain.toString(), @@ -127,27 +116,22 @@ export class ContextGate extends React.PureComponent<{}, State> { properties, version: version.toString() } - })); + }); }); - } - - render () { - const { children } = this.props; - const { isReady, system } = this.state; - - return ( - - - - {children} - - - - ); - } + }, []); + + return ( + + + + {children} + + + + ); } diff --git a/packages/ui-components/src/Address.tsx b/packages/ui-components/src/Address.tsx index 95de81cdc..79416fa08 100644 --- a/packages/ui-components/src/Address.tsx +++ b/packages/ui-components/src/Address.tsx @@ -7,21 +7,19 @@ import React from 'react'; import { CopyButton } from './CopyButton'; import { FlexSegment } from './FlexSegment'; -type Props = { +type AddressProps = { address?: string }; const PLACEHOLDER_ADDRESS = '5'.padEnd(16, 'x'); -export class Address extends React.PureComponent { - render () { - const { address } = this.props; +export function Address (props: AddressProps) { + const { address } = props; - return ( - - {address || PLACEHOLDER_ADDRESS} - - - ); - } + return ( + + {address || PLACEHOLDER_ADDRESS} + + + ); } diff --git a/packages/ui-components/src/BalanceDisplay.tsx b/packages/ui-components/src/BalanceDisplay.tsx index df9cc76e3..1509d6d11 100644 --- a/packages/ui-components/src/BalanceDisplay.tsx +++ b/packages/ui-components/src/BalanceDisplay.tsx @@ -17,21 +17,18 @@ export type BalanceDisplayProps = { const PLACEHOLDER_BALANCE = new Balance(0); const PLACEHOLDER_TOKEN_SYMBOL = 'UNIT'; +const defaultProps = { + balance: PLACEHOLDER_BALANCE, + fontSize: 'large' as FontSize, + tokenSymbol: PLACEHOLDER_TOKEN_SYMBOL +}; -export class BalanceDisplay extends React.PureComponent { - static defaultProps: BalanceDisplayProps = { - balance: PLACEHOLDER_BALANCE, - fontSize: 'large', - tokenSymbol: PLACEHOLDER_TOKEN_SYMBOL - }; - - render () { - const { balance, fontSize, fontWeight, tokenSymbol } = this.props; +export function BalanceDisplay (props: BalanceDisplayProps = defaultProps) { + const { balance, fontSize, fontWeight, tokenSymbol } = props; - return ( - - Balance: {(balance!.toString(10))} {tokenSymbol} - - ); - } + return ( + + Balance: {(balance!.toString(10))} {tokenSymbol} + + ); } diff --git a/packages/ui-components/src/Card.tsx b/packages/ui-components/src/Card.tsx index 86f5a4953..6f7bce5bc 100644 --- a/packages/ui-components/src/Card.tsx +++ b/packages/ui-components/src/Card.tsx @@ -3,12 +3,10 @@ // of the Apache-2.0 license. See the LICENSE file for details. import React from 'react'; -import SUICard from 'semantic-ui-react/dist/commonjs/views/Card'; +import SUICard, { CardProps as SUICardProps } from 'semantic-ui-react/dist/commonjs/views/Card'; import styled from 'styled-components'; -type Props = { - [index: string]: any; -}; +type CardProps = SUICardProps; const StyledCard = styled(SUICard)` &&& { @@ -21,15 +19,12 @@ const StyledCard = styled(SUICard)` } `; -export class Card extends React.PureComponent { - // TODO: move away from the defaults and use custom subcomponents - static Header = SUICard.Header; - static Description = SUICard.Description; - static Content = SUICard.Content; - - render () { - return ( - - ); - } +export function Card (props: CardProps) { + return ( + + ); } + +Card.Header = SUICard.Header; +Card.Description = SUICard.Description; +Card.Content = SUICard.Content; diff --git a/packages/ui-components/src/CopyButton.tsx b/packages/ui-components/src/CopyButton.tsx index 8f4a6ca48..79c177b7d 100644 --- a/packages/ui-components/src/CopyButton.tsx +++ b/packages/ui-components/src/CopyButton.tsx @@ -2,21 +2,17 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React from 'react'; +import React, { useState } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; import styled from 'styled-components'; import { Icon } from './Icon'; import { Stacked } from './Shared.styles'; -type Props = { +type CopyButtonProps = { value?: string }; -type State = { - copied: boolean -}; - const StyledCopyButton = styled.button` border: none; background-color: inherit; @@ -28,30 +24,24 @@ const StyledCopyButton = styled.button` } `; -export class CopyButton extends React.PureComponent { - state: State = { - copied: false - }; +export function CopyButton (props: CopyButtonProps) { + const { value } = props; + const [copied, setCopied] = useState(false); - handleCopied = () => { - this.setState({ copied: true }); + const handleCopied = () => { + setCopied(true); - setTimeout(() => this.setState({ copied: false }), 1000); - } + setTimeout(() => setCopied(false), 1000); + }; - render () { - const { copied } = this.state; - const { value } = this.props; - - return ( - - - - - {copied && Copied! } - - - - ); - } + return ( + + + + + {copied && Copied! } + + + + ); } diff --git a/packages/ui-components/src/Grid.tsx b/packages/ui-components/src/Grid.tsx deleted file mode 100644 index 662fb3b24..000000000 --- a/packages/ui-components/src/Grid.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018-2019 @paritytech/substrate-light-ui 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 React from 'react'; -import SUIGrid from 'semantic-ui-react/dist/commonjs/collections/Grid'; - -type Props = { - [index: string]: any -}; - -export class Grid extends React.PureComponent { - // FIXME To be overwritten by our own styles - static Column = SUIGrid.Column; - static Row = SUIGrid.Row; - - render () { - return ( - - ); - } -} diff --git a/packages/ui-components/src/Labelled.tsx b/packages/ui-components/src/Labelled.tsx index 822055ecf..d8e39d714 100644 --- a/packages/ui-components/src/Labelled.tsx +++ b/packages/ui-components/src/Labelled.tsx @@ -5,32 +5,24 @@ import React from 'react'; import Label from 'semantic-ui-react/dist/commonjs/elements/Label/Label'; -type Props = { +type LabelledProps = { children: React.ReactNode, isHidden?: boolean, label?: React.ReactNode, withLabel?: boolean }; -export class Labelled extends React.PureComponent { - render () { - const { children, isHidden = false, label, withLabel = false } = this.props; +export function Labelled (props: LabelledProps) { + const { children, isHidden = false, label, withLabel = false } = props; - if (isHidden) { - return null; - } - - return ( -
- { - withLabel - ? - : null - } - {children} -
- ); + if (isHidden) { + return null; } + + return ( +
+ {withLabel && } + {children} +
+ ); } diff --git a/packages/ui-components/src/Loading.tsx b/packages/ui-components/src/Loading.tsx index 0c4ded842..d2a53cfce 100644 --- a/packages/ui-components/src/Loading.tsx +++ b/packages/ui-components/src/Loading.tsx @@ -5,22 +5,20 @@ import React from 'react'; import { Dimmer, Loader } from 'semantic-ui-react'; -type Props = { +type LoadingProps = { active: boolean, children?: React.ReactNode | string }; -export class Loading extends React.PureComponent { - render () { - const { active, children } = this.props; - return ( - - - +export function Loading (props: LoadingProps) { + const { active, children } = props; + return ( + + + {children} - - - - ); - } + + + + ); } diff --git a/packages/ui-components/src/Margin.tsx b/packages/ui-components/src/Margin.tsx index bb1079b8a..d051e6de5 100644 --- a/packages/ui-components/src/Margin.tsx +++ b/packages/ui-components/src/Margin.tsx @@ -9,7 +9,7 @@ import { SUISize } from './types'; type MarginPropsValue = SUISize | boolean | undefined; -export interface MarginProps { +interface MarginProps { bottom?: MarginPropsValue; // bottom=true means bottom='medium' left?: MarginPropsValue; right?: MarginPropsValue; diff --git a/packages/ui-components/src/MnemonicSegment.tsx b/packages/ui-components/src/MnemonicSegment.tsx index a0be68bfe..c39f4d4fe 100644 --- a/packages/ui-components/src/MnemonicSegment.tsx +++ b/packages/ui-components/src/MnemonicSegment.tsx @@ -6,22 +6,20 @@ import React from 'react'; import { Icon, FadedText, FlexSegment, RefreshButton } from './'; -type Props = { +type MnemonicSegmentProps = { mnemonic?: string, onClick?: () => void }; -export class MnemonicSegment extends React.PureComponent { - render () { - const { mnemonic, onClick } = this.props; +export function MnemonicSegment (props: MnemonicSegmentProps) { + const { mnemonic, onClick } = props; - return ( - - {mnemonic} - - - - - ); - } + return ( + + {mnemonic} + + + + + ); } diff --git a/packages/ui-components/src/Modal.tsx b/packages/ui-components/src/Modal.tsx index e2f660223..432171f04 100644 --- a/packages/ui-components/src/Modal.tsx +++ b/packages/ui-components/src/Modal.tsx @@ -3,17 +3,14 @@ // of the Apache-2.0 license. See the LICENSE file for details. import React from 'react'; -import SUIModal from 'semantic-ui-react/dist/commonjs/modules/Modal/Modal'; +import SUIModal, { ModalProps as SUIModalProps } from 'semantic-ui-react/dist/commonjs/modules/Modal/Modal'; import styled from 'styled-components'; import { Header, FadedText, SubHeader } from './Shared.styles'; -type Props = { - [index: string]: any -}; +type ModalProps = SUIModalProps; -// FIXME: don't use any -const StyledContent = styled(SUIModal.Content)` +const StyledContent = styled(SUIModal.Content)` &&& { display: flex; align-items: center; @@ -27,19 +24,15 @@ const StyledActions = styled(StyledContent)` `; // FIXME: this component is reused here and in @polkadot/apps - should be moved to @polkadot/ui -export class Modal extends React.PureComponent { - static Actions = StyledActions; - static Content = StyledContent; - static Header = Header; - static SubHeader = SubHeader; - static FadedText = FadedText; - static Description = SUIModal.Description; - - render () { - return ( - - ); - } +export function Modal (props: ModalProps) { + return ( + + ); } + +Modal.Actions = StyledActions; +Modal.Content = StyledContent; +Modal.Header = Header; +Modal.SubHeader = SubHeader; +Modal.FadedText = FadedText; +Modal.Description = SUIModal.Description; diff --git a/packages/ui-components/src/NavButton.tsx b/packages/ui-components/src/NavButton.tsx index b90dbaf3a..4022c4005 100644 --- a/packages/ui-components/src/NavButton.tsx +++ b/packages/ui-components/src/NavButton.tsx @@ -7,26 +7,24 @@ import React from 'react'; import { DynamicSizeText, StyledNavButton } from './Shared.styles'; import { FontSize } from './types'; -interface Props extends React.AllHTMLAttributes { +interface NavButtonProps extends React.AllHTMLAttributes { children?: any; fontSize?: FontSize; fontWeight?: string; value?: string; } -export class NavButton extends React.PureComponent { - render () { - const { children, fontSize = 'medium', fontWeight = '300', value, ...rest } = this.props; +export function NavButton (props: NavButtonProps) { + const { children, fontSize = 'medium', fontWeight = '300', value, ...rest } = props; - return ( - // @ts-ignore FIXME I can't get this to work, though it should... - - - {value || children} - - - ); - } + return ( + // @ts-ignore FIXME I can't get this to work, though it should... + + + {value || children} + + + ); } diff --git a/packages/ui-components/src/NavLink.tsx b/packages/ui-components/src/NavLink.tsx index c91e03e8b..7a3793ee7 100644 --- a/packages/ui-components/src/NavLink.tsx +++ b/packages/ui-components/src/NavLink.tsx @@ -8,24 +8,22 @@ import { DynamicSizeText, StyledNavLink } from './Shared.styles'; import { StyledNavLinkProps } from './StyleProps'; import { FontSize } from './types'; -interface Props extends StyledNavLinkProps { +interface NavLinkProps extends StyledNavLinkProps { fontSize?: FontSize; fontWeight?: string; value?: string; } -export class NavLink extends React.PureComponent { - render () { - const { children, fontSize = 'medium', fontWeight = '300', value, ...rest } = this.props; +export function NavLink (props: NavLinkProps) { + const { children, fontSize = 'medium', fontWeight = '300', value, ...rest } = props; - return ( - - - {value || children} - - - ); - } + return ( + + + {value || children} + + + ); } diff --git a/packages/ui-components/src/Progress.tsx b/packages/ui-components/src/Progress.tsx index 5c95d132e..c7b0e02db 100644 --- a/packages/ui-components/src/Progress.tsx +++ b/packages/ui-components/src/Progress.tsx @@ -7,7 +7,7 @@ import SUIProgress from 'semantic-ui-react/dist/commonjs/modules/Progress/Progre import { SUIProgressBarSize } from './types'; -interface IProps { +interface ProgressProps { color?: any; // FIXME disabled?: boolean; percent?: number; @@ -15,7 +15,7 @@ interface IProps { value?: number; } -export function Progress (props: IProps) { +export function Progress (props: ProgressProps) { const { color = 'blue', disabled, percent, size } = props; return ( diff --git a/packages/ui-components/src/TextArea.tsx b/packages/ui-components/src/TextArea.tsx index 0432179c2..ea21e4bba 100644 --- a/packages/ui-components/src/TextArea.tsx +++ b/packages/ui-components/src/TextArea.tsx @@ -6,21 +6,19 @@ import React from 'react'; import { Form } from 'semantic-ui-react'; import SUITextArea from 'semantic-ui-react/dist/commonjs/addons/TextArea'; -type Props = { +type TextAreaProps = { placeholder?: string, onChange?: (event: React.ChangeEvent) => void, rows?: number, value?: string }; -export class TextArea extends React.PureComponent { - render () { - const { placeholder, rows, value } = this.props; +export function TextArea (props: TextAreaProps) { + const { placeholder, rows, value } = props; - return ( -
- - - ); - } + return ( +
+ + + ); } diff --git a/packages/ui-components/src/WalletCard.tsx b/packages/ui-components/src/WalletCard.tsx index b47213617..795d881d1 100644 --- a/packages/ui-components/src/WalletCard.tsx +++ b/packages/ui-components/src/WalletCard.tsx @@ -1,11 +1,12 @@ // Copyright 2018-2019 @paritytech/substrate-light-ui 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 React from 'react'; import { Card, Header, SubHeader, Stacked, WithSpaceAround } from './index'; -type Props = { +type WalletCardProps = { children: React.ReactNode, header: string, height?: string, @@ -13,22 +14,20 @@ type Props = { overflow?: string }; -export class WalletCard extends React.PureComponent { - render () { - const { children, header, height, overflow = 'none', subheader } = this.props; +export function WalletCard (props: WalletCardProps) { + const { children, header, height, overflow = 'none', subheader } = props; - return ( - - + return ( + + + +
{header}
+ {subheader} -
{header}
- {subheader} - - {children} - + {children}
-
-
- ); - } + +
+
+ ); } diff --git a/packages/ui-components/src/YayNay.tsx b/packages/ui-components/src/YayNay.tsx index d20b26063..0856be79b 100644 --- a/packages/ui-components/src/YayNay.tsx +++ b/packages/ui-components/src/YayNay.tsx @@ -5,15 +5,14 @@ import React from 'react'; import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; -// TODO export these props for reuse -interface IProps { +interface YayNayProps { yay: number; nay: number; height?: number; width?: number; } -export const YayNay = (props: IProps) => { +export const YayNay = (props: YayNayProps) => { const { yay, nay, height, width } = props; const data = [ diff --git a/packages/ui-components/src/index.ts b/packages/ui-components/src/index.ts index 6d57518f8..1ca98a600 100644 --- a/packages/ui-components/src/index.ts +++ b/packages/ui-components/src/index.ts @@ -6,6 +6,7 @@ export { Dropdown, DropdownProps, Form, FormProps, + Grid, GridProps, List, ListProps, Menu, MenuProps, Message, MessageProps, @@ -23,7 +24,6 @@ export * from './CopyButton'; export * from './constants'; export * from './FlexSegment'; export * from './globalStyle'; -export * from './Grid'; export * from './Icon'; export * from './InputFile'; export * from './Loading'; diff --git a/packages/ui-components/src/stateful/AddressSummary/AddressSummary.tsx b/packages/ui-components/src/stateful/AddressSummary/AddressSummary.tsx index 05d56a5df..b97360fdc 100644 --- a/packages/ui-components/src/stateful/AddressSummary/AddressSummary.tsx +++ b/packages/ui-components/src/stateful/AddressSummary/AddressSummary.tsx @@ -16,7 +16,7 @@ type SummaryStyles = { fontSize: FontSize }; -type Props = { +type AddressSummaryProps = { address?: string, name?: string | React.ReactNode, noBalance?: boolean, @@ -27,46 +27,44 @@ type Props = { const PLACEHOLDER_NAME = 'No Name'; const PLACEHOLDER_ADDRESS = '5'.padEnd(16, 'x'); -export class AddressSummary extends React.PureComponent { - render () { - const { address, name, noBalance = false, orientation = 'vertical', size = 'medium' } = this.props; - let styles: SummaryStyles = { identiconSize: 16, fontSize: 'medium' }; +export function AddressSummary (props: AddressSummaryProps) { + const { address, name, noBalance = false, orientation = 'vertical', size = 'medium' } = props; + let styles: SummaryStyles = { identiconSize: 16, fontSize: 'medium' }; - switch (size) { - case 'tiny': - styles = { identiconSize: 16, fontSize: 'small' }; - break; - case 'small': - styles = { identiconSize: 32, fontSize: 'medium' }; - break; - case 'medium': - styles = { identiconSize: 64, fontSize: 'large' }; - break; - case 'large': - styles = { identiconSize: 128, fontSize: 'big' }; - break; - default: - } + switch (size) { + case 'tiny': + styles = { identiconSize: 16, fontSize: 'small' }; + break; + case 'small': + styles = { identiconSize: 32, fontSize: 'medium' }; + break; + case 'medium': + styles = { identiconSize: 64, fontSize: 'large' }; + break; + case 'large': + styles = { identiconSize: 128, fontSize: 'big' }; + break; + default: + } - if (orientation === 'vertical') { - return ( + if (orientation === 'vertical') { + return ( + + + {name || PLACEHOLDER_NAME} + {!noBalance && } + + ); + } else { + return ( + + + - {name || PLACEHOLDER_NAME} {!noBalance && } - ); - } else { - return ( - - - - - {name || PLACEHOLDER_NAME} - {!noBalance && } - - - ); - } + + ); } }