Commit 4b7cec98 authored by tom's avatar tom

skeletons for output roots

parent c2226172
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import L2OutputRoots from 'ui/pages/L2OutputRoots';
import Page from 'ui/shared/Page/Page';
const L2OutputRoots = dynamic(() => import('ui/pages/L2OutputRoots'), { ssr: false });
const OutputRootsPage: NextPage = () => {
const title = getNetworkTitle();
......@@ -12,7 +15,9 @@ const OutputRootsPage: NextPage = () => {
<Head>
<title>{ title }</title>
</Head>
<L2OutputRoots/>
<Page>
<L2OutputRoots/>
</Page>
</>
);
};
......
import type { L2DepositsItem } from 'types/api/l2Deposits';
import type { L2OutputRootsItem } from 'types/api/l2OutputRoots';
import type { L2TxnBatchesItem } from 'types/api/l2TxnBatches';
import type { L2WithdrawalsItem } from 'types/api/l2Withdrawals';
......@@ -34,3 +35,12 @@ export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = {
l2_block_number: 5218590,
tx_count: 9,
};
export const L2_OUTPUT_ROOTS_ITEM: L2OutputRootsItem = {
l1_block_number: 9103684,
l1_timestamp: '2023-06-01T15:26:12.000000Z',
l1_tx_hash: TX_HASH,
l2_block_number: 10102468,
l2_output_index: 50655,
output_root: TX_HASH,
};
......@@ -9,7 +9,6 @@ export type L2OutputRootsItem = {
export type L2OutputRootsResponse = {
items: Array<L2OutputRootsItem>;
total: number;
next_page_params: {
index: number;
items_count: number;
......
......@@ -29,7 +29,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: item.l1_block_number.toString() } }) }
fontWeight={ 600 }
display="inline-flex"
display="flex"
isLoading={ isLoading }
>
<Icon as={ blockIcon } boxSize={ 6 } isLoading={ isLoading }/>
......@@ -65,7 +65,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: item.l1_tx_hash } }) }
maxW="100%"
display="inline-flex"
display="flex"
overflow="hidden"
isLoading={ isLoading }
>
......@@ -81,7 +81,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/address/[hash]', query: { hash: item.l1_tx_origin } }) }
maxW="100%"
display="inline-flex"
display="flex"
overflow="hidden"
isLoading={ isLoading }
>
......
import { Box, Flex, Text, Icon } from '@chakra-ui/react';
import { Flex, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -7,58 +7,69 @@ import type { L2OutputRootsItem } from 'types/api/l2OutputRoots';
import appConfig from 'configs/app/config';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import Icon from 'ui/shared/chakra/Icon';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
type Props = { item: L2OutputRootsItem };
type Props = { item: L2OutputRootsItem; isLoading?: boolean };
const OutputRootsListItem = ({ item }: Props) => {
const OutputRootsListItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.l1_timestamp).fromNow();
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label>L2 output index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 output index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value fontWeight={ 600 } color="text">
{ item.l2_output_index }
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.l2_output_index }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>{ timeAgo }</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
<span>{ timeAgo }</span>
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label>L2 block #</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 block #</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<LinkInternal
display="flex"
width="fit-content"
alignItems="center"
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: item.l2_block_number.toString() } }) }
isLoading={ isLoading }
>
{ item.l2_block_number }
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.l2_block_number }</Skeleton>
</LinkInternal>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label>L1 txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value py="3px">
<LinkExternal
maxW="100%"
display="inline-flex"
display="flex"
overflow="hidden"
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: item.l1_tx_hash } }) }
isLoading={ isLoading }
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_hash }/></Box>
<Icon as={ txIcon } boxSize={ 6 } isLoading={ isLoading }/>
<Skeleton isLoaded={ !isLoading } w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap" ml={ 1 }>
<HashStringShortenDynamic hash={ item.l1_tx_hash }/>
</Skeleton>
</LinkExternal>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label>Output root</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Output root</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Flex overflow="hidden" whiteSpace="nowrap" alignItems="center" w="100%" justifyContent="space-between">
<Text variant="secondary" w="calc(100% - 24px)"><HashStringShortenDynamic hash={ item.output_root }/></Text>
<CopyToClipboard text={ item.output_root }/>
<Skeleton isLoaded={ !isLoading } color="text_secondary" w="calc(100% - 24px)">
<HashStringShortenDynamic hash={ item.output_root }/>
</Skeleton>
<CopyToClipboard text={ item.output_root } isLoading={ isLoading }/>
</Flex>
</ListItemMobileGrid.Value>
......
......@@ -10,9 +10,10 @@ import OutputRootsTableItem from './OutputRootsTableItem';
type Props = {
items: Array<L2OutputRootsItem>;
top: number;
isLoading?: boolean;
}
const OutputRootsTable = ({ items, top }: Props) => {
const OutputRootsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="900px">
<Thead top={ top }>
......@@ -25,8 +26,8 @@ const OutputRootsTable = ({ items, top }: Props) => {
</Tr>
</Thead>
<Tbody>
{ items.map((item) => (
<OutputRootsTableItem key={ item.l2_output_index } item={ item }/>
{ items.map((item, index) => (
<OutputRootsTableItem key={ item.l2_output_index + (Number(isLoading ? index : '') ? String(index) : '') } item={ item } isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
......
import { Box, Flex, Td, Tr, Text, Icon } from '@chakra-ui/react';
import { Flex, Td, Tr, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -8,23 +8,24 @@ import appConfig from 'configs/app/config';
import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg';
import dayjs from 'lib/date/dayjs';
import Icon from 'ui/shared/chakra/Icon';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = { item: L2OutputRootsItem };
type Props = { item: L2OutputRootsItem; isLoading?: boolean };
const OutputRootsTableItem = ({ item }: Props) => {
const OutputRootsTableItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.l1_timestamp).fromNow();
return (
<Tr>
<Td verticalAlign="middle">
<Text>{ item.l2_output_index }</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.l2_output_index }</Skeleton>
</Td>
<Td verticalAlign="middle">
<Text variant="secondary">{ timeAgo }</Text>
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline-block"><span>{ timeAgo }</span></Skeleton>
</Td>
<Td verticalAlign="middle">
<LinkInternal
......@@ -33,9 +34,12 @@ const OutputRootsTableItem = ({ item }: Props) => {
width="fit-content"
alignItems="center"
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: item.l2_block_number.toString() } }) }
isLoading={ isLoading }
>
<Icon as={ txBatchIcon } boxSize={ 6 } mr={ 1 }/>
{ item.l2_block_number }
<Icon as={ txBatchIcon } boxSize={ 6 } isLoading={ isLoading }/>
<Skeleton isLoaded={ !isLoading } display="inline-block" ml={ 1 }>
{ item.l2_block_number }
</Skeleton>
</LinkInternal>
</Td>
<Td verticalAlign="middle" pr={ 12 }>
......@@ -44,16 +48,21 @@ const OutputRootsTableItem = ({ item }: Props) => {
maxW="100%"
display="inline-flex"
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: item.l1_tx_hash } }) }
isLoading={ isLoading }
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_hash }/></Box>
<Icon as={ txIcon } boxSize={ 6 } isLoading={ isLoading }/>
<Skeleton isLoaded={ !isLoading } w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap" ml={ 1 } >
<HashStringShortenDynamic hash={ item.l1_tx_hash }/>
</Skeleton>
</LinkExternal>
</Flex>
</Td>
<Td verticalAlign="middle">
<Flex overflow="hidden" whiteSpace="nowrap" w="100%" alignItems="center">
<Box w="calc(100% - 36px)"><HashStringShortenDynamic hash={ item.output_root }/></Box>
<CopyToClipboard text={ item.output_root } ml={ 2 }/>
<Skeleton isLoaded={ !isLoading } w="calc(100% - 36px)">
<HashStringShortenDynamic hash={ item.output_root }/>
</Skeleton>
<CopyToClipboard text={ item.output_root } ml={ 2 } isLoading={ isLoading }/>
</Flex>
</Td>
</Tr>
......
import { Flex, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import { Box, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { L2_OUTPUT_ROOTS_ITEM } from 'stubs/L2';
import { generateListStub } from 'stubs/utils';
import OutputRootsListItem from 'ui/l2OutputRoots/OutputRootsListItem';
import OutputRootsTable from 'ui/l2OutputRoots/OutputRootsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
const L2OutputRoots = () => {
const isMobile = useIsMobile();
const { data, isError, isLoading, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
resourceName: 'l2_output_roots',
options: {
placeholderData: generateListStub<'l2_output_roots'>(
L2_OUTPUT_ROOTS_ITEM,
50,
{
next_page_params: {
items_count: 50,
index: 9045200,
},
},
),
},
});
const countersQuery = useApiQuery('l2_output_roots_count');
const countersQuery = useApiQuery('l2_output_roots_count', {
queryOptions: {
placeholderData: 50617,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>{ data.items.map((item => <OutputRootsListItem key={ item.l2_output_index } item={ item }/>)) }</Show>
<Hide below="lg" ssr={ false }><OutputRootsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 }/></Hide>
<Show below="lg" ssr={ false }>
{ data.items.map(((item, index) => (
<OutputRootsListItem
key={ item.l2_output_index + (isPlaceholderData ? String(index) : '') }
item={ item }
isLoading={ isPlaceholderData }
/>
))) }
</Show>
<Hide below="lg" ssr={ false }>
<OutputRootsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
const text = (() => {
if (countersQuery.isLoading || isLoading) {
return (
<Skeleton
w={{ base: '100%', lg: '400px' }}
h={{ base: '48px', lg: '24px' }}
mb={{ base: 6, lg: isPaginationVisible ? 0 : 7 }}
mt={{ base: 0, lg: isPaginationVisible ? 0 : 7 }}
/>
);
}
if (countersQuery.isError || isError || data?.items.length === 0) {
if (countersQuery.isError || isError || !data?.items.length) {
return null;
}
return (
<Flex mb={{ base: 6, lg: isPaginationVisible ? 0 : 6 }} flexWrap="wrap">
L2 output index
<Skeleton isLoaded={ !countersQuery.isPlaceholderData && !isPlaceholderData } display="flex" flexWrap="wrap">
L2 output index
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].l2_output_index } </Text>to
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[data.items.length - 1].l2_output_index } </Text>
(total of { countersQuery.data.toLocaleString() } roots)
</Flex>
(total of { countersQuery.data?.toLocaleString() } roots)
</Skeleton>
);
})();
const actionBar = (
<>
{ (isMobile || !isPaginationVisible) && text }
{ isPaginationVisible && (
<ActionBar mt={ -6 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ !isMobile && text }
<Pagination ml="auto" { ...pagination }/>
</Flex>
</ActionBar>
) }
<Box mb={ 6 } display={{ base: 'block', lg: 'none' }}>
{ text }
</Box>
<ActionBar mt={ -6 } alignItems="center">
<Box display={{ base: 'none', lg: 'block' }}>
{ text }
</Box>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
</>
);
return (
<Page>
<>
<PageTitle title="Output roots" withTextAd/>
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
isLoading={ false }
items={ data?.items }
skeletonProps={{ skeletonDesktopColumns: [ '140px', '20%', '20%', '30%', '30%' ] }}
emptyText="There are no output roots."
content={ content }
actionBar={ actionBar }
/>
</Page>
</>
);
};
......
......@@ -20,7 +20,6 @@ const Container = chakra(({ isAnimated, children, className }: ContainerProps) =
rowGap={ 2 }
columnGap={ 2 }
gridTemplateColumns="86px auto"
gridTemplateRows="30px"
alignItems="start"
paddingY={ 4 }
borderColor="divider"
......
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