From 9725730764196f90cc73911cc2740244fc53cd1b Mon Sep 17 00:00:00 2001 From: Harshit maheshwari Date: Thu, 16 Oct 2025 20:30:20 +0530 Subject: [PATCH] Issue 120 solved --- frontend/src/pages/RegisterPage.jsx | 188 ++++++++++++++++++++++------ 1 file changed, 150 insertions(+), 38 deletions(-) diff --git a/frontend/src/pages/RegisterPage.jsx b/frontend/src/pages/RegisterPage.jsx index fec46f5..42dcdc0 100644 --- a/frontend/src/pages/RegisterPage.jsx +++ b/frontend/src/pages/RegisterPage.jsx @@ -1,94 +1,206 @@ -import React, { useState } from 'react'; -import { Link } from 'react-router-dom'; -import useAuth from '../hooks/useAuth'; -import PasswordInput from '../components/PasswordInput'; +import React, { useState, useEffect, useRef } from "react"; +import { Link } from "react-router-dom"; +import useAuth from "../hooks/useAuth"; +import PasswordInput from "../components/PasswordInput"; export default function RegisterPage() { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); const [errors, setErrors] = useState({}); // State for server-side errors - const [serverError, setServerError] = useState(''); + const [serverError, setServerError] = useState(""); const { signup } = useAuth(); + const emailTimerRef = useRef(null); + const passwordTimerRef = useRef(null); + + const clearEmailError = () => { + setErrors((prev) => { + const { email, ...rest } = prev; + return rest; + }); + if (emailTimerRef.current) { + clearTimeout(emailTimerRef.current); + emailTimerRef.current = null; + } + }; + + const clearPasswordError = () => { + setErrors((prev) => { + const { password, ...rest } = prev; + return rest; + }); + if (passwordTimerRef.current) { + clearTimeout(passwordTimerRef.current); + passwordTimerRef.current = null; + } + }; + + // Clear errors when clicking anywhere on the document + useEffect(() => { + const onDocClick = () => { + if (Object.keys(errors).length > 0) { + setErrors({}); + } + }; + document.addEventListener("click", onDocClick); + return () => document.removeEventListener("click", onDocClick); + }, [errors]); + const validate = () => { const newErrors = {}; - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + const emailRequirements = [ + "A valid email address (e.g., user@domain.com)", + "Domain must not be example.com, test.com, or invalid.com", + "Top-level domain should be at least 3 characters (e.g., .com, .org) -- adjust rule if needed", + ]; + + // stricter email: require TLD of at least 3 letters to catch common typos like ".co" + // If you do not want this strictness, change {3,} to {2,} or use your own allowed list. + const emailRegex = /^[^\s@]+@[^\s@]+\.[A-Za-z]{3,}$/; + let emailInvalid = false; if (!emailRegex.test(email)) { - newErrors.email = 'Please enter a valid email address.'; + emailInvalid = true; } else { - // Frontend blacklist for instant feedback - const domain = email.split('@')[1]; - const blockedDomains = ['example.com', 'test.com', 'invalid.com']; - if (blockedDomains.includes(domain)) { - newErrors.email = 'This email domain is not allowed.'; + const domain = email.split("@")[1] || ""; + const blockedDomains = ["example.com", "test.com", "invalid.com"]; + if (blockedDomains.includes(domain.toLowerCase())) { + emailInvalid = true; } } + if (emailInvalid) { + newErrors.email = emailRequirements; + } - if (password.length < 8 || password.length > 16) { - newErrors.password = 'Password must be 8-16 characters long.'; - } else { - const passwordRegex = /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[\W_])/; - if (!passwordRegex.test(password)) { - newErrors.password = 'Password must contain an alphabet, a digit, and a symbol.'; - } + const passwordRequirements = [ + "8-16 characters long", + "Contain at least one alphabet letter (a-z or A-Z)", + "Contain at least one digit (0-9)", + "Contain at least one symbol (e.g., !@#$%)", + ]; + const lengthOk = password.length >= 8 && password.length <= 16; + const hasLetter = /[a-zA-Z]/.test(password); + const hasDigit = /\d/.test(password); + const hasSymbol = /[\W_]/.test(password); + + if (!lengthOk || !hasLetter || !hasDigit || !hasSymbol) { + newErrors.password = passwordRequirements; } - + return newErrors; }; const handleSubmit = async (e) => { e.preventDefault(); - setServerError(''); // Clear previous server errors + setServerError(""); // Clear previous server errors const validationErrors = validate(); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); return; } setErrors({}); - - // The signup function in AuthContext needs to be updated to handle errors + try { await signup(email, password); } catch (error) { - setServerError(error.message); + // If server returns a generic failure but client validation would have caught something, + // prefer to show the validation requirements to the user. Otherwise show server error. + const validationErrorsAfterFail = validate(); + if (Object.keys(validationErrorsAfterFail).length > 0) { + setErrors(validationErrorsAfterFail); + } else { + setServerError(error?.message || "Signup failed"); + } } }; return (
- + Paisable
-

Create an account

- {serverError &&

{serverError}

} -
+

+ Create an account +

+ {serverError && ( +

+ {serverError} +

+ )} + {/* disable browser native validation UI so custom popups are used */} +
- + setEmail(e.target.value)} required /> - {errors.email &&

{errors.email}

} + {errors.email && Array.isArray(errors.email) && ( +
    { + e.stopPropagation(); // prevent document click from also firing + clearEmailError(); + }} + > + {errors.email.map((msg, i) => ( +
  • {msg}
  • + ))} +
+ )}
- - + Password + + setPassword(e.target.value)} error={errors.password} /> - {errors.password &&

{errors.password}

} + {errors.password && Array.isArray(errors.password) && ( +
    { + e.stopPropagation(); + clearPasswordError(); + }} + > + {errors.password.map((msg, i) => ( +
  • {msg}
  • + ))} +
+ )}
-
@@ -103,4 +215,4 @@ export default function RegisterPage() {
); -} \ No newline at end of file +}