* Retrofitting basic unit tests to Accounts Extraction of useAccounts and useBalance hooks * Retrofitting basic unit tests to Accounts Extraction of useAccounts and useBalance hooks * Fixing accounts unit tests - basic account balances * Cleaning up after PR remarks * Fixing arrangements of balances 1/3 Moving ArrangedAccount around * Add useStakingInfo hook * Adopt linter suggestions * Fixing arrangements of balances 2/3 Linking arranged balances values to mocked balances calls. * Fixing arrangements of balances 3/3 Formatting utils made consistent * Details row odd-even + (un)hide * Improving th/td/footer layout * Add tags to details row * Changing layout of account balance with details * Add total/details balances unit tests * Add tags unit tests * Clean up of TODOs, adding comments * Improve tags unit testing * Alternate account row details coloring unit test * Row details toggling unit tests * Row details toggling unit tests - bad commit fix * Style address balances (#5897) * Fix jumping column problem * Improve balances positioning * Format sidebar * Position header * Extract common formatting of balances in tests. Thx @vitaliyworks, great hint. * Revert Sidebar styling * Use rems * Lint * Use useToggle for expanding account row * Add tags header * Apply FormatBalance review comments * Display parent account info above account name (#5947) * Display parent address * Improve layout * Set keyring in tests * Remove redundant code from components * Use derivedAccount only in specific test * Apply lint fixes * Remove parent column from expanded row * Apply review suggestions * Improve styling in ParentAddress component * Improve layout of parent address and add short address * Apply review suggestions * Use map instead of for loops Co-authored-by: Marcin Wachulski <marcin@ethworks.io> Co-authored-by: Vitaliy Mysak <vitaliy@ethworks.io> Co-authored-by: MiZiet <55240109+MiZiet@users.noreply.github.com> Co-authored-by: MiZiet <michalzietalak@o2.pl> Co-authored-by: Ewa Kowalska <30404388+ekowalsk@users.noreply.github.com> Co-authored-by: Ewa Kowalska <ewcia.kowalska@gmail.com>
261 lines
8.0 KiB
TypeScript
261 lines
8.0 KiB
TypeScript
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type BN from 'bn.js';
|
|
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
|
import type { AccountId, ProxyDefinition, ProxyType, Voting } from '@polkadot/types/interfaces';
|
|
import type { AccountBalance, Delegation, SortedAccount } from '../types';
|
|
|
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import styled from 'styled-components';
|
|
|
|
import { Button, Input, Table } from '@polkadot/react-components';
|
|
import { useAccounts, useApi, useCall, useFavorites, useIpfs, useLedger, useLoadingDelay, useToggle } from '@polkadot/react-hooks';
|
|
import { BN_ZERO } from '@polkadot/util';
|
|
|
|
import CreateModal from '../modals/Create';
|
|
import ImportModal from '../modals/Import';
|
|
import Ledger from '../modals/Ledger';
|
|
import Multisig from '../modals/MultisigCreate';
|
|
import Proxy from '../modals/ProxiedAdd';
|
|
import Qr from '../modals/Qr';
|
|
import { useTranslation } from '../translate';
|
|
import { sortAccounts } from '../util';
|
|
import Account from './Account';
|
|
import BannerClaims from './BannerClaims';
|
|
import BannerExtension from './BannerExtension';
|
|
import Summary from './Summary';
|
|
|
|
interface Balances {
|
|
accounts: Record<string, AccountBalance>;
|
|
summary?: AccountBalance;
|
|
}
|
|
|
|
interface Sorted {
|
|
sortedAccounts: SortedAccount[];
|
|
sortedAddresses: string[];
|
|
}
|
|
|
|
interface Props {
|
|
className?: string;
|
|
onStatusChange: (status: ActionStatus) => void;
|
|
}
|
|
|
|
const STORE_FAVS = 'accounts:favorites';
|
|
|
|
function Overview ({ className = '', onStatusChange }: Props): React.ReactElement<Props> {
|
|
const { t } = useTranslation();
|
|
const { api } = useApi();
|
|
const { allAccounts, hasAccounts } = useAccounts();
|
|
const { isIpfs } = useIpfs();
|
|
const { isLedgerEnabled } = useLedger();
|
|
const [isCreateOpen, toggleCreate] = useToggle();
|
|
const [isImportOpen, toggleImport] = useToggle();
|
|
const [isLedgerOpen, toggleLedger] = useToggle();
|
|
const [isMultisigOpen, toggleMultisig] = useToggle();
|
|
const [isProxyOpen, toggleProxy] = useToggle();
|
|
const [isQrOpen, toggleQr] = useToggle();
|
|
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
|
const [balances, setBalances] = useState<Balances>({ accounts: {} });
|
|
const [filterOn, setFilter] = useState<string>('');
|
|
const [sortedAccountsWithDelegation, setSortedAccountsWithDelegation] = useState<SortedAccount[] | undefined>();
|
|
const [{ sortedAccounts, sortedAddresses }, setSorted] = useState<Sorted>({ sortedAccounts: [], sortedAddresses: [] });
|
|
const delegations = useCall<Voting[]>(api.query.democracy?.votingOf?.multi, [sortedAddresses]);
|
|
const proxies = useCall<[ProxyDefinition[], BN][]>(api.query.proxy?.proxies.multi, [sortedAddresses], {
|
|
transform: (result: [([AccountId, ProxyType] | ProxyDefinition)[], BN][]): [ProxyDefinition[], BN][] =>
|
|
api.tx.proxy.addProxy.meta.args.length === 3
|
|
? result as [ProxyDefinition[], BN][]
|
|
: (result as [[AccountId, ProxyType][], BN][]).map(([arr, bn]): [ProxyDefinition[], BN] =>
|
|
[arr.map(([delegate, proxyType]): ProxyDefinition => api.createType('ProxyDefinition', { delegate, proxyType })), bn]
|
|
)
|
|
});
|
|
const isLoading = useLoadingDelay();
|
|
|
|
const headerRef = useRef([
|
|
[t('accounts'), 'start', 3],
|
|
[t('type')],
|
|
[t('transactions'), 'media--1500'],
|
|
[t('balances'), 'balances'],
|
|
[],
|
|
[undefined, 'media--1400'],
|
|
[]
|
|
]);
|
|
|
|
useEffect((): void => {
|
|
const sortedAccounts = sortAccounts(allAccounts, favorites);
|
|
const sortedAddresses = sortedAccounts.map((a) => a.account.address);
|
|
|
|
setSorted({ sortedAccounts, sortedAddresses });
|
|
}, [allAccounts, favorites]);
|
|
|
|
useEffect(() => {
|
|
setSortedAccountsWithDelegation(
|
|
sortedAccounts?.map((account, index) => {
|
|
let delegation: Delegation | undefined;
|
|
|
|
if (delegations && delegations[index]?.isDelegating) {
|
|
const { balance: amount, conviction, target } = delegations[index].asDelegating;
|
|
|
|
delegation = {
|
|
accountDelegated: target.toString(),
|
|
amount,
|
|
conviction
|
|
};
|
|
}
|
|
|
|
return ({
|
|
...account,
|
|
delegation
|
|
});
|
|
})
|
|
);
|
|
}, [api, delegations, sortedAccounts]);
|
|
|
|
const _setBalance = useCallback(
|
|
(account: string, balance: AccountBalance) =>
|
|
setBalances(({ accounts }: Balances): Balances => {
|
|
accounts[account] = balance;
|
|
|
|
const aggregate = (key: keyof AccountBalance) =>
|
|
Object.values(accounts).reduce((total: BN, value: AccountBalance) => total.add(value[key]), BN_ZERO);
|
|
|
|
return {
|
|
accounts,
|
|
summary: {
|
|
bonded: aggregate('bonded'),
|
|
locked: aggregate('locked'),
|
|
redeemable: aggregate('redeemable'),
|
|
total: aggregate('total'),
|
|
transferrable: aggregate('transferrable'),
|
|
unbonding: aggregate('unbonding')
|
|
}
|
|
};
|
|
}),
|
|
[]
|
|
);
|
|
|
|
const filter = useMemo(() => (
|
|
<div className='filter--tags'>
|
|
<Input
|
|
autoFocus
|
|
isFull
|
|
label={t<string>('filter by name or tags')}
|
|
onChange={setFilter}
|
|
value={filterOn}
|
|
/>
|
|
</div>
|
|
), [filterOn, t]);
|
|
|
|
return (
|
|
<div className={className}>
|
|
{isCreateOpen && (
|
|
<CreateModal
|
|
onClose={toggleCreate}
|
|
onStatusChange={onStatusChange}
|
|
/>
|
|
)}
|
|
{isImportOpen && (
|
|
<ImportModal
|
|
onClose={toggleImport}
|
|
onStatusChange={onStatusChange}
|
|
/>
|
|
)}
|
|
{isLedgerOpen && (
|
|
<Ledger onClose={toggleLedger} />
|
|
)}
|
|
{isMultisigOpen && (
|
|
<Multisig
|
|
onClose={toggleMultisig}
|
|
onStatusChange={onStatusChange}
|
|
/>
|
|
)}
|
|
{isProxyOpen && (
|
|
<Proxy
|
|
onClose={toggleProxy}
|
|
onStatusChange={onStatusChange}
|
|
/>
|
|
)}
|
|
{isQrOpen && (
|
|
<Qr
|
|
onClose={toggleQr}
|
|
onStatusChange={onStatusChange}
|
|
/>
|
|
)}
|
|
<Button.Group>
|
|
<Button
|
|
icon='plus'
|
|
isDisabled={isIpfs}
|
|
label={t<string>('Add account')}
|
|
onClick={toggleCreate}
|
|
/>
|
|
<Button
|
|
icon='sync'
|
|
isDisabled={isIpfs}
|
|
label={t<string>('Restore JSON')}
|
|
onClick={toggleImport}
|
|
/>
|
|
<Button
|
|
icon='qrcode'
|
|
label={t<string>('Add via Qr')}
|
|
onClick={toggleQr}
|
|
/>
|
|
{isLedgerEnabled && (
|
|
<>
|
|
<Button
|
|
icon='project-diagram'
|
|
label={t<string>('Add via Ledger')}
|
|
onClick={toggleLedger}
|
|
/>
|
|
</>
|
|
)}
|
|
<Button
|
|
icon='plus'
|
|
isDisabled={!(api.tx.multisig || api.tx.utility) || !hasAccounts}
|
|
label={t<string>('Multisig')}
|
|
onClick={toggleMultisig}
|
|
/>
|
|
<Button
|
|
icon='plus'
|
|
isDisabled={!api.tx.proxy || !hasAccounts}
|
|
label={t<string>('Proxied')}
|
|
onClick={toggleProxy}
|
|
/>
|
|
</Button.Group>
|
|
<BannerExtension />
|
|
<BannerClaims />
|
|
<Summary balance={balances.summary} />
|
|
<Table
|
|
empty={!isLoading && sortedAccountsWithDelegation && t<string>("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")}
|
|
filter={filter}
|
|
header={headerRef.current}
|
|
>
|
|
{!isLoading && sortedAccountsWithDelegation?.map(({ account, delegation, isFavorite }, index): React.ReactNode => (
|
|
<Account
|
|
account={account}
|
|
delegation={delegation}
|
|
filter={filterOn}
|
|
isEven={!!(index % 2)}
|
|
isFavorite={isFavorite}
|
|
key={`${index}:${account.address}`}
|
|
proxy={proxies?.[index]}
|
|
setBalance={_setBalance}
|
|
toggleFavorite={toggleFavorite}
|
|
/>
|
|
))}
|
|
</Table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default React.memo(styled(Overview)`
|
|
.filter--tags {
|
|
.ui--Dropdown {
|
|
padding-left: 0;
|
|
|
|
label {
|
|
left: 1.55rem;
|
|
}
|
|
}
|
|
}
|
|
`);
|