Adjust payout toggle with days (#3274)
* Adjust payout toggle with days * Cleanups
This commit is contained in:
parent
72f97fc908
commit
1b6d2e9794
@ -61,7 +61,7 @@ function Slashing ({ className = '', isMember, members }: Props): React.ReactEle
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon='times'
|
||||
icon='sync'
|
||||
isDisabled={!isMember || !slashes.length}
|
||||
label={t<string>('Cancel slashes')}
|
||||
onClick={toggleVisible}
|
||||
@ -117,7 +117,7 @@ function Slashing ({ className = '', isMember, members }: Props): React.ReactEle
|
||||
<Modal.Actions onCancel={toggleVisible}>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='repeat'
|
||||
icon='sync'
|
||||
isDisabled={!threshold || !members.includes(accountId || '') || !proposal}
|
||||
label={t<string>('Revert')}
|
||||
onStart={toggleVisible}
|
||||
|
||||
59
packages/page-staking/src/Payouts/PayToggle.tsx
Normal file
59
packages/page-staking/src/Payouts/PayToggle.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2017-2020 @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.
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button } from '@polkadot/react-components';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onChange: (index: number) => void;
|
||||
options: { text: string, value: number }[];
|
||||
selected: number;
|
||||
}
|
||||
|
||||
function PayToggle ({ className = '', onChange, options, selected }: Props): React.ReactElement<Props> | null {
|
||||
const _onClick = useCallback(
|
||||
(index: number) => () => onChange(index),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
if (!options.length || !options[0].value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`ui--ToggleButton ${className}`}>
|
||||
{options.map(({ text }, index): React.ReactNode => (
|
||||
<Button
|
||||
icon={selected === index ? 'check' : 'circle'}
|
||||
isBasic
|
||||
isSelected={selected === index}
|
||||
key={text}
|
||||
label={text}
|
||||
onClick={_onClick(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(PayToggle)`
|
||||
display: inline-block;
|
||||
margin-right: 1.5rem;
|
||||
|
||||
.ui--Button {
|
||||
margin: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -8,16 +8,16 @@ import { PayoutStash, PayoutValidator } from './types';
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Table, Toggle } from '@polkadot/react-components';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useOwnEraRewards } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { u32 } from '@polkadot/types';
|
||||
import { BN_ZERO, isFunction } from '@polkadot/util';
|
||||
|
||||
import ElectionBanner from '../ElectionBanner';
|
||||
import { useTranslation } from '../translate';
|
||||
import useStakerPayouts from './useStakerPayouts';
|
||||
import PayButton from './PayButton';
|
||||
import PayToggle from './PayToggle';
|
||||
import Stash from './Stash';
|
||||
import Validator from './Validator';
|
||||
|
||||
@ -33,6 +33,13 @@ interface Available {
|
||||
validators?: PayoutValidator[];
|
||||
}
|
||||
|
||||
interface EraSelection {
|
||||
value: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const DAY_SECS = new BN(1000 * 60 * 60 * 24);
|
||||
|
||||
function groupByValidator (allRewards: Record<string, DeriveStakerReward[]>, stakerPayoutsAfter: BN): PayoutValidator[] {
|
||||
return Object
|
||||
.entries(allRewards)
|
||||
@ -92,16 +99,48 @@ function extractStashes (allRewards: Record<string, DeriveStakerReward[]>): Payo
|
||||
}
|
||||
|
||||
function Payouts ({ className = '', isInElection }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [{ stashTotal, stashes, validators }, setPayouts] = useState<Available>({});
|
||||
const [isPartialEras, setIsPartialEras] = useState(true);
|
||||
const [partialEras, setPartialEras] = useState(21);
|
||||
const historyDepth = useCall<u32>(api.query.staking.historyDepth, []);
|
||||
const [eraSelection, setEraSelection] = useState<EraSelection[]>([{ text: '', value: 0 }]);
|
||||
const [eraSelectionIndex, setEraSelectionIndex] = useState(0);
|
||||
const eraLength = useCall<BN>(api.derive.session.eraLength, []);
|
||||
const historyDepth = useCall<BN>(api.query.staking.historyDepth, []);
|
||||
const stakerPayoutsAfter = useStakerPayouts();
|
||||
const { allRewards, isLoadingRewards } = useOwnEraRewards((!historyDepth || isPartialEras) ? partialEras : historyDepth.toNumber());
|
||||
const { t } = useTranslation();
|
||||
const { allRewards, isLoadingRewards } = useOwnEraRewards(eraSelection[eraSelectionIndex].value);
|
||||
const isDisabled = isInElection || !isFunction(api.tx.utility?.batch);
|
||||
|
||||
useEffect((): void => {
|
||||
if (eraLength && historyDepth) {
|
||||
const blocksPerDay = DAY_SECS.div(api.consts.babe?.expectedBlockTime || api.consts.timestamp?.minimumPeriod.muln(2) || new BN(6000));
|
||||
const maxBlocks = eraLength.mul(historyDepth);
|
||||
const eraSelection: EraSelection[] = [];
|
||||
let days = 2;
|
||||
|
||||
while (true) {
|
||||
const dayBlocks = blocksPerDay.muln(days);
|
||||
|
||||
if (dayBlocks.gte(maxBlocks)) {
|
||||
break;
|
||||
}
|
||||
|
||||
eraSelection.push({
|
||||
text: t<string>('{{days}} days', { replace: { days } }),
|
||||
value: dayBlocks.div(eraLength).toNumber()
|
||||
});
|
||||
|
||||
days = days * 3;
|
||||
}
|
||||
|
||||
eraSelection.push({
|
||||
text: t<string>('Max, {{eras}} eras', { replace: { eras: historyDepth.toNumber() } }),
|
||||
value: historyDepth.toNumber()
|
||||
});
|
||||
|
||||
setEraSelection(eraSelection);
|
||||
}
|
||||
}, [api, eraLength, historyDepth, t]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (allRewards) {
|
||||
const stashes = extractStashes(allRewards);
|
||||
@ -117,12 +156,6 @@ function Payouts ({ className = '', isInElection }: Props): React.ReactElement<P
|
||||
}
|
||||
}, [allRewards, stakerPayoutsAfter]);
|
||||
|
||||
useEffect((): void => {
|
||||
historyDepth && setPartialEras(
|
||||
Math.ceil(historyDepth.toNumber() / 4)
|
||||
);
|
||||
}, [historyDepth, isPartialEras]);
|
||||
|
||||
const headerStashes = useMemo(() => [
|
||||
[t('payout/stash'), 'start', 2],
|
||||
[t('eras'), 'start'],
|
||||
@ -152,34 +185,23 @@ function Payouts ({ className = '', isInElection }: Props): React.ReactElement<P
|
||||
return (
|
||||
<div className={className}>
|
||||
{api.tx.staking.payoutStakers && (
|
||||
<>
|
||||
<Button.Group>
|
||||
<PayButton
|
||||
isAll
|
||||
isDisabled={isDisabled}
|
||||
payout={validators}
|
||||
/>
|
||||
</Button.Group>
|
||||
{historyDepth && (
|
||||
<div className='staking--optionsBar'>
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={t<string>('only query most recent {{partialEras}} of {{historyDepth}} eras', {
|
||||
replace: { historyDepth: historyDepth.toNumber(), partialEras }
|
||||
})}
|
||||
onChange={setIsPartialEras}
|
||||
value={isPartialEras}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<Button.Group>
|
||||
<PayToggle
|
||||
onChange={setEraSelectionIndex}
|
||||
options={eraSelection}
|
||||
selected={eraSelectionIndex}
|
||||
/>
|
||||
<PayButton
|
||||
isAll
|
||||
isDisabled={isDisabled}
|
||||
payout={validators}
|
||||
/>
|
||||
</Button.Group>
|
||||
)}
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
<Table
|
||||
empty={!isLoadingRewards && stashes && t<string>('No pending payouts for your stashes')}
|
||||
emptySpinner={t<string>('Retrieving info for last {{numEras}} eras, this will take some time', {
|
||||
replace: { numEras: (!historyDepth || isPartialEras) ? partialEras : historyDepth.toNumber() }
|
||||
})}
|
||||
emptySpinner={t<string>('Retrieving info for the selected eras, this will take some time')}
|
||||
footer={footer}
|
||||
header={headerStashes}
|
||||
isFixed
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { useOwnEraRewards } from '@polkadot/react-hooks';
|
||||
|
||||
export default function useCounter (): number {
|
||||
const { rewardCount } = useOwnEraRewards();
|
||||
|
||||
return rewardCount;
|
||||
}
|
||||
@ -10,7 +10,7 @@ import styled from 'styled-components';
|
||||
import Icon from '../Icon';
|
||||
import Spinner from '../Spinner';
|
||||
|
||||
function Button ({ children, className = '', icon, isBasic, isBusy, isCircular, isDisabled, isFull, isIcon, label, onClick, onMouseEnter, onMouseLeave, tabIndex }: ButtonProps): React.ReactElement<ButtonProps> {
|
||||
function Button ({ children, className = '', icon, isBasic, isBusy, isCircular, isDisabled, isFull, isIcon, isSelected, label, onClick, onMouseEnter, onMouseLeave, tabIndex }: ButtonProps): React.ReactElement<ButtonProps> {
|
||||
const _onClick = useCallback(
|
||||
() => !(isBusy || isDisabled) && onClick && onClick(),
|
||||
[isBusy, isDisabled, onClick]
|
||||
@ -18,7 +18,7 @@ function Button ({ children, className = '', icon, isBasic, isBusy, isCircular,
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`ui--Button${label ? ' hasLabel' : ''}${isBasic ? ' isBasic' : ''}${isCircular ? ' isCircular' : ''}${isFull ? ' isFull' : ''}${isIcon ? ' isIcon' : ''}${(isBusy || isDisabled) ? ' isDisabled' : ''}${isBusy ? ' isBusy' : ''}${!onClick ? ' isReadOnly' : ''} ${className}`}
|
||||
className={`ui--Button${label ? ' hasLabel' : ''}${isBasic ? ' isBasic' : ''}${isCircular ? ' isCircular' : ''}${isFull ? ' isFull' : ''}${isIcon ? ' isIcon' : ''}${(isBusy || isDisabled) ? ' isDisabled' : ''}${isBusy ? ' isBusy' : ''}${!onClick ? ' isReadOnly' : ''}${isSelected ? ' isSelected' : ''} ${className}`}
|
||||
onClick={_onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
|
||||
@ -17,6 +17,7 @@ export interface ButtonProps {
|
||||
isDisabled?: boolean;
|
||||
isFull?: boolean;
|
||||
isIcon?: boolean;
|
||||
isSelected?: boolean;
|
||||
label?: React.ReactNode;
|
||||
onClick?: Button$Callback;
|
||||
onMouseEnter?: Button$Callback;
|
||||
|
||||
@ -69,7 +69,7 @@ export default createGlobalStyle<Props>`
|
||||
}
|
||||
}
|
||||
|
||||
&.isBasic:not(.isDisabled):not(.isIcon) {
|
||||
&.isBasic:not(.isDisabled):not(.isIcon):not(.isSelected) {
|
||||
&:not(.isReadOnly) {
|
||||
box-shadow: 0 0 1px ${getHighlight};
|
||||
}
|
||||
@ -79,7 +79,12 @@ export default createGlobalStyle<Props>`
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.isDisabled):not(.isReadOnly) {
|
||||
&.isSelected {
|
||||
box-shadow: 0 0 1px ${getHighlight};
|
||||
}
|
||||
|
||||
&:hover:not(.isDisabled):not(.isReadOnly),
|
||||
&.isSelected {
|
||||
background: ${getHighlight};
|
||||
color: #f5f5f4;
|
||||
text-shadow: none;
|
||||
|
||||
@ -32,13 +32,13 @@ function getRewards ([[stashIds], available]: [[string[]], DeriveStakerReward[][
|
||||
};
|
||||
}
|
||||
|
||||
export default function useOwnEraRewards (maxEras = 1000): OwnRewards {
|
||||
export default function useOwnEraRewards (maxEras?: number): OwnRewards {
|
||||
const { api } = useApi();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const stashIds = useOwnStashIds();
|
||||
const allEras = useCall<EraIndex[]>(api.derive.staking?.erasHistoric, []);
|
||||
const [filteredEras, setFilteredEras] = useState<EraIndex[]>([]);
|
||||
const available = useCall<[[string[]], DeriveStakerReward[][]]>((filteredEras?.length > 0) && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true });
|
||||
const available = useCall<[[string[]], DeriveStakerReward[][]]>(!!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true });
|
||||
const [state, setState] = useState<OwnRewards>({ isLoadingRewards: true, rewardCount: 0 });
|
||||
|
||||
useEffect((): void => {
|
||||
@ -52,7 +52,7 @@ export default function useOwnEraRewards (maxEras = 1000): OwnRewards {
|
||||
}, [available, mountedRef]);
|
||||
|
||||
useEffect((): void => {
|
||||
allEras && setFilteredEras(
|
||||
allEras && maxEras && setFilteredEras(
|
||||
allEras.reverse().filter((_, index) => index < maxEras).reverse()
|
||||
);
|
||||
}, [allEras, maxEras]);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user