Commit f884b1f5 authored by tom's avatar tom

skeletons for tokens page

parent 0bc65c00
...@@ -383,7 +383,7 @@ export const RESOURCES = { ...@@ -383,7 +383,7 @@ export const RESOURCES = {
}, },
tokens: { tokens: {
path: '/api/v2/tokens', path: '/api/v2/tokens',
paginationFields: [ 'holder_count' as const, 'items_count' as const, 'name' as const ], paginationFields: [ 'holder_count' as const, 'items_count' as const, 'name' as const, 'market_cap' as const ],
filterFields: [ 'q' as const, 'type' as const ], filterFields: [ 'q' as const, 'type' as const ],
}, },
......
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Tokens from 'ui/pages/Tokens'; import Page from 'ui/shared/Page/Page';
const Tokens = dynamic(() => import('ui/pages/Tokens'), { ssr: false });
const TokensPage: NextPage = () => { const TokensPage: NextPage = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
...@@ -12,7 +15,9 @@ const TokensPage: NextPage = () => { ...@@ -12,7 +15,9 @@ const TokensPage: NextPage = () => {
<Head> <Head>
<title>{ title }</title> <title>{ title }</title>
</Head> </Head>
<Tokens/> <Page>
<Tokens/>
</Page>
</> </>
); );
}; };
......
...@@ -9,11 +9,11 @@ import { generateListStub } from './utils'; ...@@ -9,11 +9,11 @@ import { generateListStub } from './utils';
export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = { export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = {
address: ADDRESS_HASH, address: ADDRESS_HASH,
decimals: '18', decimals: '18',
exchange_rate: null, exchange_rate: '0.999997',
holders: '16026', holders: '16026',
name: 'Stub Token (goerli)', name: 'Stub Token (goerli)',
symbol: 'STUB', symbol: 'STUB',
total_supply: '6000000000000000000', total_supply: '60000000000000000000000',
type: 'ERC-20', type: 'ERC-20',
icon_url: null, icon_url: null,
}; };
......
...@@ -7,6 +7,7 @@ export type TokensResponse = { ...@@ -7,6 +7,7 @@ export type TokensResponse = {
holder_count: number; holder_count: number;
items_count: number; items_count: number;
name: string; name: string;
market_cap: string | null;
}; };
} }
......
import React from 'react'; import React from 'react';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import TokensList from 'ui/tokens/Tokens'; import TokensList from 'ui/tokens/Tokens';
const Tokens = () => { const Tokens = () => {
return ( return (
<Page> <>
<PageTitle title="Tokens" withTextAd/> <PageTitle title="Tokens" withTextAd/>
<TokensList/> <TokensList/>
</Page> </>
); );
}; };
......
import { Box, chakra, Icon, Tooltip } from '@chakra-ui/react'; import { Box, chakra, Icon, Skeleton, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
...@@ -11,9 +11,10 @@ import { WALLETS_INFO } from 'lib/web3/wallets'; ...@@ -11,9 +11,10 @@ import { WALLETS_INFO } from 'lib/web3/wallets';
interface Props { interface Props {
className?: string; className?: string;
token: TokenInfo; token: TokenInfo;
isLoading?: boolean;
} }
const AddressAddToWallet = ({ className, token }: Props) => { const AddressAddToWallet = ({ className, token, isLoading }: Props) => {
const toast = useToast(); const toast = useToast();
const provider = useProvider(); const provider = useProvider();
...@@ -59,6 +60,10 @@ const AddressAddToWallet = ({ className, token }: Props) => { ...@@ -59,6 +60,10 @@ const AddressAddToWallet = ({ className, token }: Props) => {
return null; return null;
} }
if (isLoading) {
return <Skeleton className={ className } boxSize={ 6 } borderRadius="base"/>;
}
const defaultWallet = appConfig.web3.defaultWallet; const defaultWallet = appConfig.web3.defaultWallet;
return ( return (
......
...@@ -9,6 +9,8 @@ import useDebounce from 'lib/hooks/useDebounce'; ...@@ -9,6 +9,8 @@ import useDebounce from 'lib/hooks/useDebounce';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import TOKEN_TYPE from 'lib/token/tokenTypes'; import TOKEN_TYPE from 'lib/token/tokenTypes';
import { TOKEN_INFO_ERC_20 } from 'stubs/token';
import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
...@@ -25,14 +27,22 @@ const getTokenFilterValue = (getFilterValuesFromQuery<TokenType>).bind(null, TOK ...@@ -25,14 +27,22 @@ const getTokenFilterValue = (getFilterValuesFromQuery<TokenType>).bind(null, TOK
const Tokens = () => { const Tokens = () => {
const router = useRouter(); const router = useRouter();
const [ filter, setFilter ] = React.useState<string>(router.query.filter?.toString() || ''); const [ filter, setFilter ] = React.useState<string>(router.query.q?.toString() || '');
const [ type, setType ] = React.useState<Array<TokenType> | undefined>(getTokenFilterValue(router.query.type)); const [ type, setType ] = React.useState<Array<TokenType> | undefined>(getTokenFilterValue(router.query.type));
const debouncedFilter = useDebounce(filter, 300); const debouncedFilter = useDebounce(filter, 300);
const { isError, isLoading, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({ const { isError, isPlaceholderData, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'tokens', resourceName: 'tokens',
filters: { q: debouncedFilter, type }, filters: { q: debouncedFilter, type },
options: {
placeholderData: generateListStub<'tokens'>(TOKEN_INFO_ERC_20, 50, {
holder_count: 81528,
items_count: 50,
name: '',
market_cap: null,
}),
},
}); });
const onSearchChange = useCallback((value: string) => { const onSearchChange = useCallback((value: string) => {
...@@ -84,15 +94,23 @@ const Tokens = () => { ...@@ -84,15 +94,23 @@ const Tokens = () => {
const content = data?.items ? ( const content = data?.items ? (
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
{ data.items.map((item, index) => <TokensListItem key={ item.address } token={ item } index={ index } page={ pagination.page }/>) } { data.items.map((item, index) => (
<TokensListItem
key={ item.address + (isPlaceholderData ? index : '') }
token={ item }
index={ index }
page={ pagination.page }
isLoading={ isPlaceholderData }
/>
)) }
</Show> </Show>
<Hide below="lg" ssr={ false }><TokensTable items={ data.items } page={ pagination.page }/></Hide></> <Hide below="lg" ssr={ false }><TokensTable items={ data.items } page={ pagination.page } isLoading={ isPlaceholderData }/></Hide></>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ false }
items={ data?.items } items={ data?.items }
skeletonProps={{ skeletonDesktopColumns: [ '25px', '33%', '33%', '33%', '110px' ] }} skeletonProps={{ skeletonDesktopColumns: [ '25px', '33%', '33%', '33%', '110px' ] }}
emptyText="There are no tokens." emptyText="There are no tokens."
......
import { Flex, Text, Tag, HStack, Grid, GridItem } from '@chakra-ui/react'; import { Flex, HStack, Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
...@@ -6,6 +6,7 @@ import type { TokenInfo } from 'types/api/token'; ...@@ -6,6 +6,7 @@ import type { TokenInfo } from 'types/api/token';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Tag from 'ui/shared/chakra/Tag';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; 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';
...@@ -14,6 +15,7 @@ type Props = { ...@@ -14,6 +15,7 @@ type Props = {
token: TokenInfo; token: TokenInfo;
index: number; index: number;
page: number; page: number;
isLoading?: boolean;
} }
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
...@@ -22,6 +24,7 @@ const TokensTableItem = ({ ...@@ -22,6 +24,7 @@ const TokensTableItem = ({
token, token,
page, page,
index, index,
isLoading,
}: Props) => { }: Props) => {
const { const {
...@@ -47,37 +50,39 @@ const TokensTableItem = ({ ...@@ -47,37 +50,39 @@ const TokensTableItem = ({
> >
<GridItem display="flex"> <GridItem display="flex">
<Flex overflow="hidden" mr={ 3 } alignItems="center"> <Flex overflow="hidden" mr={ 3 } alignItems="center">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 }/> <TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontSize="sm" fontWeight="700" hash={ address } type="token" alias={ tokenString }/> <AddressLink fontSize="sm" fontWeight="700" hash={ address } type="token" alias={ tokenString } isLoading={ isLoading } mr={ 3 }/>
<Tag flexShrink={ 0 } ml={ 3 }>{ type }</Tag> <Tag flexShrink={ 0 } isLoading={ isLoading }>{ type }</Tag>
</Flex> </Flex>
<Text fontSize="sm" ml="auto" variant="secondary">{ (page - 1) * PAGE_SIZE + index + 1 }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" ml="auto" color="text_secondary" minW="24px" textAlign="right" lineHeight={ 6 }>
<span>{ (page - 1) * PAGE_SIZE + index + 1 }</span>
</Skeleton>
</GridItem> </GridItem>
</Grid> </Grid>
<Flex justifyContent="space-between" alignItems="center" width="100%"> <Flex justifyContent="space-between" alignItems="center" width="100%">
<Flex alignItems="center" width="136px" justifyContent="space-between" ml={ 8 } mt="-8px"> <Flex alignItems="center" width="136px" justifyContent="space-between" ml={ 8 } mt="-8px">
<Flex alignItems="center"> <Flex alignItems="center">
<AddressLink fontSize="sm" hash={ address } type="address" truncation="constant"/> <AddressLink fontSize="sm" hash={ address } type="address" truncation="constant" isLoading={ isLoading }/>
<CopyToClipboard text={ address } ml={ 1 }/> <CopyToClipboard text={ address } isLoading={ isLoading }/>
</Flex> </Flex>
<AddressAddToWallet token={ token }/> <AddressAddToWallet token={ token } isLoading={ isLoading }/>
</Flex> </Flex>
</Flex> </Flex>
{ exchangeRate && ( { exchangeRate && (
<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">{ exchangeRate }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"><span>{ exchangeRate }</span></Skeleton>
</HStack> </HStack>
) } ) }
{ totalValue?.usd && ( { totalValue?.usd && (
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>On-chain market cap</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>On-chain market cap</Skeleton>
<Text fontSize="sm" variant="secondary">{ totalValue.usd }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" variant="secondary"><span>{ totalValue.usd }</span></Skeleton>
</HStack> </HStack>
) } ) }
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Holders</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>Holders</Skeleton>
<Text fontSize="sm" variant="secondary">{ Number(holders).toLocaleString() }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" variant="secondary"><span>{ Number(holders).toLocaleString() }</span></Skeleton>
</HStack> </HStack>
</ListItemMobile> </ListItemMobile>
); );
......
...@@ -10,9 +10,10 @@ import TokensTableItem from './TokensTableItem'; ...@@ -10,9 +10,10 @@ import TokensTableItem from './TokensTableItem';
type Props = { type Props = {
items: Array<TokenInfo>; items: Array<TokenInfo>;
page: number; page: number;
isLoading?: boolean;
} }
const TokensTable = ({ items, page }: Props) => { const TokensTable = ({ items, page, isLoading }: Props) => {
return ( return (
<Table style={{ tableLayout: 'auto' }}> <Table style={{ tableLayout: 'auto' }}>
<Thead top={ 80 }> <Thead top={ 80 }>
...@@ -25,7 +26,7 @@ const TokensTable = ({ items, page }: Props) => { ...@@ -25,7 +26,7 @@ const TokensTable = ({ items, page }: Props) => {
</Thead> </Thead>
<Tbody> <Tbody>
{ items.map((item, index) => ( { items.map((item, index) => (
<TokensTableItem key={ item.address } token={ item } index={ index } page={ page }/> <TokensTableItem key={ item.address + (isLoading ? index : '') } token={ item } index={ index } page={ page } isLoading={ isLoading }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
import { Box, Flex, Td, Tr, Text, Tag } from '@chakra-ui/react'; import { Box, Flex, Td, Tr, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import Address from 'ui/shared/address/Address';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Tag from 'ui/shared/chakra/Tag';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
...@@ -13,6 +15,7 @@ type Props = { ...@@ -13,6 +15,7 @@ type Props = {
token: TokenInfo; token: TokenInfo;
index: number; index: number;
page: number; page: number;
isLoading?: boolean;
} }
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
...@@ -21,6 +24,7 @@ const TokensTableItem = ({ ...@@ -21,6 +24,7 @@ const TokensTableItem = ({
token, token,
page, page,
index, index,
isLoading,
}: Props) => { }: Props) => {
const { const {
...@@ -41,8 +45,9 @@ const TokensTableItem = ({ ...@@ -41,8 +45,9 @@ const TokensTableItem = ({
return ( return (
<Tr> <Tr>
<Td> <Td>
<Flex> <Flex alignItems="flex-start">
<Text <Skeleton
isLoaded={ !isLoading }
fontSize="sm" fontSize="sm"
lineHeight="24px" lineHeight="24px"
fontWeight={ 600 } fontWeight={ 600 }
...@@ -50,28 +55,46 @@ const TokensTableItem = ({ ...@@ -50,28 +55,46 @@ const TokensTableItem = ({
minW="28px" minW="28px"
> >
{ (page - 1) * PAGE_SIZE + index + 1 } { (page - 1) * PAGE_SIZE + index + 1 }
</Text> </Skeleton>
<Box> <Box>
<Flex alignItems="center"> <Flex alignItems="center">
<TokenLogo data={ token } boxSize={ 6 } mr={ 2 }/> <TokenLogo data={ token } boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<AddressLink fontSize="sm" fontWeight="700" hash={ address } type="token" alias={ tokenString }/> <AddressLink fontSize="sm" fontWeight="700" hash={ address } type="token" alias={ tokenString } isLoading={ isLoading }/>
</Flex> </Flex>
<Flex alignItems="center" width="136px" justifyContent="space-between" ml={ 8 } mt={ 2 }> <Box ml={ 8 } mt={ 2 }>
<Flex alignItems="center"> <Address>
<AddressLink fontSize="sm" hash={ address } type="address" truncation="constant" fontWeight={ 500 }/> <AddressLink fontSize="sm" hash={ address } type="address" truncation="constant" fontWeight={ 500 } isLoading={ isLoading }/>
<CopyToClipboard text={ address } ml={ 1 }/> <CopyToClipboard text={ address } isLoading={ isLoading }/>
</Flex> <AddressAddToWallet token={ token } ml={ 2 } isLoading={ isLoading }/>
<AddressAddToWallet token={ token }/> </Address>
</Flex> <Box mt={ 3 } >
<Tag flexShrink={ 0 } ml={ 8 } mt={ 3 }>{ type }</Tag> <Tag isLoading={ isLoading }>{ type }</Tag>
</Box>
</Box>
</Box> </Box>
</Flex> </Flex>
</Td> </Td>
<Td isNumeric><Text fontSize="sm" lineHeight="24px" fontWeight={ 500 }>{ exchangeRate && `$${ exchangeRate }` }</Text></Td> <Td isNumeric>
<Skeleton isLoaded={ !isLoading } fontSize="sm" lineHeight="24px" fontWeight={ 500 } display="inline-block">
{ exchangeRate && `$${ exchangeRate }` }
</Skeleton>
</Td>
<Td isNumeric maxWidth="300px" width="300px"> <Td isNumeric maxWidth="300px" width="300px">
<Text fontSize="sm" lineHeight="24px" fontWeight={ 500 }>{ totalValue?.usd && `$${ totalValue.usd }` }</Text> <Skeleton isLoaded={ !isLoading } fontSize="sm" lineHeight="24px" fontWeight={ 500 } display="inline-block">
{ totalValue?.usd && `$${ totalValue.usd }` }
</Skeleton>
</Td>
<Td isNumeric>
<Skeleton
isLoaded={ !isLoading }
fontSize="sm"
lineHeight="24px"
fontWeight={ 500 }
display="inline-block"
>
{ Number(holders).toLocaleString() }
</Skeleton>
</Td> </Td>
<Td isNumeric><Text fontSize="sm" lineHeight="24px" fontWeight={ 500 }>{ Number(holders).toLocaleString() }</Text></Td>
</Tr> </Tr>
); );
}; };
......
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