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%
2.9 KB · 53 lines tsx
Raw Blame History
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