Files
lms-sb/src/app/(auth)/login/login-form.tsx
T
admins 85cf8be705 Explicitly resend verification email on unverified login
Better Auth does not auto-resend on 403 (verified against Resend logs),
so request a fresh link via authClient.sendVerificationEmail before
telling the user a new email was sent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:05:32 +05:00

132 lines
4.8 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { signIn, authClient } from "@/lib/auth-client";
export function LoginForm() {
const router = useRouter();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [needsVerification, setNeedsVerification] = useState(false);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError("");
setNeedsVerification(false);
setLoading(true);
const result = await signIn.email({ email, password });
if (result.error) {
if (result.error.status === 403) {
// Креды верны, но email не подтверждён. Better Auth сам письмо
// не переотправляет (проверено) — запрашиваем свежую ссылку явно.
await authClient.sendVerificationEmail({ email, callbackURL: "/dashboard" });
setNeedsVerification(true);
} else if (result.error.status === 429) {
setError("Слишком много попыток входа. Подождите минуту и попробуйте снова.");
} else {
setError("Неверный email или пароль");
}
setLoading(false);
return;
}
const session = await authClient.getSession();
const role = session.data?.user?.role;
if (role === "admin") {
router.push("/admin/dashboard");
} else if (role === "curator") {
router.push("/curator/dashboard");
} else {
router.push("/dashboard");
}
router.refresh();
}
return (
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-1.5">
<label className="block text-xs uppercase tracking-widest font-bold" style={{ color: "var(--muted-foreground)" }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-3 py-2 text-sm bg-transparent"
style={{
border: "2px solid var(--border)",
color: "var(--foreground)",
fontFamily: "var(--font-sans)",
outline: "none",
}}
onFocus={(e) => (e.target.style.borderColor = "var(--foreground)")}
onBlur={(e) => (e.target.style.borderColor = "var(--border)")}
placeholder="you@example.com"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs uppercase tracking-widest font-bold" style={{ color: "var(--muted-foreground)" }}>
Пароль
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full px-3 py-2 text-sm bg-transparent"
style={{
border: "2px solid var(--border)",
color: "var(--foreground)",
fontFamily: "var(--font-sans)",
outline: "none",
}}
onFocus={(e) => (e.target.style.borderColor = "var(--foreground)")}
onBlur={(e) => (e.target.style.borderColor = "var(--border)")}
placeholder="••••••••"
/>
</div>
{needsVerification && (
<div
className="text-xs py-3 px-3 space-y-1"
style={{ border: "2px solid var(--border)", color: "var(--foreground)" }}
>
<p className="font-bold">Email не подтверждён</p>
<p style={{ color: "var(--muted-foreground)" }}>
Мы отправили новое письмо со ссылкой подтверждения на <strong>{email}</strong>.
Проверьте почту (и папку «Спам»), затем войдите снова.
</p>
</div>
)}
{error && (
<p className="text-sm" style={{ color: "var(--destructive)" }}>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className="btn-aubade w-full justify-center"
style={loading ? { opacity: 0.6, cursor: "not-allowed" } : undefined}
>
{loading ? "Вход..." : "Войти"}
</button>
<div className="flex items-center justify-between text-xs" style={{ color: "var(--muted-foreground)" }}>
<Link href="/forgot-password" className="underline" style={{ color: "var(--muted-foreground)" }}>
Забыли пароль?
</Link>
<Link href="/register" className="underline" style={{ color: "var(--foreground)" }}>
Зарегистрироваться
</Link>
</div>
</form>
);
}