diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index dda2c76..839f2ea 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -30,6 +30,7 @@ export default function Login() { value={email} placeholder="Email" /> + {/* Email input*/} Sign in - + {' '} + {/* Sign in button */} > ); } diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx index c409a32..ad3a436 100644 --- a/app/(auth)/signup/page.tsx +++ b/app/(auth)/signup/page.tsx @@ -1,24 +1,73 @@ 'use client'; import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { useAuth } from '../../../utils/AuthProvider'; +import PasswordComplexity from '@/components/PasswordComplexity'; +import PasswordInput from '@/components/PasswordInput'; +import { useAuth } from '@/utils/AuthProvider'; export default function SignUp() { const { signUp } = useAuth(); const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const router = useRouter(); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [passwordComplexityError, setPasswordComplexityError] = useState< + string | null + >(null); + const [passwordComplexity, setPasswordComplexity] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + + // Handles input to password + const handlePasswordChange = (e: React.ChangeEvent) => { + const newPassword = e.target.value; + setPassword(newPassword); + validatePasswords(newPassword, confirmPassword); + validatePasswordComplexity(newPassword); + }; + + // Handles input to confirm password + const handleConfirmPasswordChange = ( + e: React.ChangeEvent, + ) => { + const newConfirmPassword = e.target.value; + setConfirmPassword(newConfirmPassword); + validatePasswords(password, newConfirmPassword); + }; + + // Checks if passwords match and sets error + const validatePasswords = ( + password: string | null, + confirmPassword: string | null, + ) => { + if (password !== confirmPassword) { + setPasswordError('Passwords do not match.'); + } else { + setPasswordError(''); // Clear error when passwords match + } + }; + + // Set password complexity error if requirements are not met + const validatePasswordComplexity = (password: string | null) => { + const hasLowerCase = /[a-z]/.test(password || ''); + const hasNumber = /\d/.test(password || ''); + const longEnough = (password || '').length >= 8; + + if (password && hasLowerCase && hasNumber && longEnough) { + setPasswordComplexity(true); + setPasswordComplexityError(null); // Clear error if all conditions are met + } else if (password) { + setPasswordComplexity(false); + setPasswordComplexityError('Password must meet complexity requirements'); + } else { + setPasswordComplexity(false); + setPasswordComplexityError(null); // Clear error if password is empty + } + }; const handleSignUp = async () => { - // Define handleSignUp - try { + if (password) { await signUp(email, password); - router.push('/'); - } catch (error) { - if (error instanceof Error) { - console.error(error.message); - } } }; @@ -30,24 +79,45 @@ export default function SignUp() { value={email} placeholder="Email" /> - setPassword(e.target.value)} + {/* Email input*/} + setShowPassword(!showPassword)} + name="password" /> - setPassword(e.target.value)} - value={password} + {/* Password input with toggle visibility */} + setShowConfirmPassword(!showConfirmPassword)} + name="confirmPassword" /> - + {/* Confirm password input with toggle visibility */} + Sign up {' '} {/* Sign up button */} + {confirmPassword && passwordError && ( + {passwordError} + )} + {/* Conditional password validation error message */} + + {/* Password complexity requirements */} + {password && !passwordComplexity && passwordComplexityError && ( + {passwordComplexityError} + )} + {/* Password complexity error message */} > ); } diff --git a/components/PasswordComplexity.tsx b/components/PasswordComplexity.tsx new file mode 100644 index 0000000..e7b2ba1 --- /dev/null +++ b/components/PasswordComplexity.tsx @@ -0,0 +1,26 @@ +export default function PasswordComplexity({ password }: { password: string }) { + // Display requirements if there is input + if (password.length > 0) { + return ( + + + + = 8} text="At least 8 characters" /> + + ); + } + + return null; +} + +// Helper component to display each requirement with conditional styling +function Requirement({ met, text }: { met: boolean; text: string }) { + return ( + + {met ? '✓' : '✗'} {text} + + ); +} diff --git a/components/PasswordInput.tsx b/components/PasswordInput.tsx new file mode 100644 index 0000000..e10a3c9 --- /dev/null +++ b/components/PasswordInput.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +interface PasswordInputProps { + value: string | null; + onChange: (e: React.ChangeEvent) => void; + placeholder: string; + isVisible: boolean; + toggleVisibility: () => void; + name: string; +} + +const PasswordInput: React.FC = ({ + value, + onChange, + placeholder, + isVisible, + toggleVisibility, + name, +}) => { + return ( + + + + {isVisible ? 'Hide' : 'Show'} + + + ); +}; + +export default PasswordInput; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f247f8b..2b691b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2466,7 +2466,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -2479,7 +2479,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -2501,7 +2501,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3
{passwordError}
{passwordComplexityError}
+ {met ? '✓' : '✗'} {text} +