Files
verify/frontend/app/login-otp/page.tsx
Mohamed Mathar Irfan ed6610d5d8 Initial project upload
2026-07-28 17:57:02 +05:30

165 lines
3.7 KiB
TypeScript

"use client";
import { Suspense, useState } from "react";
import Link from "next/link";
import { useSearchParams, useRouter } from "next/navigation";
import AuthShell from "@/components/AuthShell";
import { api, setToken } from "@/lib/api";
import { useLanguage } from "@/contexts/LanguageContext";
function Inner() {
const sp = useSearchParams();
const router = useRouter();
const { setLang } = useLanguage();
const email = sp.get("email") ?? "";
const [otp, setOtp] = useState("");
const [err, setErr] = useState("");
const [msg, setMsg] = useState("");
const [busy, setBusy] = useState(false);
async function submit(e: React.FormEvent) {
e.preventDefault();
setBusy(true);
setErr("");
try {
const res = await api("/auth/verify-login-otp", {
method: "POST",
body: JSON.stringify({
email,
otp,
}),
});
setToken(res.access_token);
const org = await api("/organization");
if (org.language) {
setLang(org.language);
localStorage.setItem("language", org.language);
}
if (org.timezone) {
localStorage.setItem("timezone", org.timezone);
}
const me = await api("/me");
if (me.is_product_admin) {
router.push("/admin");
} else if (!me.has_org) {
router.push("/onboarding");
} else {
router.push("/dashboard");
}
} catch (e: any) {
setErr(
typeof e.detail === "string"
? e.detail
: "Invalid or expired OTP"
);
} finally {
setBusy(false);
}
}
async function resend() {
setErr("");
setMsg("");
try {
await api("/auth/login", {
method: "POST",
body: JSON.stringify({
email,
resend: true,
}),
});
setMsg("A new OTP has been sent.");
} catch {
setErr("Unable to resend OTP.");
}
}
return (
<AuthShell
title="Verify Login"
subtitle={`Enter the 6-digit OTP sent to ${email}`}
>
<form onSubmit={submit}>
<label className="block mb-4">
<span className="text-sm font-medium text-slate-700">
Login OTP
</span>
<input
value={otp}
onChange={(e) =>
setOtp(
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 || otp.length < 6}
className="w-full bg-brand-600 text-white py-3 rounded-full font-semibold disabled:opacity-50"
>
{busy ? "Verifying..." : "Verify OTP"}
</button>
</form>
<div className="flex justify-between mt-4 text-sm">
<button
onClick={resend}
className="text-brand-600"
>
Resend OTP
</button>
<Link
href="/login"
className="text-slate-500"
>
Back to login
</Link>
</div>
<p className="text-xs text-slate-400 mt-4">
Check your email inbox (and spam folder) for the login OTP.
</p>
</AuthShell>
);
}
export default function LoginOTP() {
return (
<Suspense>
<Inner />
</Suspense>
);
}