87 lines
3.5 KiB
TypeScript
87 lines
3.5 KiB
TypeScript
"use client";
|
|
import { Suspense, useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { useSearchParams, useRouter } from "next/navigation";
|
|
import AuthShell from "@/components/AuthShell";
|
|
import { api } from "@/lib/api";
|
|
|
|
function Inner() {
|
|
const sp = useSearchParams();
|
|
const r = useRouter();
|
|
const token = sp.get("token");
|
|
const email = sp.get("email") ?? "";
|
|
const [code, setCode] = useState("");
|
|
const [err, setErr] = useState("");
|
|
const [msg, setMsg] = useState("");
|
|
const [busy, setBusy] = useState(false);
|
|
const [linkState, setLinkState] = useState<"idle" | "ok" | "err">("idle");
|
|
|
|
// If arriving via the email link, verify by token automatically.
|
|
useEffect(() => {
|
|
if (token) {
|
|
api(`/auth/verify-email?token=${token}`)
|
|
.then(() => { setLinkState("ok"); setTimeout(() => r.push("/onboarding"), 1200); })
|
|
.catch(() => setLinkState("err"));
|
|
}
|
|
}, [token, r]);
|
|
|
|
async function submit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setErr(""); setBusy(true);
|
|
try {
|
|
await api("/auth/verify-code", { method: "POST", body: JSON.stringify({ email, code }) });
|
|
r.push("/onboarding");
|
|
} catch (e: any) {
|
|
setErr(typeof e.detail === "string" ? e.detail : "Verification failed");
|
|
} finally { setBusy(false); }
|
|
}
|
|
|
|
async function resend() {
|
|
setMsg(""); setErr("");
|
|
await api("/auth/resend-code", { method: "POST", body: JSON.stringify({ email }) }).catch(() => {});
|
|
setMsg("A new code has been sent.");
|
|
}
|
|
|
|
// Token-link flow display
|
|
if (token) {
|
|
return (
|
|
<AuthShell title="Verifying your email">
|
|
{linkState === "ok" && <p className="text-emerald-600 font-medium">✓ Verified. Redirecting…</p>}
|
|
{linkState === "err" && <p className="text-rose-600">This link is invalid or expired. Enter your code below instead.</p>}
|
|
{linkState === "idle" && <p className="text-slate-500">Verifying…</p>}
|
|
<Link href="/login" className="text-brand-600 text-sm mt-4 inline-block">Back to login</Link>
|
|
</AuthShell>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<AuthShell title="Verify your email" subtitle={email ? `We sent a 6-digit code to ${email}` : "Enter the 6-digit code we emailed you"}>
|
|
<form onSubmit={submit}>
|
|
<label className="block mb-4">
|
|
<span className="text-sm font-medium text-slate-700">Verification code</span>
|
|
<input value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
|
|
inputMode="numeric" placeholder="123456"
|
|
className="mt-1 w-full text-center tracking-[0.5em] text-lg px-3 py-3 rounded-lg border border-slate-200 focus:outline-none focus:ring-2 focus:ring-brand-500/30" />
|
|
</label>
|
|
{err && <p className="text-sm text-rose-600 mb-3">{err}</p>}
|
|
{msg && <p className="text-sm text-emerald-600 mb-3">{msg}</p>}
|
|
<button disabled={busy || code.length < 6}
|
|
className="w-full bg-brand-600 text-white py-3 rounded-full font-semibold disabled:opacity-50">
|
|
{busy ? "Verifying…" : "Verify email"}
|
|
</button>
|
|
</form>
|
|
<div className="flex justify-between mt-4 text-sm">
|
|
<button onClick={resend} className="text-brand-600">Resend code</button>
|
|
<Link href="/login" className="text-slate-500">Back to login</Link>
|
|
</div>
|
|
<p className="text-xs text-slate-400 mt-4">
|
|
In local dev (MOCK_EMAIL), the code is printed in the backend console.
|
|
</p>
|
|
</AuthShell>
|
|
);
|
|
}
|
|
|
|
export default function VerifyEmail() {
|
|
return <Suspense><Inner /></Suspense>;
|
|
}
|