Commit 73bb6ce1 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into fix-ci-cd

parents adad824f ce672c4d
/* eslint-disable max-len */
export const block = {
height: 15006918,
size: 1754,
timestamp: 1662623567695,
transactionsNum: 99,
miner: {
name: 'Alex Emelyanov',
address: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41',
},
minedIn: 20,
reward: {
'static': 2,
tx_fee: 0.1550895290904872,
},
burnt_fees: 0.15116230256264004,
gas_limit: 30000000,
gas_used: 12866397,
gas_target: 14266397,
base_fee_per_gas: 11.748611718,
data: {
hex: '0x7370657468303367c18300',
utf: 'speth03g��',
},
difficulty: '14,735,262,741,740,473',
totalDifficulty: '52,307,701,288,535,570,059,059',
hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34',
parent_hash: '0x4585ac59fdfd84443d93c9216e5172f57ca204639d3e68d1908088b0ebd3e709',
parent_height: 29471346,
sha3_uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
state_root: '0x434fb3f234c8cffc1d915ad26ea360b0261d183ad37af89e31b839df4dd0b00f',
nonce: '0xde80e5b4dd984384',
};
/* eslint-disable max-len */
import { block } from './block';
export const blocks = [
{
...block,
timestamp: Date.now() - 25_000,
},
{
...block,
height: 15006917,
timestamp: Date.now() - 1_000 * 60 * 2,
miner: {
address: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41',
name: undefined,
},
transactionsNum: 185,
size: 452,
gas_limit: 30000000,
gas_used: 15671326,
gas_target: 14671326,
burnt_fees: 0.3988042215537949,
},
{
...block,
height: 15006916,
timestamp: Date.now() - 1_000 * 60 * 60 * 17,
transactionsNum: 377,
size: 5222,
gas_limit: 30000000,
gas_used: 23856751,
gas_target: 28856751,
burnt_fees: 0.0000019660909367,
},
];
......@@ -5,8 +5,6 @@ import localizedFormat from 'dayjs/plugin/localizedFormat';
import relativeTime from 'dayjs/plugin/relativeTime';
import updateLocale from 'dayjs/plugin/updateLocale';
dayjs.locale('en');
const relativeTimeConfig = {
thresholds: [
{ l: 's', r: 1 },
......@@ -51,4 +49,27 @@ dayjs.updateLocale('en', {
},
});
dayjs.locale('en-short', {
name: 'en-short',
relativeTime: {
s: '1s',
future: 'in %s',
past: '%s ago',
m: '1m',
mm: '%dm',
h: '1h',
hh: '%dh',
d: '1d',
dd: '%dd',
M: '1mo',
MM: '%dmo',
y: '1y',
yy: '%dy',
// have to trick typescript 🎩 🐇
...{ ss: '%ds' },
},
});
dayjs.locale('en');
export default dayjs;
......@@ -20,7 +20,7 @@ export default function useNavItems() {
return React.useMemo(() => {
const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute === 'blocks' },
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block') },
{ text: 'Transactions', url: link('txs_validated'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' },
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' },
......
......@@ -74,9 +74,20 @@ export const ROUTES = {
pattern: `${ BASE_PATH }/blocks`,
crossNetworkNavigation: true,
},
block: {
blocks_uncles: {
pattern: `${ BASE_PATH }/uncles`,
crossNetworkNavigation: true,
},
blocks_reorgs: {
pattern: `${ BASE_PATH }/reorgs`,
crossNetworkNavigation: true,
},
block_index: {
pattern: `${ BASE_PATH }/block/[id]`,
},
block_txs: {
pattern: `${ BASE_PATH }/block/[id]/transactions`,
},
// TOKENS
tokens: {
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import type { PageParams } from './types';
import Block from 'ui/pages/Block';
import type { Props as BlockProps } from 'ui/pages/Block';
import getSeo from './getSeo';
type Props = {
pageParams: PageParams;
tab: BlockProps['tab'];
}
const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
const { title, description } = getSeo(pageParams);
return (
<>
<Head>
<title>{ title }</title>
<meta name="description" content={ description }/>
</Head>
<Block tab={ tab }/>
</>
);
};
export default BlockNextPage;
import type { PageParams } from './types';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params?: PageParams) {
const networkTitle = getNetworkTitle(params || {});
return {
title: params ? `Block ${ params.id } - ${ networkTitle }` : '',
description: params ? `View the transactions, token transfers, and uncles for block number ${ params.id }` : '',
};
}
import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: true };
};
export type PageParams = {
network_type: string;
network_sub_type: string;
id: string;
}
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import type { PageParams } from './types';
import Blocks from 'ui/pages/Blocks';
import type { Props as BlocksProps } from 'ui/pages/Blocks';
import getSeo from './getSeo';
type Props = {
pageParams: PageParams;
tab: BlocksProps['tab'];
}
const BlocksNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
const { title } = getSeo(pageParams);
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Blocks tab={ tab }/>
</>
);
};
export default BlocksNextPage;
import type { PageParams } from './types';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params?: PageParams) {
const networkTitle = getNetworkTitle(params || {});
return {
title: params ? `${ networkTitle } - BlockScout` : '',
};
}
export type PageParams = {
network_type: string;
network_sub_type: string;
}
......@@ -15,10 +15,13 @@ type Props = {
}
const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
const { title } = getSeo(pageParams);
const { title, description } = getSeo(pageParams);
return (
<>
<Head><title>{ title }</title></Head>
<Head>
<title>{ title }</title>
<meta name="description" content={ description }/>
</Head>
<Transaction tab={ tab }/>
</>
);
......
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlockNextPage from 'lib/next/block/BlockNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlockNextPage tab="block_index" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlockNextPage from 'lib/next/block/BlockNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlockNextPage tab="block_txs" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlocksNextPage from 'lib/next/blocks/BlocksNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlocksNextPage tab="blocks" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlocksNextPage from 'lib/next/blocks/BlocksNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlocksNextPage tab="blocks_reorgs" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlocksNextPage from 'lib/next/blocks/BlocksNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlocksNextPage tab="blocks_uncles" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import { block } from 'data/block';
import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg';
import dayjs from 'lib/date/dayjs';
import useNetwork from 'lib/hooks/useNetwork';
import { space } from 'lib/html-entities';
import useLink from 'lib/link/useLink';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import PrevNext from 'ui/shared/PrevNext';
import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization';
const BlockDetails = () => {
const [ isExpanded, setIsExpanded ] = React.useState(false);
const link = useLink();
const router = useRouter();
const network = useNetwork();
const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag);
scroller.scrollTo('BlockDetails__cutLink', {
duration: 500,
smooth: true,
});
}, []);
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden">
<DetailsInfoItem
title="Block height"
hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain."
>
{ block.height }
<PrevNext ml={ 6 }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Size"
hint="Size of the block in bytes."
>
{ block.size.toLocaleString('en') }
</DetailsInfoItem>
<DetailsInfoItem
title="Timestamp"
hint="Date & time at which block was produced."
>
<Icon as={ clockIcon } boxSize={ 5 } color="gray.500"/>
<Text ml={ 1 }>{ dayjs(block.timestamp).fromNow() }</Text>
<TextSeparator/>
<Text whiteSpace="normal">{ dayjs(block.timestamp).format('LLLL') }</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Transactions"
hint="The number of transactions in the block."
>
<Link href={ link('block_txs', { id: router.query.id }) }>
{ block.transactionsNum } transactions
</Link>
</DetailsInfoItem>
<DetailsInfoItem
title="Mined by"
hint="A block producer who successfully included the block onto the blockchain."
columnGap={ 1 }
>
<AddressLink hash={ block.miner.address }/>
{ block.miner.name && <Text>(Miner: { block.miner.name })</Text> }
<Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Block reward"
hint={
`For each block, the miner is rewarded with a finite amount of ${ network?.currency || 'native token' }
on top of the fees paid for all transactions in the block.`
}
columnGap={ 1 }
>
<Text>{ block.reward.static + block.reward.tx_fee - block.burnt_fees }</Text>
<Text variant="secondary" whiteSpace="break-spaces">(
<Tooltip label="Static block reward">
<span>{ block.reward.static }</span>
</Tooltip>
{ space }+{ space }
<Tooltip label="Txn fees">
<span>{ block.reward.tx_fee }</span>
</Tooltip>
{ space }-{ space }
<Tooltip label="Burnt fees">
<span>{ block.burnt_fees }</span>
</Tooltip>
)</Text>
</DetailsInfoItem>
{ sectionGap }
<DetailsInfoItem
title="Gas used"
hint="The total gas amount used in the block and its percentage of gas filled in the block."
>
<Text>{ block.gas_used.toLocaleString('en') }</Text>
<Utilization ml={ 4 } mr={ 5 } colorScheme="gray" value={ block.gas_used / block.gas_limit }/>
<GasUsedToTargetRatio used={ block.gas_used } target={ block.gas_target }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Gas limit"
hint="Total gas limit provided by all transactions in the block."
>
<Text>{ block.gas_limit.toLocaleString('en') }</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Base fee per gas"
hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion."
>
<Text>{ (block.base_fee_per_gas / 10 ** 9).toLocaleString('en', { minimumFractionDigits: 18 }) } { network?.currency } </Text>
<Text variant="secondary" whiteSpace="pre">{ space }({ block.base_fee_per_gas.toLocaleString('en', { minimumFractionDigits: 9 }) } Gwei)</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Burnt fees"
hint={ `Amount of ${ network?.currency || 'native token' } burned from transactions included in the block. Equals Block Base Fee per Gas * Gas Used.` }
>
<Icon as={ flameIcon } boxSize={ 5 } color="gray.500"/>
<Text ml={ 1 }>{ block.burnt_fees.toLocaleString('en', { minimumFractionDigits: 18 }) } { network?.currency }</Text>
<Tooltip label="Burnt fees / Txn fees * 100%">
<Box>
<Utilization ml={ 4 } value={ block.burnt_fees / block.reward.tx_fee }/>
</Box>
</Tooltip>
</DetailsInfoItem>
<DetailsInfoItem
title="Extra data"
hint="Any data that can be included by the miner in the block."
>
<Text whiteSpace="pre">{ block.data.utf } </Text>
<Text variant="secondary">(Hex: { block.data.hex })</Text>
</DetailsInfoItem>
{ /* CUT */ }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Element name="BlockDetails__cutLink">
<Link
mt={ 6 }
display="inline-block"
fontSize="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ handleCutClick }
>
{ isExpanded ? 'Hide details' : 'View details' }
</Link>
</Element>
</GridItem>
{ /* ADDITIONAL INFO */ }
{ isExpanded && (
<>
{ sectionGap }
<DetailsInfoItem
title="Difficulty"
hint="Block difficulty for miner, used to calibrate block generation time."
>
{ block.difficulty }
</DetailsInfoItem>
<DetailsInfoItem
title="Total difficulty"
hint="Total difficulty of the chain until this block."
>
{ block.totalDifficulty }
</DetailsInfoItem>
{ sectionGap }
<DetailsInfoItem
title="Hash"
hint="The SHA256 hash of the block."
flexWrap="nowrap"
>
<Box overflow="hidden">
<HashStringShortenDynamic hash={ block.hash }/>
</Box>
<CopyToClipboard text={ block.hash }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Parent hash"
hint="The hash of the block from which this block was generated."
flexWrap="nowrap"
>
<AddressLink hash={ block.parent_hash } type="block" id={ String(block.parent_height) }/>
<CopyToClipboard text={ block.hash }/>
</DetailsInfoItem>
<DetailsInfoItem
title="State root"
hint="The root of the state trie."
>
<Text wordBreak="break-all" whiteSpace="break-spaces">{ block.state_root }</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Nonce"
hint="Block nonce is a value used during mining to demonstrate proof of work for a block."
>
{ block.nonce }
</DetailsInfoItem>
</>
) }
</Grid>
);
};
export default BlockDetails;
import React from 'react';
import TxsContent from 'ui/txs/TxsContent';
const BlockTxs = () => {
return <TxsContent showDescription={ false } showSortButton={ false }/>;
};
export default BlockTxs;
import { Box, Text, Show } from '@chakra-ui/react';
import React from 'react';
import BlocksList from 'ui/blocks/BlocksList';
import BlocksTable from 'ui/blocks/BlocksTable';
import Pagination from 'ui/shared/Pagination';
const BlocksContent = () => {
return (
<>
<Text>Total of 15,044,883 blocks</Text>
<Show below="lg"><BlocksList/></Show>
<Show above="lg"><BlocksTable/></Show>
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
<Pagination currentPage={ 1 }/>
</Box>
</>
);
};
export default BlocksContent;
import { Box } from '@chakra-ui/react';
import React from 'react';
import { blocks } from 'data/blocks';
import BlocksListItem from 'ui/blocks/BlocksListItem';
const BlocksList = () => {
return (
<Box mt={ 8 }>
{ blocks.map((item, index) => <BlocksListItem key={ item.height } data={ item } isPending={ index === 0 }/>) }
</Box>
);
};
export default BlocksList;
import { Flex, Link, Spinner, Text, Box, Icon, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { blocks } from 'data/blocks';
import flameIcon from 'icons/flame.svg';
import dayjs from 'lib/date/dayjs';
import useNetwork from 'lib/hooks/useNetwork';
import useLink from 'lib/link/useLink';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import Utilization from 'ui/shared/Utilization';
interface Props {
data: ArrayElement<typeof blocks>;
isPending?: boolean;
}
const BlocksListItem = ({ data, isPending }: Props) => {
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const link = useLink();
const network = useNetwork();
return (
<AccountListItemMobile rowGap={ 3 }>
<Flex justifyContent="space-between" w="100%">
<Flex columnGap={ 2 } alignItems="center">
{ isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> }
<Link
fontWeight={ 600 }
href={ link('block_index', { id: String(data.height) }) }
>
{ data.height }
</Link>
</Flex>
<Text variant="secondary"fontWeight={ 400 }>{ dayjs(data.timestamp).locale('en-short').fromNow() }</Text>
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Size</Text>
<Text variant="secondary">{ data.size.toLocaleString('en') } bytes</Text>
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Miner</Text>
<AddressLink alias={ data.miner?.name } hash={ data.miner.address } truncation="constant"/>
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Txn</Text>
<Text variant="secondary">{ data.transactionsNum }</Text>
</Flex>
<Box>
<Text fontWeight={ 500 }>Gas used</Text>
<Flex columnGap={ 4 }>
<Text variant="secondary">{ data.gas_used.toLocaleString('en') }</Text>
<Utilization colorScheme="gray" value={ data.gas_used / data.gas_limit }/>
<GasUsedToTargetRatio used={ data.gas_used } target={ data.gas_target }/>
</Flex>
</Box>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Reward { network?.currency }</Text>
<Text variant="secondary">{ (data.reward.static + data.reward.tx_fee - data.burnt_fees).toLocaleString('en', { maximumFractionDigits: 5 }) }</Text>
</Flex>
<Flex>
<Text fontWeight={ 500 }>Burnt fees</Text>
<Icon as={ flameIcon } boxSize={ 5 } color="gray.500" ml={ 2 }/>
<Text variant="secondary" ml={ 1 }>{ data.burnt_fees.toLocaleString('en', { maximumFractionDigits: 6 }) }</Text>
<Utilization ml={ 4 } value={ data.burnt_fees / data.reward.tx_fee }/>
</Flex>
</AccountListItemMobile>
);
};
export default BlocksListItem;
import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import React from 'react';
import { blocks } from 'data/blocks';
import useNetwork from 'lib/hooks/useNetwork';
import BlocksTableItem from 'ui/blocks/BlocksTableItem';
const BlocksTable = () => {
const network = useNetwork();
return (
<TableContainer width="100%" mt={ 8 }>
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }>
<Thead>
<Tr>
<Th width="124px">Block</Th>
<Th width="112px">Size</Th>
<Th width="144px">Miner</Th>
<Th width="64px" isNumeric>Txn</Th>
<Th width="40%">Gas used</Th>
<Th width="30%">Reward { network?.currency }</Th>
<Th width="30%">Burnt fees { network?.currency }</Th>
</Tr>
</Thead>
<Tbody>
{ blocks.map((item, index) => <BlocksTableItem key={ item.height } data={ item } isPending={ index === 0 }/>) }
</Tbody>
</Table>
</TableContainer>
);
};
export default BlocksTable;
import { Tr, Td, Text, Link, Flex, Box, Icon, Tooltip, Spinner, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement';
import type { blocks } from 'data/blocks';
import flameIcon from 'icons/flame.svg';
import dayjs from 'lib/date/dayjs';
import useLink from 'lib/link/useLink';
import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
import Utilization from 'ui/shared/Utilization';
interface Props {
data: ArrayElement<typeof blocks>;
isPending?: boolean;
}
const BlocksTableItem = ({ data, isPending }: Props) => {
const link = useLink();
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Tr>
<Td fontSize="sm">
<Flex columnGap={ 2 } alignItems="center">
{ isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> }
<Link
fontWeight={ 600 }
href={ link('block_index', { id: String(data.height) }) }
>
{ data.height }
</Link>
</Flex>
<Text variant="secondary" mt={ 2 } fontWeight={ 400 }>{ dayjs(data.timestamp).locale('en-short').fromNow() }</Text>
</Td>
<Td fontSize="sm">{ data.size.toLocaleString('en') } bytes</Td>
<Td fontSize="sm">
<AddressLink alias={ data.miner?.name } hash={ data.miner.address } truncation="constant"/>
</Td>
<Td isNumeric fontSize="sm">{ data.transactionsNum }</Td>
<Td fontSize="sm">
<Box>{ data.gas_used.toLocaleString('en') }</Box>
<Flex mt={ 2 }>
<Utilization colorScheme="gray" value={ data.gas_used / data.gas_limit }/>
<GasUsedToTargetRatio ml={ 2 } used={ data.gas_used } target={ data.gas_target }/>
</Flex>
</Td>
<Td fontSize="sm">{ (data.reward.static + data.reward.tx_fee - data.burnt_fees).toLocaleString('en', { maximumFractionDigits: 5 }) }</Td>
<Td fontSize="sm">
<Flex alignItems="center" columnGap={ 1 }>
<Icon as={ flameIcon } boxSize={ 5 } color={ useColorModeValue('gray.500', 'inherit') }/>
{ data.burnt_fees.toLocaleString('en', { maximumFractionDigits: 6 }) }
</Flex>
<Tooltip label="Burnt fees / Txn fees * 100%">
<Box>
<Utilization mt={ 2 } value={ data.burnt_fees / data.reward.tx_fee }/>
</Box>
</Tooltip>
</Td>
</Tr>
);
};
export default React.memo(BlocksTableItem);
import { useRouter } from 'next/router';
import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import BlockDetails from 'ui/block/BlockDetails';
import BlockTxs from 'ui/block/BlockTxs';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [
{ routeName: 'block_index', title: 'Details', component: <BlockDetails/> },
{ routeName: 'block_txs', title: 'Transactions', component: <BlockTxs/> },
];
export interface Props {
tab: RoutedTab['routeName'];
}
const BlockPageContent = ({ tab }: Props) => {
const router = useRouter();
return (
<Page>
<PageTitle text={ `Block #${ router.query.id || '' }` }/>
<RoutedTabs
tabs={ TABS }
defaultActiveTab={ tab }
/>
</Page>
);
};
export default BlockPageContent;
import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import BlocksContent from 'ui/blocks/BlocksContent';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [
{ routeName: 'blocks', title: 'All', component: <BlocksContent/> },
{ routeName: 'blocks_reorgs', title: 'Forked', component: <BlocksContent/> },
{ routeName: 'blocks_uncles', title: 'Uncles', component: <BlocksContent/> },
];
export interface Props {
tab: RoutedTab['routeName'];
}
const BlocksPageContent = ({ tab }: Props) => {
return (
<Page>
<PageTitle text="Blocks"/>
<RoutedTabs
tabs={ TABS }
defaultActiveTab={ tab }
/>
</Page>
);
};
export default BlocksPageContent;
import { Stat, StatArrow, Text, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
used: number;
target: number;
className?: string;
}
const GasUsedToTargetRatio = ({ used, target, className }: Props) => {
const percentage = (used / target - 1) * 100;
return (
<Stat className={ className }>
<StatArrow type={ percentage >= 0 ? 'increase' : 'decrease' }/>
<Text as="span" color={ percentage >= 0 ? 'green.500' : 'red.500' } fontWeight={ 600 }>
{ Math.abs(percentage).toLocaleString('en', { maximumFractionDigits: 2 }) } %
</Text>
</Stat>
);
};
export default React.memo(chakra(GasUsedToTargetRatio));
......@@ -10,7 +10,7 @@ interface Props {
const WIDTH = 50;
const Utilization = ({ className, value, colorScheme = 'green' }: Props) => {
const valueString = (value * 100).toFixed(2) + '%';
const valueString = (value * 100).toLocaleString('en', { maximumFractionDigits: 2 }) + '%';
const colorGrayScheme = useColorModeValue('gray.500', 'gray.500');
const color = colorScheme === 'gray' ? colorGrayScheme : 'green.500';
......
......@@ -23,7 +23,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
} else if (type === 'token') {
url = link('token_index', { id: id || hash });
} else if (type === 'block') {
url = link('block', { id: id || hash });
url = link('block_index', { id: id || hash });
} else {
url = link('address_index', { id: id || hash });
}
......
......@@ -13,10 +13,11 @@ import TxsListItem from './TxsListItem';
import TxsTable from './TxsTable';
type Props = {
isPending?: boolean;
showDescription?: boolean;
showSortButton?: boolean;
}
const TxsContent = ({ isPending }: Props) => {
const TxsContent = ({ showSortButton = true, showDescription = true }: Props) => {
const [ sorting, setSorting ] = useState<Sort>();
const [ sortedTxs, setSortedTxs ] = useState(txs);
......@@ -67,7 +68,7 @@ const TxsContent = ({ isPending }: Props) => {
return (
<>
{ !isPending && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> }
{ showDescription && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> }
<HStack mb={ 6 }>
{ /* TODO */ }
<FilterButton
......@@ -76,12 +77,14 @@ const TxsContent = ({ isPending }: Props) => {
onClick={ () => {} }
appliedFiltersNum={ 0 }
/>
<SortButton
// eslint-disable-next-line react/jsx-no-bind
handleSort={ () => {} }
isSortActive={ Boolean(sorting) }
display={{ base: 'block', lg: 'none' }}
/>
{ showSortButton && (
<SortButton
// eslint-disable-next-line react/jsx-no-bind
handleSort={ () => {} }
isSortActive={ Boolean(sorting) }
display={{ base: 'block', lg: 'none' }}
/>
) }
<FilterInput
// eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} }
......
......@@ -79,7 +79,7 @@ const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
</Flex>
<Box mt={ 2 }>
<Text as="span">Block </Text>
<Link href={ link('block', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
<Link href={ link('block_index', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
</Box>
<Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address width="calc((100%-40px)/2)">
......
......@@ -3,7 +3,7 @@ import React from 'react';
import TxsContent from './TxsContent';
const TxsPending = () => {
return <TxsContent isPending/>;
return <TxsContent showDescription={ false }/>;
};
export default TxsPending;
......@@ -104,7 +104,7 @@ const TxsTableItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
</TruncatedTextTooltip>
</Td>
<Td>
<Link href={ link('block', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
<Link href={ link('block_index', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
</Td>
<Show above="xl">
<Td>
......
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