polkadot-js/apps

View on GitHub
packages/page-staking-legacy/src/Slashes/Era.tsx

Summary

Maintainability
F
6 days
Test Coverage
// Copyright 2017-2024 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { SlashEra } from './types.js';

import React, { useCallback, useRef, useState } from 'react';

import { Button, Table, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance } from '@polkadot/react-hooks';
import { BN_ONE, isFunction } from '@polkadot/util';

import { useTranslation } from '../translate.js';
import Row from './Row.js';
import Summary from './Summary.js';

interface Props {
  buttons: React.ReactNode;
  councilId: string | null;
  councilThreshold: number;
  slash: SlashEra;
}

interface Proposal {
  length: number;
  proposal: SubmittableExtrinsic<'promise'>
}

interface Selected {
  selected: number[];
  txAll: Proposal | null;
  txSome: Proposal | null;
}

function Slashes ({ buttons, councilId, councilThreshold, slash }: Props): React.ReactElement<Props> | null {
  const { t } = useTranslation();
  const { api } = useApi();
  const councilMod = useCollectiveInstance('council');
  const [{ selected, txAll, txSome }, setSelected] = useState<Selected>((): Selected => {
    const proposal = api.tx.staking.cancelDeferredSlash(slash.era, slash.slashes.map((_, index) => index));

    return {
      selected: [],
      txAll: councilMod
        ? { length: proposal.encodedLength, proposal }
        : null,
      txSome: null
    };
  });

  const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
    [t('era {{era}}/unapplied', {
      replace: {
        era: api.query.staking.earliestUnappliedSlash || !api.consts.staking.slashDeferDuration
          ? slash.era.toString()
          : slash.era.sub(api.consts.staking.slashDeferDuration).sub(BN_ONE).toString()
      }
    }), 'start', 3],
    [t('reporters'), 'address'],
    [t('own')],
    [t('other')],
    [t('total')],
    [t('payout')],
    !api.query.staking.earliestUnappliedSlash && !!api.consts.staking.slashDeferDuration &&
      [t('apply')],
    []
  ]);

  const _onSelect = useCallback(
    (index: number) => setSelected((state): Selected => {
      const selected = state.selected.includes(index)
        ? state.selected.filter((i) => i !== index)
        : state.selected.concat(index).sort((a, b) => a - b);
      const proposal = selected.length
        ? api.tx.staking.cancelDeferredSlash(slash.era, selected)
        : null;

      return {
        selected,
        txAll: state.txAll,
        txSome: proposal && councilMod && isFunction(api.tx[councilMod].propose)
          ? { length: proposal.encodedLength, proposal }
          : null
      };
    }),
    [api, councilMod, slash]
  );

  return (
    <>
      <Summary slash={slash} />
      <Button.Group>
        {buttons}
        {councilMod && (
          <>
            <TxButton
              accountId={councilId}
              isDisabled={!txSome}
              isToplevel
              label={t('Cancel selected')}
              params={txSome && (
                api.tx[councilMod].propose.meta.args.length === 3
                  ? [councilThreshold, txSome.proposal, txSome.length]
                  : [councilThreshold, txSome.proposal]
              )}
              tx={api.tx[councilMod].propose}
            />
            <TxButton
              accountId={councilId}
              isDisabled={!txAll}
              isToplevel
              label={t('Cancel all')}
              params={txAll && (
                api.tx[councilMod].propose.meta.args.length === 3
                  ? [councilThreshold, txAll.proposal, txAll.length]
                  : [councilThreshold, txAll.proposal]
              )}
              tx={api.tx[councilMod].propose}
            />
          </>
        )}
      </Button.Group>
      <Table header={headerRef.current}>
        {slash.slashes.map((slash, index): React.ReactNode => (
          <Row
            index={index}
            isSelected={selected.includes(index)}
            key={index}
            onSelect={
              councilId
                ? _onSelect
                : undefined
            }
            slash={slash}
          />
        ))}
      </Table>
    </>
  );
}

export default React.memo(Slashes);