From e17437a5ad9b09d90d1cf0859ab067e0cd34e251 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Fri, 12 Dec 2025 16:45:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en-US.json | 3 +- src/app/auth/AuthForm.tsx | 65 ++++++++++++++++++++++++++++++--------- src/lib/actions/auth.ts | 52 +++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 15 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index c8adb6d..a92aeb7 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -108,7 +108,8 @@ "nameRequired": "Please enter your name", "emailRequired": "Please enter your email", "passwordRequired": "Please enter your password", - "confirmPasswordRequired": "Please confirm your password" + "confirmPasswordRequired": "Please confirm your password", + "loading": "Loading..." }, "memorize": { "folder_selector": { diff --git a/src/app/auth/AuthForm.tsx b/src/app/auth/AuthForm.tsx index 01efce2..3b61829 100644 --- a/src/app/auth/AuthForm.tsx +++ b/src/app/auth/AuthForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useActionState } from "react"; +import { useState, useActionState, startTransition } from "react"; import { useTranslations } from "next-intl"; import { signInAction, signUpAction, SignUpState } from "@/lib/actions/auth"; import Container from "@/components/ui/Container"; @@ -16,13 +16,27 @@ interface AuthFormProps { export default function AuthForm({ redirectTo }: AuthFormProps) { const t = useTranslations("auth"); const [mode, setMode] = useState<'signin' | 'signup'>('signin'); + const [clearSignIn, setClearSignIn] = useState(false); + const [clearSignUp, setClearSignUp] = useState(false); const [signInState, signInActionForm, isSignInPending] = useActionState( - async (prevState: SignUpState | undefined, formData: FormData) => signInAction(prevState || {}, formData), + async (prevState: SignUpState | undefined, formData: FormData) => { + if (clearSignIn) { + setClearSignIn(false); + return undefined; + } + return signInAction(prevState || {}, formData); + }, undefined ); const [signUpState, signUpActionForm, isSignUpPending] = useActionState( - async (prevState: SignUpState | undefined, formData: FormData) => signUpAction(prevState || {}, formData), + async (prevState: SignUpState | undefined, formData: FormData) => { + if (clearSignUp) { + setClearSignUp(false); + return undefined; + } + return signUpAction(prevState || {}, formData); + }, undefined ); @@ -64,21 +78,29 @@ export default function AuthForm({ redirectTo }: AuthFormProps) { return Object.keys(newErrors).length === 0; }; - const handleFormSubmit = async (e: React.FormEvent) => { + const handleFormSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); - if (validateForm(formData)) { - if (redirectTo) { - formData.append("redirectTo", redirectTo); - } - - if (mode === 'signin') { - await signInActionForm(formData); - } else { - await signUpActionForm(formData); - } + // 基本客户端验证 + if (!validateForm(formData)) { + return; } + + // 添加 redirectTo 到 formData + if (redirectTo) { + formData.append("redirectTo", redirectTo); + } + + // 使用 startTransition 包装 action 调用 + startTransition(() => { + // 根据模式调用相应的 action + if (mode === 'signin') { + signInActionForm(formData); + } else { + signUpActionForm(formData); + } + }); }; const handleGitHubSignIn = async () => { @@ -115,6 +137,9 @@ export default function AuthForm({ redirectTo }: AuthFormProps) { {errors.name && (

{errors.name}

)} + {currentError?.errors?.username && ( +

{currentError.errors.username[0]}

+ )} )} @@ -128,6 +153,9 @@ export default function AuthForm({ redirectTo }: AuthFormProps) { {errors.email && (

{errors.email}

)} + {currentError?.errors?.email && ( +

{currentError.errors.email[0]}

+ )}
@@ -140,6 +168,9 @@ export default function AuthForm({ redirectTo }: AuthFormProps) { {errors.password && (

{errors.password}

)} + {currentError?.errors?.password && ( +

{currentError.errors.password[0]}

+ )}
{mode === 'signup' && ( @@ -194,6 +225,12 @@ export default function AuthForm({ redirectTo }: AuthFormProps) { onClick={() => { setMode(mode === 'signin' ? 'signup' : 'signin'); setErrors({}); + // 清除服务器端错误状态 + if (mode === 'signin') { + setClearSignIn(true); + } else { + setClearSignUp(true); + } }} className="text-[#35786f] hover:underline" > diff --git a/src/lib/actions/auth.ts b/src/lib/actions/auth.ts index 17c1d93..0c43439 100644 --- a/src/lib/actions/auth.ts +++ b/src/lib/actions/auth.ts @@ -26,6 +26,36 @@ export async function signUpAction(prevState: SignUpState, formData: FormData) { const password = formData.get("password") as string; const redirectTo = formData.get("redirectTo") as string; + // 服务器端验证 + const errors: SignUpState['errors'] = {}; + + if (!email) { + errors.email = ["邮箱是必填项"]; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + errors.email = ["请输入有效的邮箱地址"]; + } + + if (!name) { + errors.username = ["姓名是必填项"]; + } else if (name.length < 2) { + errors.username = ["姓名至少需要2个字符"]; + } + + if (!password) { + errors.password = ["密码是必填项"]; + } else if (password.length < 8) { + errors.password = ["密码至少需要8个字符"]; + } + + // 如果有验证错误,返回错误状态 + if (Object.keys(errors).length > 0) { + return { + success: false, + message: "请修正表单中的错误", + errors + }; + } + try { await auth.api.signUpEmail({ body: { @@ -49,6 +79,28 @@ export async function signInAction(prevState: SignUpState, formData: FormData) { const password = formData.get("password") as string; const redirectTo = formData.get("redirectTo") as string; + // 服务器端验证 + const errors: SignUpState['errors'] = {}; + + if (!email) { + errors.email = ["邮箱是必填项"]; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + errors.email = ["请输入有效的邮箱地址"]; + } + + if (!password) { + errors.password = ["密码是必填项"]; + } + + // 如果有验证错误,返回错误状态 + if (Object.keys(errors).length > 0) { + return { + success: false, + message: "请修正表单中的错误", + errors + }; + } + try { await auth.api.signInEmail({ body: {