Commit 05bc3818 authored by tom's avatar tom

skeletons for verified contracts

parent 8d1de2c4
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 VerifiedContracts from 'ui/pages/VerifiedContracts';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
const VerifiedContracts = dynamic(() => import('ui/pages/VerifiedContracts'), { ssr: false });
const VerifiedContractsPage: NextPage = () => { const VerifiedContractsPage: NextPage = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
......
import type { SmartContract } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
import type { VerifiedContract } from 'types/api/contracts';
import { ADDRESS_PARAMS } from './addressParams';
export const CONTRACT_CODE_UNVERIFIED = { export const CONTRACT_CODE_UNVERIFIED = {
creation_bytecode: '0x60806040526e', creation_bytecode: '0x60806040526e',
...@@ -38,3 +41,15 @@ export const CONTRACT_CODE_VERIFIED = { ...@@ -38,3 +41,15 @@ export const CONTRACT_CODE_VERIFIED = {
source_code: 'source_code', source_code: 'source_code',
verified_at: '2023-02-21T14:39:16.906760Z', verified_at: '2023-02-21T14:39:16.906760Z',
} as unknown as SmartContract; } as unknown as SmartContract;
export const VERIFIED_CONTRACT_INFO: VerifiedContract = {
address: { ...ADDRESS_PARAMS, name: 'StubContract' },
coin_balance: '30319033612988277',
compiler_version: 'v0.8.17+commit.8df45f5f',
has_constructor_args: true,
language: 'solidity',
market_cap: null,
optimization_enabled: false,
tx_count: 565058,
verified_at: '2023-04-10T13:16:33.884921Z',
};
...@@ -9,6 +9,8 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -9,6 +9,8 @@ import useIsMobile from 'lib/hooks/useIsMobile';
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 getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { VERIFIED_CONTRACT_INFO } from 'stubs/contract';
import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
...@@ -32,9 +34,21 @@ const VerifiedContracts = () => { ...@@ -32,9 +34,21 @@ const VerifiedContracts = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isLoading, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({ const { isError, isPlaceholderData, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'verified_contracts', resourceName: 'verified_contracts',
filters: { q: debouncedSearchTerm, filter: type }, filters: { q: debouncedSearchTerm, filter: type },
options: {
placeholderData: generateListStub<'verified_contracts'>(
VERIFIED_CONTRACT_INFO,
50,
{
next_page_params: {
items_count: '50',
smart_contract_id: '50',
},
},
),
},
}); });
const handleSearchTermChange = React.useCallback((value: string) => { const handleSearchTermChange = React.useCallback((value: string) => {
...@@ -107,10 +121,10 @@ const VerifiedContracts = () => { ...@@ -107,10 +121,10 @@ const VerifiedContracts = () => {
const content = sortedData ? ( const content = sortedData ? (
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
<VerifiedContractsList data={ sortedData }/> <VerifiedContractsList data={ sortedData } isLoading={ isPlaceholderData }/>
</Show> </Show>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<VerifiedContractsTable data={ sortedData } sort={ sort } onSortToggle={ handleSortToggle }/> <VerifiedContractsTable data={ sortedData } sort={ sort } onSortToggle={ handleSortToggle } isLoading={ isPlaceholderData }/>
</Hide> </Hide>
</> </>
) : null; ) : null;
...@@ -121,7 +135,7 @@ const VerifiedContracts = () => { ...@@ -121,7 +135,7 @@ const VerifiedContracts = () => {
<VerifiedContractsCounters/> <VerifiedContractsCounters/>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ false }
items={ data?.items } items={ data?.items }
skeletonProps={{ skeletonDesktopColumns: [ '50%', '130px', '130px', '50%', '80px', '110px' ] }} skeletonProps={{ skeletonDesktopColumns: [ '50%', '130px', '130px', '50%', '80px', '110px' ] }}
emptyText="There are no verified contracts." emptyText="There are no verified contracts."
......
...@@ -5,10 +5,16 @@ import type { VerifiedContract } from 'types/api/contracts'; ...@@ -5,10 +5,16 @@ import type { VerifiedContract } from 'types/api/contracts';
import VerifiedContractsListItem from './VerifiedContractsListItem'; import VerifiedContractsListItem from './VerifiedContractsListItem';
const VerifiedContractsList = ({ data }: { data: Array<VerifiedContract>}) => { const VerifiedContractsList = ({ data, isLoading }: { data: Array<VerifiedContract>; isLoading: boolean }) => {
return ( return (
<Box> <Box>
{ data.map((item) => <VerifiedContractsListItem key={ item.address.hash } data={ item }/>) } { data.map((item, index) => (
<VerifiedContractsListItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }
/>
)) }
</Box> </Box>
); );
}; };
......
import { Box, Flex, Icon } from '@chakra-ui/react'; import { Box, Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -12,14 +12,16 @@ import dayjs from 'lib/date/dayjs'; ...@@ -12,14 +12,16 @@ import dayjs from 'lib/date/dayjs';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Icon from 'ui/shared/chakra/Icon';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
interface Props { interface Props {
data: VerifiedContract; data: VerifiedContract;
isLoading?: boolean;
} }
const VerifiedContractsListItem = ({ data }: Props) => { const VerifiedContractsListItem = ({ data, isLoading }: Props) => {
const balance = data.coin_balance && data.coin_balance !== '0' ? const balance = data.coin_balance && data.coin_balance !== '0' ?
BigNumber(data.coin_balance).div(10 ** appConfig.network.currency.decimals).dp(6).toFormat() : BigNumber(data.coin_balance).div(10 ** appConfig.network.currency.decimals).dp(6).toFormat() :
'0'; '0';
...@@ -27,50 +29,50 @@ const VerifiedContractsListItem = ({ data }: Props) => { ...@@ -27,50 +29,50 @@ const VerifiedContractsListItem = ({ data }: Props) => {
return ( return (
<ListItemMobile rowGap={ 3 }> <ListItemMobile rowGap={ 3 }>
<Address columnGap={ 2 } overflow="hidden" w="100%"> <Address columnGap={ 2 } overflow="hidden" w="100%">
<AddressIcon address={ data.address }/> <AddressIcon address={ data.address } isLoading={ isLoading }/>
<AddressLink hash={ data.address.hash } type="address" alias={ data.address.name }/> <AddressLink hash={ data.address.hash } type="address" alias={ data.address.name } isLoading={ isLoading }/>
<Box color="text_secondary" ml="auto"> <Skeleton isLoaded={ !isLoading } color="text_secondary" ml="auto">
<HashStringShorten hash={ data.address.hash } isTooltipDisabled/> <HashStringShorten hash={ data.address.hash } isTooltipDisabled/>
</Box> </Skeleton>
</Address> </Address>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Box fontWeight={ 500 }>Balance { appConfig.network.currency.symbol }</Box> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Balance { appConfig.network.currency.symbol }</Skeleton>
<Box color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary">
{ balance } <span>{ balance }</span>
</Box> </Skeleton>
</Flex> </Flex>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Box fontWeight={ 500 }>Txs count</Box> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Txs count</Skeleton>
<Box color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary">
{ data.tx_count ? data.tx_count.toLocaleString() : '0' } <span>{ data.tx_count ? data.tx_count.toLocaleString() : '0' }</span>
</Box> </Skeleton>
</Flex> </Flex>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Box fontWeight={ 500 } flexShrink="0">Compiler</Box> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 } flexShrink="0">Compiler</Skeleton>
<Flex flexWrap="wrap"> <Skeleton isLoaded={ !isLoading } display="flex" flexWrap="wrap">
<Box textTransform="capitalize">{ data.language }</Box> <Box textTransform="capitalize">{ data.language }</Box>
<Box color="text_secondary" wordBreak="break-all" whiteSpace="pre-wrap"> ({ data.compiler_version })</Box> <Box color="text_secondary" wordBreak="break-all" whiteSpace="pre-wrap"> ({ data.compiler_version })</Box>
</Flex> </Skeleton>
</Flex> </Flex>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Box fontWeight={ 500 }>Optimization</Box> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Optimization</Skeleton>
{ data.optimization_enabled ? { data.optimization_enabled ?
<Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer"/> : <Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer" isLoading={ isLoading }/> :
<Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer"/> } <Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer" isLoading={ isLoading }/> }
</Flex> </Flex>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Box fontWeight={ 500 }>Constructor args</Box> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Constructor args</Skeleton>
{ data.has_constructor_args ? { data.has_constructor_args ?
<Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer"/> : <Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer" isLoading={ isLoading }/> :
<Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer"/> } <Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer" isLoading={ isLoading }/> }
</Flex> </Flex>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Box fontWeight={ 500 }>Verified</Box> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Verified</Skeleton>
<Flex alignItems="center" columnGap={ 2 }> <Flex alignItems="center" columnGap={ 2 }>
<Icon as={ iconSuccess } boxSize={ 4 } color="green.500"/> <Icon as={ iconSuccess } boxSize={ 4 } color="green.500" isLoading={ isLoading }/>
<Box color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary">
{ dayjs(data.verified_at).fromNow() } <span>{ dayjs(data.verified_at).fromNow() }</span>
</Box> </Skeleton>
</Flex> </Flex>
</Flex> </Flex>
{ /* <Flex columnGap={ 3 }> { /* <Flex columnGap={ 3 }>
......
...@@ -14,9 +14,10 @@ interface Props { ...@@ -14,9 +14,10 @@ interface Props {
data: Array<VerifiedContract>; data: Array<VerifiedContract>;
sort: Sort | undefined; sort: Sort | undefined;
onSortToggle: (field: SortField) => () => void; onSortToggle: (field: SortField) => () => void;
isLoading?: boolean;
} }
const VerifiedContractsTable = ({ data, sort, onSortToggle }: Props) => { const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
...@@ -25,13 +26,13 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle }: Props) => { ...@@ -25,13 +26,13 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle }: Props) => {
<Tr> <Tr>
<Th width="50%">Contract</Th> <Th width="50%">Contract</Th>
<Th width="130px" isNumeric> <Th width="130px" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('balance') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('balance') } columnGap={ 1 }>
{ sort?.includes('balance') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('balance') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Balance { appConfig.network.currency.symbol } Balance { appConfig.network.currency.symbol }
</Link> </Link>
</Th> </Th>
<Th width="130px" isNumeric> <Th width="130px" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('txs') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('txs') } columnGap={ 1 }>
{ sort?.includes('txs') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('txs') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Txs Txs
</Link> </Link>
...@@ -43,7 +44,12 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle }: Props) => { ...@@ -43,7 +44,12 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle }: Props) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item) => <VerifiedContractsTableItem key={ item.address.hash } data={ item }/>) } { data.map((item, index) => (
<VerifiedContractsTableItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }/>
)) }
</Tbody> </Tbody>
</Table> </Table>
); );
......
import { Tr, Td, Icon, Box, Flex, chakra, Tooltip } from '@chakra-ui/react'; import { Tr, Td, Flex, chakra, Tooltip, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -11,13 +11,15 @@ import iconSuccess from 'icons/status/success.svg'; ...@@ -11,13 +11,15 @@ import iconSuccess from 'icons/status/success.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Icon from 'ui/shared/chakra/Icon';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
interface Props { interface Props {
data: VerifiedContract; data: VerifiedContract;
isLoading?: boolean;
} }
const VerifiedContractsTableItem = ({ data }: Props) => { const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
const balance = data.coin_balance && data.coin_balance !== '0' ? const balance = data.coin_balance && data.coin_balance !== '0' ?
BigNumber(data.coin_balance).div(10 ** appConfig.network.currency.decimals).dp(6).toFormat() : BigNumber(data.coin_balance).div(10 ** appConfig.network.currency.decimals).dp(6).toFormat() :
'0'; '0';
...@@ -26,52 +28,58 @@ const VerifiedContractsTableItem = ({ data }: Props) => { ...@@ -26,52 +28,58 @@ const VerifiedContractsTableItem = ({ data }: Props) => {
<Tr> <Tr>
<Td> <Td>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
<AddressIcon address={ data.address }/> <AddressIcon address={ data.address } isLoading={ isLoading }/>
<Flex columnGap={ 2 } flexWrap="wrap" lineHeight={ 6 } w="calc(100% - 32px)"> <Flex columnGap={ 2 } flexWrap="wrap" w="calc(100% - 32px)">
<AddressLink hash={ data.address.hash } type="address" alias={ data.address.name }/> <AddressLink hash={ data.address.hash } type="address" alias={ data.address.name } isLoading={ isLoading } my={ 1 }/>
<Box color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary" my={ 1 }>
<HashStringShorten hash={ data.address.hash } isTooltipDisabled/> <HashStringShorten hash={ data.address.hash } isTooltipDisabled/>
</Box> </Skeleton>
</Flex> </Flex>
</Flex> </Flex>
</Td> </Td>
<Td isNumeric lineHeight={ 6 }> <Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" my={ 1 }>
{ balance } { balance }
</Skeleton>
</Td> </Td>
<Td isNumeric lineHeight={ 6 }> <Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" my={ 1 }>
{ data.tx_count ? data.tx_count.toLocaleString() : '0' } { data.tx_count ? data.tx_count.toLocaleString() : '0' }
</Skeleton>
</Td> </Td>
<Td lineHeight={ 6 }> <Td>
<Flex flexWrap="wrap" columnGap={ 2 }> <Flex flexWrap="wrap" columnGap={ 2 }>
<chakra.span textTransform="capitalize">{ data.language }</chakra.span> <Skeleton isLoaded={ !isLoading } textTransform="capitalize" my={ 1 }>{ data.language }</Skeleton>
<chakra.span color="text_secondary" wordBreak="break-all">{ data.compiler_version }</chakra.span> <Skeleton isLoaded={ !isLoading } color="text_secondary" wordBreak="break-all" my={ 1 }>
<span>{ data.compiler_version }</span>
</Skeleton>
</Flex> </Flex>
</Td> </Td>
<Td> <Td>
<Tooltip label="Optimization"> <Tooltip label={ isLoading ? undefined : 'Optimization' }>
<span> <chakra.span display="inline-block">
{ data.optimization_enabled ? { data.optimization_enabled ?
<Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer"/> : <Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer" isLoading={ isLoading }/> :
<Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer"/> } <Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer" isLoading={ isLoading }/> }
</span> </chakra.span>
</Tooltip> </Tooltip>
<Tooltip label="Constructor args"> <Tooltip label={ isLoading ? undefined : 'Constructor args' }>
<chakra.span ml={ 3 }> <chakra.span display="inline-block" ml={ 3 }>
{ data.has_constructor_args ? { data.has_constructor_args ?
<Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer"/> : <Icon as={ iconCheck } boxSize={ 6 } color="green.500" cursor="pointer" isLoading={ isLoading }/> :
<Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer"/> } <Icon as={ iconCross } boxSize={ 6 } color="red.600" cursor="pointer" isLoading={ isLoading }/> }
</chakra.span> </chakra.span>
</Tooltip> </Tooltip>
</Td> </Td>
<Td lineHeight={ 6 }> <Td>
<Flex alignItems="center" columnGap={ 2 }> <Flex alignItems="center" columnGap={ 2 } my={ 1 }>
<Icon as={ iconSuccess } boxSize={ 4 } color="green.500"/> <Icon as={ iconSuccess } boxSize={ 4 } color="green.500" isLoading={ isLoading }/>
<chakra.span color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary">
{ dayjs(data.verified_at).fromNow() } <span>{ dayjs(data.verified_at).fromNow() }</span>
</chakra.span> </Skeleton>
</Flex> </Flex>
</Td> </Td>
{ /* <Td lineHeight={ 6 }> { /* <Td>
N/A N/A
</Td> */ } </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