Browse Source

Adjust payout toggle with days (#3274)

* Adjust payout toggle with days

* Cleanups
Jaco Greeff 4 years ago
parent
commit
1b6d2e9794

+ 2 - 2
packages/page-council/src/Motions/Slashing.tsx

@@ -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 - 0
packages/page-staking/src/Payouts/PayToggle.tsx

@@ -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;
+    }
+  }
+`);

+ 59 - 37
packages/page-staking/src/Payouts/index.tsx

@@ -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

+ 0 - 11
packages/page-staking/src/useCounter.ts

@@ -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;
-}

+ 2 - 2
packages/react-components/src/Button/Button.tsx

@@ -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}

+ 1 - 0
packages/react-components/src/Button/types.ts

@@ -17,6 +17,7 @@ export interface ButtonProps {
   isDisabled?: boolean;
   isFull?: boolean;
   isIcon?: boolean;
+  isSelected?: boolean;
   label?: React.ReactNode;
   onClick?: Button$Callback;
   onMouseEnter?: Button$Callback;

+ 7 - 2
packages/react-components/src/styles/index.ts

@@ -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;

+ 3 - 3
packages/react-hooks/src/useOwnEraRewards.ts

@@ -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]);