fix: 添加邮箱验证重发功能
- 登录时检测 403 错误(邮箱未验证) - 显示重发验证邮件按钮 - 修复邮件发送失败时静默忽略的问题 - 添加 8 种语言的验证相关翻译
This commit is contained in:
@@ -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")}
|
||||
</Link>
|
||||
|
||||
{showResendOption && (
|
||||
<div className="w-full p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg text-sm">
|
||||
<p className="text-yellow-800 dark:text-yellow-200 mb-2">
|
||||
{t("emailNotVerifiedHint")}
|
||||
</p>
|
||||
<LinkButton
|
||||
onClick={handleResendVerification}
|
||||
loading={resendLoading}
|
||||
size="sm"
|
||||
>
|
||||
{t("resendVerification")}
|
||||
</LinkButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PrimaryButton
|
||||
onClick={handleLogin}
|
||||
loading={loading}
|
||||
|
||||
10
src/auth.ts
10
src/auth.ts
@@ -18,21 +18,27 @@ export const auth = betterAuth({
|
||||
enabled: true,
|
||||
requireEmailVerification: true,
|
||||
sendResetPassword: async ({ user, url }) => {
|
||||
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: {
|
||||
|
||||
Reference in New Issue
Block a user