useMemo for table headers/footers/filters (#2601)
* useMemo for table headers/footers/filters * contacts mishap
This commit is contained in:
parent
4939cbd75c
commit
284f7a667d
@ -7,7 +7,7 @@ import { ComponentProps as Props } from '../types';
|
||||
import { SortedAccount } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { getLedger, isLedger } from '@polkadot/react-api';
|
||||
@ -98,7 +98,7 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
|
||||
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
||||
const [{ balanceTotal }, setBalances] = useState<Balances>({ accounts: {} });
|
||||
const [sortedAccounts, setSortedAccounts] = useState<SortedAccount[]>([]);
|
||||
const [filter, setFilter] = useState<string>('');
|
||||
const [filterOn, setFilter] = useState<string>('');
|
||||
|
||||
useEffect((): void => {
|
||||
setSortedAccounts(
|
||||
@ -119,6 +119,38 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
|
||||
[]
|
||||
);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('accounts'), 'start', 3],
|
||||
[t('parent'), 'address'],
|
||||
[t('type')],
|
||||
[t('tags'), 'start'],
|
||||
[t('transactions')],
|
||||
[t('balances')],
|
||||
[undefined, undefined, 2]
|
||||
], [t]);
|
||||
|
||||
const footer = useMemo(() => (
|
||||
<tr>
|
||||
<td colSpan={7} />
|
||||
<td className='number'>
|
||||
{balanceTotal && <FormatBalance value={balanceTotal} />}
|
||||
</td>
|
||||
<td colSpan={2} />
|
||||
</tr>
|
||||
), [balanceTotal]);
|
||||
|
||||
const filter = useMemo(() => (
|
||||
<div className='filter--tags'>
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t('filter by name or tags')}
|
||||
onChange={setFilter}
|
||||
value={filterOn}
|
||||
/>
|
||||
</div>
|
||||
), [filterOn, t]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Banner />
|
||||
@ -171,40 +203,14 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
|
||||
</Button.Group>
|
||||
<Table
|
||||
empty={t('no accounts yet, create or import an existing')}
|
||||
filter={
|
||||
<div className='filter--tags'>
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t('filter by name or tags')}
|
||||
onChange={setFilter}
|
||||
value={filter}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
footer={
|
||||
<tr>
|
||||
<td colSpan={7} />
|
||||
<td className='number'>
|
||||
{balanceTotal && <FormatBalance value={balanceTotal} />}
|
||||
</td>
|
||||
<td colSpan={2} />
|
||||
</tr>
|
||||
}
|
||||
header={[
|
||||
[t('accounts'), 'start', 3],
|
||||
[t('parent'), 'address'],
|
||||
[t('type')],
|
||||
[t('tags'), 'start'],
|
||||
[t('transactions')],
|
||||
[t('balances')],
|
||||
[undefined, undefined, 2]
|
||||
]}
|
||||
filter={filter}
|
||||
footer={footer}
|
||||
header={header}
|
||||
>
|
||||
{sortedAccounts.map(({ account, isFavorite }): React.ReactNode => (
|
||||
<Account
|
||||
account={account}
|
||||
filter={filter}
|
||||
filter={filterOn}
|
||||
isFavorite={isFavorite}
|
||||
key={account.address}
|
||||
setBalance={_setBalance}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { ComponentProps as Props } from '../types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Input, Table } from '@polkadot/react-components';
|
||||
import { useAddresses, useFavorites, useToggle } from '@polkadot/react-hooks';
|
||||
@ -23,7 +23,7 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
|
||||
const [isCreateOpen, toggleCreate] = useToggle(false);
|
||||
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
||||
const [sortedAddresses, setSortedAddresses] = useState<SortedAddress[]>([]);
|
||||
const [filter, setFilter] = useState<string>('');
|
||||
const [filterOn, setFilter] = useState<string>('');
|
||||
|
||||
useEffect((): void => {
|
||||
setSortedAddresses(
|
||||
@ -39,6 +39,26 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
|
||||
);
|
||||
}, [allAddresses, favorites]);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('contacts'), 'start', 2],
|
||||
[t('tags'), 'start'],
|
||||
[t('transactions')],
|
||||
[t('balances')],
|
||||
[undefined, undefined, 2]
|
||||
], [t]);
|
||||
|
||||
const filter = useMemo(() => (
|
||||
<div className='filter--tags'>
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t('filter by name or tags')}
|
||||
onChange={setFilter}
|
||||
value={filterOn}
|
||||
/>
|
||||
</div>
|
||||
), [filterOn, t]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Button.Group>
|
||||
@ -56,29 +76,13 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
|
||||
)}
|
||||
<Table
|
||||
empty={t('no addresses saved yet, add any existing address')}
|
||||
filter={
|
||||
<div className='filter--tags'>
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t('filter by name or tags')}
|
||||
onChange={setFilter}
|
||||
value={filter}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
header={[
|
||||
[t('contacts'), 'start', 2],
|
||||
[t('tags'), 'start'],
|
||||
[t('transactions')],
|
||||
[t('balances')],
|
||||
[undefined, undefined, 2]
|
||||
]}
|
||||
filter={filter}
|
||||
header={header}
|
||||
>
|
||||
{sortedAddresses.map(({ address, isFavorite }): React.ReactNode => (
|
||||
<Address
|
||||
address={address}
|
||||
filter={filter}
|
||||
filter={filterOn}
|
||||
isFavorite={isFavorite}
|
||||
key={address}
|
||||
toggleFavorite={toggleFavorite}
|
||||
|
||||
@ -6,7 +6,7 @@ import { KeypairType } from '@polkadot/util-crypto/types';
|
||||
import { GeneratorMatches, GeneratorMatch, GeneratorResult } from '@polkadot/vanitygen/types';
|
||||
import { ComponentProps as Props } from '../types';
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Dropdown, Input, Table } from '@polkadot/react-components';
|
||||
import { useIsMountedRef } from '@polkadot/react-hooks';
|
||||
@ -163,6 +163,19 @@ function VanityApp ({ className, onStatusChange }: Props): React.ReactElement<Pr
|
||||
}
|
||||
}, [_executeGeneration, isRunning]);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('matches'), 'start', 2],
|
||||
[t('Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)', {
|
||||
replace: {
|
||||
avg: (keyCount / (elapsed / 1000)).toFixed(3),
|
||||
count: keyCount,
|
||||
elapsed: (elapsed / 1000).toFixed(2)
|
||||
}
|
||||
}), 'start'],
|
||||
[t('secret'), 'start'],
|
||||
[]
|
||||
], [elapsed, keyCount, t]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='ui--row'>
|
||||
@ -217,18 +230,7 @@ function VanityApp ({ className, onStatusChange }: Props): React.ReactElement<Pr
|
||||
<Table
|
||||
className='vanity--App-matches'
|
||||
empty={t('No matches found')}
|
||||
header={[
|
||||
[t('matches'), 'start', 2],
|
||||
[t('Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)', {
|
||||
replace: {
|
||||
avg: (keyCount / (elapsed / 1000)).toFixed(3),
|
||||
count: keyCount,
|
||||
elapsed: (elapsed / 1000).toFixed(2)
|
||||
}
|
||||
}), 'start'],
|
||||
[t('secret'), 'start'],
|
||||
[]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{matches.map((match): React.ReactNode => (
|
||||
<Match
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { DeriveCollectiveProposals, DeriveCollectiveProposal } from '@polkadot/api-derive/types';
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
import { useMembers } from '@polkadot/react-hooks';
|
||||
|
||||
@ -25,6 +25,15 @@ function Proposals ({ className, motions, prime }: Props): React.ReactElement<Pr
|
||||
const { t } = useTranslation();
|
||||
const { isMember, members } = useMembers();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('motions'), 'start', 2],
|
||||
[t('threshold')],
|
||||
[t('voting end')],
|
||||
[t('aye'), 'address'],
|
||||
[t('nay'), 'address'],
|
||||
[undefined, undefined, 2]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Button.Group>
|
||||
@ -45,14 +54,7 @@ function Proposals ({ className, motions, prime }: Props): React.ReactElement<Pr
|
||||
</Button.Group>
|
||||
<Table
|
||||
empty={motions && t('No council motions')}
|
||||
header={[
|
||||
[t('motions'), 'start', 2],
|
||||
[t('threshold')],
|
||||
[t('voting end')],
|
||||
[t('aye'), 'address'],
|
||||
[t('nay'), 'address'],
|
||||
[undefined, undefined, 2]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{motions?.map((motion: DeriveCollectiveProposal): React.ReactNode => (
|
||||
<Motion
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -19,14 +19,21 @@ interface Props extends ComponentProps {
|
||||
function Candidates ({ allVotes = {}, electionsInfo }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headerRunners = useMemo(() => [
|
||||
[t('runners up'), 'start', 2],
|
||||
[t('backing')]
|
||||
], [t]);
|
||||
|
||||
const headerCandidates = useMemo(() => [
|
||||
[t('candidates'), 'start', 2],
|
||||
[t('backing')]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
empty={electionsInfo && t('No runners up found')}
|
||||
header={[
|
||||
[t('runners up'), 'start', 2],
|
||||
[t('backing')]
|
||||
]}
|
||||
header={headerRunners}
|
||||
>
|
||||
{electionsInfo?.runnersUp.map(([accountId, balance]): React.ReactNode => (
|
||||
<Candidate
|
||||
@ -39,10 +46,7 @@ function Candidates ({ allVotes = {}, electionsInfo }: Props): React.ReactElemen
|
||||
</Table>
|
||||
<Table
|
||||
empty={electionsInfo && t('No candidates found')}
|
||||
header={[
|
||||
[t('candidates'), 'start', 2],
|
||||
[t('backing')]
|
||||
]}
|
||||
header={headerCandidates}
|
||||
>
|
||||
{electionsInfo?.candidates.map((accountId): React.ReactNode => (
|
||||
<Candidate
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -20,14 +20,16 @@ interface Props extends ComponentProps {
|
||||
function Members ({ allVotes = {}, className, electionsInfo, prime }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('members'), 'start', 2],
|
||||
[t('backing')]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={electionsInfo && t('No members found')}
|
||||
header={[
|
||||
[t('members'), 'start', 2],
|
||||
[t('backing')]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{electionsInfo?.members.map(([accountId, balance]): React.ReactNode => (
|
||||
<Candidate
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveDispatch } from '@polkadot/api-derive/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -20,15 +20,17 @@ function DispatchQueue ({ className }: Props): React.ReactElement<Props> | null
|
||||
const { api } = useApi();
|
||||
const queued = useCall<DeriveDispatch[]>(api.derive.democracy.dispatchQueue, []);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('dispatch queue'), 'start', 2],
|
||||
[t('enact')],
|
||||
[undefined, undefined, 2]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={queued && t('Nothing queued for execution')}
|
||||
header={[
|
||||
[t('dispatch queue'), 'start', 2],
|
||||
[t('enact')],
|
||||
[undefined, undefined, 2]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{queued?.map((entry): React.ReactNode => (
|
||||
<DispatchEntry
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveProposalExternal } from '@polkadot/api-derive/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -20,16 +20,18 @@ function Externals ({ className }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const external = useCall<DeriveProposalExternal | null>(api.derive.democracy.nextExternal, []);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('external'), 'start'],
|
||||
[t('proposer'), 'address'],
|
||||
[t('locked')],
|
||||
[]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={external === null && t('No external proposal')}
|
||||
header={[
|
||||
[t('external'), 'start'],
|
||||
[t('proposer'), 'address'],
|
||||
[t('locked')],
|
||||
[]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{external && <External value={external} />}
|
||||
</Table>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveProposal } from '@polkadot/api-derive/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -20,16 +20,18 @@ function Proposals ({ className }: Props): React.ReactElement<Props> {
|
||||
const { api } = useApi();
|
||||
const proposals = useCall<DeriveProposal[]>(api.derive.democracy.proposals, []);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('proposals'), 'start', 2],
|
||||
[t('proposer'), 'address'],
|
||||
[t('locked')],
|
||||
[undefined, undefined, 3]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={proposals && t('No active proposals')}
|
||||
header={[
|
||||
[t('proposals'), 'start', 2],
|
||||
[t('proposer'), 'address'],
|
||||
[t('locked')],
|
||||
[undefined, undefined, 3]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{proposals?.map((proposal): React.ReactNode => (
|
||||
<ProposalDisplay
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveReferendumExt } from '@polkadot/api-derive/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -20,18 +20,20 @@ function Referendums ({ className }: Props): React.ReactElement<Props> {
|
||||
const { api } = useApi();
|
||||
const referendums = useCall<DeriveReferendumExt[]>(api.derive.democracy.referendums, []);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('referenda'), 'start', 2],
|
||||
[t('remaining')],
|
||||
[t('activate')],
|
||||
[t('aye')],
|
||||
[t('nay')],
|
||||
[undefined, undefined, 3]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={referendums && t('No active referendums')}
|
||||
header={[
|
||||
[t('referenda'), 'start', 2],
|
||||
[t('remaining')],
|
||||
[t('activate')],
|
||||
[t('aye')],
|
||||
[t('nay')],
|
||||
[undefined, undefined, 3]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{referendums?.map((referendum): React.ReactNode => (
|
||||
<Referendum
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { HeaderExtended } from '@polkadot/api-derive';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
@ -16,12 +16,14 @@ interface Props {
|
||||
function BlockHeaders ({ headers }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('recent blocks'), 'start', 3]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
empty={t('No blocks available')}
|
||||
header={[
|
||||
[t('recent blocks'), 'start', 3]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{headers
|
||||
.filter((header) => !!header)
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { BlockNumber, EventRecord, Extrinsic } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -21,15 +21,17 @@ interface Props {
|
||||
function Extrinsics ({ blockNumber, className, events, label, value }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[label || t('extrinsics'), 'start', 2],
|
||||
[t('events'), 'start', 2],
|
||||
[t('signer'), 'address']
|
||||
], [label, t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={t('No pending extrinsics are in the queue')}
|
||||
header={[
|
||||
[label || t('extrinsics'), 'start', 2],
|
||||
[t('events'), 'start', 2],
|
||||
[t('signer'), 'address']
|
||||
]}
|
||||
header={header}
|
||||
isFixed
|
||||
>
|
||||
{value?.map((extrinsic, index): React.ReactNode =>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { DigestItem } from '@polkadot/types/interfaces';
|
||||
import { Codec, TypeDef } from '@polkadot/types/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Struct, Tuple, Raw, Vec, getTypeDef } from '@polkadot/types';
|
||||
import { Expander, Table } from '@polkadot/react-components';
|
||||
import Params from '@polkadot/react-params';
|
||||
@ -103,10 +103,12 @@ function Logs (props: Props): React.ReactElement<Props> | null {
|
||||
const { value } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [[t('logs'), 'start']], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
empty={t('No logs available')}
|
||||
header={[[t('logs'), 'start']]}
|
||||
header={header}
|
||||
>
|
||||
{value?.map((log, index) => (
|
||||
<tr key={`log:${index}`}>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { EventRecord } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import Event from './Event';
|
||||
@ -20,12 +20,14 @@ interface Props {
|
||||
function Events ({ emptyLabel, eventClassName, events, label }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[label || t('recent events'), 'start']
|
||||
], [label, t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
empty={emptyLabel || t('No events available')}
|
||||
header={[
|
||||
[label || t('recent events'), 'start']
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{events
|
||||
.filter(({ event: { method, section } }): boolean => !!method && !!section)
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { PeerInfo } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -18,16 +18,18 @@ interface Props {
|
||||
function Peers ({ className, peers }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('connected peers'), 'start'],
|
||||
[t('role'), 'start'],
|
||||
[t('best #'), 'number'],
|
||||
[t('best hash'), 'hash']
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={t('no peers connected')}
|
||||
header={[
|
||||
[t('connected peers'), 'start'],
|
||||
[t('role'), 'start'],
|
||||
[t('best #'), 'number'],
|
||||
[t('best hash'), 'hash']
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{peers?.sort((a, b): number => b.bestNumber.cmp(a.bestNumber)).map((peer) => (
|
||||
<tr key={peer.peerId.toString()}>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveParachain } from '@polkadot/api-derive/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
@ -18,14 +18,14 @@ interface Props {
|
||||
function Parachains ({ parachains }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('parachains'), 'start', 4],
|
||||
[t('swap to id')],
|
||||
[t('scheduling')]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
header={[
|
||||
[t('parachains'), 'start', 4],
|
||||
[t('swap to id')],
|
||||
[t('scheduling')]
|
||||
]}
|
||||
>
|
||||
<Table header={header}>
|
||||
{parachains.map((parachain): React.ReactNode => (
|
||||
<Parachain
|
||||
key={parachain.id.toString()}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { Bid } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -20,15 +20,17 @@ function Bids ({ className }: Props): React.ReactElement<Props> {
|
||||
const { api } = useApi();
|
||||
const bids = useCall<Bid[]>(api.query.society.bids, []);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('bids'), 'start'],
|
||||
[t('kind')],
|
||||
[t('value')]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={bids && t('No bids')}
|
||||
header={[
|
||||
[t('bids'), 'start'],
|
||||
[t('kind')],
|
||||
[t('value')]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{bids?.map((bid): React.ReactNode => (
|
||||
<BidRow
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { DeriveSocietyCandidate } from '@polkadot/api-derive/types';
|
||||
import { OwnMembers } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -21,16 +21,18 @@ function Candidates ({ allMembers, className, isMember, ownMembers }: Props): Re
|
||||
const { api } = useApi();
|
||||
const candidates = useCall<DeriveSocietyCandidate[]>(api.derive.society.candidates, []);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('candidates'), 'start'],
|
||||
[t('kind')],
|
||||
[t('value')],
|
||||
[t('votes'), 'start']
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={candidates && t('No candidates')}
|
||||
header={[
|
||||
[t('candidates'), 'start'],
|
||||
[t('kind')],
|
||||
[t('value')],
|
||||
[t('votes'), 'start']
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{candidates?.map((candidate): React.ReactNode => (
|
||||
<Candidate
|
||||
|
||||
@ -6,7 +6,7 @@ import { DeriveSociety, DeriveSocietyMember } from '@polkadot/api-derive/types';
|
||||
import { SocietyVote } from '@polkadot/types/interfaces';
|
||||
import { OwnMembers, VoteType } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { AddressSmall, Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -29,6 +29,12 @@ function Defender ({ className, info, isMember, ownMembers }: Props): React.Reac
|
||||
.map(({ accountId, vote }): VoteType => [accountId.toString(), vote as SocietyVote])
|
||||
});
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('defender'), 'start'],
|
||||
[t('votes'), 'start'],
|
||||
[]
|
||||
], [t]);
|
||||
|
||||
if (!info || !info.hasDefender || !info.defender) {
|
||||
return null;
|
||||
}
|
||||
@ -36,11 +42,7 @@ function Defender ({ className, info, isMember, ownMembers }: Props): React.Reac
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
header={[
|
||||
[t('defender'), 'start'],
|
||||
[t('votes'), 'start'],
|
||||
[]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
<tr>
|
||||
<td className='address all'>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveSociety, DeriveSocietyMember } from '@polkadot/api-derive/types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
@ -28,14 +28,16 @@ function Members ({ className, info }: Props): React.ReactElement<Props> {
|
||||
);
|
||||
}, [info, members]);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('members'), 'start', 3],
|
||||
[t('strikes')]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={info && t('No active members')}
|
||||
header={[
|
||||
[t('members'), 'start', 3],
|
||||
[t('strikes')]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{filtered.map((member): React.ReactNode => (
|
||||
<Member
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { ActiveEraInfo, EraIndex } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useCall, useApi, useOwnStashes } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
@ -72,28 +72,32 @@ function Actions ({ allStashes, className, isInElection, next, validators }: Pro
|
||||
[]
|
||||
);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('stashes'), 'start'],
|
||||
[t('controller'), 'address'],
|
||||
[t('rewards'), 'number'],
|
||||
[t('bonded'), 'number'],
|
||||
[undefined, undefined, 2]
|
||||
], [t]);
|
||||
|
||||
const footer = useMemo(() => (
|
||||
<tr>
|
||||
<td colSpan={3} />
|
||||
<td className='number'>
|
||||
{bondedTotal && <FormatBalance value={bondedTotal} />}
|
||||
</td>
|
||||
<td colSpan={2} />
|
||||
</tr>
|
||||
), [bondedTotal]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<NewStake />
|
||||
<ElectionBanner isInElection={isInElection} />
|
||||
<Table
|
||||
empty={t('No funds staked yet. Bond funds to validate or nominate a validator')}
|
||||
footer={
|
||||
<tr>
|
||||
<td colSpan={3} />
|
||||
<td className='number'>
|
||||
{bondedTotal && <FormatBalance value={bondedTotal} />}
|
||||
</td>
|
||||
<td colSpan={2} />
|
||||
</tr>
|
||||
}
|
||||
header={[
|
||||
[t('stashes'), 'start'],
|
||||
[t('controller'), 'address'],
|
||||
[t('rewards'), 'number'],
|
||||
[t('bonded'), 'number'],
|
||||
[undefined, undefined, 2]
|
||||
]}
|
||||
footer={footer}
|
||||
header={header}
|
||||
>
|
||||
{foundStashes?.map(([stashId, isOwnStash]): React.ReactNode => (
|
||||
<Account
|
||||
|
||||
@ -6,7 +6,7 @@ import { DeriveHeartbeats, DeriveStakingOverview } from '@polkadot/api-derive/ty
|
||||
import { AccountId, Nominations } from '@polkadot/types/interfaces';
|
||||
import { Authors } from '@polkadot/react-query/BlockAuthors';
|
||||
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Input, Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useFavorites } from '@polkadot/react-hooks';
|
||||
import { BlockAuthorsContext } from '@polkadot/react-query';
|
||||
@ -111,6 +111,33 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
|
||||
);
|
||||
}, [nominators]);
|
||||
|
||||
const headerActive = useMemo(() => [
|
||||
[t('intentions'), 'start', 3],
|
||||
[t('nominators'), 'start', 2],
|
||||
[t('commission'), 'number', 1],
|
||||
[undefined, undefined, 3]
|
||||
], [t]);
|
||||
|
||||
const headerWaiting = useMemo(() => [
|
||||
[t('validators'), 'start', 3],
|
||||
[t('other stake')],
|
||||
[t('own stake')],
|
||||
[t('commission')],
|
||||
[t('points')],
|
||||
[t('last #')],
|
||||
[]
|
||||
], [t]);
|
||||
|
||||
const filter = useMemo(() => (
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t('filter by name, address or index')}
|
||||
onChange={setNameFilter}
|
||||
value={nameFilter}
|
||||
/>
|
||||
), [nameFilter, t]);
|
||||
|
||||
const _renderRows = useCallback(
|
||||
(addresses?: AccountExtend[], isMain?: boolean): React.ReactNode[] =>
|
||||
(addresses || []).map(([address, isElected, isFavorite]): React.ReactNode => (
|
||||
@ -139,12 +166,7 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
|
||||
? (
|
||||
<Table
|
||||
empty={waiting && t('No waiting validators found')}
|
||||
header={[
|
||||
[t('intentions'), 'start', 3],
|
||||
[t('nominators'), 'start', 2],
|
||||
[t('commission'), 'number', 1],
|
||||
[undefined, undefined, 3]
|
||||
]}
|
||||
header={headerActive}
|
||||
>
|
||||
{_renderRows(elected, false).concat(..._renderRows(waiting, false))}
|
||||
</Table>
|
||||
@ -152,24 +174,8 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
|
||||
: (
|
||||
<Table
|
||||
empty={validators && t('No active validators found')}
|
||||
filter={
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t('filter by name, address or index')}
|
||||
onChange={setNameFilter}
|
||||
value={nameFilter}
|
||||
/>
|
||||
}
|
||||
header={[
|
||||
[t('validators'), 'start', 3],
|
||||
[t('other stake')],
|
||||
[t('own stake')],
|
||||
[t('commission')],
|
||||
[t('points')],
|
||||
[t('last #')],
|
||||
[]
|
||||
]}
|
||||
filter={filter}
|
||||
header={headerWaiting}
|
||||
>
|
||||
{_renderRows(validators, true)}
|
||||
</Table>
|
||||
|
||||
@ -6,7 +6,7 @@ import { DeriveStakerReward } from '@polkadot/api-derive/types';
|
||||
import { PayoutStash, PayoutValidator } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
import { useApi, useOwnEraRewards } from '@polkadot/react-hooks';
|
||||
@ -109,6 +109,32 @@ function Payouts ({ className, isInElection }: Props): React.ReactElement<Props>
|
||||
}
|
||||
}, [allRewards]);
|
||||
|
||||
const headerStashes = useMemo(() => [
|
||||
[t('payout/stash'), 'start'],
|
||||
[t('eras'), 'start'],
|
||||
[t('available')],
|
||||
[('remaining')],
|
||||
[undefined, undefined, 3]
|
||||
], [t]);
|
||||
|
||||
const headerValidators = useMemo(() => [
|
||||
[t('payout/validator'), 'start'],
|
||||
[t('eras'), 'start'],
|
||||
[t('available')],
|
||||
[('remaining')],
|
||||
[undefined, undefined, 3]
|
||||
], [t]);
|
||||
|
||||
const footer = useMemo(() => (
|
||||
<tr>
|
||||
<td colSpan={2} />
|
||||
<td className='number'>
|
||||
{stashTotal && <FormatBalance value={stashTotal} />}
|
||||
</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
), [stashTotal]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{api.tx.staking.payoutStakers && (
|
||||
@ -123,22 +149,8 @@ function Payouts ({ className, isInElection }: Props): React.ReactElement<Props>
|
||||
<Table
|
||||
empty={stashes && t('No pending payouts for your stashes')}
|
||||
emptySpinner={t('Retrieving info for all applicable eras, this will take some time')}
|
||||
footer={
|
||||
<tr>
|
||||
<td colSpan={2} />
|
||||
<td className='number'>
|
||||
{stashTotal && <FormatBalance value={stashTotal} />}
|
||||
</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
}
|
||||
header={[
|
||||
[t('payout/stash'), 'start'],
|
||||
[t('eras'), 'start'],
|
||||
[t('available')],
|
||||
[('remaining')],
|
||||
[undefined, undefined, 3]
|
||||
]}
|
||||
footer={footer}
|
||||
header={headerStashes}
|
||||
isFixed
|
||||
>
|
||||
{stashes?.map((payout): React.ReactNode => (
|
||||
@ -153,13 +165,7 @@ function Payouts ({ className, isInElection }: Props): React.ReactElement<Props>
|
||||
{api.tx.staking.payoutStakers && (
|
||||
<Table
|
||||
empty={validators && t('No pending era payouts from validators')}
|
||||
header={[
|
||||
[t('payout/validator'), 'start'],
|
||||
[t('eras'), 'start'],
|
||||
[t('available')],
|
||||
[('remaining')],
|
||||
[undefined, undefined, 3]
|
||||
]}
|
||||
header={headerValidators}
|
||||
isFixed
|
||||
>
|
||||
{validators?.map((payout): React.ReactNode => (
|
||||
|
||||
@ -210,6 +210,28 @@ function Targets ({ className }: Props): React.ReactElement<Props> {
|
||||
}
|
||||
}, [allAccounts, amount, electedInfo, favorites, lastReward, sortBy, sortFromMax]);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('validators'), 'start', 3],
|
||||
...['rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header) => [
|
||||
<>{labels[header]}<Icon name={sortBy === header ? (sortFromMax ? 'chevron down' : 'chevron up') : 'minus'} /></>,
|
||||
`isClickable ${sortBy === header && 'ui--highlight--border'} number`,
|
||||
1,
|
||||
(): void => _sort(header as 'rankComm')
|
||||
]),
|
||||
[]
|
||||
], [_sort, labels, sortBy, sortFromMax, t]);
|
||||
|
||||
const filter = useMemo(() => (
|
||||
<InputBalance
|
||||
className='balanceInput'
|
||||
help={t('The amount that will be used on a per-validator basis to calculate rewards for that validator.')}
|
||||
isFull
|
||||
label={t('amount to use for estimation')}
|
||||
onChange={setAmount}
|
||||
value={_amount}
|
||||
/>
|
||||
), [_amount, t]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Summary
|
||||
@ -220,26 +242,8 @@ function Targets ({ className }: Props): React.ReactElement<Props> {
|
||||
/>
|
||||
<Table
|
||||
empty={sorted && t('No active validators to check for rewards available')}
|
||||
filter={
|
||||
<InputBalance
|
||||
className='balanceInput'
|
||||
help={t('The amount that will be used on a per-validator basis to calculate rewards for that validator.')}
|
||||
isFull
|
||||
label={t('amount to use for estimation')}
|
||||
onChange={setAmount}
|
||||
value={_amount}
|
||||
/>
|
||||
}
|
||||
header={[
|
||||
[t('validators'), 'start', 3],
|
||||
...['rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header) => [
|
||||
<>{labels[header]}<Icon name={sortBy === header ? (sortFromMax ? 'chevron down' : 'chevron up') : 'minus'} /></>,
|
||||
`isClickable ${sortBy === header && 'ui--highlight--border'} number`,
|
||||
1,
|
||||
(): void => _sort(header as 'rankComm')
|
||||
]),
|
||||
[]
|
||||
]}
|
||||
filter={filter}
|
||||
header={header}
|
||||
>
|
||||
{sorted?.map((info): React.ReactNode =>
|
||||
<Validator
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { AddressSmall, Table, Tag } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -18,13 +18,15 @@ interface Props {
|
||||
function Members ({ className, members, prime }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('members'), 'start', 3]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={members && t('No members found')}
|
||||
header={[
|
||||
[t('members'), 'start', 3]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{members?.map((accountId): React.ReactNode => (
|
||||
<tr key={accountId.toString()}>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { Hash } from '@polkadot/types/interfaces';
|
||||
import { ComponentProps as Props } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -15,6 +15,14 @@ import Propose from './Propose';
|
||||
function Proposals ({ className, isMember, members, prime, proposals }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('proposals'), 'start', 2],
|
||||
[t('threshold')],
|
||||
[t('aye'), 'address'],
|
||||
[t('nay'), 'address'],
|
||||
[]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Button.Group>
|
||||
@ -25,13 +33,7 @@ function Proposals ({ className, isMember, members, prime, proposals }: Props):
|
||||
</Button.Group>
|
||||
<Table
|
||||
empty={proposals && t('No committee proposals')}
|
||||
header={[
|
||||
[t('proposals'), 'start', 2],
|
||||
[t('threshold')],
|
||||
[t('aye'), 'address'],
|
||||
[t('nay'), 'address'],
|
||||
[]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{proposals?.map((hash: Hash): React.ReactNode => (
|
||||
<Proposal
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { DeriveTreasuryProposal } from '@polkadot/api-derive/types';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
@ -29,17 +29,19 @@ function ProposalsBase ({ className, isApprovals, isMember, proposals }: Props):
|
||||
[history]
|
||||
);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[isApprovals ? t('Approved') : t('Proposals'), 'start', 2],
|
||||
[t('beneficiary'), 'address'],
|
||||
[t('payment')],
|
||||
[t('bond')],
|
||||
[undefined, undefined, 2]
|
||||
], [isApprovals, t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={proposals && (isApprovals ? t('No approved proposals') : t('No pending proposals'))}
|
||||
header={[
|
||||
[isApprovals ? t('Approved') : t('Proposals'), 'start', 2],
|
||||
[t('beneficiary'), 'address'],
|
||||
[t('payment')],
|
||||
[t('bond')],
|
||||
[undefined, undefined, 2]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{proposals?.map((proposal): React.ReactNode => (
|
||||
<Proposal
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -18,18 +18,20 @@ interface Props {
|
||||
function Tips ({ className, hashes, isMember, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('tips'), 'start'],
|
||||
[t('finder'), 'address'],
|
||||
[t('fee')],
|
||||
[t('reason'), 'start'],
|
||||
[],
|
||||
[]
|
||||
], [t]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={hashes && t('No open tips')}
|
||||
header={[
|
||||
[t('tips'), 'start'],
|
||||
[t('finder'), 'address'],
|
||||
[t('fee')],
|
||||
[t('reason'), 'start'],
|
||||
[],
|
||||
[]
|
||||
]}
|
||||
header={header}
|
||||
>
|
||||
{hashes?.map((hash): React.ReactNode => (
|
||||
<Tip
|
||||
|
||||
Loading…
Reference in New Issue
Block a user