Files
web_sport/apps/storefront/src/lib/search-params.ts
T
2026-08-13 23:20:22 +07:00

53 lines
1.9 KiB
TypeScript

import type { ProductListQuery } from '@sport/api-client';
/**
* Parses listing filters out of a Next.js `searchParams` object.
*
* Kept deliberately permissive: unknown or malformed values are dropped rather
* than rejected, because a listing page reached from a stale bookmark should
* render products, not an error. The API re-validates everything anyway
* (@sport/validation), so this is convenience, not enforcement.
*/
export type RawSearchParams = Record<string, string | string[] | undefined>;
const SORTS = ['newest', 'price_asc', 'price_desc', 'best_selling', 'relevance'] as const;
export function parseListingParams(params: RawSearchParams): ProductListQuery {
return {
q: single(params['q']),
colors: list(params['colors']),
sizes: list(params['sizes']),
brandSlugs: list(params['brandSlugs']),
categorySlug: single(params['category']),
onSale: single(params['onSale']) === 'true' ? true : undefined,
sort: parseSort(single(params['sort'])),
// Present only when someone followed the "load more" link without
// JavaScript, or a crawler did. The client path appends in place and never
// puts a cursor in the address bar.
cursor: single(params['cursor']),
};
}
function single(value: string | string[] | undefined): string | undefined {
const raw = Array.isArray(value) ? value[0] : value;
return raw && raw.length > 0 ? raw : undefined;
}
/** Accepts both `?colors=a,b` and repeated `?colors=a&colors=b`. */
function list(value: string | string[] | undefined): string[] | undefined {
if (!value) return undefined;
const items = (Array.isArray(value) ? value : [value])
.flatMap((entry) => entry.split(','))
.map((entry) => entry.trim())
.filter(Boolean);
return items.length > 0 ? items : undefined;
}
function parseSort(value: string | undefined): ProductListQuery['sort'] {
return SORTS.includes(value as (typeof SORTS)[number])
? (value as ProductListQuery['sort'])
: undefined;
}