TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { requireUser } from '@/lib/auth/session';4import { dealRadar } from '@/lib/account/queries';5import { getDisplay } from '@/lib/account/display';6import { PageHeader, btnSecondary } from '@/components/account/page-header';7import { Card, CardHeader, EmptyState, Table, th, td, tdNum, Badge } from '@/components/ui/primitives';8import { fmtPct, fmtRelative, confidenceLabel } from '@/lib/format';910export const metadata: Metadata = { title: 'Deal Radar', robots: { index: false } };1112export default async function DealsPage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {13 const sp = await searchParams;14 const u = await requireUser('/deals');15 const d = await getDisplay();16 const min = Math.min(0.9, Math.max(0.05, Number(sp.min ?? 15) / 100));17 const rows = await dealRadar(u.id, { minDiscount: min, limit: 100 });18 return (19 <>20 <PageHeader21 title="Deal Radar"22 description="Active listings priced materially below the RareIndex Valuation, restricted to the categories in your collections and watchlist. Analytical data, not investment advice — a discount can also mean a misidentified or damaged item."23 actions={24 <div className="flex gap-1 text-xs">25 {[10, 15, 25, 40].map((p) => (26 <Link key={p} href={`?min=${p}`} className={`rounded-full border px-3 py-1.5 ${Math.round(min * 100) === p ? 'border-fg bg-accent text-accent-fg' : 'border-border text-muted'}`}>27 ≥ {p}% below RIV28 </Link>29 ))}30 </div>31 }32 />33 <Card>34 <CardHeader title={`${rows.length} listing${rows.length === 1 ? '' : 's'}`} subtitle="Only assets with RIV confidence ≥ 50% and at least 5 sales qualify. Watched assets first." />35 {rows.length === 0 ? (36 <EmptyState title="No deals in your universe right now" description="Deal Radar looks at the categories of your collections and watchlist. Add items or watch a category to widen the net, or lower the discount threshold." action={<Link href="/watchlist" className={btnSecondary}>Manage watchlist</Link>} />37 ) : (38 <Table>39 <thead>40 <tr>41 <th className={th}>Listing</th>42 <th className={`${th} text-right`}>Ask</th>43 <th className={`${th} text-right`}>RIV</th>44 <th className={`${th} text-right`}>Discount</th>45 <th className={th}>Confidence</th>46 <th className={th}>Grade</th>47 <th className={th}>Source</th>48 <th className={th}>Seen</th>49 </tr>50 </thead>51 <tbody>52 {rows.map((r) => (53 <tr key={String(r.id)} className="hover:bg-sunken">54 <td className={td}>55 <div className="flex items-center gap-2.5">56 {r.hero_image_url ? (57 // eslint-disable-next-line @next/next/no-img-element58 <img src={String(r.hero_image_url)} alt="" className="h-9 w-9 rounded-sm object-cover" />59 ) : (60 <span className="h-9 w-9 rounded-sm bg-inset" />61 )}62 <div className="max-w-[280px]">63 <Link href={`/asset/${String(r.slug)}`} className="block truncate font-medium hover:underline">64 {String(r.title)}65 </Link>66 <p className="text-[11px] text-subtle">67 {String(r.category_slug).replace(/_/g, ' ')}68 {r.watched ? (69 <Badge tone="index" className="ml-1">70 watched71 </Badge>72 ) : null}73 </p>74 </div>75 </div>76 </td>77 <td className={tdNum}>{d.money(Number(r.price_usd))}</td>78 <td className={tdNum}>{d.money(Number(r.riv_usd))}</td>79 <td className={`${tdNum} text-gain`}>{fmtPct(Number(r.discount_to_riv))}</td>80 <td className={`${td} text-xs`}>81 {confidenceLabel(Number(r.riv_confidence))} · {Number(r.riv_sample_size)} sales82 </td>83 <td className={`${td} text-xs`}>{r.grader ? `${String(r.grader).toUpperCase()} ${r.grade ?? ''}` : (r.condition as string | null) ?? '—'}</td>84 <td className={td}>85 <a href={String(r.source_url)} target="_blank" rel="noopener nofollow" className="text-xs underline-offset-4 hover:underline">86 {String(r.source_id)} ↗87 </a>88 </td>89 <td className={`${td} text-xs text-muted`}>{fmtRelative(new Date(String(r.last_seen_at)))}</td>90 </tr>91 ))}92 </tbody>93 </Table>94 )}95 </Card>96 </>97 );98}99