Some more func component conversions (#1639)
This commit is contained in:
parent
75bc4f3ddc
commit
a9cd43c8f0
@ -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(
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}`
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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 />
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)`
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user