From dfcb6d18806df97d115997f6ddfd86ad40b00542 Mon Sep 17 00:00:00 2001 From: s0up4200 Date: Wed, 6 Nov 2024 12:43:46 +0100 Subject: [PATCH] feat(auth): add password confirm field --- package.json | 1 + pnpm-lock.yaml | 11 +++ src/components/auth/LoginPage.tsx | 158 ++++++++++++++++++++++++++++-- 3 files changed, 160 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 47e1b94..3e654a8 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@emotion/styled": "^11.13.0", "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-brands-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f9cebd..7f453fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@fortawesome/free-brands-svg-icons': specifier: ^6.6.0 version: 6.6.0 + '@fortawesome/free-solid-svg-icons': + specifier: ^6.6.0 + version: 6.6.0 '@fortawesome/react-fontawesome': specifier: ^0.2.2 version: 0.2.2(@fortawesome/fontawesome-svg-core@6.6.0)(react@18.3.1) @@ -525,6 +528,10 @@ packages: resolution: {integrity: sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==} engines: {node: '>=6'} + '@fortawesome/free-solid-svg-icons@6.6.0': + resolution: {integrity: sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==} + engines: {node: '>=6'} + '@fortawesome/react-fontawesome@0.2.2': resolution: {integrity: sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==} peerDependencies: @@ -2678,6 +2685,10 @@ snapshots: dependencies: '@fortawesome/fontawesome-common-types': 6.6.0 + '@fortawesome/free-solid-svg-icons@6.6.0': + dependencies: + '@fortawesome/fontawesome-common-types': 6.6.0 + '@fortawesome/react-fontawesome@0.2.2(@fortawesome/fontawesome-svg-core@6.6.0)(react@18.3.1)': dependencies: '@fortawesome/fontawesome-svg-core': 6.6.0 diff --git a/src/components/auth/LoginPage.tsx b/src/components/auth/LoginPage.tsx index 3fa9962..b563202 100644 --- a/src/components/auth/LoginPage.tsx +++ b/src/components/auth/LoginPage.tsx @@ -5,6 +5,7 @@ import { RegisterCredentials } from "../../types/auth"; import { toast } from "react-hot-toast"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faOpenid } from "@fortawesome/free-brands-svg-icons"; +import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; import Toast from "../Toast"; import logo from "../../assets/logo.svg"; import { Footer } from "../shared/Footer"; @@ -27,12 +28,24 @@ export function LoginPage() { const [checkingRegistration, setCheckingRegistration] = useState(true); // Form state - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState< + RegisterCredentials & { confirmPassword?: string } + >({ username: "", password: "", + confirmPassword: "", email: "", // Will be set during registration }); + // Password validation state + const [passwordValidation, setPasswordValidation] = useState({ + minLength: false, + hasUppercase: false, + hasLowercase: false, + hasNumber: false, + passwordsMatch: false, + }); + // Get the return URL from location state, or default to '/' const from = (location.state as { from?: { pathname: string } })?.from?.pathname || "/"; @@ -73,6 +86,21 @@ export function LoginPage() { } }, [isAuthenticated, loading, navigate, from]); + // Password validation effect + useEffect(() => { + if (isRegistering) { + const password = formData.password; + setPasswordValidation({ + minLength: password.length >= 8, + hasUppercase: /[A-Z]/.test(password), + hasLowercase: /[a-z]/.test(password), + hasNumber: /[0-9]/.test(password), + passwordsMatch: + password === formData.confirmPassword && password !== "", + }); + } + }, [formData.password, formData.confirmPassword, isRegistering]); + const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ @@ -88,6 +116,31 @@ export function LoginPage() { try { if (isRegistering) { + // Check if passwords match + if (formData.password !== formData.confirmPassword) { + setError("Passwords do not match"); + toast.custom((t) => ( + + )); + return; + } + + // Check if all password requirements are met + const allRequirementsMet = Object.values(passwordValidation).every( + (value) => value + ); + if (!allRequirementsMet) { + setError("Please meet all password requirements"); + toast.custom((t) => ( + + )); + return; + } + try { // Generate a default email using the username const defaultEmail = `${formData.username}@dashbrr.local`; @@ -224,24 +277,109 @@ export function LoginPage() { name="password" type="password" required - className="appearance-none rounded-b-md relative block w-full px-3 py-2 border border-gray-700 dark:border-gray-900 bg-gray-700 text-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" + className="appearance-none relative block w-full px-3 py-2 border border-gray-700 dark:border-gray-900 bg-gray-700 text-gray-300 placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" placeholder="Password" value={formData.password} onChange={handleInputChange} /> + {isRegistering && ( +
+ + +
+ )} {isRegistering && (
-
-

Password Requirements:

-
    -
  • Minimum 8 characters
  • -
  • At least one uppercase letter
  • -
  • At least one lowercase letter
  • -
  • At least one number
  • -
  • At least one special character
  • +
    +

    + Password Requirements: +

    +
      +
    • + + Minimum 8 characters +
    • +
    • + + At least one uppercase letter +
    • +
    • + + At least one lowercase letter +
    • +
    • + + At least one number +
    • +
    • + + Passwords match +