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