From 6f4b123a84d5dfdd0d4e9ab086a15c566a70edf8 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Tue, 10 Mar 2026 19:38:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E9=87=8D=E5=8F=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 登录时检测 403 错误(邮箱未验证) - 显示重发验证邮件按钮 - 修复邮件发送失败时静默忽略的问题 - 添加 8 种语言的验证相关翻译 --- messages/de-DE.json | 7 +++- messages/en-US.json | 7 +++- messages/fr-FR.json | 7 +++- messages/it-IT.json | 7 +++- messages/ja-JP.json | 7 +++- messages/ko-KR.json | 7 +++- messages/ug-CN.json | 7 +++- messages/zh-CN.json | 7 +++- src/app/(auth)/login/page.tsx | 60 ++++++++++++++++++++++++++++++++--- src/auth.ts | 10 ++++-- 10 files changed, 111 insertions(+), 15 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 2163486..84584f5 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -175,7 +175,12 @@ "requestNewToken": "Neuen Reset-Link anfordern", "resetPasswordSuccess": "Passwort erfolgreich zurückgesetzt", "resetPasswordSuccessTitle": "Passwort-Zurücksetzung abgeschlossen", - "resetPasswordSuccessHint": "Ihr Passwort wurde erfolgreich zurückgesetzt. Sie können sich jetzt mit Ihrem neuen Passwort anmelden." + "resetPasswordSuccessHint": "Ihr Passwort wurde erfolgreich zurückgesetzt. Sie können sich jetzt mit Ihrem neuen Passwort anmelden.", + "emailNotVerified": "Bitte verifizieren Sie Ihre E-Mail-Adresse", + "emailNotVerifiedHint": "Ihre E-Mail-Adresse wurde nicht verifiziert. Bitte überprüfen Sie Ihren Posteingang oder fordern Sie eine neue Verifizierungs-E-Mail an.", + "resendVerification": "Verifizierungs-E-Mail erneut senden", + "resendSuccess": "Verifizierungs-E-Mail gesendet! Bitte überprüfen Sie Ihren Posteingang.", + "resendFailed": "Verifizierungs-E-Mail konnte nicht gesendet werden" }, "memorize": { "deck_selector": { diff --git a/messages/en-US.json b/messages/en-US.json index ee8c8ee..082fcf9 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -166,7 +166,12 @@ "requestNewToken": "Request New Reset Link", "resetPasswordSuccess": "Password reset successfully", "resetPasswordSuccessTitle": "Password Reset Complete", - "resetPasswordSuccessHint": "Your password has been reset successfully. You can now log in with your new password." + "resetPasswordSuccessHint": "Your password has been reset successfully. You can now log in with your new password.", + "emailNotVerified": "Please verify your email address", + "emailNotVerifiedHint": "Your email has not been verified. Please check your inbox or request a new verification email.", + "resendVerification": "Resend Verification Email", + "resendSuccess": "Verification email sent! Please check your inbox.", + "resendFailed": "Failed to send verification email" }, "memorize": { "deck_selector": { diff --git a/messages/fr-FR.json b/messages/fr-FR.json index c23c7df..af78506 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -175,7 +175,12 @@ "requestNewToken": "Demander un nouveau lien de réinitialisation", "resetPasswordSuccess": "Mot de passe réinitialisé avec succès", "resetPasswordSuccessTitle": "Réinitialisation du mot de passe terminée", - "resetPasswordSuccessHint": "Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter avec votre nouveau mot de passe." + "resetPasswordSuccessHint": "Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.", + "emailNotVerified": "Veuillez vérifier votre adresse e-mail", + "emailNotVerifiedHint": "Votre adresse e-mail n'a pas été vérifiée. Veuillez vérifier votre boîte de réception ou demander un nouvel e-mail de vérification.", + "resendVerification": "Renvoyer l'e-mail de vérification", + "resendSuccess": "E-mail de vérification envoyé ! Veuillez vérifier votre boîte de réception.", + "resendFailed": "Échec de l'envoi de l'e-mail de vérification" }, "memorize": { "deck_selector": { diff --git a/messages/it-IT.json b/messages/it-IT.json index 489913c..56dce91 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -175,7 +175,12 @@ "requestNewToken": "Richiedi Nuovo Link di Reset", "resetPasswordSuccess": "Password reimpostata con successo", "resetPasswordSuccessTitle": "Reimpostazione Password Completata", - "resetPasswordSuccessHint": "La tua password è stata reimpostata con successo. Ora puoi accedere con la tua nuova password." + "resetPasswordSuccessHint": "La tua password è stata reimpostata con successo. Ora puoi accedere con la tua nuova password.", + "emailNotVerified": "Verifica il tuo indirizzo email", + "emailNotVerifiedHint": "Il tuo indirizzo email non è stato verificato. Controlla la tua casella di posta o richiedi una nuova email di verifica.", + "resendVerification": "Invia di nuovo email di verifica", + "resendSuccess": "Email di verifica inviata! Controlla la tua casella di posta.", + "resendFailed": "Impossibile inviare l'email di verifica" }, "memorize": { "deck_selector": { diff --git a/messages/ja-JP.json b/messages/ja-JP.json index 0f50803..e868fec 100644 --- a/messages/ja-JP.json +++ b/messages/ja-JP.json @@ -166,7 +166,12 @@ "requestNewToken": "新しいリセットリンクをリクエスト", "resetPasswordSuccess": "パスワードのリセットに成功しました", "resetPasswordSuccessTitle": "パスワードリセット完了", - "resetPasswordSuccessHint": "パスワードが正常にリセットされました。新しいパスワードでログインできます。" + "resetPasswordSuccessHint": "パスワードが正常にリセットされました。新しいパスワードでログインできます。", + "emailNotVerified": "メールアドレスを確認してください", + "emailNotVerifiedHint": "メールアドレスが確認されていません。受信トレイをご確認いただくか、新しい確認メールをリクエストしてください。", + "resendVerification": "確認メールを再送信", + "resendSuccess": "確認メールを送信しました!受信トレイをご確認ください。", + "resendFailed": "確認メールの送信に失敗しました" }, "memorize": { "deck_selector": { diff --git a/messages/ko-KR.json b/messages/ko-KR.json index e5d9d9e..4525399 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -175,7 +175,12 @@ "requestNewToken": "새 재설정 링크 요청", "resetPasswordSuccess": "비밀번호 재설정 성공", "resetPasswordSuccessTitle": "비밀번호 재설정 완료", - "resetPasswordSuccessHint": "비밀번호가 성공적으로 재설정되었습니다. 새 비밀번호로 로그인할 수 있습니다." + "resetPasswordSuccessHint": "비밀번호가 성공적으로 재설정되었습니다. 새 비밀번호로 로그인할 수 있습니다.", + "emailNotVerified": "이메일 주소를 인증해 주세요", + "emailNotVerifiedHint": "이메일이 인증되지 않았습니다. 받은 편지함을 확인하거나 새 인증 이메일을 요청해 주세요.", + "resendVerification": "인증 이메일 다시 보내기", + "resendSuccess": "인증 이메일이 발송되었습니다! 받은 편지함을 확인해 주세요.", + "resendFailed": "인증 이메일 발송에 실패했습니다" }, "memorize": { "deck_selector": { diff --git a/messages/ug-CN.json b/messages/ug-CN.json index 5256c94..0eb8b1c 100644 --- a/messages/ug-CN.json +++ b/messages/ug-CN.json @@ -175,7 +175,12 @@ "requestNewToken": "يېڭى ئەسلىگە قايتۇرۇش ئۇلانمىسى سوراش", "resetPasswordSuccess": "پارول مۇۋەپپەقىيەتلىك ئەسلىگە قايتۇرۇلدى", "resetPasswordSuccessTitle": "پارول ئەسلىگە قايتۇرۇش تاماملاندى", - "resetPasswordSuccessHint": "پارولىڭىز مۇۋەپپەقىيەتلىك ئەسلىگە قايتۇرۇلدى. يېڭى پارول بىلەن كىرسىڭىز بولىدۇ." + "resetPasswordSuccessHint": "پارولىڭىز مۇۋەپپەقىيەتلىك ئەسلىگە قايتۇرۇلدى. يېڭى پارول بىلەن كىرسىڭىز بولىدۇ.", + "emailNotVerified": "ئېلخەت ئادرېسىڭىزنى دەلىللەڭ", + "emailNotVerifiedHint": "ئېلخەت ئادرېسىڭىز دەلىللەنمىگەن. ئېلخەت ساندۇقىڭىزنى تەكشۈرۈڭ ياكى يېڭى دەلىللەش ئېلخېتى سوراڭ.", + "resendVerification": "دەلىللەش ئېلخېتىنى قايتا ئەۋەتىش", + "resendSuccess": "دەلىللەش ئېلخېتى ئەۋەتىلدى! ئېلخەت ساندۇقىڭىزنى تەكشۈرۈڭ.", + "resendFailed": "دەلىللەش ئېلخېتى ئەۋەتىش مەغلۇپ بولدى" }, "memorize": { "deck_selector": { diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 956ac9f..7b3d4f9 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -166,7 +166,12 @@ "requestNewToken": "重新申请重置链接", "resetPasswordSuccess": "密码重置成功", "resetPasswordSuccessTitle": "密码重置完成", - "resetPasswordSuccessHint": "您的密码已成功重置,现在可以使用新密码登录了。" + "resetPasswordSuccessHint": "您的密码已成功重置,现在可以使用新密码登录了。", + "emailNotVerified": "请验证您的邮箱地址", + "emailNotVerifiedHint": "您的邮箱尚未验证。请检查收件箱或重新发送验证邮件。", + "resendVerification": "重新发送验证邮件", + "resendSuccess": "验证邮件已发送!请检查您的收件箱。", + "resendFailed": "发送验证邮件失败" }, "memorize": { "deck_selector": { diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 145ec6d..a807434 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -8,7 +8,7 @@ import { toast } from "sonner"; import { useTranslations } from "next-intl"; import { Card, CardBody } from "@/design-system/base/card"; import { Input } from "@/design-system/base/input"; -import { PrimaryButton } from "@/design-system/base/button"; +import { PrimaryButton, LinkButton } from "@/design-system/base/button"; import { VStack } from "@/design-system/layout/stack"; export default function LoginPage() { @@ -16,6 +16,9 @@ export default function LoginPage() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); + const [resendLoading, setResendLoading] = useState(false); + const [showResendOption, setShowResendOption] = useState(false); + const [unverifiedEmail, setUnverifiedEmail] = useState(""); const searchParams = useSearchParams(); const redirectTo = searchParams.get("redirect"); @@ -25,10 +28,31 @@ export default function LoginPage() { useEffect(() => { if (!isPending && session?.user?.username && !redirectTo) { - router.push("/folders"); + router.push("/decks"); } }, [session, isPending, router, redirectTo]); + const handleResendVerification = async () => { + if (!unverifiedEmail) return; + + setResendLoading(true); + try { + const { error } = await authClient.sendVerificationEmail({ + email: unverifiedEmail, + callbackURL: "/login", + }); + + if (error) { + toast.error(t("resendFailed")); + } else { + toast.success(t("resendSuccess")); + setShowResendOption(false); + } + } finally { + setResendLoading(false); + } + }; + const handleLogin = async () => { if (!username || !password) { toast.error(t("enterCredentials")); @@ -36,6 +60,7 @@ export default function LoginPage() { } setLoading(true); + setShowResendOption(false); try { if (username.includes("@")) { const { error } = await authClient.signIn.email({ @@ -43,7 +68,13 @@ export default function LoginPage() { password: password, }); if (error) { - toast.error(error.message ?? t("loginFailed")); + if (error.status === 403) { + setUnverifiedEmail(username); + setShowResendOption(true); + toast.error(t("emailNotVerified")); + } else { + toast.error(error.message ?? t("loginFailed")); + } return; } } else { @@ -52,11 +83,15 @@ export default function LoginPage() { password: password, }); if (error) { - toast.error(error.message ?? t("loginFailed")); + if (error.status === 403) { + toast.error(t("emailNotVerified")); + } else { + toast.error(error.message ?? t("loginFailed")); + } return; } } - router.push(redirectTo ?? "/folders"); + router.push(redirectTo ?? "/decks"); } finally { setLoading(false); } @@ -91,6 +126,21 @@ export default function LoginPage() { {t("forgotPassword")} + {showResendOption && ( +
+

+ {t("emailNotVerifiedHint")} +

+ + {t("resendVerification")} + +
+ )} + { - void sendEmail({ + const result = await sendEmail({ to: user.email, subject: "重置您的密码 - Learn Languages", html: generateResetPasswordEmailHtml(url, user.name || "用户"), }); + if (!result.success) { + console.error("[email] Failed to send reset password email:", result.error); + } }, }, emailVerification: { sendOnSignUp: true, sendVerificationEmail: async ({ user, url }) => { - void sendEmail({ + const result = await sendEmail({ to: user.email, subject: "验证您的邮箱 - Learn Languages", html: generateVerificationEmailHtml(url, user.name || "用户"), }); + if (!result.success) { + console.error("[email] Failed to send verification email:", result.error); + } }, }, socialProviders: {