diff --git a/packages/app-council/src/Overview/Candidate.tsx b/packages/app-council/src/Overview/Candidate.tsx index 08ccebb29b..37594e27ad 100644 --- a/packages/app-council/src/Overview/Candidate.tsx +++ b/packages/app-council/src/Overview/Candidate.tsx @@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types'; import { AccountId } from '@polkadot/types/interfaces'; import React from 'react'; -import { AddressCard } from '@polkadot/react-components'; +import { AddressCard, Badge, Icon } from '@polkadot/react-components'; import translate from '../translate'; import Voters from './Voters'; @@ -21,6 +21,14 @@ function Candidate ({ address, isRunnerUp, t, voters }: Props): React.ReactEleme return ( } + isTooltip + type='runnerup' + /> + )} value={address} withIndexOrAddress > diff --git a/packages/app-council/src/Overview/Member.tsx b/packages/app-council/src/Overview/Member.tsx index a4e19eb5f9..e75d822fd7 100644 --- a/packages/app-council/src/Overview/Member.tsx +++ b/packages/app-council/src/Overview/Member.tsx @@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types'; import { AccountId } from '@polkadot/types/interfaces'; import React from 'react'; -import { AddressCard } from '@polkadot/react-components'; +import { AddressCard, Badge, Icon } from '@polkadot/react-components'; import translate from '../translate'; import Voters from './Voters'; @@ -20,6 +20,14 @@ function Member ({ address, t, voters }: Props): React.ReactElement { return ( } + isTooltip + type='selected' + /> + } value={address} withIndexOrAddress > diff --git a/packages/app-council/src/Overview/Summary.tsx b/packages/app-council/src/Overview/Summary.tsx index b47cc67797..c9a63aec49 100644 --- a/packages/app-council/src/Overview/Summary.tsx +++ b/packages/app-council/src/Overview/Summary.tsx @@ -24,8 +24,11 @@ function Summary ({ bestNumber, electionsInfo: { members, candidateCount, desire {formatNumber(members.length)}/{formatNumber(desiredSeats)} + + {formatNumber(runnersUp.length)} + - {formatNumber(candidateCount.addn(runnersUp.length))} + {formatNumber(candidateCount)} {voteCount && ( diff --git a/packages/app-council/src/Overview/Vote.tsx b/packages/app-council/src/Overview/Vote.tsx index 6be0be43f6..4abe55b82e 100644 --- a/packages/app-council/src/Overview/Vote.tsx +++ b/packages/app-council/src/Overview/Vote.tsx @@ -31,6 +31,8 @@ interface State extends TxModalState { // voterPositions: DerivedVoterPositions; } +// const MAX_VOTES = 16; + // const AlreadyVoted = styled.article` // display: flex; // align-items: center; diff --git a/packages/app-staking/src/Actions/Account/Nominate.tsx b/packages/app-staking/src/Actions/Account/Nominate.tsx index fdbc81790a..f7669bf62a 100644 --- a/packages/app-staking/src/Actions/Account/Nominate.tsx +++ b/packages/app-staking/src/Actions/Account/Nominate.tsx @@ -5,14 +5,16 @@ import { I18nProps } from '@polkadot/react-components/types'; import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types'; -import React, { useState } from 'react'; -import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components'; +import React, { useEffect, useState } from 'react'; +import store from 'store'; +import styled from 'styled-components'; +import { AddressMini, Button, InputAddress, Modal, Toggle, TxButton } from '@polkadot/react-components'; +import { STORE_FAVS } from '../../constants'; import translate from '../../translate'; interface Props extends I18nProps { controllerId: string; - isOpen: boolean; nominees?: string[]; onClose: () => void; stashId: string; @@ -22,27 +24,52 @@ interface Props extends I18nProps { // We only allow a maximum of 16 nominees, negative to slice const MAX_NOMINEES = -16; -function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }: Props): React.ReactElement | null { - const [nominees, setNominees] = useState([]); +function Nominate ({ className, controllerId, nominees, onClose, stashId, stashOptions, t }: Props): React.ReactElement | null { + const [favorites] = useState(store.get(STORE_FAVS, [])); + const [next, setNext] = useState(); + const [{ options, shortlist }, setShortlist] = useState<{ options: KeyringSectionOption[]; shortlist: string[] }>({ options: [], shortlist: [] }); - if (!isOpen) { - return null; - } + useEffect((): void => { + if (!next && nominees) { + setNext(nominees); + } + }, [next, nominees]); + + useEffect((): void => { + if (nominees) { + const shortlist = [ + // ensure that the favorite is included in the list of stashes + ...favorites.filter((accountId): boolean => stashOptions.some(({ value }): boolean => value === accountId)), + // make sure the nominee is not in our favorites already + ...nominees.filter((accountId): boolean => !favorites.includes(accountId)) + ]; + + setShortlist({ + options: stashOptions.filter(({ value }): boolean => !shortlist.includes(value as string)), + shortlist + }); + } + }, [favorites, nominees, stashOptions]); const _onChangeNominees = (_nominees: string[]): void => { const newNominees = _nominees.slice(MAX_NOMINEES); if (JSON.stringify(newNominees) !== JSON.stringify(nominees)) { - setNominees(newNominees); + setNext(newNominees); } }; + const _onToggleNominee = (nominee: string): void => + setNext( + (next || []).includes(nominee) + ? (next || []).filter((accountId): boolean => accountId !== nominee) + : [...(next || []), nominee].slice(MAX_NOMINEES) + ); return ( {t('Nominate Validators')} @@ -67,11 +94,39 @@ function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }: isMultiple label={t('nominate the following addresses')} onChangeMulti={_onChangeNominees} - options={stashOptions} + options={options} placeholder={t('select accounts(s) nominate')} type='account' - value={nominees} + value={next || []} /> + {shortlist.length !== 0 && ( + + {shortlist.map((address): React.ReactNode => { + const isAye = next?.includes(address); + const _onChange = (): void => _onToggleNominee(address); + + return ( + + + + + + ); + })} + + )} @@ -84,10 +139,10 @@ function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }: { const { stashOptions } = this.props; const { controllerId, isNominateOpen, nominees, stashId } = this.state; - if (!stashId || !controllerId) { + if (!isNominateOpen || !stashId || !controllerId) { return null; } return ( !without.includes(accountId as any)) .sort((a, b): number => { - const aIdx = favorites.includes(a); - const bIdx = favorites.includes(b); + const isFavA = favorites.includes(a); + const isFavB = favorites.includes(b); - return aIdx === bIdx + return isFavA === isFavB ? 0 - : aIdx - ? -1 - : 1; + : (isFavA ? -1 : 1); }) - .map((accountId): AccountExtend => [ - accountId, - elected.includes(accountId), - favorites.includes(accountId), - noPoints - ? -1 - : elected.indexOf(accountId) - ]); + .map((accountId): AccountExtend => { + const electedIdx = elected.indexOf(accountId); + + return [ + accountId, + elected.includes(accountId), + favorites.includes(accountId), + electedIdx !== -1 + ? eraPoints?.individual[electedIdx] + : undefined + ]; + }); } function accountsToString (accounts: AccountId[]): string[] { @@ -66,13 +67,13 @@ function CurrentList ({ authorsMap, lastAuthors, next, recentlyOnline, stakingOv if (stakingOverview) { const _elected = accountsToString(stakingOverview.currentElected); const _validators = accountsToString(stakingOverview.validators); - const validators = filterAccounts(_validators, _elected, favorites, []); - const elected = isSubstrateV2 ? filterAccounts(_elected, _elected, favorites, _validators, true) : []; + const validators = filterAccounts(_validators, _elected, favorites, [], stakingOverview.eraPoints); + const elected = isSubstrateV2 ? filterAccounts(_elected, _elected, favorites, _validators) : []; setFiltered({ elected, validators, - waiting: filterAccounts(next, [], favorites, _elected, true) + waiting: filterAccounts(next, [], favorites, _elected) }); } }, [favorites, next, stakingOverview]); @@ -88,7 +89,7 @@ function CurrentList ({ authorsMap, lastAuthors, next, recentlyOnline, stakingOv ); const _renderColumn = (addresses: AccountExtend[], defaultName: string, withOnline: boolean): React.ReactNode => - addresses.map(([address, isElected, isFavorite, pointIndex]): React.ReactNode => ( + addresses.map(([address, isElected, isFavorite, points]): React.ReactNode => (