Files
dark-lord/app/sign-in/page.tsx
2026-04-26 22:28:40 -07:00

130 lines
4.5 KiB
TypeScript

"use client";
import { FormEvent, Suspense, useEffect, useState } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { useAccount } from "@/contexts/AccountContext";
function SignInContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { user, hydrated, signIn } = useAccount();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
useEffect(() => {
if (!hydrated || !user) return;
const next = searchParams.get("next");
router.replace(next && next.startsWith("/") ? next : "/dashboard");
}, [hydrated, user, router, searchParams]);
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
setBusy(true);
const res = await signIn(username, password);
setBusy(false);
if (!res.ok) {
setError(res.error);
return;
}
const next = searchParams.get("next");
router.push(next && next.startsWith("/") ? next : "/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-cyan/80">session</p>
<h1 className="mt-3 font-orbitron text-3xl font-bold">Sign in</h1>
<p className="mt-3 text-sm text-zinc-500">
Client-side vault unlock your handle ties together forum posts, listings, and checkout history on{" "}
<span className="text-zinc-400">this device</span>. Use a unique passphrase; staff never log access to
your secrets.
</p>
<p className="mt-3 text-sm text-zinc-500">
Using a different .onion host?{" "}
<Link href="/account/hidden-services" className="text-neon-cyan hover:underline">
Register or import on every mirror
</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</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="your_alias"
/>
</div>
<div>
<label className="mb-2 block text-xs font-medium text-zinc-400">Passphrase</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-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-cyan to-neon-purple py-3 font-bold text-background disabled:opacity-50"
>
{busy ? "Unlocking…" : "Unlock dashboard"}
</button>
</form>
<p className="mt-8 text-center text-sm text-zinc-500">
Need an identity?{" "}
<Link
href={searchParams.get("next") ? `/sign-up?next=${encodeURIComponent(searchParams.get("next")!)}` : "/sign-up"}
className="text-neon-cyan hover:underline"
>
Create account
</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>
);
}
export default function SignInPage() {
return (
<Suspense
fallback={
<div className="flex min-h-[50vh] items-center justify-center font-mono text-sm text-zinc-500">
Loading
</div>
}
>
<SignInContent />
</Suspense>
);
}