Files
explore/ui/home/LatestBlocks.tsx
T
Zach Kelling d15691316a feat: rename @blockscout to @luxfi, monochrome theme, horizontal blocks
- Rename all @blockscout/* npm packages to @luxfi/* aliases (150+ files)
- Override blue accent colors to gray for monochrome theme
- Fix dropdown/menu/popover fonts to use Geist Sans
- Change Latest Blocks to horizontal scrolling layout
- Show dashes instead of zeros for validator stats on error
- Fix GHA deploy step to use correct deployment name
2026-03-02 12:28:15 -08:00

138 lines
4.6 KiB
TypeScript

import { chakra, Box, Flex, HStack, Text } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { upperFirst } from 'es-toolkit';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Block } from 'types/api/block';
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 getNetworkUtilizationParams from 'lib/networks/getNetworkUtilizationParams';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { BLOCK } from 'stubs/block';
import { HOMEPAGE_STATS } from 'stubs/stats';
import { Heading } from 'toolkit/chakra/heading';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import { nbsp } from 'toolkit/utils/htmlEntities';
import LatestBlocksItem from './LatestBlocksItem';
const LatestBlocks = () => {
const isMobile = useIsMobile();
// const blocksMaxCount = isMobile ? 2 : 3;
let blocksMaxCount: number;
if (config.features.rollup.isEnabled || config.UI.views.block.hiddenFields?.total_reward) {
blocksMaxCount = isMobile ? 4 : 5;
} else {
blocksMaxCount = isMobile ? 2 : 3;
}
const { data, isPlaceholderData, isError } = useApiQuery('general:homepage_blocks', {
queryOptions: {
placeholderData: Array(blocksMaxCount).fill(BLOCK),
},
});
const initialList = useInitialList({
data: data ?? [],
idFn: (block) => block.height,
enabled: !isPlaceholderData,
});
const queryClient = useQueryClient();
const statsQueryResult = useApiQuery('general:stats', {
queryOptions: {
refetchOnMount: false,
placeholderData: HOMEPAGE_STATS,
},
});
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
queryClient.setQueryData(getResourceKey('general:homepage_blocks'), (prevData: Array<Block> | undefined) => {
const newData = prevData ? [ ...prevData ] : [];
if (newData.some((block => block.height === payload.block.height))) {
return newData;
}
return [ payload.block, ...newData ].sort((b1, b2) => b2.height - b1.height).slice(0, blocksMaxCount);
});
}, [ queryClient, blocksMaxCount ]);
const channel = useSocketChannel({
topic: 'blocks:new_block',
isDisabled: isPlaceholderData || isError,
});
useSocketMessage({
channel,
event: 'new_block',
handler: handleNewBlockMessage,
});
let content;
if (isError) {
content = <Text>No data. Please reload the page.</Text>;
}
if (data) {
const dataToShow = data.slice(0, blocksMaxCount);
content = (
<>
<HStack gap={ 3 } mb={ 3 } overflowX="auto" overflowY="hidden" alignItems="stretch" pb={ 1 }>
{ dataToShow.map(((block, index) => (
<LatestBlocksItem
key={ block.height + (isPlaceholderData ? String(index) : '') }
block={ block }
isLoading={ isPlaceholderData }
animation={ initialList.getAnimationProp(block) }
/>
))) }
</HStack>
<Flex justifyContent="center">
<Link textStyle="sm" href={ route({ pathname: '/blocks' }) }>View all blocks</Link>
</Flex>
</>
);
}
const networkUtilization = getNetworkUtilizationParams(statsQueryResult.data?.network_utilization_percentage ?? 0);
return (
<Box width="100%">
<Heading level="3">Latest blocks</Heading>
{ statsQueryResult.data?.network_utilization_percentage !== undefined && (
<Skeleton loading={ statsQueryResult.isPlaceholderData } mt={ 2 } display="inline-block" textStyle="sm">
<Text as="span">
Network utilization:{ nbsp }
</Text>
<Tooltip content={ `${ upperFirst(networkUtilization.load) } load` }>
<Text as="span" color={ networkUtilization.color } fontWeight={ 700 }>
{ statsQueryResult.data?.network_utilization_percentage.toFixed(2) }%
</Text>
</Tooltip>
</Skeleton>
) }
{ statsQueryResult.data?.celo && (
<Box whiteSpace="pre-wrap" textStyle="sm" mt={ 2 }>
<span>Current epoch: </span>
<chakra.span fontWeight={ 700 }>#{ statsQueryResult.data.celo.epoch_number }</chakra.span>
</Box>
) }
<Box mt={ 3 }>
{ content }
</Box>
</Box>
);
};
export default LatestBlocks;