Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 201 additions & 41 deletions frontend/src/Pages/Authentication/forms.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useContext, useState, useEffect } from 'react';
import { useContext, useState, useEffect, useRef } from 'react';
import { AuthContext } from '../../context/authContext';
import { useCallback } from "react";

Expand All @@ -10,6 +9,110 @@ interface LoginFormProps {
infoMessage?: string;
}

// Email autocomplete component
interface EmailInputWithSuggestionsProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
className?: string;
}

const EmailInputWithSuggestions: React.FC<EmailInputWithSuggestionsProps> = ({
value,
onChange,
placeholder = "name@example.com",
className = ""
}) => {
const [showSuggestions, setShowSuggestions] = useState(false);
const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(-1);
const wrapperRef = useRef<HTMLDivElement>(null);

const emailDomains = ['@gmail.com', '@yahoo.com', '@outlook.com', '@hotmail.com'];

const getEmailSuggestions = (email: string) => {
if (!email || email.includes('@')) return [];
return emailDomains.map(domain => email + domain);
};

const suggestions = getEmailSuggestions(value);

const handleInputChange = (newValue: string) => {
onChange(newValue);
if (newValue && !newValue.includes('@')) {
setShowSuggestions(true);
setActiveSuggestionIndex(-1);
} else {
setShowSuggestions(false);
}
};

const handleSuggestionClick = (suggestion: string) => {
onChange(suggestion);
setShowSuggestions(false);
};

const handleKeyDown = (e: React.KeyboardEvent) => {
if (!showSuggestions || suggestions.length === 0) return;

if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveSuggestionIndex(prev =>
prev < suggestions.length - 1 ? prev + 1 : prev
);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveSuggestionIndex(prev => prev > 0 ? prev - 1 : -1);
} else if (e.key === 'Enter' && activeSuggestionIndex >= 0) {
e.preventDefault();
handleSuggestionClick(suggestions[activeSuggestionIndex]);
} else if (e.key === 'Escape') {
setShowSuggestions(false);
}
};

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
setShowSuggestions(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

return (
<div ref={wrapperRef} className="relative w-full">
<Input
type="text"
value={value}
onChange={(e) => handleInputChange(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => value && !value.includes('@') && setShowSuggestions(true)}
placeholder={placeholder}
className={className}
/>
{showSuggestions && suggestions.length > 0 && (
<div className="absolute z-10 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-300 dark:border-white rounded-md shadow-lg max-h-48 overflow-auto">
{suggestions.map((suggestion, index) => (
<div
key={suggestion}
onClick={() => handleSuggestionClick(suggestion)}
className={`px-4 py-2 cursor-pointer ${
index === activeSuggestionIndex
? 'bg-gray-100 dark:bg-slate-700'
: 'hover:bg-gray-100 dark:hover:bg-slate-700'
}`}
>
{suggestion}
</div>
))}
</div>
Comment thread
compiler041 marked this conversation as resolved.
)}
</div>
);
};

export const LoginForm: React.FC<LoginFormProps> = ({ startForgotPassword, infoMessage }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
Expand All @@ -24,28 +127,50 @@ export const LoginForm: React.FC<LoginFormProps> = ({ startForgotPassword, infoM

const [localError, setLocalError] = useState<string | null>(null);

const MIN_PASSWORD_LENGTH = 8;

const MIN_PASSWORD_LENGTH = 8;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (password.length < MIN_PASSWORD_LENGTH) {
setLocalError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
return;
}
setLocalError(null);
await login(email, password);
};
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLocalError(null);

// Validate email
if (!email.trim()) {
setLocalError('Please enter your email address');
return;
}

if (!validateEmail(email)) {
setLocalError('Please enter a valid email address');
return;
}

// Validate password
if (!password) {
setLocalError('Please enter your password');
return;
}

if (password.length < MIN_PASSWORD_LENGTH) {
setLocalError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
return;
}

await login(email, password);
};

const handleGoogleLogin = useCallback(
(response: { credential: string; select_by: string }) => {
const idToken = response.credential;
googleLogin(idToken);
},
[googleLogin]
);

const handleGoogleLogin = useCallback(
(response: { credential: string; select_by: string }) => {
const idToken = response.credential;
googleLogin(idToken);
},
[googleLogin]
);
useEffect(() => {
const google = window.google;
if (!google?.accounts) {
Expand Down Expand Up @@ -75,11 +200,10 @@ const handleGoogleLogin = useCallback(
return (
<form className="w-full" onSubmit={handleSubmit}>
{infoMessage && <p className="text-sm text-green-500 mb-2">{infoMessage}</p>}
<Input
type="email"
placeholder="name@example.com"
<EmailInputWithSuggestions
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={setEmail}
placeholder="name@example.com"
className="mb-2 dark:border-white"
/>
<Input
Expand All @@ -90,10 +214,10 @@ const handleGoogleLogin = useCallback(
className="mb-1 dark:border-white"
/>
{localError && (
<p className="text-red-500 text-sm mt-2">
<p className="text-red-500 text-sm mt-2 mb-2">
{localError}
</p>
)}
)}
<div className='w-full flex justify-start items-center pl-1'>
<div className='w-4'>
<Input
Expand Down Expand Up @@ -137,26 +261,63 @@ export const SignUpForm: React.FC<SignUpFormProps> = ({ startOtpVerification })

const { signup, googleLogin, error, loading } = authContext;

const [localError, setLocalError] = useState<string | null>(null);

const MIN_PASSWORD_LENGTH = 8;

const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLocalError(null);

// Validate email
if (!email.trim()) {
setLocalError('Please enter your email address');
return;
}

if (!validateEmail(email)) {
setLocalError('Please enter a valid email address');
return;
}

// Validate password
if (!password) {
setLocalError('Please enter a password');
return;
}

if (password.length < MIN_PASSWORD_LENGTH) {
setLocalError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
return;
}

// Validate confirm password
if (!confirmPassword) {
setLocalError('Please confirm your password');
return;
}

if (password !== confirmPassword) {
authContext.handleError('Passwords do not match');
setLocalError('Passwords do not match');
return;
}

await signup(email, password);
startOtpVerification(email);
};

const handleGoogleLogin = useCallback(
(response: { credential: string; select_by: string }) => {
const idToken = response.credential;
googleLogin(idToken);
},
[googleLogin]
);

const handleGoogleLogin = useCallback(
(response: { credential: string; select_by: string }) => {
const idToken = response.credential;
googleLogin(idToken);
},
[googleLogin]
);

useEffect(() => {
const google = window.google;
Expand Down Expand Up @@ -186,11 +347,10 @@ export const SignUpForm: React.FC<SignUpFormProps> = ({ startOtpVerification })

return (
<form className="w-full" onSubmit={handleSubmit}>
<Input
type="email"
placeholder="name@example.com"
<EmailInputWithSuggestions
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={setEmail}
placeholder="name@example.com"
className="mb-2 dark:border-white"
/>
<Input
Expand Down Expand Up @@ -218,6 +378,7 @@ export const SignUpForm: React.FC<SignUpFormProps> = ({ startOtpVerification })
</div>
<div className='pl-2'>show password</div>
</div>
{localError && <p className="text-sm text-red-500 mb-2">{localError}</p>}
{error && <p className="text-sm text-red-500 mb-2">{error}</p>}
<Button type="submit" className="w-full mb-2 border dark:border-white" disabled={loading}>
{loading ? 'Creating Account...' : 'Sign Up With Email'}
Expand Down Expand Up @@ -308,10 +469,9 @@ export const ForgotPasswordForm: React.FC<ForgotPasswordFormProps> = ({
<h3 className="text-2xl font-medium my-4">Reset Password</h3>
<p className="mb-4">Enter your email to receive a password reset code.</p>
<form onSubmit={handleSubmit} className="w-full">
<Input
type="email"
<EmailInputWithSuggestions
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={setEmail}
placeholder="name@example.com"
className="w-full mb-4 dark:border-white"
/>
Expand Down
Loading