317 lines
13 KiB
TypeScript
317 lines
13 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useUser } from '../../contexts/UserContext';
|
|
import { authService } from '../../services/auth/auth.service';
|
|
import { PillIcon } from '../icons/Icons';
|
|
|
|
const AuthPage: React.FC = () => {
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [isSignUp, setIsSignUp] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const { login, register, loginWithOAuth } = useUser();
|
|
|
|
// State for email verification result
|
|
const [verificationResult, setVerificationResult] = useState<
|
|
null | 'success' | 'error'
|
|
>(null);
|
|
|
|
// Extract token from URL and verify email
|
|
useEffect(() => {
|
|
const path = window.location.pathname;
|
|
const params = new URLSearchParams(window.location.search);
|
|
const token = params.get('token');
|
|
if (path === '/verify-email' && token) {
|
|
authService
|
|
.verifyEmail(token)
|
|
.then(() => setVerificationResult('success'))
|
|
.catch(() => setVerificationResult('error'));
|
|
}
|
|
}, []);
|
|
|
|
// FIX: Made the function async and added await to handle promises from login.
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!email.trim()) {
|
|
setError('Email cannot be empty.');
|
|
return;
|
|
}
|
|
|
|
if (!password.trim()) {
|
|
setError('Password cannot be empty.');
|
|
return;
|
|
}
|
|
|
|
// Validate email format (allow localhost for admin)
|
|
const emailRegex = /^[^\s@]+@[^\s@]+$/; // Simplified to allow any domain including localhost
|
|
if (!emailRegex.test(email)) {
|
|
setError('Please enter a valid email address.');
|
|
return;
|
|
}
|
|
|
|
if (isSignUp) {
|
|
// Registration
|
|
if (password.length < 6) {
|
|
setError('Password must be at least 6 characters long.');
|
|
return;
|
|
}
|
|
|
|
if (password !== confirmPassword) {
|
|
setError('Passwords do not match.');
|
|
return;
|
|
}
|
|
|
|
const success = await register(email, password);
|
|
if (success) {
|
|
setError(
|
|
'Registration successful! Please check your email for verification (demo: verification not actually sent).'
|
|
);
|
|
setIsSignUp(false); // Switch back to login mode
|
|
setPassword('');
|
|
setConfirmPassword('');
|
|
} else {
|
|
setError('Registration failed. Email may already be in use.');
|
|
}
|
|
} else {
|
|
// Login
|
|
const success = await login(email, password);
|
|
if (!success) {
|
|
setError('Login failed. Please check your email and password.');
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleOAuthLogin = async (provider: 'google' | 'github') => {
|
|
setError('');
|
|
try {
|
|
// Mock OAuth data - in a real app, this would come from the OAuth provider
|
|
const mockUserData = {
|
|
email: provider === 'google' ? 'user@gmail.com' : 'user@github.com',
|
|
username: `${provider}_user_${Date.now()}`,
|
|
avatar: `https://via.placeholder.com/150?text=${provider.toUpperCase()}`,
|
|
};
|
|
|
|
const success = await loginWithOAuth(provider, mockUserData);
|
|
if (!success) {
|
|
setError(`${provider} authentication failed. Please try again.`);
|
|
}
|
|
} catch {
|
|
setError(`${provider} authentication failed. Please try again.`);
|
|
}
|
|
};
|
|
|
|
if (verificationResult) {
|
|
return (
|
|
<div className='min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-900'>
|
|
<div className='text-center'>
|
|
{verificationResult === 'success' ? (
|
|
<p className='text-green-600'>
|
|
Email verified successfully! You can now sign in.
|
|
</p>
|
|
) : (
|
|
<p className='text-red-600'>
|
|
Email verification failed. Please try again.
|
|
</p>
|
|
)}
|
|
<button
|
|
onClick={() => {
|
|
setVerificationResult(null);
|
|
window.location.href = '/';
|
|
}}
|
|
className='mt-4 px-4 py-2 bg-indigo-600 text-white rounded'
|
|
>
|
|
Go to Sign In
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-900 px-4'>
|
|
<div className='w-full max-w-sm'>
|
|
<div className='text-center mb-8'>
|
|
<div className='inline-block bg-indigo-600 p-3 rounded-xl mb-4'>
|
|
<PillIcon className='w-8 h-8 text-white' />
|
|
</div>
|
|
<h1 className='text-3xl font-bold text-slate-800 dark:text-slate-100'>
|
|
Medication Reminder
|
|
</h1>
|
|
<p className='text-slate-500 dark:text-slate-400 mt-1'>
|
|
Sign in with your email or create an account
|
|
</p>
|
|
</div>
|
|
|
|
<div className='bg-white dark:bg-slate-800 rounded-lg shadow-lg p-8'>
|
|
<div className='flex space-x-1 mb-6'>
|
|
<button
|
|
type='button'
|
|
onClick={() => {
|
|
setIsSignUp(false);
|
|
setError('');
|
|
setPassword('');
|
|
setConfirmPassword('');
|
|
}}
|
|
className={`flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors ${
|
|
!isSignUp
|
|
? 'bg-indigo-600 text-white'
|
|
: 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600'
|
|
}`}
|
|
>
|
|
Sign In
|
|
</button>
|
|
<button
|
|
type='button'
|
|
onClick={() => {
|
|
setIsSignUp(true);
|
|
setError('');
|
|
setPassword('');
|
|
setConfirmPassword('');
|
|
}}
|
|
className={`flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors ${
|
|
isSignUp
|
|
? 'bg-indigo-600 text-white'
|
|
: 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600'
|
|
}`}
|
|
>
|
|
Sign Up
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className='mb-6'>
|
|
<div className='space-y-4'>
|
|
<div>
|
|
<label
|
|
htmlFor='email'
|
|
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
|
|
>
|
|
Email Address
|
|
</label>
|
|
<input
|
|
type='email'
|
|
id='email'
|
|
value={email}
|
|
onChange={e => setEmail(e.target.value)}
|
|
required
|
|
autoFocus
|
|
className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white'
|
|
placeholder='your@email.com'
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
htmlFor='password'
|
|
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
|
|
>
|
|
Password
|
|
</label>
|
|
<input
|
|
type='password'
|
|
id='password'
|
|
value={password}
|
|
onChange={e => setPassword(e.target.value)}
|
|
required
|
|
className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white'
|
|
placeholder={
|
|
isSignUp
|
|
? 'Create a password (min 6 characters)'
|
|
: 'Enter your password'
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{isSignUp && (
|
|
<div>
|
|
<label
|
|
htmlFor='confirmPassword'
|
|
className='block text-sm font-medium text-slate-700 dark:text-slate-300'
|
|
>
|
|
Confirm Password
|
|
</label>
|
|
<input
|
|
type='password'
|
|
id='confirmPassword'
|
|
value={confirmPassword}
|
|
onChange={e => setConfirmPassword(e.target.value)}
|
|
required
|
|
className='mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm bg-white dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white'
|
|
placeholder='Confirm your password'
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{error && (
|
|
<p
|
|
className={`text-sm mt-3 ${error.includes('successful') ? 'text-green-600' : 'text-red-500'}`}
|
|
>
|
|
{error}
|
|
</p>
|
|
)}
|
|
|
|
<button
|
|
type='submit'
|
|
className='w-full mt-6 bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-md transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500'
|
|
>
|
|
{isSignUp ? 'Create Account' : 'Sign In'}
|
|
</button>
|
|
</form>
|
|
|
|
<div className='relative mb-6'>
|
|
<div className='absolute inset-0 flex items-center'>
|
|
<div className='w-full border-t border-slate-300 dark:border-slate-600' />
|
|
</div>
|
|
<div className='relative flex justify-center text-sm'>
|
|
<span className='px-2 bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400'>
|
|
Or create an account with
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className='space-y-3'>
|
|
<button
|
|
onClick={() => handleOAuthLogin('google')}
|
|
className='w-full flex items-center justify-center px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-md shadow-sm text-sm font-medium text-slate-700 dark:text-slate-200 bg-white dark:bg-slate-700 hover:bg-slate-50 dark:hover:bg-slate-600 transition-colors duration-200'
|
|
>
|
|
<svg className='w-4 h-4 mr-2' viewBox='0 0 24 24'>
|
|
<path
|
|
fill='#4285F4'
|
|
d='M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z'
|
|
/>
|
|
<path
|
|
fill='#34A853'
|
|
d='M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z'
|
|
/>
|
|
<path
|
|
fill='#FBBC05'
|
|
d='M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z'
|
|
/>
|
|
<path
|
|
fill='#EA4335'
|
|
d='M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z'
|
|
/>
|
|
</svg>
|
|
Continue with Google
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => handleOAuthLogin('github')}
|
|
className='w-full flex items-center justify-center px-4 py-2 border border-slate-300 dark:border-slate-600 rounded-md shadow-sm text-sm font-medium text-slate-700 dark:text-slate-200 bg-white dark:bg-slate-700 hover:bg-slate-50 dark:hover:bg-slate-600 transition-colors duration-200'
|
|
>
|
|
<svg className='w-4 h-4 mr-2 fill-current' viewBox='0 0 24 24'>
|
|
<path d='M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z' />
|
|
</svg>
|
|
Continue with GitHub
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AuthPage;
|