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%
4.6 KB · 80 lines tsx
Raw Blame History
1import type { SaleRow, ListingRow } from '@/lib/queries/assets';2import { fmtDate, fmtMoney, fmtRelative, cn } from '@/lib/format';3import { humanize } from '@/lib/taxonomy';4import { EmptyState, VsRiv } from '@/components/ui/primitives';5import { FeeBasisPill, GradeBadge, SourceLink, Thumb, VerificationBadge } 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">{s.allInUsd !== null && Math.abs(s.allInUsd - s.priceUsd) >= 0.5 ? `${s.feeBasis === 'added_approximate' || s.feeBasis === 'added_default' ? '≈ ' : ''}${fmtMoney(s.allInUsd)}` : 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.allInUsd !== null && Math.abs(s.allInUsd - s.priceUsd) >= 0.5 ? (23              <div className="num flex items-center gap-1.5 text-[10px] text-subtle">24                <span>{fmtMoney(s.priceUsd)} hammer{s.price !== null && s.currency && s.currency !== 'USD' ? ` · ${fmtMoney(s.price, s.currency)}` : ''}</span>25                <FeeBasisPill basis={s.feeBasis} rate={s.buyerPremiumRate} className="text-[9px]" />26              </div>27            ) : s.price !== null && s.currency && s.currency !== 'USD' ? (28              <div className="num text-[10px] text-subtle">{fmtMoney(s.price, s.currency)} native</div>29            ) : null}30            <p className="mt-0.5 truncate text-[12px] text-muted" title={s.rawTitle}>31              {s.rawTitle}32            </p>33            <div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted">34              <GradeBadge grader={s.grader} grade={s.grade} />35              {s.condition ? <span>{humanize(s.condition)}</span> : null}36              <span>{humanize(s.saleType)}</span>37              <SourceLink name={s.sourceName} url={s.sourceUrl} />38              <VerificationBadge label={s.verification} reason={s.verificationReason} />39            </div>40          </div>41        </li>42      ))}43    </ul>44  );45}4647export function ListingsCards({ items, className, emptyDescription = 'Live listings appear once marketplace connectors run.' }: { items: ListingRow[]; className?: string; emptyDescription?: string }) {48  if (!items.length) return <EmptyState title="No active listings" description={emptyDescription} />;49  return (50    <ul className={cn('divide-y divide-border', className)}>51      {items.map((l) => (52        <li key={l.id} className="flex items-start gap-3 px-3 py-2.5">53          <Thumb src={l.imageUrls[0]} alt="" size={44} rounded="rounded-md" />54          <div className="min-w-0 flex-1">55            <div className="flex items-baseline justify-between gap-2">56              <span className="num text-[15px] font-semibold text-fg">{fmtMoney(l.priceUsd)}</span>57              {l.discountToRiv !== null ? (58                <VsRiv discount={l.discountToRiv} className="text-[11px]" />59              ) : (60                <span className="text-[11px] text-subtle">{fmtRelative(l.lastSeenAt)}</span>61              )}62            </div>63            {l.price !== null && l.currency && l.currency !== 'USD' ? <div className="num text-[10px] text-subtle">{fmtMoney(l.price, l.currency)} native</div> : null}64            <p className="mt-0.5 truncate text-[12px] text-muted" title={l.rawTitle}>65              {l.rawTitle}66            </p>67            <div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted">68              <GradeBadge grader={l.grader} grade={l.grade} />69              <span>{humanize(l.listingType)}</span>70              {l.seller ? <span className="truncate">{l.seller}</span> : null}71              {l.location ? <span className="text-subtle">{l.location}</span> : null}72              <SourceLink name={l.sourceName} url={l.sourceUrl} />73            </div>74          </div>75        </li>76      ))}77    </ul>78  );79}80