diff --git a/packages/app-staking/src/Overview/Address.tsx b/packages/app-staking/src/Overview/Address.tsx index 1297cf1ec2..530c66a4fc 100644 --- a/packages/app-staking/src/Overview/Address.tsx +++ b/packages/app-staking/src/Overview/Address.tsx @@ -31,6 +31,7 @@ interface Props { lastBlock?: string; myAccounts: string[]; points?: Points; + setNominators?: (nominators: string[]) => void; toggleFavorite: (accountId: string) => void; withNominations?: boolean; } @@ -113,7 +114,7 @@ function checkVisibility (api: ApiPromise, address: string, filterName: string, return isVisible; } -export default function Address ({ address, className, filter, filterName, hasQueries, heartbeat, isAuthor, isElected, isFavorite, lastBlock, myAccounts, points, toggleFavorite, withNominations }: Props): React.ReactElement | null { +export default function Address ({ address, className, filter, filterName, hasQueries, heartbeat, isAuthor, isElected, isFavorite, lastBlock, myAccounts, points, setNominators, toggleFavorite, withNominations }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); // FIXME Any horrors, caused by derive type mismatches @@ -124,9 +125,12 @@ export default function Address ({ address, className, filter, filterName, hasQu const [isVisible, setIsVisible] = useState(true); useEffect((): void => { - stakingInfo && setStakingState( - expandInfo(stakingInfo, myAccounts, withNominations) - ); + if (stakingInfo) { + const info = expandInfo(stakingInfo, myAccounts, withNominations); + + setNominators && setNominators(info.nominators.map(([who]): string => who.toString())); + setStakingState(info); + } }, [stakingInfo]); useEffect((): void => { diff --git a/packages/app-staking/src/Overview/CurrentList.tsx b/packages/app-staking/src/Overview/CurrentList.tsx index 07c8924528..2e149cba4f 100644 --- a/packages/app-staking/src/Overview/CurrentList.tsx +++ b/packages/app-staking/src/Overview/CurrentList.tsx @@ -23,6 +23,7 @@ interface Props { lastAuthors?: string[]; next: string[]; recentlyOnline?: DerivedHeartbeats; + setNominators: (nominators: string[]) => void; stakingOverview?: DerivedStakingOverview; } @@ -64,7 +65,7 @@ function reduceDetails (state: Record, _details: Address }, { ...state }); } -export default function CurrentList ({ authorsMap, hasQueries, isIntentions, isVisible, lastAuthors, next, recentlyOnline, stakingOverview }: Props): React.ReactElement | null { +export default function CurrentList ({ authorsMap, hasQueries, isIntentions, isVisible, lastAuthors, next, recentlyOnline, setNominators, stakingOverview }: Props): React.ReactElement | null { const { t } = useTranslation(); const { allAccounts } = useAccounts(); const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE); @@ -137,6 +138,7 @@ export default function CurrentList ({ authorsMap, hasQueries, isIntentions, isV ? addressDetails[address] && addressDetails[address].points : undefined } + setNominators={isIntentions ? undefined : setNominators} toggleFavorite={toggleFavorite} /> )); diff --git a/packages/app-staking/src/Overview/Summary.tsx b/packages/app-staking/src/Overview/Summary.tsx index d6c937189b..3908a85b26 100644 --- a/packages/app-staking/src/Overview/Summary.tsx +++ b/packages/app-staking/src/Overview/Summary.tsx @@ -16,11 +16,12 @@ interface Props { className?: string; isVisible: boolean; next: string[]; + nominators: string[]; stakingOverview?: DerivedStakingOverview; style?: any; } -function Summary ({ className, isVisible, next, stakingOverview, style }: Props): React.ReactElement { +function Summary ({ className, isVisible, next, nominators, stakingOverview, style }: Props): React.ReactElement { const { t } = useTranslation(); const { lastBlockAuthors, lastBlockNumber } = useContext(BlockAuthorsContext); @@ -35,11 +36,16 @@ function Summary ({ className, isVisible, next, stakingOverview, style }: Props) {stakingOverview.validators.length}{`/${stakingOverview.validatorCount.toString()}`} )} - {next && ( + {next && next.length !== 0 && ( {next.length} )} + {nominators.length !== 0 && ( + + {nominators.length} + + )}
void; stakingOverview?: DerivedStakingOverview; } -export default function Overview ({ hasQueries, isVisible, className, recentlyOnline, next, stakingOverview }: Props): React.ReactElement { +export default function Overview ({ hasQueries, isVisible, className, recentlyOnline, next, setNominators, stakingOverview }: Props): React.ReactElement { const { pathname } = useLocation(); const { byAuthor, lastBlockAuthors } = useContext(BlockAuthorsContext); const isIntentions = pathname !== '/staking'; @@ -34,6 +35,7 @@ export default function Overview ({ hasQueries, isVisible, className, recentlyOn lastAuthors={lastBlockAuthors} next={next} recentlyOnline={recentlyOnline} + setNominators={setNominators} stakingOverview={stakingOverview} /> diff --git a/packages/app-staking/src/Targets/Summary.tsx b/packages/app-staking/src/Targets/Summary.tsx index 6ee1b61d8a..3f17d2bb39 100644 --- a/packages/app-staking/src/Targets/Summary.tsx +++ b/packages/app-staking/src/Targets/Summary.tsx @@ -14,6 +14,8 @@ import { useTranslation } from '../translate'; interface Props { lastReward: BN; + numNominators: number; + numValidators: number; totalStaked: BN; } @@ -21,7 +23,7 @@ interface StakeInfo { percentage: string; } -export default function Summary ({ lastReward, totalStaked }: Props): React.ReactElement { +export default function Summary ({ lastReward, numNominators, numValidators, totalStaked }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const totalInsurance = useCall(api.query.balances.totalIssuance, []); @@ -55,6 +57,9 @@ export default function Summary ({ lastReward, totalStaked }: Props): React.Reac {percentage} + + {numValidators}/{numNominators} + { - return isNominating || allAccounts.includes(indv.who.toString()); + const nominator = indv.who.toString(); + + if (!nominators.includes(nominator)) { + nominators.push(nominator); + } + + return isNominating || allAccounts.includes(nominator); }, allAccounts.includes(key)); totalStaked = totalStaked.add(bondTotal); @@ -145,7 +153,7 @@ function extractInfo (allAccounts: string[], amount: BN = new BN(0), electedInfo }) ); - return { totalStaked, validators }; + return { nominators, totalStaked, validators }; } function Targets ({ className, sessionRewards }: Props): React.ReactElement { @@ -156,7 +164,7 @@ function Targets ({ className, sessionRewards }: Props): React.ReactElement(api.derive.staking.electedInfo, []); const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE); const [lastReward, setLastReward] = useState(new BN(0)); - const [{ validators, totalStaked }, setWorkable] = useState({ totalStaked: new BN(0), validators: [] }); + const [{ nominators, validators, totalStaked }, setWorkable] = useState({ nominators: [], totalStaked: new BN(0), validators: [] }); const [{ sorted, sortBy, sortFromMax }, setSorted] = useState<{ sorted: ValidatorInfo[]; sortBy: SortBy; sortFromMax: boolean }>({ sorted: [], sortBy: 'rankOverall', sortFromMax: true }); const amount = useDebounce(_amount); @@ -196,9 +204,9 @@ function Targets ({ className, sessionRewards }: Props): React.ReactElement { if (electedInfo) { - const { totalStaked, validators } = extractInfo(allAccounts, amount, electedInfo, favorites, lastReward); + const { nominators, totalStaked, validators } = extractInfo(allAccounts, amount, electedInfo, favorites, lastReward); - setWorkable({ totalStaked, validators }); + setWorkable({ nominators, totalStaked, validators }); _sort('rankOverall', validators, false); } }, [allAccounts, amount, electedInfo, favorites, lastReward]); @@ -207,6 +215,8 @@ function Targets ({ className, sessionRewards }: Props): React.ReactElement {sorted.length diff --git a/packages/app-staking/src/index.tsx b/packages/app-staking/src/index.tsx index b440c25d6a..2158cdbc24 100644 --- a/packages/app-staking/src/index.tsx +++ b/packages/app-staking/src/index.tsx @@ -6,7 +6,7 @@ import { DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/ import { AppProps as Props } from '@polkadot/react-components/types'; import { AccountId } from '@polkadot/types/interfaces'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useReducer, useState } from 'react'; import { Route, Switch } from 'react-router'; import { useLocation } from 'react-router-dom'; import styled from 'styled-components'; @@ -24,6 +24,10 @@ import { MAX_SESSIONS } from './constants'; import { useTranslation } from './translate'; import useSessionRewards from './useSessionRewards'; +function reduceNominators (nominators: string[], additional: string[]): string[] { + return nominators.concat(...additional.filter((nominator): boolean => !nominators.includes(nominator))); +} + function StakingApp ({ basePath, className }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); @@ -39,6 +43,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement const stakingOverview = useCall(api.derive.staking.overview, []); const sessionRewards = useSessionRewards(MAX_SESSIONS); const hasQueries = hasAccounts && !!(api.query.imOnline?.authoredBlocks); + const [nominators, dispatchNominators] = useReducer(reduceNominators, [] as string[]); const items = useMemo(() => [ { isRoot: true, @@ -89,6 +94,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement @@ -111,6 +117,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement isVisible={[basePath, `${basePath}/waiting`].includes(pathname)} recentlyOnline={recentlyOnline} next={next} + setNominators={dispatchNominators} stakingOverview={stakingOverview} />