diff --git a/packages/page-staking/src/Actions/index.tsx b/packages/page-staking/src/Actions/index.tsx index 7a66a6085b..9e5eaf3d2e 100644 --- a/packages/page-staking/src/Actions/index.tsx +++ b/packages/page-staking/src/Actions/index.tsx @@ -2,13 +2,14 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { ActiveEraInfo, ElectionStatus, EraIndex } from '@polkadot/types/interfaces'; +import { ActiveEraInfo, EraIndex } from '@polkadot/types/interfaces'; import React, { useCallback, useEffect, useState } from 'react'; import { Table } from '@polkadot/react-components'; import { useCall, useApi, useOwnStashes } from '@polkadot/react-hooks'; import { Option } from '@polkadot/types'; +import ElectionBanner from '../ElectionBanner'; import { useTranslation } from '../translate'; import Account from './Account'; import NewStake from './NewStake'; @@ -16,19 +17,17 @@ import NewStake from './NewStake'; interface Props { allStashes?: string[]; className?: string; + isInElection?: boolean; next?: string[]; validators?: string[]; } -function Actions ({ allStashes, className, next, validators }: Props): React.ReactElement { +function Actions ({ allStashes, className, isInElection, next, validators }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const activeEra = useCall(api.query.staking?.activeEra, [], { transform: (activeEra: Option) => activeEra.unwrapOr({ index: undefined }).index }); - const isInElection = useCall(api.query.staking?.eraElectionStatus, [], { - transform: (status: ElectionStatus) => status.isOpen - }); const ownStashes = useOwnStashes(); const [foundStashes, setFoundStashes] = useState<[string, boolean][] | null>(null); const [stashTypes, setStashTypes] = useState>({}); @@ -57,11 +56,7 @@ function Actions ({ allStashes, className, next, validators }: Props): React.Rea return (
- {isInElection && ( -
- {t('There is currently an ongoing election for new validator candidates. As such staking operations are not permitted.')} -
- )} + | null { + const { t } = useTranslation(); + + if (!isInElection) { + return null; + } + + return ( +
+ {t('There is currently an ongoing election for new validator candidates. As such staking operations are not permitted.')} +
+ ); +} + +export default React.memo(ElectionBanner); diff --git a/packages/page-staking/src/Payouts/PayButton.tsx b/packages/page-staking/src/Payouts/PayButton.tsx index 73086a1f8d..4a9f1efb50 100644 --- a/packages/page-staking/src/Payouts/PayButton.tsx +++ b/packages/page-staking/src/Payouts/PayButton.tsx @@ -3,21 +3,44 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { PayoutValidator } from './types'; -import BN from 'bn.js'; import React, { useState, useEffect } from 'react'; -import { Button, Modal, InputAddress, TxButton } from '@polkadot/react-components'; +import { ApiPromise } from '@polkadot/api'; +import { AddressMini, Button, Modal, InputAddress, Static, TxButton } from '@polkadot/react-components'; import { useApi, useToggle } from '@polkadot/react-hooks'; import { useTranslation } from '../translate'; interface Props { - eras: BN[]; isInElection?: boolean; - validatorId: string; + payout?: PayoutValidator | PayoutValidator[]; } -function PayButton ({ eras, isInElection, validatorId }: Props): React.ReactElement { +function createExtrinsic (api: ApiPromise, payout: PayoutValidator | PayoutValidator[]): SubmittableExtrinsic<'promise'> { + if (Array.isArray(payout)) { + if (payout.length === 1) { + return createExtrinsic(api, payout[0]); + } + + return api.tx.utility.batch( + payout.reduce((calls: SubmittableExtrinsic<'promise'>[], { eras, validatorId }): SubmittableExtrinsic<'promise'>[] => + calls.concat( + ...eras.map(({ era }) => api.tx.staking.payoutStakers(validatorId, era)) + ), []) + ); + } + + const { eras, validatorId } = payout; + + return eras.length === 1 + ? api.tx.staking.payoutStakers(validatorId, eras[0].era) + : api.tx.utility.batch( + eras.map(({ era }) => api.tx.staking.payoutStakers(validatorId, era)) + ); +} + +function PayButton ({ isInElection, payout }: Props): React.ReactElement { const { api } = useApi(); const { t } = useTranslation(); const [isVisible, togglePayout] = useToggle(); @@ -25,29 +48,42 @@ function PayButton ({ eras, isInElection, validatorId }: Props): React.ReactElem const [extrinsic, setExtrinsic] = useState | null>(null); useEffect((): void => { - setExtrinsic( - () => eras.length === 1 - ? api.tx.staking.payoutStakers(validatorId, eras[0]) - : api.tx.utility.batch( - eras.map((era): SubmittableExtrinsic<'promise'> => - api.tx.staking.payoutStakers(validatorId, era) - ) - ) + payout && setExtrinsic( + () => createExtrinsic(api, payout) ); - }, [api, eras, validatorId]); + }, [api, payout]); + + const isPayoutEmpty = !payout || (Array.isArray(payout) && payout.length === 0); return ( <> - {isVisible && ( + {payout && isVisible && ( + {Array.isArray(payout) + ? ( + ( + + )) + } + /> + ) + : ( + + ) + } - diff --git a/packages/page-staking/src/Payouts/Validator.tsx b/packages/page-staking/src/Payouts/Validator.tsx index ff2a623f06..22cb66872e 100644 --- a/packages/page-staking/src/Payouts/Validator.tsx +++ b/packages/page-staking/src/Payouts/Validator.tsx @@ -21,23 +21,20 @@ interface Props { interface State { eraStr: string; - eras: BN[]; nominators: Record; numNominators: number; } function Payout ({ className, isInElection, payout }: Props): React.ReactElement { const { t } = useTranslation(); - const [{ eraStr, eras, nominators, numNominators }, setState] = useState({ + const [{ eraStr, nominators, numNominators }, setState] = useState({ eraStr: '', - eras: [], nominators: {}, numNominators: 0 }); useEffect((): void => { - const eras = payout.eras.map(({ era }) => era); - const eraStr = createErasString(eras); + const eraStr = createErasString(payout.eras.map(({ era }) => era)); const nominators = payout.eras.reduce((nominators: Record, { stashes }): Record => { Object.entries(stashes).forEach(([stashId, value]): void => { if (nominators[stashId]) { @@ -50,7 +47,7 @@ function Payout ({ className, isInElection, payout }: Props): React.ReactElement return nominators; }, {}); - setState({ eraStr, eras, nominators, numNominators: Object.keys(nominators).length }); + setState({ eraStr, nominators, numNominators: Object.keys(nominators).length }); }, [payout]); return ( @@ -82,9 +79,8 @@ function Payout ({ className, isInElection, payout }: Props): React.ReactElement diff --git a/packages/page-staking/src/Payouts/index.tsx b/packages/page-staking/src/Payouts/index.tsx index 4e9857b377..2e9eb5886c 100644 --- a/packages/page-staking/src/Payouts/index.tsx +++ b/packages/page-staking/src/Payouts/index.tsx @@ -8,11 +8,13 @@ import { PayoutStash, PayoutValidator } from './types'; import BN from 'bn.js'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { Table } from '@polkadot/react-components'; +import { Button, Table } from '@polkadot/react-components'; import { useApi, useOwnEraRewards } from '@polkadot/react-hooks'; +import ElectionBanner from '../ElectionBanner'; import { useTranslation } from '../translate'; import useStakerPayouts from './useStakerPayouts'; +import PayButton from './PayButton'; import Stash from './Stash'; import Validator from './Validator'; @@ -96,6 +98,15 @@ function Payouts ({ className, isInElection }: Props): React.ReactElement return (
+ {api.tx.staking.payoutStakers && ( + + + + )} +
stashes.map((accountId): string => accountId.toString()) }); const stakingOverview = useCall(api.derive.staking.overview, []); + const isInElection = useCall(api.query.staking?.eraElectionStatus, [], { + transform: (status: ElectionStatus) => status.isOpen + }); const [nominators, dispatchNominators] = useReducer(reduceNominators, [] as string[]); const hasQueries = useMemo( (): boolean => @@ -117,7 +120,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement - + @@ -135,6 +138,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement