Commit 1a558d07 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge branch 'main' into tx-api-update

parents ce2b4b18 98c2507a
...@@ -16,6 +16,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURREN ...@@ -16,6 +16,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURREN
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS__ NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS__
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS__ NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS__
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED__ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED__
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE
# ui config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=__PLACEHOLDER_FOR_NEXT_PUBLIC_BLOCKSCOUT_VERSION__ NEXT_PUBLIC_BLOCKSCOUT_VERSION=__PLACEHOLDER_FOR_NEXT_PUBLIC_BLOCKSCOUT_VERSION__
......
...@@ -69,6 +69,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -69,6 +69,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` *(optional)* | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` | | NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` *(optional)* | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` |
| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | `https://airtable.com/shrqUAcjgGJ4jU88C` | | NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | `https://airtable.com/shrqUAcjgGJ4jU88C` |
| NEXT_PUBLIC_NETWORK_EXPLORERS | `Array<NetworkExplorer>` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` | | NEXT_PUBLIC_NETWORK_EXPLORERS | `Array<NetworkExplorer>` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` *(optional)* | Verification type in the network | `mining` |
### App configuration ### App configuration
......
...@@ -29,6 +29,7 @@ const config = Object.freeze({ ...@@ -29,6 +29,7 @@ const config = Object.freeze({
nativeTokenAddress: process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS, nativeTokenAddress: process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS,
basePath: '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/'), basePath: '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/'),
explorers: process.env.NEXT_PUBLIC_NETWORK_EXPLORERS?.replaceAll('\'', '"'), explorers: process.env.NEXT_PUBLIC_NETWORK_EXPLORERS?.replaceAll('\'', '"'),
verificationType: process.env.NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE || 'mining',
}, },
footerLinks: { footerLinks: {
github: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, github: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK,
......
...@@ -16,6 +16,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA ...@@ -16,6 +16,7 @@ NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172 NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
# api config # api config
NEXT_PUBLIC_API_BASE_PATH=/poa/core NEXT_PUBLIC_API_BASE_PATH=/poa/core
...@@ -315,6 +315,8 @@ frontend: ...@@ -315,6 +315,8 @@ frontend:
_default: SPOA _default: SPOA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS:
_default: 18 _default: 18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE:
_default: validation
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true' _default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS: NEXT_PUBLIC_FEATURED_NETWORKS:
......
...@@ -307,10 +307,21 @@ frontend: ...@@ -307,10 +307,21 @@ frontend:
_default: 77 _default: 77
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: NEXT_PUBLIC_NETWORK_CURRENCY_NAME:
_default: POA Network Sokol _default: POA Network Sokol
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL:
_default: SPOA _default: SPOA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS:
_default: 18 _default: 18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE:
_default: validation
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true' _default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS: NEXT_PUBLIC_FEATURED_NETWORKS:
......
...@@ -311,6 +311,8 @@ frontend: ...@@ -311,6 +311,8 @@ frontend:
_default: SPOA _default: SPOA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS:
_default: 18 _default: 18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE:
_default: validation
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true' _default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS: NEXT_PUBLIC_FEATURED_NETWORKS:
......
import { useBreakpointValue } from '@chakra-ui/react'; import { useBreakpointValue } from '@chakra-ui/react';
export default function useIsMobile() { export default function useIsMobile(ssr = true) {
return useBreakpointValue({ base: true, lg: false }); return useBreakpointValue({ base: true, lg: false }, { ssr });
} }
import appConfig from 'configs/app/config';
export default function getNetworkValidatorTitle() {
return appConfig.network.verificationType === 'validation' ? 'validator' : 'miner';
}
...@@ -16,3 +16,5 @@ export interface NetworkExplorer { ...@@ -16,3 +16,5 @@ export interface NetworkExplorer {
tx: string; tx: string;
}; };
} }
export type NetworkVerificationType = 'mining' | 'validation';
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import type { MarketplaceCategoriesIds, MarketplaceCategory } from 'types/client/apps'; import type { MarketplaceCategoriesIds, MarketplaceCategory } from 'types/client/apps';
import marketplaceApps from 'data/marketplaceApps.json';
import eastMiniArrowIcon from 'icons/arrows/east-mini.svg'; import eastMiniArrowIcon from 'icons/arrows/east-mini.svg';
import CategoriesMenuItem from './CategoriesMenuItem'; import CategoriesMenuItem from './CategoriesMenuItem';
...@@ -20,6 +21,10 @@ type Props = { ...@@ -20,6 +21,10 @@ type Props = {
const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => { const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => {
const selectedCategory = categoriesList.find(category => category.id === selectedCategoryId); const selectedCategory = categoriesList.find(category => category.id === selectedCategoryId);
const actualCategories = marketplaceApps.map(app => app.categories).flat();
const displayedCategories = categoriesList.filter(category => category.id === 'all' ||
category.id === 'favorites' ||
actualCategories.includes(category.id));
return ( return (
<Menu> <Menu>
...@@ -43,7 +48,7 @@ const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => { ...@@ -43,7 +48,7 @@ const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => {
</MenuButton> </MenuButton>
<MenuList zIndex={ 3 }> <MenuList zIndex={ 3 }>
{ categoriesList.map((category: MarketplaceCategory) => ( { displayedCategories.map((category: MarketplaceCategory) => (
<CategoriesMenuItem <CategoriesMenuItem
key={ category.id } key={ category.id }
id={ category.id } id={ category.id }
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, Alert } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, Alert } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
...@@ -18,6 +19,7 @@ import type { ErrorType } from 'lib/hooks/useFetch'; ...@@ -18,6 +19,7 @@ import type { ErrorType } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import link from 'lib/link/link'; import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlockDetailsSkeleton from 'ui/block/details/BlockDetailsSkeleton'; import BlockDetailsSkeleton from 'ui/block/details/BlockDetailsSkeleton';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
...@@ -70,6 +72,8 @@ const BlockDetails = () => { ...@@ -70,6 +72,8 @@ const BlockDetails = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>; const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data); const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data);
const validatorTitle = getNetworkValidatorTitle();
return ( return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"> <Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden">
<DetailsInfoItem <DetailsInfoItem
...@@ -110,12 +114,12 @@ const BlockDetails = () => { ...@@ -110,12 +114,12 @@ const BlockDetails = () => {
</Link> </Link>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Mined by" title={ appConfig.network.verificationType === 'validation' ? 'Validated by' : 'Mined by' }
hint="A block producer who successfully included the block onto the blockchain." hint="A block producer who successfully included the block onto the blockchain."
columnGap={ 1 } columnGap={ 1 }
> >
<AddressLink hash={ data.miner.hash }/> <AddressLink hash={ data.miner.hash }/>
{ data.miner.name && <Text>(Miner: { data.miner.name })</Text> } { data.miner.name && <Text>{ `(${ capitalize(validatorTitle) }: ${ data.miner.name })` }</Text> }
{ /* api doesn't return the block processing time yet */ } { /* api doesn't return the block processing time yet */ }
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ } { /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -123,7 +127,7 @@ const BlockDetails = () => { ...@@ -123,7 +127,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Block reward" title="Block reward"
hint={ hint={
`For each block, the miner is rewarded with a finite amount of ${ appConfig.network.currency.symbol || 'native token' } `For each block, the ${ validatorTitle } is rewarded with a finite amount of ${ appConfig.network.currency.symbol || 'native token' }
on top of the fees paid for all transactions in the block.` on top of the fees paid for all transactions in the block.`
} }
columnGap={ 1 } columnGap={ 1 }
...@@ -160,7 +164,8 @@ const BlockDetails = () => { ...@@ -160,7 +164,8 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
key={ type } key={ type }
title={ type } title={ type }
hint="Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." // is this text correct for validators?
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees.` }
> >
{ BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol } { BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -231,7 +236,7 @@ const BlockDetails = () => { ...@@ -231,7 +236,7 @@ const BlockDetails = () => {
{ /* api doesn't support extra data yet */ } { /* api doesn't support extra data yet */ }
{ /* <DetailsInfoItem { /* <DetailsInfoItem
title="Extra data" title="Extra data"
hint="Any data that can be included by the miner in the block." hint={ `Any data that can be included by the ${ validatorTitle } in the block.` }
> >
<Text whiteSpace="pre">{ data.extra_data } </Text> <Text whiteSpace="pre">{ data.extra_data } </Text>
<Text variant="secondary">(Hex: { data.extra_data })</Text> <Text variant="secondary">(Hex: { data.extra_data })</Text>
...@@ -260,7 +265,7 @@ const BlockDetails = () => { ...@@ -260,7 +265,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Difficulty" title="Difficulty"
hint="Block difficulty for miner, used to calibrate block generation time." hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time.` }
> >
{ BigNumber(data.difficulty).toFormat() } { BigNumber(data.difficulty).toFormat() }
</DetailsInfoItem> </DetailsInfoItem>
......
import React from 'react'; import React from 'react';
import TxsContent from 'ui/txs/TxsContent'; import TxsWithSort from 'ui/txs/TxsWithSort';
const BlockTxs = () => { const BlockTxs = () => {
return <TxsContent showDescription={ false } showSortButton={ false } txs={ [] }/>; return (
// <TxsContent
// showDescription={ false }
// showSortButton={ false }
// txs={ [] }
// page={ 1 }
// // eslint-disable-next-line react/jsx-no-bind
// onNextPageClick={ () => {} }
// // eslint-disable-next-line react/jsx-no-bind
// onPrevPageClick={ () => {} }
// />
// eslint-disable-next-line react/jsx-no-bind
<TxsWithSort txs={ [] } sort={ () => () => {} }/>
);
}; };
export default BlockTxs; export default BlockTxs;
...@@ -53,7 +53,8 @@ const BlocksContent = ({ type }: Props) => { ...@@ -53,7 +53,8 @@ const BlocksContent = ({ type }: Props) => {
<Show below="lg" key="content-mobile"><BlocksList data={ data.items }/></Show> <Show below="lg" key="content-mobile"><BlocksList data={ data.items }/></Show>
<Show above="lg" key="content-desktop"><BlocksTable data={ data.items }/></Show> <Show above="lg" key="content-desktop"><BlocksTable data={ data.items }/></Show>
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}> <Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
<Pagination currentPage={ 1 }/> { /* eslint-disable-next-line react/jsx-no-bind */ }
<Pagination currentPage={ 1 } onNextPageClick={ () => {} } onPrevPageClick={ () => {} } hasNextPage/>
</Box> </Box>
</> </>
); );
......
import { Flex, Link, Spinner, Text, Box, Icon } from '@chakra-ui/react'; import { Flex, Link, Spinner, Text, Box, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
...@@ -9,6 +10,7 @@ import flameIcon from 'icons/flame.svg'; ...@@ -9,6 +10,7 @@ import flameIcon from 'icons/flame.svg';
import { WEI, ZERO } from 'lib/consts'; import { WEI, ZERO } from 'lib/consts';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import link from 'lib/link/link'; import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile'; import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio'; import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
...@@ -45,7 +47,7 @@ const BlocksListItem = ({ data, isPending }: Props) => { ...@@ -45,7 +47,7 @@ const BlocksListItem = ({ data, isPending }: Props) => {
<Text variant="secondary">{ data.size.toLocaleString('en') } bytes</Text> <Text variant="secondary">{ data.size.toLocaleString('en') } bytes</Text>
</Flex> </Flex>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Miner</Text> <Text fontWeight={ 500 }>{ capitalize(getNetworkValidatorTitle()) }</Text>
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/> <AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/>
</Flex> </Flex>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
......
import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react'; import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlocksTableItem from 'ui/blocks/BlocksTableItem'; import BlocksTableItem from 'ui/blocks/BlocksTableItem';
interface Props { interface Props {
...@@ -19,7 +21,7 @@ const BlocksTable = ({ data }: Props) => { ...@@ -19,7 +21,7 @@ const BlocksTable = ({ data }: Props) => {
<Tr> <Tr>
<Th width="125px">Block</Th> <Th width="125px">Block</Th>
<Th width="120px">Size</Th> <Th width="120px">Size</Th>
<Th width="21%" minW="144px">Miner</Th> <Th width="21%" minW="144px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="64px" isNumeric>Txn</Th> <Th width="64px" isNumeric>Txn</Th>
<Th width="35%">Gas used</Th> <Th width="35%">Gas used</Th>
<Th width="22%">Reward { appConfig.network.currency.symbol }</Th> <Th width="22%">Reward { appConfig.network.currency.symbol }</Th>
......
...@@ -5,18 +5,18 @@ import React from 'react'; ...@@ -5,18 +5,18 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import appConfig from 'configs/app/config';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TxsPending from 'ui/txs/TxsPending'; import TxsTab from 'ui/txs/TxsTab';
import TxsValidated from 'ui/txs/TxsValidated';
const TABS: Array<RoutedTab> = [
{ id: 'validated', title: 'Validated', component: <TxsValidated/> },
{ id: 'pending', title: 'Pending', component: <TxsPending/> },
];
const Transactions = () => { const Transactions = () => {
const verifiedTitle = appConfig.network.verificationType === 'validation' ? 'Validated' : 'Mined';
const TABS: Array<RoutedTab> = [
{ id: 'validated', title: verifiedTitle, component: <TxsTab tab="validated"/> },
{ id: 'pending', title: 'Pending', component: <TxsTab tab="pending"/> },
];
return ( return (
<Page> <Page>
......
import { Button, Flex, Input, Icon, IconButton } from '@chakra-ui/react'; import { Button, Flex, Icon, IconButton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import arrowIcon from 'icons/arrows/east-mini.svg'; import arrowIcon from 'icons/arrows/east-mini.svg';
...@@ -6,11 +6,14 @@ import arrowIcon from 'icons/arrows/east-mini.svg'; ...@@ -6,11 +6,14 @@ import arrowIcon from 'icons/arrows/east-mini.svg';
type Props = { type Props = {
currentPage: number; currentPage: number;
maxPage?: number; maxPage?: number;
onNextPageClick: () => void;
onPrevPageClick: () => void;
hasNextPage: boolean;
} }
const MAX_PAGE_DEFAULT = 50; const MAX_PAGE_DEFAULT = 50;
const Pagination = ({ currentPage, maxPage }: Props) => { const Pagination = ({ currentPage, maxPage, onNextPageClick, onPrevPageClick, hasNextPage }: Props) => {
const pageNumber = ( const pageNumber = (
<Flex alignItems="center"> <Flex alignItems="center">
<Button <Button
...@@ -25,6 +28,7 @@ const Pagination = ({ currentPage, maxPage }: Props) => { ...@@ -25,6 +28,7 @@ const Pagination = ({ currentPage, maxPage }: Props) => {
> >
{ currentPage } { currentPage }
</Button> </Button>
{ /* max page will be removed */ }
of of
<Button <Button
variant="outline" variant="outline"
...@@ -50,25 +54,30 @@ const Pagination = ({ currentPage, maxPage }: Props) => { ...@@ -50,25 +54,30 @@ const Pagination = ({ currentPage, maxPage }: Props) => {
<Flex alignItems="center" justifyContent="space-between" w={{ base: '100%', lg: 'auto' }}> <Flex alignItems="center" justifyContent="space-between" w={{ base: '100%', lg: 'auto' }}>
<IconButton <IconButton
variant="outline" variant="outline"
onClick={ onPrevPageClick }
size="sm" size="sm"
aria-label="Next page" aria-label="Next page"
w="36px" w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> } icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
mr={ 8 } mr={ 8 }
disabled={ currentPage === 1 }
/> />
{ pageNumber } { pageNumber }
<IconButton <IconButton
variant="outline" variant="outline"
onClick={ onNextPageClick }
size="sm" size="sm"
aria-label="Next page" aria-label="Next page"
w="36px" w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> } icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
ml={ 8 } ml={ 8 }
disabled={ !hasNextPage }
/> />
</Flex> </Flex>
<Flex alignItems="center" width="132px" ml={ 16 } display={{ base: 'none', lg: 'flex' }}> { /* not implemented yet */ }
{ /* <Flex alignItems="center" width="132px" ml={ 16 } display={{ base: 'none', lg: 'flex' }}>
Go to <Input w="84px" size="xs" ml={ 2 }/> Go to <Input w="84px" size="xs" ml={ 2 }/>
</Flex> </Flex> */ }
</Flex> </Flex>
); );
......
...@@ -14,7 +14,7 @@ import { menuButton } from './utils'; ...@@ -14,7 +14,7 @@ import { menuButton } from './utils';
interface Props { interface Props {
tabs: Array<RoutedTab | MenuButton>; tabs: Array<RoutedTab | MenuButton>;
activeTab: RoutedTab; activeTab?: RoutedTab;
tabsCut: number; tabsCut: number;
isActive: boolean; isActive: boolean;
styles?: StyleProps; styles?: StyleProps;
...@@ -52,7 +52,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe ...@@ -52,7 +52,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe
key={ tab.id } key={ tab.id }
variant="ghost" variant="ghost"
onClick={ handleItemClick } onClick={ handleItemClick }
isActive={ activeTab.id === tab.id } isActive={ activeTab ? activeTab.id === tab.id : false }
justifyContent="left" justifyContent="left"
data-index={ index } data-index={ index }
> >
......
import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, Link, StatArrow, Stat, AccordionPanel } from '@chakra-ui/react'; import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, Link, StatArrow, Stat, AccordionPanel } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
...@@ -6,6 +7,7 @@ import type ArrayElement from 'types/utils/ArrayElement'; ...@@ -6,6 +7,7 @@ import type ArrayElement from 'types/utils/ArrayElement';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import type { data } from 'data/txState'; import type { data } from 'data/txState';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile'; import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
...@@ -60,7 +62,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props ...@@ -60,7 +62,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
) } ) }
<Flex rowGap={ 2 } flexDir="column" fontSize="sm" whiteSpace="pre" fontWeight={ 500 }> <Flex rowGap={ 2 } flexDir="column" fontSize="sm" whiteSpace="pre" fontWeight={ 500 }>
<Box> <Box>
<Text as="span">Miner </Text> <Text as="span">{ capitalize(getNetworkValidatorTitle()) }</Text>
<Link>{ miner }</Link> <Link>{ miner }</Link>
</Box> </Box>
<Box> <Box>
......
...@@ -6,10 +6,12 @@ import { ...@@ -6,10 +6,12 @@ import {
Th, Th,
TableContainer, TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import { data } from 'data/txState'; import { data } from 'data/txState';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem'; import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
const TxStateTable = () => { const TxStateTable = () => {
...@@ -20,7 +22,7 @@ const TxStateTable = () => { ...@@ -20,7 +22,7 @@ const TxStateTable = () => {
<Tr> <Tr>
<Th width="92px">Storage</Th> <Th width="92px">Storage</Th>
<Th width="146px">Address</Th> <Th width="146px">Address</Th>
<Th width="120px">Miner</Th> <Th width="120px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th> <Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency.symbol }` }</Th> <Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency.symbol }` }</Th> <Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency.symbol }` }</Th>
......
import { Box, HStack, Show } from '@chakra-ui/react'; import { Alert, Box, HStack, Show, Button } from '@chakra-ui/react';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useState, useCallback } from 'react';
import type { TransactionsResponse } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort'; import type { Sort } from 'types/client/txs-sort';
import compareBns from 'lib/bigint/compareBns'; import useIsMobile from 'lib/hooks/useIsMobile';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import FilterButton from 'ui/shared/FilterButton'; import FilterButton from 'ui/shared/FilterButton';
import FilterInput from 'ui/shared/FilterInput'; import FilterInput from 'ui/shared/FilterInput';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import SortButton from 'ui/shared/SortButton'; import SortButton from 'ui/shared/SortButton';
import TxsListItem from './TxsListItem'; import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsTable from './TxsTable'; import TxsSkeletonMobile from './TxsSkeletonMobile';
import TxsWithSort from './TxsWithSort';
import useQueryWithPages from './useQueryWithPages';
type Props = { type Props = {
txs: TransactionsResponse['items']; queryName: string;
showDescription?: boolean; showDescription?: boolean;
showSortButton?: boolean; stateFilter: 'validated' | 'pending';
} }
const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Props) => { const TxsContent = ({
showDescription,
queryName,
stateFilter,
}: Props) => {
const [ sorting, setSorting ] = useState<Sort>(); const [ sorting, setSorting ] = useState<Sort>();
const [ sortedTxs, setSortedTxs ] = useState(txs);
// sorting should be preserved with pagination!
const sort = useCallback((field: 'val' | 'fee') => () => { const sort = useCallback((field: 'val' | 'fee') => () => {
if (field === 'val') { if (field === 'val') {
setSorting((prevVal => { setSorting((prevVal => {
...@@ -47,26 +51,41 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop ...@@ -47,26 +51,41 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop
return 'fee-desc'; return 'fee-desc';
})); }));
} }
}, []); }, [ setSorting ]);
useEffect(() => { const {
switch (sorting) { data,
case 'val-desc': isLoading,
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value))); isError,
break; page,
case 'val-asc': onPrevPageClick,
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value))); onNextPageClick,
break; hasPagination,
case 'fee-desc': resetPage,
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value))); } = useQueryWithPages(queryName, stateFilter);
break;
case 'fee-asc': const isMobile = useIsMobile(false);
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value)));
break; if (isError) {
default: return <DataFetchAlert/>;
setSortedTxs(txs); }
}
}, [ sorting, txs ]); const txs = data?.items;
if (!isLoading && !txs) {
return <Alert>There are no transactions.</Alert>;
}
let content = (
<>
<Show below="lg" ssr={ false }><TxsSkeletonMobile/></Show>
<Show above="lg" ssr={ false }><TxsSkeletonDesktop/></Show>
</>
);
if (!isLoading && txs) {
content = <TxsWithSort txs={ txs } sorting={ sorting } sort={ sort }/>;
}
return ( return (
<> <>
...@@ -79,7 +98,7 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop ...@@ -79,7 +98,7 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop
onClick={ () => {} } onClick={ () => {} }
appliedFiltersNum={ 0 } appliedFiltersNum={ 0 }
/> />
{ showSortButton && ( { isMobile && (
<SortButton <SortButton
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
handleSort={ () => {} } handleSort={ () => {} }
...@@ -95,10 +114,19 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop ...@@ -95,10 +114,19 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop
placeholder="Search by addresses, hash, method..." placeholder="Search by addresses, hash, method..."
/> />
</HStack> </HStack>
<Show below="lg"><Box>{ sortedTxs.map(tx => <TxsListItem tx={ tx } key={ tx.hash }/>) }</Box></Show> { content }
<Show above="lg"><TxsTable txs={ sortedTxs } sort={ sort } sorting={ sorting }/></Show>
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}> <Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
<Pagination currentPage={ 1 }/> { hasPagination ? (
<Pagination
currentPage={ page }
hasNextPage={ data?.next_page_params !== undefined && Object.keys(data?.next_page_params).length > 0 }
onNextPageClick={ onNextPageClick }
onPrevPageClick={ onPrevPageClick }
/>
) :
// temporary button, waiting for new pagination mockups
<Button onClick={ resetPage }>Reset</Button>
}
</Box> </Box>
</> </>
); );
......
import { Show, Alert } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { TransactionsResponse } from 'types/api/transaction';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TxsContent from './TxsContent';
import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsSkeletonMobile from './TxsSkeletonMobile';
const TxsValidated = () => {
const fetch = useFetch();
const { data, isLoading, isError } =
useQuery<unknown, unknown, TransactionsResponse>([ QueryKeys.transactionsPending ], async() => fetch('/api/transactions/?filter=pending'));
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return (
<>
<Show below="lg"><TxsSkeletonMobile isPending/></Show>
<Show above="lg"><TxsSkeletonDesktop isPending/></Show>
</>
);
}
if (!data || !data.items) {
return <Alert>There are no transactions.</Alert>;
}
return <TxsContent txs={ data.items } showDescription={ false }/>;
};
export default TxsValidated;
import { Skeleton, Flex } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import SkeletonTable from 'ui/shared/SkeletonTable'; import SkeletonTable from 'ui/shared/SkeletonTable';
const TxsInternalsSkeletonDesktop = ({ isPending }: {isPending?: boolean}) => { const TxsInternalsSkeletonDesktop = () => {
return ( return (
<> <Box mb={ 8 }>
{ !isPending && <Skeleton h={ 6 } w="100%" mb={ 12 }/> }
<Flex columnGap={ 3 } h={ 8 } mb={ 6 }>
<Skeleton w="78px"/>
<Skeleton w="360px"/>
</Flex>
<SkeletonTable columns={ [ '32px', '20%', '18%', '15%', '11%', '292px', '18%', '18%' ] }/> <SkeletonTable columns={ [ '32px', '20%', '18%', '15%', '11%', '292px', '18%', '18%' ] }/>
</> </Box>
); );
}; };
......
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react'; import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const TxInternalsSkeletonMobile = ({ isPending }: {isPending?: boolean}) => { const TxInternalsSkeletonMobile = () => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<> <Box>
{ !isPending && <Skeleton h={ 6 } w="100%" mb={ 12 }/> } { Array.from(Array(2)).map((item, index) => (
<Flex columnGap={ 3 } h={ 8 } mb={ 6 }> <Flex
<Skeleton w="36px" flexShrink={ 0 }/> key={ index }
<Skeleton w="36px" flexShrink={ 0 }/> flexDirection="column"
<Skeleton w="100%"/> paddingBottom={ 3 }
</Flex> paddingTop={ 4 }
<Box> borderTopWidth="1px"
{ Array.from(Array(2)).map((item, index) => ( borderColor={ borderColor }
<Flex _last={{
key={ index } borderBottomWidth: '1px',
flexDirection="column" }}
paddingBottom={ 3 } >
paddingTop={ 4 } <Flex h={ 6 }>
borderTopWidth="1px" <Skeleton w="100px" mr={ 2 } h={ 6 }/>
borderColor={ borderColor } <Skeleton w="100px" h={ 6 }/>
_last={{
borderBottomWidth: '1px',
}}
>
<Flex h={ 6 }>
<Skeleton w="100px" mr={ 2 } h={ 6 }/>
<Skeleton w="100px" h={ 6 }/>
</Flex>
<Skeleton w="100%" h="30px" mt={ 3 }/>
<Skeleton w="50%" h={ 6 } mt={ 3 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
<Skeleton w="100%" h={ 6 } mt={ 6 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
</Flex> </Flex>
)) } <Skeleton w="100%" h="30px" mt={ 3 }/>
</Box> <Skeleton w="50%" h={ 6 } mt={ 3 }/>
</> <Skeleton w="50%" h={ 6 } mt={ 2 }/>
<Skeleton w="100%" h={ 6 } mt={ 6 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
</Flex>
)) }
</Box>
); );
}; };
......
import React from 'react';
import { QueryKeys } from 'types/client/queries';
import TxsContent from './TxsContent';
type Props = {
tab: 'validated' | 'pending';
}
const TxsTab = ({ tab }: Props) => {
return (
<TxsContent
queryName={ tab === 'validated' ? QueryKeys.transactionsValidated : QueryKeys.transactionsPending }
showDescription={ tab === 'validated' }
stateFilter={ tab }
/>
);
};
export default TxsTab;
...@@ -12,7 +12,6 @@ import { ...@@ -12,7 +12,6 @@ import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
Portal,
useColorModeValue, useColorModeValue,
Show, Show,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
...@@ -40,7 +39,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => { ...@@ -40,7 +39,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
<Tooltip label={ tx.from.implementation_name }> <Tooltip label={ tx.from.implementation_name }>
<Box display="flex"><AddressIcon hash={ tx.from.hash }/></Box> <Box display="flex"><AddressIcon hash={ tx.from.hash }/></Box>
</Tooltip> </Tooltip>
<AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 }/> <AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant"/>
</Address> </Address>
); );
...@@ -49,7 +48,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => { ...@@ -49,7 +48,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
<Tooltip label={ tx.to.implementation_name }> <Tooltip label={ tx.to.implementation_name }>
<Box display="flex"><AddressIcon hash={ tx.to.hash }/></Box> <Box display="flex"><AddressIcon hash={ tx.to.hash }/></Box>
</Tooltip> </Tooltip>
<AddressLink hash={ tx.to.hash } alias={ tx.to.name } fontWeight="500" ml={ 2 }/> <AddressLink hash={ tx.to.hash } alias={ tx.to.name } fontWeight="500" ml={ 2 } truncation="constant"/>
</Address> </Address>
); );
...@@ -57,19 +56,17 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => { ...@@ -57,19 +56,17 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
return ( return (
<Tr> <Tr>
<Td pl={ 4 }> <Td pl={ 4 }>
<Popover placement="right-start" openDelay={ 300 }> <Popover placement="right-start" openDelay={ 300 } isLazy>
{ ({ isOpen }) => ( { ({ isOpen }) => (
<> <>
<PopoverTrigger> <PopoverTrigger>
<TxAdditionalInfoButton isOpen={ isOpen }/> <TxAdditionalInfoButton isOpen={ isOpen }/>
</PopoverTrigger> </PopoverTrigger>
<Portal> <PopoverContent border="1px solid" borderColor={ infoBorderColor }>
<PopoverContent border="1px solid" borderColor={ infoBorderColor }> <PopoverBody>
<PopoverBody> <TxAdditionalInfo tx={ tx }/>
<TxAdditionalInfo tx={ tx }/> </PopoverBody>
</PopoverBody> </PopoverContent>
</PopoverContent>
</Portal>
</> </>
) } ) }
</Popover> </Popover>
...@@ -115,8 +112,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => { ...@@ -115,8 +112,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
<Td> <Td>
{ tx.block && <Link href={ link('block', { id: tx.block.toString() }) }>{ tx.block }</Link> } { tx.block && <Link href={ link('block', { id: tx.block.toString() }) }>{ tx.block }</Link> }
</Td> </Td>
{ /* TODO: fix "show" problem */ } <Show above="xl" ssr={ false }>
<Show above="xl">
<Td> <Td>
{ addressFrom } { addressFrom }
</Td> </Td>
...@@ -127,7 +123,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => { ...@@ -127,7 +123,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
{ addressTo } { addressTo }
</Td> </Td>
</Show> </Show>
<Show below="xl"> <Show below="xl" ssr={ false }>
<Td colSpan={ 3 }> <Td colSpan={ 3 }>
<Box> <Box>
{ addressFrom } { addressFrom }
......
import { Show, Alert } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { TransactionsResponse } from 'types/api/transaction';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TxsContent from './TxsContent';
import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsSkeletonMobile from './TxsSkeletonMobile';
const TxsValidated = () => {
const fetch = useFetch();
const { data, isLoading, isError } =
useQuery<unknown, unknown, TransactionsResponse>([ QueryKeys.transactionsValidated ], async() => fetch('/api/transactions/?filter=validated'));
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return (
<>
<Show below="lg"><TxsSkeletonMobile/></Show>
<Show above="lg"><TxsSkeletonDesktop/></Show>
</>
);
}
if (!data || !data.items) {
return <Alert>There are no transactions.</Alert>;
}
return <TxsContent txs={ data.items }/>;
};
export default TxsValidated;
import { Box, Show } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import type { TransactionsResponse } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import compareBns from 'lib/bigint/compareBns';
import TxsListItem from './TxsListItem';
import TxsTable from './TxsTable';
type Props = {
txs: TransactionsResponse['items'];
sorting?: Sort;
sort: (field: 'val' | 'fee') => () => void;
}
const TxsWithSort = ({
txs,
sorting,
sort,
}: Props) => {
const [ sortedTxs, setSortedTxs ] = useState(txs);
useEffect(() => {
switch (sorting) {
case 'val-desc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value)));
break;
case 'val-asc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value)));
break;
case 'fee-desc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value)));
break;
case 'fee-asc':
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value)));
break;
default:
setSortedTxs(txs);
}
}, [ sorting, txs ]);
return (
<>
<Show below="lg" ssr={ false }><Box>{ sortedTxs.map(tx => <TxsListItem tx={ tx } key={ tx.hash }/>) }</Box></Show>
<Show above="lg" ssr={ false }><TxsTable txs={ sortedTxs } sort={ sort } sorting={ sorting }/></Show>
</>
);
};
export default TxsWithSort;
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { pick, omit } from 'lodash';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll';
import type { TransactionsResponse } from 'types/api/transaction';
import useFetch from 'lib/hooks/useFetch';
const PAGINATION_FIELDS = [ 'block_number', 'index', 'items_count' ];
export default function useQueryWithPages(queryName: string, filter: string) {
const queryClient = useQueryClient();
const router = useRouter();
const [ page, setPage ] = React.useState(1);
const currPageParams = pick(router.query, PAGINATION_FIELDS);
const [ pageParams, setPageParams ] = React.useState<Array<Partial<TransactionsResponse['next_page_params']>>>([ {} ]);
const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, TransactionsResponse>(
[ queryName, { page } ],
async() => {
const params: Array<string> = [];
Object.entries(currPageParams).forEach(([ key, val ]) => params.push(`${ key }=${ val }`));
return fetch(`/api/transactions?filter=${ filter }${ params.length ? '&' + params.join('&') : '' }`);
},
{ staleTime: Infinity },
);
const onNextPageClick = useCallback(() => {
if (!data?.next_page_params) {
// we hide next page button if no next_page_params
return;
}
// api adds filters into next-page-params now
// later filters will be removed from response
const nextPageParams = pick(data.next_page_params, PAGINATION_FIELDS);
if (page >= pageParams.length && data?.next_page_params) {
setPageParams(prev => [ ...prev, nextPageParams ]);
}
const nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true });
animateScroll.scrollToTop({ duration: 0 });
setPage(prev => prev + 1);
}, [ data, page, pageParams, router ]);
const onPrevPageClick = useCallback(() => {
// returning to the first page
// we dont have pagination params for the first page
let nextPageQuery: typeof router.query;
if (page === 2) {
nextPageQuery = omit(router.query, PAGINATION_FIELDS);
} else {
const nextPageParams = { ...pageParams[page - 2] };
nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
}
router.query = nextPageQuery;
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true });
animateScroll.scrollToTop({ duration: 0 });
setPage(prev => prev - 1);
}, [ router, page, pageParams ]);
const resetPage = useCallback(() => {
queryClient.clear();
animateScroll.scrollToTop({ duration: 0 });
router.push({ pathname: router.pathname, query: omit(router.query, PAGINATION_FIELDS) }, undefined, { shallow: true });
}, [ router, queryClient ]);
// if there are pagination params on the initial page, we shouldn't show pagination
const hasPagination = !(page === 1 && Object.keys(currPageParams).length > 0);
return { data, isError, isLoading, page, onNextPageClick, onPrevPageClick, hasPagination, resetPage };
}
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