Commit 8b4ee616 authored by tom's avatar tom

skeleton for token holders

parent 558f514c
import type { TokenCounters, TokenInfo } from 'types/api/token'; import type { TokenCounters, TokenHolder, TokenInfo } from 'types/api/token';
export const TOKEN_INFO: TokenInfo<'ERC-20'> = { export const TOKEN_INFO: TokenInfo<'ERC-20'> = {
address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
...@@ -15,3 +15,17 @@ export const TOKEN_COUNTERS: TokenCounters = { ...@@ -15,3 +15,17 @@ export const TOKEN_COUNTERS: TokenCounters = {
token_holders_count: '123456', token_holders_count: '123456',
transfers_count: '123456', transfers_count: '123456',
}; };
export const TOKEN_HOLDER: TokenHolder = {
address: {
hash: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
value: '1021378038331138520668',
};
...@@ -22,7 +22,7 @@ export type TokenInfoGeneric<Type extends TokenType> = Omit<TokenInfo, 'type'> & ...@@ -22,7 +22,7 @@ export type TokenInfoGeneric<Type extends TokenType> = Omit<TokenInfo, 'type'> &
export interface TokenHolders { export interface TokenHolders {
items: Array<TokenHolder>; items: Array<TokenHolder>;
next_page_params: TokenHoldersPagination; next_page_params: TokenHoldersPagination | null;
} }
export type TokenHolder = { export type TokenHolder = {
......
...@@ -75,6 +75,7 @@ const TokenPageContent = () => { ...@@ -75,6 +75,7 @@ const TokenPageContent = () => {
scrollRef, scrollRef,
options: { options: {
enabled: Boolean(router.query.hash && router.query.tab === 'holders' && tokenQuery.data), enabled: Boolean(router.query.hash && router.query.tab === 'holders' && tokenQuery.data),
placeholderData: { items: Array(50).fill(stubs.TOKEN_HOLDER), next_page_params: null },
}, },
}); });
......
import { Box, Flex, Text, chakra, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, Text, chakra, useColorModeValue, Skeleton } from '@chakra-ui/react';
import clamp from 'lodash/clamp'; import clamp from 'lodash/clamp';
import React from 'react'; import React from 'react';
...@@ -6,21 +6,26 @@ interface Props { ...@@ -6,21 +6,26 @@ interface Props {
className?: string; className?: string;
value: number; value: number;
colorScheme?: 'green' | 'gray'; colorScheme?: 'green' | 'gray';
isLoading?: boolean;
} }
const WIDTH = 50; const WIDTH = 50;
const Utilization = ({ className, value, colorScheme = 'green' }: Props) => { const Utilization = ({ className, value, colorScheme = 'green', isLoading }: Props) => {
const valueString = (clamp(value * 100 || 0, 0, 100)).toLocaleString('en', { maximumFractionDigits: 2 }) + '%'; const valueString = (clamp(value * 100 || 0, 0, 100)).toLocaleString('en', { maximumFractionDigits: 2 }) + '%';
const colorGrayScheme = useColorModeValue('gray.500', 'gray.400'); const colorGrayScheme = useColorModeValue('gray.500', 'gray.400');
const color = colorScheme === 'gray' ? colorGrayScheme : 'green.500'; const color = colorScheme === 'gray' ? colorGrayScheme : 'green.500';
return ( return (
<Flex className={ className } alignItems="center"> <Flex className={ className } alignItems="center" columnGap="10px">
<Box bg={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden"> <Skeleton isLoaded={ !isLoading }>
<Box bg={ color } w={ valueString } h="100%"/> <Box bg={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') } w={ `${ WIDTH }px` } h="4px" borderRadius="full" overflow="hidden">
</Box> <Box bg={ color } w={ valueString } h="100%"/>
<Text color={ color } ml="10px" fontWeight="bold">{ valueString }</Text> </Box>
</Skeleton>
<Skeleton isLoaded={ !isLoading }>
<Text color={ color } fontWeight="bold">{ valueString }</Text>
</Skeleton>
</Flex> </Flex>
); );
}; };
......
...@@ -38,15 +38,28 @@ const TokenHoldersContent = ({ holdersQuery, tokenQuery }: Props) => { ...@@ -38,15 +38,28 @@ const TokenHoldersContent = ({ holdersQuery, tokenQuery }: Props) => {
const content = items && tokenQuery.data ? ( const content = items && tokenQuery.data ? (
<> <>
{ !isMobile && <TokenHoldersTable data={ items } token={ tokenQuery.data } top={ holdersQuery.isPaginationVisible ? 80 : 0 }/> } { !isMobile && (
{ isMobile && <TokenHoldersList data={ items } token={ tokenQuery.data }/> } <TokenHoldersTable
data={ items }
token={ tokenQuery.data }
top={ holdersQuery.isPaginationVisible ? 80 : 0 }
isLoading={ tokenQuery.isPlaceholderData || holdersQuery.isPlaceholderData }
/>
) }
{ isMobile && (
<TokenHoldersList
data={ items }
token={ tokenQuery.data }
isLoading={ tokenQuery.isPlaceholderData || holdersQuery.isPlaceholderData }
/>
) }
</> </>
) : null; ) : null;
return ( return (
<DataListDisplay <DataListDisplay
isError={ holdersQuery.isError || tokenQuery.isError } isError={ holdersQuery.isError || tokenQuery.isError }
isLoading={ holdersQuery.isLoading || tokenQuery.isLoading } isLoading={ false }
items={ holdersQuery.data?.items } items={ holdersQuery.data?.items }
skeletonProps={{ skeletonDesktopColumns: [ '100%', '300px', '175px' ] }} skeletonProps={{ skeletonDesktopColumns: [ '100%', '300px', '175px' ] }}
emptyText="There are no holders for this token." emptyText="There are no holders for this token."
......
...@@ -8,16 +8,18 @@ import TokenHoldersListItem from './TokenHoldersListItem'; ...@@ -8,16 +8,18 @@ import TokenHoldersListItem from './TokenHoldersListItem';
interface Props { interface Props {
data: Array<TokenHolder>; data: Array<TokenHolder>;
token: TokenInfo; token: TokenInfo;
isLoading?: boolean;
} }
const TokenHoldersList = ({ data, token }: Props) => { const TokenHoldersList = ({ data, token, isLoading }: Props) => {
return ( return (
<Box> <Box>
{ data.map((item) => ( { data.map((item, index) => (
<TokenHoldersListItem <TokenHoldersListItem
key={ item.address.hash } key={ item.address.hash + (isLoading ? index : '') }
token={ token } token={ token }
holder={ item } holder={ item }
isLoading={ isLoading }
/> />
)) } )) }
</Box> </Box>
......
import { Flex } from '@chakra-ui/react'; import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -13,24 +13,36 @@ import Utilization from 'ui/shared/Utilization/Utilization'; ...@@ -13,24 +13,36 @@ import Utilization from 'ui/shared/Utilization/Utilization';
interface Props { interface Props {
holder: TokenHolder; holder: TokenHolder;
token: TokenInfo; token: TokenInfo;
isLoading?: boolean;
} }
const TokenHoldersListItem = ({ holder, token }: Props) => { const TokenHoldersListItem = ({ holder, token, isLoading }: Props) => {
const quantity = BigNumber(holder.value).div(BigNumber(10 ** Number(token.decimals))).dp(6).toFormat(); const quantity = BigNumber(holder.value).div(BigNumber(10 ** Number(token.decimals))).dp(6).toFormat();
return ( return (
<ListItemMobile rowGap={ 3 }> <ListItemMobile rowGap={ 3 }>
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%">
<AddressIcon address={ holder.address }/> <AddressIcon address={ holder.address } isLoading={ isLoading }/>
<AddressLink type="address" ml={ 2 } fontWeight="700" hash={ holder.address.hash } alias={ holder.address.name } flexGrow={ 1 }/> <AddressLink
type="address"
ml={ 2 }
fontWeight="700"
hash={ holder.address.hash }
alias={ holder.address.name }
flexGrow={ 1 }
isLoading={ isLoading }
/>
</Address> </Address>
<Flex justifyContent="space-between" alignItems="center" width="100%"> <Flex justifyContent="space-between" alignItems="center" width="100%">
{ quantity } <Skeleton isLoaded={ !isLoading } display="inline-block">
{ quantity }
</Skeleton>
{ token.total_supply && ( { token.total_supply && (
<Utilization <Utilization
value={ BigNumber(holder.value).div(BigNumber(token.total_supply)).dp(4).toNumber() } value={ BigNumber(holder.value).div(BigNumber(token.total_supply)).dp(4).toNumber() }
colorScheme="green" colorScheme="green"
ml={ 6 } ml={ 6 }
isLoading={ isLoading }
/> />
) } ) }
</Flex> </Flex>
......
...@@ -10,9 +10,10 @@ interface Props { ...@@ -10,9 +10,10 @@ interface Props {
data: Array<TokenHolder>; data: Array<TokenHolder>;
token: TokenInfo; token: TokenInfo;
top: number; top: number;
isLoading?: boolean;
} }
const TokenHoldersTable = ({ data, token, top }: Props) => { const TokenHoldersTable = ({ data, token, 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 TokenHoldersTable = ({ data, token, top }: Props) => { ...@@ -23,8 +24,8 @@ const TokenHoldersTable = ({ data, token, top }: Props) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item) => ( { data.map((item, index) => (
<TokenHoldersTableItem key={ item.address.hash } holder={ item } token={ token }/> <TokenHoldersTableItem key={ item.address.hash + (isLoading ? index : '') } holder={ item } token={ token } isLoading={ isLoading }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
import { Tr, Td } from '@chakra-ui/react'; import { Tr, Td, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -12,28 +12,40 @@ import Utilization from 'ui/shared/Utilization/Utilization'; ...@@ -12,28 +12,40 @@ import Utilization from 'ui/shared/Utilization/Utilization';
type Props = { type Props = {
holder: TokenHolder; holder: TokenHolder;
token: TokenInfo; token: TokenInfo;
isLoading?: boolean;
} }
const TokenTransferTableItem = ({ holder, token }: Props) => { const TokenTransferTableItem = ({ holder, token, isLoading }: Props) => {
const quantity = BigNumber(holder.value).div(BigNumber(10 ** Number(token.decimals))).toFormat(); const quantity = BigNumber(holder.value).div(BigNumber(10 ** Number(token.decimals))).toFormat();
return ( return (
<Tr> <Tr>
<Td> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%">
<AddressIcon address={ holder.address }/> <AddressIcon address={ holder.address } isLoading={ isLoading }/>
<AddressLink type="address" ml={ 2 } fontWeight="700" hash={ holder.address.hash } alias={ holder.address.name } flexGrow={ 1 }/> <AddressLink
type="address"
ml={ 2 }
fontWeight="700"
hash={ holder.address.hash }
alias={ holder.address.name }
flexGrow={ 1 }
isLoading={ isLoading }
/>
</Address> </Address>
</Td> </Td>
<Td isNumeric> <Td verticalAlign="middle" isNumeric>
{ quantity } <Skeleton isLoaded={ !isLoading } display="inline-block">
{ quantity }
</Skeleton>
</Td> </Td>
{ token.total_supply && ( { token.total_supply && (
<Td isNumeric> <Td verticalAlign="middle" isNumeric>
<Utilization <Utilization
value={ BigNumber(holder.value).div(BigNumber(token.total_supply)).dp(4).toNumber() } value={ BigNumber(holder.value).div(BigNumber(token.total_supply)).dp(4).toNumber() }
colorScheme="green" colorScheme="green"
display="inline-flex" display="inline-flex"
isLoading={ isLoading }
/> />
</Td> </Td>
) } ) }
......
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