Commit a2cced29 authored by tom's avatar tom

skeletons for logs

parent 3c3e22ea
import type { Log, LogsResponseAddress, LogsResponseTx } from 'types/api/log';
import { ADDRESS_PARAMS } from './addressParams';
import { TX_HASH } from './tx';
export const LOG: Log = {
address: ADDRESS_PARAMS,
data: '0x000000000000000000000000000000000000000000000000000000d75e4be200',
decoded: null,
index: 42,
topics: [
'0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925',
'0x000000000000000000000000c52ea157a7fb3e25a069d47df0428ac70cd656b1',
'0x000000000000000000000000302fd86163cb9ad5533b3952dafa3b633a82bc51',
null,
],
tx_hash: TX_HASH,
};
export const TX_LOGS: LogsResponseTx = {
items: Array(3).fill(LOG),
next_page_params: null,
};
export const ADDRESS_LOGS: LogsResponseAddress = {
items: Array(3).fill(LOG),
next_page_params: {
block_number: 9005750,
index: 42,
items_count: 50,
transaction_index: 23,
},
};
...@@ -16,7 +16,7 @@ export interface LogsResponseTx { ...@@ -16,7 +16,7 @@ export interface LogsResponseTx {
index: number; index: number;
items_count: number; items_count: number;
transaction_hash: string; transaction_hash: string;
}; } | null;
} }
export interface LogsResponseAddress { export interface LogsResponseAddress {
...@@ -26,5 +26,5 @@ export interface LogsResponseAddress { ...@@ -26,5 +26,5 @@ export interface LogsResponseAddress {
items_count: number; items_count: number;
transaction_index: number; transaction_index: number;
block_number: number; block_number: number;
}; } | null;
} }
...@@ -3,20 +3,23 @@ import React from 'react'; ...@@ -3,20 +3,23 @@ import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { ADDRESS_LOGS } from 'stubs/log';
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 LogItem from 'ui/shared/logs/LogItem'; import LogItem from 'ui/shared/logs/LogItem';
import LogSkeleton from 'ui/shared/logs/LogSkeleton';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => { const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter(); const router = useRouter();
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({ const { data, isPlaceholderData, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_logs', resourceName: 'address_logs',
pathParams: { hash }, pathParams: { hash },
scrollRef, scrollRef,
options: {
placeholderData: ADDRESS_LOGS,
},
}); });
const actionBar = isPaginationVisible ? ( const actionBar = isPaginationVisible ? (
...@@ -25,19 +28,17 @@ const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement> ...@@ -25,19 +28,17 @@ const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>
</ActionBar> </ActionBar>
) : null; ) : null;
const content = data?.items ? data.items.map((item, index) => <LogItem key={ index } { ...item } type="address"/>) : null; const content = data?.items ? data.items.map((item, index) => <LogItem key={ index } { ...item } type="address" isLoading={ isPlaceholderData }/>) : null;
const skeleton = <><LogSkeleton/><LogSkeleton/></>;
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
isLoading={ isLoading } isLoading={ false }
items={ data?.items } items={ data?.items }
emptyText="There are no logs for this address." emptyText="There are no logs for this address."
content={ content } content={ content }
actionBar={ actionBar } actionBar={ actionBar }
skeletonProps={{ customSkeleton: skeleton }} skeletonProps={{ customSkeleton: null }}
/> />
); );
}; };
......
import { Text, Grid, GridItem, Tooltip, Button, useColorModeValue, Alert, Link } from '@chakra-ui/react'; import { Grid, GridItem, Tooltip, Button, useColorModeValue, Alert, Link, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -14,15 +14,16 @@ import LogTopic from 'ui/shared/logs/LogTopic'; ...@@ -14,15 +14,16 @@ import LogTopic from 'ui/shared/logs/LogTopic';
type Props = Log & { type Props = Log & {
type: 'address' | 'transaction'; type: 'address' | 'transaction';
isLoading?: boolean;
}; };
const RowHeader = ({ children }: { children: React.ReactNode }) => ( const RowHeader = ({ children, isLoading }: { children: React.ReactNode; isLoading?: boolean }) => (
<GridItem _notFirst={{ my: { base: 4, lg: 0 } }}> <GridItem _notFirst={{ my: { base: 4, lg: 0 } }}>
<Text fontWeight={ 500 }>{ children }</Text> <Skeleton fontWeight={ 500 } isLoaded={ !isLoading } display="inline-block">{ children }</Skeleton>
</GridItem> </GridItem>
); );
const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash }: Props) => { const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash, isLoading }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
...@@ -50,14 +51,15 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash ...@@ -50,14 +51,15 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
</Alert> </Alert>
</GridItem> </GridItem>
) } ) }
{ hasTxInfo ? <RowHeader>Transaction</RowHeader> : <RowHeader>Address</RowHeader> } { hasTxInfo ? <RowHeader isLoading={ isLoading }>Transaction</RowHeader> : <RowHeader isLoading={ isLoading }>Address</RowHeader> }
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address mr={{ base: 9, lg: 0 }}> <Address mr={{ base: 9, lg: 0 }}>
{ !hasTxInfo && <AddressIcon address={ address } mr={ 2 }/> } { !hasTxInfo && <AddressIcon address={ address } mr={ 2 } isLoading={ isLoading }/> }
<AddressLink <AddressLink
hash={ hasTxInfo ? txHash : address.hash } hash={ hasTxInfo ? txHash : address.hash }
alias={ hasTxInfo ? undefined : address.name } alias={ hasTxInfo ? undefined : address.name }
type={ type === 'address' ? 'transaction' : 'address' } type={ type === 'address' ? 'transaction' : 'address' }
isLoading={ isLoading }
/> />
</Address> </Address>
{ /* api doesn't have find topic feature yet */ } { /* api doesn't have find topic feature yet */ }
...@@ -66,11 +68,13 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash ...@@ -66,11 +68,13 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
<Icon as={ searchIcon } boxSize={ 5 }/> <Icon as={ searchIcon } boxSize={ 5 }/>
</Link> </Link>
</Tooltip> */ } </Tooltip> */ }
<Tooltip label="Log index"> <Skeleton isLoaded={ !isLoading } ml="auto" borderRadius="base">
<Button variant="outline" colorScheme="gray" isActive ml="auto" size="sm" fontWeight={ 400 }> <Tooltip label="Log index">
{ index } <Button variant="outline" colorScheme="gray" isActive size="sm" fontWeight={ 400 }>
</Button> { index }
</Tooltip> </Button>
</Tooltip>
</Skeleton>
</GridItem> </GridItem>
{ decoded && ( { decoded && (
<> <>
...@@ -80,20 +84,21 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash ...@@ -80,20 +84,21 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
</GridItem> </GridItem>
</> </>
) } ) }
<RowHeader>Topics</RowHeader> <RowHeader isLoading={ isLoading }>Topics</RowHeader>
<GridItem> <GridItem>
{ topics.filter(Boolean).map((item, index) => ( { topics.filter(Boolean).map((item, index) => (
<LogTopic <LogTopic
key={ index } key={ index }
hex={ item } hex={ item }
index={ index } index={ index }
isLoading={ isLoading }
/> />
)) } )) }
</GridItem> </GridItem>
<RowHeader>Data</RowHeader> <RowHeader isLoading={ isLoading }>Data</RowHeader>
<GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }> <Skeleton isLoaded={ !isLoading } p={ 4 } fontSize="sm" borderRadius="md" bgColor={ isLoading ? undefined : dataBgColor }>
{ data } { data }
</GridItem> </Skeleton>
</Grid> </Grid>
); );
}; };
......
import { Flex, Grid, GridItem, Skeleton, SkeletonCircle, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const RowHeader = () => (
<GridItem _notFirst={{ my: { base: 4, lg: 0 } }} _first={{ alignSelf: 'center' }}>
<Skeleton h={ 6 } borderRadius="full" w="150px"/>
</GridItem>
);
const TopicRow = () => (
<Flex columnGap={ 3 }>
<SkeletonCircle size="6"/>
<Skeleton h={ 6 } w="70px" borderRadius="full"/>
<Skeleton h={ 6 } flexGrow={ 1 } borderRadius="full"/>
</Flex>
);
const LogSkeleton = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Grid
gap={{ base: 2, lg: 8 }}
templateColumns={{ base: '1fr', lg: '200px 1fr' }}
py={ 8 }
_notFirst={{
borderTopWidth: '1px',
borderTopColor: borderColor,
}}
_first={{
pt: 0,
}}
>
<RowHeader/>
<GridItem display="flex" alignItems="center">
<SkeletonCircle size="6"/>
<Skeleton h={ 6 } flexGrow={ 1 } borderRadius="full" ml={ 2 } mr={ 9 }/>
<Skeleton h={ 8 } w={ 8 } borderRadius="base"/>
</GridItem>
<RowHeader/>
<GridItem>
<Skeleton h="150px" w="100%" borderRadius="base"/>
</GridItem>
<RowHeader/>
<GridItem display="flex" flexDir="column" rowGap={ 3 }>
<TopicRow/>
<TopicRow/>
<TopicRow/>
</GridItem>
<RowHeader/>
<GridItem>
<Skeleton h="60px" w="100%" borderRadius="base"/>
</GridItem>
</Grid>
);
};
export default LogSkeleton;
import { Flex, Button, Select, Box } from '@chakra-ui/react'; import { Flex, Button, Select, Skeleton } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
...@@ -12,6 +12,7 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; ...@@ -12,6 +12,7 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
hex: string; hex: string;
index: number; index: number;
isLoading?: boolean;
} }
type DataType = 'hex' | 'text' | 'address' | 'number'; type DataType = 'hex' | 'text' | 'address' | 'number';
...@@ -24,7 +25,7 @@ const VALUE_CONVERTERS: Record<DataType, (hex: string) => string> = { ...@@ -24,7 +25,7 @@ const VALUE_CONVERTERS: Record<DataType, (hex: string) => string> = {
}; };
const OPTIONS: Array<DataType> = [ 'hex', 'address', 'text', 'number' ]; const OPTIONS: Array<DataType> = [ 'hex', 'address', 'text', 'number' ];
const LogTopic = ({ hex, index }: Props) => { const LogTopic = ({ hex, index, isLoading }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('hex'); const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('hex');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => { const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
...@@ -40,10 +41,10 @@ const LogTopic = ({ hex, index }: Props) => { ...@@ -40,10 +41,10 @@ const LogTopic = ({ hex, index }: Props) => {
case 'text': { case 'text': {
return ( return (
<> <>
<Box overflow="hidden" whiteSpace="nowrap"> <Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="nowrap">
<HashStringShortenDynamic hash={ value }/> <HashStringShortenDynamic hash={ value }/>
</Box> </Skeleton>
<CopyToClipboard text={ value }/> <CopyToClipboard text={ value } isLoading={ isLoading }/>
</> </>
); );
} }
...@@ -51,8 +52,8 @@ const LogTopic = ({ hex, index }: Props) => { ...@@ -51,8 +52,8 @@ const LogTopic = ({ hex, index }: Props) => {
case 'address': { case 'address': {
return ( return (
<Address> <Address>
<AddressLink type="address" hash={ value }/> <AddressLink type="address" hash={ value } isLoading={ isLoading }/>
<CopyToClipboard text={ value }/> <CopyToClipboard text={ value } isLoading={ isLoading }/>
</Address> </Address>
); );
} }
...@@ -61,22 +62,24 @@ const LogTopic = ({ hex, index }: Props) => { ...@@ -61,22 +62,24 @@ const LogTopic = ({ hex, index }: Props) => {
return ( return (
<Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%"> <Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%">
<Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }> <Skeleton isLoaded={ !isLoading } mr={ 3 } borderRadius="base">
{ index } <Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } w={ 6 }>
</Button> { index }
</Button>
</Skeleton>
{ index !== 0 && ( { index !== 0 && (
<Select <Skeleton isLoaded={ !isLoading } mr={ 3 } flexShrink={ 0 } borderRadius="base">
size="xs" <Select
borderRadius="base" size="xs"
value={ selectedDataType } borderRadius="base"
onChange={ handleSelectChange } value={ selectedDataType }
mr={ 3 } onChange={ handleSelectChange }
flexShrink={ 0 } w="auto"
w="auto" aria-label="Data type"
aria-label="Data type" >
> { OPTIONS.map((option) => <option key={ option } value={ option }>{ capitalize(option) }</option>) }
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ capitalize(option) }</option>) } </Select>
</Select> </Skeleton>
) } ) }
{ content } { content }
</Flex> </Flex>
......
...@@ -3,10 +3,10 @@ import React from 'react'; ...@@ -3,10 +3,10 @@ import React from 'react';
import { SECOND } from 'lib/consts'; import { SECOND } from 'lib/consts';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { TX_LOGS } from 'stubs/log';
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 LogItem from 'ui/shared/logs/LogItem'; import LogItem from 'ui/shared/logs/LogItem';
import LogSkeleton from 'ui/shared/logs/LogSkeleton';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import TxPendingAlert from 'ui/tx/TxPendingAlert'; import TxPendingAlert from 'ui/tx/TxPendingAlert';
import TxSocketAlert from 'ui/tx/TxSocketAlert'; import TxSocketAlert from 'ui/tx/TxSocketAlert';
...@@ -14,15 +14,16 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo'; ...@@ -14,15 +14,16 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxLogs = () => { const TxLogs = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND }); const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({ const { data, isPlaceholderData, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'tx_logs', resourceName: 'tx_logs',
pathParams: { hash: txInfo.data?.hash }, pathParams: { hash: txInfo.data?.hash },
options: { options: {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status), enabled: !txInfo.isPlaceholderData && Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
placeholderData: TX_LOGS,
}, },
}); });
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/>;
} }
...@@ -30,16 +31,7 @@ const TxLogs = () => { ...@@ -30,16 +31,7 @@ const TxLogs = () => {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
if (isLoading || txInfo.isLoading) { if (!data?.items.length) {
return (
<Box>
<LogSkeleton/>
<LogSkeleton/>
</Box>
);
}
if (data.items.length === 0) {
return <Text as="span">There are no logs for this transaction.</Text>; return <Text as="span">There are no logs for this transaction.</Text>;
} }
...@@ -50,7 +42,7 @@ const TxLogs = () => { ...@@ -50,7 +42,7 @@ const TxLogs = () => {
<Pagination ml="auto" { ...pagination }/> <Pagination ml="auto" { ...pagination }/>
</ActionBar> </ActionBar>
) } ) }
{ data.items.map((item, index) => <LogItem key={ index } { ...item } type="transaction"/>) } { data?.items.map((item, index) => <LogItem key={ index } { ...item } type="transaction" isLoading={ isPlaceholderData }/>) }
</Box> </Box>
); );
}; };
......
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