Some more func component conversions (#1639)

This commit is contained in:
Jaco Greeff 2019-09-20 12:04:58 +02:00 committed by GitHub
parent 75bc4f3ddc
commit a9cd43c8f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 585 additions and 965 deletions

View File

@ -6,11 +6,10 @@ import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { ComponentProps, LocationProps } from './types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Route, Switch } from 'react-router';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { HelpOverlay, Tabs } from '@polkadot/react-components';
import { TabItem } from '@polkadot/react-components/Tabs';
import { withMulti, withObservable } from '@polkadot/react-api';
import basicMd from './md/basic.md';
@ -23,92 +22,20 @@ interface Props extends AppProps, I18nProps {
location: any;
}
interface State {
hidden: string[];
tabs: TabItem[];
}
function AccountsApp ({ allAccounts = {}, basePath, location, onStatusChange, t }: Props): React.ReactElement<Props> {
const [hidden, setHidden] = useState<string[]>(['vanity']);
class AccountsApp extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
const { allAccounts = {}, t } = props;
const baseState = Object.keys(allAccounts).length !== 0
? AccountsApp.showTabsState()
: AccountsApp.hideTabsState();
this.state = {
...(baseState as State),
tabs: [
{
isRoot: true,
name: 'overview',
text: t('My accounts')
},
{
name: 'vanity',
text: t('Vanity address')
}
]
};
}
private static showTabsState (): Partial<State> {
return {
hidden: [] as string[]
};
}
private static hideTabsState (): Partial<State> {
// Hide vanity as well - since the route order and matching changes, the
// /create/:seed route become problematic, so don't allow that option
return {
hidden: ['vanity']
};
}
public static getDerivedStateFromProps ({ allAccounts = {} }: Props, { hidden }: State): State | null {
const hasAddresses = Object.keys(allAccounts).length !== 0;
if (hidden.length === 0) {
return hasAddresses
? null
: AccountsApp.hideTabsState() as State;
}
return hasAddresses
? AccountsApp.showTabsState() as State
: null;
}
public render (): React.ReactNode {
const { basePath } = this.props;
const { hidden, tabs } = this.state;
return (
<main className='accounts--App'>
<HelpOverlay md={basicMd} />
<header>
<Tabs
basePath={basePath}
hidden={hidden}
items={tabs}
/>
</header>
<Switch>
<Route path={`${basePath}/vanity`} render={this.renderComponent(Vanity)} />
<Route render={this.renderComponent(Overview)} />
</Switch>
</main>
useEffect((): void => {
setHidden(
Object.keys(allAccounts).length !== 0
? []
: ['vanity']
);
}
}, [allAccounts]);
private renderComponent (Component: React.ComponentType<ComponentProps>): (props: LocationProps) => React.ReactNode {
const _renderComponent = (Component: React.ComponentType<ComponentProps>): (props: LocationProps) => React.ReactNode => {
// eslint-disable-next-line react/display-name
return ({ match }: LocationProps): React.ReactNode => {
const { basePath, location, onStatusChange } = this.props;
return (
<Component
basePath={basePath}
@ -118,7 +45,34 @@ class AccountsApp extends React.PureComponent<Props, State> {
/>
);
};
}
};
return (
<main className='accounts--App'>
<HelpOverlay md={basicMd} />
<header>
<Tabs
basePath={basePath}
hidden={hidden}
items={[
{
isRoot: true,
name: 'overview',
text: t('My accounts')
},
{
name: 'vanity',
text: t('Vanity address')
}
]}
/>
</header>
<Switch>
<Route path={`${basePath}/vanity`} render={_renderComponent(Vanity)} />
<Route render={_renderComponent(Overview)} />
</Switch>
</main>
);
}
export default withMulti(

View File

@ -170,7 +170,9 @@ class Create extends React.PureComponent<Props, State> {
<Modal.Actions>
<Button.Group>
<Button
isNegative
label={t('Cancel')}
labelIcon='cancel'
onClick={this.onDiscard}
/>
<Button.Or />
@ -178,6 +180,7 @@ class Create extends React.PureComponent<Props, State> {
isDisabled={!isValid}
isPrimary
label={t('Save')}
labelIcon='plus'
onClick={this.onShowWarning}
/>
</Button.Group>

View File

@ -40,12 +40,14 @@ function CreateConfirmation ({ address, name, onCommit, onHideWarning, t }: Prop
<Button
isNegative
label={t('Cancel')}
labelIcon='cancel'
onClick={onHideWarning}
/>
<Button.Or />
<Button
isPrimary
label={t('Create and backup account')}
labelIcon='plus'
onClick={onCommit}
/>
</Button.Group>

View File

@ -71,7 +71,9 @@ class Transfer extends React.PureComponent<Props, State> {
}
public render (): React.ReactNode {
const { t } = this.props;
const { className, onClose, recipientId: propRecipientId, senderId: propSenderId, t } = this.props;
const { extrinsic, hasAvailable, maxBalance, recipientId, senderId } = this.state;
const available = <span className='label'>{t('available ')}</span>;
return (
<Modal
@ -80,8 +82,63 @@ class Transfer extends React.PureComponent<Props, State> {
open
>
<Modal.Header>{t('Send funds')}</Modal.Header>
{this.renderContent()}
{this.renderButtons()}
<Modal.Content>
<div className={className}>
<InputAddress
defaultValue={propSenderId}
help={t('The account you will send funds from.')}
isDisabled={!!propSenderId}
label={t('send from account')}
labelExtra={<Available label={available} params={senderId} />}
onChange={this.onChangeFrom}
type='account'
/>
<InputAddress
defaultValue={propRecipientId}
help={t('Select a contact or paste the address you want to send funds to.')}
isDisabled={!!propRecipientId}
label={t('send to address')}
labelExtra={<Available label={available} params={recipientId} />}
onChange={this.onChangeTo}
type='allPlus'
/>
<InputBalance
help={t('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 mili is equivalent to sending 0.001.')}
isError={!hasAvailable}
label={t('amount')}
maxValue={maxBalance}
onChange={this.onChangeAmount}
withMax
/>
<Checks
accountId={senderId}
extrinsic={extrinsic}
isSendable
onChange={this.onChangeFees}
/>
</div>
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
isNegative
label={t('Cancel')}
labelIcon='cancel'
onClick={onClose}
/>
<Button.Or />
<TxButton
accountId={senderId}
extrinsic={extrinsic}
isDisabled={!hasAvailable}
isPrimary
label={t('Make Transfer')}
labelIcon='send'
onStart={onClose}
withSpinner={false}
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
@ -105,80 +162,6 @@ class Transfer extends React.PureComponent<Props, State> {
});
}
private renderButtons (): React.ReactNode {
const { onClose, t } = this.props;
const { extrinsic, hasAvailable, senderId } = this.state;
return (
<Modal.Actions>
<Button.Group>
<Button
isNegative
label={t('Cancel')}
labelIcon='cancel'
onClick={onClose}
/>
<Button.Or />
<TxButton
accountId={senderId}
extrinsic={extrinsic}
isDisabled={!hasAvailable}
isPrimary
label={t('Make Transfer')}
labelIcon='send'
onStart={onClose}
withSpinner={false}
/>
</Button.Group>
</Modal.Actions>
);
}
private renderContent (): React.ReactNode {
const { className, recipientId: propRecipientId, senderId: propSenderId, t } = this.props;
const { extrinsic, hasAvailable, maxBalance, recipientId, senderId } = this.state;
const available = <span className='label'>{t('available ')}</span>;
return (
<Modal.Content>
<div className={className}>
<InputAddress
defaultValue={propSenderId}
help={t('The account you will send funds from.')}
isDisabled={!!propSenderId}
label={t('send from account')}
labelExtra={<Available label={available} params={senderId} />}
onChange={this.onChangeFrom}
type='account'
/>
<InputAddress
defaultValue={propRecipientId}
help={t('Select a contact or paste the address you want to send funds to.')}
isDisabled={!!propRecipientId}
label={t('send to address')}
labelExtra={<Available label={available} params={recipientId} />}
onChange={this.onChangeTo}
type='allPlus'
/>
<InputBalance
help={t('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 mili is equivalent to sending 0.001.')}
isError={!hasAvailable}
label={t('amount')}
maxValue={maxBalance}
onChange={this.onChangeAmount}
withMax
/>
<Checks
accountId={senderId}
extrinsic={extrinsic}
isSendable
onChange={this.onChangeFees}
/>
</div>
</Modal.Content>
);
}
private onChangeAmount = (amount: BN = new BN(0)): void => {
this.nextState({ amount });
}

View File

@ -28,6 +28,9 @@ interface State {
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;
@ -47,12 +50,43 @@ class Address extends React.PureComponent<Props, State> {
}
public render (): React.ReactNode {
const { address, className } = this.props;
const { isEditable } = this.state;
const { address, className, t } = this.props;
const { current, genesisHash, isEditable, isForgetOpen, isTransferOpen } = this.state;
return (
<AddressCard
buttons={this.renderButtons()}
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')}
/>
)}
<Button
isPrimary
key='deposit'
label={<><Icon name='paper plane' /> {t('deposit')}</>}
onClick={this.toggleTransfer}
size='small'
tooltip={t('Send funds to this address')}
/>
</div>
{isEditable && (
<div className='others'>
<ChainLock
genesisHash={genesisHash}
onChange={this.onGenesisChange}
/>
</div>
)}
</div>
}
className={className}
isEditable={isEditable}
type='address'
@ -61,51 +95,35 @@ class Address extends React.PureComponent<Props, State> {
withIndex
withTags
>
{this.renderModals()}
{address && current && (
<>
{isForgetOpen && (
<Forget
address={current.address}
onForget={this.onForget}
key='modal-forget-account'
mode='address'
onClose={this.toggleForget}
/>
)}
{isTransferOpen && (
<Transfer
key='modal-transfer'
onClose={this.toggleTransfer}
recipientId={address}
/>
)}
</>
)}
<AddressInfo
address={address}
withBalance={{ available: true, free: true, total: true }}
withExtended={{ nonce: true }}
withBalance={WITH_BALANCE}
withExtended={WITH_EXTENDED}
/>
</AddressCard>
);
}
private renderModals (): React.ReactNode {
const { address } = this.props;
const { isForgetOpen, isTransferOpen, current } = this.state;
if (!address || !current) {
return null;
}
const modals = [];
if (isForgetOpen) {
modals.push(
<Forget
address={current.address}
onForget={this.onForget}
key='modal-forget-account'
mode='address'
onClose={this.toggleForget}
/>
);
}
if (isTransferOpen) {
modals.push(
<Transfer
key='modal-transfer'
onClose={this.toggleTransfer}
recipientId={address}
/>
);
}
return modals;
}
private toggleForget = (): void => {
this.setState(({ isForgetOpen }): Pick<State, never> => ({
isForgetOpen: !isForgetOpen
@ -149,46 +167,6 @@ class Address extends React.PureComponent<Props, State> {
account && keyring.saveAddress(address, { ...account.meta, genesisHash });
});
}
private renderButtons (): React.ReactNode {
const { t } = this.props;
const { genesisHash, isEditable } = this.state;
return (
<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')}
/>
</>
)}
<Button
isPrimary
key='deposit'
label={<><Icon name='paper plane' /> {t('deposit')}</>}
onClick={this.toggleTransfer}
size='small'
tooltip={t('Send funds to this address')}
/>
</div>
{isEditable && (
<div className='others'>
<ChainLock
genesisHash={genesisHash}
onChange={this.onGenesisChange}
/>
</div>
)}
</div>
);
}
}
export default translate(
@ -200,5 +178,6 @@ export default translate(
margin-right: 0.125rem;
margin-top: 0.25rem;
}
}`
}
`
);

View File

@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React from 'react';
import React, { useState } from 'react';
import { Button, CardGrid } from '@polkadot/react-components';
import addressObservable from '@polkadot/ui-keyring/observable/addresses';
import { withMulti, withObservable } from '@polkadot/react-api';
@ -19,56 +19,41 @@ interface Props extends ComponentProps, I18nProps {
addresses?: SubjectInfo[];
}
interface State {
isCreateOpen: boolean;
}
function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<Props> {
const [isCreateOpen, setIsCreateOpen] = useState(false);
const emptyScreen = !isCreateOpen && (!addresses || Object.keys(addresses).length === 0);
class Overview extends React.PureComponent<Props, State> {
public state: State = {
isCreateOpen: false
};
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
public render (): React.ReactNode {
const { addresses, onStatusChange, t } = this.props;
const { isCreateOpen } = this.state;
const emptyScreen = !isCreateOpen && (!addresses || Object.keys(addresses).length === 0);
return (
<CardGrid
buttons={
<Button.Group>
<Button
isPrimary
label={t('Add contact')}
labelIcon='add'
onClick={this.toggleCreate}
/>
</Button.Group>
}
isEmpty={emptyScreen}
emptyText={t('No contact found.')}
>
{isCreateOpen && (
<CreateModal
onClose={this.toggleCreate}
onStatusChange={onStatusChange}
return (
<CardGrid
buttons={
<Button.Group>
<Button
isPrimary
label={t('Add contact')}
labelIcon='add'
onClick={_toggleCreate}
/>
)}
{addresses && Object.keys(addresses).map((address): React.ReactNode => (
<Address
address={address}
key={address}
/>
))}
</CardGrid>
);
}
private toggleCreate = (): void => {
this.setState(({ isCreateOpen }): State => ({
isCreateOpen: !isCreateOpen
}));
}
</Button.Group>
}
isEmpty={emptyScreen}
emptyText={t('No contacts found.')}
>
{isCreateOpen && (
<CreateModal
onClose={_toggleCreate}
onStatusChange={onStatusChange}
/>
)}
{addresses && Object.keys(addresses).map((address): React.ReactNode => (
<Address
address={address}
key={address}
/>
))}
</CardGrid>
);
}
export default withMulti(

View File

@ -8,10 +8,8 @@ import { ComponentProps } from './types';
import React from 'react';
import { Route, Switch } from 'react-router';
import addressObservable from '@polkadot/ui-keyring/observable/addresses';
import { HelpOverlay } from '@polkadot/react-components';
import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
import { withMulti, withObservable } from '@polkadot/react-api';
import Tabs from '@polkadot/react-components/Tabs';
import basicMd from './md/basic.md';
import Overview from './Overview';
@ -22,100 +20,37 @@ interface Props extends AppProps, I18nProps {
location: any;
}
interface State {
hidden: string[];
items: TabItem[];
isCreateOpen: boolean;
}
function AddressBookApp ({ basePath, onStatusChange, t }: Props): React.ReactElement<Props> {
const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
// eslint-disable-next-line react/display-name
return (): React.ReactNode =>
<Component
basePath={basePath}
location={location}
onStatusChange={onStatusChange}
/>;
};
class AddressBookApp extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
const { allAddresses = {}, t } = props;
const baseState = Object.keys(allAddresses).length !== 0
? AddressBookApp.showEditState()
: AddressBookApp.hideEditState();
this.state = {
...(baseState as State),
isCreateOpen: false,
items: [
{
isRoot: true,
name: 'overview',
text: t('My contacts')
}
]
};
}
private static showEditState (): Partial<State> {
return {
hidden: []
};
}
private static hideEditState (): Partial<State> {
return {
hidden: ['edit']
};
}
public static getDerivedStateFromProps ({ allAddresses = {} }: Props, { hidden }: State): State | null {
const hasAddresses = Object.keys(allAddresses).length !== 0;
if (hidden.length === 0) {
return hasAddresses
? null
: AddressBookApp.hideEditState() as State;
}
return hasAddresses
? AddressBookApp.showEditState() as State
: null;
}
public render (): React.ReactNode {
const { basePath } = this.props;
const { hidden, items } = this.state;
return (
<main className='address-book--App'>
<HelpOverlay md={basicMd} />
<header>
<Tabs
basePath={basePath}
hidden={hidden}
items={items}
/>
</header>
<Switch>
<Route render={this.renderComponent(Overview)} />
</Switch>
</main>
);
}
private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
return (): React.ReactNode => {
const { basePath, location, onStatusChange } = this.props;
return (
<Component
return (
<main className='address-book--App'>
<HelpOverlay md={basicMd} />
<header>
<Tabs
basePath={basePath}
location={location}
onStatusChange={onStatusChange}
items={[
{
isRoot: true,
name: 'overview',
text: t('My contacts')
}
]}
/>
);
};
}
</header>
<Switch>
<Route render={_renderComponent(Overview)} />
</Switch>
</main>
);
}
export default withMulti(
AddressBookApp,
translate,
withObservable(addressObservable.subject, { propName: 'allAddresses' })
);
export default translate(AddressBookApp);

View File

@ -27,16 +27,19 @@ interface State {
}
class Create extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
this.state = this.emptyState();
}
public state: State = {
address: '',
isAddressExisting: false,
isAddressValid: false,
isNameValid: true,
isValid: false,
name: 'new address',
tags: []
};
public render (): React.ReactNode {
const { t } = this.props;
const { address, isAddressValid, isNameValid, isValid, name } = this.state;
return (
<Modal
@ -44,84 +47,54 @@ class Create extends React.PureComponent<Props, State> {
open
>
<Modal.Header>{t('Add an address')}</Modal.Header>
{this.renderContent()}
{this.renderButtons()}
<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
isNegative
onClick={this.onDiscard}
label={t('Cancel')}
labelIcon='cancel'
/>
<Button.Or />
<Button
isDisabled={!isValid}
isPrimary
onClick={this.onCommit}
label={t('Save')}
labelIcon='save'
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
private renderButtons (): React.ReactNode {
const { t } = this.props;
const { isValid } = this.state;
return (
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={this.onDiscard}
label={t('Cancel')}
labelIcon='cancel'
/>
<Button.Or />
<Button
isDisabled={!isValid}
isPrimary
onClick={this.onCommit}
label={t('Save')}
labelIcon='save'
/>
</Button.Group>
</Modal.Actions>
);
}
private renderContent (): React.ReactNode {
const { t } = this.props;
const { address, isAddressValid, isNameValid, name } = this.state;
return (
<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>
);
}
private emptyState (): State {
return {
address: '',
isAddressExisting: false,
isAddressValid: false,
isNameValid: true,
isValid: false,
name: 'new address',
tags: []
};
}
private nextState (newState: Partial<State>, allowEdit = false): void {
this.setState(
(prevState: State): State => {

View File

@ -4,10 +4,10 @@
import React from 'react';
export default class Spacer extends React.PureComponent<{}> {
public render (): React.ReactNode {
return (
<div />
);
}
type Props = {};
export default function Spacer (): React.ReactElement<Props> {
return (
<div />
);
}

View File

@ -5,7 +5,7 @@
import { Route } from '@polkadot/apps-routing/types';
import { AppProps } from '@polkadot/react-components/types';
import React from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import routing from '@polkadot/apps-routing';
@ -16,52 +16,40 @@ interface Props extends AppProps {
className?: string;
}
interface State {
routes: Route[];
function renderEntry (route: Route): React.ReactNode {
return (
<Entry
key={route.name}
route={route}
/>
);
}
class App extends React.PureComponent<Props, State> {
// FIXME Atm we are not applying all the logic around should this be hidden or not, i.e.
// is the api available, are there accounts, etc. (That logic should also be extracted so
// it can be used in a proper way here)
public state: State = {
routes: routing.routes.filter((route): boolean =>
// NOTE: This _looks_ weird and it looks weird, because it is weird. Basically we want all
// the entries of an equal width. So here we add a non-content spacers at the end that just
// ensures flex has enough items to render something usable to the user. Since we don't
// quite know how many items per row, we just render a bunch, n === routes.length
function renderSpacer (route: Route, index: number): React.ReactNode {
return (
<Spacer key={index} />
);
}
function App ({ className }: Props): React.ReactElement<Props> {
const [routes] = useState(
routing.routes.filter((route): boolean =>
!!route && !route.display.isHidden && route.name !== 'dashboard'
) as Route[]
};
);
public render (): React.ReactNode {
const { className } = this.props;
const { routes } = this.state;
return (
<main className={className}>
<div className='routes'>
{routes.map(this.renderEntry)}
{routes.map(this.renderSpacer)}
</div>
</main>
);
}
private renderEntry = (route: Route): React.ReactNode => {
return (
<Entry
key={route.name}
route={route}
/>
);
}
// NOTE: This _looks_ weird and it looks weird, because it is weird. Basically we want all
// the entries of an equal width. So here we add a non-content spacers at the end that just
// ensures flex has enough items to render something usable to the user. Since we don't
// quite know how many items per row, we just render a bunch, n === routes.length
private renderSpacer = (route: Route, index: number): React.ReactNode => {
return (
<Spacer key={index} />
);
}
return (
<main className={className}>
<div className='routes'>
{routes.map(renderEntry)}
{routes.map(renderSpacer)}
</div>
</main>
);
}
export default styled(App)`

View File

@ -6,7 +6,8 @@
import { BlockNumber } from '@polkadot/types/interfaces';
import { BareProps } from '@polkadot/react-components/types';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { withCalls, withMulti } from '@polkadot/react-api';
import { isHex } from '@polkadot/util';
@ -23,54 +24,37 @@ interface Props extends BareProps {
};
}
interface State {
value?: string;
}
function Entry ({ chain_bestNumber, match: { params: { value } } }: Props): React.ReactElement<Props> | null {
const [stateValue, setStateValue] = useState<string>(value);
class Entry extends React.Component<Props, State> {
public state: State = {
value: undefined
};
public static getDerivedStateFromProps ({ chain_bestNumber, match: { params } }: Props): State {
let { value } = params;
if ((!value || !value.length) && chain_bestNumber) {
value = chain_bestNumber.toString();
useEffect((): void => {
if (value && value !== stateValue) {
setStateValue(value);
} else if (!stateValue && chain_bestNumber) {
setStateValue(chain_bestNumber.toString());
}
}, [chain_bestNumber, value]);
return {
value
};
if (!stateValue) {
return null;
}
public shouldComponentUpdate (nextProps: Props): boolean {
return this.props.match !== nextProps.match || !this.state.value;
}
const Component = isHex(stateValue)
? BlockByHash
: BlockByNumber;
public render (): React.ReactNode {
const { value } = this.state;
if (!value) {
return null;
}
const Component = isHex(value)
? BlockByHash
: BlockByNumber;
return (
<>
<Query />
<Component
key={value}
value={value}
/>
</>
);
}
return (
<>
<Query />
<Component
key={stateValue}
value={stateValue}
/>
</>
);
}
export default withMulti(
Entry,
withRouter(Entry),
withCalls<Props>('derive.chain.bestNumber')
);

View File

@ -3,59 +3,45 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import styled from 'styled-components';
import { Card, Button } from '@polkadot/react-components';
import { I18nProps } from '@polkadot/react-components/types';
import AssetRow from './AssetRow';
import translate from './translate';
type Props = I18nProps & {
interface Props extends I18nProps {
assetId: string;
name: string;
onSaveName: (id: string, name: string) => void;
onForget: (id: string) => void;
};
}
const Details = styled.div`
`;
function AssetCard ({ className, assetId, name, onForget, onSaveName, t }: Props): React.ReactElement<Props> {
const _onForget = (): void => onForget(assetId);
const _onSaveName = (name: string): void => onSaveName(assetId, name);
class AssetCard extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, assetId, name, t } = this.props;
return (
<Card className={className}>
<Details>
<AssetRow
isEditable
assetId={assetId}
defaultName={name}
buttons={(
<Button
isNegative
onClick={this.onForget}
icon='trash'
key='forget'
size='small'
tooltip={t('Forget this asset')}
/>
)}
onSaveName={this.onSaveName}
/>
</Details>
</Card>
);
}
private onForget = (): void => {
this.props.onForget(this.props.assetId);
};
private onSaveName = (name: string): void => {
this.props.onSaveName(this.props.assetId, name);
}
return (
<Card className={className}>
<div>
<AssetRow
isEditable
assetId={assetId}
defaultName={name}
buttons={(
<Button
isNegative
onClick={_onForget}
icon='trash'
key='forget'
size='small'
tooltip={t('Forget this asset')}
/>
)}
onSaveName={_onSaveName}
/>
</div>
</Card>
);
}
export default translate(AssetCard);

View File

@ -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 BN from 'bn.js';
import React from 'react';
import { I18nProps } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React, { useState } from 'react';
import { Button, CardGrid } from '@polkadot/react-components';
import { withMulti, withObservable } from '@polkadot/react-api';
@ -18,71 +18,47 @@ type Props = I18nProps & {
assets?: AssetsSubjectInfo;
};
interface State {
isCreateOpen: boolean;
}
function Assets ({ assets, t }: Props): React.ReactElement<Props> {
const [isCreateOpen, setIsCreateOpen] = useState(false);
const emptyScreen = !isCreateOpen && (!assets || Object.keys(assets).length === 0);
class Assets extends React.PureComponent<Props, State> {
public state: State = {
isCreateOpen: false
};
const _onForget = (id: string): void => assetRegistry.remove(id);
const _onRegister = (id: BN, name: string): void => assetRegistry.add(id.toString(), name);
const _onSaveName = (id: string, name: string): void => assetRegistry.add(id, name);
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
public render (): React.ReactNode {
const { assets, t } = this.props;
const { isCreateOpen } = this.state;
const emptyScreen = !isCreateOpen && (!assets || Object.keys(assets).length === 0);
return (
<CardGrid
buttons={
<Button.Group>
<Button
isPrimary
label={t('Register Asset')}
labelIcon='registered'
onClick={this.toggleCreate}
/>
</Button.Group>
}
isEmpty={emptyScreen}
emptyText={t('No assets found.')}
>
{isCreateOpen && (
<CreateModal
onClose={this.toggleCreate}
onRegister={this.onRegister}
return (
<CardGrid
buttons={
<Button.Group>
<Button
isPrimary
label={t('Register Asset')}
labelIcon='registered'
onClick={_toggleCreate}
/>
)}
{assets && Object.entries(assets).map(([id, name]): React.ReactNode => (
<AssetCard
key={id}
assetId={id}
name={name}
onSaveName={this.onSaveName}
onForget={this.onForget}
/>
))}
</CardGrid>
);
}
private onRegister = (id: BN, name: string): void => {
assetRegistry.add(id.toString(), name);
}
private toggleCreate = (): void => {
this.setState(({ isCreateOpen }): State => ({
isCreateOpen: !isCreateOpen
}));
}
private onSaveName = (id: string, name: string): void => {
assetRegistry.add(id, name);
}
private onForget = (id: string): void => {
assetRegistry.remove(id);
}
</Button.Group>
}
isEmpty={emptyScreen}
emptyText={t('No assets found.')}
>
{isCreateOpen && (
<CreateModal
onClose={_toggleCreate}
onRegister={_onRegister}
/>
)}
{assets && Object.entries(assets).map(([id, name]): React.ReactNode => (
<AssetCard
key={id}
assetId={id}
name={name}
onSaveName={_onSaveName}
onForget={_onForget}
/>
))}
</CardGrid>
);
}
export default withMulti(

View File

@ -3,7 +3,6 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AppProps, BareProps, I18nProps } from '@polkadot/react-components/types';
import { TabItem } from '@polkadot/react-components/Tabs';
import React from 'react';
import { Route, Switch } from 'react-router';
@ -14,52 +13,33 @@ import Assets from './Assets';
import translate from './translate';
type Props = AppProps & BareProps & I18nProps;
interface Props extends AppProps, BareProps, I18nProps {}
interface State {
tabs: TabItem[];
}
class App extends React.PureComponent<Props, State> {
public constructor (props: Props) {
super(props);
const { t } = props;
this.state = {
tabs: [
{
isRoot: true,
name: 'assets',
text: t('Assets')
},
{
name: 'transfer',
text: t('Transfer')
}
]
};
}
public render (): React.ReactNode {
const { basePath } = this.props;
const { tabs } = this.state;
return (
<main className='treasury--App'>
<header>
<Tabs
basePath={basePath}
items={tabs}
/>
</header>
<Switch>
<Route path={`${basePath}/transfer`} component={Transfer} />
<Route component={Assets} />
</Switch>
</main>
);
}
function App ({ basePath, t }: Props): React.ReactElement<Props> {
return (
<main className='treasury--App'>
<header>
<Tabs
basePath={basePath}
items={[
{
isRoot: true,
name: 'assets',
text: t('Assets')
},
{
name: 'transfer',
text: t('Transfer')
}
]}
/>
</header>
<Switch>
<Route path={`${basePath}/transfer`} component={Transfer} />
<Route component={Assets} />
</Switch>
</main>
);
}
export default translate(App);

View File

@ -6,7 +6,7 @@ import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import { AccountId, StakingLedger } from '@polkadot/types/interfaces';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Icon } from '@polkadot/react-components';
import { Option } from '@polkadot/types';
import { withCalls } from '@polkadot/react-api';
@ -23,19 +23,13 @@ interface Props extends ApiProps, I18nProps {
stashId?: string | null;
}
interface State {
error: string | null;
}
const DISTINCT = 'Distinct stash and controller accounts are recommended to ensure fund security.';
class ValidateController extends React.PureComponent<Props, State> {
public state: State = {
error: null
};
function ValidateController ({ accountId, bondedId, controllerId, defaultController, isUnsafeChain, onError, stashId, t }: Props): React.ReactElement<Props> | null {
const [error, setError] = useState<string | null>(null);
public static getDerivedStateFromProps ({ accountId, bondedId, controllerId, defaultController, isUnsafeChain, onError, stashId, t }: Props, prevState: State): State {
const error = ((): string | null => {
useEffect((): void => {
const newError = ((): string | null => {
if (defaultController === controllerId) {
// don't show an error if the selected controller is the default
// this applies when changing controller
@ -53,29 +47,21 @@ class ValidateController extends React.PureComponent<Props, State> {
return null;
})();
if (prevState.error !== error) {
onError(error);
if (error !== newError) {
onError(newError);
setError(newError);
}
}, [accountId, controllerId, defaultController]);
return {
error
};
if (!error || !accountId) {
return null;
}
public render (): React.ReactNode {
const { accountId } = this.props;
const { error } = this.state;
if (!error || !accountId) {
return null;
}
return (
<article className='warning'>
<div><Icon name='warning sign' />{error}</div>
</article>
);
}
return (
<article className='warning'>
<div><Icon name='warning sign' />{error}</div>
</article>
);
}
export default translate(

View File

@ -4,7 +4,7 @@
import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Icon } from '@polkadot/react-components';
import translate from '../../translate';
@ -16,46 +16,31 @@ interface Props extends I18nProps {
stashId: string;
}
interface State {
error: string | null;
}
function ValidateSessionEd25519 ({ onError, sessionId, stashId, t }: Props): React.ReactElement<Props> | null {
const [error, setError] = useState<string | null>(null);
class ValidateSessionEd25519 extends React.PureComponent<Props, State> {
public state: State = {
error: null
};
public static getDerivedStateFromProps ({ onError, sessionId, stashId, t }: Props, prevState: State): State | null {
let error = null;
useEffect((): void => {
let newError = null;
if (sessionId === stashId) {
error = t('For fund security, your session key should not match your stash key.');
} else {
error = null;
newError = t('For fund security, your session key should not match your stash key.');
}
if (error === prevState.error) {
return null;
if (error !== newError) {
onError(newError);
setError(newError);
}
}, [sessionId, stashId]);
onError(error);
return { error };
if (!error) {
return null;
}
public render (): React.ReactNode {
const { error } = this.state;
if (!error) {
return null;
}
return (
<article className='warning'>
<div><Icon name='warning sign' />{error}</div>
</article>
);
}
return (
<article className='warning'>
<div><Icon name='warning sign' />{error}</div>
</article>
);
}
export default translate(ValidateSessionEd25519);

View File

@ -5,7 +5,7 @@
import { I18nProps } from '@polkadot/react-components/types';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
import React from 'react';
import React, { useState } from 'react';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import translate from '../../translate';
@ -19,40 +19,47 @@ interface Props extends I18nProps {
stashOptions: KeyringSectionOption[];
}
interface State {
nominees: string[] | undefined;
}
function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }: Props): React.ReactElement<Props> | null {
const [nominees, setNominees] = useState<string[] | undefined>();
class Nominate extends React.PureComponent<Props, State> {
public state: State = {
nominees: this.props.nominees
};
public render (): React.ReactNode {
const { isOpen } = this.props;
if (!isOpen) {
return null;
}
return (
<Modal
className='staking--Nominating'
dimmer='inverted'
open
size='small'
>
{this.renderContent()}
{this.renderButtons()}
</Modal>
);
if (!isOpen) {
return null;
}
public renderButtons (): React.ReactNode {
const { controllerId, onClose, t } = this.props;
const { nominees } = this.state;
return (
return (
<Modal
className='staking--Nominating'
dimmer='inverted'
open
size='small'
>
<Modal.Header>
{t('Nominate Validators')}
</Modal.Header>
<Modal.Content className='ui--signer-Signer-Content'>
<InputAddress
className='medium'
defaultValue={controllerId}
isDisabled
label={t('controller account')}
/>
<InputAddress
className='medium'
defaultValue={stashId}
isDisabled
label={t('stash account')}
/>
<InputAddress
className='medium'
isMultiple
help={t('Stash accounts that are to be nominated. Block rewards are split between validators and nominators')}
label={t('nominate the following addresses')}
onChangeMulti={setNominees}
options={stashOptions}
placeholder={t('select accounts(s) nominate')}
type='account'
/>
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
@ -74,48 +81,8 @@ class Nominate extends React.PureComponent<Props, State> {
/>
</Button.Group>
</Modal.Actions>
);
}
public renderContent (): React.ReactNode {
const { controllerId, stashId, stashOptions, t } = this.props;
return (
<>
<Modal.Header>
{t('Nominate Validators')}
</Modal.Header>
<Modal.Content className='ui--signer-Signer-Content'>
<InputAddress
className='medium'
defaultValue={controllerId}
isDisabled
label={t('controller account')}
/>
<InputAddress
className='medium'
defaultValue={stashId}
isDisabled
label={t('stash account')}
/>
<InputAddress
className='medium'
isMultiple
help={t('Stash accounts that are to be nominated. Block rewards are split between validators and nominators')}
label={t('nominate the following addresses')}
onChangeMulti={this.onChangeNominees}
options={stashOptions}
placeholder={t('select accounts(s) nominate')}
type='account'
/>
</Modal.Content>
</>
);
}
private onChangeNominees = (nominees: string[]): void => {
this.setState({ nominees });
}
</Modal>
);
}
export default translate(Nominate);

View File

@ -5,12 +5,13 @@
import { ApiProps } from '@polkadot/react-api/types';
import { ComponentProps } from '../types';
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
import { withCalls, withMulti } from '@polkadot/react-api/with';
import React from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import { Button, CardGrid, Icon } from '@polkadot/react-components';
import { Button, CardGrid } from '@polkadot/react-components';
import createOption from '@polkadot/ui-keyring/options/item';
import { getAddressName } from '@polkadot/react-components/util';
@ -22,44 +23,52 @@ interface Props extends I18nProps, ComponentProps, ApiProps {
myControllers?: string[];
}
interface State {
isNewStakeOpen: boolean;
function getMyStashes (myControllers?: string[], allAccounts?: SubjectInfo): string[] | null {
const result: string[] = [];
if (!myControllers) {
return null;
}
myControllers.forEach((value, index): void => {
if (value.toString() !== '') {
allAccounts && result.push(Object.keys(allAccounts)[index]);
}
});
return result;
}
class Accounts extends React.PureComponent<Props, State> {
public state: State = {
isNewStakeOpen: false
};
function Accounts ({ allAccounts, allStashes, className, myControllers, recentlyOnline, t }: Props): React.ReactElement<Props> {
const [isNewStakeOpen, setIsNewStateOpen] = useState(false);
const myStashes = getMyStashes(myControllers, allAccounts);
const stashOptions = allStashes.map((stashId): KeyringSectionOption =>
createOption(stashId, getAddressName(stashId, 'account'))
);
const isEmpty = !isNewStakeOpen && (!myStashes || myStashes.length === 0);
public render (): React.ReactNode {
const { allStashes, className, recentlyOnline, t } = this.props;
const { isNewStakeOpen } = this.state;
const stashOptions = this.getStashOptions();
const myStashes = this.getMyStashes();
const isEmpty = !isNewStakeOpen && (!myStashes || myStashes.length === 0);
const _toggleNewStake = (): void => setIsNewStateOpen(!isNewStakeOpen);
return (
<CardGrid
buttons={
<Button
isPrimary
key='new-stake'
label={
<>
<Icon name='add'/>
{t('New stake')}
</>
}
onClick={this.toggleNewStake}
/>
}
className={className}
emptyText={t('No funds staked yet.')}
isEmpty={isEmpty}
>
{this.renderNewStake()}
{myStashes && myStashes.map((address, index): React.ReactNode => (
address &&
return (
<CardGrid
buttons={
<Button
isPrimary
key='new-stake'
label={t('New stake')}
labelIcon='add'
onClick={_toggleNewStake}
/>
}
className={className}
emptyText={t('No funds staked yet.')}
isEmpty={isEmpty}
>
{isNewStakeOpen && (
<StartStaking onClose={_toggleNewStake} />
)}
{myStashes && myStashes.map((address, index): React.ReactNode => (
address && (
<Account
allStashes={allStashes}
accountId={address}
@ -67,59 +76,14 @@ class Accounts extends React.PureComponent<Props, State> {
recentlyOnline={recentlyOnline}
stashOptions={stashOptions}
/>
))}
</CardGrid>
);
}
private getMyStashes (): string[] | null {
const { myControllers, allAccounts } = this.props;
const result: string[] = [];
if (!myControllers) {
return null;
}
myControllers.forEach((value, index): void => {
if (value.toString() !== '') {
allAccounts && result.push(Object.keys(allAccounts)[index]);
}
});
return result;
}
private getStashOptions (): KeyringSectionOption[] {
const { allStashes } = this.props;
return allStashes.map((stashId): KeyringSectionOption =>
createOption(stashId, getAddressName(stashId, 'account'))
);
}
private renderNewStake (): React.ReactNode {
const { isNewStakeOpen } = this.state;
if (!isNewStakeOpen) {
return null;
}
return (
<StartStaking
onClose={this.toggleNewStake}
/>
);
}
private toggleNewStake = (): void => {
this.setState(({ isNewStakeOpen }): Pick<State, never> => ({
isNewStakeOpen: !isNewStakeOpen
}));
}
)
))}
</CardGrid>
);
}
export default withMulti(
styled(Accounts as React.ComponentClass<Props, State>)`
styled(Accounts)`
.ui--CardGrid-buttons {
text-align: right;
}

View File

@ -14,7 +14,7 @@ import React from 'react';
import { Route, Switch } from 'react-router';
import { createType, Option } from '@polkadot/types';
import { HelpOverlay } from '@polkadot/react-components';
import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
import Tabs from '@polkadot/react-components/Tabs';
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
@ -38,35 +38,15 @@ interface State {
allStashes: string[];
currentValidatorsControllersV1OrStashesV2: string[];
recentlyOnline: Record<string, BlockNumber>;
tabs: TabItem[];
}
class App extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
const { t } = props;
this.state = {
allControllers: [],
allStashes: [],
currentValidatorsControllersV1OrStashesV2: [],
recentlyOnline: {},
tabs: [
{
isRoot: true,
name: 'overview',
text: t('Staking overview')
},
{
name: 'actions',
text: t('Account actions')
}
]
};
}
public state: State = {
allControllers: [],
allStashes: [],
currentValidatorsControllersV1OrStashesV2: [],
recentlyOnline: {}
};
public static getDerivedStateFromProps (props: Props, state: State): Pick<State, never> {
const { allStashesAndControllers = [[], []], chain_bestNumber, currentValidatorsControllersV1OrStashesV2 = [] } = props;
@ -95,11 +75,7 @@ class App extends React.PureComponent<Props, State> {
}
public render (): React.ReactNode {
const { allAccounts, basePath } = this.props;
const { tabs } = this.state;
const hidden = !allAccounts || Object.keys(allAccounts).length === 0
? ['actions']
: [];
const { allAccounts, basePath, t } = this.props;
return (
<main className='staking--App'>
@ -107,8 +83,22 @@ class App extends React.PureComponent<Props, State> {
<header>
<Tabs
basePath={basePath}
hidden={hidden}
items={tabs}
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>