From 640ca2a56356019ce122d58a8fed47a15d157ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Sat, 6 Dec 2025 18:34:03 +0100 Subject: [PATCH] feat(ui): improve password validation display with detailed requirements - Enhance PasswordStrengthIndicator with visual checkmarks for each requirement - Add explicit validation for uppercase, lowercase, and number requirements - Show clear pass/fail indicators (CheckCircle/XCircle icons) for each criterion - Add front-end validation matching production password policy - Display specific error messages listing all missing requirements - Align with production standards (8+ chars, uppercase, lowercase, number) --- .../common/PasswordStrengthIndicator.jsx | 98 +++++++++++++++---- frontend/src/pages/RegisterPage.jsx | 19 +++- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/common/PasswordStrengthIndicator.jsx b/frontend/src/components/common/PasswordStrengthIndicator.jsx index 6907efc..23848bd 100644 --- a/frontend/src/components/common/PasswordStrengthIndicator.jsx +++ b/frontend/src/components/common/PasswordStrengthIndicator.jsx @@ -1,24 +1,37 @@ import { useMemo } from 'react'; +import { CheckCircle, XCircle } from 'lucide-react'; /** * Password Strength Indicator Component * Calculates and displays password strength with visual feedback + * Shows requirements based on production environment standards */ const PasswordStrengthIndicator = ({ password }) => { + const requirements = useMemo(() => { + const checks = { + minLength: password.length >= 8, + hasUppercase: /[A-Z]/.test(password), + hasLowercase: /[a-z]/.test(password), + hasNumber: /[0-9]/.test(password), + }; + + return checks; + }, [password]); + const strength = useMemo(() => { if (!password) return { score: 0, label: '', color: '' }; let score = 0; - // Length check - if (password.length >= 8) score++; - if (password.length >= 12) score++; + // Count met requirements (production standards) + if (requirements.minLength) score++; + if (requirements.hasUppercase) score++; + if (requirements.hasLowercase) score++; + if (requirements.hasNumber) score++; - // Character variety checks - if (/[a-z]/.test(password)) score++; // lowercase - if (/[A-Z]/.test(password)) score++; // uppercase - if (/[0-9]/.test(password)) score++; // numbers - if (/[^a-zA-Z0-9]/.test(password)) score++; // special chars + // Bonus points for extra length and special chars + if (password.length >= 12) score++; + if (/[^a-zA-Z0-9]/.test(password)) score++; // Determine label and color if (score <= 2) { @@ -28,7 +41,13 @@ const PasswordStrengthIndicator = ({ password }) => { } else { return { score, label: 'Strong', color: 'bg-green-500' }; } - }, [password]); + }, [password, requirements]); + + // Check if all required criteria are met + const allRequirementsMet = requirements.minLength && + requirements.hasUppercase && + requirements.hasLowercase && + requirements.hasNumber; if (!password) return null; @@ -52,17 +71,56 @@ const PasswordStrengthIndicator = ({ password }) => { style={{ width: `${widthPercentage}%` }} /> - +
+

Password requirements:

+ +
+ {!allRequirementsMet && password.length > 0 && ( +

+ Please meet all requirements above +

+ )} ); }; diff --git a/frontend/src/pages/RegisterPage.jsx b/frontend/src/pages/RegisterPage.jsx index bda9f4d..19657cc 100644 --- a/frontend/src/pages/RegisterPage.jsx +++ b/frontend/src/pages/RegisterPage.jsx @@ -174,14 +174,29 @@ const RegisterPage = () => { e.preventDefault(); setError(''); - // Validation + // Validation - passwords match if (formData.password !== formData.confirmPassword) { setError('Passwords do not match'); return; } + // Validation - password requirements (production standards) + const passwordErrors = []; if (formData.password.length < 8) { - setError('Password must be at least 8 characters long'); + passwordErrors.push('at least 8 characters'); + } + if (!/[A-Z]/.test(formData.password)) { + passwordErrors.push('one uppercase letter'); + } + if (!/[a-z]/.test(formData.password)) { + passwordErrors.push('one lowercase letter'); + } + if (!/[0-9]/.test(formData.password)) { + passwordErrors.push('one number'); + } + + if (passwordErrors.length > 0) { + setError(`Password must contain ${passwordErrors.join(', ')}`); return; }