- Replace fake-site footer with dark-web launch CTA; fix HubChatter inside AppProviders (client crash) - Add /launch, DEPLOY.md, onions:list|status|health, list-onion-urls, install-systemd, onion-status - start.sh CYBERLUX_PREPARE_ONLY; copy pass on satellite pages; Navbar launch link - README: operator cheat sheet, phone Tor note, systemd via install-systemd.sh Made-with: Cursor
113 lines
5.5 KiB
TypeScript
113 lines
5.5 KiB
TypeScript
import Link from "next/link";
|
|
import { SHOP_PRODUCTS } from "@/lib/shopCatalog";
|
|
import { SHOP_SELLERS, SHOP_SELLER_COUNT } from "@/lib/shopSellers";
|
|
|
|
export default function VendorsHallPage() {
|
|
const listingsByVendor = new Map<string, number>();
|
|
for (const p of SHOP_PRODUCTS) {
|
|
listingsByVendor.set(p.sellerId, (listingsByVendor.get(p.sellerId) ?? 0) + 1);
|
|
}
|
|
const totalSales = SHOP_SELLERS.reduce((a, s) => a + s.completedSales, 0);
|
|
const avgRating = SHOP_SELLERS.reduce((a, s) => a + s.rating, 0) / SHOP_SELLERS.length;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#060508] text-[#e8e4dc]">
|
|
<div className="border-b border-amber-900/40 bg-gradient-to-r from-[#1a0f08] via-[#120a06] to-[#1a0f08]">
|
|
<div className="mx-auto max-w-6xl px-4 py-10">
|
|
<p className="font-mono text-[10px] uppercase tracking-[0.45em] text-amber-500/70">bazaar density</p>
|
|
<h1 className="mt-2 font-orbitron text-3xl font-black text-amber-100 md:text-5xl">Vendor hall</h1>
|
|
<p className="mt-3 max-w-2xl text-sm text-amber-200/60">
|
|
<strong className="text-amber-300">{SHOP_SELLER_COUNT}</strong> vendor stalls fueling{" "}
|
|
<strong className="text-amber-300">{SHOP_PRODUCTS.length}</strong> SKUs — scroll the arcade, click a handle for their shelf.
|
|
</p>
|
|
<div className="mt-6 flex flex-wrap items-center gap-3">
|
|
<Link
|
|
href="/vendor/apply"
|
|
className="rounded border-2 border-amber-400/60 bg-amber-500/15 px-5 py-2.5 font-mono text-xs font-bold uppercase tracking-wider text-amber-100 hover:bg-amber-500/25"
|
|
>
|
|
Apply to open a stall →
|
|
</Link>
|
|
</div>
|
|
<div className="mt-6 flex flex-wrap gap-4 font-mono text-xs">
|
|
<div className="rounded border border-amber-700/40 bg-black/40 px-4 py-2">
|
|
<span className="text-amber-500/60">Aggregate sales (lore)</span>
|
|
<span className="ml-2 text-amber-200">{totalSales.toLocaleString()}</span>
|
|
</div>
|
|
<div className="rounded border border-amber-700/40 bg-black/40 px-4 py-2">
|
|
<span className="text-amber-500/60">Avg rating</span>
|
|
<span className="ml-2 text-amber-200">★ {avgRating.toFixed(2)}</span>
|
|
</div>
|
|
<div className="rounded border border-amber-700/40 bg-black/40 px-4 py-2">
|
|
<span className="text-amber-500/60">Median reply</span>
|
|
<span className="ml-2 text-amber-200">~42m</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-b border-amber-900/25 bg-[#0a0604] px-2 py-3">
|
|
<p className="mb-2 text-center font-mono text-[9px] uppercase tracking-[0.35em] text-amber-700/70">
|
|
Live handles · pick one
|
|
</p>
|
|
<div className="flex flex-wrap justify-center gap-x-3 gap-y-2 font-mono text-[11px]">
|
|
{SHOP_SELLERS.map((s) => (
|
|
<Link
|
|
key={s.id}
|
|
href={`/vendor/${s.slug}`}
|
|
className="rounded border border-orange-900/35 bg-black/30 px-2 py-1 text-amber-400/95 hover:border-amber-600/50 hover:text-amber-100"
|
|
>
|
|
{s.handle}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<main className="mx-auto max-w-6xl px-4 py-10">
|
|
<div className="mb-8 flex flex-wrap gap-3 text-xs font-mono">
|
|
<Link href="/" className="rounded border border-amber-800/50 px-3 py-2 text-amber-300 hover:bg-amber-950/80">
|
|
← Hub
|
|
</Link>
|
|
<Link href="/market" className="rounded border border-amber-800/50 px-3 py-2 text-amber-300 hover:bg-amber-950/80">
|
|
Full catalog
|
|
</Link>
|
|
<Link
|
|
href="/vendor/apply"
|
|
className="rounded border border-amber-400/50 px-3 py-2 text-amber-200 hover:bg-amber-950/80"
|
|
>
|
|
Apply to vend
|
|
</Link>
|
|
</div>
|
|
|
|
<ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
{SHOP_SELLERS.map((v) => {
|
|
const n = listingsByVendor.get(v.id) ?? 0;
|
|
return (
|
|
<li key={v.id}>
|
|
<Link
|
|
href={`/vendor/${v.slug}`}
|
|
className="flex h-full flex-col rounded-lg border border-amber-900/40 bg-[#0c0907] p-5 shadow-[inset_0_0_0_1px_rgba(251,191,36,0.05)] transition-colors hover:border-amber-600/50 hover:bg-[#100b08]"
|
|
>
|
|
<div className="flex items-start justify-between gap-2">
|
|
<h2 className="font-orbitron text-lg font-bold text-amber-100">{v.handle}</h2>
|
|
<span className="shrink-0 rounded bg-amber-500/15 px-2 py-0.5 font-mono text-[10px] text-amber-300">★{v.rating.toFixed(2)}</span>
|
|
</div>
|
|
<p className="mt-1 font-mono text-[10px] uppercase tracking-wider text-amber-600/80">{v.tagline}</p>
|
|
<p className="mt-3 flex-1 text-sm leading-relaxed text-amber-200/75">{v.bio}</p>
|
|
<div className="mt-4 flex flex-wrap gap-3 border-t border-amber-950/80 pt-3 font-mono text-[10px] text-amber-500/70">
|
|
<span>{n} listings here</span>
|
|
<span>·</span>
|
|
<span>{v.completedSales.toLocaleString()} lore sales</span>
|
|
<span>·</span>
|
|
<span>~{v.responseMins}m reply</span>
|
|
</div>
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</main>
|
|
|
|
</div>
|
|
);
|
|
}
|