Files
shadow-gunbuilder-ai-proto/app/login/page.tsx
T

189 lines
5.5 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.
// app/login/page.tsx
"use client";
import { FormEvent, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { useAuth } from "@/context/AuthContext";
import { Button, Field, Input } from "@/components/ui/form";
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
export default function LoginPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { login, loading } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
// Magic link UI state (separate from password login)
const [magicLoading, setMagicLoading] = useState(false);
const [magicMessage, setMagicMessage] = useState<string | null>(null);
const [magicError, setMagicError] = useState<string | null>(null);
const next = useMemo(
() => searchParams.get("next") || "/builder",
[searchParams]
);
async function handleSubmit(e: FormEvent) {
e.preventDefault();
setError(null);
try {
await login({ email, password });
router.push(next);
} catch (err: any) {
setError(err?.message ?? "Failed to log in");
}
}
async function handleMagicLinkRequest() {
setMagicMessage(null);
setMagicError(null);
const normalizedEmail = email.trim();
if (!normalizedEmail) {
setMagicError("Please enter your email above first.");
return;
}
setMagicLoading(true);
try {
// ✅ This endpoint exists in your AuthController:
// POST /api/auth/beta/signup { email, useCase }
const res = await fetch(`${API_BASE_URL}/api/auth/beta/signup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: normalizedEmail,
useCase: "login_magic",
}),
});
// Soft-fail (avoid email enumeration)
if (!res.ok) {
const text = await res.text().catch(() => "");
console.error("Magic link request failed:", res.status, text);
}
setMagicMessage(
"If that email is eligible, youll get a sign-in link shortly ✨"
);
} catch (e) {
console.error(e);
// Soft-fail
setMagicMessage(
"If that email is eligible, youll get a sign-in link shortly ✨"
);
} finally {
setMagicLoading(false);
}
}
return (
<main className="min-h-screen bg-black text-zinc-50">
<div className="mx-auto flex max-w-md flex-col px-4 py-10">
<h1 className="text-2xl font-semibold tracking-tight">
Log In to <span className="text-amber-300">The Armory</span>
</h1>
<p className="mt-2 text-sm text-zinc-400">
Use your beta credentials to get back to your saved builds.
</p>
{/* Password Login */}
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
{error && (
<div className="rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-sm text-red-200">
{error}
</div>
)}
<Field label="Email" htmlFor="email">
<Input
id="email"
type="email"
autoComplete="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</Field>
<Field label="Password" htmlFor="password">
<Input
id="password"
type="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</Field>
<Button type="submit" disabled={loading}>
{loading ? "Signing in…" : "Log In"}
</Button>
</form>
{/* Divider */}
<div className="mt-8 flex items-center gap-3">
<div className="h-px flex-1 bg-white/10" />
<span className="text-xs text-zinc-500">or</span>
<div className="h-px flex-1 bg-white/10" />
</div>
{/* Magic Link Request */}
<div className="mt-6 space-y-3">
<div>
<h2 className="text-sm font-medium text-zinc-200">
Already a beta user?
</h2>
<p className="mt-1 text-xs text-zinc-500">
Request a one-time sign-in link (no password needed).
</p>
</div>
{magicError && (
<div className="rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2 text-sm text-red-200">
{magicError}
</div>
)}
{magicMessage && (
<div className="rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-200">
{magicMessage}
</div>
)}
<Button
type="button"
disabled={magicLoading}
onClick={handleMagicLinkRequest}
>
{magicLoading ? "Sending link…" : "Email me a sign-in link"}
</Button>
<p className="text-[11px] text-zinc-500">
Tip: check spam/promotions. Links expire.
</p>
</div>
<p className="mt-8 text-xs text-zinc-500">
New here?{" "}
<Link href="/register" className="text-amber-300 hover:text-amber-200">
Join the beta
</Link>
.
</p>
</div>
</main>
);
}