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; 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; }