SPB Git

spb/rareindex

Public
4.1 KB · 76 lines tsx
1import type { SaleRow, ListingRow } from '@/lib/queries/assets';2import { fmtDate, fmtMoney, fmtRelative, cn } from '@/lib/format';3import { humanize } from '@/lib/taxonomy';4import { Badge, Delta, EmptyState } from '@/components/ui/primitives';5import { GradeBadge, SourceLink, Thumb } from '@/components/market/bits';67/** Phone-first card lists for sales and listings (tables stay for md+). */8export function SalesCards({ items, className, emptyDescription = 'Sales appear once connectors ingest verified transactions.' }: { items: SaleRow[]; className?: string; emptyDescription?: string }) {9  if (!items.length) return <EmptyState title="No sales recorded" description={emptyDescription} />;10  return (11    <ul className={cn('divide-y divide-border', className)}>12      {items.map((s) => (13        <li key={s.id} className="flex items-start gap-3 px-3 py-2.5">14          <Thumb src={s.imageUrls[0]} alt="" size={44} rounded="rounded-md" />15          <div className="min-w-0 flex-1">16            <div className="flex items-baseline justify-between gap-2">17              <span className="num text-[15px] font-semibold text-fg">{fmtMoney(s.priceUsd)}</span>18              <span className="text-[11px] text-muted" title={s.saleDate.toISOString()}>19                {fmtDate(s.saleDate)}20              </span>21            </div>22            {s.price !== null && s.currency && s.currency !== 'USD' ? <div className="num text-[10px] text-subtle">{fmtMoney(s.price, s.currency)} native</div> : null}23            <p className="mt-0.5 truncate text-[12px] text-muted" title={s.rawTitle}>24              {s.rawTitle}25            </p>26            <div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted">27              <GradeBadge grader={s.grader} grade={s.grade} />28              {s.condition ? <span>{humanize(s.condition)}</span> : null}29              <span>{humanize(s.saleType)}</span>30              <SourceLink name={s.sourceName} url={s.sourceUrl} />31              {s.status !== 'valid' ? <Badge tone="alert">{s.status}</Badge> : null}32            </div>33          </div>34        </li>35      ))}36    </ul>37  );38}3940export function ListingsCards({ items, className, emptyDescription = 'Live listings appear once marketplace connectors run.' }: { items: ListingRow[]; className?: string; emptyDescription?: string }) {41  if (!items.length) return <EmptyState title="No active listings" description={emptyDescription} />;42  return (43    <ul className={cn('divide-y divide-border', className)}>44      {items.map((l) => (45        <li key={l.id} className="flex items-start gap-3 px-3 py-2.5">46          <Thumb src={l.imageUrls[0]} alt="" size={44} rounded="rounded-md" />47          <div className="min-w-0 flex-1">48            <div className="flex items-baseline justify-between gap-2">49              <span className="num text-[15px] font-semibold text-fg">{fmtMoney(l.priceUsd)}</span>50              {l.discountToRiv !== null ? (51                <span className="inline-flex items-center gap-1 text-[11px]">52                  <span className="text-subtle">vs RIV</span>53                  <Delta value={-l.discountToRiv} />54                </span>55              ) : (56                <span className="text-[11px] text-subtle">{fmtRelative(l.lastSeenAt)}</span>57              )}58            </div>59            {l.price !== null && l.currency && l.currency !== 'USD' ? <div className="num text-[10px] text-subtle">{fmtMoney(l.price, l.currency)} native</div> : null}60            <p className="mt-0.5 truncate text-[12px] text-muted" title={l.rawTitle}>61              {l.rawTitle}62            </p>63            <div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted">64              <GradeBadge grader={l.grader} grade={l.grade} />65              <span>{humanize(l.listingType)}</span>66              {l.seller ? <span className="truncate">{l.seller}</span> : null}67              {l.location ? <span className="text-subtle">{l.location}</span> : null}68              <SourceLink name={l.sourceName} url={l.sourceUrl} />69            </div>70          </div>71        </li>72      ))}73    </ul>74  );75}76
Files · f5091c0a3f32b0f99bc705c36f16b636342fbdb4
Menu

Theme