diff --git a/lib/api/resources.ts b/lib/api/resources.ts index c1ddac5826..788a187a22 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -58,9 +58,16 @@ import type { } from 'types/api/token'; import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; -import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction, TransactionsResponseWatchlist } from 'types/api/transaction'; +import type { + TransactionsResponseValidated, + TransactionsResponsePending, + Transaction, + TransactionsResponseWatchlist, + TransactionsSorting, +} 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 +732,7 @@ never; export type PaginationSorting = Q extends 'tokens' ? TokensSorting : Q extends 'tokens_bridged' ? TokensSorting : +Q extends 'verified_contracts' ? VerifiedContractsSorting : +Q extends 'address_txs' ? TransactionsSorting : never; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/tx/sortTxs.ts b/lib/tx/sortTxs.ts deleted file mode 100644 index 4caf89332b..0000000000 --- a/lib/tx/sortTxs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Transaction } from 'types/api/transaction'; -import type { Sort } from 'types/client/txs-sort'; - -import compareBns from 'lib/bigint/compareBns'; - -const sortTxs = (sorting?: Sort) => (tx1: Transaction, tx2: Transaction) => { - switch (sorting) { - case 'val-desc': - return compareBns(tx1.value, tx2.value); - case 'val-asc': - return compareBns(tx2.value, tx1.value); - case 'fee-desc': - return compareBns(tx1.fee.value || 0, tx2.fee.value || 0); - case 'fee-asc': - return compareBns(tx2.fee.value || 0, tx1.fee.value || 0); - default: - return 0; - } -}; - -export default sortTxs; diff --git a/types/api/transaction.ts b/types/api/transaction.ts index e4e669bbfd..c76f2f9a19 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -117,3 +117,12 @@ export type TransactionType = 'rootstock_remasc' | 'coin_transfer' export type TxsResponse = TransactionsResponseValidated | TransactionsResponsePending | BlockTransactionsResponse; + +export interface TransactionsSorting { + sort: 'value' | 'fee'; + order: 'asc' | 'desc'; +} + +export type TransactionsSortingField = TransactionsSorting['sort']; + +export type TransactionsSortingValue = `${ TransactionsSortingField }-${ TransactionsSorting['order'] }`; diff --git a/types/api/verifiedContracts.ts b/types/api/verifiedContracts.ts new file mode 100644 index 0000000000..12d45f31fc --- /dev/null +++ b/types/api/verifiedContracts.ts @@ -0,0 +1,8 @@ +export interface VerifiedContractsSorting { + sort: 'balance' | 'txs_count'; + order: 'asc' | 'desc'; +} + +export type VerifiedContractsSortingField = VerifiedContractsSorting['sort']; + +export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }`; diff --git a/types/client/txs-sort.ts b/types/client/txs-sort.ts deleted file mode 100644 index 501b625a56..0000000000 --- a/types/client/txs-sort.ts +++ /dev/null @@ -1 +0,0 @@ -export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | ''; diff --git a/ui/address/AddressTxs.tsx b/ui/address/AddressTxs.tsx index 3dcbec39f9..cc21c355df 100644 --- a/ui/address/AddressTxs.tsx +++ b/ui/address/AddressTxs.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { SocketMessage } from 'lib/socket/types'; import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address'; import { AddressFromToFilterValues } from 'types/api/address'; -import type { Transaction } from 'types/api/transaction'; +import type { Transaction, TransactionsSortingField, TransactionsSortingValue, TransactionsSorting } from 'types/api/transaction'; import { getResourceKey } from 'lib/api/useApiQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; @@ -18,7 +18,10 @@ import { generateListStub } from 'stubs/utils'; import ActionBar from 'ui/shared/ActionBar'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import TxsContent from 'ui/txs/TxsContent'; +import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; +import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; +import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting'; +import { SORT_OPTIONS } from 'ui/txs/useTxsSort'; import AddressCsvExportLink from './AddressCsvExportLink'; import AddressTxsFilter from './AddressTxsFilter'; @@ -53,6 +56,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { const [ socketAlert, setSocketAlert ] = React.useState(''); const [ newItemsCount, setNewItemsCount ] = React.useState(0); + const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS)); const isMobile = useIsMobile(); const currentAddress = getQueryParamString(router.query.hash); @@ -63,6 +67,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { resourceName: 'address_txs', pathParams: { hash: currentAddress }, filters: { filter: filterValue }, + sorting: getSortParamsFromValue(sort), scrollRef, options: { placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: { @@ -177,7 +182,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ) } - { socketInfoAlert={ socketAlert } socketInfoNum={ newItemsCount } top={ 80 } + sorting={ sort } + setSort={ setSort } /> ); diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index 5be50b8007..a1bbb094c5 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -24,7 +24,7 @@ import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { marginBottom: 0, @@ -82,7 +82,7 @@ const BlockPageContent = () => { const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', component: }, - { id: 'txs', title: 'Transactions', component: }, + { id: 'txs', title: 'Transactions', component: }, config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ? { id: 'withdrawals', title: 'Withdrawals', component: } : null, diff --git a/ui/pages/KettleTxs.tsx b/ui/pages/KettleTxs.tsx index 0e2444dc4e..8866bced13 100644 --- a/ui/pages/KettleTxs.tsx +++ b/ui/pages/KettleTxs.tsx @@ -7,7 +7,7 @@ import { generateListStub } from 'stubs/utils'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const KettleTxs = () => { const router = useRouter(); @@ -31,7 +31,7 @@ const KettleTxs = () => { <> - diff --git a/ui/pages/Tokens.tsx b/ui/pages/Tokens.tsx index 1cac311216..2db3e0d8c7 100644 --- a/ui/pages/Tokens.tsx +++ b/ui/pages/Tokens.tsx @@ -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(q ?? ''); - const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query)); + const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS)); const [ tokenTypes, setTokenTypes ] = React.useState | undefined>(getTokenFilterValue(router.query.type)); const [ bridgeChains, setBridgeChains ] = React.useState | 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(sort), options: { placeholderData: generateListStub<'tokens'>( TOKEN_INFO_ERC_20, diff --git a/ui/pages/Transactions.tsx b/ui/pages/Transactions.tsx index 19fc842934..2d7027aa47 100644 --- a/ui/pages/Transactions.tsx +++ b/ui/pages/Transactions.tsx @@ -13,8 +13,8 @@ import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; -import TxsContent from 'ui/txs/TxsContent'; import TxsWatchlist from 'ui/txs/TxsWatchlist'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; const TAB_LIST_PROPS = { marginBottom: 0, @@ -60,12 +60,13 @@ const Transactions = () => { { id: 'validated', title: verifiedTitle, - component: }, + component: + }, { id: 'pending', title: 'Pending', component: ( - { 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(); + const [ sort, setSort ] = + React.useState(getSortValueFromQuery(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(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 = ; @@ -89,7 +92,7 @@ const VerifiedContracts = () => { ); @@ -112,15 +115,13 @@ const VerifiedContracts = () => { ); - const sortedData = data?.items.slice().sort(sortFn(sort)); - - const content = sortedData ? ( + const content = data?.items ? ( <> - + - + ) : null; diff --git a/ui/pages/ZkEvmL2TxnBatch.tsx b/ui/pages/ZkEvmL2TxnBatch.tsx index e2cb9811ff..388703bf41 100644 --- a/ui/pages/ZkEvmL2TxnBatch.tsx +++ b/ui/pages/ZkEvmL2TxnBatch.tsx @@ -14,7 +14,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; import ZkEvmL2TxnBatchDetails from 'ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails'; const ZkEvmL2TxnBatch = () => { @@ -51,7 +51,7 @@ const ZkEvmL2TxnBatch = () => { const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', component: }, - { id: 'txs', title: 'Transactions', component: }, + { id: 'txs', title: 'Transactions', component: }, ].filter(Boolean)), [ batchQuery, batchTxsQuery ]); const backLink = React.useMemo(() => { diff --git a/ui/shared/sort/getSortParamsFromValue.ts b/ui/shared/sort/getSortParamsFromValue.ts new file mode 100644 index 0000000000..a4da57565f --- /dev/null +++ b/ui/shared/sort/getSortParamsFromValue.ts @@ -0,0 +1,8 @@ +export default function getSortParamsFromValue(val?: SortValue) { + if (!val) { + return undefined; + } + + const sortingChunks = val.split('-') as [ SortField, SortOrder ]; + return { sort: sortingChunks[0], order: sortingChunks[1] }; +} diff --git a/ui/shared/sort/getSortValueFromQuery.ts b/ui/shared/sort/getSortValueFromQuery.ts new file mode 100644 index 0000000000..9e8f823c60 --- /dev/null +++ b/ui/shared/sort/getSortValueFromQuery.ts @@ -0,0 +1,14 @@ +import type { Query } from 'nextjs-routes'; + +import type { Option } from 'ui/shared/sort/Sort'; + +export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { + 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; + } +} diff --git a/ui/tokens/utils.ts b/ui/tokens/utils.ts index dd4aea8383..804271ccbc 100644 --- a/ui/tokens/utils.ts +++ b/ui/tokens/utils.ts @@ -1,7 +1,5 @@ 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).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] }; -}; diff --git a/ui/txs/TxsContent.tsx b/ui/txs/TxsContent.tsx index fb81ae48b3..101a7ed31c 100644 --- a/ui/txs/TxsContent.tsx +++ b/ui/txs/TxsContent.tsx @@ -2,17 +2,23 @@ import { Box, Show, Hide } from '@chakra-ui/react'; import React from 'react'; import type { AddressFromToFilter } from 'types/api/address'; +import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import useIsMobile from 'lib/hooks/useIsMobile'; import AddressCsvExportLink from 'ui/address/AddressCsvExportLink'; import DataListDisplay from 'ui/shared/DataListDisplay'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; +import getNextSortValue from 'ui/shared/sort/getNextSortValue'; import TxsHeaderMobile from './TxsHeaderMobile'; import TxsListItem from './TxsListItem'; import TxsTable from './TxsTable'; -import useTxsSort from './useTxsSort'; + +const SORT_SEQUENCE: Record> = { + value: [ 'value-desc', 'value-asc', undefined ], + fee: [ 'fee-desc', 'fee-asc', undefined ], +}; type Props = { // eslint-disable-next-line max-len @@ -26,12 +32,17 @@ type Props = { filterValue?: AddressFromToFilter; enableTimeIncrement?: boolean; top?: number; + items?: Array; + isPlaceholderData: boolean; + isError: boolean; + setSorting: (value: TransactionsSortingValue | undefined) => void; + sort: TransactionsSortingValue | undefined; } const TxsContent = ({ + query, filter, filterValue, - query, showBlockInfo = true, showSocketInfo = true, socketInfoAlert, @@ -39,11 +50,20 @@ const TxsContent = ({ currentAddress, enableTimeIncrement, top, + items, + isPlaceholderData, + isError, + setSorting, + sort, }: Props) => { - const { data, isPlaceholderData, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query); const isMobile = useIsMobile(); - const content = data?.items ? ( + const onSortToggle = React.useCallback((field: TransactionsSortingField) => () => { + const value = getNextSortValue(SORT_SEQUENCE, field)(sort); + setSorting(value); + }, [ sort, setSorting ]); + + const content = items ? ( <> @@ -55,7 +75,7 @@ const TxsContent = ({ isLoading={ isPlaceholderData } /> ) } - { data.items.map((tx, index) => ( + { items.map((tx, index) => ( > = [ - { title: 'Default', id: undefined }, - { title: 'Value ascending', id: 'val-asc' }, - { title: 'Value descending', id: 'val-desc' }, - { title: 'Fee ascending', id: 'fee-asc' }, - { title: 'Fee descending', id: 'fee-desc' }, -]; +// import TxsFilters from './TxsFilters'; type Props = { - sorting: TSort; - setSorting: (val: TSort | undefined) => void; + sorting: TransactionsSortingValue | undefined; + setSorting: (val: TransactionsSortingValue | undefined) => void; paginationProps: PaginationParams; className?: string; showPagination?: boolean; diff --git a/ui/txs/TxsTable.tsx b/ui/txs/TxsTable.tsx index 7af0f4c013..14b0c16573 100644 --- a/ui/txs/TxsTable.tsx +++ b/ui/txs/TxsTable.tsx @@ -2,8 +2,7 @@ import { Link, Table, Tbody, Tr, Th, Icon, Show, Hide } from '@chakra-ui/react'; import { AnimatePresence } from 'framer-motion'; import React from 'react'; -import type { Transaction } from 'types/api/transaction'; -import type { Sort } from 'types/client/txs-sort'; +import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction'; import config from 'configs/app'; import rightArrowIcon from 'icons/arrows/east.svg'; @@ -14,8 +13,8 @@ import TxsTableItem from './TxsTableItem'; type Props = { txs: Array; - sort: (field: 'val' | 'fee') => () => void; - sorting?: Sort; + sort: (field: TransactionsSortingField) => () => void; + sorting?: TransactionsSortingValue; top: number; showBlockInfo: boolean; showSocketInfo: boolean; @@ -58,9 +57,9 @@ const TxsTable = ({ { !config.UI.views.tx.hiddenFields?.value && ( - - { sorting === 'val-asc' && } - { sorting === 'val-desc' && } + + { sorting === 'value-asc' && } + { sorting === 'value-desc' && } { `Value ${ config.chain.currency.symbol }` } diff --git a/ui/txs/TxsWatchlist.tsx b/ui/txs/TxsWatchlist.tsx index 7f0567bffc..67edde53d2 100644 --- a/ui/txs/TxsWatchlist.tsx +++ b/ui/txs/TxsWatchlist.tsx @@ -2,7 +2,7 @@ import React from 'react'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; -import TxsContent from 'ui/txs/TxsContent'; +import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting'; type Props = { query: QueryWithPagesResult<'txs_watchlist'>; @@ -10,7 +10,7 @@ type Props = { const TxsWatchlist = ({ query }: Props) => { useRedirectForInvalidAuthToken(); - return ; + return ; }; export default TxsWatchlist; diff --git a/ui/txs/TxsWithAPISorting.tsx b/ui/txs/TxsWithAPISorting.tsx new file mode 100644 index 0000000000..140c8f0a99 --- /dev/null +++ b/ui/txs/TxsWithAPISorting.tsx @@ -0,0 +1,68 @@ +import React from 'react'; + +import type { AddressFromToFilter } from 'types/api/address'; +import type { TransactionsSortingValue } from 'types/api/transaction'; + +import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; +import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; + +import TxsContent from './TxsContent'; + +type Props = { + // eslint-disable-next-line max-len + query: QueryWithPagesResult<'address_txs'>; + showBlockInfo?: boolean; + showSocketInfo?: boolean; + socketInfoAlert?: string; + socketInfoNum?: number; + currentAddress?: string; + filter?: React.ReactNode; + filterValue?: AddressFromToFilter; + enableTimeIncrement?: boolean; + top?: number; + sorting: TransactionsSortingValue | undefined; + setSort: (value?: TransactionsSortingValue) => void; +} + +const TxsWithAPISorting = ({ + filter, + filterValue, + query, + showBlockInfo = true, + showSocketInfo = true, + socketInfoAlert, + socketInfoNum, + currentAddress, + enableTimeIncrement, + top, + sorting, + setSort, +}: Props) => { + + const handleSortChange = React.useCallback((value?: TransactionsSortingValue) => { + setSort(value); + query.onSortingChange(getSortParamsFromValue(value)); + }, [ setSort, query ]); + + return ( + + ); +}; + +export default TxsWithAPISorting; diff --git a/ui/txs/TxsWithFrontendSorting.tsx b/ui/txs/TxsWithFrontendSorting.tsx new file mode 100644 index 0000000000..913a7fb8a4 --- /dev/null +++ b/ui/txs/TxsWithFrontendSorting.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import type { AddressFromToFilter } from 'types/api/address'; + +import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; + +import TxsContent from './TxsContent'; +import useTxsSort from './useTxsSort'; + +type Props = { + // eslint-disable-next-line max-len + query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>; + showBlockInfo?: boolean; + showSocketInfo?: boolean; + socketInfoAlert?: string; + socketInfoNum?: number; + currentAddress?: string; + filter?: React.ReactNode; + filterValue?: AddressFromToFilter; + enableTimeIncrement?: boolean; + top?: number; +} + +const TxsWithFrontendSorting = ({ + filter, + filterValue, + query, + showBlockInfo = true, + showSocketInfo = true, + socketInfoAlert, + socketInfoNum, + currentAddress, + enableTimeIncrement, + top, +}: Props) => { + const { data, isPlaceholderData, isError, setSortByValue, sorting } = useTxsSort(query); + + return ( + + ); +}; + +export default TxsWithFrontendSorting; diff --git a/ui/txs/useTxsSort.tsx b/ui/txs/useTxsSort.tsx index 3a77967507..bee6eefddd 100644 --- a/ui/txs/useTxsSort.tsx +++ b/ui/txs/useTxsSort.tsx @@ -1,78 +1,71 @@ import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; -import type { TxsResponse } from 'types/api/transaction'; -import type { Sort } from 'types/client/txs-sort'; +import type { Transaction, TransactionsSortingValue, TxsResponse } from 'types/api/transaction'; import type { ResourceError } from 'lib/api/resources'; +import compareBns from 'lib/bigint/compareBns'; import * as cookies from 'lib/cookies'; -import sortTxs from 'lib/tx/sortTxs'; +import type { Option } from 'ui/shared/sort/Sort'; + +export const SORT_OPTIONS: Array> = [ + { title: 'Default', id: undefined }, + { title: 'Value ascending', id: 'value-asc' }, + { title: 'Value descending', id: 'value-desc' }, + { title: 'Fee ascending', id: 'fee-asc' }, + { title: 'Fee descending', id: 'fee-desc' }, +]; + +type SortingValue = TransactionsSortingValue | undefined; type HookResult = UseQueryResult> & { - sorting: Sort; - setSortByField: (field: 'val' | 'fee') => () => void; - setSortByValue: (value: Sort | undefined) => void; + sorting: SortingValue; + setSortByValue: (value: SortingValue) => void; } +const sortTxs = (sorting: SortingValue) => (tx1: Transaction, tx2: Transaction) => { + switch (sorting) { + case 'value-desc': + return compareBns(tx1.value, tx2.value); + case 'value-asc': + return compareBns(tx2.value, tx1.value); + case 'fee-desc': + return compareBns(tx1.fee.value || 0, tx2.fee.value || 0); + case 'fee-asc': + return compareBns(tx2.fee.value || 0, tx1.fee.value || 0); + default: + return 0; + } +}; + export default function useTxsSort( queryResult: UseQueryResult>, ): HookResult { - const [ sorting, setSorting ] = React.useState(cookies.get(cookies.NAMES.TXS_SORT) as Sort); - - const setSortByField = React.useCallback((field: 'val' | 'fee') => () => { - if (queryResult.isPlaceholderData) { - return; - } - - setSorting((prevVal) => { - let newVal: Sort = ''; - if (field === 'val') { - if (prevVal === 'val-asc') { - newVal = ''; - } else if (prevVal === 'val-desc') { - newVal = 'val-asc'; - } else { - newVal = 'val-desc'; - } - } - if (field === 'fee') { - if (prevVal === 'fee-asc') { - newVal = ''; - } else if (prevVal === 'fee-desc') { - newVal = 'fee-asc'; - } else { - newVal = 'fee-desc'; - } - } - cookies.set(cookies.NAMES.TXS_SORT, newVal); - return newVal; - }); - }, [ queryResult.isPlaceholderData ]); + const [ sorting, setSorting ] = React.useState(cookies.get(cookies.NAMES.TXS_SORT) as SortingValue); - const setSortByValue = React.useCallback((value: Sort | undefined) => { - setSorting((prevVal: Sort) => { - let newVal: Sort = ''; + const setSortByValue = React.useCallback((value: SortingValue) => { + setSorting((prevVal: SortingValue) => { + let newVal: SortingValue = undefined; if (value !== prevVal) { - newVal = value as Sort; + newVal = value as SortingValue; } - cookies.set(cookies.NAMES.TXS_SORT, newVal); + cookies.set(cookies.NAMES.TXS_SORT, newVal ? newVal : ''); return newVal; }); }, []); return React.useMemo(() => { if (queryResult.isError || queryResult.isPending) { - return { ...queryResult, setSortByField, setSortByValue, sorting }; + return { ...queryResult, setSortByValue, sorting }; } return { ...queryResult, data: { ...queryResult.data, items: queryResult.data.items.slice().sort(sortTxs(sorting)) }, - setSortByField, setSortByValue, sorting, }; - }, [ queryResult, setSortByField, setSortByValue, sorting ]); + }, [ queryResult, setSortByValue, sorting ]); } diff --git a/ui/verifiedContracts/VerifiedContractsTable.tsx b/ui/verifiedContracts/VerifiedContractsTable.tsx index e485616dd4..81694f193c 100644 --- a/ui/verifiedContracts/VerifiedContractsTable.tsx +++ b/ui/verifiedContracts/VerifiedContractsTable.tsx @@ -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; - 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(SORT_SEQUENCE, field)(sort); + setSorting(value); + }, [ sort, setSorting ]); return ( @@ -32,8 +39,8 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) diff --git a/ui/verifiedContracts/utils.ts b/ui/verifiedContracts/utils.ts index 71325c67be..caf609899f 100644 --- a/ui/verifiedContracts/utils.ts +++ b/ui/verifiedContracts/utils.ts @@ -1,42 +1,16 @@ -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> = [ +export const SORT_OPTIONS: Array> = [ { 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> = { +export const SORT_SEQUENCE: Record> = { balance: [ 'balance-desc', 'balance-asc', undefined ], - txs: [ 'txs-desc', 'txs-asc', undefined ], -}; - -export const getNextSortValue = (getNextSortValueShared).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 ], };
- - { sort?.includes('txs') && } + + { sort?.includes('txs_count') && } Txs