Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: change the pagination to cursor and add sorter---contract list #735

Merged
merged 9 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 157 additions & 44 deletions pages/contracts.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -34,11 +88,12 @@ const contractListQuery = gql`
}
`

const fetchList = () =>
const fetchList = (variables: Variables): Promise<ContractListProps['smart_contracts']> =>
client
.request<ContractListProps>(contractListQuery)
.request<ContractListProps>(contractListQuery, variables)
.then(data => data.smart_contracts)
.catch((): ContractListProps['smart_contracts'] => ({
entries: [],
metadata: {
total_count: 0,
before: null,
Expand All @@ -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<HTMLOrSVGElement>, type) => {
const {
dataset: { order },
} = e.currentTarget
push(handleUrlForPush({ type, order: order === 'DESC' ? 'ASC' : 'DESC' }))
}

return (
<>
Expand Down Expand Up @@ -99,7 +202,7 @@ const ContractList = () => {
{!isLoading ? (
<Typography variant="inherit" color="secondary" fontWeight={500} fontSize={{ xs: 14, md: 16 }}>
{t(`n_kinds_in_total`, {
number: list?.metadata.total_count ?? '-',
number: data?.metadata.total_count ?? '-',
})}
</Typography>
) : (
Expand All @@ -118,8 +221,20 @@ const ContractList = () => {
<th>
{t(`balance`)}
<span style={{ textTransform: 'none' }}>{`(${PCKB_UDT_INFO.symbol})`}</span>
<SortIcon
onClick={e => handleSorterClick(e, SortTypesEnum.balance_sort)}
data-order={balance_sort}
className={styles.sorter}
/>
</th>
<th style={{ textAlign: 'end' }}>
{t(`tx_count`)}
<SortIcon
onClick={e => handleSorterClick(e, SortTypesEnum.tx_count_sort)}
data-order={tx_count_sort}
className={styles.sorter}
/>
</th>
<th style={{ textAlign: 'end' }}>{t(`tx_count`)}</th>
</tr>
</thead>
<tbody>
Expand All @@ -131,22 +246,27 @@ const ContractList = () => {
</td>
</tr>
))
) : data.meta.totalPage ? (
data.contracts.map(c => (
<tr key={c.id} title={c.address}>
) : data.metadata.total_count ? (
data.entries.map(c => (
<tr key={c.id} title={c.account.eth_address}>
<td style={{ width: '25%' }}>
<Address address={c.address} leading={isMobile ? 8 : 30} sx={{ width: 'min-content' }} />
<Address
address={c.account.eth_address}
leading={isMobile ? 8 : 30}
sx={{ width: 'min-content' }}
/>
</td>
<td title={c.name}>{c.name}</td>
<td style={{ textTransform: 'capitalize' }} title={c.compiler.fileFormat}>
{c.compiler.fileFormat?.split(' ')[0] ?? '-'}
<td style={{ textTransform: 'capitalize' }} title={c?.compiler_file_format}>
{c?.compiler_file_format?.split(' ')[0] ?? '-'}
</td>
<td title={c.compiler.version}>{c.compiler.version?.split('+')[0]}</td>
<td title={c.balance}>
<Amount amount={c.balance ?? '0'} udt={{ symbol: PCKB_UDT_INFO.symbol, decimal: 10 }} />
<td title={c.compiler_version}>{c.compiler_version?.split('+')[0]}</td>
<td title={c.ckb_balance}>
<Amount amount={c.ckb_balance ?? '0'} udt={{ symbol: PCKB_UDT_INFO.symbol, decimal: 10 }} />
</td>
<td style={{ textAlign: 'end' }} title={`${c.txCount}`}>
{c.txCount.toLocaleString('en')}
<td style={{ textAlign: 'end' }} title={`${c.account.transaction_count}`}>
{/* {c.account.transaction_count.toLocaleString('en')} */}
{c.account.transaction_count}
</td>
</tr>
))
Expand All @@ -160,22 +280,15 @@ const ContractList = () => {
</tbody>
</Table>
</TableContainer>

<Stack
direction="row"
flexWrap="wrap"
justifyContent={isMobile ? 'center' : 'end'}
alignItems="center"
mt={{ xs: 0, md: 2 }}
px={{ xs: 1.5, md: 3 }}
>
{/* <PageSize pageSize={+page_size} /> */}
{isLoading ? (
<Skeleton animation="wave" width="20px" />
) : (
<Pagination total={data?.meta.totalPage * +page_size} page={+page} pageSize={+page_size} />
)}
</Stack>
{data?.metadata.total_count ? (
<div style={{ overflow: 'hidden' }}>
{!data ? (
<Skeleton animation="wave" width="calc(100% - 48px)" sx={{ mx: '24px', my: '20px' }} />
) : (
<Pagination {...data.metadata} />
)}
</div>
) : null}
</Box>
</Container>
</>
Expand Down
9 changes: 9 additions & 0 deletions pages/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@
}
}
}

.sorter {
cursor: pointer;
margin-left: 4px;

&[data-order='DESC'] {
transform: rotate(0.5turn);
}
}
50 changes: 50 additions & 0 deletions utils/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,53 @@ export const handleCopy = async (value: string) => {
export const handleNftImageLoadError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
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)
alexsupa597 marked this conversation as resolved.
Show resolved Hide resolved
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
}