Commit 093bb8b2 authored by isstuev's avatar isstuev

accounts page

parent 7424b281
...@@ -14,6 +14,7 @@ import type { ...@@ -14,6 +14,7 @@ import type {
AddressTokensFilter, AddressTokensFilter,
AddressTokensResponse, AddressTokensResponse,
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod } from 'types/api/contract'; import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod } from 'types/api/contract';
...@@ -128,6 +129,13 @@ export const RESOURCES = { ...@@ -128,6 +129,13 @@ export const RESOURCES = {
path: '/api/v2/transactions/:id/raw-trace', path: '/api/v2/transactions/:id/raw-trace',
}, },
// ADDRESSES
addresses: {
path: '/api/v2/addresses/',
paginationFields: [ 'fetched_coin_balance' as const, 'hash' as const, 'items_count' as const ],
filterFields: [ ],
},
// ADDRESS // ADDRESS
address: { address: {
path: '/api/v2/addresses/:id', path: '/api/v2/addresses/:id',
...@@ -286,6 +294,7 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }> ...@@ -286,6 +294,7 @@ export type ResourceErrorAccount<T> = ResourceError<{ errors: T }>
export type PaginatedResources = 'blocks' | 'block_txs' | export type PaginatedResources = 'blocks' | 'block_txs' |
'txs_validated' | 'txs_pending' | 'txs_validated' | 'txs_pending' |
'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' |
'addresses' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' |
...@@ -321,6 +330,7 @@ Q extends 'tx_internal_txs' ? InternalTransactionsResponse : ...@@ -321,6 +330,7 @@ Q extends 'tx_internal_txs' ? InternalTransactionsResponse :
Q extends 'tx_logs' ? LogsResponseTx : Q extends 'tx_logs' ? LogsResponseTx :
Q extends 'tx_token_transfers' ? TokenTransferResponse : Q extends 'tx_token_transfers' ? TokenTransferResponse :
Q extends 'tx_raw_trace' ? RawTracesResponse : Q extends 'tx_raw_trace' ? RawTracesResponse :
Q extends 'addresses' ? AddressesResponse :
Q extends 'address' ? Address : Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters : Q extends 'address_counters' ? AddressCounters :
Q extends 'address_token_balances' ? Array<AddressTokenBalance> : Q extends 'address_token_balances' ? Array<AddressTokenBalance> :
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Accounts from 'ui/pages/Accounts';
const AccountsPage: NextPage = () => {
const title = `Top Accounts - ${ getNetworkTitle() }`;
return (
<>
<Head><title>{ title }</title></Head>
<Accounts/>
</>
);
};
export default AccountsPage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
import type { AddressParam } from './addressParams';
export type AddressesItem = AddressParam &{ tx_count: string; coin_balance: string }
export type AddressesResponse = {
items: Array<AddressesItem>;
next_page_params: {
fetched_coin_balance: string;
hash: string;
items_count: number;
};
total_supply: string;
}
import { Flex, Tag, Text, HStack } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import appConfig from 'configs/app/config';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
type Props = {
item: AddressesItem;
index: number;
totalSupply: string;
}
const AddressesListItem = ({
item,
index,
totalSupply,
}: Props) => {
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** appConfig.network.currency.decimals));
return (
<ListItemMobile rowGap={ 3 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
<Address maxW="100%" mr={ 8 }>
<AddressIcon address={ item } mr={ 2 }/>
<AddressLink
fontWeight={ 700 }
flexGrow={ 1 }
w="calc(100% - 32px)"
hash={ item.hash }
alias={ item.name }
type="address"
/>
</Address>
<Text fontSize="sm" ml="auto" variant="secondary">{ index }</Text>
</Flex>
{ item.public_tags !== null && item.public_tags.length > 0 && item.public_tags.map(tag => (
<Tag key={ tag.label }>{ tag.display_name }</Tag>
)) }
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>{ `Balance ${ appConfig.network.currency.symbol }` }</Text>
<Text fontSize="sm" variant="secondary">{ addressBalance.dp(8).toFormat() }</Text>
</HStack>
{ totalSupply && totalSupply !== '0' && (
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Percentage</Text>
<Text fontSize="sm" variant="secondary">{ addressBalance.div(BigNumber(totalSupply)).multipliedBy(100).dp(8).toFormat() + '%' }</Text>
</HStack>
) }
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Txn count</Text>
<Text fontSize="sm" variant="secondary">{ Number(item.tx_count).toLocaleString('en') }</Text>
</HStack>
</ListItemMobile>
);
};
export default React.memo(AddressesListItem);
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import appConfig from 'configs/app/config';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressesTableItem from './AddressesTableItem';
interface Props {
items: Array<AddressesItem>;
totalSupply: string;
pageStartIndex: number;
}
const AddressesTable = ({ items, totalSupply, pageStartIndex }: Props) => {
const hasPercentage = Boolean(totalSupply && totalSupply !== '0');
return (
<Table variant="simple" size="sm">
<Thead top={ 80 }>
<Tr>
<Th width="64px">Rank</Th>
<Th width={ hasPercentage ? '30%' : '40%' }>Address</Th>
<Th width="20%" pl={ 10 }>Public tag</Th>
<Th width={ hasPercentage ? '20%' : '25%' } isNumeric>{ `Balance ${ appConfig.network.currency.symbol }` }</Th>
{ hasPercentage && <Th width="15%" isNumeric>Percentage</Th> }
<Th width="15%" isNumeric>Txn count</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<AddressesTableItem
key={ item.hash }
item={ item }
totalSupply={ totalSupply }
index={ pageStartIndex + index }
hasPercentage={ hasPercentage }
/>
)) }
</Tbody>
</Table>
);
};
export default AddressesTable;
import { Tr, Td, Tag, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressesItem } from 'types/api/addresses';
import appConfig from 'configs/app/config';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
type Props = {
item: AddressesItem;
index: number;
totalSupply: string;
hasPercentage: boolean;
}
const AddressesTableItem = ({
item,
index,
totalSupply,
hasPercentage,
}: Props) => {
const addressBalance = BigNumber(item.coin_balance).div(BigNumber(10 ** appConfig.network.currency.decimals));
const addressBalanceChunks = addressBalance.dp(8).toFormat().split('.');
return (
<Tr>
<Td>
<Text lineHeight="24px">{ index }</Text>
</Td>
<Td>
<Address display="inline-flex" maxW="100%">
<AddressIcon address={ item } mr={ 2 }/>
<AddressLink
fontWeight={ 700 }
flexGrow={ 1 }
w="calc(100% - 32px)"
hash={ item.hash }
alias={ item.name }
type="address"
/>
</Address>
</Td>
<Td pl={ 10 }>
{ item.public_tags && item.public_tags.length ? item.public_tags.map(tag => (
<Tag key={ tag.label }>{ tag.display_name }</Tag>
)) : <Text lineHeight="24px">-</Text> }
</Td>
<Td isNumeric>
<Text lineHeight="24px" as="span">{ addressBalanceChunks[0] + '.' }</Text>
<Text lineHeight="24px" variant="secondary" as="span">{ addressBalanceChunks[1] }</Text>
</Td>
{ hasPercentage && (
<Td isNumeric>
<Text lineHeight="24px">{ addressBalance.div(BigNumber(totalSupply)).multipliedBy(100).dp(8).toFormat() + '%' }</Text>
</Td>
) }
<Td isNumeric>
<Text lineHeight="24px">{ Number(item.tx_count).toLocaleString('en') }</Text>
</Td>
</Tr>
);
};
export default React.memo(AddressesTableItem);
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { AddressesResponse } from 'types/api/addresses';
import * as addressMocks from 'mocks/address/address';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import Accounts from './Accounts';
const ADDRESSES_API_URL = buildApiUrl('addresses');
const addresses: AddressesResponse = {
items: [
{
...addressMocks.withName,
tx_count: '1',
coin_balance: '12345678901234567890000',
}, {
...addressMocks.withToken,
tx_count: '109123890123',
coin_balance: '22222345678901234567890000',
}, {
...addressMocks.withoutName,
tx_count: '11',
coin_balance: '123456789012000',
},
],
total_supply: '25222000',
next_page_params: {
items_count: 50,
fetched_coin_balance: '123',
hash: 'aa',
},
};
test('base view +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(ADDRESSES_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(addresses),
}));
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: '',
}));
const component = await mount(
<TestApp>
<Accounts/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { Hide, Show } from '@chakra-ui/react';
import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import AddressesListItem from 'ui/addresses/AddressesListItem';
import AddressesTable from 'ui/addresses/AddressesTable';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
const PAGE_SIZE = 50;
const Accounts = () => {
const { isError, isLoading, data, isPaginationVisible, pagination } = useQueryWithPages({
resourceName: 'addresses',
});
const content = (() => {
if (isError) {
return <DataFetchAlert/>;
}
const bar = isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
);
if (isLoading) {
return null;
}
const pageStartIndex = (pagination.page - 1) * PAGE_SIZE + 1;
return (
<>
{ bar }
<Hide below="lg">
<AddressesTable
items={ data.items }
totalSupply={ data.total_supply }
pageStartIndex={ pageStartIndex }
/>
</Hide>
<Show below="lg">
{ data.items.map((item, index) => {
return (
<AddressesListItem
key={ item.hash }
item={ item }
index={ pageStartIndex + index }
totalSupply={ data.total_supply }
/>
);
}) }
</Show>
</>
);
})();
return (
<Page>
<PageTitle text="Top accounts" withTextAd/>
{ content }
</Page>
);
};
export default Accounts;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment