Skip to content

Commit 1a18fef

Browse files
Merge pull request #1559 from curvefi/feat/dex-table-pagination
feat: table pagination & e2e tests
2 parents a9d1da0 + 125b303 commit 1a18fef

File tree

22 files changed

+420
-62
lines changed

22 files changed

+420
-62
lines changed

apps/main/src/dex/features/pool-list/PoolListTable.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { isEqual } from 'lodash'
22
import { useCallback, useMemo, useState } from 'react'
33
import { type NetworkConfig } from '@/dex/types/main.types'
4-
import { ColumnFiltersState, ExpandedState, useReactTable } from '@tanstack/react-table'
4+
import { ColumnFiltersState, ExpandedState, getPaginationRowModel, useReactTable } from '@tanstack/react-table'
55
import { CurveApi } from '@ui-kit/features/connect-wallet'
66
import { useUserProfileStore } from '@ui-kit/features/user-profile'
77
import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store'
88
import { useIsTablet } from '@ui-kit/hooks/useBreakpoints'
9+
import { usePageFromQueryString } from '@ui-kit/hooks/usePageFromQueryString'
910
import { useSortFromQueryString } from '@ui-kit/hooks/useSortFromQueryString'
1011
import type { MigrationOptions } from '@ui-kit/hooks/useStoredState'
1112
import { t } from '@ui-kit/lib/i18n'
@@ -43,6 +44,8 @@ const useSearch = (columnFiltersById: Record<string, unknown>, setColumnFilter:
4344
useCallback((search: string) => setColumnFilter(PoolColumnId.PoolName, search || undefined), [setColumnFilter]),
4445
] as const
4546

47+
const PER_PAGE = 50
48+
4649
export const PoolListTable = ({ network, curve }: { network: NetworkConfig; curve: CurveApi | null }) => {
4750
// todo: this needs to be more complicated, we need to show at least the top 10 per chain
4851
const minLiquidity = useUserProfileStore((s) => s.hideSmallPools) ? SMALL_POOL_TVL : 0
@@ -59,26 +62,27 @@ export const PoolListTable = ({ network, curve }: { network: NetworkConfig; curv
5962
defaultFilters,
6063
)
6164
const [sorting, onSortingChange] = useSortFromQueryString(DEFAULT_SORT)
65+
const [pagination, onPaginationChange] = usePageFromQueryString(PER_PAGE)
6266
const { columnSettings, columnVisibility, sortField } = usePoolListVisibilitySettings(LOCAL_STORAGE_KEY, {
6367
isLite,
6468
sorting,
6569
})
6670
const [expanded, onExpandedChange] = useState<ExpandedState>({})
6771
const [searchText, onSearch] = useSearch(columnFiltersById, setColumnFilter)
68-
6972
const table = useReactTable({
7073
columns: POOL_LIST_COLUMNS,
7174
data: useMemo(() => data ?? [], [data]),
72-
state: { expanded, sorting, columnVisibility, columnFilters },
75+
state: { expanded, sorting, columnVisibility, columnFilters, pagination },
7376
onSortingChange,
7477
onExpandedChange,
78+
onPaginationChange,
7579
...getTableOptions(data),
80+
getPaginationRowModel: getPaginationRowModel(),
7681
})
7782

7883
const resultCount = table.getFilteredRowModel().rows.length
7984
return (
8085
<DataTable
81-
lazy
8286
table={table}
8387
emptyState={
8488
<EmptyStateRow table={table}>
Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Fragment } from 'react'
22
import CampaignRewardsRow from '@/dex/components/CampaignRewardsRow'
33
import PoolRewardsCrv from '@/dex/components/PoolRewardsCrv'
4-
import { useNetworkFromUrl } from '@/dex/hooks/useChainId'
54
import { RewardsApy } from '@/dex/types/main.types'
65
import type { Chain } from '@curvefi/prices-api'
6+
import Typography from '@mui/material/Typography'
77
import type { CellContext } from '@tanstack/react-table'
8-
import { Chip } from '@ui/Typography'
98
import { FORMAT_OPTIONS, formatNumber } from '@ui/utils'
109
import { useCampaignsByAddress } from '@ui-kit/entities/campaigns'
1110
import { isSortedBy } from '@ui-kit/shared/ui/DataTable/data-table.utils'
@@ -22,29 +21,25 @@ export const RewardsOtherCell = ({ getValue, table, column, row: { original: poo
2221
})
2322
const rewards = getValue()
2423
const { other, crv } = rewards ?? {}
25-
const isCrvRewardsEnabled = useNetworkFromUrl()?.isCrvRewardsEnabled
2624
return (
2725
<>
28-
{isCrvRewardsEnabled && crv && (
26+
{crv && (
2927
<PoolRewardsCrv
3028
poolData={poolData}
3129
rewardsApy={rewards}
3230
isHighlight={isSortedBy(table, PoolColumnId.RewardsCrv)}
3331
/>
3432
)}
35-
{!!other?.length && (
36-
<div>
37-
{other?.map((o) => (
38-
<Fragment key={o.tokenAddress}>
39-
<Chip size="md" isBold={isSortedBy(table, PoolColumnId.RewardsIncentives)}>
40-
{formatNumber(o.apy, FORMAT_OPTIONS.PERCENT)} {o.symbol}
41-
</Chip>
42-
<br />
43-
</Fragment>
44-
))}
45-
</div>
46-
)}
33+
{other?.map((o) => (
34+
<Typography
35+
fontWeight={isSortedBy(table, PoolColumnId.RewardsIncentives) ? 'bold' : 'normal'}
36+
key={o.tokenAddress}
37+
>
38+
{formatNumber(o.apy, FORMAT_OPTIONS.PERCENT)} {o.symbol}
39+
</Typography>
40+
))}
4741
{campaigns.length > 0 && <CampaignRewardsRow rewardItems={campaigns} />}
42+
{!crv && !other?.length && !campaigns.length && '-'}
4843
</>
4944
)
5045
}

apps/main/src/dex/features/pool-list/drawers/PoolListFilterDrawer.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React from 'react'
1+
import React, { useCallback } from 'react'
2+
import type { PoolColumnId } from '@/dex/features/pool-list/columns'
23
import { Button, Grid, Stack } from '@mui/material'
34
import { useSwitch } from '@ui-kit/hooks/useSwitch'
45
import { t } from '@ui-kit/lib/i18n'
@@ -25,9 +26,17 @@ export const PoolListFilterDrawer = ({
2526
hasFilters,
2627
searchText,
2728
onSearch,
29+
setColumnFilter,
2830
...filterProps
2931
}: Props) => {
30-
const [open, openDrawer, closeDrawer] = useSwitch(false)
32+
const [open, openDrawer, closeDrawer, , setOpen] = useSwitch(false)
33+
const setFilterAndClose = useCallback(
34+
(id: PoolColumnId, value: unknown) => {
35+
setColumnFilter(id, value)
36+
closeDrawer()
37+
},
38+
[setColumnFilter, closeDrawer],
39+
)
3140
return (
3241
<SwipeableDrawer
3342
paperSx={{ maxHeight: SizesAndSpaces.MaxHeight.drawer }}
@@ -43,7 +52,7 @@ export const PoolListFilterDrawer = ({
4352
</Button>
4453
}
4554
open={open}
46-
setOpen={closeDrawer}
55+
setOpen={setOpen}
4756
>
4857
<DrawerHeader title={t`Filters`}>
4958
<HiddenMarketsResetFilters
@@ -60,7 +69,7 @@ export const PoolListFilterDrawer = ({
6069
>
6170
<DrawerHeader title={t`Popular Filters`} />
6271
<Grid container spacing={Spacing.sm}>
63-
<PoolListFilterChips {...filterProps} />
72+
<PoolListFilterChips {...filterProps} setColumnFilter={setFilterAndClose} />
6473
</Grid>
6574
</Stack>
6675
</SwipeableDrawer>

apps/main/src/dex/features/pool-list/hooks/usePoolListData.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { NetworkConfig, PoolData } from '@/dex/types/main.types'
88
import { getPath } from '@/dex/utils/utilsRouter'
99
import { notFalsy, recordValues } from '@curvefi/prices-api/objects.util'
1010
import { useConnection } from '@ui-kit/features/connect-wallet'
11-
import { useLayoutStore } from '@ui-kit/features/layout'
1211
import usePageVisibleInterval from '@ui-kit/hooks/usePageVisibleInterval'
1312
import { REFRESH_INTERVAL } from '@ui-kit/lib/model'
1413
import { DEX_ROUTES } from '@ui-kit/shared/routes'
@@ -31,7 +30,6 @@ const getPoolTags = (hasPosition: boolean, { pool, pool: { address, id, name, re
3130
export function usePoolListData({ id: network, chainId, isLite }: NetworkConfig) {
3231
const { curveApi } = useConnection()
3332
const userActiveKey = getUserActiveKey(curveApi)
34-
const isPageVisible = useLayoutStore((state) => state.isPageVisible)
3533
const poolDataMapper = useStore((state) => state.pools.poolsMapper[chainId])
3634
const rewardsApyMapper = useStore((state) => state.pools.rewardsApyMapper[chainId])
3735
const tvlMapper = useStore((state) => state.pools.tvlMapper[chainId])

apps/main/src/llamalend/features/market-list/LlamaMarketsTable.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const migration: MigrationOptions<ColumnFiltersState> = {
4141
migrate: (oldValue, initial) => [...initial.filter((i) => !oldValue.some((o) => o.id === i.id)), ...oldValue],
4242
}
4343

44+
const pagination = { pageIndex: 0, pageSize: 200 }
45+
4446
export const LlamaMarketsTable = ({
4547
onReload,
4648
result,
@@ -75,6 +77,7 @@ export const LlamaMarketsTable = ({
7577
columns: LLAMA_MARKET_COLUMNS,
7678
data,
7779
state: { expanded, sorting, columnVisibility, columnFilters },
80+
initialState: { pagination },
7881
onSortingChange,
7982
onExpandedChange,
8083
...getTableOptions(result),

apps/main/src/llamalend/features/market-list/UserPositionsTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type UserPositionsTableProps = {
4242
}
4343

4444
const migration: MigrationOptions<ColumnFiltersState> = { version: 1 }
45+
const pagination = { pageIndex: 0, pageSize: 50 }
4546

4647
export const UserPositionsTable = ({ result, loading, tab }: UserPositionsTableProps) => {
4748
const { markets: data = [], userHasPositions } = result ?? {}
@@ -60,6 +61,7 @@ export const UserPositionsTable = ({ result, loading, tab }: UserPositionsTableP
6061
columns: LLAMA_MARKET_COLUMNS,
6162
data,
6263
state: { expanded, sorting, columnVisibility, columnFilters },
64+
initialState: { pagination },
6365
onSortingChange,
6466
onExpandedChange,
6567
...getTableOptions(result),

apps/main/src/llamalend/features/user-position-history/UserEventsTable.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type UserEventsTableProps = {
1818
loading: boolean
1919
}
2020

21+
const pagination = { pageIndex: 0, pageSize: 50 }
22+
2123
export const UserEventsTable = ({ events, loading, isError }: UserEventsTableProps) => {
2224
const { columnVisibility } = useUserPositionHistoryVisibility()
2325
const [sorting, setSorting] = useState<SortingState>(DEFAULT_SORT)
@@ -26,6 +28,7 @@ export const UserEventsTable = ({ events, loading, isError }: UserEventsTablePro
2628
data: events,
2729
columns: USER_POSITION_HISTORY_COLUMNS,
2830
state: { columnVisibility, sorting },
31+
initialState: { pagination },
2932
onSortingChange: setSorting,
3033
...getTableOptions(events),
3134
})

packages/curve-ui-kit/src/features/user-profile/store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { ThemeKey } from '@ui-kit/themes/basic-theme'
66

77
export const SMALL_POOL_TVL = 10000
88

9-
type State = {
9+
export type UserProfileState = {
1010
theme: ThemeKey
1111
/** Key is either 'crypto', 'stable' or a chainIdPoolId from getChainPoolIdActiveKey. */
1212
maxSlippage: { crypto: string; stable: string } & Partial<Record<string, string>>
@@ -37,7 +37,7 @@ type Action = {
3737
setHideSmallPools: (hideSmallPools: boolean) => void
3838
}
3939

40-
type Store = State & Action
40+
type Store = UserProfileState & Action
4141

4242
const INITIAL_THEME =
4343
typeof window !== 'undefined'
@@ -46,7 +46,7 @@ const INITIAL_THEME =
4646
: 'light'
4747
: 'light'
4848

49-
const INITIAL_STATE: State = {
49+
const INITIAL_STATE: UserProfileState = {
5050
theme: INITIAL_THEME,
5151
maxSlippage: { crypto: '0.1', stable: '0.03' },
5252
isAdvancedMode: false,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useCallback, useMemo } from 'react'
2+
import { OnChangeFn, PaginationState } from '@tanstack/react-table'
3+
import { useSearchParams } from '@ui-kit/hooks/router'
4+
5+
/**
6+
* Hook to manage pagination state synchronized with the URL query string.
7+
* The page index is zero-based internally but one-based in the URL.
8+
* @param pageSize - Number of items per page.
9+
* @param fieldName - The query string parameter name for the page index. The default is 'page'.
10+
* @returns A tuple containing the pagination state and an onChange handler.
11+
*/
12+
export function usePageFromQueryString(pageSize: number, fieldName = 'page') {
13+
const searchParams = useSearchParams()
14+
const pageIndex = useMemo(
15+
() => (searchParams.has(fieldName) ? +searchParams.get(fieldName)! - 1 : 0),
16+
[fieldName, searchParams],
17+
)
18+
const onChange: OnChangeFn<PaginationState> = useCallback(
19+
(newPagination) => {
20+
const { pageIndex: newPage } =
21+
typeof newPagination == 'function' ? newPagination({ pageIndex, pageSize }) : newPagination
22+
const params = new URLSearchParams(searchParams)
23+
if (newPage > 0) {
24+
params.set(fieldName, (newPage + 1).toString())
25+
} else {
26+
params.delete(fieldName)
27+
}
28+
window.history.pushState(null, '', params.size ? `?${params.toString()}` : window.location.pathname)
29+
},
30+
[pageIndex, pageSize, searchParams, fieldName],
31+
)
32+
return [{ pageSize, pageIndex }, onChange] as const
33+
}

packages/curve-ui-kit/src/hooks/useSortFromQueryString.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ export function useSortFromQueryString(defaultSort: SortingState, fieldName = 's
1616
return [sort, onChange] as const
1717
}
1818

19-
function parseSort(search: URLSearchParams | null, defaultSort: SortingState, fieldName: string) {
19+
function parseSort(search: URLSearchParams, defaultSort: SortingState, fieldName: string) {
2020
const sort = search
21-
?.getAll(fieldName)
21+
.getAll(fieldName)
2222
.map((id) => (id.startsWith('-') ? { id: id.slice(1), desc: true } : { id, desc: false }))
2323
return sort?.length ? sort : defaultSort.map(({ id, desc }) => ({ id: id.replace(/\./g, '_'), desc }))
2424
}
2525

26-
function updateSort(search: URLSearchParams | null, state: SortingState, fieldName: string): string {
27-
const params = new URLSearchParams(search ?? [])
26+
function updateSort(search: URLSearchParams, state: SortingState, fieldName: string): string {
27+
const params = new URLSearchParams(search)
2828
params.delete(fieldName)
2929
state.forEach(({ id, desc }) => params.append('sort', `${desc ? '-' : ''}${id}`))
3030
return `?${params.toString()}`

0 commit comments

Comments
 (0)