Nominate multiples (#899)
* Nominate multiples * Swap columns to stashes, pass through targets to nominate * Nominate Validator -> Nominate Validators
This commit is contained in:
parent
e5474be46a
commit
dc4a9318c7
@ -3,10 +3,10 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Input, InputAddress, Modal, TxButton } from '@polkadot/ui-app';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { Button, InputAddress, Modal, TxButton } from '@polkadot/ui-app';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
@ -15,19 +15,16 @@ type Props = I18nProps & {
|
||||
isOpen: boolean,
|
||||
onClose: () => void,
|
||||
intentions: Array<string>,
|
||||
stashId: string
|
||||
stashId: string,
|
||||
targets: Array<KeyringSectionOption>
|
||||
};
|
||||
|
||||
type State = {
|
||||
isNomineeValid: boolean,
|
||||
isAddressFormatValid: boolean,
|
||||
nominees: Array<string>
|
||||
};
|
||||
|
||||
class Nominating extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
isNomineeValid: false,
|
||||
isAddressFormatValid: false,
|
||||
nominees: []
|
||||
};
|
||||
|
||||
@ -53,7 +50,7 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
|
||||
renderButtons () {
|
||||
const { accountId, onClose, t } = this.props;
|
||||
const { isNomineeValid, nominees } = this.state;
|
||||
const { nominees } = this.state;
|
||||
|
||||
return (
|
||||
<Modal.Actions>
|
||||
@ -66,7 +63,7 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isDisabled={!isNomineeValid || !nominees.length}
|
||||
isDisabled={nominees.length === 0}
|
||||
isPrimary
|
||||
onClick={onClose}
|
||||
params={[nominees]}
|
||||
@ -79,13 +76,12 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const { accountId, stashId, t } = this.props;
|
||||
const { isNomineeValid, nominees } = this.state;
|
||||
const { accountId, stashId, t, targets } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Nominate Validator')}
|
||||
{t('Nominate Validators')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
@ -100,63 +96,23 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
isDisabled
|
||||
label={t('stash account')}
|
||||
/>
|
||||
<Input
|
||||
autoFocus
|
||||
<InputAddress
|
||||
className='medium'
|
||||
isError={!isNomineeValid}
|
||||
label={t('nominate the following address (validator or intention)')}
|
||||
onChange={this.onChangeNominee}
|
||||
value={nominees[0]}
|
||||
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={targets}
|
||||
placeholder={t('select accounts(s) nominate')}
|
||||
type='account'
|
||||
/>
|
||||
{this.renderErrors()}
|
||||
</Modal.Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderErrors () {
|
||||
const { t } = this.props;
|
||||
const { isNomineeValid, isAddressFormatValid } = this.state;
|
||||
const hasError = !isNomineeValid || !isAddressFormatValid;
|
||||
|
||||
if (!hasError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<article className='error'>
|
||||
{
|
||||
!isNomineeValid && isAddressFormatValid
|
||||
? t('The address you input is not intending to stake, and is therefore invalid. Please try again with a validator address.')
|
||||
: null
|
||||
}
|
||||
{
|
||||
!isAddressFormatValid
|
||||
? t('The address does not conform to a recognized address format. Please make sure you enter a valid address.')
|
||||
: null
|
||||
}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
private onChangeNominee = (nominee: string) => {
|
||||
// const { intentions } = this.props;
|
||||
|
||||
let isAddressFormatValid = false;
|
||||
|
||||
try {
|
||||
keyring.decodeAddress(nominee);
|
||||
|
||||
isAddressFormatValid = true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isNomineeValid: isAddressFormatValid, // intentions.includes(nominee),
|
||||
isAddressFormatValid,
|
||||
nominees: [nominee]
|
||||
});
|
||||
private onChangeNominees = (nominees: Array<string>) => {
|
||||
this.setState({ nominees });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import { DerivedBalancesMap } from '@polkadot/api-derive/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { ApiProps } from '@polkadot/ui-api/types';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
import { Nominators } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
@ -22,15 +23,16 @@ type Props = ApiProps & I18nProps & {
|
||||
accountId: string,
|
||||
balances: DerivedBalancesMap,
|
||||
balanceArray: (_address: AccountId | string) => Array<Balance> | undefined,
|
||||
intentions: Array<string>,
|
||||
isValidator: boolean,
|
||||
name: string,
|
||||
nominators: Nominators,
|
||||
session_nextKeyFor?: Option<AccountId>,
|
||||
staking_bonded?: Option<AccountId>,
|
||||
staking_ledger?: Option<StakingLedger>,
|
||||
staking_stakers?: Exposure,
|
||||
staking_validators?: [ValidatorPrefs],
|
||||
intentions: Array<string>,
|
||||
nominators: Nominators,
|
||||
isValidator: boolean,
|
||||
targets: Array<KeyringSectionOption>,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
@ -57,10 +59,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
stashId: null
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ session_nextKeyFor, staking_bonded, staking_ledger }: Props): Partial<State> {
|
||||
|
||||
// console.error('staking_stakers', JSON.stringify(staking_stakers));
|
||||
|
||||
static getDerivedStateFromProps ({ session_nextKeyFor, staking_bonded, staking_ledger }: Props, state: State): Partial<State> {
|
||||
return {
|
||||
bondedId: staking_bonded && staking_bonded.isSome
|
||||
? staking_bonded.unwrap().toString()
|
||||
@ -256,7 +255,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderNominating () {
|
||||
const { accountId, intentions } = this.props;
|
||||
const { accountId, intentions, targets } = this.props;
|
||||
const { isNominateOpen, stashId } = this.state;
|
||||
|
||||
if (!stashId) {
|
||||
@ -270,6 +269,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
onClose={this.toggleNominate}
|
||||
intentions={intentions}
|
||||
stashId={stashId}
|
||||
targets={targets}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -29,23 +29,40 @@ type Props = I18nProps & {
|
||||
};
|
||||
|
||||
type State = {
|
||||
stashId: string | null,
|
||||
badgeExpanded: boolean;
|
||||
};
|
||||
|
||||
class Address extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
stashId: null,
|
||||
badgeExpanded: false
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ staking_ledger }: Props): State | null {
|
||||
if (!staking_ledger || staking_ledger.isNone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
stashId: staking_ledger.unwrap().stash.toString()
|
||||
} as State;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { address, balanceArray, isAuthor, lastBlock } = this.props;
|
||||
const { balanceArray, isAuthor, lastBlock } = this.props;
|
||||
const { stashId } = this.state;
|
||||
|
||||
if (!stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<article key={address}>
|
||||
<article key={stashId}>
|
||||
<AddressRow
|
||||
balance={balanceArray(address)}
|
||||
balance={balanceArray(stashId)}
|
||||
name={this.getDisplayName()}
|
||||
value={address}
|
||||
value={stashId}
|
||||
withCopy={false}
|
||||
withNonce={false}
|
||||
>
|
||||
@ -63,18 +80,23 @@ class Address extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private getDisplayName = (): string | undefined => {
|
||||
const { address, defaultName } = this.props;
|
||||
const { defaultName } = this.props;
|
||||
const { stashId } = this.state;
|
||||
|
||||
const pair = keyring.getAccount(address).isValid()
|
||||
? keyring.getAccount(address)
|
||||
: keyring.getAddress(address);
|
||||
if (!stashId) {
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
const pair = keyring.getAccount(stashId).isValid()
|
||||
? keyring.getAccount(stashId)
|
||||
: keyring.getAddress(stashId);
|
||||
|
||||
return pair.isValid()
|
||||
? pair.getMeta().name
|
||||
: defaultName;
|
||||
}
|
||||
|
||||
private onClickBadge = (): void => {
|
||||
private toggleBadge = (): void => {
|
||||
const { badgeExpanded } = this.state;
|
||||
|
||||
this.setState({ badgeExpanded: !badgeExpanded });
|
||||
@ -111,11 +133,8 @@ class Address extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderOffline () {
|
||||
const { recentlyOffline, staking_ledger, t } = this.props;
|
||||
const { badgeExpanded } = this.state;
|
||||
const stashId: string | null = staking_ledger && staking_ledger.isSome
|
||||
? staking_ledger.unwrap().stash.toString()
|
||||
: null;
|
||||
const { recentlyOffline, t } = this.props;
|
||||
const { badgeExpanded, stashId } = this.state;
|
||||
|
||||
if (!stashId || !recentlyOffline[stashId]) {
|
||||
return null;
|
||||
@ -128,7 +147,7 @@ class Address extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<div
|
||||
className={['recentlyOffline', badgeExpanded ? 'expand' : ''].join(' ')}
|
||||
onClick={this.onClickBadge}
|
||||
onClick={this.toggleBadge}
|
||||
>
|
||||
<div className='badge'>
|
||||
{count.toString()}
|
||||
|
||||
@ -76,7 +76,7 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
})}
|
||||
</h1>
|
||||
{this.renderColumn(current, t('validator'))}
|
||||
{this.renderColumn(current, t('validator (stash)'))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -87,7 +87,7 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<>
|
||||
<h1>{t('next up')}</h1>
|
||||
{this.renderColumn(next, t('intention'))}
|
||||
{this.renderColumn(next, t('intention (stash)'))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,9 +7,11 @@ import { ComponentProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import createOption from '@polkadot/ui-keyring/options/item';
|
||||
|
||||
import Account from './Account';
|
||||
import translate from './translate';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
type Props = I18nProps & ComponentProps;
|
||||
|
||||
@ -33,6 +35,7 @@ class StakeList extends React.PureComponent<Props> {
|
||||
key={address}
|
||||
name={name}
|
||||
nominators={nominators}
|
||||
targets={this.getTargetOptions()}
|
||||
validators={validators}
|
||||
/>
|
||||
);
|
||||
@ -40,6 +43,21 @@ class StakeList extends React.PureComponent<Props> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getTargetOptions (): Array<KeyringSectionOption> {
|
||||
const { targets } = this.props;
|
||||
|
||||
return targets.map((stashId) => {
|
||||
const pair = keyring.getAccount(stashId).isValid()
|
||||
? keyring.getAccount(stashId)
|
||||
: keyring.getAddress(stashId);
|
||||
const name = pair.isValid()
|
||||
? pair.getMeta().name
|
||||
: undefined;
|
||||
|
||||
return createOption(stashId, name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StakeList);
|
||||
|
||||
@ -33,6 +33,7 @@ type State = {
|
||||
intentions: Array<string>,
|
||||
nominators: Nominators,
|
||||
tabs: Array<TabItem>,
|
||||
targets: Array<string>,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
@ -57,13 +58,12 @@ class App extends React.PureComponent<Props, State> {
|
||||
text: t('Account Actions')
|
||||
}
|
||||
],
|
||||
targets: [],
|
||||
validators: []
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps ({ staking_controllers = [[], []], session_validators = [], staking_nominators = [[], []] }: Props): State {
|
||||
console.error(staking_controllers[0].map((id) => id.toString()));
|
||||
|
||||
return {
|
||||
intentions: staking_controllers[1].filter((optId) => optId.isSome).map((accountId) =>
|
||||
accountId.unwrap().toString()
|
||||
@ -75,6 +75,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
|
||||
return result;
|
||||
}, {} as Nominators),
|
||||
targets: staking_controllers[0].map((accountId) => accountId.toString()),
|
||||
validators: session_validators.map((authorityId) =>
|
||||
authorityId.toString()
|
||||
)
|
||||
@ -107,7 +108,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
|
||||
private renderComponent (Component: React.ComponentType<ComponentProps>) {
|
||||
return (): React.ReactNode => {
|
||||
const { intentions, nominators, validators } = this.state;
|
||||
const { intentions, nominators, targets, validators } = this.state;
|
||||
const { balances = {} } = this.props;
|
||||
|
||||
return (
|
||||
@ -116,6 +117,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
balanceArray={this.balanceArray}
|
||||
intentions={intentions}
|
||||
nominators={nominators}
|
||||
targets={targets}
|
||||
validators={validators}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -16,6 +16,7 @@ export type ComponentProps = {
|
||||
balanceArray: (_address: AccountId | string) => Array<Balance> | undefined,
|
||||
intentions: Array<string>,
|
||||
nominators: Nominators,
|
||||
targets: Array<string>,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
|
||||
@ -18,11 +18,13 @@ type Props<Option> = BareProps & {
|
||||
isButton?: boolean,
|
||||
isDisabled?: boolean,
|
||||
isError?: boolean,
|
||||
isMultiple?: boolean,
|
||||
label?: React.ReactNode,
|
||||
onChange?: (value: any) => void,
|
||||
onSearch?: (filteredOptions: Array<any>, query: string) => Array<Option>,
|
||||
options: Array<Option>,
|
||||
placeholder?: string,
|
||||
renderLabel?: (item: any) => any,
|
||||
transform?: (value: any) => any,
|
||||
value?: any,
|
||||
withLabel?: boolean
|
||||
@ -55,7 +57,7 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, defaultValue, help, isButton, isDisabled, isError, label, onSearch, options, placeholder, style, withLabel, value } = this.props;
|
||||
const { className, defaultValue, help, isButton, isDisabled, isError, isMultiple, label, onSearch, options, placeholder, renderLabel, style, withLabel, value } = this.props;
|
||||
const dropdown = (
|
||||
<SUIDropdown
|
||||
button={isButton}
|
||||
@ -63,9 +65,11 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
|
||||
disabled={isDisabled}
|
||||
error={isError}
|
||||
floating={isButton}
|
||||
multiple={isMultiple}
|
||||
onChange={this.onChange}
|
||||
options={options}
|
||||
placeholder={placeholder}
|
||||
renderLabel={renderLabel}
|
||||
search={onSearch}
|
||||
selection
|
||||
value={
|
||||
|
||||
@ -25,12 +25,15 @@ type Props = BareProps & {
|
||||
isDisabled?: boolean,
|
||||
isError?: boolean,
|
||||
isInput?: boolean,
|
||||
isMultiple?: boolean,
|
||||
label?: string,
|
||||
onChange?: (value: string | null) => void,
|
||||
onChangeMulti?: (value: Array<string>) => void,
|
||||
options?: Array<KeyringSectionOption>,
|
||||
optionsAll?: KeyringOptions,
|
||||
placeholder?: string,
|
||||
type?: KeyringOption$Type,
|
||||
value?: string | Uint8Array,
|
||||
value?: string | Uint8Array | Array<string>,
|
||||
withLabel?: boolean
|
||||
};
|
||||
|
||||
@ -81,7 +84,10 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
static getDerivedStateFromProps ({ value }: Props): State | null {
|
||||
try {
|
||||
return {
|
||||
value: addressToAddress(value) || undefined
|
||||
value: Array.isArray(value)
|
||||
? value.map(addressToAddress)
|
||||
: (addressToAddress(value) || undefined)
|
||||
|
||||
} as State;
|
||||
} catch (error) {
|
||||
return null;
|
||||
@ -106,9 +112,9 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, label, optionsAll, type = DEFAULT_TYPE, style, withLabel } = this.props;
|
||||
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, isMultiple, label, options, optionsAll, placeholder, type = DEFAULT_TYPE, style, withLabel } = this.props;
|
||||
const { value } = this.state;
|
||||
const hasOptions = optionsAll && Object.keys(optionsAll[type]).length !== 0;
|
||||
const hasOptions = (options && options.length !== 0) || (optionsAll && Object.keys(optionsAll[type]).length !== 0);
|
||||
|
||||
if (!hasOptions && !isDisabled) {
|
||||
return null;
|
||||
@ -128,28 +134,62 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
<Dropdown
|
||||
className={classes('ui--InputAddress', hideAddress ? 'flag--hideAddress' : '', className)}
|
||||
defaultValue={
|
||||
value !== undefined
|
||||
isMultiple || (value !== undefined)
|
||||
? undefined
|
||||
: actualValue
|
||||
}
|
||||
help={help}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
isMultiple={isMultiple}
|
||||
label={label}
|
||||
onChange={this.onChange}
|
||||
onChange={
|
||||
isMultiple
|
||||
? this.onChangeMulti
|
||||
: this.onChange
|
||||
}
|
||||
onSearch={this.onSearch}
|
||||
options={
|
||||
isDisabled && actualValue
|
||||
? [createOption(actualValue)]
|
||||
: (optionsAll ? optionsAll[type] : [])
|
||||
options
|
||||
? options
|
||||
: (
|
||||
isDisabled && actualValue
|
||||
? [createOption(actualValue)]
|
||||
: (optionsAll ? optionsAll[type] : [])
|
||||
)
|
||||
}
|
||||
placeholder={placeholder}
|
||||
renderLabel={
|
||||
isMultiple
|
||||
? this.renderLabel
|
||||
: undefined
|
||||
}
|
||||
style={style}
|
||||
value={value}
|
||||
value={
|
||||
isMultiple
|
||||
? undefined
|
||||
: value
|
||||
}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = ({ value }: KeyringSectionOption): string | null => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pair = keyring.getAccount(value).isValid()
|
||||
? keyring.getAccount(value)
|
||||
: keyring.getAddress(value);
|
||||
const name = pair.isValid()
|
||||
? pair.getMeta().name
|
||||
: undefined;
|
||||
|
||||
return name || `${value.slice(0, 6)}…${value.slice(-6)}`;
|
||||
}
|
||||
|
||||
private getLastOptionValue (): KeyringSectionOption | undefined {
|
||||
const { optionsAll, type = DEFAULT_TYPE } = this.props;
|
||||
|
||||
@ -186,6 +226,18 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
onChange && onChange(transformToAccountId(address));
|
||||
}
|
||||
|
||||
private onChangeMulti = (addresses: Array<string>) => {
|
||||
const { onChangeMulti } = this.props;
|
||||
|
||||
if (onChangeMulti) {
|
||||
onChangeMulti(
|
||||
addresses
|
||||
.map(transformToAccountId)
|
||||
.filter((address) => address) as Array<string>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private onSearch = (filteredOptions: KeyringSectionOptions, _query: string): KeyringSectionOptions => {
|
||||
const { isInput = true } = this.props;
|
||||
const query = _query.trim();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user