Files
dark-lord/app/sign-up/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

119 lines
4.4 KiB
TypeScript

"use client";
import { FormEvent, useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useAccount } from "@/contexts/AccountContext";
export default function SignUpPage() {
const router = useRouter();
const { user, hydrated, signUp } = useAccount();
const [username, setUsername] = useState("");
const [displayName, setDisplayName] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
useEffect(() => {
if (hydrated && user) router.replace("/dashboard");
}, [hydrated, user, router]);
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
setBusy(true);
const res = await signUp(username, password, displayName || undefined);
setBusy(false);
if (!res.ok) {
setError(res.error);
return;
}
router.push("/dashboard");
};
if (!hydrated || user) {
return (
<div className="flex min-h-[50vh] items-center justify-center font-mono text-sm text-zinc-500">
Loading
</div>
);
}
return (
<div className="min-h-screen bg-[#0a0a0a] px-4 py-16 text-zinc-200">
<div className="mx-auto max-w-md">
<p className="font-mono text-[10px] uppercase tracking-[0.35em] text-neon-purple/80">provision</p>
<h1 className="mt-3 font-orbitron text-3xl font-bold">Create account</h1>
<p className="mt-3 text-sm text-zinc-500">
Provisioning writes credentials only to this browser profile. Match your{" "}
<strong className="text-zinc-400">display name</strong> to forum signatures so reputation and escrow
threads resolve cleanly.
</p>
<p className="mt-3 text-sm text-zinc-500">
Separate forum / exchange / wiki onions each need their own copy:{" "}
<Link href="/account/hidden-services" className="text-neon-cyan hover:underline">
Hidden services account guide
</Link>
.
</p>
<form onSubmit={onSubmit} className="glass mt-10 space-y-5 rounded-2xl border border-white/10 p-8">
{error ? (
<p className="rounded-lg border border-red-500/40 bg-red-500/10 px-4 py-2 text-sm text-red-300">{error}</p>
) : null}
<div>
<label className="mb-2 block text-xs font-medium text-zinc-400">Handle (unique, login id)</label>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username"
required
className="glass w-full rounded-xl border border-white/10 bg-transparent px-4 py-3 font-mono text-sm"
placeholder="ledger_rat"
/>
</div>
<div>
<label className="mb-2 block text-xs font-medium text-zinc-400">Display name (shown on posts)</label>
<input
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
className="glass w-full rounded-xl border border-white/10 bg-transparent px-4 py-3 font-mono text-sm"
placeholder="Defaults to handle if empty"
/>
</div>
<div>
<label className="mb-2 block text-xs font-medium text-zinc-400">Passphrase (8+ characters)</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="new-password"
required
className="glass w-full rounded-xl border border-white/10 bg-transparent px-4 py-3 font-mono text-sm"
/>
</div>
<button
type="submit"
disabled={busy}
className="w-full rounded-xl bg-gradient-to-r from-neon-purple to-neon-pink py-3 font-bold text-white disabled:opacity-50"
>
{busy ? "Provisioning…" : "Create & enter"}
</button>
</form>
<p className="mt-8 text-center text-sm text-zinc-500">
Already have a handle?{" "}
<Link href="/sign-in" className="text-neon-cyan hover:underline">
Sign in
</Link>
</p>
<p className="mt-6 text-center">
<Link href="/" className="text-xs text-zinc-600 hover:text-zinc-400">
Hub
</Link>
</p>
</div>
</div>
);
}