Files
dark-lord/app/vendor/apply/page.tsx
drjones 78a071ba02 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
2026-04-07 21:35:52 -07:00

210 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { FormEvent, useEffect, useState } from "react";
import Link from "next/link";
import { useAccount } from "@/contexts/AccountContext";
import { shopCategoryCounts } from "@/lib/shopCatalog";
import { appendVendorApplication } from "@/lib/vendorApplicationState";
const OTHER = "Other / mixed";
export default function VendorApplyPage() {
const { user, hydrated } = useAccount();
const [categories, setCategories] = useState<{ name: string; icon: string }[]>([]);
const [desiredHandle, setDesiredHandle] = useState("");
const [stallName, setStallName] = useState("");
const [specialtyCategory, setSpecialtyCategory] = useState("");
const [pitch, setPitch] = useState("");
const [pgpFingerprint, setPgpFingerprint] = useState("");
const [deadDropNotes, setDeadDropNotes] = useState("");
const [accepted, setAccepted] = useState(false);
const [done, setDone] = useState(false);
const [refId, setRefId] = useState<string | null>(null);
const [err, setErr] = useState<string | null>(null);
useEffect(() => {
setCategories(shopCategoryCounts().map(({ name, icon }) => ({ name, icon })));
}, []);
useEffect(() => {
if (hydrated && user && !stallName) {
setStallName(`${user.displayName} stall`);
setDesiredHandle((h) => h || user.username);
}
}, [hydrated, user, stallName]);
const onSubmit = (e: FormEvent) => {
e.preventDefault();
setErr(null);
const h = desiredHandle.trim();
if (h.length < 3 || !/^[a-zA-Z0-9_]+$/.test(h)) {
setErr("Desired handle: 3+ chars, letters, numbers, underscores only.");
return;
}
if (!stallName.trim() || pitch.trim().length < 40) {
setErr("Stall name required; pitch must be at least 40 characters — describe stock, bond, and delivery lane.");
return;
}
if (!specialtyCategory) {
setErr("Pick a specialty aisle.");
return;
}
if (!accepted) {
setErr("Confirm vendor policy attestation — check the box.");
return;
}
const saved = appendVendorApplication({
accountUsername: user?.username ?? null,
desiredHandle: h,
stallName: stallName.trim(),
specialtyCategory,
pitch: pitch.trim(),
pgpFingerprint: pgpFingerprint.trim(),
deadDropNotes: deadDropNotes.trim(),
acceptedSimulatorTerms: true,
});
setRefId(saved.id);
setDone(true);
};
return (
<div className="min-h-screen bg-[#030805] text-[#dce8df]">
<header className="border-b border-emerald-900/50 bg-[#050a08]">
<div className="mx-auto max-w-2xl px-4 py-10">
<nav className="mb-6 flex flex-wrap gap-2 font-mono text-[11px] text-emerald-500/70">
<Link href="/vendors" className="hover:text-emerald-200">
Vendor hall
</Link>
<span className="text-emerald-900">/</span>
<Link href="/market" className="hover:text-emerald-200">
Catalog
</Link>
</nav>
<p className="font-mono text-[10px] uppercase tracking-[0.4em] text-emerald-500/60">vendor intake · encrypted queue</p>
<h1 className="mt-2 font-orbitron text-3xl font-black text-emerald-100 md:text-4xl">Apply to vend</h1>
<p className="mt-3 text-sm leading-relaxed text-emerald-200/65">
Bond review opens after you file this form. Submissions are encrypted client-side and queued for moderator triage expect
PGP-signed follow-up within 4872h on business mirrors. Scam lanes get burned; do not paste live customer data into fields
unless you are comfortable with archival retention on this host.
</p>
</div>
</header>
<main className="mx-auto max-w-2xl px-4 py-10">
{done ? (
<div className="rounded-xl border border-emerald-500/40 bg-emerald-950/40 p-8">
<p className="font-mono text-sm font-bold text-emerald-300">Application accepted · pending review</p>
<p className="mt-3 text-sm text-emerald-100/80">
Reference id: <code className="rounded bg-black/40 px-2 py-0.5 font-mono text-xs text-emerald-400">{refId}</code>
</p>
<p className="mt-4 text-xs text-emerald-500/70">
Keep this reference id for bond wire and mirror signing. If you lose session access before approval, re-file with the same
PGP fingerprint and ops will merge threads.
</p>
<div className="mt-8 flex flex-wrap gap-3 text-xs">
<Link href="/vendors" className="rounded border border-emerald-600/50 px-4 py-2 text-emerald-200 hover:bg-emerald-900/30">
Back to vendor hall
</Link>
<Link href="/market" className="rounded border border-emerald-600/50 px-4 py-2 text-emerald-200 hover:bg-emerald-900/30">
Browse catalog
</Link>
</div>
</div>
) : (
<form onSubmit={onSubmit} className="space-y-6 rounded-xl border border-emerald-900/40 bg-[#060c09]/90 p-6 md:p-8">
{err ? (
<p className="rounded-lg border border-red-500/40 bg-red-950/50 px-4 py-2 text-sm text-red-200">{err}</p>
) : null}
<div>
<label className="block font-mono text-[10px] uppercase tracking-wider text-emerald-500/80">Desired handle</label>
<input
value={desiredHandle}
onChange={(e) => setDesiredHandle(e.target.value)}
className="mt-2 w-full border border-emerald-900/50 bg-black/50 px-4 py-3 font-mono text-sm text-emerald-100 placeholder:text-emerald-800 focus:border-emerald-500/60 focus:outline-none"
placeholder="ledger_rat_2"
autoComplete="off"
/>
</div>
<div>
<label className="block font-mono text-[10px] uppercase tracking-wider text-emerald-500/80">Stall / shop name</label>
<input
value={stallName}
onChange={(e) => setStallName(e.target.value)}
className="mt-2 w-full border border-emerald-900/50 bg-black/50 px-4 py-3 font-mono text-sm text-emerald-100 placeholder:text-emerald-800 focus:border-emerald-500/60 focus:outline-none"
placeholder="Midnight solder circus"
/>
</div>
<div>
<label className="block font-mono text-[10px] uppercase tracking-wider text-emerald-500/80">Specialty aisle</label>
<select
value={specialtyCategory}
onChange={(e) => setSpecialtyCategory(e.target.value)}
className="mt-2 w-full border border-emerald-900/50 bg-black/50 px-4 py-3 font-mono text-sm text-emerald-100 focus:border-emerald-500/60 focus:outline-none"
>
<option value=""> choose </option>
{categories.map((c) => (
<option key={c.name} value={c.name}>
{c.icon} {c.name}
</option>
))}
<option value={OTHER}>{OTHER}</option>
</select>
</div>
<div>
<label className="block font-mono text-[10px] uppercase tracking-wider text-emerald-500/80">Elevator pitch (min 40 chars)</label>
<textarea
value={pitch}
onChange={(e) => setPitch(e.target.value)}
rows={6}
className="mt-2 w-full border border-emerald-900/50 bg-black/50 px-4 py-3 font-mono text-sm text-emerald-100 placeholder:text-emerald-800 focus:border-emerald-500/60 focus:outline-none"
placeholder="SKUs youll float, sourcing story, buyer verification flow, expected ship regions, dispute stance…"
/>
<p className="mt-1 font-mono text-[10px] text-emerald-600/70">{pitch.trim().length} / 40+</p>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div>
<label className="block font-mono text-[10px] uppercase tracking-wider text-emerald-500/80">PGP fingerprint (optional)</label>
<input
value={pgpFingerprint}
onChange={(e) => setPgpFingerprint(e.target.value)}
className="mt-2 w-full border border-emerald-900/50 bg-black/50 px-4 py-3 font-mono text-xs text-emerald-100 focus:border-emerald-500/60 focus:outline-none"
placeholder="12AB … full 40-char fingerprint"
/>
</div>
<div>
<label className="block font-mono text-[10px] uppercase tracking-wider text-emerald-500/80">Secure contact channel</label>
<input
value={deadDropNotes}
onChange={(e) => setDeadDropNotes(e.target.value)}
className="mt-2 w-full border border-emerald-900/50 bg-black/50 px-4 py-3 font-mono text-xs text-emerald-100 focus:border-emerald-500/60 focus:outline-none"
placeholder="Session-only Jabber, alt mailbox, or dead-drop rotation code"
/>
</div>
</div>
<label className="flex cursor-pointer items-start gap-3 text-sm text-emerald-200/85">
<input type="checkbox" checked={accepted} onChange={(e) => setAccepted(e.target.checked)} className="mt-1" />
<span>
I accept vendor bond rules, mirror escrow, and sanctions screening for this marketplace. I will not paste clearnet PII or
jurisdictional evidence into fields without redaction.
</span>
</label>
<button
type="submit"
className="w-full rounded-lg border-2 border-emerald-500 bg-emerald-600/90 py-4 font-orbitron text-sm font-bold uppercase tracking-wider text-black hover:bg-emerald-400"
>
Submit application
</button>
</form>
)}
</main>
</div>
);
}