TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { Metadata } from 'next';2import { PageHeader } from '@/components/ui/page-header';3import { Tabs } from '@/components/ui/tabs';4import { Card, EmptyState, Badge } from '@/components/ui/primitives';5import { Pagination } from '@/components/ui/pagination';6import { listNews } from '@/lib/queries/market-lists';7import { fmtDate } from '@/lib/format';8import { catName, humanize } from '@/lib/taxonomy';9import { sp1, spInt, type SP } from '@/lib/search-params';1011export const metadata: Metadata = { title: 'Market news', description: 'Collectibles market news: auction results, record sales, grading changes, releases, trends, discoveries and events, summarised with links to the source.' };1213const TYPES = ['auction_results', 'record_sales', 'grading', 'releases', 'trends', 'discoveries', 'events'];1415export default async function NewsPage({ searchParams }: { searchParams: Promise<SP> }) {16 const sp = await searchParams;17 const type = sp1(sp, 'type') ?? null;18 const category = sp1(sp, 'category') ?? null;19 const page = spInt(sp, 'page');20 const res = await listNews({ type, category, page, limit: 40 });21 return (22 <div>23 <PageHeader kicker="Editorial" title="News" description="Aggregated from public sources with attribution. AI summaries, when present, always link to the original article and never replace it (§156)." compact />24 <Tabs active={type ?? 'all'} tabs={[{ id: 'all', label: 'All', href: '/news' }, ...TYPES.map((t) => ({ id: t, label: humanize(t), href: `/news?type=${t}` }))]} className="mb-4" />25 {res.items.length ? (26 <>27 <ul className="grid grid-cols-1 gap-2 md:grid-cols-2">28 {res.items.map((n) => (29 <li key={n.id} className="card p-3">30 <div className="flex items-center gap-2 text-[11px] text-muted">31 {n.newsType ? <Badge>{humanize(n.newsType)}</Badge> : null}32 <span>{n.sourceName}</span>33 <span>· {n.publishedAt ? fmtDate(n.publishedAt) : 'undated'}</span>34 </div>35 <a href={n.url} target="_blank" rel="noopener nofollow" className="mt-1 block text-[14px] font-medium text-fg hover:underline">36 {n.title}37 </a>38 {n.aiSummary || n.summary ? <p className="mt-1 line-clamp-3 text-[12px] leading-relaxed text-muted">{n.aiSummary ?? n.summary}</p> : null}39 {n.categorySlugs.length ? <div className="mt-1.5 text-[11px] text-subtle">{n.categorySlugs.map(catName).join(' · ')}</div> : null}40 </li>41 ))}42 </ul>43 <Pagination page={page} pageSize={40} total={res.total} basePath="/news" params={{ type: type ?? undefined, category: category ?? undefined }} />44 </>45 ) : (46 <Card>47 <EmptyState title="No news indexed yet" description="News connectors are not yet publishing. Nothing is shown until real articles are ingested." />48 </Card>49 )}50 </div>51 );52}53