From a7edde6ea470b4031d691230ce37b6e1c1231809 Mon Sep 17 00:00:00 2001 From: Swayymalcolm99 Date: Tue, 28 Apr 2026 04:30:29 +0100 Subject: [PATCH] changes made updated files --- frontend/src/app/login/page.tsx | 78 ++++++++--- frontend/src/app/register/page.tsx | 128 +++++++++++++++--- .../components/ui/FormValidationSummary.tsx | 58 ++++++++ frontend/src/components/ui/Input.tsx | 43 ++++-- .../ui/PasswordStrengthIndicator.tsx | 78 +++++++++++ .../src/components/ui/ValidationMessage.tsx | 49 +++++++ frontend/src/hooks/useDebounce.ts | 17 +++ frontend/src/hooks/useFormFieldValidation.ts | 87 ++++++++++++ frontend/src/lib/validations/auth.ts | 56 +++++++- 9 files changed, 544 insertions(+), 50 deletions(-) create mode 100644 frontend/src/components/ui/FormValidationSummary.tsx create mode 100644 frontend/src/components/ui/PasswordStrengthIndicator.tsx create mode 100644 frontend/src/components/ui/ValidationMessage.tsx create mode 100644 frontend/src/hooks/useDebounce.ts create mode 100644 frontend/src/hooks/useFormFieldValidation.ts diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index d0ca143f..3862e1e4 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -14,10 +14,12 @@ import { CardTitle, } from "@/components/ui/Card"; import { FormError } from "@/components/ui/FormError"; +import { ValidationMessage } from "@/components/ui/ValidationMessage"; +import { FormValidationSummary } from "@/components/ui/FormValidationSummary"; import { useAuth } from "@/hooks/useAuth"; import { useNotifications } from "@/contexts/NotificationContext"; import { loginSchema, type LoginFormData } from "@/lib/validations/auth"; -import { cn } from "@/lib/utils"; +import { useFormFieldValidation } from "@/hooks/useFormFieldValidation"; export default function LoginPage() { const router = useRouter(); @@ -27,6 +29,22 @@ export default function LoginPage() { const [password, setPassword] = useState(""); const [rememberMe, setRememberMe] = useState(false); const [fieldErrors, setFieldErrors] = useState>>({}); + const [showValidationSummary, setShowValidationSummary] = useState(false); + + // Real-time field validation + const emailValidation = useFormFieldValidation({ + value: email, + schema: loginSchema, + fieldName: "email", + delay: 400, + }); + + const passwordValidation = useFormFieldValidation({ + value: password, + schema: loginSchema, + fieldName: "password", + delay: 400, + }); // Error toast when auth error is set useEffect(() => { @@ -59,6 +77,7 @@ export default function LoginPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setFieldErrors({}); + setShowValidationSummary(true); const result = loginSchema.safeParse({ email, password, rememberMe }); if (!result.success) { @@ -90,7 +109,14 @@ export default function LoginPage() { -
+ + {/* Validation Summary */} + } + isValid={Object.keys(fieldErrors).length === 0} + isVisible={showValidationSummary} + /> +
@@ -134,13 +170,23 @@ export default function LoginPage() { value={password} onChange={(e) => setPassword(e.target.value)} disabled={loading} - error={!!fieldErrors.password} + error={passwordValidation.isValid === false || !!fieldErrors.password} + success={passwordValidation.isValid === true} autoComplete="current-password" - aria-invalid={!!fieldErrors.password} + aria-invalid={passwordValidation.isValid === false || !!fieldErrors.password} + aria-describedby={passwordValidation.error ? "password-error" : passwordValidation.isValid ? "password-success" : undefined} + /> + - {fieldErrors.password && ( -

{fieldErrors.password}

- )}
setRememberMe(e.target.checked)} disabled={loading} - className={cn( - "h-4 w-4 rounded border-input bg-background text-primary", - "focus:ring-2 focus:ring-ring focus:ring-offset-2", - "disabled:cursor-not-allowed disabled:opacity-50" - )} + className={ + "h-4 w-4 rounded border-input bg-background text-primary focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" + } aria-describedby="rememberMe-description" />
) } ) diff --git a/frontend/src/components/ui/PasswordStrengthIndicator.tsx b/frontend/src/components/ui/PasswordStrengthIndicator.tsx new file mode 100644 index 00000000..1e96a2b1 --- /dev/null +++ b/frontend/src/components/ui/PasswordStrengthIndicator.tsx @@ -0,0 +1,78 @@ +import { getPasswordStrength } from "@/lib/validations/auth"; +import { Check, X } from "lucide-react"; + +interface PasswordStrengthIndicatorProps { + password: string; +} + +export function PasswordStrengthIndicator({ + password, +}: PasswordStrengthIndicatorProps) { + if (!password) return null; + + const strength = getPasswordStrength(password); + + const requirements = [ + { key: "minLength", label: "At least 8 characters" }, + { key: "hasUppercase", label: "One uppercase letter" }, + { key: "hasLowercase", label: "One lowercase letter" }, + { key: "hasNumber", label: "One number" }, + { key: "hasSpecial", label: "One special character" }, + ]; + + return ( +
+ {/* Strength Bar */} +
+
+ + Password Strength + + + {strength.label} + +
+
+ {[1, 2, 3, 4, 5].map((level) => ( + +
+ + {/* Requirements List */} +
    + {requirements.map(({ key, label }) => { + const met = strength.requirements[key as keyof typeof strength.requirements]; + return ( +
  • + {met ? ( +
  • + ); + })} +
+
+ ); +} diff --git a/frontend/src/components/ui/ValidationMessage.tsx b/frontend/src/components/ui/ValidationMessage.tsx new file mode 100644 index 00000000..7008e2ef --- /dev/null +++ b/frontend/src/components/ui/ValidationMessage.tsx @@ -0,0 +1,49 @@ +import { CheckCircle2, XCircle, AlertCircle } from "lucide-react"; + +interface ValidationMessageProps { + message: string | null; + state: "error" | "success" | "idle"; + id?: string; +} + +export function ValidationMessage({ + message, + state, + id, +}: ValidationMessageProps) { + if (!message && state === "idle") return null; + + const getIcon = () => { + switch (state) { + case "error": + return