Files
rxminder/components/auth/AuthPage.tsx

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;