import Link from 'next/link';
import type { AssetCard as AssetCardData, SaleRow } from '@/lib/queries/assets';
import type { RadarRow, LotRow } from '@/lib/queries/market-lists';
import type { LatestSale } from '@/lib/queries/site';
import type { MarketRow } from '@/lib/queries/markets';
import { fmtMoney, fmtRelative, fmtNum, cn, fmtDate } from '@/lib/format';
import { catName, humanize } from '@/lib/taxonomy';
import { Card, CardHeader, Delta, EmptyState } from '@/components/ui/primitives';
import { GradeBadge, SourceLink, Thumb } from '@/components/market/bits';
import { heatStyle } from '@/components/charts/heat';
function Row({ href, image, title, sub, right, rightSub }: { href: string; image?: string | null; title: string; sub?: string | null; right: React.ReactNode; rightSub?: React.ReactNode }) {
return (
{title}
{sub ? {sub} : null}
{right}
{rightSub ? {rightSub} : null}
);
}
const EMPTY = 'Populates as the pipeline ingests data.';
export function AssetRail({ title, subtitle, href, items, metric, empty = EMPTY }: { title: string; subtitle?: string; href: string; items: AssetCardData[]; metric: 'change7d' | 'change1d' | 'change30d' | 'trending' | 'watchers' | 'riv' | 'opportunity' | 'new' | 'guide'; empty?: string }) {
return (
View all →} />
{items.length ? (
{items.map((a) => (
{fmtMoney(a.guideUsd)} : —) : fmtMoney(a.rivUsd)}
rightSub={
metric === 'guide' ? {a.rivUsd === null && a.guideUsd != null ? `guide · ${a.guideSource ?? ''}` : a.rivUsd !== null ? `RIV · ${a.rivSampleSize} sales` : 'catalogued'}
: metric === 'trending' ? score {a.trendingScore?.toFixed(1) ?? '—'}
: metric === 'watchers' ? {fmtNum(a.watchers)} watching
: metric === 'new' ? added {fmtRelative(a.updatedAt)}
: metric === 'riv' ? {a.rivSampleSize} sales
: metric === 'opportunity' ?
:
}
/>
))}
) : (
)}
);
}
export function SalesRail({ title, subtitle, href, items, hideWhenEmpty = false }: { title: string; subtitle?: string; href: string; items: SaleRow[]; hideWhenEmpty?: boolean }) {
if (hideWhenEmpty && !items.length) return null;
return (
View all →} />
{items.length ? (
{items.map((s) => (
} />
))}
) : (
)}
);
}
export function RadarRail({ items, hideWhenEmpty = false }: { items: RadarRow[]; hideWhenEmpty?: boolean }) {
if (hideWhenEmpty && !items.length) return null;
return (
Open radar →} />
{items.length ? (
{items.map((r) => (
— : fmtMoney(r.rivUsd)} rightSub={signal {r.score.toFixed(0)}} />
))}
) : (
)}
);
}
export function LotsRail({ items, hideWhenEmpty = false }: { items: LotRow[]; hideWhenEmpty?: boolean }) {
if (hideWhenEmpty && !items.length) return null;
return (
All auctions →} />
{items.length ? (
) : (
)}
);
}
export function MarketInsights({ rows }: { rows: MarketRow[] }) {
const withData = rows.filter((r) => r.counts.assets > 0);
return (
Markets →} />
{withData.length ? (
{withData.slice(0, 10).map((r) => (
-
{r.node.name}
{fmtNum(r.counts.assets)} assets · {fmtNum(r.counts.sales)} sales
))}
) : (
)}
);
}
export interface LatestObservation {
id: string;
assetSlug: string;
assetTitle: string;
categorySlug: string;
heroImageUrl: string | null;
priceUsd: number;
priceKind: string;
observationDate: string;
sourceName: string;
}
export function LatestTransactions({ items, observations = [] }: { items: LatestSale[]; observations?: LatestObservation[] }) {
if (!items.length && observations.length) {
return (
Browse →} />
{observations.map((o) => (
-
{o.assetTitle}
{catName(o.categorySlug)} · {o.sourceName} · {humanize(o.priceKind)} · {fmtDate(o.observationDate)}
{fmtMoney(o.priceUsd)}
))}
);
}
return (
All sales →} />
{items.length ? (
{items.map((s) => (
-
{s.assetTitle}
{fmtDate(s.saleDate)} ·
{fmtMoney(s.priceUsd)}
))}
) : (
)}
);
}
export function cls(...c: Array): string {
return cn(...c);
}