Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation. Made-with: Cursor
79 lines
3.4 KiB
TypeScript
79 lines
3.4 KiB
TypeScript
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import { VendorShelfRow } from "@/components/shop/VendorShelfRow";
|
|
import { shopProductsForSeller } from "@/lib/shopCatalog";
|
|
import { SHOP_SELLERS, getSellerBySlug } from "@/lib/shopSellers";
|
|
|
|
export function generateStaticParams() {
|
|
return SHOP_SELLERS.map((s) => ({ slug: s.slug }));
|
|
}
|
|
|
|
type Props = { params: Promise<{ slug: string }> };
|
|
|
|
export default async function VendorProfilePage({ params }: Props) {
|
|
const { slug } = await params;
|
|
const v = getSellerBySlug(slug);
|
|
if (!v) notFound();
|
|
|
|
const stock = shopProductsForSeller(v.id);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#050302] text-[#f5f0e8]">
|
|
<header className="border-b border-orange-950/60 bg-[#0a0705]">
|
|
<div className="mx-auto max-w-4xl px-4 py-10">
|
|
<nav className="mb-6 flex flex-wrap gap-2 font-mono text-[11px] text-orange-300/60">
|
|
<Link href="/vendors" className="hover:text-orange-100">
|
|
← Vendor hall
|
|
</Link>
|
|
<span className="text-orange-900">/</span>
|
|
<Link href="/market" className="hover:text-orange-100">
|
|
Catalog
|
|
</Link>
|
|
<span className="text-orange-900">/</span>
|
|
<Link href="/" className="hover:text-orange-100">
|
|
Hub
|
|
</Link>
|
|
</nav>
|
|
<div className="flex flex-wrap items-baseline gap-3">
|
|
<h1 className="font-orbitron text-3xl font-black text-orange-50 md:text-4xl">{v.handle}</h1>
|
|
<span className="rounded-full border border-orange-500/40 bg-orange-500/10 px-3 py-0.5 font-mono text-xs text-orange-200">
|
|
since {v.since}
|
|
</span>
|
|
</div>
|
|
<p className="mt-2 font-mono text-sm text-orange-400/90">{v.tagline}</p>
|
|
<p className="mt-4 max-w-2xl text-sm leading-relaxed text-orange-100/75">{v.bio}</p>
|
|
<dl className="mt-6 grid gap-3 font-mono text-xs sm:grid-cols-4">
|
|
<div className="rounded border border-orange-900/40 bg-black/30 px-3 py-2">
|
|
<dt className="text-orange-500/60">Rating</dt>
|
|
<dd className="text-orange-100">★ {v.rating.toFixed(2)}</dd>
|
|
</div>
|
|
<div className="rounded border border-orange-900/40 bg-black/30 px-3 py-2">
|
|
<dt className="text-orange-500/60">Lore sales</dt>
|
|
<dd className="text-orange-100">{v.completedSales.toLocaleString()}</dd>
|
|
</div>
|
|
<div className="rounded border border-orange-900/40 bg-black/30 px-3 py-2">
|
|
<dt className="text-orange-500/60">Typical reply</dt>
|
|
<dd className="text-orange-100">~{v.responseMins}m</dd>
|
|
</div>
|
|
<div className="rounded border border-orange-900/40 bg-black/30 px-3 py-2">
|
|
<dt className="text-orange-500/60">Focus</dt>
|
|
<dd className="text-orange-100">{v.specialty}</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="mx-auto max-w-4xl px-4 py-10">
|
|
<h2 className="font-mono text-[11px] font-bold uppercase tracking-[0.3em] text-orange-500/70">
|
|
Listings on this stall · {stock.length}
|
|
</h2>
|
|
<ul className="mt-6 space-y-4">
|
|
{stock.map((p) => (
|
|
<VendorShelfRow key={p.id} product={p} />
|
|
))}
|
|
</ul>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|