256 lines
8.0 KiB
JavaScript
256 lines
8.0 KiB
JavaScript
import React, { useState } from 'react';
|
|
import Modal from './Modal';
|
|
import Spinner from './common/Spinner';
|
|
import { login, register } from '../client/auth-client';
|
|
|
|
const VIEW = {
|
|
LOGIN: 'login',
|
|
REGISTER: 'register',
|
|
};
|
|
|
|
export default function AuthModal({ isOpen, setIsOpen, onAuthSuccess }) {
|
|
const [view, setView] = useState(VIEW.LOGIN);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
const [loginUsername, setLoginUsername] = useState('');
|
|
const [loginPassword, setLoginPassword] = useState('');
|
|
|
|
const [registerUsername, setRegisterUsername] = useState('');
|
|
const [registerEmail, setRegisterEmail] = useState('');
|
|
const [registerPassword, setRegisterPassword] = useState('');
|
|
const [registerConfirmPassword, setRegisterConfirmPassword] = useState('');
|
|
|
|
const resetForm = () => {
|
|
setLoginUsername('');
|
|
setLoginPassword('');
|
|
setRegisterUsername('');
|
|
setRegisterEmail('');
|
|
setRegisterPassword('');
|
|
setRegisterConfirmPassword('');
|
|
setError('');
|
|
};
|
|
|
|
const handleClose = () => {
|
|
resetForm();
|
|
setView(VIEW.LOGIN);
|
|
};
|
|
|
|
const switchView = newView => {
|
|
resetForm();
|
|
setView(newView);
|
|
};
|
|
|
|
const handleLogin = async e => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!loginUsername || !loginPassword) {
|
|
setError('Please fill in all fields');
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
const result = await login(loginUsername, loginPassword);
|
|
setIsLoading(false);
|
|
|
|
if (result.success) {
|
|
resetForm();
|
|
setIsOpen(false);
|
|
if (onAuthSuccess) {
|
|
onAuthSuccess(result.value);
|
|
}
|
|
} else {
|
|
setError(result.error || 'Login failed');
|
|
}
|
|
};
|
|
|
|
const handleRegister = async e => {
|
|
e.preventDefault();
|
|
setError('');
|
|
|
|
if (!registerUsername || !registerEmail || !registerPassword || !registerConfirmPassword) {
|
|
setError('Please fill in all fields');
|
|
return;
|
|
}
|
|
|
|
if (registerPassword !== registerConfirmPassword) {
|
|
setError('Passwords do not match');
|
|
return;
|
|
}
|
|
|
|
if (registerPassword.length < 8) {
|
|
setError('Password must be at least 8 characters');
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
const result = await register(registerUsername, registerEmail, registerPassword);
|
|
setIsLoading(false);
|
|
|
|
if (result.success) {
|
|
resetForm();
|
|
setIsOpen(false);
|
|
if (onAuthSuccess) {
|
|
onAuthSuccess(result.value);
|
|
}
|
|
} else {
|
|
setError(result.error || 'Registration failed');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
isOpen={isOpen}
|
|
setIsOpen={setIsOpen}
|
|
onClose={handleClose}
|
|
className='w-96 shadow shadow-primary rounded-md bg-primary-alt'
|
|
>
|
|
<Modal.Header className='text-center small-caps tracking-wide text-xl text-accent font-semibold'>
|
|
{view === VIEW.LOGIN ? 'Login' : 'Create Account'}
|
|
</Modal.Header>
|
|
|
|
<Modal.Body className='text-primary text-sm'>
|
|
{view === VIEW.LOGIN ? (
|
|
<form onSubmit={handleLogin} className='m-4 flex flex-col gap-3'>
|
|
<label htmlFor='login-username' className='flex flex-col gap-1'>
|
|
<span className='text-secondary text-xs'>Username</span>
|
|
<input
|
|
id='login-username'
|
|
name='username'
|
|
type='text'
|
|
className='input-primary text-sm form-input'
|
|
placeholder='Enter username'
|
|
value={loginUsername}
|
|
onChange={e => setLoginUsername(e.target.value)}
|
|
disabled={isLoading}
|
|
autoComplete='username'
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor='login-password' className='flex flex-col gap-1'>
|
|
<span className='text-secondary text-xs'>Password</span>
|
|
<input
|
|
id='login-password'
|
|
name='password'
|
|
type='password'
|
|
className='input-primary text-sm form-input'
|
|
placeholder='Enter password'
|
|
value={loginPassword}
|
|
onChange={e => setLoginPassword(e.target.value)}
|
|
disabled={isLoading}
|
|
autoComplete='current-password'
|
|
/>
|
|
</label>
|
|
|
|
{error && <div className='text-error text-xs text-center'>{error}</div>}
|
|
|
|
<button type='submit' className='button-filled py-2 mt-2' disabled={isLoading}>
|
|
{isLoading ? (
|
|
<span className='flex items-center justify-center gap-2'>
|
|
<Spinner size={Spinner.SIZE.sm} /> Logging in...
|
|
</span>
|
|
) : (
|
|
'Login'
|
|
)}
|
|
</button>
|
|
</form>
|
|
) : (
|
|
<form onSubmit={handleRegister} className='m-4 flex flex-col gap-3'>
|
|
<label htmlFor='register-username' className='flex flex-col gap-1'>
|
|
<span className='text-secondary text-xs'>Username</span>
|
|
<input
|
|
id='register-username'
|
|
name='username'
|
|
type='text'
|
|
className='input-primary text-sm form-input'
|
|
placeholder='Choose a username'
|
|
value={registerUsername}
|
|
onChange={e => setRegisterUsername(e.target.value)}
|
|
disabled={isLoading}
|
|
autoComplete='username'
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor='register-email' className='flex flex-col gap-1'>
|
|
<span className='text-secondary text-xs'>Email</span>
|
|
<input
|
|
id='register-email'
|
|
name='email'
|
|
type='email'
|
|
className='input-primary text-sm form-input'
|
|
placeholder='Enter your email'
|
|
value={registerEmail}
|
|
onChange={e => setRegisterEmail(e.target.value)}
|
|
disabled={isLoading}
|
|
autoComplete='email'
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor='register-password' className='flex flex-col gap-1'>
|
|
<span className='text-secondary text-xs'>Password</span>
|
|
<input
|
|
id='register-password'
|
|
name='new-password'
|
|
type='password'
|
|
className='input-primary text-sm form-input'
|
|
placeholder='Create a password'
|
|
value={registerPassword}
|
|
onChange={e => setRegisterPassword(e.target.value)}
|
|
disabled={isLoading}
|
|
autoComplete='new-password'
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor='register-confirm-password' className='flex flex-col gap-1'>
|
|
<span className='text-secondary text-xs'>Confirm Password</span>
|
|
<input
|
|
id='register-confirm-password'
|
|
name='confirm-password'
|
|
type='password'
|
|
className='input-primary text-sm form-input'
|
|
placeholder='Confirm your password'
|
|
value={registerConfirmPassword}
|
|
onChange={e => setRegisterConfirmPassword(e.target.value)}
|
|
disabled={isLoading}
|
|
autoComplete='new-password'
|
|
/>
|
|
</label>
|
|
|
|
{error && <div className='text-error text-xs text-center'>{error}</div>}
|
|
|
|
<button type='submit' className='button-filled py-2 mt-2' disabled={isLoading}>
|
|
{isLoading ? (
|
|
<span className='flex items-center justify-center gap-2'>
|
|
<Spinner size={Spinner.SIZE.sm} /> Creating account...
|
|
</span>
|
|
) : (
|
|
'Create Account'
|
|
)}
|
|
</button>
|
|
</form>
|
|
)}
|
|
</Modal.Body>
|
|
|
|
<Modal.Footer className='text-center text-sm'>
|
|
{view === VIEW.LOGIN ? (
|
|
<p className='text-secondary py-2'>
|
|
Don't have an account?{' '}
|
|
<button type='button' className='text-accent hover:underline' onClick={() => switchView(VIEW.REGISTER)}>
|
|
Register
|
|
</button>
|
|
</p>
|
|
) : (
|
|
<p className='text-secondary py-2'>
|
|
Already have an account?{' '}
|
|
<button type='button' className='text-accent hover:underline' onClick={() => switchView(VIEW.LOGIN)}>
|
|
Login
|
|
</button>
|
|
</p>
|
|
)}
|
|
</Modal.Footer>
|
|
</Modal>
|
|
);
|
|
|
|
}
|