Commit f665a2a7 authored by tom's avatar tom

skeletons for tx state changes

parent a2cced29
import type { TxStateChange, TxStateChanges } from 'types/api/txStateChanges';
import { ADDRESS_PARAMS } from './addressParams';
import { TOKEN_INFO_ERC_721 } from './token';
export const STATE_CHANGE_MINER: TxStateChange = {
address: ADDRESS_PARAMS,
balance_after: '124280364215547113',
balance_before: '123405277440098758',
change: '875086775448355',
is_miner: true,
token: null,
type: 'coin',
};
export const STATE_CHANGE_COIN: TxStateChange = {
address: ADDRESS_PARAMS,
balance_after: '61659392141463351540',
balance_before: '61660292436225994690',
change: '-900294762600000',
is_miner: false,
token: null,
type: 'coin',
};
export const STATE_CHANGE_TOKEN: TxStateChange = {
address: ADDRESS_PARAMS,
balance_after: '43',
balance_before: '42',
change: [
{
direction: 'to',
total: {
token_id: '1621395',
},
},
],
is_miner: false,
token: TOKEN_INFO_ERC_721,
type: 'token',
};
export const TX_STATE_CHANGES: TxStateChanges = [
STATE_CHANGE_MINER,
STATE_CHANGE_COIN,
STATE_CHANGE_TOKEN,
];
import { Grid, chakra, GridItem } from '@chakra-ui/react'; import { Grid, chakra, GridItem, Skeleton } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -38,13 +38,20 @@ const Container = chakra(({ isAnimated, children, className }: ContainerProps) = ...@@ -38,13 +38,20 @@ const Container = chakra(({ isAnimated, children, className }: ContainerProps) =
interface LabelProps { interface LabelProps {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
isLoading?: boolean;
} }
const Label = chakra(({ children, className }: LabelProps) => { const Label = chakra(({ children, className, isLoading }: LabelProps) => {
return ( return (
<GridItem className={ className } fontWeight={ 500 } lineHeight="20px" py="5px"> <Skeleton
className={ className }
isLoaded={ !isLoading }
fontWeight={ 500 }
lineHeight="20px"
my="5px"
>
{ children } { children }
</GridItem> </Skeleton>
); );
}); });
......
import { Accordion, Hide, Show, Skeleton, Text } from '@chakra-ui/react'; import { Accordion, Hide, Show, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { SECOND } from 'lib/consts'; import { SECOND } from 'lib/consts';
import { TX_STATE_CHANGES } from 'stubs/txStateChanges';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import TxStateList from 'ui/tx/state/TxStateList'; import TxStateList from 'ui/tx/state/TxStateList';
import TxStateTable from 'ui/tx/state/TxStateTable'; import TxStateTable from 'ui/tx/state/TxStateTable';
import useFetchTxInfo from 'ui/tx/useFetchTxInfo'; import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
...@@ -15,59 +14,44 @@ import TxSocketAlert from './TxSocketAlert'; ...@@ -15,59 +14,44 @@ import TxSocketAlert from './TxSocketAlert';
const TxState = () => { const TxState = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND }); const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError } = useApiQuery('tx_state_changes', { const { data, isPlaceholderData, isError } = useApiQuery('tx_state_changes', {
pathParams: { hash: txInfo.data?.hash }, pathParams: { hash: txInfo.data?.hash },
queryOptions: { queryOptions: {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status), enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
placeholderData: TX_STATE_CHANGES,
}, },
}); });
if (!txInfo.isLoading && !txInfo.isError && !txInfo.data.status) { if (!txInfo.isLoading && !txInfo.isPlaceholderData && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>; return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
} }
const skeleton = ( const content = data ? (
<> <Accordion allowMultiple defaultIndex={ [] }>
<Show below="lg" ssr={ false }>
<Skeleton h={ 4 } borderRadius="full" w="100%"/>
<Skeleton h={ 4 } borderRadius="full" w="100%" mt={ 2 }/>
<Skeleton h={ 4 } borderRadius="full" w="100%" mt={ 2 }/>
<Skeleton h={ 4 } borderRadius="full" w="50%" mt={ 2 } mb={ 6 }/>
<SkeletonList/>
</Show>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<Skeleton h={ 6 } borderRadius="full" w="90%" mb={ 6 }/> <TxStateTable data={ data } isLoading={ isPlaceholderData }/>
<SkeletonTable columns={ [ '140px', '146px', '33%', '33%', '33%', '150px' ] }/>
</Hide> </Hide>
</> <Show below="lg" ssr={ false }>
); <TxStateList data={ data } isLoading={ isPlaceholderData }/>
</Show>
</Accordion>
) : null;
const content = data ? ( return (
<> <>
<Text> <Text>
A set of information that represents the current state is updated when a transaction takes place on the network. A set of information that represents the current state is updated when a transaction takes place on the network.
The below is a summary of those changes. The below is a summary of those changes.
</Text> </Text>
<Accordion allowMultiple defaultIndex={ [] }> <DataListDisplay
<Hide below="lg" ssr={ false }> isError={ isError }
<TxStateTable data={ data }/> isLoading={ false }
</Hide> items={ data }
<Show below="lg" ssr={ false }> emptyText="There are no state changes for this transaction."
<TxStateList data={ data }/> content={ content }
</Show> skeletonProps={{ customSkeleton: null }}
</Accordion> />
</> </>
) : null;
return (
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
items={ data }
emptyText="There are no state changes for this transaction."
content={ content }
skeletonProps={{ customSkeleton: skeleton }}
/>
); );
}; };
......
...@@ -7,12 +7,13 @@ import TxStateListItem from 'ui/tx/state/TxStateListItem'; ...@@ -7,12 +7,13 @@ import TxStateListItem from 'ui/tx/state/TxStateListItem';
interface Props { interface Props {
data: TxStateChanges; data: TxStateChanges;
isLoading?: boolean;
} }
const TxStateList = ({ data }: Props) => { const TxStateList = ({ data, isLoading }: Props) => {
return ( return (
<Box mt={ 6 }> <Box mt={ 6 }>
{ data.map((item, index) => <TxStateListItem key={ index } data={ item }/>) } { data.map((item, index) => <TxStateListItem key={ index } data={ item } isLoading={ isLoading }/>) }
</Box> </Box>
); );
}; };
......
...@@ -11,48 +11,49 @@ import { getStateElements } from './utils'; ...@@ -11,48 +11,49 @@ import { getStateElements } from './utils';
interface Props { interface Props {
data: TxStateChange; data: TxStateChange;
isLoading?: boolean;
} }
const TxStateListItem = ({ data }: Props) => { const TxStateListItem = ({ data, isLoading }: Props) => {
const { before, after, change, tag, tokenId } = getStateElements(data); const { before, after, change, tag, tokenId } = getStateElements(data, isLoading);
return ( return (
<ListItemMobileGrid.Container> <ListItemMobileGrid.Container>
<ListItemMobileGrid.Label>Address</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py="3px"> <ListItemMobileGrid.Value py="3px">
<Address flexGrow={ 1 } w="100%" alignSelf="center"> <Address flexGrow={ 1 } w="100%" alignSelf="center">
<AddressIcon address={ data.address }/> <AddressIcon address={ data.address } isLoading={ isLoading }/>
<AddressLink type="address" hash={ data.address.hash } ml={ 2 } truncation="constant" mr={ 3 }/> <AddressLink type="address" hash={ data.address.hash } ml={ 2 } truncation="constant" mr={ 3 } isLoading={ isLoading }/>
{ tag } { tag }
</Address> </Address>
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
{ before && ( { before && (
<> <>
<ListItemMobileGrid.Label>Before</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Before</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>{ before }</ListItemMobileGrid.Value> <ListItemMobileGrid.Value>{ before }</ListItemMobileGrid.Value>
</> </>
) } ) }
{ after && ( { after && (
<> <>
<ListItemMobileGrid.Label>After</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>After</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>{ after }</ListItemMobileGrid.Value> <ListItemMobileGrid.Value>{ after }</ListItemMobileGrid.Value>
</> </>
) } ) }
{ change && ( { change && (
<> <>
<ListItemMobileGrid.Label>Change</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Change</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>{ change }</ListItemMobileGrid.Value> <ListItemMobileGrid.Value>{ change }</ListItemMobileGrid.Value>
</> </>
) } ) }
{ tokenId && ( { tokenId && (
<> <>
<ListItemMobileGrid.Label>Token ID</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Token ID</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py="0">{ tokenId }</ListItemMobileGrid.Value> <ListItemMobileGrid.Value py="0">{ tokenId }</ListItemMobileGrid.Value>
</> </>
) } ) }
......
...@@ -13,9 +13,10 @@ import TxStateTableItem from 'ui/tx/state/TxStateTableItem'; ...@@ -13,9 +13,10 @@ import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
interface Props { interface Props {
data: TxStateChanges; data: TxStateChanges;
isLoading?: boolean;
} }
const TxStateTable = ({ data }: Props) => { const TxStateTable = ({ data, isLoading }: Props) => {
return ( return (
<Table variant="simple" minWidth="1000px" size="sm" w="auto" mt={ 6 }> <Table variant="simple" minWidth="1000px" size="sm" w="auto" mt={ 6 }>
<Thead top={ 0 }> <Thead top={ 0 }>
...@@ -29,7 +30,7 @@ const TxStateTable = ({ data }: Props) => { ...@@ -29,7 +30,7 @@ const TxStateTable = ({ data }: Props) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item, index) => <TxStateTableItem data={ item } key={ index }/>) } { data.map((item, index) => <TxStateTableItem data={ item } key={ index } isLoading={ isLoading }/>) }
</Tbody> </Tbody>
</Table> </Table>
); );
......
import { Tr, Td } from '@chakra-ui/react'; import { Tr, Td, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TxStateChange } from 'types/api/txStateChanges'; import type { TxStateChange } from 'types/api/txStateChanges';
...@@ -11,26 +11,37 @@ import { getStateElements } from './utils'; ...@@ -11,26 +11,37 @@ import { getStateElements } from './utils';
interface Props { interface Props {
data: TxStateChange; data: TxStateChange;
isLoading?: boolean;
} }
const TxStateTableItem = ({ data }: Props) => { const TxStateTableItem = ({ data, isLoading }: Props) => {
const { before, after, change, tag, tokenId } = getStateElements(data); const { before, after, change, tag, tokenId } = getStateElements(data, isLoading);
return ( return (
<Tr> <Tr>
<Td lineHeight="30px"> <Td>
{ tag } <Box py="3px">
{ tag }
</Box>
</Td> </Td>
<Td> <Td>
<Address height="30px"> <Address py="3px">
<AddressIcon address={ data.address }/> <AddressIcon address={ data.address } isLoading={ isLoading }/>
<AddressLink type="address" hash={ data.address.hash } alias={ data.address.name } fontWeight="500" truncation="constant" ml={ 2 }/> <AddressLink
type="address"
hash={ data.address.hash }
alias={ data.address.name }
fontWeight="500"
truncation="constant"
ml={ 2 }
isLoading={ isLoading }
/>
</Address> </Address>
</Td> </Td>
<Td isNumeric lineHeight="30px">{ before }</Td> <Td isNumeric><Box py="7px">{ before }</Box></Td>
<Td isNumeric lineHeight="30px">{ after }</Td> <Td isNumeric><Box py="7px">{ after }</Box></Td>
<Td isNumeric lineHeight="30px"> { change } </Td> <Td isNumeric><Box py="7px">{ change }</Box></Td>
<Td lineHeight="30px">{ tokenId }</Td> <Td>{ tokenId }</Td>
</Tr> </Tr>
); );
}; };
......
...@@ -8,9 +8,10 @@ import type { TxStateChangeNftItemFlatten } from './utils'; ...@@ -8,9 +8,10 @@ import type { TxStateChangeNftItemFlatten } from './utils';
interface Props { interface Props {
items: Array<TxStateChangeNftItemFlatten>; items: Array<TxStateChangeNftItemFlatten>;
tokenAddress: string; tokenAddress: string;
isLoading?: boolean;
} }
const TxStateTokenIdList = ({ items, tokenAddress }: Props) => { const TxStateTokenIdList = ({ items, tokenAddress, isLoading }: Props) => {
const [ isCut, setIsCut ] = useBoolean(true); const [ isCut, setIsCut ] = useBoolean(true);
return ( return (
...@@ -22,6 +23,7 @@ const TxStateTokenIdList = ({ items, tokenAddress }: Props) => { ...@@ -22,6 +23,7 @@ const TxStateTokenIdList = ({ items, tokenAddress }: Props) => {
id={ item.total.token_id } id={ item.total.token_id }
w="auto" w="auto"
truncation="constant" truncation="constant"
isLoading={ isLoading }
/> />
)) } )) }
{ items.length > 3 && ( { items.length > 3 && (
......
import { Box, Flex, Tag, Tooltip } from '@chakra-ui/react'; import { Flex, Skeleton, Tooltip } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -7,19 +7,22 @@ import type { ArrayElement } from 'types/utils'; ...@@ -7,19 +7,22 @@ import type { ArrayElement } from 'types/utils';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import { ZERO_ADDRESS } from 'lib/consts'; import { ZERO_ADDRESS } from 'lib/consts';
import { nbsp } from 'lib/html-entities'; import { nbsp, space } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Tag from 'ui/shared/chakra/Tag';
import TxStateTokenIdList from './TxStateTokenIdList'; import TxStateTokenIdList from './TxStateTokenIdList';
export function getStateElements(data: TxStateChange) { export function getStateElements(data: TxStateChange, isLoading?: boolean) {
const tag = (() => { const tag = (() => {
if (data.is_miner) { if (data.is_miner) {
return ( return (
<Tooltip label="A block producer who successfully included the block into the blockchain"> <Tooltip label="A block producer who successfully included the block into the blockchain">
<Tag textTransform="capitalize" colorScheme="yellow">{ getNetworkValidatorTitle() }</Tag> <Tag textTransform="capitalize" colorScheme="yellow" isLoading={ isLoading }>
{ getNetworkValidatorTitle() }
</Tag>
</Tooltip> </Tooltip>
); );
} }
...@@ -37,7 +40,7 @@ export function getStateElements(data: TxStateChange) { ...@@ -37,7 +40,7 @@ export function getStateElements(data: TxStateChange) {
const text = changeDirection === 'from' ? 'Mint' : 'Burn'; const text = changeDirection === 'from' ? 'Mint' : 'Burn';
return ( return (
<Tooltip label="Address used in tokens mintings and burnings"> <Tooltip label="Address used in tokens mintings and burnings">
<Tag textTransform="capitalize" colorScheme="yellow">{ text } address</Tag> <Tag textTransform="capitalize" colorScheme="yellow" isLoading={ isLoading }>{ text } address</Tag>
</Tooltip> </Tooltip>
); );
} }
...@@ -55,14 +58,25 @@ export function getStateElements(data: TxStateChange) { ...@@ -55,14 +58,25 @@ export function getStateElements(data: TxStateChange) {
const changeSign = beforeBn.lte(afterBn) ? '+' : '-'; const changeSign = beforeBn.lte(afterBn) ? '+' : '-';
return { return {
before: <Box>{ beforeBn.toFormat() } { appConfig.network.currency.symbol }</Box>, before: <Skeleton isLoaded={ !isLoading } display="inline-block">{ beforeBn.toFormat() } { appConfig.network.currency.symbol }</Skeleton>,
after: <Box>{ afterBn.toFormat() } { appConfig.network.currency.symbol }</Box>, after: <Skeleton isLoaded={ !isLoading } display="inline-block">{ afterBn.toFormat() } { appConfig.network.currency.symbol }</Skeleton>,
change: <Box color={ changeColor }>{ changeSign }{ nbsp }{ differenceBn.abs().toFormat() }</Box>, change: (
<Skeleton isLoaded={ !isLoading } display="inline-block" color={ changeColor }>
<span>{ changeSign }{ nbsp }{ differenceBn.abs().toFormat() }</span>
</Skeleton>
),
tag, tag,
}; };
} }
case 'token': { case 'token': {
const tokenLink = <AddressLink type="token" hash={ data.token.address } alias={ trimTokenSymbol(data.token?.symbol || data.token.address) }/>; const tokenLink = (
<AddressLink
type="token"
hash={ data.token.address }
alias={ trimTokenSymbol(data.token?.symbol || data.token.address) }
isLoading={ isLoading }
/>
);
const before = Number(data.balance_before); const before = Number(data.balance_before);
const after = Number(data.balance_after); const after = Number(data.balance_after);
const change = (() => { const change = (() => {
...@@ -75,7 +89,11 @@ export function getStateElements(data: TxStateChange) { ...@@ -75,7 +89,11 @@ export function getStateElements(data: TxStateChange) {
const changeColor = difference >= 0 ? 'green.500' : 'red.500'; const changeColor = difference >= 0 ? 'green.500' : 'red.500';
const changeSign = difference >= 0 ? '+' : '-'; const changeSign = difference >= 0 ? '+' : '-';
return <Box color={ changeColor }>{ changeSign }{ nbsp }{ Math.abs(difference).toLocaleString() }</Box>; return (
<Skeleton isLoaded={ !isLoading } display="inline-block" color={ changeColor }>
<span>{ changeSign }{ nbsp }{ Math.abs(difference).toLocaleString() }</span>
</Skeleton>
);
})(); })();
const tokenId = (() => { const tokenId = (() => {
...@@ -84,19 +102,21 @@ export function getStateElements(data: TxStateChange) { ...@@ -84,19 +102,21 @@ export function getStateElements(data: TxStateChange) {
} }
const items = (data.change as Array<TxStateChangeNftItem>).reduce(flattenTotal, []); const items = (data.change as Array<TxStateChangeNftItem>).reduce(flattenTotal, []);
return <TxStateTokenIdList items={ items } tokenAddress={ data.token.address }/>; return <TxStateTokenIdList items={ items } tokenAddress={ data.token.address } isLoading={ isLoading }/>;
})(); })();
return { return {
before: data.balance_before ? ( before: data.balance_before ? (
<Flex whiteSpace="pre-wrap" justifyContent={{ base: 'flex-start', lg: 'flex-end' }}> <Flex whiteSpace="pre-wrap" justifyContent={{ base: 'flex-start', lg: 'flex-end' }}>
<span>{ before.toLocaleString() } </span> <Skeleton isLoaded={ !isLoading }>{ before.toLocaleString() }</Skeleton>
<span>{ space }</span>
{ tokenLink } { tokenLink }
</Flex> </Flex>
) : null, ) : null,
after: data.balance_after ? ( after: data.balance_after ? (
<Flex whiteSpace="pre-wrap" justifyContent={{ base: 'flex-start', lg: 'flex-end' }}> <Flex whiteSpace="pre-wrap" justifyContent={{ base: 'flex-start', lg: 'flex-end' }}>
<span>{ after.toLocaleString() } </span> <Skeleton isLoaded={ !isLoading }>{ after.toLocaleString() }</Skeleton>
<span>{ space }</span>
{ tokenLink } { tokenLink }
</Flex> </Flex>
) : null, ) : null,
......
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