Commit 3a2be4be authored by isstuev's avatar isstuev

verified contracts api sorting

parent 2d7ce289
......@@ -61,6 +61,7 @@ import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/toke
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction, TransactionsResponseWatchlist } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches';
......@@ -725,5 +726,6 @@ never;
export type PaginationSorting<Q extends PaginatedResources> =
Q extends 'tokens' ? TokensSorting :
Q extends 'tokens_bridged' ? TokensSorting :
Q extends 'verified_contracts' ? VerifiedContractsSorting :
never;
/* eslint-enable @typescript-eslint/indent */
export interface VerifiedContractsSorting {
sort: 'balance' | 'txs_count';
order: 'asc' | 'desc';
}
export type VerifiedContractsSortingField = VerifiedContractsSorting['sort'];
export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }`;
......@@ -3,7 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react';
import type { TokenType } from 'types/api/token';
import type { TokensSortingValue } from 'types/api/tokens';
import type { TokensSortingValue, TokensSortingField, TokensSorting } from 'types/api/tokens';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import config from 'configs/app';
......@@ -16,11 +16,13 @@ import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TokensList from 'ui/tokens/Tokens';
import TokensActionBar from 'ui/tokens/TokensActionBar';
import TokensBridgedChainsFilter from 'ui/tokens/TokensBridgedChainsFilter';
import { getSortParamsFromValue, getSortValueFromQuery, getTokenFilterValue, getBridgedChainsFilterValue } from 'ui/tokens/utils';
import { SORT_OPTIONS, getTokenFilterValue, getBridgedChainsFilterValue } from 'ui/tokens/utils';
const TAB_LIST_PROPS = {
marginBottom: 0,
......@@ -44,7 +46,7 @@ const Tokens = () => {
const q = getQueryParamString(router.query.q);
const [ searchTerm, setSearchTerm ] = React.useState<string>(q ?? '');
const [ sort, setSort ] = React.useState<TokensSortingValue | undefined>(getSortValueFromQuery(router.query));
const [ sort, setSort ] = React.useState<TokensSortingValue | undefined>(getSortValueFromQuery<TokensSortingValue>(router.query, SORT_OPTIONS));
const [ tokenTypes, setTokenTypes ] = React.useState<Array<TokenType> | undefined>(getTokenFilterValue(router.query.type));
const [ bridgeChains, setBridgeChains ] = React.useState<Array<string> | undefined>(getBridgedChainsFilterValue(router.query.chain_ids));
......@@ -53,7 +55,7 @@ const Tokens = () => {
const tokensQuery = useQueryWithPages({
resourceName: tab === 'bridged' ? 'tokens_bridged' : 'tokens',
filters: tab === 'bridged' ? { q: debouncedSearchTerm, chain_ids: bridgeChains } : { q: debouncedSearchTerm, type: tokenTypes },
sorting: getSortParamsFromValue(sort),
sorting: getSortParamsFromValue<TokensSortingValue, TokensSortingField, TokensSorting['order']>(sort),
options: {
placeholderData: generateListStub<'tokens'>(
TOKEN_INFO_ERC_20,
......
......@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react';
import type { VerifiedContractsFilters } from 'types/api/contracts';
import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts';
import useDebounce from 'lib/hooks/useDebounce';
import useIsMobile from 'lib/hooks/useIsMobile';
......@@ -16,9 +17,10 @@ import FilterInput from 'ui/shared/filters/FilterInput';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort';
import type { SortField, Sort as TSort } from 'ui/verifiedContracts/utils';
import { SORT_OPTIONS, sortFn, getNextSortValue } from 'ui/verifiedContracts/utils';
import { SORT_OPTIONS } from 'ui/verifiedContracts/utils';
import VerifiedContractsCounters from 'ui/verifiedContracts/VerifiedContractsCounters';
import VerifiedContractsFilter from 'ui/verifiedContracts/VerifiedContractsFilter';
import VerifiedContractsList from 'ui/verifiedContracts/VerifiedContractsList';
......@@ -28,15 +30,17 @@ const VerifiedContracts = () => {
const router = useRouter();
const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.q) || undefined);
const [ type, setType ] = React.useState(getQueryParamString(router.query.filter) as VerifiedContractsFilters['filter'] || undefined);
const [ sort, setSort ] = React.useState<TSort>();
const [ sort, setSort ] =
React.useState<VerifiedContractsSortingValue | undefined>(getSortValueFromQuery<VerifiedContractsSortingValue>(router.query, SORT_OPTIONS));
const debouncedSearchTerm = useDebounce(searchTerm || '', 300);
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({
const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({
resourceName: 'verified_contracts',
filters: { q: debouncedSearchTerm, filter: type },
sorting: getSortParamsFromValue<VerifiedContractsSortingValue, VerifiedContractsSortingField, VerifiedContractsSorting['order']>(sort),
options: {
placeholderData: generateListStub<'verified_contracts'>(
VERIFIED_CONTRACT_INFO,
......@@ -67,11 +71,10 @@ const VerifiedContracts = () => {
setType(filter);
}, [ debouncedSearchTerm, onFilterChange ]);
const handleSortToggle = React.useCallback((field: SortField) => {
return () => {
setSort(getNextSortValue(field));
};
}, []);
const handleSortChange = React.useCallback((value?: VerifiedContractsSortingValue) => {
setSort(value);
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const typeFilter = <VerifiedContractsFilter onChange={ handleTypeChange } defaultValue={ type } isActive={ Boolean(type) }/>;
......@@ -89,7 +92,7 @@ const VerifiedContracts = () => {
<Sort
options={ SORT_OPTIONS }
sort={ sort }
setSort={ setSort }
setSort={ handleSortChange }
/>
);
......@@ -112,15 +115,13 @@ const VerifiedContracts = () => {
</>
);
const sortedData = data?.items.slice().sort(sortFn(sort));
const content = sortedData ? (
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
<VerifiedContractsList data={ sortedData } isLoading={ isPlaceholderData }/>
<VerifiedContractsList data={ data.items } isLoading={ isPlaceholderData }/>
</Show>
<Hide below="lg" ssr={ false }>
<VerifiedContractsTable data={ sortedData } sort={ sort } onSortToggle={ handleSortToggle } isLoading={ isPlaceholderData }/>
<VerifiedContractsTable data={ data.items } sort={ sort } setSorting={ handleSortChange } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
......
export default function getSortParamsFromValue<SortValue extends string, SortField extends string, SortOrder extends string>(val?: SortValue) {
if (!val) {
return undefined;
}
const sortingChunks = val.split('-') as [ SortField, SortOrder ];
return { sort: sortingChunks[0], order: sortingChunks[1] };
}
import type { Query } from 'nextjs-routes';
import type { Option } from 'ui/shared/sort/Sort';
export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<Option<SortValue>>) {
if (!query.sort || !query.order) {
return undefined;
}
const str = query.sort + '-' + query.order;
if (sortOptions.map(option => option.id).includes(str as SortValue)) {
return str as SortValue;
}
}
import type { TokenType } from 'types/api/token';
import type { TokensSortingField, TokensSortingValue, TokensSorting } from 'types/api/tokens';
import type { Query } from 'nextjs-routes';
import type { TokensSortingValue } from 'types/api/tokens';
import config from 'configs/app';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
......@@ -29,22 +27,3 @@ const bridgedTokensChainIds = (() => {
return feature.chains.map(chain => chain.id);
})();
export const getBridgedChainsFilterValue = (getFilterValuesFromQuery<string>).bind(null, bridgedTokensChainIds);
export const getSortValueFromQuery = (query: Query): TokensSortingValue | undefined => {
if (!query.sort || !query.order) {
return undefined;
}
const str = query.sort + '-' + query.order;
if (SORT_OPTIONS.map(option => option.id).includes(str)) {
return str as TokensSortingValue;
}
};
export const getSortParamsFromValue = (val?: TokensSortingValue): TokensSorting | undefined => {
if (!val) {
return undefined;
}
const sortingChunks = val.split('-') as [ TokensSortingField, TokensSorting['order'] ];
return { sort: sortingChunks[0], order: sortingChunks[1] };
};
......@@ -2,23 +2,30 @@ import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react';
import React from 'react';
import type { VerifiedContract } from 'types/api/contracts';
import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts';
import config from 'configs/app';
import arrowIcon from 'icons/arrows/east.svg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky';
import { SORT_SEQUENCE } from 'ui/verifiedContracts/utils';
import type { Sort, SortField } from './utils';
import VerifiedContractsTableItem from './VerifiedContractsTableItem';
interface Props {
data: Array<VerifiedContract>;
sort: Sort | undefined;
onSortToggle: (field: SortField) => () => void;
sort: VerifiedContractsSortingValue | undefined;
setSorting: (val: VerifiedContractsSortingValue | undefined) => void;
isLoading?: boolean;
}
const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc' as VerifiedContractsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: VerifiedContractsSortingField) => () => {
const value = getNextSortValue<VerifiedContractsSortingField, VerifiedContractsSortingValue>(SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm">
......@@ -32,8 +39,8 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props)
</Link>
</Th>
<Th width="130px" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('txs') } columnGap={ 1 }>
{ sort?.includes('txs') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('txs_count') } columnGap={ 1 }>
{ sort?.includes('txs_count') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Txs
</Link>
</Th>
......
import type { VerifiedContract } from 'types/api/contracts';
import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts';
import compareBns from 'lib/bigint/compareBns';
import { default as getNextSortValueShared } from 'ui/shared/sort/getNextSortValue';
import type { Option } from 'ui/shared/sort/Sort';
export type SortField = 'balance' | 'txs';
export type Sort = `${ SortField }-asc` | `${ SortField }-desc`;
export const SORT_OPTIONS: Array<Option<Sort>> = [
export const SORT_OPTIONS: Array<Option<VerifiedContractsSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Balance descending', id: 'balance-desc' },
{ title: 'Balance ascending', id: 'balance-asc' },
{ title: 'Txs count descending', id: 'txs-desc' },
{ title: 'Txs count ascending', id: 'txs-asc' },
{ title: 'Txs count descending', id: 'txs_count-desc' },
{ title: 'Txs count ascending', id: 'txs_count-asc' },
];
const SORT_SEQUENCE: Record<SortField, Array<Sort | undefined>> = {
export const SORT_SEQUENCE: Record<VerifiedContractsSortingField, Array<VerifiedContractsSortingValue | undefined>> = {
balance: [ 'balance-desc', 'balance-asc', undefined ],
txs: [ 'txs-desc', 'txs-asc', undefined ],
};
export const getNextSortValue = (getNextSortValueShared<SortField, Sort>).bind(undefined, SORT_SEQUENCE);
export const sortFn = (sort: Sort | undefined) => (a: VerifiedContract, b: VerifiedContract) => {
switch (sort) {
case 'balance-asc':
case 'balance-desc': {
const result = compareBns(b.coin_balance, a.coin_balance) * (sort.includes('desc') ? 1 : -1);
return a.coin_balance === b.coin_balance ? 0 : result;
}
case 'txs-asc':
case 'txs-desc': {
const result = ((a.tx_count || 0) > (b.tx_count || 0) ? -1 : 1) * (sort.includes('desc') ? 1 : -1);
return a.tx_count === b.tx_count ? 0 : result;
}
default:
return 0;
}
txs_count: [ 'txs_count-desc', 'txs_count-asc', undefined ],
};
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