diff --git a/src/app/(auth)/login/login-form.tsx b/src/app/(auth)/login/login-form.tsx index 05fdf5b..ff0afb2 100644 --- a/src/app/(auth)/login/login-form.tsx +++ b/src/app/(auth)/login/login-form.tsx @@ -10,17 +10,27 @@ export function LoginForm() { 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) { - setError("Неверный email или пароль"); + if (result.error.status === 403) { + // Креды верны, но email не подтверждён. Better Auth уже + // переотправил письмо подтверждения — сообщаем об этом. + setNeedsVerification(true); + } else if (result.error.status === 429) { + setError("Слишком много попыток входа. Подождите минуту и попробуйте снова."); + } else { + setError("Неверный email или пароль"); + } setLoading(false); return; } @@ -82,6 +92,18 @@ export function LoginForm() { placeholder="••••••••" /> + {needsVerification && ( +
Email не подтверждён
++ Мы отправили новое письмо со ссылкой подтверждения на {email}. + Проверьте почту (и папку «Спам»), затем войдите снова. +
+{error} diff --git a/src/app/api/register/route.ts b/src/app/api/register/route.ts index 5d79477..d6bb361 100644 --- a/src/app/api/register/route.ts +++ b/src/app/api/register/route.ts @@ -83,7 +83,7 @@ export async function POST(request: NextRequest) { "Content-Type": "application/json", Origin: baseUrl, }), - body: JSON.stringify({ name, email, password }), + body: JSON.stringify({ name, email, password, callbackURL: "/dashboard" }), }) ); } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index fa04aa7..d9888b5 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -3,7 +3,7 @@ import { prismaAdapter } from "better-auth/adapters/prisma"; import { admin } from "better-auth/plugins"; import { prisma } from "./prisma"; import bcrypt from "bcryptjs"; -import { sendWelcomeEmail, sendPasswordResetEmail } from "./email"; +import { sendWelcomeEmail, sendPasswordResetEmail, sendVerificationEmail } from "./email"; export const auth = betterAuth({ database: prismaAdapter(prisma, { @@ -20,6 +20,16 @@ export const auth = betterAuth({ await sendPasswordResetEmail(user.email, user.name, url); }, }, + emailVerification: { + // Без этого блока requireEmailVerification блокирует вход, + // а письмо подтверждения никто не отправляет — тупик для новых юзеров. + sendVerificationEmail: async ({ user, url }) => { + await sendVerificationEmail(user.email, user.name, url); + }, + sendOnSignUp: true, + autoSignInAfterVerification: true, + expiresIn: 60 * 60 * 24, // 24h + }, databaseHooks: { user: { create: { diff --git a/src/lib/email.ts b/src/lib/email.ts index 47cd196..4d4d4b6 100644 --- a/src/lib/email.ts +++ b/src/lib/email.ts @@ -167,7 +167,7 @@ export async function sendWelcomeEmail(to: string, name: string) { subject: `Добро пожаловать в ${school}`, html: base(`
Привет, ${name}!
-Ваш аккаунт на образовательной платформе ${school} подтверждён.
+Ваш аккаунт на образовательной платформе ${school} создан.
После того как администратор откроет вам доступ к курсу, вы получите письмо и сможете начать обучение.
${btn(`${BASE_URL}/dashboard`, "Перейти на платформу")} `, school), @@ -229,6 +229,22 @@ export async function sendPasswordResetEmail(to: string, name: string, resetUrl: }).catch((e) => console.error("[email] sendPasswordResetEmail:", e)); } +export async function sendVerificationEmail(to: string, name: string, verifyUrl: string) { + const school = await getSchoolName(); + await getResend().emails.send({ + from: FROM, + to, + subject: `Подтвердите email — ${school}`, + html: base(` +Привет, ${name}!
+Вы зарегистрировались на образовательной платформе ${school}.
+Нажмите на кнопку ниже, чтобы подтвердить email и активировать аккаунт. Ссылка действительна 24 часа.
+Если вы не регистрировались — просто проигнорируйте это письмо.
+ ${btn(verifyUrl, "Подтвердить email")} + `, school), + }).catch((e) => console.error("[email] sendVerificationEmail:", e)); +} + export async function sendQuestionCreatedEmail( to: string, recipientName: string,