Harden onion boot flow and deepen site surfaces
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
This commit is contained in:
78
app/vendor/[slug]/page.tsx
vendored
Normal file
78
app/vendor/[slug]/page.tsx
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user