Feat/397 list transactions (#1770)
* chore(explorer): add stagnet 1 env vars * feat(explorer): add info block component * feat(explorer): change tx order type lozenge * feat(explorer): change truncated-link.tsx style * feat(explorer): add stats info component for txs * feat(explorer): change page title size * feat(explorer): update txs list and add txs for party * fix(explorer): remove unused var * fix(explorer): change copy and remove unused vars * fix(explorer): pr review fixes
This commit is contained in:
parent
033ee14009
commit
cc8f052a5b
10
apps/explorer/.env.stagnet1
Normal file
10
apps/explorer/.env.stagnet1
Normal file
@ -0,0 +1,10 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"}
|
||||
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
|
||||
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
1
apps/explorer/src/app/components/info-block/index.ts
Normal file
1
apps/explorer/src/app/components/info-block/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './info-block';
|
29
apps/explorer/src/app/components/info-block/info-block.tsx
Normal file
29
apps/explorer/src/app/components/info-block/info-block.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
export interface InfoBlockProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
tooltipInfo: string;
|
||||
}
|
||||
|
||||
export const InfoBlock = ({ title, subtitle, tooltipInfo }: InfoBlockProps) => {
|
||||
return (
|
||||
<div className="flex flex-col text-center ">
|
||||
<h3 className="text-4xl">{title}</h3>
|
||||
<p className="text-zinc-800 dark:text-zinc-300">
|
||||
{subtitle}
|
||||
{tooltipInfo ? (
|
||||
<Tooltip description={tooltipInfo} align="center">
|
||||
<span>
|
||||
<Icon
|
||||
name="info-sign"
|
||||
className="ml-2 text-zinc-400 dark:text-zinc-600"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : undefined}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -14,7 +14,7 @@ export const RouteTitle = ({
|
||||
}: RouteTitleProps) => {
|
||||
const classes = classnames(
|
||||
'font-alpha',
|
||||
'text-2xl',
|
||||
'text-4xl',
|
||||
'uppercase',
|
||||
'mb-8',
|
||||
className
|
||||
|
@ -20,7 +20,7 @@ export const TruncatedLink = ({
|
||||
text={text}
|
||||
startChars={startChars}
|
||||
endChars={endChars}
|
||||
className="font-mono font-bold underline"
|
||||
className="underline uppercase underline-offset-2"
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
35
apps/explorer/src/app/components/txs/__generated__/TxsStats.ts
generated
Normal file
35
apps/explorer/src/app/components/txs/__generated__/TxsStats.ts
generated
Normal file
@ -0,0 +1,35 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: TxsStats
|
||||
// ====================================================
|
||||
|
||||
export interface TxsStats_statistics {
|
||||
__typename: "Statistics";
|
||||
/**
|
||||
* Average number of orders added per blocks
|
||||
*/
|
||||
averageOrdersPerBlock: string;
|
||||
/**
|
||||
* Number of orders per seconds
|
||||
*/
|
||||
ordersPerSecond: string;
|
||||
/**
|
||||
* Number of transaction processed per block
|
||||
*/
|
||||
txPerBlock: string;
|
||||
/**
|
||||
* Number of the trades per seconds
|
||||
*/
|
||||
tradesPerSecond: string;
|
||||
}
|
||||
|
||||
export interface TxsStats {
|
||||
/**
|
||||
* Get statistics about the Vega node
|
||||
*/
|
||||
statistics: TxsStats_statistics;
|
||||
}
|
@ -3,3 +3,4 @@ export { BlockTxsData } from './block-txs-data';
|
||||
export { TxOrderType } from './tx-order-type';
|
||||
export { TxsInfiniteList } from './txs-infinite-list';
|
||||
export { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
export { TxsStatsInfo } from './txs-stats-info';
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { Lozenge, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface TxOrderTypeProps {
|
||||
orderType: string;
|
||||
className?: string;
|
||||
@ -34,10 +32,13 @@ const displayString: StringMap = {
|
||||
ValidatorHeartbeat: 'Validator Heartbeat',
|
||||
};
|
||||
|
||||
export const TxOrderType = ({ orderType, className }: TxOrderTypeProps) => {
|
||||
export const TxOrderType = ({ orderType }: TxOrderTypeProps) => {
|
||||
return (
|
||||
<Lozenge data-testid="tx-type" variant={Intent.None} className={className}>
|
||||
<div
|
||||
data-testid="tx-type"
|
||||
className="text-sm rounded-md leading-none px-2 py-2 inline-block text-white dark:text-white bg-zinc-800 dark:bg-zinc-800 "
|
||||
>
|
||||
{displayString[orderType] || orderType}
|
||||
</Lozenge>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -83,8 +83,7 @@ describe('Txs infinite list item', () => {
|
||||
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
|
||||
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
||||
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
|
||||
expect(screen.getByTestId('tx-block')).toHaveTextContent(
|
||||
'Block 1 (index 1)'
|
||||
);
|
||||
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('tx-index')).toHaveTextContent('1');
|
||||
});
|
||||
});
|
||||
|
@ -27,20 +27,13 @@ export const TxsInfiniteListItem = ({
|
||||
return (
|
||||
<div
|
||||
data-testid="transaction-row"
|
||||
className="grid grid-flow-col auto-cols-auto border-t border-neutral-600 dark:border-neutral-800 py-2 txs-infinite-list-item"
|
||||
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2"
|
||||
>
|
||||
<div className="whitespace-nowrap" data-testid="tx-type">
|
||||
<TxOrderType orderType={type} />
|
||||
</div>
|
||||
<div className="whitespace-nowrap" data-testid="pub-key">
|
||||
<TruncatedLink
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
startChars={TRUNCATE_LENGTH}
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div className="whitespace-nowrap" data-testid="tx-hash">
|
||||
<div
|
||||
className="text-sm col-span-10 xl:col-span-3 leading-none"
|
||||
data-testid="tx-hash"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">ID: </span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${toHex(hash)}`}
|
||||
text={hash}
|
||||
@ -48,14 +41,40 @@ export const TxsInfiniteListItem = ({
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div className="whitespace-nowrap" data-testid="tx-block">
|
||||
<div
|
||||
className="text-sm col-span-10 xl:col-span-3 leading-none"
|
||||
data-testid="pub-key"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">By: </span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.BLOCKS}/${block}`}
|
||||
text={`Block ${block} (index ${index})`}
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
startChars={TRUNCATE_LENGTH}
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
|
||||
<TxOrderType orderType={type} />
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-block"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">Block: </span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.BLOCKS}/${block}`}
|
||||
text={block}
|
||||
startChars={TRUNCATE_LENGTH}
|
||||
endChars={TRUNCATE_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-index"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">Index: </span>
|
||||
{index}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -65,7 +65,7 @@ describe('Txs infinite list', () => {
|
||||
it('item renders data of n length into list of n length', () => {
|
||||
// Provided the number of items doesn't exceed the 30 it initially
|
||||
// desires, all txs will initially render
|
||||
const txs = generateTxs(10);
|
||||
const txs = generateTxs(7);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteList
|
||||
@ -82,7 +82,7 @@ describe('Txs infinite list', () => {
|
||||
screen
|
||||
.getByTestId('infinite-scroll-wrapper')
|
||||
.querySelectorAll('.txs-infinite-list-item')
|
||||
).toHaveLength(10);
|
||||
).toHaveLength(7);
|
||||
});
|
||||
|
||||
it('tries to load more items when required to initially fill the list', () => {
|
||||
@ -126,7 +126,7 @@ describe('Txs infinite list', () => {
|
||||
});
|
||||
|
||||
it('loads more items is called when scrolled', () => {
|
||||
const txs = generateTxs(20);
|
||||
const txs = generateTxs(14);
|
||||
const callback = jest.fn();
|
||||
|
||||
render(
|
||||
@ -143,7 +143,7 @@ describe('Txs infinite list', () => {
|
||||
|
||||
act(() => {
|
||||
fireEvent.scroll(screen.getByTestId('infinite-scroll-wrapper'), {
|
||||
target: { scrollY: 600 },
|
||||
target: { scrollY: 2000 },
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import InfiniteLoader from 'react-window-infinite-loader';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { t, useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
|
||||
@ -55,6 +55,9 @@ export const TxsInfiniteList = ({
|
||||
error,
|
||||
className,
|
||||
}: TxsInfiniteListProps) => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
|
||||
|
||||
if (!txs) {
|
||||
return <div>No items</div>;
|
||||
}
|
||||
@ -71,11 +74,15 @@ export const TxsInfiniteList = ({
|
||||
|
||||
return (
|
||||
<div className={className} data-testid="transactions-list">
|
||||
<div className="grid grid-flow-col auto-cols-auto w-full mb-8">
|
||||
<div className="text-lg font-bold pl-2">Type</div>
|
||||
<div className="text-lg font-bold">Submitted By</div>
|
||||
<div className="text-lg font-bold">Transaction ID</div>
|
||||
<div className="text-lg font-bold">Block</div>
|
||||
<div className="xl:grid grid-cols-10 w-full mb-3 hidden text-zinc-500 uppercase">
|
||||
<div className="col-span-3">
|
||||
<span className="hidden xl:inline">Transaction </span>
|
||||
<span>ID</span>
|
||||
</div>
|
||||
<div className="col-span-3">Submitted By</div>
|
||||
<div className="col-span-2">Type</div>
|
||||
<div className="col-span-1">Block</div>
|
||||
<div className="col-span-1">Index</div>
|
||||
</div>
|
||||
<div data-testid="infinite-scroll-wrapper">
|
||||
<InfiniteLoader
|
||||
@ -88,7 +95,7 @@ export const TxsInfiniteList = ({
|
||||
className="List"
|
||||
height={595}
|
||||
itemCount={itemCount}
|
||||
itemSize={41}
|
||||
itemSize={isStacked ? 134 : 72}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={'100%'}
|
||||
|
79
apps/explorer/src/app/components/txs/txs-stats-info.tsx
Normal file
79
apps/explorer/src/app/components/txs/txs-stats-info.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect } from 'react';
|
||||
import { InfoBlock } from '../../components/info-block';
|
||||
import type { TxsStats, TxsStats_statistics } from './__generated__/TxsStats';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const STATS_QUERY = gql`
|
||||
query TxsStats {
|
||||
statistics {
|
||||
averageOrdersPerBlock
|
||||
ordersPerSecond
|
||||
txPerBlock
|
||||
tradesPerSecond
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface StatsMap {
|
||||
field: keyof TxsStats_statistics;
|
||||
label: string;
|
||||
info: string;
|
||||
}
|
||||
|
||||
export const TXS_STATS_MAP: StatsMap[] = [
|
||||
{
|
||||
field: 'averageOrdersPerBlock',
|
||||
label: t('Orders per block'),
|
||||
info: t(
|
||||
'Number of new orders processed in the last block. All orders derived from pegged orders and liquidity commitments count as a single order'
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'txPerBlock',
|
||||
label: t('Transactions per block'),
|
||||
info: t('Number of transactions processed in the last block'),
|
||||
},
|
||||
{
|
||||
field: 'tradesPerSecond',
|
||||
label: t('Trades per second'),
|
||||
info: t('Number of trades processed in the last second'),
|
||||
},
|
||||
{
|
||||
field: 'ordersPerSecond',
|
||||
label: t('Order per second'),
|
||||
info: t(
|
||||
'Number of orders processed in the last second. All orders derived from pegged orders and liquidity commitments count as a single order'
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
interface TxsStatsInfoProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TxsStatsInfo = ({ className }: TxsStatsInfoProps) => {
|
||||
const { data, startPolling, stopPolling } = useQuery<TxsStats>(STATS_QUERY);
|
||||
|
||||
useEffect(() => {
|
||||
startPolling(1000);
|
||||
return () => stopPolling();
|
||||
});
|
||||
|
||||
const gridStyles =
|
||||
'grid grid-rows-2 gap-4 grid-cols-2 xl:gap-8 xl:grid-rows-1 xl:grid-cols-4';
|
||||
const containerStyles = 'p-5 border rounded-lg border-zinc-500';
|
||||
|
||||
return (
|
||||
<aside className={classNames(gridStyles, containerStyles, className)}>
|
||||
{TXS_STATS_MAP.map((field) => (
|
||||
<InfoBlock
|
||||
subtitle={field.label}
|
||||
tooltipInfo={field.info}
|
||||
title={data?.statistics[field.field] || ''}
|
||||
/>
|
||||
))}
|
||||
</aside>
|
||||
);
|
||||
};
|
82
apps/explorer/src/app/hooks/use-txs-data.ts
Normal file
82
apps/explorer/src/app/hooks/use-txs-data.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
BlockExplorerTransactionResult,
|
||||
BlockExplorerTransactions,
|
||||
} from '../routes/types/block-explorer-response';
|
||||
import { DATA_SOURCES } from '../config';
|
||||
|
||||
export interface TxsStateProps {
|
||||
txsData: BlockExplorerTransactionResult[];
|
||||
hasMoreTxs: boolean;
|
||||
lastCursor: string;
|
||||
}
|
||||
|
||||
export interface IUseTxsData {
|
||||
limit?: number;
|
||||
filters?: string;
|
||||
}
|
||||
|
||||
export const getTxsDataUrl = ({ limit = 10, filters = '' }) => {
|
||||
let url = `${DATA_SOURCES.blockExplorerUrl}/transactions?limit=${limit}`;
|
||||
|
||||
if (filters) {
|
||||
url = `${url}&${filters}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const useTxsData = ({ limit = 10, filters }: IUseTxsData) => {
|
||||
const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] =
|
||||
useState<TxsStateProps>({
|
||||
txsData: [],
|
||||
hasMoreTxs: true,
|
||||
lastCursor: '',
|
||||
});
|
||||
|
||||
const url = getTxsDataUrl({ limit, filters });
|
||||
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, false);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.transactions?.length) {
|
||||
setTxsState((prev) => ({
|
||||
txsData: [...prev.txsData, ...data.transactions],
|
||||
hasMoreTxs: true,
|
||||
lastCursor:
|
||||
data.transactions[data.transactions.length - 1].cursor || '',
|
||||
}));
|
||||
}
|
||||
}, [setTxsState, data?.transactions]);
|
||||
|
||||
const loadTxs = useCallback(() => {
|
||||
return refetch({
|
||||
limit: limit,
|
||||
before: lastCursor,
|
||||
});
|
||||
}, [lastCursor, limit, refetch]);
|
||||
|
||||
const refreshTxs = useCallback(async () => {
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
lastCursor: '',
|
||||
hasMoreTxs: true,
|
||||
txsData: [],
|
||||
}));
|
||||
}, [setTxsState]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
txsData,
|
||||
hasMoreTxs,
|
||||
lastCursor,
|
||||
refreshTxs,
|
||||
loadTxs,
|
||||
};
|
||||
};
|
@ -10,7 +10,7 @@ import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { SubHeading } from '../../../components/sub-heading';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '../../../components/panel';
|
||||
import { InfoPanel } from '../../../components/info-panel';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
@ -19,7 +19,9 @@ import type {
|
||||
PartyAssetsQuery,
|
||||
PartyAssetsQueryVariables,
|
||||
} from './__generated__/PartyAssetsQuery';
|
||||
import type { BlockExplorerTransactions } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
|
||||
const PARTY_ASSETS_QUERY = gql`
|
||||
query PartyAssetsQuery($partyId: ID!) {
|
||||
@ -59,11 +61,16 @@ const PARTY_ASSETS_QUERY = gql`
|
||||
const Party = () => {
|
||||
const { party } = useParams<{ party: string }>();
|
||||
const partyId = party ? toNonHex(party) : '';
|
||||
const filters = `filters[tx.submitter]=${partyId}`;
|
||||
const { hasMoreTxs, loadTxs, error, txsData, loading } = useTxsData({
|
||||
limit: 10,
|
||||
filters,
|
||||
});
|
||||
|
||||
const {
|
||||
state: { data: partyData },
|
||||
} = useFetch<BlockExplorerTransactions>(
|
||||
`${DATA_SOURCES.blockExplorerUrl}/transactions?limit=1&filters[tx.submitter]=${partyId}`
|
||||
} = useFetch<TendermintSearchTransactionResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${partyId}'"`
|
||||
);
|
||||
|
||||
const { data } = useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(
|
||||
@ -149,6 +156,21 @@ const Party = () => {
|
||||
{accounts}
|
||||
<SubHeading>{t('Staking')}</SubHeading>
|
||||
{staking}
|
||||
<SubHeading>{t('Transactions')}</SubHeading>
|
||||
<AsyncRenderer
|
||||
loading={loading as boolean}
|
||||
error={error}
|
||||
data={txsData}
|
||||
>
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={loadTxs}
|
||||
error={error}
|
||||
className="mb-28"
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
<SubHeading>{t('JSON')}</SubHeading>
|
||||
<section data-testid="parties-json">
|
||||
<SyntaxHighlighter data={data} />
|
||||
|
@ -1,73 +1,19 @@
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { t, useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { RouteTitle } from '../../../components/route-title';
|
||||
import { BlocksRefetch } from '../../../components/blocks';
|
||||
import { TxsInfiniteList } from '../../../components/txs';
|
||||
import type {
|
||||
BlockExplorerTransactionResult,
|
||||
BlockExplorerTransactions,
|
||||
} from '../../../routes/types/block-explorer-response';
|
||||
|
||||
interface TxsStateProps {
|
||||
txsData: BlockExplorerTransactionResult[];
|
||||
hasMoreTxs: boolean;
|
||||
lastCursor: string;
|
||||
}
|
||||
import { TxsInfiniteList, TxsStatsInfo } from '../../../components/txs';
|
||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
||||
|
||||
const BE_TXS_PER_REQUEST = 100;
|
||||
|
||||
export const TxsList = () => {
|
||||
const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] =
|
||||
useState<TxsStateProps>({
|
||||
txsData: [],
|
||||
hasMoreTxs: true,
|
||||
lastCursor: '',
|
||||
});
|
||||
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(
|
||||
`${DATA_SOURCES.blockExplorerUrl}/transactions?` +
|
||||
new URLSearchParams({
|
||||
limit: BE_TXS_PER_REQUEST.toString(10),
|
||||
}),
|
||||
{},
|
||||
false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.transactions?.length) {
|
||||
setTxsState((prev) => ({
|
||||
txsData: [...prev.txsData, ...data.transactions],
|
||||
hasMoreTxs: true,
|
||||
lastCursor:
|
||||
data.transactions[data.transactions.length - 1].cursor || '',
|
||||
}));
|
||||
}
|
||||
}, [data?.transactions]);
|
||||
|
||||
const loadTxs = useCallback(() => {
|
||||
return refetch({
|
||||
limit: BE_TXS_PER_REQUEST,
|
||||
before: lastCursor,
|
||||
});
|
||||
}, [lastCursor, refetch]);
|
||||
|
||||
const refreshTxs = useCallback(async () => {
|
||||
setTxsState((prev) => ({
|
||||
...prev,
|
||||
lastCursor: '',
|
||||
hasMoreTxs: true,
|
||||
txsData: [],
|
||||
}));
|
||||
}, [setTxsState]);
|
||||
|
||||
const { hasMoreTxs, loadTxs, error, txsData, refreshTxs, loading } =
|
||||
useTxsData({ limit: BE_TXS_PER_REQUEST });
|
||||
return (
|
||||
<section>
|
||||
<section className="md:p-2 lg:p-4 xl:p-6">
|
||||
<RouteTitle>{t('Transactions')}</RouteTitle>
|
||||
<BlocksRefetch refetch={refreshTxs} />
|
||||
<TxsStatsInfo className="my-8 py-8" />
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
|
Loading…
Reference in New Issue
Block a user