Commit ea01511b authored by tom's avatar tom

refactor latest blocks, batches and deposits

parent 083fd33f
import React from 'react';
type Id = string | number;
interface Params<T> {
data: Array<T>;
idFn: (item: T) => Id;
enabled: boolean;
}
export default function useInitialList<T>({ data, idFn, enabled }: Params<T>) {
const [ list, setList ] = React.useState<Array<Id>>([]);
React.useEffect(() => {
if (enabled) {
setList(data.map(idFn));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ enabled ]);
const isNew = React.useCallback((data: T) => {
return !list.includes(idFn(data));
}, [ list, idFn ]);
return React.useMemo(() => {
return {
list,
isNew,
};
}, [ list, isNew ]);
}
......@@ -57,6 +57,14 @@ export const textStyles: ThemingConfig['textStyles'] = {
},
},
text: {
xl: {
value: {
fontSize: '20px',
lineHeight: '28px',
fontWeight: '400',
fontFamily: 'body',
},
},
md: {
value: {
fontSize: '16px',
......
import { chakra, Box, Heading, Flex, Text, VStack } from '@chakra-ui/react';
import { chakra, Box, Flex, Text, VStack } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
......@@ -10,13 +9,15 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useInitialList from 'lib/hooks/useInitialList';
import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { BLOCK } from 'stubs/block';
import { HOMEPAGE_STATS } from 'stubs/stats';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Heading } from 'toolkit/chakra/heading';
import { Skeleton } from 'toolkit/chakra/skeleton';
import LinkInternal from 'ui/shared/links/LinkInternal';
import LatestBlocksItem from './LatestBlocksItem';
......@@ -35,6 +36,11 @@ const LatestBlocks = () => {
placeholderData: Array(blocksMaxCount).fill(BLOCK),
},
});
const initialList = useInitialList({
data: data ?? [],
idFn: (block) => block.height,
enabled: !isPlaceholderData,
});
const queryClient = useQueryClient();
const statsQueryResult = useApiQuery('stats', {
......@@ -78,16 +84,15 @@ const LatestBlocks = () => {
content = (
<>
<VStack spacing={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
<AnimatePresence initial={ false } >
<VStack gap={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
{ dataToShow.map(((block, index) => (
<LatestBlocksItem
key={ block.height + (isPlaceholderData ? String(index) : '') }
block={ block }
isLoading={ isPlaceholderData }
isNew={ initialList.isNew(block) }
/>
))) }
</AnimatePresence>
</VStack>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ route({ pathname: '/blocks' }) }>View all blocks</LinkInternal>
......@@ -98,9 +103,9 @@ const LatestBlocks = () => {
return (
<Box width={{ base: '100%', lg: '280px' }} flexShrink={ 0 }>
<Heading as="h4" size="sm">Latest blocks</Heading>
<Heading level="3" mb={ 3 }>Latest blocks</Heading>
{ statsQueryResult.data?.network_utilization_percentage !== undefined && (
<Skeleton isLoaded={ !statsQueryResult.isPlaceholderData } mt={ 1 } display="inline-block">
<Skeleton loading={ statsQueryResult.isPlaceholderData } mt={ 1 } display="inline-block">
<Text as="span" fontSize="sm">
Network utilization:{ nbsp }
</Text>
......
import {
Box,
Flex,
Grid,
Tooltip,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { Box, Flex, Grid } from '@chakra-ui/react';
import React from 'react';
import type { Block } from 'types/api/block';
......@@ -12,7 +6,8 @@ import type { Block } from 'types/api/block';
import config from 'configs/app';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import IconSvg from 'ui/shared/IconSvg';
......@@ -21,18 +16,14 @@ import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
type Props = {
block: Block;
isLoading?: boolean;
isNew?: boolean;
};
const LatestBlocksItem = ({ block, isLoading }: Props) => {
const LatestBlocksItem = ({ block, isLoading, isNew }: Props) => {
const totalReward = getBlockTotalReward(block);
return (
<Box
as={ motion.div }
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ display: 'none' }}
transitionDuration="normal"
transitionTimingFunction="linear"
animation={ isNew ? 'fade-in 500ms linear' : undefined }
borderRadius="md"
border="1px solid"
borderColor="border.divider"
......@@ -43,13 +34,12 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => {
isLoading={ isLoading }
number={ block.height }
tailLength={ 2 }
fontSize="xl"
lineHeight={ 7 }
textStyle="xl"
fontWeight={ 500 }
mr="auto"
/>
{ block.celo?.is_epoch_block && (
<Tooltip label={ `Finalized epoch #${ block.celo.epoch_number }` }>
<Tooltip content={ `Finalized epoch #${ block.celo.epoch_number }` }>
<IconSvg name="checkered_flag" boxSize={ 5 } p="1px" ml={ 2 } isLoading={ isLoading } flexShrink={ 0 }/>
</Tooltip>
) }
......@@ -57,28 +47,27 @@ const LatestBlocksItem = ({ block, isLoading }: Props) => {
timestamp={ block.timestamp }
enableIncrement={ !isLoading }
isLoading={ isLoading }
color="text_secondary"
fontWeight={ 400 }
color="text.secondary"
display="inline-block"
fontSize="sm"
textStyle="sm"
flexShrink={ 0 }
ml={ 2 }
/>
</Flex>
<Grid gridGap={ 2 } templateColumns="auto minmax(0, 1fr)" fontSize="sm">
<Skeleton isLoaded={ !isLoading }>Txn</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ block.transaction_count }</span></Skeleton>
<Skeleton loading={ isLoading }>Txn</Skeleton>
<Skeleton loading={ isLoading } color="text.secondary"><span>{ block.transaction_count }</span></Skeleton>
{ !config.features.rollup.isEnabled && !config.UI.views.block.hiddenFields?.total_reward && (
<>
<Skeleton isLoaded={ !isLoading }>Reward</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ totalReward.dp(10).toFixed() }</span></Skeleton>
<Skeleton loading={ isLoading }>Reward</Skeleton>
<Skeleton loading={ isLoading } color="text.secondary"><span>{ totalReward.dp(10).toFixed() }</span></Skeleton>
</>
) }
{ !config.features.rollup.isEnabled && !config.UI.views.block.hiddenFields?.miner && (
<>
<Skeleton isLoaded={ !isLoading } textTransform="capitalize">{ getNetworkValidatorTitle() }</Skeleton>
<Skeleton loading={ isLoading } textTransform="capitalize">{ getNetworkValidatorTitle() }</Skeleton>
<AddressEntity
address={ block.miner }
isLoading={ isLoading }
......
......@@ -19,7 +19,7 @@ const ChainIndicatorChartContainer = ({ data, isError, isPending }: Props) => {
}
if (isError) {
return <DataFetchAlert fontSize="xs" p={ 3 }/>;
return <DataFetchAlert fontSize="xs"/>;
}
if (data[0].items.length === 0) {
......
import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react';
import { Box, Flex, Text, VStack } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
// import { AnimatePresence } from 'framer-motion';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
......@@ -9,10 +9,12 @@ import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
import { route } from 'nextjs-routes';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useInitialList from 'lib/hooks/useInitialList';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2';
import { Heading } from 'toolkit/chakra/heading';
import LinkInternal from 'ui/shared/links/LinkInternal';
import LatestBatchItem from './LatestBatchItem';
......@@ -28,6 +30,12 @@ const LatestArbitrumL2Batches = () => {
},
});
const initialList = useInitialList({
data: data?.items ?? [],
idFn: (batch) => batch.number,
enabled: !isPlaceholderData,
});
const handleNewBatchMessage: SocketMessage.NewArbitrumL2Batch['handler'] = React.useCallback((payload) => {
queryClient.setQueryData(getResourceKey('homepage_arbitrum_l2_batches'), (prevData: { items: Array<ArbitrumL2TxnBatchesItem> } | undefined) => {
const newItems = prevData?.items ? [ ...prevData.items ] : [];
......@@ -61,8 +69,7 @@ const LatestArbitrumL2Batches = () => {
content = (
<>
<VStack spacing={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
<AnimatePresence initial={ false } >
<VStack gap={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
{ dataToShow.map(((batch, index) => (
<LatestBatchItem
key={ batch.number + (isPlaceholderData ? String(index) : '') }
......@@ -70,9 +77,9 @@ const LatestArbitrumL2Batches = () => {
timestamp={ batch.commitment_transaction.timestamp }
txCount={ batch.transactions_count }
isLoading={ isPlaceholderData }
isNew={ initialList.isNew(batch) }
/>
))) }
</AnimatePresence>
</VStack>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ route({ pathname: '/batches' }) }>View all batches</LinkInternal>
......@@ -83,7 +90,7 @@ const LatestArbitrumL2Batches = () => {
return (
<Box width={{ base: '100%', lg: '280px' }} flexShrink={ 0 }>
<Heading as="h4" size="sm" mb={ 3 }>Latest batches</Heading>
<Heading level="3" mb={ 3 }>Latest batches</Heading>
{ content }
</Box>
);
......
import {
Box,
Flex,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
import LinkInternal from 'ui/shared/links/LinkInternal';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......@@ -18,17 +14,13 @@ type Props = {
txCount: number;
status?: React.ReactNode;
isLoading: boolean;
isNew?: boolean;
};
const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Props) => {
const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading, isNew }: Props) => {
return (
<Box
as={ motion.div }
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ display: 'none' }}
transitionDuration="normal"
transitionTimingFunction="linear"
animation={ isNew ? 'fade-in 500ms linear' : undefined }
borderRadius="md"
border="1px solid"
borderColor="border.divider"
......@@ -39,8 +31,7 @@ const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Prop
isLoading={ isLoading }
number={ number }
tailLength={ 2 }
fontSize="xl"
lineHeight={ 7 }
textStyle="xl"
fontWeight={ 500 }
mr="auto"
/>
......@@ -48,22 +39,21 @@ const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Prop
timestamp={ timestamp }
enableIncrement={ !isLoading }
isLoading={ isLoading }
color="text_secondary"
fontWeight={ 400 }
color="text.secondary"
display="inline-block"
fontSize="sm"
textStyle="sm"
flexShrink={ 0 }
ml={ 2 }
/>
</Flex>
<Flex alignItems="center" justifyContent="space-between" w="100%" flexWrap="wrap">
<Flex alignItems="center">
<Skeleton isLoaded={ !isLoading } mr={ 2 }>Txn</Skeleton>
<Skeleton loading={ isLoading } mr={ 2 }>Txn</Skeleton>
<LinkInternal
href={ route({ pathname: '/batches/[number]', query: { number: number.toString(), tab: 'txs' } }) }
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
{ txCount }
</Skeleton>
</LinkInternal>
......
import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react';
import { Box, Flex, Text, VStack } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
......@@ -9,10 +8,12 @@ import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2';
import { route } from 'nextjs-routes';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useInitialList from 'lib/hooks/useInitialList';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { ZKEVM_L2_TXN_BATCHES_ITEM } from 'stubs/zkEvmL2';
import { Heading } from 'toolkit/chakra/heading';
import LinkInternal from 'ui/shared/links/LinkInternal';
import ZkEvmL2TxnBatchStatus from 'ui/shared/statusTag/ZkEvmL2TxnBatchStatus';
......@@ -29,6 +30,12 @@ const LatestZkEvmL2Batches = () => {
},
});
const initialList = useInitialList({
data: data?.items ?? [],
idFn: (batch) => batch.number,
enabled: !isPlaceholderData,
});
const handleNewBatchMessage: SocketMessage.NewZkEvmL2Batch['handler'] = React.useCallback((payload) => {
queryClient.setQueryData(getResourceKey('homepage_zkevm_l2_batches'), (prevData: { items: Array<ZkEvmL2TxnBatchesItem> } | undefined) => {
const newItems = prevData?.items ? [ ...prevData.items ] : [];
......@@ -62,8 +69,7 @@ const LatestZkEvmL2Batches = () => {
content = (
<>
<VStack spacing={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
<AnimatePresence initial={ false } >
<VStack gap={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
{ dataToShow.map(((batch, index) => {
const status = <ZkEvmL2TxnBatchStatus status={ batch.status } isLoading={ isPlaceholderData }/>;
return (
......@@ -74,10 +80,10 @@ const LatestZkEvmL2Batches = () => {
timestamp={ batch.timestamp }
status={ status }
isLoading={ isPlaceholderData }
isNew={ initialList.isNew(batch) }
/>
);
})) }
</AnimatePresence>
</VStack>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ route({ pathname: '/batches' }) }>View all batches</LinkInternal>
......@@ -88,7 +94,7 @@ const LatestZkEvmL2Batches = () => {
return (
<Box width={{ base: '100%', lg: '280px' }} flexShrink={ 0 }>
<Heading as="h4" size="sm" mb={ 3 }>Latest batches</Heading>
<Heading level="3" mb={ 3 }>Latest batches</Heading>
{ content }
</Box>
);
......
......@@ -9,7 +9,7 @@ import React from 'react';
import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -43,16 +43,14 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
<BlockEntityL1
number={ item.l1BlockNumber }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
fontWeight={ 700 }
/>
) : (
<BlockEntityL1
number="TBD"
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
fontWeight={ 700 }
noLink
/>
......@@ -62,16 +60,14 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
<TxEntityL1
isLoading={ isLoading }
hash={ item.l1TxHash }
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
truncation={ isMobile ? 'constant_long' : 'dynamic' }
/>
) : (
<TxEntityL1
isLoading={ isLoading }
hash="To be determined"
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
truncation="none"
noLink
/>
......@@ -81,8 +77,7 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
<TxEntity
isLoading={ isLoading }
hash={ item.l2TxHash }
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
truncation={ isMobile ? 'constant_long' : 'dynamic' }
/>
);
......@@ -97,16 +92,16 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
color="text.secondary"
/>
) : <GridItem/> }
</Flex>
<Grid gridTemplateColumns="56px auto">
<Skeleton isLoaded={ !isLoading } my="5px" w="fit-content">
<Skeleton loading={ isLoading } my="5px" w="fit-content">
L1 txn
</Skeleton>
{ l1TxLink }
<Skeleton isLoaded={ !isLoading } my="3px" w="fit-content">
<Skeleton loading={ isLoading } my="3px" w="fit-content">
L2 txn
</Skeleton>
{ l2TxLink }
......@@ -118,7 +113,7 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
return (
<Grid width="100%" columnGap={ 4 } rowGap={ 2 } templateColumns="max-content max-content auto" w="100%">
{ l1BlockLink }
<Skeleton isLoaded={ !isLoading } w="fit-content" h="fit-content" my="5px">
<Skeleton loading={ isLoading } w="fit-content" h="fit-content" my="5px">
L1 txn
</Skeleton>
{ l1TxLink }
......@@ -126,13 +121,13 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
color="text.secondary"
w="fit-content"
h="fit-content"
my="2px"
/>
) : <GridItem/> }
<Skeleton isLoaded={ !isLoading } w="fit-content" h="fit-content" my="2px">
<Skeleton loading={ isLoading } w="fit-content" h="fit-content" my="2px">
L2 txn
</Skeleton>
{ l2TxLink }
......@@ -148,8 +143,7 @@ const LatestDepositsItem = ({ item, isLoading }: ItemProps) => {
py={ 4 }
px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor: 'border.divider' }}
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
>
{ content }
</Box>
......@@ -171,7 +165,7 @@ const LatestDeposits = ({ isLoading, items, socketAlert, socketItemsNum }: Props
))) }
</Box>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ depositsUrl }>View all deposits</LinkInternal>
<LinkInternal textStyle="sm" href={ depositsUrl }>View all deposits</LinkInternal>
</Flex>
</>
);
......
......@@ -15,18 +15,18 @@ const rollupFeature = config.features.rollup;
const Home = () => {
// const leftWidget = (() => {
// if (rollupFeature.isEnabled && !rollupFeature.homepage.showLatestBlocks) {
// switch (rollupFeature.type) {
// case 'zkEvm':
// return <LatestZkEvmL2Batches/>;
// case 'arbitrum':
// return <LatestArbitrumL2Batches/>;
// }
// }
const leftWidget = (() => {
if (rollupFeature.isEnabled && !rollupFeature.homepage.showLatestBlocks) {
switch (rollupFeature.type) {
case 'zkEvm':
return <LatestZkEvmL2Batches/>;
case 'arbitrum':
return <LatestArbitrumL2Batches/>;
}
}
// return <LatestBlocks/>;
// })();
return <LatestBlocks/>;
})();
return (
<Box as="main">
......@@ -37,7 +37,7 @@ const Home = () => {
</Flex>
<AdBanner mt={ 6 } mx="auto" display={{ base: 'flex', lg: 'none' }} justifyContent="center"/>
<Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 6 }>
{ /* { leftWidget } */ }
{ leftWidget }
<Box flexGrow={ 1 }>
<Transactions/>
</Box>
......
......@@ -174,7 +174,7 @@ const AddressEntry = (props: EntityProps) => {
zIndex={ 0 }
>
<Icon { ...partsProps.icon }/>
{ props.noLink ? content : (<Link { ...partsProps.link }> { content } </Link>) }
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
<Copy { ...partsProps.copy } altHash={ altHash }/>
</Container>
);
......
import type { As } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
......@@ -55,17 +54,17 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
const BlockEntity = (props: EntityProps) => {
const partsProps = distributeEntityProps(props);
const content = <Content { ...partsProps.content }/>;
return (
<Container { ...partsProps.container }>
<Icon { ...partsProps.icon }/>
<Link { ...partsProps.link }>
<Content { ...partsProps.content }/>
</Link>
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
</Container>
);
};
export default React.memo(chakra<As, EntityProps>(BlockEntity));
export default React.memo(chakra(BlockEntity));
export {
Container,
......
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