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 });