Commit 473d2b27 authored by tom's avatar tom

basic pagination for blocks

parent d6fd6c2c
...@@ -4,23 +4,32 @@ import { useRouter } from 'next/router'; ...@@ -4,23 +4,32 @@ import { useRouter } from 'next/router';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll } from 'react-scroll';
import type { TransactionsResponse } from 'types/api/transaction'; import type { BlockFilters } from 'types/api/block';
import type { PaginationParams } from 'types/api/pagination';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
import type { QueryKeys } from 'types/client/queries'; import type { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
const PAGINATION_FIELDS = [ 'block_number', 'index', 'items_count' ]; const PAGINATION_FIELDS: Array<keyof PaginationParams> = [ 'block_number', 'index', 'items_count' ];
export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, filters?: TTxsFilters) { interface ResponseWithPagination {
next_page_params: PaginationParams | null;
}
export default function useQueryWithPages<Response extends ResponseWithPagination>(
apiPath: string,
queryName: QueryKeys,
filters?: TTxsFilters | BlockFilters,
) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const [ page, setPage ] = React.useState(1); const [ page, setPage ] = React.useState(1);
const currPageParams = pick(router.query, PAGINATION_FIELDS); const currPageParams = pick(router.query, PAGINATION_FIELDS);
const [ pageParams, setPageParams ] = React.useState<Array<Partial<TransactionsResponse['next_page_params']>>>([ {} ]); const [ pageParams, setPageParams ] = React.useState<Array<Partial<PaginationParams>>>([ {} ]);
const fetch = useFetch(); const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, TransactionsResponse>( const queryResult = useQuery<unknown, unknown, Response>(
[ queryName, { page, filters } ], [ queryName, { page, filters } ],
async() => { async() => {
const params: Array<string> = []; const params: Array<string> = [];
...@@ -37,6 +46,7 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -37,6 +46,7 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
}, },
{ staleTime: Infinity }, { staleTime: Infinity },
); );
const { data } = queryResult;
const onNextPageClick = useCallback(() => { const onNextPageClick = useCallback(() => {
if (!data?.next_page_params) { if (!data?.next_page_params) {
...@@ -88,6 +98,7 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -88,6 +98,7 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
}, [ router, queryClient ]); }, [ router, queryClient ]);
const hasPaginationParams = Object.keys(currPageParams).length > 0; const hasPaginationParams = Object.keys(currPageParams).length > 0;
const nextPageParams = data?.next_page_params;
const pagination = { const pagination = {
page, page,
...@@ -95,7 +106,8 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -95,7 +106,8 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
onPrevPageClick, onPrevPageClick,
hasPaginationParams, hasPaginationParams,
resetPage, resetPage,
hasNextPage: nextPageParams ? Object.keys(nextPageParams).length > 0 : false,
}; };
return { data, isError, isLoading, pagination }; return { ...queryResult, pagination };
} }
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import getSearchParams from 'lib/api/getSearchParams';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => { const getUrl = (req: NextApiRequest) => {
return `/v2/blocks${ req.query.type ? `?type=${ req.query.type }` : '' }`; const searchParamsStr = getSearchParams(req);
return `/v2/blocks${ searchParamsStr ? '?' + searchParamsStr : '' }`;
}; };
const requestHandler = handler(getUrl, [ 'GET' ]); const requestHandler = handler(getUrl, [ 'GET' ]);
......
import type { AddressParam } from 'types/api/addressParams'; import type { AddressParam } from 'types/api/addressParams';
import type { PaginationParams } from 'types/api/pagination';
import type { Reward } from 'types/api/reward'; import type { Reward } from 'types/api/reward';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
...@@ -33,22 +34,19 @@ export interface Block { ...@@ -33,22 +34,19 @@ export interface Block {
export interface BlocksResponse { export interface BlocksResponse {
items: Array<Block>; items: Array<Block>;
next_page_params: { next_page_params: PaginationParams | null;
block_number: number;
items_count: number;
} | null;
} }
export interface BlockTransactionsResponse { export interface BlockTransactionsResponse {
items: Array<Transaction>; items: Array<Transaction>;
next_page_params: { next_page_params: PaginationParams | null;
block_number: number;
index: number;
items_count: number;
} | null;
} }
export interface NewBlockSocketResponse { export interface NewBlockSocketResponse {
average_block_time: string; average_block_time: string;
block: Block; block: Block;
} }
export interface BlockFilters {
type?: BlockType;
}
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { PaginationParams } from './pagination';
export type TxInternalsType = 'call' | 'delegatecall' | 'staticcall' | 'create' | 'create2' | 'selfdestruct' | 'reward' export type TxInternalsType = 'call' | 'delegatecall' | 'staticcall' | 'create' | 'create2' | 'selfdestruct' | 'reward'
...@@ -19,10 +20,7 @@ export interface InternalTransaction { ...@@ -19,10 +20,7 @@ export interface InternalTransaction {
export interface InternalTransactionsResponse { export interface InternalTransactionsResponse {
items: Array<InternalTransaction>; items: Array<InternalTransaction>;
next_page_params: { next_page_params: PaginationParams & {
block_number: number;
index: number;
items_count: number;
transaction_hash: string; transaction_hash: string;
transaction_index: number; transaction_index: number;
}; };
......
export interface PaginationParams {
block_number: number;
index?: number;
items_count: number;
}
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { DecodedInput } from './decodedInput'; import type { DecodedInput } from './decodedInput';
import type { Fee } from './fee'; import type { Fee } from './fee';
import type { PaginationParams } from './pagination';
import type { TokenTransfer } from './tokenTransfer'; import type { TokenTransfer } from './tokenTransfer';
export type TransactionRevertReason = { export type TransactionRevertReason = {
...@@ -45,11 +46,7 @@ export interface Transaction { ...@@ -45,11 +46,7 @@ export interface Transaction {
export interface TransactionsResponse { export interface TransactionsResponse {
items: Array<Transaction>; items: Array<Transaction>;
next_page_params: { next_page_params: PaginationParams | null;
block_number: number;
index: number;
items_count: number;
} | null;
} }
export type TransactionType = 'token_transfer' | 'contract_creation' | 'contract_call' | 'token_creation' | 'coin_transfer' export type TransactionType = 'token_transfer' | 'contract_creation' | 'contract_call' | 'token_creation' | 'coin_transfer'
export enum QueryKeys { export enum QueryKeys {
addressTags = 'address-tags', addressTags = 'address-tags',
apiKeys = 'api-keys', apiKeys = 'api-keys',
block = 'block',
blocks = 'blocks',
customAbis = 'custom-abis', customAbis = 'custom-abis',
profile = 'profile', profile = 'profile',
publicTags = 'public-tags', publicTags = 'public-tags',
......
...@@ -7,4 +7,6 @@ export enum QueryKeys { ...@@ -7,4 +7,6 @@ export enum QueryKeys {
txLog = 'tx-log', txLog = 'tx-log',
txRawTrace = 'tx-raw-trace', txRawTrace = 'tx-raw-trace',
blockTxs = 'block-transactions', blockTxs = 'block-transactions',
block = 'block',
blocks = 'blocks',
} }
...@@ -7,7 +7,7 @@ import React from 'react'; ...@@ -7,7 +7,7 @@ import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
import { QueryKeys } from 'types/client/accountQueries'; import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import clockIcon from 'icons/clock.svg'; import clockIcon from 'icons/clock.svg';
......
import { Text, Show, Alert, Skeleton } from '@chakra-ui/react'; import { Text, Show, Alert, Skeleton } from '@chakra-ui/react';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { SocketMessage } from 'lib/socket/types';
import type { BlockType, BlocksResponse } from 'types/api/block'; import type { BlockType, BlocksResponse } from 'types/api/block';
import { QueryKeys } from 'types/client/accountQueries'; import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage'; import useSocketMessage from 'lib/socket/useSocketMessage';
import BlocksList from 'ui/blocks/BlocksList'; import BlocksList from 'ui/blocks/BlocksList';
...@@ -22,17 +22,13 @@ interface Props { ...@@ -22,17 +22,13 @@ interface Props {
} }
const BlocksContent = ({ type }: Props) => { const BlocksContent = ({ type }: Props) => {
const fetch = useFetch();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [ socketAlert, setSocketAlert ] = React.useState(''); const [ socketAlert, setSocketAlert ] = React.useState('');
const { data, isLoading, isError } = useQuery<unknown, unknown, BlocksResponse>( const { data, isLoading, isError, pagination } = useQueryWithPages<BlocksResponse>('/node-api/blocks', QueryKeys.blocks, { type });
[ QueryKeys.blocks, type ],
async() => await fetch(`/node-api/blocks${ type ? `?type=${ type }` : '' }`),
);
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => { const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
queryClient.setQueryData([ QueryKeys.blocks, type ], (prevData: BlocksResponse | undefined) => { queryClient.setQueryData([ QueryKeys.blocks, { page: pagination.page, filters: { type } } ], (prevData: BlocksResponse | undefined) => {
const shouldAddToList = !type || type === payload.block.type; const shouldAddToList = !type || type === payload.block.type;
if (!prevData) { if (!prevData) {
...@@ -43,7 +39,7 @@ const BlocksContent = ({ type }: Props) => { ...@@ -43,7 +39,7 @@ const BlocksContent = ({ type }: Props) => {
} }
return shouldAddToList ? { ...prevData, items: [ payload.block, ...prevData.items ] } : prevData; return shouldAddToList ? { ...prevData, items: [ payload.block, ...prevData.items ] } : prevData;
}); });
}, [ queryClient, type ]); }, [ pagination.page, queryClient, type ]);
const handleSocketClose = React.useCallback(() => { const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please click here to load new blocks.'); setSocketAlert('Connection is lost. Please click here to load new blocks.');
...@@ -57,7 +53,7 @@ const BlocksContent = ({ type }: Props) => { ...@@ -57,7 +53,7 @@ const BlocksContent = ({ type }: Props) => {
topic: 'blocks:new_block', topic: 'blocks:new_block',
onSocketClose: handleSocketClose, onSocketClose: handleSocketClose,
onSocketError: handleSocketError, onSocketError: handleSocketError,
isDisabled: isLoading || isError, isDisabled: isLoading || isError || pagination.page !== 1,
}); });
useSocketMessage({ useSocketMessage({
channel, channel,
...@@ -91,8 +87,7 @@ const BlocksContent = ({ type }: Props) => { ...@@ -91,8 +87,7 @@ const BlocksContent = ({ type }: Props) => {
<> <>
<Text as="span">Total of { data.items[0].height.toLocaleString() } blocks</Text> <Text as="span">Total of { data.items[0].height.toLocaleString() } blocks</Text>
<ActionBar> <ActionBar>
{ /* eslint-disable-next-line react/jsx-no-bind */ } <Pagination ml="auto" { ...pagination }/>
<Pagination ml="auto" page={ 1 } onNextPageClick={ () => {} } onPrevPageClick={ () => {} } resetPage={ () => {} } hasNextPage/>
</ActionBar> </ActionBar>
{ socketAlert && <Alert status="warning" mt={ 8 } as="a" href={ window.document.location.href }>{ socketAlert }</Alert> } { socketAlert && <Alert status="warning" mt={ 8 } as="a" href={ window.document.location.href }>{ socketAlert }</Alert> }
<Show below="lg" key="content-mobile"><BlocksList data={ data.items }/></Show> <Show below="lg" key="content-mobile"><BlocksList data={ data.items }/></Show>
......
import { Alert, Box, Show } from '@chakra-ui/react'; import { Alert, Box, Show } from '@chakra-ui/react';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import type { TransactionsResponse } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
import type { QueryKeys } from 'types/client/queries'; import type { QueryKeys } from 'types/client/queries';
import type { Sort } from 'types/client/txs-sort'; import type { Sort } from 'types/client/txs-sort';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import TxsHeader from './TxsHeader'; import TxsHeader from './TxsHeader';
import TxsSkeletonDesktop from './TxsSkeletonDesktop'; import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsSkeletonMobile from './TxsSkeletonMobile'; import TxsSkeletonMobile from './TxsSkeletonMobile';
import TxsWithSort from './TxsWithSort'; import TxsWithSort from './TxsWithSort';
import useQueryWithPages from './useQueryWithPages';
type Props = { type Props = {
queryName: QueryKeys; queryName: QueryKeys;
...@@ -61,7 +62,7 @@ const TxsContent = ({ ...@@ -61,7 +62,7 @@ const TxsContent = ({
isLoading, isLoading,
isError, isError,
pagination, pagination,
} = useQueryWithPages(apiPath, queryName, stateFilter && { filter: stateFilter }); } = useQueryWithPages<TransactionsResponse>(apiPath, queryName, stateFilter && { filter: stateFilter });
// } = useQueryWithPages({ ...filters, filter: stateFilter, apiPath }); // } = useQueryWithPages({ ...filters, filter: stateFilter, apiPath });
if (isError) { if (isError) {
...@@ -85,15 +86,10 @@ const TxsContent = ({ ...@@ -85,15 +86,10 @@ const TxsContent = ({
content = <TxsWithSort txs={ txs } sorting={ sorting } sort={ sort }/>; content = <TxsWithSort txs={ txs } sorting={ sorting } sort={ sort }/>;
} }
const paginationProps = {
...pagination,
hasNextPage: data?.next_page_params !== undefined && data?.next_page_params !== null && Object.keys(data?.next_page_params).length > 0,
};
return ( return (
<> <>
{ showDescription && <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> }
<TxsHeader sorting={ sorting } setSorting={ setSorting } paginationProps={ paginationProps }/> <TxsHeader sorting={ sorting } setSorting={ setSorting } paginationProps={ pagination }/>
{ content } { content }
</> </>
); );
......
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