Commit 09ade688 authored by tom's avatar tom

skeletons for address tokens

parent 8071589e
...@@ -296,7 +296,7 @@ export const RESOURCES = { ...@@ -296,7 +296,7 @@ export const RESOURCES = {
address_tokens: { address_tokens: {
path: '/api/v2/addresses/:hash/tokens', path: '/api/v2/addresses/:hash/tokens',
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'token_name' as const, 'token_type' as const, 'value' as const ], paginationFields: [ 'items_count' as const, 'token_name' as const, 'token_type' as const, 'value' as const, 'fiat_value' as const, 'id' as const ],
filterFields: [ 'type' as const ], filterFields: [ 'type' as const ],
}, },
address_withdrawals: { address_withdrawals: {
......
import type { Address, AddressCoinBalanceHistoryItem, AddressCounters } from 'types/api/address'; import type { Address, AddressCoinBalanceHistoryItem, AddressCounters, AddressTokenBalance } from 'types/api/address';
import type { AddressesItem } from 'types/api/addresses'; import type { AddressesItem } from 'types/api/addresses';
import { ADDRESS_HASH } from './addressParams'; import { ADDRESS_HASH } from './addressParams';
import { TOKEN_INFO_ERC_20 } from './token'; import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INSTANCE } from './token';
import { TX_HASH } from './tx'; import { TX_HASH } from './tx';
export const ADDRESS_INFO: Address = { export const ADDRESS_INFO: Address = {
...@@ -62,3 +62,24 @@ export const ADDRESS_COIN_BALANCE: AddressCoinBalanceHistoryItem = { ...@@ -62,3 +62,24 @@ export const ADDRESS_COIN_BALANCE: AddressCoinBalanceHistoryItem = {
transaction_hash: TX_HASH, transaction_hash: TX_HASH,
value: '953427250000000000000000', value: '953427250000000000000000',
}; };
export const ADDRESS_TOKEN_BALANCE_ERC_20: AddressTokenBalance = {
token: TOKEN_INFO_ERC_20,
token_id: null,
token_instance: null,
value: '1000000000000000000000000',
};
export const ADDRESS_TOKEN_BALANCE_ERC_721: AddressTokenBalance = {
token: TOKEN_INFO_ERC_721,
token_id: null,
token_instance: null,
value: '176',
};
export const ADDRESS_TOKEN_BALANCE_ERC_1155: AddressTokenBalance = {
token: TOKEN_INFO_ERC_1155,
token_id: '188882',
token_instance: TOKEN_INSTANCE,
value: '176',
};
...@@ -45,7 +45,7 @@ export interface AddressTokenBalance { ...@@ -45,7 +45,7 @@ export interface AddressTokenBalance {
token: TokenInfo; token: TokenInfo;
token_id: string | null; token_id: string | null;
value: string; value: string;
token_instance?: TokenInstance; token_instance: TokenInstance | null;
} }
export interface AddressTokensResponse { export interface AddressTokensResponse {
...@@ -55,6 +55,7 @@ export interface AddressTokensResponse { ...@@ -55,6 +55,7 @@ export interface AddressTokensResponse {
token_name: 'string' | null; token_name: 'string' | null;
token_type: TokenType; token_type: TokenType;
value: number; value: number;
fiat_value: string | null;
} | null; } | null;
} }
......
...@@ -6,6 +6,8 @@ import type { TokenType } from 'types/api/token'; ...@@ -6,6 +6,8 @@ import type { TokenType } from 'types/api/token';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { ADDRESS_TOKEN_BALANCE_ERC_1155, ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_TOKEN_BALANCE_ERC_721 } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import { tokenTabsByType } from 'ui/pages/Address'; import { tokenTabsByType } from 'ui/pages/Address';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination'; import type { Props as PaginationProps } from 'ui/shared/Pagination';
...@@ -43,7 +45,9 @@ const AddressTokens = () => { ...@@ -43,7 +45,9 @@ const AddressTokens = () => {
filters: { type: 'ERC-20' }, filters: { type: 'ERC-20' },
scrollRef, scrollRef,
options: { options: {
refetchOnMount: false,
enabled: tokenType === 'ERC-20', enabled: tokenType === 'ERC-20',
placeholderData: generateListStub<'address_tokens'>(ADDRESS_TOKEN_BALANCE_ERC_20, 10, { next_page_params: null }),
}, },
}); });
...@@ -53,7 +57,9 @@ const AddressTokens = () => { ...@@ -53,7 +57,9 @@ const AddressTokens = () => {
filters: { type: 'ERC-721' }, filters: { type: 'ERC-721' },
scrollRef, scrollRef,
options: { options: {
refetchOnMount: false,
enabled: tokenType === 'ERC-721', enabled: tokenType === 'ERC-721',
placeholderData: generateListStub<'address_tokens'>(ADDRESS_TOKEN_BALANCE_ERC_721, 10, { next_page_params: null }),
}, },
}); });
...@@ -63,7 +69,9 @@ const AddressTokens = () => { ...@@ -63,7 +69,9 @@ const AddressTokens = () => {
filters: { type: 'ERC-1155' }, filters: { type: 'ERC-1155' },
scrollRef, scrollRef,
options: { options: {
refetchOnMount: false,
enabled: tokenType === 'ERC-1155', enabled: tokenType === 'ERC-1155',
placeholderData: generateListStub<'address_tokens'>(ADDRESS_TOKEN_BALANCE_ERC_1155, 10, { next_page_params: null }),
}, },
}); });
......
import { Grid, Skeleton } from '@chakra-ui/react'; import { Grid } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -22,7 +22,7 @@ type Props = { ...@@ -22,7 +22,7 @@ type Props = {
const ERC1155Tokens = ({ tokensQuery }: Props) => { const ERC1155Tokens = ({ tokensQuery }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isLoading, data, pagination, isPaginationVisible } = tokensQuery; const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = tokensQuery;
const actionBar = isMobile && isPaginationVisible && ( const actionBar = isMobile && isPaginationVisible && (
<ActionBar mt={ -6 }> <ActionBar mt={ -6 }>
...@@ -30,21 +30,6 @@ const ERC1155Tokens = ({ tokensQuery }: Props) => { ...@@ -30,21 +30,6 @@ const ERC1155Tokens = ({ tokensQuery }: Props) => {
</ActionBar> </ActionBar>
); );
const skeleton = (
<Grid
w="100%"
columnGap={{ base: 3, lg: 6 }}
rowGap={{ base: 3, lg: 6 }}
gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }}
>
<Skeleton w={{ base: '100%', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: '100%', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: '100%', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: '100%', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: '100%', lg: '210px' }} h="272px"/>
</Grid>
);
const content = data?.items ? ( const content = data?.items ? (
<Grid <Grid
w="100%" w="100%"
...@@ -52,19 +37,29 @@ const ERC1155Tokens = ({ tokensQuery }: Props) => { ...@@ -52,19 +37,29 @@ const ERC1155Tokens = ({ tokensQuery }: Props) => {
rowGap={{ base: 3, lg: 6 }} rowGap={{ base: 3, lg: 6 }}
gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }} gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }}
> >
{ data.items.map(item => <NFTItem key={ item.token.address } { ...item }/>) } { data.items.map((item, index) => {
const key = item.token.address + '_' + (item.token_instance?.id && !isPlaceholderData ? `id_${ item.token_instance?.id }` : `index_${ index }`);
return (
<NFTItem
key={ key }
{ ...item }
isLoading={ isPlaceholderData }
/>
);
}) }
</Grid> </Grid>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ false }
items={ data?.items } items={ data?.items }
emptyText="There are no tokens of selected type." emptyText="There are no tokens of selected type."
content={ content } content={ content }
actionBar={ actionBar } actionBar={ actionBar }
skeletonProps={{ customSkeleton: skeleton }} skeletonProps={{ customSkeleton: null }}
/> />
); );
}; };
......
...@@ -23,7 +23,7 @@ type Props = { ...@@ -23,7 +23,7 @@ type Props = {
const ERC20Tokens = ({ tokensQuery }: Props) => { const ERC20Tokens = ({ tokensQuery }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isLoading, data, pagination, isPaginationVisible } = tokensQuery; const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = tokensQuery;
const actionBar = isMobile && isPaginationVisible && ( const actionBar = isMobile && isPaginationVisible && (
<ActionBar mt={ -6 }> <ActionBar mt={ -6 }>
...@@ -33,14 +33,20 @@ const ERC20Tokens = ({ tokensQuery }: Props) => { ...@@ -33,14 +33,20 @@ const ERC20Tokens = ({ tokensQuery }: Props) => {
const content = data?.items ? ( const content = data?.items ? (
<> <>
<Hide below="lg" ssr={ false }><ERC20TokensTable data={ data.items } top={ isPaginationVisible ? 72 : 0 }/></Hide> <Hide below="lg" ssr={ false }><ERC20TokensTable data={ data.items } top={ isPaginationVisible ? 72 : 0 } isLoading={ isPlaceholderData }/></Hide>
<Show below="lg" ssr={ false }>{ data.items.map(item => <ERC20TokensListItem key={ item.token.address } { ...item }/>) }</Show></> <Show below="lg" ssr={ false }>{ data.items.map((item, index) => (
<ERC20TokensListItem
key={ item.token.address + (isPlaceholderData ? index : '') }
{ ...item }
isLoading={ isPlaceholderData }
/>
)) }</Show></>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ false }
items={ data?.items } items={ data?.items }
skeletonProps={{ skeletonProps={{
isLongSkeleton: true, isLongSkeleton: true,
......
import { Flex, HStack, Text } from '@chakra-ui/react'; import { Flex, HStack, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressTokenBalance } from 'types/api/address';
...@@ -10,9 +10,9 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard'; ...@@ -10,9 +10,9 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
type Props = AddressTokenBalance; type Props = AddressTokenBalance & { isLoading: boolean};
const ERC20TokensListItem = ({ token, value }: Props) => { const ERC20TokensListItem = ({ token, value, isLoading }: Props) => {
const tokenString = [ token.name, token.symbol && `(${ token.symbol })` ].filter(Boolean).join(' '); const tokenString = [ token.name, token.symbol && `(${ token.symbol })` ].filter(Boolean).join(' ');
...@@ -24,28 +24,34 @@ const ERC20TokensListItem = ({ token, value }: Props) => { ...@@ -24,28 +24,34 @@ const ERC20TokensListItem = ({ token, value }: Props) => {
return ( return (
<ListItemMobile rowGap={ 2 }> <ListItemMobile rowGap={ 2 }>
<Flex alignItems="center" width="100%"> <Flex alignItems="center" width="100%">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 }/> <TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontWeight="700" hash={ token.address } type="token" alias={ tokenString }/> <AddressLink fontWeight="700" hash={ token.address } type="token" alias={ tokenString } isLoading={ isLoading }/>
</Flex> </Flex>
<Flex alignItems="center" pl={ 8 }> <Flex alignItems="center" pl={ 8 }>
<AddressLink hash={ token.address } type="address" truncation="constant"/> <AddressLink hash={ token.address } type="address" truncation="constant" isLoading={ isLoading }/>
<CopyToClipboard text={ token.address } ml={ 1 }/> <CopyToClipboard text={ token.address } isLoading={ isLoading }/>
<AddressAddToWallet token={ token } ml={ 2 }/> <AddressAddToWallet token={ token } ml={ 2 } isLoading={ isLoading }/>
</Flex> </Flex>
{ token.exchange_rate !== undefined && token.exchange_rate !== null && ( { token.exchange_rate !== undefined && token.exchange_rate !== null && (
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Price</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Price</Skeleton>
<Text fontSize="sm" variant="secondary">{ `$${ token.exchange_rate }` }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
<span>{ `$${ token.exchange_rate }` }</span>
</Skeleton>
</HStack> </HStack>
) } ) }
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Quantity</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Quantity</Skeleton>
<Text fontSize="sm" variant="secondary">{ tokenQuantity }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
<span>{ tokenQuantity }</span>
</Skeleton>
</HStack> </HStack>
{ tokenValue !== undefined && ( { tokenValue !== undefined && (
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Value</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Value</Skeleton>
<Text fontSize="sm" variant="secondary">{ tokenValue }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
<span>{ tokenValue }</span>
</Skeleton>
</HStack> </HStack>
) } ) }
</ListItemMobile> </ListItemMobile>
......
...@@ -10,9 +10,10 @@ import ERC20TokensTableItem from './ERC20TokensTableItem'; ...@@ -10,9 +10,10 @@ import ERC20TokensTableItem from './ERC20TokensTableItem';
interface Props { interface Props {
data: Array<AddressTokenBalance>; data: Array<AddressTokenBalance>;
top: number; top: number;
isLoading: boolean;
} }
const ERC20TokensTable = ({ data, top }: Props) => { const ERC20TokensTable = ({ data, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ top }> <Thead top={ top }>
...@@ -25,8 +26,8 @@ const ERC20TokensTable = ({ data, top }: Props) => { ...@@ -25,8 +26,8 @@ const ERC20TokensTable = ({ data, top }: Props) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item) => ( { data.map((item, index) => (
<ERC20TokensTableItem key={ item.token.address } { ...item }/> <ERC20TokensTableItem key={ item.token.address + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
import { Tr, Td, Flex } from '@chakra-ui/react'; import { Tr, Td, Flex, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressTokenBalance } from 'types/api/address'; import type { AddressTokenBalance } from 'types/api/address';
...@@ -9,11 +9,12 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -9,11 +9,12 @@ import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
type Props = AddressTokenBalance; type Props = AddressTokenBalance & { isLoading: boolean };
const ERC20TokensTableItem = ({ const ERC20TokensTableItem = ({
token, token,
value, value,
isLoading,
}: Props) => { }: Props) => {
const tokenString = [ token.name, token.symbol && `(${ token.symbol })` ].filter(Boolean).join(' '); const tokenString = [ token.name, token.symbol && `(${ token.symbol })` ].filter(Boolean).join(' ');
...@@ -27,27 +28,33 @@ const ERC20TokensTableItem = ({ ...@@ -27,27 +28,33 @@ const ERC20TokensTableItem = ({
<Tr> <Tr>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Flex alignItems="center"> <Flex alignItems="center">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 }/> <TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontWeight="700" hash={ token.address } type="token" alias={ tokenString }/> <AddressLink fontWeight="700" hash={ token.address } type="token" alias={ tokenString } isLoading={ isLoading }/>
</Flex> </Flex>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Flex alignItems="center" width="150px" justifyContent="space-between"> <Flex alignItems="center" width="150px" justifyContent="space-between">
<Flex alignItems="center"> <Flex alignItems="center">
<AddressLink hash={ token.address } type="address" truncation="constant"/> <AddressLink hash={ token.address } type="address" truncation="constant" isLoading={ isLoading }/>
<CopyToClipboard text={ token.address } ml={ 1 }/> <CopyToClipboard text={ token.address } isLoading={ isLoading }/>
</Flex> </Flex>
<AddressAddToWallet token={ token } ml={ 4 }/> <AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading }/>
</Flex> </Flex>
</Td> </Td>
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
{ token.exchange_rate && `$${ token.exchange_rate }` } <Skeleton isLoaded={ !isLoading } display="inline-block">
{ token.exchange_rate && `$${ token.exchange_rate }` }
</Skeleton>
</Td> </Td>
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
{ tokenQuantity } <Skeleton isLoaded={ !isLoading } display="inline">
{ tokenQuantity }
</Skeleton>
</Td> </Td>
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
{ tokenValue && `$${ tokenValue }` } <Skeleton isLoaded={ !isLoading } display="inline">
{ tokenValue && `$${ tokenValue }` }
</Skeleton>
</Td> </Td>
</Tr> </Tr>
); );
......
...@@ -23,7 +23,7 @@ type Props = { ...@@ -23,7 +23,7 @@ type Props = {
const ERC721Tokens = ({ tokensQuery }: Props) => { const ERC721Tokens = ({ tokensQuery }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isLoading, data, pagination, isPaginationVisible } = tokensQuery; const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = tokensQuery;
const actionBar = isMobile && isPaginationVisible && ( const actionBar = isMobile && isPaginationVisible && (
<ActionBar mt={ -6 }> <ActionBar mt={ -6 }>
...@@ -33,14 +33,20 @@ const ERC721Tokens = ({ tokensQuery }: Props) => { ...@@ -33,14 +33,20 @@ const ERC721Tokens = ({ tokensQuery }: Props) => {
const content = data?.items ? ( const content = data?.items ? (
<> <>
<Hide below="lg" ssr={ false }><ERC721TokensTable data={ data.items } top={ isPaginationVisible ? 72 : 0 }/></Hide> <Hide below="lg" ssr={ false }><ERC721TokensTable data={ data.items } isLoading={ isPlaceholderData } top={ isPaginationVisible ? 72 : 0 }/></Hide>
<Show below="lg" ssr={ false }>{ data.items.map(item => <ERC721TokensListItem key={ item.token.address } { ...item }/>) }</Show></> <Show below="lg" ssr={ false }>{ data.items.map((item, index) => (
<ERC721TokensListItem
key={ item.token.address + (isPlaceholderData ? index : '') }
{ ...item }
isLoading={ isPlaceholderData }
/>
)) }</Show></>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ false }
items={ data?.items } items={ data?.items }
skeletonProps={{ skeletonProps={{
isLongSkeleton: true, isLongSkeleton: true,
......
import { Flex, HStack, Text } from '@chakra-ui/react'; import { Flex, HStack, Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -10,9 +10,9 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard'; ...@@ -10,9 +10,9 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
type Props = AddressTokenBalance; type Props = AddressTokenBalance & { isLoading: boolean};
const ERC721TokensListItem = ({ token, value }: Props) => { const ERC721TokensListItem = ({ token, value, isLoading }: Props) => {
const router = useRouter(); const router = useRouter();
const hash = router.query.hash?.toString() || ''; const hash = router.query.hash?.toString() || '';
...@@ -22,17 +22,19 @@ const ERC721TokensListItem = ({ token, value }: Props) => { ...@@ -22,17 +22,19 @@ const ERC721TokensListItem = ({ token, value }: Props) => {
return ( return (
<ListItemMobile rowGap={ 2 }> <ListItemMobile rowGap={ 2 }>
<Flex alignItems="center" width="100%"> <Flex alignItems="center" width="100%">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 }/> <TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontWeight="700" hash={ hash } tokenHash={ token.address } type="address_token" alias={ tokenString }/> <AddressLink fontWeight="700" hash={ hash } tokenHash={ token.address } type="address_token" alias={ tokenString } isLoading={ isLoading }/>
</Flex> </Flex>
<Flex alignItems="center" pl={ 8 }> <Flex alignItems="center" pl={ 8 }>
<AddressLink hash={ token.address } type="address" truncation="constant"/> <AddressLink hash={ token.address } type="address" truncation="constant" isLoading={ isLoading }/>
<CopyToClipboard text={ token.address } ml={ 1 }/> <CopyToClipboard text={ token.address } isLoading={ isLoading }/>
<AddressAddToWallet token={ token } ml={ 2 }/> <AddressAddToWallet token={ token } ml={ 2 } isLoading={ isLoading }/>
</Flex> </Flex>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Quantity</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Quantity</Skeleton>
<Text fontSize="sm" variant="secondary">{ value }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary">
<span>{ value }</span>
</Skeleton>
</HStack> </HStack>
</ListItemMobile> </ListItemMobile>
); );
......
...@@ -10,9 +10,10 @@ import ERC721TokensTableItem from './ERC721TokensTableItem'; ...@@ -10,9 +10,10 @@ import ERC721TokensTableItem from './ERC721TokensTableItem';
interface Props { interface Props {
data: Array<AddressTokenBalance>; data: Array<AddressTokenBalance>;
top: number; top: number;
isLoading: boolean;
} }
const ERC721TokensTable = ({ data, top }: Props) => { const ERC721TokensTable = ({ data, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ top }> <Thead top={ top }>
...@@ -23,8 +24,8 @@ const ERC721TokensTable = ({ data, top }: Props) => { ...@@ -23,8 +24,8 @@ const ERC721TokensTable = ({ data, top }: Props) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item) => ( { data.map((item, index) => (
<ERC721TokensTableItem key={ item.token.address } { ...item }/> <ERC721TokensTableItem key={ item.token.address + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
import { Tr, Td, Flex } from '@chakra-ui/react'; import { Tr, Td, Flex, Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -9,11 +9,12 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -9,11 +9,12 @@ import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
type Props = AddressTokenBalance; type Props = AddressTokenBalance & { isLoading: boolean};
const ERC721TokensTableItem = ({ const ERC721TokensTableItem = ({
token, token,
value, value,
isLoading,
}: Props) => { }: Props) => {
const router = useRouter(); const router = useRouter();
...@@ -24,21 +25,23 @@ const ERC721TokensTableItem = ({ ...@@ -24,21 +25,23 @@ const ERC721TokensTableItem = ({
<Tr> <Tr>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Flex alignItems="center"> <Flex alignItems="center">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 }/> <TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontWeight="700" hash={ hash } tokenHash={ token.address } type="address_token" alias={ tokenString }/> <AddressLink fontWeight="700" hash={ hash } tokenHash={ token.address } type="address_token" alias={ tokenString } isLoading={ isLoading }/>
</Flex> </Flex>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Flex alignItems="center" width="150px" justifyContent="space-between"> <Flex alignItems="center" width="150px" justifyContent="space-between">
<Flex alignItems="center"> <Flex alignItems="center">
<AddressLink hash={ token.address } type="address" truncation="dynamic"/> <AddressLink hash={ token.address } type="address" truncation="dynamic" isLoading={ isLoading }/>
<CopyToClipboard text={ token.address } ml={ 1 }/> <CopyToClipboard text={ token.address } isLoading={ isLoading }/>
</Flex> </Flex>
<AddressAddToWallet token={ token } ml={ 4 }/> <AddressAddToWallet token={ token } ml={ 4 } isLoading={ isLoading }/>
</Flex> </Flex>
</Td> </Td>
<Td isNumeric verticalAlign="middle"> <Td isNumeric verticalAlign="middle">
{ value } <Skeleton isLoaded={ !isLoading } display="inline-block">
{ value }
</Skeleton>
</Td> </Td>
</Tr> </Tr>
); );
......
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue } from '@chakra-ui/react'; import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -8,9 +8,9 @@ import NftMedia from 'ui/shared/nft/NftMedia'; ...@@ -8,9 +8,9 @@ import NftMedia from 'ui/shared/nft/NftMedia';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = AddressTokenBalance; type Props = AddressTokenBalance & { isLoading: boolean };
const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance }: Props) => { const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLoading }: Props) => {
const tokenLink = route({ pathname: '/token/[hash]', query: { hash: token.address } }); const tokenLink = route({ pathname: '/token/[hash]', query: { hash: token.address } });
return ( return (
...@@ -25,33 +25,40 @@ const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance }: Pr ...@@ -25,33 +25,40 @@ const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance }: Pr
fontWeight={ 500 } fontWeight={ 500 }
lineHeight="20px" lineHeight="20px"
> >
<LinkOverlay href={ tokenLink }> <LinkOverlay href={ isLoading ? undefined : tokenLink }>
<NftMedia <NftMedia
mb="18px" mb="18px"
imageUrl={ tokenInstance?.image_url || null } imageUrl={ tokenInstance?.image_url || null }
animationUrl={ tokenInstance?.animation_url || null } animationUrl={ tokenInstance?.animation_url || null }
isLoading={ isLoading }
/> />
</LinkOverlay> </LinkOverlay>
{ tokenId && ( { tokenId && (
<Flex mb={ 2 } ml={ 1 }> <Flex mb={ 2 } ml={ 1 }>
<Text whiteSpace="pre" variant="secondary">ID# </Text> <Text whiteSpace="pre" variant="secondary">ID# </Text>
<TruncatedTextTooltip label={ tokenId }> <TruncatedTextTooltip label={ tokenId }>
<Link <Skeleton isLoaded={ !isLoading } overflow="hidden" h="20px">
overflow="hidden" <Link
whiteSpace="nowrap" w="100%"
textOverflow="ellipsis" display="inline-block"
href={ route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } }) } whiteSpace="nowrap"
> textOverflow="ellipsis"
{ tokenId } overflow="hidden"
</Link> href={ route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } }) }
>
{ tokenId }
</Link>
</Skeleton>
</TruncatedTextTooltip> </TruncatedTextTooltip>
</Flex> </Flex>
) } ) }
{ token.name && ( { token.name && (
<Flex alignItems="center"> <Flex alignItems="center">
<TokenLogo data={ token } boxSize={ 6 } ml={ 1 } mr={ 1 }/> <TokenLogo data={ token } boxSize={ 6 } ml={ 1 } mr={ 1 } isLoading={ isLoading }/>
<TruncatedTextTooltip label={ token.name }> <TruncatedTextTooltip label={ token.name }>
<Text variant="secondary" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">{ token.name }</Text> <Skeleton isLoaded={ !isLoading } color="text_secondary" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">
<span>{ token.name }</span>
</Skeleton>
</TruncatedTextTooltip> </TruncatedTextTooltip>
</Flex> </Flex>
) } ) }
......
import { Flex, Skeleton } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -19,7 +19,7 @@ const TokenBalances = () => { ...@@ -19,7 +19,7 @@ const TokenBalances = () => {
const addressQuery = useApiQuery('address', { const addressQuery = useApiQuery('address', {
pathParams: { hash }, pathParams: { hash },
queryOptions: { enabled: Boolean(hash) }, queryOptions: { enabled: Boolean(hash), refetchOnMount: false },
}); });
const tokenQuery = useFetchTokens({ hash }); const tokenQuery = useFetchTokens({ hash });
...@@ -28,23 +28,12 @@ const TokenBalances = () => { ...@@ -28,23 +28,12 @@ const TokenBalances = () => {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
if (addressQuery.isLoading || tokenQuery.isLoading) {
const item = <Skeleton w={{ base: '100%', lg: '240px' }} h="82px" borderRadius="16px"/>;
return (
<Flex columnGap={ 3 } rowGap={ 3 } mt={{ base: '6px', lg: 0 }} flexDirection={{ base: 'column', lg: 'row' }}>
{ item }
{ item }
{ item }
</Flex>
);
}
const addressData = addressQuery.data; const addressData = addressQuery.data;
const { valueStr: nativeValue, usdBn: nativeUsd } = getCurrencyValue({ const { valueStr: nativeValue, usdBn: nativeUsd } = getCurrencyValue({
value: addressData.coin_balance || '0', value: addressData?.coin_balance || '0',
accuracy: 8, accuracy: 8,
accuracyUsd: 2, accuracyUsd: 2,
exchangeRate: addressData.exchange_rate, exchangeRate: addressData?.exchange_rate,
decimals: String(appConfig.network.currency.decimals), decimals: String(appConfig.network.currency.decimals),
}); });
...@@ -57,10 +46,15 @@ const TokenBalances = () => { ...@@ -57,10 +46,15 @@ const TokenBalances = () => {
return ( return (
<Flex columnGap={ 3 } rowGap={ 3 } mt={{ base: '6px', lg: 0 }} flexDirection={{ base: 'column', lg: 'row' }}> <Flex columnGap={ 3 } rowGap={ 3 } mt={{ base: '6px', lg: 0 }} flexDirection={{ base: 'column', lg: 'row' }}>
<TokenBalancesItem name="Net Worth" value={ addressData.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) } USD` : 'N/A' }/> <TokenBalancesItem
name="Net Worth"
value={ addressData?.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) } USD` : 'N/A' }
isLoading={ addressQuery.isLoading || tokenQuery.isLoading }
/>
<TokenBalancesItem <TokenBalancesItem
name={ `${ appConfig.network.currency.symbol } Balance` } name={ `${ appConfig.network.currency.symbol } Balance` }
value={ (!nativeUsd.eq(ZERO) ? `$${ nativeUsd.toFormat(2) } USD | ` : '') + `${ nativeValue } ${ appConfig.network.currency.symbol }` } value={ (!nativeUsd.eq(ZERO) ? `$${ nativeUsd.toFormat(2) } USD | ` : '') + `${ nativeValue } ${ appConfig.network.currency.symbol }` }
isLoading={ addressQuery.isLoading || tokenQuery.isLoading }
/> />
<TokenBalancesItem <TokenBalancesItem
name="Tokens" name="Tokens"
...@@ -68,9 +62,10 @@ const TokenBalances = () => { ...@@ -68,9 +62,10 @@ const TokenBalances = () => {
`${ prefix }$${ tokensInfo.usd.toFormat(2) } USD ` + `${ prefix }$${ tokensInfo.usd.toFormat(2) } USD ` +
tokensNumText tokensNumText
} }
isLoading={ addressQuery.isLoading || tokenQuery.isLoading }
/> />
</Flex> </Flex>
); );
}; };
export default React.memo(TokenBalances); export default TokenBalances;
import { Box, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, Icon, Skeleton, Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import walletIcon from 'icons/wallet.svg'; import walletIcon from 'icons/wallet.svg';
const TokenBalancesItem = ({ name, value }: {name: string; value: string }) => { const TokenBalancesItem = ({ name, value, isLoading }: {name: string; value: string; isLoading: boolean }) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
...@@ -12,7 +12,7 @@ const TokenBalancesItem = ({ name, value }: {name: string; value: string }) => { ...@@ -12,7 +12,7 @@ const TokenBalancesItem = ({ name, value }: {name: string; value: string }) => {
<Icon as={ walletIcon } boxSize="30px" mr={ 3 }/> <Icon as={ walletIcon } boxSize="30px" mr={ 3 }/>
<Box> <Box>
<Text variant="secondary" fontSize="xs">{ name }</Text> <Text variant="secondary" fontSize="xs">{ name }</Text>
<Text fontWeight="500">{ value }</Text> <Skeleton isLoaded={ !isLoading } fontWeight="500">{ value }</Skeleton>
</Box> </Box>
</Flex> </Flex>
); );
......
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