diff --git a/src/pages/Signup/Signup.tsx b/src/pages/Signup/Signup.tsx index 3043d57..5e49a8b 100644 --- a/src/pages/Signup/Signup.tsx +++ b/src/pages/Signup/Signup.tsx @@ -8,108 +8,63 @@ import type { ThemeContextType } from "../../context/ThemeContext"; const backendUrl = import.meta.env.VITE_BACKEND_URL; +// Must mirror the Zod rule in backend/validators/authValidator.js exactly. +// Requires: lowercase, uppercase, digit, and one special character from @$!%*?& +// Minimum 8 characters, maximum enforced by the backend (100). +const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; +const PASSWORD_ERROR = + "Password must be at least 8 characters and include uppercase, lowercase, a number, and a special character (@$!%*?&)"; + interface SignUpFormData { username: string; email: string; password: string; } -interface FormErrors { - firstName?: string; - lastName?: string; - email?: string; - password?: string; -} - -interface IconEyeProps { - open: boolean; -} - -function IconUser(): JSX.Element { - return ( - - - - ); -} - -function IconMail(): JSX.Element { - return ( - - - - ); -} - -function IconLock(): JSX.Element { - return ( - - - - ); -} - -function IconEye({ open }: IconEyeProps): JSX.Element { - return open ? ( - - - - ) : ( - - - - ); -} - -function IconArrow(): JSX.Element { - return ( - - - - ); -} - -function IconCheck(): JSX.Element { - return ( - - - - ); -} - -function GoogleIcon(): JSX.Element { - return ( - - - - - - - ); -} - -function GitHubIcon(): JSX.Element { - return ( - - - - ); -} - -export default function RegisterPage(): JSX.Element { - const [form, setForm] = useState({ firstName: "", lastName: "", email: "", password: "" }); - const [showPass, setShowPass] = useState(false); - const [errors, setErrors] = useState({}); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - - const validate = (): FormErrors => { - const e: FormErrors = {}; - if (!form.firstName.trim()) e.firstName = "Required"; - if (!form.lastName.trim()) e.lastName = "Required"; - if (!form.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) e.email = "Enter a valid email"; - if (form.password.length < 8) e.password = "Min 8 characters"; - return e; +const SignUp: React.FC = () => { + const [formData, setFormData] = useState({ + username: "", + email: "", + password: "", + }); + const [message, setMessage] = useState(""); + const [errors, setErrors] = useState({ + username: "", + email: "", + password: "", + }); + const [showPassword, setShowPassword] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + const themeContext = useContext(ThemeContext) as ThemeContextType; + const { mode } = themeContext; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + let errorMessage = ""; + if (name === "username") { + if (!value.trim()) { + errorMessage = "Username is required"; + } else if (!/^[A-Za-z\s]+$/.test(value)) { + errorMessage = "Only letters are allowed"; + } + } + if (name === "email") { + if (!value.trim()) { + errorMessage = "Email is required"; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())) { + errorMessage = "Enter a valid email"; + } + } + if (name === "password") { + if (!value.trim()) { + errorMessage = "Password is required"; + } else if (!PASSWORD_REGEX.test(value)) { + errorMessage = PASSWORD_ERROR; + } + } + setErrors((prev) => ({ ...prev, [name]: errorMessage })); }; const handleSubmit = async (e: React.FormEvent) => { @@ -126,8 +81,8 @@ export default function RegisterPage(): JSX.Element { : ""; const passwordError = !formData.password.trim() ? "Password is required" - : !/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(formData.password) - ? "Password must be 8+ characters with letters and numbers" + : !PASSWORD_REGEX.test(formData.password) + ? PASSWORD_ERROR : ""; if (usernameError || emailError || passwordError) { setErrors({ username: usernameError, email: emailError, password: passwordError });