diff --git a/pages/contracts.tsx b/pages/contracts.tsx index c70155c7a..60c31cc6d 100644 --- a/pages/contracts.tsx +++ b/pages/contracts.tsx @@ -1,4 +1,5 @@ import type { GetStaticProps } from 'next' +import { useEffect } from 'react' import { useQuery } from 'react-query' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' @@ -10,21 +11,74 @@ import { gql } from 'graphql-request' import PageTitle from 'components/PageTitle' import SubpageHead from 'components/SubpageHead' import Address from 'components/TruncatedAddress' -import Pagination from 'components/Pagination' +import Pagination from 'components/SimplePagination' import Table from 'components/Table' import Amount from 'components/Amount' +import SortIcon from 'assets/icons/sort.svg' import { SIZES } from 'components/PageSize' -import { fetchContractList, PCKB_UDT_INFO, client, GraphQLSchema } from 'utils' +import { + PCKB_UDT_INFO, + client, + GraphQLSchema, + handleSorterArrayAboutPath, + handleSorterArrayInOrder, + sorterType, +} from 'utils' +import styles from './index.module.scss' -interface ContractListProps { +interface Variables { + before: string | null + after: string | null + limit: number + sorter: SmartContractsSorterInput[] | [] +} +type SmartContractsSorterInput = { + sort_type: 'ASC' | 'DESC' + sort_value: 'EX_TX_COUNT' | 'ID' | 'NAME' | 'CKB_BALANCE' +} +enum SortTypesEnum { + tx_count_sort = 'tx_count_sort', + balance_sort = 'balance_sort', +} +enum SmartContractsSorterValueEnum { + tx_count_sort = 'EX_TX_COUNT', + balance_sort = 'CKB_BALANCE', +} +type ContractListProps = { smart_contracts: { + entries: Array<{ + name: string + id: string + ckb_balance: string + compiler_file_format: string + compiler_version: string + deployment_tx_hash: string + other_info: string | null + account: { + eth_address: string + transaction_count: number + } + }> metadata: GraphQLSchema.PageMetadata } } const contractListQuery = gql` - query { - smart_contracts(input: {}) { + query ($before: String, $after: String, $limit: Int, $sorter: SmartContractsSorterInput) { + smart_contracts(input: { before: $before, after: $after, limit: $limit, sorter: $sorter }) { + entries { + name + id + ckb_balance + compiler_file_format + compiler_version + deployment_tx_hash + other_info + account { + eth_address + transaction_count + } + } metadata { total_count after @@ -34,11 +88,12 @@ const contractListQuery = gql` } ` -const fetchList = () => +const fetchList = (variables: Variables): Promise => client - .request(contractListQuery) + .request(contractListQuery, variables) .then(data => data.smart_contracts) .catch((): ContractListProps['smart_contracts'] => ({ + entries: [], metadata: { total_count: 0, before: null, @@ -47,21 +102,69 @@ const fetchList = () => })) const ContractList = () => { + const theme = useTheme() + const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const [t] = useTranslation(['list', 'common']) + const title = t('contract_list_title') + const { - query: { page = '1', page_size = SIZES[2] }, + push, + asPath, + query: { before = null, after = null, page_size = SIZES[1], tx_count_sort, balance_sort, ...restQuery }, } = useRouter() - const theme = useTheme() - const isMobile = useMediaQuery(theme.breakpoints.down('sm')) - const title = t('contract_list_title') + const sorters = ['tx_count_sort', 'balance_sort'] + + const DEFAULT_SORTERS: sorterType[] = [ + { type: 'tx_count_sort', order: 'ASC' }, + { type: 'balance_sort', order: 'ASC' }, + ] + + const sorterArrayFromPath = handleSorterArrayAboutPath(asPath, sorters) + + // get a sorter array to query listdata from server + const sorterArrayForQuery = handleSorterArrayAboutPath(asPath, sorters, SmartContractsSorterValueEnum) + + const handleUrlForPush = (clickedSorter: sorterType = null) => { + const searchParams = new URLSearchParams({ + ...restQuery, + page_size: page_size as string, + }) + + const orderedSorter = handleSorterArrayInOrder(clickedSorter ? sorterArrayFromPath : DEFAULT_SORTERS, clickedSorter) + for (const item of orderedSorter) { + searchParams.append(item.type, item.order) + } + + return `${asPath.split('?')[0] ?? ''}?${searchParams}` + } + + useEffect(() => { + if (!sorterArrayFromPath.length) { + push(handleUrlForPush()) + } + }, [sorterArrayFromPath]) + const { isLoading, data } = useQuery( - ['contracts', page, page_size], - () => fetchContractList({ page: page as string, page_size: page_size as string }), - { refetchInterval: 10000 }, + ['contract-list', before, after, page_size, tx_count_sort, balance_sort], + () => + fetchList({ + before: before as string, + after: after as string, + limit: Number.isNaN(+page_size) ? +SIZES[1] : +page_size, + sorter: sorterArrayForQuery, + }), + { + refetchInterval: 10000, + }, ) - const { data: list } = useQuery(['contract-list'], () => fetchList()) + const handleSorterClick = (e: React.MouseEvent, type) => { + const { + dataset: { order }, + } = e.currentTarget + push(handleUrlForPush({ type, order: order === 'DESC' ? 'ASC' : 'DESC' })) + } return ( <> @@ -99,7 +202,7 @@ const ContractList = () => { {!isLoading ? ( {t(`n_kinds_in_total`, { - number: list?.metadata.total_count ?? '-', + number: data?.metadata.total_count ?? '-', })} ) : ( @@ -118,8 +221,20 @@ const ContractList = () => { {t(`balance`)} {`(${PCKB_UDT_INFO.symbol})`} + handleSorterClick(e, SortTypesEnum.balance_sort)} + data-order={balance_sort} + className={styles.sorter} + /> + + + {t(`tx_count`)} + handleSorterClick(e, SortTypesEnum.tx_count_sort)} + data-order={tx_count_sort} + className={styles.sorter} + /> - {t(`tx_count`)} @@ -131,22 +246,27 @@ const ContractList = () => { )) - ) : data.meta.totalPage ? ( - data.contracts.map(c => ( - + ) : data.metadata.total_count ? ( + data.entries.map(c => ( + -
+
{c.name} - - {c.compiler.fileFormat?.split(' ')[0] ?? '-'} + + {c?.compiler_file_format?.split(' ')[0] ?? '-'} - {c.compiler.version?.split('+')[0]} - - + {c.compiler_version?.split('+')[0]} + + - - {c.txCount.toLocaleString('en')} + + {/* {c.account.transaction_count.toLocaleString('en')} */} + {c.account.transaction_count} )) @@ -160,22 +280,15 @@ const ContractList = () => { - - - {/* */} - {isLoading ? ( - - ) : ( - - )} - + {data?.metadata.total_count ? ( +
+ {!data ? ( + + ) : ( + + )} +
+ ) : null} diff --git a/pages/index.module.scss b/pages/index.module.scss index 9ea9d2399..1ed26453a 100644 --- a/pages/index.module.scss +++ b/pages/index.module.scss @@ -6,3 +6,12 @@ } } } + +.sorter { + cursor: pointer; + margin-left: 4px; + + &[data-order='DESC'] { + transform: rotate(0.5turn); + } +} diff --git a/utils/handler.ts b/utils/handler.ts index 9cd0d62e6..97535b639 100644 --- a/utils/handler.ts +++ b/utils/handler.ts @@ -73,3 +73,53 @@ export const handleCopy = async (value: string) => { export const handleNftImageLoadError = (e: React.SyntheticEvent) => { e.currentTarget.src = '/images/nft-placeholder.svg' } + +// return a sorter array +export const handleSorterArrayAboutPath = (url: string, sorters: string[], sorterValueEnum = null) => { + const params = url.slice(url.indexOf('?') + 1) + const sorterParamsArray = [] + + const searchParams = new URLSearchParams(params) + const keys = [...searchParams.keys()] + + keys?.map((item, index) => { + if (sorters.includes(item)) { + // return sort array which used for query, like: [{sort_type: ASC , sort_value: xxx}] + if (sorterValueEnum) { + sorterParamsArray.push({ + sort_type: decodeURIComponent([...searchParams.values()][index]), + sort_value: sorterValueEnum[item], + }) + + // return sort array which from url, like: [{type: xxx , order: ASC}] + } else { + sorterParamsArray.push({ type: item, order: decodeURIComponent([...searchParams.values()][index]) }) + } + } + }) + + return sorterParamsArray +} +export type sorterType = { + type: string + order: 'ASC' | 'DESC' +} + +export const handleSorterArrayInOrder = (pathSorterArray: sorterType[], onClickedSorter: sorterType = null) => { + if (onClickedSorter) { + const { type } = onClickedSorter + + // return a sorter array with the clicked one is on the first position + pathSorterArray.sort((preSorter, curSorter) => { + if (preSorter.type === type) { + return -1 + } else if (curSorter.type === type) { + return 1 + } else { + return 0 + } + }) + pathSorterArray[0] = onClickedSorter + } + return pathSorterArray +}