Commit 3c6224f8 authored by isstuev's avatar isstuev

tokens table

parent 22fba855
...@@ -11,6 +11,8 @@ import type { ...@@ -11,6 +11,8 @@ import type {
AddressInternalTxsResponse, AddressInternalTxsResponse,
AddressTxsFilters, AddressTxsFilters,
AddressTokenTransferFilters, AddressTokenTransferFilters,
AddressTokensFilter,
AddressTokensResponse,
} from 'types/api/address'; } from 'types/api/address';
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';
...@@ -163,6 +165,11 @@ export const RESOURCES = { ...@@ -163,6 +165,11 @@ export const RESOURCES = {
paginationFields: [ 'items_count' as const, 'transaction_index' as const, 'index' as const, 'block_number' as const ], paginationFields: [ 'items_count' as const, 'transaction_index' as const, 'index' as const, 'block_number' as const ],
filterFields: [ ], filterFields: [ ],
}, },
address_tokens: {
path: '/api/v2/addresses/:id/tokens',
paginationFields: [ 'items_count' as const, 'token_name' as const, 'token_type' as const, 'value' as const ],
filterFields: [ 'type' ],
},
// CONTRACT // CONTRACT
contract: { contract: {
...@@ -264,8 +271,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -264,8 +271,8 @@ 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' |
'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' |
'address_logs' |
'search' | 'search' |
'address_logs' | 'address_tokens' |
'token_holders'; 'token_holders';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -307,6 +314,7 @@ Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse : ...@@ -307,6 +314,7 @@ Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse : Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart : Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart :
Q extends 'address_logs' ? LogsResponseAddress : Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'token' ? TokenInfo : Q extends 'token' ? TokenInfo :
Q extends 'token_counters' ? TokenCounters : Q extends 'token_counters' ? TokenCounters :
Q extends 'token_holders' ? TokenHolders : Q extends 'token_holders' ? TokenHolders :
...@@ -326,6 +334,7 @@ Q extends 'txs_validated' | 'txs_pending' ? TTxsFilters : ...@@ -326,6 +334,7 @@ Q extends 'txs_validated' | 'txs_pending' ? TTxsFilters :
Q extends 'tx_token_transfers' ? TokenTransferFilters : Q extends 'tx_token_transfers' ? TokenTransferFilters :
Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters :
Q extends 'address_token_transfers' ? AddressTokenTransferFilters : Q extends 'address_token_transfers' ? AddressTokenTransferFilters :
Q extends 'address_tokens' ? AddressTokensFilter :
Q extends 'search' ? SearchResultFilters : Q extends 'search' ? SearchResultFilters :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
...@@ -48,6 +48,16 @@ export interface AddressTokenBalance { ...@@ -48,6 +48,16 @@ export interface AddressTokenBalance {
value: string; value: string;
} }
export interface AddressTokensResponse {
items: Array<AddressTokenBalance>;
next_page_params: {
items_count: number;
token_name: 'string' | null;
token_type: TokenType;
value: number;
};
}
export interface AddressTransactionsResponse { export interface AddressTransactionsResponse {
items: Array<Transaction>; items: Array<Transaction>;
next_page_params: { next_page_params: {
...@@ -75,6 +85,10 @@ export type AddressTokenTransferFilters = { ...@@ -75,6 +85,10 @@ export type AddressTokenTransferFilters = {
type: Array<TokenType>; type: Array<TokenType>;
} }
export type AddressTokensFilter = {
type: TokenType;
}
export interface AddressCoinBalanceHistoryItem { export interface AddressCoinBalanceHistoryItem {
block_number: number; block_number: number;
block_timestamp: string; block_timestamp: string;
......
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { tokenTabsByType } from 'ui/pages/Address';
import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TokenBalances from './tokens/TokenBalances'; import TokenBalances from './tokens/TokenBalances';
const AddressTokens = () => { type Props = {
return <TokenBalances/>; tabs: Array<RoutedTab>;
}
const AddressTokens = ({ tabs }: Props) => {
const router = useRouter();
const isMobile = useIsMobile();
const tokenType: TokenType = (Object.keys(tokenTabsByType) as Array<TokenType>).find(key => tokenTabsByType[key] === router.query.tab) || 'ERC-20';
const { pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_tokens',
pathParams: { id: router.query.id?.toString() },
filters: { type: tokenType },
});
const TAB_LIST_PROPS = {
marginBottom: 0,
py: 5,
marginTop: 3,
columnGap: 3,
};
return (
<>
<TokenBalances/>
<RoutedTabs
tabs={ tabs }
variant="outline"
colorScheme="gray"
size="sm"
tabListProps={ isMobile ? { mt: 8, columnGap: 3 } : TAB_LIST_PROPS }
rightSlot={ isPaginationVisible && !isMobile ? <Pagination { ...pagination }/> : null }
stickyEnabled={ !isMobile }
/>
</>
);
}; };
export default AddressTokens; export default AddressTokens;
import { Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Pagination from 'ui/shared/Pagination';
import TokensTable from './TokensTable';
type Props = {
type: TokenType;
}
const TokensERC20 = ({ type }: Props) => {
const router = useRouter();
const isMobile = useIsMobile();
const { isError, isLoading, data, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_tokens',
pathParams: { id: router.query.id?.toString() },
filters: { type },
});
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return <Skeleton w="500px" h="50px"/>;
}
return (
<>
{ isMobile && isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) }
<TokensTable data={ data.items } top={ isPaginationVisible ? 72 : 0 }/>
</>
);
};
export default TokensERC20;
import { Table, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import { default as Thead } from 'ui/shared/TheadSticky';
import TokensTableItem from './TokensTableItem';
interface Props {
data: Array<AddressTokenBalance>;
top: number;
}
const TokensTable = ({ data, top }: Props) => {
return (
<Table variant="simple" size="sm">
<Thead top={ top }>
<Tr>
<Th width="20%">Asset</Th>
<Th width="40%">Contract address</Th>
<Th width="10%" isNumeric>Price</Th>
<Th width="20%" isNumeric>Quantity</Th>
<Th width="10%" isNumeric>Value</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<TokensTableItem key={ item.token.address } { ...item }/>
)) }
</Tbody>
</Table>
);
};
export default TokensTable;
import { Tr, Td, Flex } from '@chakra-ui/react';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressAddToMetaMask from '../details/AddressAddToMetaMask';
type Props = AddressTokenBalance;
const TokensTableItem = ({
token,
value,
}: Props) => {
const tokenString = [ token.name, token.symbol && `(${ token.symbol })` ].filter(Boolean).join(' ');
const {
valueStr: tokenQuantity,
usd: tokenValue,
} = getCurrencyValue({ value: value, exchangeRate: token.exchange_rate, decimals: token.decimals, accuracy: 8, accuracyUsd: 2 });
return (
<Tr>
<Td verticalAlign="middle">
<AddressLink fontWeight="700" hash={ token.address } type="token" alias={ tokenString }/>
</Td>
<Td verticalAlign="middle">
<Flex alignItems="center" width="150px" justifyContent="space-between">
<Flex alignItems="center">
<AddressLink hash={ token.address } type="address" truncation="constant"/>
<CopyToClipboard text={ token.address } ml={ 1 }/>
</Flex>
<AddressAddToMetaMask token={ token } ml={ 4 }/>
</Flex>
</Td>
<Td isNumeric verticalAlign="middle">
{ token.exchange_rate ? `$${ token.exchange_rate }` : '-' }
</Td>
<Td isNumeric verticalAlign="middle">
{ tokenQuantity }
</Td>
<Td isNumeric verticalAlign="middle">
{ tokenValue ? `$${ tokenValue }` : '-' }
</Td>
</Tr>
);
};
export default React.memo(TokensTableItem);
...@@ -2,6 +2,7 @@ import { Flex, Skeleton, Tag, Box } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Flex, Skeleton, Tag, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
...@@ -18,12 +19,25 @@ import AddressTxs from 'ui/address/AddressTxs'; ...@@ -18,12 +19,25 @@ import AddressTxs from 'ui/address/AddressTxs';
import ContractCode from 'ui/address/contract/ContractCode'; import ContractCode from 'ui/address/contract/ContractCode';
import ContractRead from 'ui/address/contract/ContractRead'; import ContractRead from 'ui/address/contract/ContractRead';
import ContractWrite from 'ui/address/contract/ContractWrite'; import ContractWrite from 'ui/address/contract/ContractWrite';
import Tokens from 'ui/address/tokens/Tokens';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import SkeletonTabs from 'ui/shared/skeletons/SkeletonTabs'; import SkeletonTabs from 'ui/shared/skeletons/SkeletonTabs';
export const tokenTabsByType: Record<TokenType, string> = {
'ERC-20': 'tokens_erc20',
'ERC-721': 'tokens_erc721',
'ERC-1155': 'tokens_erc1155',
} as const;
const TOKENS_TABS = [
{ id: tokenTabsByType['ERC-20'], title: 'ERC-20', component: <Tokens type="ERC-20"/> },
{ id: tokenTabsByType['ERC-721'], title: 'ERC-721', component: null },
{ id: tokenTabsByType['ERC-1155'], title: 'ERC-1155', component: null },
];
const AddressPageContent = () => { const AddressPageContent = () => {
const router = useRouter(); const router = useRouter();
...@@ -73,7 +87,7 @@ const AddressPageContent = () => { ...@@ -73,7 +87,7 @@ const AddressPageContent = () => {
addressQuery.data?.has_token_transfers ? addressQuery.data?.has_token_transfers ?
{ id: 'token_transfers', title: 'Token transfers', component: <AddressTokenTransfers scrollRef={ tabsScrollRef }/> } : { id: 'token_transfers', title: 'Token transfers', component: <AddressTokenTransfers scrollRef={ tabsScrollRef }/> } :
undefined, undefined,
addressQuery.data?.has_tokens ? { id: 'tokens', title: 'Tokens', component: <AddressTokens/> } : undefined, addressQuery.data?.has_tokens ? { id: 'tokens', title: 'Tokens', component: <AddressTokens tabs={ TOKENS_TABS }/>, subTabs: TOKENS_TABS } : undefined,
{ id: 'internal_txns', title: 'Internal txns', component: <AddressInternalTxs scrollRef={ tabsScrollRef }/> }, { id: 'internal_txns', title: 'Internal txns', component: <AddressInternalTxs scrollRef={ tabsScrollRef }/> },
{ id: 'coin_balance_history', title: 'Coin balance history', component: <AddressCoinBalance/> }, { id: 'coin_balance_history', title: 'Coin balance history', component: <AddressCoinBalance/> },
addressQuery.data?.has_validated_blocks ? addressQuery.data?.has_validated_blocks ?
......
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