1import type { Metadata } from 'next';2import Link from 'next/link';3import { PageHeader } from '@/components/ui/page-header';4import { Card, CardHeader, EmptyState, Table, th, td, tdNum, Badge } from '@/components/ui/primitives';5import { Tabs } from '@/components/ui/tabs';6import { LotsTable } from '@/components/market/sales-table';7import { listAuctionHouses, listAuctions, listLots } from '@/lib/queries/market-lists';8import { fmtDate, fmtNum, fmtRelative, cn } from '@/lib/format';9import { catName } from '@/lib/taxonomy';10import { spEnum, type SP } from '@/lib/search-params';1112export const metadata: Metadata = { title: 'Auctions', description: 'Auction houses, live and upcoming lots, ending-soon lots and results across the collectibles market.' };1314export default async function AuctionsPage({ searchParams }: { searchParams: Promise<SP> }) {15 const sp = await searchParams;16 const tab = spEnum(sp, 'tab', ['ending', 'live', 'upcoming', 'results', 'houses'] as const, 'ending');17 const [houses, ending, lots, auctions] = await Promise.all([18 listAuctionHouses(),19 listLots({ endingWithinHours: 72, limit: 60 }),20 tab === 'results' ? listLots({ status: 'ended', limit: 60, sort: 'value' }) : tab === 'live' ? listLots({ status: 'live', limit: 60 }) : tab === 'upcoming' ? listLots({ status: 'upcoming', limit: 60 }) : Promise.resolve([]),21 listAuctions({ limit: 100 }),22 ]);23 return (24 <div>25 <PageHeader kicker="Auction houses"26 title="Auctions"27 description="Lots aggregated from auction-house connectors. Estimates and bids are the house’s own figures; hammer prices flow into Sales once an auction ends and the lot is matched to a canonical asset."28 compact29 actions={30 <Link href="/auctions/calendar" className="rounded-md border border-border px-2.5 py-1.5 text-xs font-medium hover:bg-inset">31 Auction calendar →32 </Link>33 }34 />35 <Tabs36 active={tab}37 tabs={[38 { id: 'ending', label: 'Ending soon', count: ending.length, href: '/auctions?tab=ending' },39 { id: 'live', label: 'Live', href: '/auctions?tab=live' },40 { id: 'upcoming', label: 'Upcoming', href: '/auctions?tab=upcoming' },41 { id: 'results', label: 'Results', href: '/auctions?tab=results' },42 { id: 'houses', label: 'Auction houses', count: houses.length, href: '/auctions?tab=houses' },43 ]}44 className="mb-4"45 />46 {tab === 'houses' ? (47 <Card className="overflow-hidden">48 <CardHeader title="Auction houses" subtitle="Sources with auction connectors" />49 {houses.length ? (50 <Table>51 <thead>52 <tr>53 <th className={th}>House</th>54 <th className={cn(th, 'text-right')}>Auctions</th>55 <th className={cn(th, 'text-right')}>Lots tracked</th>56 <th className={cn(th, 'text-right')}>Open</th>57 </tr>58 </thead>59 <tbody>60 {houses.map((h) => (61 <tr key={h.house}>62 <td className={cn(td, 'font-medium')}>{h.house}</td>63 <td className={tdNum}>{fmtNum(h.auctions)}</td>64 <td className={tdNum}>{fmtNum(h.lots)}</td>65 <td className={tdNum}>{fmtNum(h.upcoming)}</td>66 </tr>67 ))}68 </tbody>69 </Table>70 ) : (71 <EmptyState title="No auction houses connected yet" description="Auction-house connectors (results and catalogues) are part of the Phase 1 connector roadmap." />72 )}73 </Card>74 ) : (75 <div className="grid gap-4 xl:grid-cols-[2fr_1fr]">76 <Card className="overflow-hidden">77 <CardHeader title={tab === 'ending' ? 'Lots ending in the next 72 hours' : tab === 'results' ? 'Recent results by value' : `${tab[0]!.toUpperCase()}${tab.slice(1)} lots`} />78 <LotsTable items={tab === 'ending' ? ending : lots} />79 </Card>80 <Card className="overflow-hidden">81 <CardHeader title="Auctions" subtitle="Catalogues tracked" />82 {auctions.length ? (83 <ul className="divide-y divide-border text-[12px]">84 {auctions.slice(0, 40).map((a) => (85 <li key={a.id} className="px-4 py-2">86 <a href={a.url} target="_blank" rel="noopener nofollow" className="font-medium text-fg hover:underline">87 {a.name}88 </a>89 <div className="mt-0.5 flex flex-wrap items-center gap-1.5 text-[11px] text-muted">90 <Badge tone={a.status === 'live' ? 'gain' : a.status === 'upcoming' ? 'index' : 'neutral'}>{a.status}</Badge>91 <span>{a.auctionHouse}</span>92 <span>· {a.endsAt ? `ends ${fmtDate(a.endsAt)} (${fmtRelative(a.endsAt)})` : a.startsAt ? `starts ${fmtDate(a.startsAt)}` : 'dates n/a'}</span>93 <span>· {a.lotsTracked} lots{a.lotCount ? ` / ${a.lotCount}` : ''}</span>94 {a.categorySlugs.slice(0, 2).map((c) => (95 <span key={c}>· {catName(c)}</span>96 ))}97 </div>98 </li>99 ))}100 </ul>101 ) : (102 <EmptyState title="No auctions tracked" className="py-8" />103 )}104 </Card>105 </div>106 )}107 </div>108 );109}110