Skip to content

Commit 10ffb1f

Browse files
committed
Clean up how pagination parameters are handled
1 parent 093809c commit 10ffb1f

16 files changed

+248
-95
lines changed

frontend/package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@radix-ui/react-collapsible": "1.0.3",
2323
"@radix-ui/react-dialog": "^1.0.5",
2424
"@tanstack/react-router": "^1.58.15",
25+
"@tanstack/router-zod-adapter": "^1.58.15",
2526
"@urql/core": "^5.0.6",
2627
"@urql/devtools": "^2.0.3",
2728
"@urql/exchange-graphcache": "^7.1.3",

frontend/src/components/UserProfile/UserEmailList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import { useTransition } from "react";
88
import { useQuery } from "urql";
99

10-
import { FragmentType, graphql, useFragment } from "../../gql";
10+
import { type FragmentType, graphql, useFragment } from "../../gql";
1111
import {
1212
FIRST_PAGE,
13-
Pagination,
13+
type Pagination,
1414
usePages,
1515
usePagination,
1616
} from "../../pagination";

frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const BrowserSessionsOverview: React.FC<{
3838
})}
3939
</Text>
4040
</div>
41-
<Link to="/sessions/browsers" search={{ first: 6 }}>
41+
<Link to="/sessions/browsers">
4242
{t("frontend.browser_sessions_overview.view_all_button")}
4343
</Link>
4444
</div>

frontend/src/components/UserSessionsOverview/__snapshots__/BrowserSessionsOverview.test.tsx.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ exports[`BrowserSessionsOverview > renders with no browser sessions 1`] = `
2222
<a
2323
class="_link_1mzip_17"
2424
data-kind="primary"
25-
href="/sessions/browsers?first=6"
25+
href="/sessions/browsers"
2626
rel="noreferrer noopener"
2727
>
2828
View all
@@ -53,7 +53,7 @@ exports[`BrowserSessionsOverview > renders with sessions 1`] = `
5353
<a
5454
class="_link_1mzip_17"
5555
data-kind="primary"
56-
href="/sessions/browsers?first=6"
56+
href="/sessions/browsers"
5757
rel="noreferrer noopener"
5858
>
5959
View all

frontend/src/pagination.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@
77
import { useState } from "react";
88
import * as z from "zod";
99

10-
import { PageInfo } from "./gql/graphql";
10+
import type { PageInfo } from "./gql/graphql";
1111

1212
export const FIRST_PAGE = Symbol("FIRST_PAGE");
1313
export const LAST_PAGE = Symbol("LAST_PAGE");
1414

15+
export const anyPaginationSchema = z.object({
16+
first: z.number().optional(),
17+
after: z.string().optional(),
18+
last: z.number().optional(),
19+
before: z.string().optional(),
20+
});
21+
1522
export const forwardPaginationSchema = z.object({
1623
first: z.number(),
1724
after: z.string().optional(),
1825
});
1926

20-
export const backwardPaginationSchema = z.object({
27+
const backwardPaginationSchema = z.object({
2128
last: z.number(),
2229
before: z.string().optional(),
2330
});
2431

25-
export const paginationSchema = z.union([
32+
const paginationSchema = z.union([
2633
forwardPaginationSchema,
2734
backwardPaginationSchema,
2835
]);
2936

30-
export type ForwardPagination = z.infer<typeof forwardPaginationSchema>;
31-
export type BackwardPagination = z.infer<typeof backwardPaginationSchema>;
37+
type ForwardPagination = z.infer<typeof forwardPaginationSchema>;
38+
type BackwardPagination = z.infer<typeof backwardPaginationSchema>;
3239
export type Pagination = z.infer<typeof paginationSchema>;
40+
export type AnyPagination = z.infer<typeof anyPaginationSchema>;
41+
42+
// Check if the pagination is a valid pagination
43+
export const isValidPagination = (
44+
pagination: AnyPagination,
45+
): pagination is Pagination =>
46+
typeof pagination.first === "number" || typeof pagination.last === "number";
3347

3448
// Check if the pagination is forward pagination.
3549
export const isForwardPagination = (
@@ -47,26 +61,40 @@ export const isBackwardPagination = (
4761

4862
type Action = typeof FIRST_PAGE | typeof LAST_PAGE | Pagination;
4963

64+
// Normalize pagination parameters to a valid pagination object
65+
export const normalizePagination = (
66+
pagination: AnyPagination,
67+
pageSize = 6,
68+
type: "forward" | "backward" = "forward",
69+
): Pagination => {
70+
if (isValidPagination(pagination)) {
71+
return pagination;
72+
}
73+
74+
if (type === "forward") {
75+
return { first: pageSize } satisfies ForwardPagination;
76+
}
77+
78+
return { last: pageSize } satisfies BackwardPagination;
79+
};
80+
5081
// Hook to handle pagination state.
5182
export const usePagination = (
5283
pageSize = 6,
5384
): [Pagination, (action: Action) => void] => {
5485
const [pagination, setPagination] = useState<Pagination>({
5586
first: pageSize,
56-
after: undefined,
5787
});
5888

5989
const handlePagination = (action: Action): void => {
6090
if (action === FIRST_PAGE) {
6191
setPagination({
6292
first: pageSize,
63-
after: undefined,
64-
});
93+
} satisfies ForwardPagination);
6594
} else if (action === LAST_PAGE) {
6695
setPagination({
6796
last: pageSize,
68-
before: undefined,
69-
});
97+
} satisfies BackwardPagination);
7098
} else {
7199
setPagination(action);
72100
}
@@ -78,7 +106,7 @@ export const usePagination = (
78106
// Compute the next backward and forward pagination parameters based on the current pagination and the page info.
79107
export const usePages = (
80108
currentPagination: Pagination,
81-
pageInfo: PageInfo | null,
109+
pageInfo: PageInfo,
82110
pageSize = 6,
83111
): [BackwardPagination | null, ForwardPagination | null] => {
84112
const hasProbablyPreviousPage =
@@ -90,17 +118,17 @@ export const usePages = (
90118

91119
let previousPagination: BackwardPagination | null = null;
92120
let nextPagination: ForwardPagination | null = null;
93-
if (pageInfo?.hasPreviousPage || hasProbablyPreviousPage) {
121+
if (pageInfo.hasPreviousPage || hasProbablyPreviousPage) {
94122
previousPagination = {
95123
last: pageSize,
96-
before: pageInfo?.startCursor ?? undefined,
124+
before: pageInfo.startCursor ?? undefined,
97125
};
98126
}
99127

100-
if (pageInfo?.hasNextPage || hasProbablyNextPage) {
128+
if (pageInfo.hasNextPage || hasProbablyNextPage) {
101129
nextPagination = {
102130
first: pageSize,
103-
after: pageInfo?.endCursor ?? undefined,
131+
after: pageInfo.endCursor ?? undefined,
104132
};
105133
}
106134

frontend/src/routeTree.gen.ts

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -204,21 +204,138 @@ declare module '@tanstack/react-router' {
204204

205205
// Create and export the route tree
206206

207-
export const routeTree = rootRoute.addChildren({
208-
AccountRoute: AccountRoute.addChildren({
209-
AccountIndexRoute,
210-
AccountSessionsIdRoute,
211-
AccountSessionsBrowsersRoute,
212-
AccountSessionsIndexRoute,
213-
}),
214-
ResetCrossSigningRoute,
215-
ClientsIdRoute,
216-
DevicesSplatRoute,
217-
EmailsIdVerifyRoute,
218-
PasswordChangeSuccessLazyRoute,
219-
PasswordChangeIndexRoute,
220-
PasswordRecoveryIndexRoute,
221-
})
207+
interface AccountRouteChildren {
208+
AccountIndexRoute: typeof AccountIndexRoute
209+
AccountSessionsIdRoute: typeof AccountSessionsIdRoute
210+
AccountSessionsBrowsersRoute: typeof AccountSessionsBrowsersRoute
211+
AccountSessionsIndexRoute: typeof AccountSessionsIndexRoute
212+
}
213+
214+
const AccountRouteChildren: AccountRouteChildren = {
215+
AccountIndexRoute: AccountIndexRoute,
216+
AccountSessionsIdRoute: AccountSessionsIdRoute,
217+
AccountSessionsBrowsersRoute: AccountSessionsBrowsersRoute,
218+
AccountSessionsIndexRoute: AccountSessionsIndexRoute,
219+
}
220+
221+
const AccountRouteWithChildren =
222+
AccountRoute._addFileChildren(AccountRouteChildren)
223+
224+
export interface FileRoutesByFullPath {
225+
'': typeof AccountRouteWithChildren
226+
'/reset-cross-signing': typeof ResetCrossSigningRoute
227+
'/clients/$id': typeof ClientsIdRoute
228+
'/devices/$': typeof DevicesSplatRoute
229+
'/': typeof AccountIndexRoute
230+
'/sessions/$id': typeof AccountSessionsIdRoute
231+
'/sessions/browsers': typeof AccountSessionsBrowsersRoute
232+
'/emails/$id/verify': typeof EmailsIdVerifyRoute
233+
'/password/change/success': typeof PasswordChangeSuccessLazyRoute
234+
'/sessions': typeof AccountSessionsIndexRoute
235+
'/password/change': typeof PasswordChangeIndexRoute
236+
'/password/recovery': typeof PasswordRecoveryIndexRoute
237+
}
238+
239+
export interface FileRoutesByTo {
240+
'/reset-cross-signing': typeof ResetCrossSigningRoute
241+
'/clients/$id': typeof ClientsIdRoute
242+
'/devices/$': typeof DevicesSplatRoute
243+
'/': typeof AccountIndexRoute
244+
'/sessions/$id': typeof AccountSessionsIdRoute
245+
'/sessions/browsers': typeof AccountSessionsBrowsersRoute
246+
'/emails/$id/verify': typeof EmailsIdVerifyRoute
247+
'/password/change/success': typeof PasswordChangeSuccessLazyRoute
248+
'/sessions': typeof AccountSessionsIndexRoute
249+
'/password/change': typeof PasswordChangeIndexRoute
250+
'/password/recovery': typeof PasswordRecoveryIndexRoute
251+
}
252+
253+
export interface FileRoutesById {
254+
__root__: typeof rootRoute
255+
'/_account': typeof AccountRouteWithChildren
256+
'/reset-cross-signing': typeof ResetCrossSigningRoute
257+
'/clients/$id': typeof ClientsIdRoute
258+
'/devices/$': typeof DevicesSplatRoute
259+
'/_account/': typeof AccountIndexRoute
260+
'/_account/sessions/$id': typeof AccountSessionsIdRoute
261+
'/_account/sessions/browsers': typeof AccountSessionsBrowsersRoute
262+
'/emails/$id/verify': typeof EmailsIdVerifyRoute
263+
'/password/change/success': typeof PasswordChangeSuccessLazyRoute
264+
'/_account/sessions/': typeof AccountSessionsIndexRoute
265+
'/password/change/': typeof PasswordChangeIndexRoute
266+
'/password/recovery/': typeof PasswordRecoveryIndexRoute
267+
}
268+
269+
export interface FileRouteTypes {
270+
fileRoutesByFullPath: FileRoutesByFullPath
271+
fullPaths:
272+
| ''
273+
| '/reset-cross-signing'
274+
| '/clients/$id'
275+
| '/devices/$'
276+
| '/'
277+
| '/sessions/$id'
278+
| '/sessions/browsers'
279+
| '/emails/$id/verify'
280+
| '/password/change/success'
281+
| '/sessions'
282+
| '/password/change'
283+
| '/password/recovery'
284+
fileRoutesByTo: FileRoutesByTo
285+
to:
286+
| '/reset-cross-signing'
287+
| '/clients/$id'
288+
| '/devices/$'
289+
| '/'
290+
| '/sessions/$id'
291+
| '/sessions/browsers'
292+
| '/emails/$id/verify'
293+
| '/password/change/success'
294+
| '/sessions'
295+
| '/password/change'
296+
| '/password/recovery'
297+
id:
298+
| '__root__'
299+
| '/_account'
300+
| '/reset-cross-signing'
301+
| '/clients/$id'
302+
| '/devices/$'
303+
| '/_account/'
304+
| '/_account/sessions/$id'
305+
| '/_account/sessions/browsers'
306+
| '/emails/$id/verify'
307+
| '/password/change/success'
308+
| '/_account/sessions/'
309+
| '/password/change/'
310+
| '/password/recovery/'
311+
fileRoutesById: FileRoutesById
312+
}
313+
314+
export interface RootRouteChildren {
315+
AccountRoute: typeof AccountRouteWithChildren
316+
ResetCrossSigningRoute: typeof ResetCrossSigningRoute
317+
ClientsIdRoute: typeof ClientsIdRoute
318+
DevicesSplatRoute: typeof DevicesSplatRoute
319+
EmailsIdVerifyRoute: typeof EmailsIdVerifyRoute
320+
PasswordChangeSuccessLazyRoute: typeof PasswordChangeSuccessLazyRoute
321+
PasswordChangeIndexRoute: typeof PasswordChangeIndexRoute
322+
PasswordRecoveryIndexRoute: typeof PasswordRecoveryIndexRoute
323+
}
324+
325+
const rootRouteChildren: RootRouteChildren = {
326+
AccountRoute: AccountRouteWithChildren,
327+
ResetCrossSigningRoute: ResetCrossSigningRoute,
328+
ClientsIdRoute: ClientsIdRoute,
329+
DevicesSplatRoute: DevicesSplatRoute,
330+
EmailsIdVerifyRoute: EmailsIdVerifyRoute,
331+
PasswordChangeSuccessLazyRoute: PasswordChangeSuccessLazyRoute,
332+
PasswordChangeIndexRoute: PasswordChangeIndexRoute,
333+
PasswordRecoveryIndexRoute: PasswordRecoveryIndexRoute,
334+
}
335+
336+
export const routeTree = rootRoute
337+
._addFileChildren(rootRouteChildren)
338+
._addFileTypes<FileRouteTypes>()
222339

223340
/* prettier-ignore-end */
224341

0 commit comments

Comments
 (0)