"use client"; /** * 邮箱注册表单 * * * * 视觉规格(与 Dart 对齐): * - 4 字段:username / email / password / confirmPassword(顺序与 Dart 一致) * - 字段无 label(仅 placeholder) * - 错误仅以红色横幅显示(AuthErrorMessage),无字段级错误 * - 切换链接由父级 AuthEmailPanel 渲染 * - 提交派发 `AuthEmailRegisterSubmitted` */ import { useState } from "react"; import { useAuthDispatch } from "@/stores/auth/auth-context"; import { validateConfirmPassword, validateEmail, validatePassword, validateUsername, } from "@/app/auth/components/auth-validators"; import { AuthTextField } from "./auth-text-field"; import { AuthPrimaryButton } from "./auth-primary-button"; import { AuthErrorMessage } from "./auth-error-message"; import { Logger } from "@/utils"; const log = new Logger("AppAuthComponentsEmailRegisterForm"); export interface EmailRegisterFormProps { globalError?: string | null; isLoading?: boolean; } export function EmailRegisterForm({ globalError, isLoading, }: EmailRegisterFormProps) { const dispatch = useAuthDispatch(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [username, setUsername] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); // 本地校验错误(client-side)—— 校验失败时展示第一个非 null 的错误 // 优先级:local validationError > server globalError const [validationError, setValidationError] = useState(null); // 用户在任一字段开始输入 → 清掉旧校验错误(标准 UX:边改边消) // 直接在 onChange 调 setState —— React useState 对相同值自动 bail-out 不触发额外渲染 // 避免 useEffect 内 setState 的"级联渲染"反模式 const clearValidationOnType = () => setValidationError(null); const onUsernameChange = (v: string) => { setUsername(v); clearValidationOnType(); }; const onEmailChange = (v: string) => { setEmail(v); clearValidationOnType(); }; const onPasswordChange = (v: string) => { setPassword(v); clearValidationOnType(); }; const onConfirmPasswordChange = (v: string) => { setConfirmPassword(v); clearValidationOnType(); }; const submit = () => { log.debug("[email-register-form] submit ENTRY", { emailLength: email.length, emailPreview: email.slice(0, 8), passwordLength: password.length, usernameLength: username.length, confirmPasswordLength: confirmPassword.length, }); const emailErr = validateEmail(email); const passwordErr = validatePassword(password); const usernameErr = validateUsername(username); const confirmErr = validateConfirmPassword(password, confirmPassword); if (emailErr || passwordErr || usernameErr || confirmErr) { // 按 username → email → password → confirm 顺序挑第一个错(与字段顺序一致) const firstErr = usernameErr ?? emailErr ?? passwordErr ?? confirmErr ?? null; log.warn( "[email-register-form] submit VALIDATION FAILED (silent return)", { emailErr, passwordErr, usernameErr, confirmErr, surfaced: firstErr, }, ); setValidationError(firstErr); return; } setValidationError(null); log.debug( "[email-register-form] submit dispatching AuthEmailRegisterSubmitted", ); dispatch({ type: "AuthEmailRegisterSubmitted", email, password, username, confirmPassword, }); }; return (
{ e.preventDefault(); submit(); }} > {/* 优先级:本地校验错误 > 后端 globalError */} Register ); }