SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
5.0 KB · 99 lines tsx
Raw Blame History
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