From a3a433ffe7de07a27ac4809b3b37b8e01569e8dd Mon Sep 17 00:00:00 2001 From: Parixit Chauhan Date: Thu, 12 Mar 2026 21:22:00 +0530 Subject: [PATCH] added password validation with demo credentials at the pade later remove it from page --- Frontend/package-lock.json | 64 ++++++++- Frontend/package.json | 6 +- Frontend/src/pages/login.jsx | 240 +++++++++++++++++++++++++++++++-- Frontend/src/styles/input.css | 91 +++++++++++++ Frontend/src/styles/output.css | 84 ++++++++++++ 5 files changed, 468 insertions(+), 17 deletions(-) diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 2d584d4..1188ff9 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -12,8 +12,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-icons": "^5.6.0", - "react-router-dom": "^7.13.1", - "tailwindcss": "^4.2.1" + "react-router-dom": "^7.13.1" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -21,10 +20,13 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.27", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", "vite": "^7.3.1" } }, @@ -2097,6 +2099,43 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2672,6 +2711,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3410,6 +3463,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index 8708764..3ac4da7 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -16,8 +16,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-icons": "^5.6.0", - "react-router-dom": "^7.13.1", - "tailwindcss": "^4.2.1" + "react-router-dom": "^7.13.1" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -25,10 +24,13 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.27", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", "vite": "^7.3.1" } } diff --git a/Frontend/src/pages/login.jsx b/Frontend/src/pages/login.jsx index 8deca66..269f240 100644 --- a/Frontend/src/pages/login.jsx +++ b/Frontend/src/pages/login.jsx @@ -12,6 +12,7 @@ import { FaUser, FaUsers, FaUsersCog, + FaExclamationCircle, } from "react-icons/fa"; const roleOptions = [ @@ -45,6 +46,28 @@ const roleOptions = [ }, ]; +// Mock user database with credentials +const mockUsers = [ + { + username: "john_tenant", + password: "Tenant@123", + role: "tenant", + fullName: "John Tenant", + }, + { + username: "sarah_owner", + password: "Owner@456", + role: "owner", + fullName: "Sarah Johnson", + }, + { + username: "admin_user", + password: "Admin@789", + role: "admin", + fullName: "Admin User", + }, +]; + function Login() { const navigate = useNavigate(); const [isLogin, setIsLogin] = useState(true); @@ -57,6 +80,90 @@ function Login() { password: "", confirmPassword: "", }); + const [errors, setErrors] = useState({}); + + // Validation functions + const validateUsername = (username) => { + if (!username.trim()) { + return "Username is required"; + } + if (username.trim().length < 3) { + return "Username must be at least 3 characters long"; + } + if (username.trim().length > 20) { + return "Username must not exceed 20 characters"; + } + if (!/^[a-zA-Z0-9_-]+$/.test(username)) { + return "Username can only contain letters, numbers, hyphens and underscores"; + } + return ""; + }; + + const validatePassword = (password) => { + if (!password) { + return "Password is required"; + } + if (password.length < 6) { + return "Password must be at least 6 characters long"; + } + if (password.length > 50) { + return "Password must not exceed 50 characters"; + } + if (!/[a-z]/.test(password)) { + return "Password must contain at least one lowercase letter"; + } + if (!/[A-Z]/.test(password)) { + return "Password must contain at least one uppercase letter"; + } + if (!/[0-9]/.test(password)) { + return "Password must contain at least one number"; + } + return ""; + }; + + const validateEmail = (email) => { + if (!email.trim()) { + return "Email is required"; + } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + if (!emailRegex) { + return "Please enter a valid email address"; + } + return ""; + }; + + const validatePhone = (phone) => { + if (!phone.trim()) { + return "Phone number is required"; + } + if (!/^[0-9+\-\s()]{10,}$/.test(phone)) { + return "Please enter a valid phone number"; + } + return ""; + }; + + const validateFullName = (fullName) => { + if (!fullName.trim()) { + return "Full name is required"; + } + if (fullName.trim().length < 3) { + return "Full name must be at least 3 characters long"; + } + if (!/^[a-zA-Z\s'-]+$/.test(fullName)) { + return "Full name can only contain letters, spaces, hyphens and apostrophes"; + } + return ""; + }; + + const validateConfirmPassword = (password, confirmPassword) => { + if (!confirmPassword) { + return "Please confirm your password"; + } + if (password !== confirmPassword) { + return "Passwords do not match"; + } + return ""; + }; const activeRole = useMemo( () => @@ -67,11 +174,77 @@ function Login() { const handleInputChange = (event) => { const { name, value } = event.target; setFormData((prev) => ({ ...prev, [name]: value })); + // Clear error for this field when user starts typing + if (errors[name]) { + setErrors((prev) => ({ ...prev, [name]: "" })); + } }; const handleSubmit = (event) => { event.preventDefault(); - navigate(activeRole.route); + const newErrors = {}; + + // Validate username + const usernameError = validateUsername(formData.username); + if (usernameError) newErrors.username = usernameError; + + // Validate password + const passwordError = validatePassword(formData.password); + if (passwordError) newErrors.password = passwordError; + + // Validation for signup + if (!isLogin) { + const fullNameError = validateFullName(formData.fullName); + if (fullNameError) newErrors.fullName = fullNameError; + + const emailError = validateEmail(formData.email); + if (emailError) newErrors.email = emailError; + + const phoneError = validatePhone(formData.phone); + if (phoneError) newErrors.phone = phoneError; + + const confirmPasswordError = validateConfirmPassword( + formData.password, + formData.confirmPassword + ); + if (confirmPasswordError) newErrors.confirmPassword = confirmPasswordError; + } + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + return; + } + + // If login mode, authenticate against mock users + if (isLogin) { + const user = mockUsers.find( + (u) => u.username === formData.username && u.password === formData.password + ); + + if (!user) { + setErrors({ + username: "Invalid username or password", + password: "Invalid username or password", + }); + return; + } + + // Check if selected role matches user's role + if (user.role !== selectedRole) { + setErrors({ + username: `This account is for ${user.role} role, but you selected ${selectedRole}`, + }); + return; + } + + // Authentication successful + setErrors({}); + navigate(activeRole.route); + } else { + // Signup - just navigate after validation passes + setErrors({}); + navigate(activeRole.route); + } }; return ( @@ -179,6 +352,7 @@ function Login() { value={formData.fullName} placeholder="Full name" onChange={handleInputChange} + error={errors.fullName} /> )} @@ -188,6 +362,7 @@ function Login() { value={formData.username} placeholder="Username" onChange={handleInputChange} + error={errors.username} /> {!isLogin && ( @@ -199,6 +374,7 @@ function Login() { value={formData.email} placeholder="Email address" onChange={handleInputChange} + error={errors.email} /> } @@ -206,6 +382,7 @@ function Login() { value={formData.phone} placeholder="Phone number" onChange={handleInputChange} + error={errors.phone} /> )} @@ -217,6 +394,7 @@ function Login() { value={formData.password} placeholder="Password" onChange={handleInputChange} + error={errors.password} /> {!isLogin && ( @@ -227,6 +405,7 @@ function Login() { value={formData.confirmPassword} placeholder="Confirm password" onChange={handleInputChange} + error={errors.confirmPassword} /> )} @@ -243,25 +422,60 @@ function Login() {

Selected role: {activeRole.label}

+ + {isLogin && ( +
+

Demo Credentials:

+
+ {mockUsers.map((user) => ( +
+
+ Username: + {user.username} +
+
+ Password: + {user.password} +
+
+ Role: + + {user.role} + +
+
+ ))} +
+
+ )} ); } -function InputRow({ icon, type = "text", name, value, placeholder, onChange }) { +function InputRow({ icon, type = "text", name, value, placeholder, onChange, error }) { return ( - +
+ + {error && ( +
+ + {error} +
+ )} +
); } diff --git a/Frontend/src/styles/input.css b/Frontend/src/styles/input.css index 2d0abe0..b9579b8 100644 --- a/Frontend/src/styles/input.css +++ b/Frontend/src/styles/input.css @@ -353,6 +353,43 @@ a { color: #8ca7c3; } +.input-wrapper { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.input-row--error { + border-color: rgba(255, 102, 102, 0.68); + background: rgba(255, 102, 102, 0.08); +} + +.input-row--error:focus-within { + border-color: rgba(255, 102, 102, 0.88); + box-shadow: 0 0 0 3px rgba(255, 102, 102, 0.16); +} + +.input-error { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.75rem; + color: #ff6666; + padding: 0.2rem 0.4rem; + animation: slideIn 150ms ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + .auth-submit { margin-top: 0.32rem; border: 0; @@ -382,6 +419,60 @@ a { font-size: 0.86rem; } +.auth-credentials { + margin-top: 1.2rem; + padding: 1rem; + background: rgba(73, 211, 211, 0.08); + border: 1px solid rgba(73, 211, 211, 0.24); + border-radius: 0.75rem; +} + +.auth-credentials-title { + margin: 0 0 0.6rem 0; + font-size: 0.85rem; + font-weight: 600; + color: #49d3d3; +} + +.auth-credentials-list { + display: grid; + gap: 0.8rem; +} + +.auth-credential-item { + padding: 0.7rem; + background: rgba(12, 27, 52, 0.5); + border-radius: 0.6rem; + border: 1px solid rgba(73, 211, 211, 0.16); +} + +.auth-credential-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.8rem; + font-size: 0.8rem; + margin: 0.3rem 0; +} + +.auth-credential-label { + color: #7fa7c5; + font-weight: 500; +} + +.auth-credential-value { + color: #49d3d3; + font-family: "Courier New", monospace; + font-weight: 600; +} + +.auth-credential-role { + text-transform: capitalize; + background: rgba(73, 211, 211, 0.15); + padding: 0.2rem 0.5rem; + border-radius: 0.35rem; +} + .soc-navbar { position: sticky; top: 0; diff --git a/Frontend/src/styles/output.css b/Frontend/src/styles/output.css index e8449b1..af6320d 100644 --- a/Frontend/src/styles/output.css +++ b/Frontend/src/styles/output.css @@ -761,6 +761,12 @@ .text-yellow-400 { color: var(--color-yellow-400); } + .lowercase { + text-transform: lowercase; + } + .uppercase { + text-transform: uppercase; + } .accent-blue-500 { accent-color: var(--color-blue-500); } @@ -1196,6 +1202,38 @@ a { .input-row input::placeholder { color: #8ca7c3; } +.input-wrapper { + display: flex; + flex-direction: column; + gap: 0.35rem; +} +.input-row--error { + border-color: rgba(255, 102, 102, 0.68); + background: rgba(255, 102, 102, 0.08); +} +.input-row--error:focus-within { + border-color: rgba(255, 102, 102, 0.88); + box-shadow: 0 0 0 3px rgba(255, 102, 102, 0.16); +} +.input-error { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.75rem; + color: #ff6666; + padding: 0.2rem 0.4rem; + animation: slideIn 150ms ease; +} +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} .auth-submit { margin-top: 0.32rem; border: 0; @@ -1220,6 +1258,52 @@ a { color: #a6bdd8; font-size: 0.86rem; } +.auth-credentials { + margin-top: 1.2rem; + padding: 1rem; + background: rgba(73, 211, 211, 0.08); + border: 1px solid rgba(73, 211, 211, 0.24); + border-radius: 0.75rem; +} +.auth-credentials-title { + margin: 0 0 0.6rem 0; + font-size: 0.85rem; + font-weight: 600; + color: #49d3d3; +} +.auth-credentials-list { + display: grid; + gap: 0.8rem; +} +.auth-credential-item { + padding: 0.7rem; + background: rgba(12, 27, 52, 0.5); + border-radius: 0.6rem; + border: 1px solid rgba(73, 211, 211, 0.16); +} +.auth-credential-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.8rem; + font-size: 0.8rem; + margin: 0.3rem 0; +} +.auth-credential-label { + color: #7fa7c5; + font-weight: 500; +} +.auth-credential-value { + color: #49d3d3; + font-family: "Courier New", monospace; + font-weight: 600; +} +.auth-credential-role { + text-transform: capitalize; + background: rgba(73, 211, 211, 0.15); + padding: 0.2rem 0.5rem; + border-radius: 0.35rem; +} .soc-navbar { position: sticky; top: 0;