Commit 842d9465 authored by tom's avatar tom

skeletons for token page header

parent 230f34d0
import type { Address } from 'types/api/address';
import type { AddressParam } from 'types/api/addressParams';
import { TOKEN_INFO } from './token';
export const ADDRESS_HASH = '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a'; export const ADDRESS_HASH = '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a';
export const ADDRESS_PARAMS = { export const ADDRESS_PARAMS: AddressParam = {
hash: ADDRESS_HASH, hash: ADDRESS_HASH,
implementation_name: null, implementation_name: null,
is_contract: false, is_contract: false,
...@@ -10,3 +15,32 @@ export const ADDRESS_PARAMS = { ...@@ -10,3 +15,32 @@ export const ADDRESS_PARAMS = {
public_tags: [], public_tags: [],
watchlist_names: [], watchlist_names: [],
}; };
export const ADDRESS_INFO: Address = {
block_number_balance_updated_at: 8774377,
coin_balance: '0',
creation_tx_hash: null,
creator_address_hash: null,
exchange_rate: null,
has_custom_methods_read: false,
has_custom_methods_write: false,
has_decompiled_code: false,
has_logs: true,
has_methods_read: false,
has_methods_read_proxy: false,
has_methods_write: false,
has_methods_write_proxy: false,
has_token_transfers: false,
has_tokens: false,
has_validated_blocks: false,
hash: ADDRESS_HASH,
implementation_address: null,
implementation_name: null,
is_contract: false,
is_verified: false,
name: 'ChainLink Token (goerli)',
token: TOKEN_INFO,
private_tags: [],
public_tags: [],
watchlist_names: [],
};
import type { TokenInfo } from 'types/api/token';
export const TOKEN_INFO: TokenInfo<'ERC-20'> = {
address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a',
decimals: '18',
exchange_rate: null,
holders: '16026',
name: 'Stub Token (goerli)',
symbol: 'STUB',
total_supply: '6000000000000000',
type: 'ERC-20',
};
import { chakra, Alert, Icon, Modal, ModalBody, ModalContent, ModalCloseButton, ModalOverlay, Box, useDisclosure, Tooltip, IconButton } from '@chakra-ui/react'; import {
chakra,
Alert,
Icon,
Modal,
ModalBody,
ModalContent,
ModalCloseButton,
ModalOverlay,
Box,
useDisclosure,
Tooltip,
IconButton,
Skeleton,
} from '@chakra-ui/react';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import React from 'react'; import React from 'react';
...@@ -13,9 +27,10 @@ const SVG_OPTIONS = { ...@@ -13,9 +27,10 @@ const SVG_OPTIONS = {
interface Props { interface Props {
className?: string; className?: string;
hash: string; hash: string;
isLoading?: boolean;
} }
const AddressQrCode = ({ hash, className }: Props) => { const AddressQrCode = ({ hash, className, isLoading }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [ qr, setQr ] = React.useState(''); const [ qr, setQr ] = React.useState('');
...@@ -36,6 +51,10 @@ const AddressQrCode = ({ hash, className }: Props) => { ...@@ -36,6 +51,10 @@ const AddressQrCode = ({ hash, className }: Props) => {
} }
}, [ hash, isOpen, onClose ]); }, [ hash, isOpen, onClose ]);
if (isLoading) {
return <Skeleton className={ className } w="36px" h="32px" borderRadius="base"/>;
}
return ( return (
<> <>
<Tooltip label="Click to view QR code"> <Tooltip label="Click to view QR code">
......
import { Skeleton, Box, Flex, SkeletonCircle, Icon, Tag } from '@chakra-ui/react'; import { Skeleton, Box, Icon, Tag } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
...@@ -11,6 +11,7 @@ import useContractTabs from 'lib/hooks/useContractTabs'; ...@@ -11,6 +11,7 @@ import useContractTabs from 'lib/hooks/useContractTabs';
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 trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import * as stubs from 'stubs/token';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
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';
...@@ -42,7 +43,7 @@ const TokenPageContent = () => { ...@@ -42,7 +43,7 @@ const TokenPageContent = () => {
const tokenQuery = useApiQuery('token', { const tokenQuery = useApiQuery('token', {
pathParams: { hash: hashString }, pathParams: { hash: hashString },
queryOptions: { enabled: Boolean(router.query.hash) }, queryOptions: { enabled: Boolean(router.query.hash), placeholderData: stubs.TOKEN_INFO },
}); });
useEffect(() => { useEffect(() => {
...@@ -154,28 +155,17 @@ const TokenPageContent = () => { ...@@ -154,28 +155,17 @@ const TokenPageContent = () => {
return ( return (
<Page> <Page>
{ tokenQuery.isLoading ? ( <TextAd mb={ 6 }/>
<> <PageTitle
<Skeleton h={{ base: 12, lg: 6 }} mb={ 6 } w="100%" maxW="680px"/> isLoading={ tokenQuery.isPlaceholderData }
<Flex alignItems="center" mb={ 6 }> text={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
<SkeletonCircle w={ 6 } h={ 6 } mr={ 3 }/> backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
<Skeleton w="500px" h={ 10 }/> backLinkLabel="Back to tokens list"
</Flex> additionalsLeft={ (
</> <TokenLogo hash={ tokenQuery.data?.address } name={ tokenQuery.data?.name } boxSize={ 6 } isLoading={ tokenQuery.isPlaceholderData }/>
) : ( ) }
<> additionalsRight={ <Skeleton isLoaded={ !tokenQuery.isPlaceholderData } borderRadius="sm"><Tag>{ tokenQuery.data?.type }</Tag></Skeleton> }
<TextAd mb={ 6 }/> />
<PageTitle
text={ `${ tokenQuery.data?.name || 'Unnamed' }${ tokenSymbolText } token` }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to tokens list"
additionalsLeft={ (
<TokenLogo hash={ tokenQuery.data?.address } name={ tokenQuery.data?.name } boxSize={ 6 }/>
) }
additionalsRight={ <Tag>{ tokenQuery.data?.type }</Tag> }
/>
</>
) }
<TokenContractInfo tokenQuery={ tokenQuery }/> <TokenContractInfo tokenQuery={ tokenQuery }/>
<TokenDetails tokenQuery={ tokenQuery }/> <TokenDetails tokenQuery={ tokenQuery }/>
{ /* should stay before tabs to scroll up with pagination */ } { /* should stay before tabs to scroll up with pagination */ }
......
...@@ -16,14 +16,15 @@ interface Props { ...@@ -16,14 +16,15 @@ interface Props {
address: Pick<AddressParam, 'hash' | 'is_contract' | 'implementation_name' | 'watchlist_names'>; address: Pick<AddressParam, 'hash' | 'is_contract' | 'implementation_name' | 'watchlist_names'>;
token?: TokenInfo | null; token?: TokenInfo | null;
isLinkDisabled?: boolean; isLinkDisabled?: boolean;
isLoading?: boolean;
} }
const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => { const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
return ( return (
<Flex alignItems="center"> <Flex alignItems="center">
<AddressIcon address={ address }/> <AddressIcon address={ address } isLoading={ isLoading }/>
<AddressLink <AddressLink
type="address" type="address"
hash={ address.hash } hash={ address.hash }
...@@ -32,13 +33,14 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => { ...@@ -32,13 +33,14 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => {
fontWeight={ 500 } fontWeight={ 500 }
truncation={ isMobile ? 'constant' : 'none' } truncation={ isMobile ? 'constant' : 'none' }
isDisabled={ isLinkDisabled } isDisabled={ isLinkDisabled }
isLoading={ isLoading }
/> />
<CopyToClipboard text={ address.hash }/> <CopyToClipboard text={ address.hash } isLoading={ isLoading }/>
{ address.is_contract && token && <AddressAddToMetaMask ml={ 2 } token={ token }/> } { !isLoading && address.is_contract && token && <AddressAddToMetaMask ml={ 2 } token={ token }/> }
{ !address.is_contract && ( { !isLoading && !address.is_contract && (
<AddressFavoriteButton hash={ address.hash } isAdded={ Boolean(address.watchlist_names?.length) } ml={ 3 }/> <AddressFavoriteButton hash={ address.hash } isAdded={ Boolean(address.watchlist_names?.length) } ml={ 3 }/>
) } ) }
<AddressQrCode hash={ address.hash } ml={ 2 }/> <AddressQrCode hash={ address.hash } ml={ 2 } isLoading={ isLoading }/>
</Flex> </Flex>
); );
}; };
......
import { Heading, Flex, Grid, Tooltip, Icon, chakra } from '@chakra-ui/react'; import { Heading, Flex, Grid, Tooltip, Icon, chakra, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
...@@ -13,18 +13,21 @@ type Props = { ...@@ -13,18 +13,21 @@ type Props = {
className?: string; className?: string;
backLinkLabel?: string; backLinkLabel?: string;
backLinkUrl?: string; backLinkUrl?: string;
isLoading?: boolean;
} }
const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLinkUrl, backLinkLabel, className }: Props) => { const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLinkUrl, backLinkLabel, className, isLoading }: Props) => {
const title = ( const title = (
<Heading <Skeleton isLoaded={ !isLoading }>
as="h1" <Heading
size="lg" as="h1"
flex="none" size="lg"
wordBreak="break-word" flex="none"
> wordBreak="break-word"
{ text } >
</Heading> { text }
</Heading>
</Skeleton>
); );
return ( return (
......
import { Image, chakra, useColorModeValue, Icon } from '@chakra-ui/react'; import { Image, chakra, useColorModeValue, Icon, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
...@@ -27,9 +27,15 @@ interface Props { ...@@ -27,9 +27,15 @@ interface Props {
hash?: string; hash?: string;
name?: string | null; name?: string | null;
className?: string; className?: string;
isLoading?: boolean;
} }
const TokenLogo = ({ hash, name, className }: Props) => { const TokenLogo = ({ hash, name, className, isLoading }: Props) => {
if (isLoading) {
return <Skeleton className={ className } borderRadius="base"/>;
}
const logoSrc = appConfig.network.assetsPathname && hash ? [ const logoSrc = appConfig.network.assetsPathname && hash ? [
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/', 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/',
appConfig.network.assetsPathname, appConfig.network.assetsPathname,
......
import { Flex, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -6,6 +5,7 @@ import React from 'react'; ...@@ -6,6 +5,7 @@ import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import * as stubs from 'stubs/address';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
interface Props { interface Props {
...@@ -17,34 +17,28 @@ const TokenContractInfo = ({ tokenQuery }: Props) => { ...@@ -17,34 +17,28 @@ const TokenContractInfo = ({ tokenQuery }: Props) => {
const contractQuery = useApiQuery('address', { const contractQuery = useApiQuery('address', {
pathParams: { hash: router.query.hash?.toString() }, pathParams: { hash: router.query.hash?.toString() },
queryOptions: { enabled: Boolean(router.query.hash) }, queryOptions: { enabled: Boolean(router.query.hash), placeholderData: stubs.ADDRESS_INFO },
}); });
if (tokenQuery.isLoading || contractQuery.isLoading) {
return (
<Flex alignItems="center">
<SkeletonCircle boxSize={ 6 }/>
<Skeleton w="400px" h={ 5 } ml={ 2 }/>
<Skeleton w={ 5 } h={ 5 } ml={ 1 }/>
<Skeleton w={ 9 } h={ 8 } ml={ 2 }/>
<Skeleton w={ 9 } h={ 8 } ml={ 2 }/>
</Flex>
);
}
// we show error in parent component, this is only for TS // we show error in parent component, this is only for TS
if (tokenQuery.isError) { if (tokenQuery.isError) {
return null; return null;
} }
const address = { const address = {
hash: tokenQuery.data.address, hash: tokenQuery.data?.address || '',
is_contract: true, is_contract: true,
implementation_name: null, implementation_name: null,
watchlist_names: [], watchlist_names: [],
}; };
return <AddressHeadingInfo address={ address } token={ contractQuery.data?.token }/>; return (
<AddressHeadingInfo
address={ address }
token={ contractQuery.data?.token }
isLoading={ tokenQuery.isPlaceholderData || contractQuery.isPlaceholderData }
/>
);
}; };
export default React.memo(TokenContractInfo); export default React.memo(TokenContractInfo);
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