mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
index blocks
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import type { Block } from 'types/api/block';
|
||||
|
||||
import { WEI, ZERO } from 'lib/consts';
|
||||
|
||||
export default function getBlockTotalReward(block: Block) {
|
||||
const totalReward = block.rewards
|
||||
?.map(({ reward }) => BigNumber(reward))
|
||||
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
|
||||
|
||||
return totalReward.div(WEI).toFixed();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import handler from 'lib/api/handler';
|
||||
|
||||
const getUrl = () => '/v2/main-page/blocks';
|
||||
|
||||
const requestHandler = handler(getUrl, [ 'GET' ]);
|
||||
|
||||
export default requestHandler;
|
||||
@@ -14,4 +14,5 @@ export enum QueryKeys {
|
||||
blocks = 'blocks',
|
||||
chartsTxs = 'charts-txs',
|
||||
chartsMarket = 'charts-market',
|
||||
indexBlocks='indexBlocks',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import type { TypographyProps } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
@@ -6,12 +7,13 @@ import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
|
||||
interface Props {
|
||||
ts: string;
|
||||
isEnabled?: boolean;
|
||||
fontSize?: TypographyProps['fontSize'];
|
||||
}
|
||||
|
||||
const BlockTimestamp = ({ ts, isEnabled }: Props) => {
|
||||
const BlockTimestamp = ({ ts, isEnabled, fontSize }: Props) => {
|
||||
const timeAgo = useTimeAgoIncrement(ts, isEnabled);
|
||||
|
||||
return <Text variant="secondary" fontWeight={ 400 }>{ timeAgo }</Text>;
|
||||
return <Text variant="secondary" fontWeight={ 400 } fontSize={ fontSize }>{ timeAgo }</Text>;
|
||||
};
|
||||
|
||||
export default React.memo(BlockTimestamp);
|
||||
|
||||
@@ -7,7 +7,8 @@ import type { Block } from 'types/api/block';
|
||||
|
||||
import appConfig from 'configs/app/config';
|
||||
import flameIcon from 'icons/flame.svg';
|
||||
import { WEI, ZERO } from 'lib/consts';
|
||||
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
|
||||
import { WEI } from 'lib/consts';
|
||||
import link from 'lib/link/link';
|
||||
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
|
||||
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
|
||||
@@ -23,9 +24,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
|
||||
const totalReward = data.rewards
|
||||
?.map(({ reward }) => BigNumber(reward))
|
||||
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
|
||||
const totalReward = getBlockTotalReward(data);
|
||||
const burntFees = BigNumber(data.burnt_fees || 0);
|
||||
const txFees = BigNumber(data.tx_fees || 0);
|
||||
|
||||
@@ -65,7 +64,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
|
||||
</Box>
|
||||
<Flex columnGap={ 2 }>
|
||||
<Text fontWeight={ 500 }>Reward { appConfig.network.currency.symbol }</Text>
|
||||
<Text variant="secondary">{ totalReward.div(WEI).toFixed() }</Text>
|
||||
<Text variant="secondary">{ totalReward }</Text>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Text fontWeight={ 500 }>Burnt fees</Text>
|
||||
|
||||
@@ -6,7 +6,8 @@ import React from 'react';
|
||||
import type { Block } from 'types/api/block';
|
||||
|
||||
import flameIcon from 'icons/flame.svg';
|
||||
import { WEI, ZERO } from 'lib/consts';
|
||||
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
|
||||
import { WEI } from 'lib/consts';
|
||||
import link from 'lib/link/link';
|
||||
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
@@ -20,9 +21,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
|
||||
const totalReward = data.rewards
|
||||
?.map(({ reward }) => BigNumber(reward))
|
||||
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
|
||||
const totalReward = getBlockTotalReward(data);
|
||||
const burntFees = BigNumber(data.burnt_fees || 0);
|
||||
const txFees = BigNumber(data.tx_fees || 0);
|
||||
|
||||
@@ -61,7 +60,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
|
||||
<GasUsedToTargetRatio ml={ 2 } value={ data.gas_target_percentage || undefined }/>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm">{ totalReward.dividedBy(WEI).toFixed() }</Td>
|
||||
<Td fontSize="sm">{ totalReward }</Td>
|
||||
<Td fontSize="sm">
|
||||
<Flex alignItems="center" columnGap={ 1 }>
|
||||
<Icon as={ flameIcon } boxSize={ 5 } color={ useColorModeValue('gray.500', 'inherit') }/>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Box, Heading, Link, Text, VStack, Skeleton, useColorModeValue } from '@chakra-ui/react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import React from 'react';
|
||||
|
||||
import type { SocketMessage } from 'lib/socket/types';
|
||||
import type { Block } from 'types/api/block';
|
||||
import { QueryKeys } from 'types/client/queries';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import { nbsp } from 'lib/html-entities';
|
||||
import useSocketChannel from 'lib/socket/useSocketChannel';
|
||||
import useSocketMessage from 'lib/socket/useSocketMessage';
|
||||
|
||||
import LatestBlocksItem from './LatestBlocksItem';
|
||||
|
||||
const LatestBlocks = () => {
|
||||
const fetch = useFetch();
|
||||
const { data, isLoading, isError } = useQuery<unknown, unknown, Array<Block>>(
|
||||
[ QueryKeys.indexBlocks ],
|
||||
async() => await fetch(`/api/index/blocks`),
|
||||
);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
|
||||
queryClient.setQueryData([ QueryKeys.indexBlocks ], (prevData: Array<Block> | undefined) => {
|
||||
|
||||
const newData = prevData ? [ ...prevData ] : [];
|
||||
|
||||
return [ payload.block, ...newData ].slice(0, 4);
|
||||
});
|
||||
}, [ queryClient ]);
|
||||
|
||||
const channel = useSocketChannel({
|
||||
topic: 'blocks:new_block',
|
||||
isDisabled: isLoading || isError,
|
||||
});
|
||||
useSocketMessage({
|
||||
channel,
|
||||
event: 'new_block',
|
||||
handler: handleNewBlockMessage,
|
||||
});
|
||||
|
||||
let content;
|
||||
|
||||
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
|
||||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<>
|
||||
<Skeleton w="100%" h={ 6 } mb={ 9 }/>
|
||||
{ Array.from(Array(4)).map((item, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={ index }
|
||||
width="100%"
|
||||
borderRadius="12px"
|
||||
border="1px solid"
|
||||
borderColor={ borderColor }
|
||||
p={ 6 }
|
||||
mb={ 6 }
|
||||
>
|
||||
<Skeleton w="100%" h="30px" mb={ 3 }/>
|
||||
<Skeleton w="100%" h="21px" mb={ 2 }/>
|
||||
<Skeleton w="100%" h="21px" mb={ 2 }/>
|
||||
<Skeleton w="100%" h="21px"/>
|
||||
</Box>
|
||||
);
|
||||
}) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
// ???
|
||||
content = null;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
content = (
|
||||
<>
|
||||
<Box mb={ 9 }>
|
||||
<Text as="span" fontSize="sm">
|
||||
Network utilization:{ nbsp }
|
||||
</Text>
|
||||
{ /* Not implemented in API yet */ }
|
||||
<Text as="span" fontSize="sm" color="blue.400" fontWeight={ 700 }>
|
||||
43.8%
|
||||
</Text>
|
||||
</Box>
|
||||
<VStack spacing={ 6 } mb={ 6 }>
|
||||
<AnimatePresence initial={ false }>
|
||||
{ data.map((block => <LatestBlocksItem key={ block.height } block={ block }/>)) }
|
||||
</AnimatePresence>
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box width="280px">
|
||||
<Heading as="h4" fontSize="18px" mb={ 8 }>Latest Blocks</Heading>
|
||||
{ content }
|
||||
<Link fontSize="sm">View all blocks</Link>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatestBlocks;
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Grid,
|
||||
GridItem,
|
||||
HStack,
|
||||
Icon,
|
||||
Link,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import React from 'react';
|
||||
|
||||
import type { Block } from 'types/api/block';
|
||||
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
|
||||
import link from 'lib/link/link';
|
||||
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
|
||||
type Props = {
|
||||
block: Block;
|
||||
}
|
||||
|
||||
const LatestBlocksItem = ({ block }: Props) => {
|
||||
const totalReward = getBlockTotalReward(block);
|
||||
return (
|
||||
<Box
|
||||
as={ motion.div }
|
||||
initial={{ opacity: 0, scale: 0.97 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transitionDuration="normal"
|
||||
transitionTimingFunction="linear"
|
||||
width="100%"
|
||||
borderRadius="12px"
|
||||
border="1px solid"
|
||||
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') }
|
||||
p={ 6 }
|
||||
>
|
||||
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
|
||||
<HStack spacing={ 2 }>
|
||||
<Icon as={ blockIcon } boxSize="30px" color={ useColorModeValue('blue.600', 'blue.300') }/>
|
||||
<Link
|
||||
href={ link('block', { id: String(block.height) }) }
|
||||
fontSize="xl"
|
||||
fontWeight="500"
|
||||
>
|
||||
{ block.height }
|
||||
</Link>
|
||||
</HStack>
|
||||
{ /* <Text fontSize="sm" variant="secondary">{ block.timestamp }</Text> */ }
|
||||
<BlockTimestamp ts={ block.timestamp } isEnabled fontSize="sm"/>
|
||||
</Flex>
|
||||
<Grid gridGap={ 2 } templateColumns="auto minmax(0, 1fr)" fontSize="sm">
|
||||
<GridItem>Txn</GridItem>
|
||||
<GridItem><Text variant="secondary">{ block.tx_count }</Text></GridItem>
|
||||
{ totalReward !== '0' && (
|
||||
<>
|
||||
<GridItem>Reward</GridItem>
|
||||
<GridItem><Text variant="secondary">{ totalReward }</Text></GridItem>
|
||||
</>
|
||||
) }
|
||||
<GridItem>Miner</GridItem>
|
||||
<GridItem><AddressLink alias={ block.miner.name } hash={ block.miner.hash } truncation="constant" maxW="100%"/></GridItem>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatestBlocksItem;
|
||||
+1
-1
@@ -18,7 +18,7 @@ const Stats = () => {
|
||||
|
||||
const { data, isLoading, isError } = useQuery<unknown, unknown, Stats>(
|
||||
[ QueryKeys.stats ],
|
||||
async() => await fetch(`/api/stats`),
|
||||
async() => await fetch(`/api/index/stats`),
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
import { Box, Heading } from '@chakra-ui/react';
|
||||
import { Box, Heading, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import LatestBlocks from 'ui/home/LatestBlocks';
|
||||
import Stats from 'ui/home/Stats';
|
||||
import Page from 'ui/shared/Page/Page';
|
||||
import SearchBar from 'ui/snippets/searchBar/SearchBar';
|
||||
@@ -10,7 +11,6 @@ const Home = () => {
|
||||
<Page hasSearch={ false }>
|
||||
<Box
|
||||
w="100%"
|
||||
// h="236px"
|
||||
backgroundImage="radial-gradient(farthest-corner at 0 0, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%)"
|
||||
borderRadius="24px"
|
||||
padding={{ base: '24px 40px', lg: '48px' }}
|
||||
@@ -27,6 +27,10 @@ const Home = () => {
|
||||
<SearchBar backgroundColor="white" isHomepage/>
|
||||
</Box>
|
||||
<Stats/>
|
||||
<Flex mt={ 12 }>
|
||||
<Box mr={ 12 }><LatestBlocks/></Box>
|
||||
<Box><Heading as="h4" fontSize="18px" mb={ 8 }>Latest transactions</Heading></Box>
|
||||
</Flex>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user