More functional components (#1667)
* More functional components * sudo setKey * Additional components
This commit is contained in:
parent
6bb3ab14f7
commit
c3ce788215
@ -6,7 +6,7 @@ import { KeyringAddress } from '@polkadot/ui-keyring/types';
|
||||
import { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressCard, AddressInfo, Button, ChainLock, Forget } from '@polkadot/react-components';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
@ -20,154 +20,121 @@ interface Props extends I18nProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
current?: KeyringAddress;
|
||||
genesisHash: string | null;
|
||||
isEditable: boolean;
|
||||
isForgetOpen: boolean;
|
||||
isTransferOpen: boolean;
|
||||
}
|
||||
|
||||
const WITH_BALANCE = { available: true, free: true, total: true };
|
||||
const WITH_EXTENDED = { nonce: true };
|
||||
|
||||
class Address extends React.PureComponent<Props, State> {
|
||||
public state: State;
|
||||
const isEditable = true;
|
||||
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
function Address ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
const [current, setCurrent] = useState<KeyringAddress | null>(null);
|
||||
const [genesisHash, setGenesisHash] = useState<string | null>(null);
|
||||
const [isForgetOpen, setIsForgetOpen] = useState(false);
|
||||
const [isTransferOpen, setIsTransferOpen] = useState(false);
|
||||
|
||||
const { address } = this.props;
|
||||
useEffect((): void => {
|
||||
const current = keyring.getAddress(address);
|
||||
|
||||
this.state = {
|
||||
current,
|
||||
genesisHash: (current && current.meta.genesisHash) || null,
|
||||
isEditable: true,
|
||||
isForgetOpen: false,
|
||||
isTransferOpen: false
|
||||
};
|
||||
}
|
||||
setCurrent(current || null);
|
||||
setGenesisHash((current && current.meta.genesisHash) || null);
|
||||
}, []);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { address, className, t } = this.props;
|
||||
const { current, genesisHash, isEditable, isForgetOpen, isTransferOpen } = this.state;
|
||||
const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
|
||||
const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
|
||||
const _onForget = (): void => {
|
||||
if (address) {
|
||||
const status: Partial<ActionStatus> = {
|
||||
account: address,
|
||||
action: 'forget'
|
||||
};
|
||||
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={
|
||||
<div className='addresses--Address-buttons buttons'>
|
||||
<div className='actions'>
|
||||
{isEditable && (
|
||||
<Button
|
||||
isNegative
|
||||
onClick={this.toggleForget}
|
||||
icon='trash'
|
||||
key='forget'
|
||||
size='small'
|
||||
tooltip={t('Forget this address')}
|
||||
/>
|
||||
)}
|
||||
try {
|
||||
keyring.forgetAddress(address);
|
||||
status.status = 'success';
|
||||
status.message = t('address forgotten');
|
||||
} catch (error) {
|
||||
status.status = 'error';
|
||||
status.message = error.message;
|
||||
}
|
||||
}
|
||||
};
|
||||
const _onGenesisChange = (genesisHash: string | null): void => {
|
||||
setGenesisHash(genesisHash);
|
||||
|
||||
const account = keyring.getAddress(address);
|
||||
|
||||
account && keyring.saveAddress(address, { ...account.meta, genesisHash });
|
||||
};
|
||||
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={
|
||||
<div className='addresses--Address-buttons buttons'>
|
||||
<div className='actions'>
|
||||
{isEditable && (
|
||||
<Button
|
||||
icon='paper plane'
|
||||
isPrimary
|
||||
key='deposit'
|
||||
label={t('deposit')}
|
||||
onClick={this.toggleTransfer}
|
||||
isNegative
|
||||
onClick={_toggleForget}
|
||||
icon='trash'
|
||||
key='forget'
|
||||
size='small'
|
||||
tooltip={t('Send funds to this address')}
|
||||
tooltip={t('Forget this address')}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon='paper plane'
|
||||
isPrimary
|
||||
key='deposit'
|
||||
label={t('deposit')}
|
||||
onClick={_toggleTransfer}
|
||||
size='small'
|
||||
tooltip={t('Send funds to this address')}
|
||||
/>
|
||||
</div>
|
||||
{isEditable && (
|
||||
<div className='others'>
|
||||
<ChainLock
|
||||
genesisHash={genesisHash}
|
||||
onChange={_onGenesisChange}
|
||||
/>
|
||||
</div>
|
||||
{isEditable && (
|
||||
<div className='others'>
|
||||
<ChainLock
|
||||
genesisHash={genesisHash}
|
||||
onChange={this.onGenesisChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
className={className}
|
||||
isEditable={isEditable}
|
||||
type='address'
|
||||
value={address}
|
||||
withExplorer
|
||||
withIndex
|
||||
withTags
|
||||
>
|
||||
{address && current && (
|
||||
<>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={current.address}
|
||||
onForget={this.onForget}
|
||||
key='modal-forget-account'
|
||||
mode='address'
|
||||
onClose={this.toggleForget}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={this.toggleTransfer}
|
||||
recipientId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={WITH_BALANCE}
|
||||
withExtended={WITH_EXTENDED}
|
||||
/>
|
||||
</AddressCard>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleForget = (): void => {
|
||||
this.setState(({ isForgetOpen }): Pick<State, never> => ({
|
||||
isForgetOpen: !isForgetOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleTransfer = (): void => {
|
||||
this.setState(({ isTransferOpen }): Pick<State, never> => ({
|
||||
isTransferOpen: !isTransferOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private onForget = (): void => {
|
||||
const { address, t } = this.props;
|
||||
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
const status: Partial<ActionStatus> = {
|
||||
account: address,
|
||||
action: 'forget'
|
||||
};
|
||||
|
||||
try {
|
||||
keyring.forgetAddress(address);
|
||||
status.status = 'success';
|
||||
status.message = t('address forgotten');
|
||||
} catch (error) {
|
||||
status.status = 'error';
|
||||
status.message = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
private onGenesisChange = (genesisHash: string | null): void => {
|
||||
const { address } = this.props;
|
||||
|
||||
this.setState({ genesisHash }, (): void => {
|
||||
const account = keyring.getAddress(address);
|
||||
|
||||
account && keyring.saveAddress(address, { ...account.meta, genesisHash });
|
||||
});
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
className={className}
|
||||
isEditable={isEditable}
|
||||
type='address'
|
||||
value={address}
|
||||
withExplorer
|
||||
withIndex
|
||||
withTags
|
||||
>
|
||||
{address && current && (
|
||||
<>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={current.address}
|
||||
onForget={_onForget}
|
||||
key='modal-forget-account'
|
||||
mode='address'
|
||||
onClose={_toggleForget}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
recipientId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={WITH_BALANCE}
|
||||
withExtended={WITH_EXTENDED}
|
||||
/>
|
||||
</AddressCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { AppProps, I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import store from 'store';
|
||||
import styled from 'styled-components';
|
||||
import { getTypeRegistry } from '@polkadot/types';
|
||||
@ -15,96 +15,45 @@ import translate from './translate';
|
||||
|
||||
interface Props extends AppProps, I18nProps {}
|
||||
|
||||
interface State {
|
||||
code: string;
|
||||
isJsonValid: boolean;
|
||||
isTypesValid: boolean;
|
||||
types: Record<string, any> | {};
|
||||
typesPlaceholder?: string;
|
||||
}
|
||||
const EMPTY_CODE = '{\n\n}';
|
||||
const EMPTY_TYPES = {};
|
||||
|
||||
class Developer extends React.PureComponent<Props, State> {
|
||||
private defaultCode = '{\n\n}';
|
||||
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
function Developer ({ className, onStatusChange, t }: Props): React.ReactElement<Props> {
|
||||
const [code, setCode] = useState(EMPTY_CODE);
|
||||
const [isJsonValid, setIsJsonValid] = useState(true);
|
||||
const [isTypesValid, setIsTypesValid] = useState(true);
|
||||
const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
|
||||
const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
const types = store.get('types') || {};
|
||||
const names = Object.keys(types);
|
||||
|
||||
this.state = {
|
||||
code: Object.keys(types).length ? JSON.stringify(types, null, 2) : this.defaultCode,
|
||||
isJsonValid: true,
|
||||
isTypesValid: true,
|
||||
types: names.length ? types : {},
|
||||
typesPlaceholder: names.length
|
||||
? names.join(', ')
|
||||
: undefined
|
||||
};
|
||||
}
|
||||
if (Object.keys(types).length) {
|
||||
setCode(JSON.stringify(types, null, 2));
|
||||
setTypes({});
|
||||
setTypesPlaceholder(Object.keys(types).join(', '));
|
||||
}
|
||||
}, []);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, t } = this.props;
|
||||
const { code, isJsonValid, isTypesValid, types, typesPlaceholder } = this.state;
|
||||
const typesHasNoEntries = Object.keys(types).length === 0;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<InputFile
|
||||
clearContent={typesHasNoEntries && isTypesValid}
|
||||
help={t('Save the type definitions for your custom structures as key-value pairs in a valid JSON file. The key should be the name of your custom structure and the value an object containing your type definitions.')}
|
||||
isError={!isTypesValid}
|
||||
label={t('Additional types as a JSON file (or edit below)')}
|
||||
onChange={this.onChangeTypes}
|
||||
placeholder={typesPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Editor
|
||||
className='editor'
|
||||
code={code}
|
||||
isValid={isJsonValid}
|
||||
onEdit={this.onEditTypes}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isDisabled={typesHasNoEntries}
|
||||
isNegative
|
||||
onClick={this.clearTypes}
|
||||
label={t('Reset')}
|
||||
icon='sync'
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isDisabled={!isTypesValid || !isJsonValid || typesHasNoEntries}
|
||||
isPrimary
|
||||
onClick={this.saveDeveloper}
|
||||
label={t('Save')}
|
||||
icon='save'
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private clearTypes = (): void => {
|
||||
const _setState = ({ code, isJsonValid, isTypesValid, types, typesPlaceholder }: { code: string; isJsonValid: boolean; isTypesValid: boolean; types: Record<string, any>; typesPlaceholder: string | null }): void => {
|
||||
setCode(code);
|
||||
setIsJsonValid(isJsonValid);
|
||||
setIsTypesValid(isTypesValid);
|
||||
setTypes(types);
|
||||
setTypesPlaceholder(typesPlaceholder);
|
||||
};
|
||||
const _clearTypes = (): void => {
|
||||
store.remove('types');
|
||||
this.setState({
|
||||
code: this.defaultCode,
|
||||
|
||||
_setState({
|
||||
code: EMPTY_CODE,
|
||||
isJsonValid: true,
|
||||
isTypesValid: true,
|
||||
types: {},
|
||||
typesPlaceholder: undefined
|
||||
types: EMPTY_TYPES,
|
||||
typesPlaceholder: null
|
||||
});
|
||||
}
|
||||
|
||||
private onChangeTypes = (data: Uint8Array): void => {
|
||||
};
|
||||
const _onChangeTypes = (data: Uint8Array): void => {
|
||||
const code = u8aToString(data);
|
||||
|
||||
try {
|
||||
@ -113,7 +62,7 @@ class Developer extends React.PureComponent<Props, State> {
|
||||
|
||||
console.log('Detected types:', typesPlaceholder);
|
||||
|
||||
this.setState({
|
||||
_setState({
|
||||
code,
|
||||
isJsonValid: true,
|
||||
isTypesValid: true,
|
||||
@ -123,7 +72,7 @@ class Developer extends React.PureComponent<Props, State> {
|
||||
} catch (error) {
|
||||
console.error('Error registering types:', error);
|
||||
|
||||
this.setState({
|
||||
_setState({
|
||||
code,
|
||||
isJsonValid: false,
|
||||
isTypesValid: false,
|
||||
@ -131,56 +80,84 @@ class Developer extends React.PureComponent<Props, State> {
|
||||
typesPlaceholder: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onEditTypes = (code: string): void => {
|
||||
};
|
||||
const _onEditTypes = (code: string): void => {
|
||||
try {
|
||||
if (!isJsonObject(code)) {
|
||||
throw Error(this.props.t('This is not a valid JSON object.'));
|
||||
throw Error(t('This is not a valid JSON object.'));
|
||||
}
|
||||
|
||||
this.setState((prevState: State): Pick<State, never> => ({
|
||||
...prevState,
|
||||
code,
|
||||
isJsonValid: true
|
||||
}));
|
||||
|
||||
this.onChangeTypes(stringToU8a(code));
|
||||
_onChangeTypes(stringToU8a(code));
|
||||
} catch (e) {
|
||||
this.setState((prevState: State): Pick<State, never> => ({
|
||||
...prevState,
|
||||
code,
|
||||
isJsonValid: false,
|
||||
typesPlaceholder: e.message
|
||||
}));
|
||||
setCode(code);
|
||||
setIsJsonValid(false);
|
||||
setTypesPlaceholder(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private saveDeveloper = (): void => {
|
||||
const { onStatusChange, t } = this.props;
|
||||
const { types } = this.state;
|
||||
|
||||
};
|
||||
const _saveDeveloper = (): void => {
|
||||
try {
|
||||
getTypeRegistry().register(types);
|
||||
|
||||
store.set('types', types);
|
||||
|
||||
this.setState({ isTypesValid: true });
|
||||
|
||||
setIsTypesValid(true);
|
||||
onStatusChange({
|
||||
status: 'success',
|
||||
action: t('Your custom types have been added')
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({ isTypesValid: false });
|
||||
|
||||
setIsTypesValid(false);
|
||||
onStatusChange({
|
||||
status: 'error',
|
||||
action: t(`Error saving your custom types. ${error.message}`)
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const typesHasNoEntries = Object.keys(types).length === 0;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<InputFile
|
||||
clearContent={typesHasNoEntries && isTypesValid}
|
||||
help={t('Save the type definitions for your custom structures as key-value pairs in a valid JSON file. The key should be the name of your custom structure and the value an object containing your type definitions.')}
|
||||
isError={!isTypesValid}
|
||||
label={t('Additional types as a JSON file (or edit below)')}
|
||||
onChange={_onChangeTypes}
|
||||
placeholder={typesPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Editor
|
||||
className='editor'
|
||||
code={code}
|
||||
isValid={isJsonValid}
|
||||
onEdit={_onEditTypes}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isDisabled={typesHasNoEntries}
|
||||
isNegative
|
||||
onClick={_clearTypes}
|
||||
label={t('Reset')}
|
||||
icon='sync'
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isDisabled={!isTypesValid || !isJsonValid || typesHasNoEntries}
|
||||
isPrimary
|
||||
onClick={_saveDeveloper}
|
||||
label={t('Save')}
|
||||
icon='save'
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(
|
||||
|
||||
@ -82,8 +82,8 @@ class Address extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { address, className, defaultName, filter } = this.props;
|
||||
const { controllerId, stakers, stashId } = this.state;
|
||||
const { address, className, defaultName, filter, isSubstrateV2, lastAuthor, lastBlock, t } = this.props;
|
||||
const { controllerId, onlineStatus, sessionId, stakers, stashId } = this.state;
|
||||
const bonded = stakers && !stakers.own.isEmpty
|
||||
? [stakers.own.unwrap(), stakers.total.unwrap().sub(stakers.own.unwrap())]
|
||||
: true;
|
||||
@ -96,54 +96,66 @@ class Address extends React.PureComponent<Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAuthor = !!lastBlock && !!lastAuthor && [address, controllerId, stashId].includes(lastAuthor);
|
||||
const nominators = this.getNominators();
|
||||
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={this.renderKeys()}
|
||||
buttons={
|
||||
<div className='staking--Address-info'>
|
||||
{isAuthor && (
|
||||
<div className={classes(isSubstrateV2 ? 'blockNumberV2' : 'blockNumberV1')}>#{lastBlock}</div>
|
||||
)}
|
||||
{controllerId && (
|
||||
<div>
|
||||
<label className={classes('staking--label', isSubstrateV2 && !isAuthor && 'controllerSpacer')}>{t('controller')}</label>
|
||||
<AddressMini value={controllerId} />
|
||||
</div>
|
||||
)}
|
||||
{!isSubstrateV2 && sessionId && (
|
||||
<div>
|
||||
<label className='staking--label'>{t('session')}</label>
|
||||
<AddressMini value={sessionId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
className={className}
|
||||
defaultName={defaultName}
|
||||
iconInfo={this.renderOnlineStatus()}
|
||||
iconInfo={controllerId && onlineStatus && (
|
||||
<OnlineStatus
|
||||
accountId={controllerId}
|
||||
value={onlineStatus}
|
||||
tooltip
|
||||
/>
|
||||
)}
|
||||
key={stashId || controllerId || undefined}
|
||||
value={stashId || address}
|
||||
withBalance={{ bonded }}
|
||||
>
|
||||
{this.renderNominators()}
|
||||
{nominators.length && (
|
||||
<details>
|
||||
<summary>
|
||||
{t('Nominators ({{count}})', {
|
||||
replace: {
|
||||
count: nominators.length
|
||||
}
|
||||
})}
|
||||
</summary>
|
||||
{nominators.map(([who, bonded]): React.ReactNode =>
|
||||
<AddressMini
|
||||
bonded={bonded}
|
||||
key={who.toString()}
|
||||
value={who}
|
||||
withBonded
|
||||
/>
|
||||
)}
|
||||
</details>
|
||||
)}
|
||||
</AddressCard>
|
||||
);
|
||||
}
|
||||
|
||||
private renderKeys (): React.ReactNode {
|
||||
const { address, isSubstrateV2, lastAuthor, lastBlock, t } = this.props;
|
||||
const { controllerId, sessionId, stashId } = this.state;
|
||||
const isAuthor = !!lastBlock && !!lastAuthor && [address, controllerId, stashId].includes(lastAuthor);
|
||||
|
||||
return (
|
||||
<div className='staking--Address-info'>
|
||||
{isAuthor
|
||||
? <div className={classes(isSubstrateV2 ? 'blockNumberV2' : 'blockNumberV1')}>#{lastBlock}</div>
|
||||
: null
|
||||
}
|
||||
{controllerId
|
||||
? (
|
||||
<div>
|
||||
<label className={classes('staking--label', isSubstrateV2 && !isAuthor && 'controllerSpacer')}>{t('controller')}</label>
|
||||
<AddressMini value={controllerId} />
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{!isSubstrateV2 && sessionId
|
||||
? (
|
||||
<div>
|
||||
<label className='staking--label'>{t('session')}</label>
|
||||
<AddressMini value={sessionId} />
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getNominators (): [AccountId, Balance][] {
|
||||
const { stakers } = this.state;
|
||||
|
||||
@ -176,50 +188,6 @@ class Address extends React.PureComponent<Props, State> {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private renderNominators (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const nominators = this.getNominators();
|
||||
|
||||
if (!nominators.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<details>
|
||||
<summary>
|
||||
{t('Nominators ({{count}})', {
|
||||
replace: {
|
||||
count: nominators.length
|
||||
}
|
||||
})}
|
||||
</summary>
|
||||
{nominators.map(([who, bonded]): React.ReactNode =>
|
||||
<AddressMini
|
||||
bonded={bonded}
|
||||
key={who.toString()}
|
||||
value={who}
|
||||
withBonded
|
||||
/>
|
||||
)}
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
private renderOnlineStatus (): React.ReactNode {
|
||||
const { controllerId, onlineStatus } = this.state;
|
||||
if (!controllerId || !onlineStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OnlineStatus
|
||||
accountId={controllerId}
|
||||
value={onlineStatus}
|
||||
tooltip
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
|
||||
@ -6,81 +6,24 @@ import { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ValidatorFilter } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-components';
|
||||
|
||||
import translate from '../translate';
|
||||
import Address from './Address';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
currentValidatorsControllersV1OrStashesV2: string[];
|
||||
currentValidators: string[];
|
||||
lastAuthor?: string;
|
||||
lastBlock: string;
|
||||
next: string[];
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
filter: ValidatorFilter;
|
||||
filterOptions: { text: React.ReactNode; value: ValidatorFilter }[];
|
||||
}
|
||||
|
||||
class CurrentList extends React.PureComponent<Props, State> {
|
||||
public state: State;
|
||||
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
const { t } = props;
|
||||
|
||||
this.state = {
|
||||
filter: 'all',
|
||||
filterOptions: [
|
||||
{ text: t('Show all validators and intentions'), value: 'all' },
|
||||
{ text: t('Show only my nominations'), value: 'iNominated' },
|
||||
{ text: t('Show only with nominators'), value: 'hasNominators' },
|
||||
{ text: t('Show only without nominators'), value: 'noNominators' },
|
||||
{ text: t('Show only with warnings'), value: 'hasWarnings' },
|
||||
{ text: t('Show only without warnings'), value: 'noWarnings' }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { currentValidatorsControllersV1OrStashesV2, next, t } = this.props;
|
||||
const { filter, filterOptions } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<FilterOverlay>
|
||||
<Dropdown
|
||||
onChange={this.onChangeFilter}
|
||||
options={filterOptions}
|
||||
value={filter}
|
||||
withLabel={false}
|
||||
/>
|
||||
</FilterOverlay>
|
||||
<Columar className='validator--ValidatorsList'>
|
||||
<Column
|
||||
emptyText={t('No addresses found')}
|
||||
headerText={t('validators')}
|
||||
>
|
||||
{this.renderColumn(currentValidatorsControllersV1OrStashesV2, t('validator'))}
|
||||
</Column>
|
||||
<Column
|
||||
emptyText={t('No addresses found')}
|
||||
headerText={t('next up')}
|
||||
>
|
||||
{this.renderColumn(next, t('intention'))}
|
||||
</Column>
|
||||
</Columar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderColumn (addresses: string[], defaultName: string): React.ReactNode {
|
||||
const { lastAuthor, lastBlock, recentlyOnline } = this.props;
|
||||
const { filter } = this.state;
|
||||
function CurrentList ({ currentValidators, lastAuthor, lastBlock, next, recentlyOnline, t }: Props): React.ReactElement<Props> {
|
||||
const [filter, setFilter] = useState<ValidatorFilter>('all');
|
||||
|
||||
const _renderColumn = (addresses: string[], defaultName: string): React.ReactNode => {
|
||||
return addresses.map((address): React.ReactNode => (
|
||||
<Address
|
||||
address={address}
|
||||
@ -92,11 +35,41 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
recentlyOnline={recentlyOnline}
|
||||
/>
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
private onChangeFilter = (filter: ValidatorFilter): void => {
|
||||
this.setState({ filter });
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<FilterOverlay>
|
||||
<Dropdown
|
||||
onChange={setFilter}
|
||||
options={[
|
||||
{ text: t('Show all validators and intentions'), value: 'all' },
|
||||
{ text: t('Show only my nominations'), value: 'iNominated' },
|
||||
{ text: t('Show only with nominators'), value: 'hasNominators' },
|
||||
{ text: t('Show only without nominators'), value: 'noNominators' },
|
||||
{ text: t('Show only with warnings'), value: 'hasWarnings' },
|
||||
{ text: t('Show only without warnings'), value: 'noWarnings' }
|
||||
]}
|
||||
value={filter}
|
||||
withLabel={false}
|
||||
/>
|
||||
</FilterOverlay>
|
||||
<Columar className='validator--ValidatorsList'>
|
||||
<Column
|
||||
emptyText={t('No addresses found')}
|
||||
headerText={t('validators')}
|
||||
>
|
||||
{_renderColumn(currentValidators, t('validator'))}
|
||||
</Column>
|
||||
<Column
|
||||
emptyText={t('No addresses found')}
|
||||
headerText={t('next up')}
|
||||
>
|
||||
{_renderColumn(next, t('intention'))}
|
||||
</Column>
|
||||
</Columar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(CurrentList);
|
||||
|
||||
@ -17,7 +17,7 @@ import translate from '../translate';
|
||||
interface Props extends I18nProps {
|
||||
allControllers: string[];
|
||||
className?: string;
|
||||
currentValidatorsControllersV1OrStashesV2: string[];
|
||||
currentValidators: string[];
|
||||
lastAuthor?: string;
|
||||
lastBlock: string;
|
||||
staking_validatorCount?: BN;
|
||||
@ -25,7 +25,7 @@ interface Props extends I18nProps {
|
||||
}
|
||||
|
||||
function Summary (props: Props): React.ReactElement<Props> {
|
||||
const { className, currentValidatorsControllersV1OrStashesV2, lastAuthor, lastBlock, next, style, staking_validatorCount, t } = props;
|
||||
const { className, currentValidators, lastAuthor, lastBlock, next, style, staking_validatorCount, t } = props;
|
||||
|
||||
return (
|
||||
<SummaryBox
|
||||
@ -34,7 +34,7 @@ function Summary (props: Props): React.ReactElement<Props> {
|
||||
>
|
||||
<section>
|
||||
<CardSummary label={t('validators')}>
|
||||
{currentValidatorsControllersV1OrStashesV2.length}/{staking_validatorCount ? staking_validatorCount.toString() : '-'}
|
||||
{currentValidators.length}/{staking_validatorCount ? staking_validatorCount.toString() : '-'}
|
||||
</CardSummary>
|
||||
<CardSummary label={t('waiting')}>
|
||||
{next.length}
|
||||
|
||||
@ -22,18 +22,18 @@ interface Props extends BareProps, ComponentProps {
|
||||
// TODO: Switch to useState
|
||||
function Overview (props: Props): React.ReactElement<Props> {
|
||||
const { isSubstrateV2 } = useContext(ApiContext);
|
||||
const { chain_subscribeNewHeads, allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOnline } = props;
|
||||
const { chain_subscribeNewHeads, allControllers, allStashes, currentValidators, recentlyOnline } = props;
|
||||
let nextSorted: string[];
|
||||
|
||||
if (isSubstrateV2) {
|
||||
// this is a V2 node currentValidatorsControllersV1OrStashesV2 is a list of stashes
|
||||
// this is a V2 node currentValidators is a list of stashes
|
||||
nextSorted = allStashes.filter((address): boolean =>
|
||||
!currentValidatorsControllersV1OrStashesV2.includes(address)
|
||||
!currentValidators.includes(address)
|
||||
);
|
||||
} else {
|
||||
// this is a V1 node currentValidatorsControllersV1OrStashesV2 is a list of controllers
|
||||
// this is a V1 node currentValidators is a list of controllers
|
||||
nextSorted = allControllers.filter((address): boolean =>
|
||||
!currentValidatorsControllersV1OrStashesV2.includes(address)
|
||||
!currentValidators.includes(address)
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,13 +49,13 @@ function Overview (props: Props): React.ReactElement<Props> {
|
||||
<div className='staking--Overview'>
|
||||
<Summary
|
||||
allControllers={allControllers}
|
||||
currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
|
||||
currentValidators={currentValidators}
|
||||
lastBlock={lastBlock}
|
||||
lastAuthor={lastAuthor}
|
||||
next={nextSorted}
|
||||
/>
|
||||
<CurrentList
|
||||
currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
|
||||
currentValidators={currentValidators}
|
||||
lastBlock={lastBlock}
|
||||
lastAuthor={lastAuthor}
|
||||
next={nextSorted}
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the Apache-2.0 license. See the LICENSE file for details. */
|
||||
|
||||
.staking--App .rx--updated {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.staking--Account-links {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
@ -10,8 +9,9 @@ import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { createType, Option } from '@polkadot/types';
|
||||
import { HelpOverlay } from '@polkadot/react-components';
|
||||
import Tabs from '@polkadot/react-components/Tabs';
|
||||
@ -28,7 +28,7 @@ import translate from './translate';
|
||||
interface Props extends AppProps, ApiProps, I18nProps {
|
||||
allAccounts?: SubjectInfo;
|
||||
allStashesAndControllers?: [AccountId[], Option<AccountId>[]];
|
||||
chain_bestNumber?: BlockNumber;
|
||||
bestNumber?: BlockNumber;
|
||||
currentValidatorsControllersV1OrStashesV2?: AccountId[];
|
||||
recentlyOnline?: AuthorityId[];
|
||||
}
|
||||
@ -36,84 +36,45 @@ interface Props extends AppProps, ApiProps, I18nProps {
|
||||
interface State {
|
||||
allControllers: string[];
|
||||
allStashes: string[];
|
||||
currentValidatorsControllersV1OrStashesV2: string[];
|
||||
currentValidators: string[];
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
}
|
||||
|
||||
class App extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
function App ({ allAccounts, allStashesAndControllers = [[], []], bestNumber, className, currentValidatorsControllersV1OrStashesV2 = [], basePath, recentlyOnline: propsRecentlyOnline, t }: Props): React.ReactElement<Props> {
|
||||
const [{ allControllers, allStashes, currentValidators, recentlyOnline }, setState] = useState<State>({
|
||||
allControllers: [],
|
||||
allStashes: [],
|
||||
currentValidatorsControllersV1OrStashesV2: [],
|
||||
currentValidators: [],
|
||||
recentlyOnline: {}
|
||||
};
|
||||
});
|
||||
|
||||
public static getDerivedStateFromProps (props: Props, state: State): Pick<State, never> {
|
||||
const { allStashesAndControllers = [[], []], chain_bestNumber, currentValidatorsControllersV1OrStashesV2 = [] } = props;
|
||||
|
||||
const recentlyOnline: Record<string, BlockNumber> = {
|
||||
...(state.recentlyOnline || {}),
|
||||
...(props.recentlyOnline || []).reduce(
|
||||
(result: Record<string, BlockNumber>, authorityId): Record<string, BlockNumber> => ({
|
||||
...result,
|
||||
[authorityId.toString()]: chain_bestNumber || createType('BlockNumber', new BN(0))
|
||||
}),
|
||||
{}
|
||||
)
|
||||
};
|
||||
|
||||
return {
|
||||
allControllers: allStashesAndControllers[1].filter((optId): boolean => optId.isSome).map((accountId): string =>
|
||||
accountId.unwrap().toString()
|
||||
),
|
||||
allStashes: allStashesAndControllers[0].filter((): boolean => true).map((accountId): string => accountId.toString()),
|
||||
currentValidatorsControllersV1OrStashesV2: currentValidatorsControllersV1OrStashesV2.map((authorityId): string =>
|
||||
useEffect((): void => {
|
||||
setState({
|
||||
allControllers: allStashesAndControllers[1]
|
||||
.filter((optId): boolean => optId.isSome)
|
||||
.map((accountId): string => accountId.unwrap().toString()),
|
||||
allStashes: allStashesAndControllers[0]
|
||||
.filter((): boolean => true)
|
||||
.map((accountId): string => accountId.toString()),
|
||||
currentValidators: currentValidatorsControllersV1OrStashesV2.map((authorityId): string =>
|
||||
authorityId.toString()
|
||||
),
|
||||
recentlyOnline
|
||||
};
|
||||
}
|
||||
recentlyOnline: {
|
||||
...(recentlyOnline || {}),
|
||||
...(propsRecentlyOnline || []).reduce(
|
||||
(result: Record<string, BlockNumber>, authorityId): Record<string, BlockNumber> => ({
|
||||
...result,
|
||||
[authorityId.toString()]: bestNumber || createType('BlockNumber', new BN(0))
|
||||
}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
});
|
||||
}, [allStashesAndControllers, bestNumber, currentValidatorsControllersV1OrStashesV2]);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { allAccounts, basePath, t } = this.props;
|
||||
|
||||
return (
|
||||
<main className='staking--App'>
|
||||
<HelpOverlay md={basicMd} />
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
hidden={
|
||||
!allAccounts || Object.keys(allAccounts).length === 0
|
||||
? ['actions']
|
||||
: []
|
||||
}
|
||||
items={[
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t('Staking overview')
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
text: t('Account actions')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/actions`} render={this.renderComponent(Accounts)} />
|
||||
<Route render={this.renderComponent(Overview)} />
|
||||
</Switch>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
|
||||
const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
|
||||
// eslint-disable-next-line react/display-name
|
||||
return (): React.ReactNode => {
|
||||
const { allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOnline } = this.state;
|
||||
const { allAccounts } = this.props;
|
||||
|
||||
if (!allAccounts) {
|
||||
return null;
|
||||
}
|
||||
@ -123,35 +84,67 @@ class App extends React.PureComponent<Props, State> {
|
||||
allAccounts={allAccounts}
|
||||
allControllers={allControllers}
|
||||
allStashes={allStashes}
|
||||
currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
|
||||
currentValidators={currentValidators}
|
||||
recentlyOnline={recentlyOnline}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className={`staking--App ${className}`}>
|
||||
<HelpOverlay md={basicMd} />
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
hidden={
|
||||
!allAccounts || Object.keys(allAccounts).length === 0
|
||||
? ['actions']
|
||||
: []
|
||||
}
|
||||
items={[
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t('Staking overview')
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
text: t('Account actions')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/actions`} render={_renderComponent(Accounts)} />
|
||||
<Route render={_renderComponent(Overview)} />
|
||||
</Switch>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
App,
|
||||
styled(App)`
|
||||
.rx--updated {
|
||||
background: transparent !important;
|
||||
}
|
||||
`,
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
'derive.chain.bestNumber',
|
||||
['derive.chain.bestNumber', { propName: 'bestNumber' }],
|
||||
['derive.staking.controllers', { propName: 'allStashesAndControllers' }],
|
||||
['query.session.validators', { propName: 'currentValidatorsControllersV1OrStashesV2' }],
|
||||
[
|
||||
'query.system.events',
|
||||
{
|
||||
propName: 'recentlyOnline',
|
||||
transform: (value?: EventRecord[]): AuthorityId[] =>
|
||||
(value || [])
|
||||
.filter(({ event: { method, section } }): boolean =>
|
||||
section === 'imOnline' && method === 'HeartbeatReceived'
|
||||
)
|
||||
.map(
|
||||
({ event: { data: [authorityId] } }): AuthorityId => authorityId as AuthorityId
|
||||
)
|
||||
}
|
||||
]
|
||||
['query.system.events', {
|
||||
propName: 'recentlyOnline',
|
||||
transform: (value?: EventRecord[]): AuthorityId[] =>
|
||||
(value || [])
|
||||
.filter(({ event: { method, section } }): boolean =>
|
||||
section === 'imOnline' && method === 'HeartbeatReceived'
|
||||
)
|
||||
.map(({ event: { data: [authorityId] } }): AuthorityId =>
|
||||
authorityId as AuthorityId
|
||||
)
|
||||
}]
|
||||
),
|
||||
withObservable(accountObservable.subject, { propName: 'allAccounts' })
|
||||
);
|
||||
|
||||
@ -14,7 +14,7 @@ export interface ComponentProps {
|
||||
allAccounts?: SubjectInfo;
|
||||
allControllers: string[];
|
||||
allStashes: string[];
|
||||
currentValidatorsControllersV1OrStashesV2: string[];
|
||||
currentValidators: string[];
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { AddressMini, Icon, InputAddress, Labelled, TxButton } from '@polkadot/react-components';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
@ -13,110 +13,84 @@ import translate from './translate';
|
||||
|
||||
interface Props extends I18nProps, ComponentProps {}
|
||||
|
||||
interface State {
|
||||
selected?: string;
|
||||
}
|
||||
function SetKey ({ allAccounts, className, isMine, sudoKey, t }: Props): React.ReactElement<Props> {
|
||||
const [selected, setSelected] = useState<string | undefined>();
|
||||
|
||||
const SudoInputAddress = styled(InputAddress)`
|
||||
margin: -0.25rem 0.5rem -0.25rem 0;
|
||||
`;
|
||||
|
||||
const SudoLabelled = styled(Labelled)`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
class SetKey extends React.PureComponent<Props, State> {
|
||||
public state: State = {};
|
||||
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selected: props.sudoKey
|
||||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps ({ sudoKey }: Props, state: State): Pick<State, never> {
|
||||
if (sudoKey && (!state.selected || sudoKey !== state.selected)) {
|
||||
return { selected: sudoKey };
|
||||
useEffect((): void => {
|
||||
if (sudoKey && !selected) {
|
||||
setSelected(sudoKey);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}, [selected, sudoKey]);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, isMine, sudoKey, t } = this.props;
|
||||
const { selected } = this.state;
|
||||
const willLose = isMine &&
|
||||
!!Object.keys(allAccounts).length &&
|
||||
!!selected &&
|
||||
selected !== sudoKey &&
|
||||
!Object.keys(allAccounts).find((s): boolean => s === selected);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section className={`${className} ui--row`}>
|
||||
{isMine
|
||||
? (
|
||||
<>
|
||||
<SudoInputAddress
|
||||
value={selected}
|
||||
label={t('sudo key')}
|
||||
isInput={true}
|
||||
onChange={this.onChange}
|
||||
type='all'
|
||||
/>
|
||||
<TxButton
|
||||
accountId={sudoKey}
|
||||
isDisabled={!isMine || sudoKey === selected}
|
||||
isPrimary
|
||||
label={t('Reassign')}
|
||||
icon='sign-in'
|
||||
params={[selected]}
|
||||
tx='sudo.setKey'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<SudoLabelled
|
||||
className='ui--Dropdown'
|
||||
return (
|
||||
<section>
|
||||
<section className={`${className} ui--row`}>
|
||||
{isMine
|
||||
? (
|
||||
<>
|
||||
<InputAddress
|
||||
className='sudoInputAddress'
|
||||
value={selected}
|
||||
label={t('sudo key')}
|
||||
withLabel
|
||||
>
|
||||
<AddressMini value={sudoKey} />
|
||||
</SudoLabelled>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
{this.willLose() && (
|
||||
<article className='warning padded'>
|
||||
<div>
|
||||
<Icon name='warning' />
|
||||
{t('You will no longer have sudo access')}
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
isInput={true}
|
||||
onChange={setSelected}
|
||||
type='all'
|
||||
/>
|
||||
<TxButton
|
||||
accountId={sudoKey}
|
||||
isDisabled={!isMine || sudoKey === selected}
|
||||
isPrimary
|
||||
label={t('Reassign')}
|
||||
icon='sign-in'
|
||||
params={[selected]}
|
||||
tx='sudo.setKey'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<Labelled
|
||||
className='ui--Dropdown sudoLabelled'
|
||||
label={t('sudo key')}
|
||||
withLabel
|
||||
>
|
||||
<AddressMini value={sudoKey} />
|
||||
</Labelled>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
private onChange = (selected?: string): void => {
|
||||
this.setState({ selected });
|
||||
}
|
||||
|
||||
private willLose = (): boolean => {
|
||||
const { allAccounts, isMine, sudoKey } = this.props;
|
||||
const { selected } = this.state;
|
||||
|
||||
return (
|
||||
isMine &&
|
||||
!!Object.keys(allAccounts).length &&
|
||||
!!selected &&
|
||||
selected !== sudoKey &&
|
||||
!Object.keys(allAccounts).find((s): boolean => s === selected)
|
||||
);
|
||||
}
|
||||
{willLose && (
|
||||
<article className='warning padded'>
|
||||
<div>
|
||||
<Icon name='warning' />
|
||||
{t('You will no longer have sudo access')}
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(styled(SetKey as React.ComponentClass<Props, State>)`
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
export default translate(
|
||||
styled(SetKey)`
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
|
||||
.summary {
|
||||
text-align: center;
|
||||
}
|
||||
`);
|
||||
.summary {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sudoInputAddress {
|
||||
margin: -0.25rem 0.5rem -0.25rem 0;
|
||||
}
|
||||
|
||||
.sudoLabelled {
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@ -6,7 +6,7 @@ import { TreasuryProposal as TreasuryProposalType } from '@polkadot/types/interf
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Option } from '@polkadot/types';
|
||||
import { ActionItem, Icon, TreasuryProposal } from '@polkadot/react-components';
|
||||
@ -30,83 +30,51 @@ interface Props extends I18nProps {
|
||||
onRespond: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isApproveOpen: boolean;
|
||||
}
|
||||
function ProposalDisplay ({ allAccounts, isApproved, onPopulate, onRespond, proposal, proposalId, t }: Props): React.ReactElement<Props> | null {
|
||||
useEffect((): void => {
|
||||
onPopulate();
|
||||
}, [proposal]);
|
||||
|
||||
class ProposalDisplay extends React.PureComponent<Props, State> {
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
const { proposal, onPopulate } = props;
|
||||
if (proposal) {
|
||||
onPopulate();
|
||||
}
|
||||
if (!proposal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public componentDidUpdate ({ proposal }: Props): void {
|
||||
const { onPopulate } = this.props;
|
||||
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
|
||||
|
||||
if (this.props.proposal && !proposal) {
|
||||
onPopulate();
|
||||
}
|
||||
}
|
||||
|
||||
public state: State = {
|
||||
isApproveOpen: false
|
||||
};
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { proposal, proposalId } = this.props;
|
||||
|
||||
if (!proposal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionItem
|
||||
accessory={this.renderAccessory()}
|
||||
idNumber={proposalId}
|
||||
>
|
||||
<TreasuryProposal proposal={proposal} />
|
||||
</ActionItem>
|
||||
);
|
||||
}
|
||||
|
||||
private renderAccessory (): React.ReactNode {
|
||||
const { allAccounts, isApproved, onRespond, proposal, proposalId, t } = this.props;
|
||||
|
||||
if (isApproved) {
|
||||
return (
|
||||
<Approved>
|
||||
<Icon name='check' />
|
||||
{' '}
|
||||
{t('Approved')}
|
||||
</Approved>
|
||||
);
|
||||
}
|
||||
|
||||
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
|
||||
if (!hasAccounts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Approve
|
||||
proposalInfo={
|
||||
<>
|
||||
<h3>Proposal #{proposalId}</h3>
|
||||
<details>
|
||||
<TreasuryProposal proposal={proposal} />
|
||||
</details>
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
proposalId={proposalId}
|
||||
onSuccess={onRespond}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ActionItem
|
||||
accessory={
|
||||
isApproved
|
||||
? (
|
||||
<Approved>
|
||||
<Icon name='check' />
|
||||
{' '}
|
||||
{t('Approved')}
|
||||
</Approved>
|
||||
)
|
||||
: hasAccounts
|
||||
? (
|
||||
<Approve
|
||||
proposalInfo={
|
||||
<>
|
||||
<h3>Proposal #{proposalId}</h3>
|
||||
<details>
|
||||
<TreasuryProposal proposal={proposal} />
|
||||
</details>
|
||||
<br />
|
||||
</>
|
||||
}
|
||||
proposalId={proposalId}
|
||||
onSuccess={onRespond}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
idNumber={proposalId}
|
||||
>
|
||||
<TreasuryProposal proposal={proposal} />
|
||||
</ActionItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user