import type { Metadata } from 'next'; import Link from 'next/link'; import { Suspense } from 'react'; import { searchAssets, searchSets } from '@rareindex/search'; import { Thumb } from '@/components/market/bits'; import { fmtNum } from '@/lib/format'; import { PageHeader } from '@/components/ui/page-header'; import { Card, CardHeader, EmptyState, Badge, Skeleton } from '@/components/ui/primitives'; import { Pagination } from '@/components/ui/pagination'; import { AssetList } from '@/components/market/asset-list'; import { attachGuidePrices, getAssetsBySlugs } from '@/lib/queries/assets'; import { rows, sql } from '@/lib/queries/_util'; import { CATEGORIES, INDICES, getGrader } from '@rareindex/taxonomy'; import { catName } from '@/lib/taxonomy'; import { sp1, spInt, type SP } from '@/lib/search-params'; export const metadata: Metadata = { title: 'Search', description: 'Search collectible assets, sets, categories, brands and sources with natural-language filters.' }; export default async function SearchPage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const q = sp1(sp, 'q') ?? ''; const category = sp1(sp, 'category') ?? null; const page = spInt(sp, 'page'); return (
Results for “{q}” : 'Search'} description="Hybrid search: lexical + trigram + popularity, with structured filters parsed from your words (grader, grade, year, price bounds, condition, edition)." compact />
{category ? : null}
{q ? ( }> ) : ( )}
); } async function Results({ q, category, page }: { q: string; category: string | null; page: number }) { const pageSize = 24; const [res, setHits] = await Promise.all([searchAssets(q, { limit: pageSize, offset: (page - 1) * pageSize, categorySlug: category }), searchSets(q, 8)]); const items = await attachGuidePrices(await getAssetsBySlugs(res.hits.map((h) => h.slug))); const p = res.parsed; const lower = q.toLowerCase(); const matchedCats = CATEGORIES.filter((c) => c.name.toLowerCase().includes(lower) || p.categorySlugs.includes(c.slug)).slice(0, 6); const matchedIdx = INDICES.filter((i) => i.ticker.toLowerCase().includes(lower) || i.name.toLowerCase().includes(lower)).slice(0, 3); const brands = (await rows<{ brand: string; n: number }>(sql`SELECT brand, count(*) AS n FROM assets WHERE brand ILIKE ${'%' + q + '%'} GROUP BY brand ORDER BY n DESC LIMIT 6`)).map((b) => ({ ...b })); const sources = (await rows<{ id: string; name: string }>(sql`SELECT id, name FROM sources WHERE name ILIKE ${'%' + q + '%'} LIMIT 4`)).map((s) => ({ ...s })); const chips = [ p.grader ? `${getGrader(p.grader)?.name ?? p.grader.toUpperCase()}${p.grade ? ` ${p.grade}` : ''}` : null, p.year ? `year ${p.year}` : null, p.priceMin != null ? `≥ $${p.priceMin.toLocaleString('en-US')}` : null, p.priceMax != null ? `≤ $${p.priceMax.toLocaleString('en-US')}` : null, ...p.conditions, ...p.editions, ...p.categorySlugs.map((c) => catName(c)), ].filter(Boolean) as string[]; return (
{res.total.toLocaleString('en-US')} assets · {res.tookMs} ms {chips.length ? · understood: : null} {chips.map((c) => ( {c} ))} {p.text && p.text !== q ? · text “{p.text}” : null}
{setHits.length ? (
    {setHits.map((s) => (
  • {s.name} {s.code ? {s.code} : null} {s.releaseYear ? {s.releaseYear} : null} {catName(s.categorySlug)} · {fmtNum(s.assets)} assets
  • ))}
) : null}
); }