feat: initialize database and authentication routes in backend

This commit is contained in:
LaurensBartelds committed 2026-04-21 22:51:22 +02:00
1 parent be69db04a5
commit eb013d4978
22 files changed
+1808 -8

No files matched your search

+201
View File
@@ -0,0 +1,201 @@
import { useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { ApiError } from '../utils/api'
interface Props {
onClose: () => void
onForgotPassword: () => void
}
type Tab = 'login' | 'register'
export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>) {
const { login, register } = useAuth()
const [tab, setTab] = useState<Tab>('login')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [displayName, setDisplayName] = useState('')
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null)
const [submitting, setSubmitting] = useState(false)
const switchTab = (t: Tab) => {
setTab(t)
setError(null)
setSuccess(null)
}
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
setSubmitting(true)
try {
await login(email, password)
onClose()
} catch (err) {
setError(err instanceof ApiError ? err.message : 'Login failed')
} finally {
setSubmitting(false)
}
}
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
if (password !== confirmPassword) {
setError('Passwords do not match')
return
}
setSubmitting(true)
try {
const message = await register(email, password, displayName)
setSuccess(message)
} catch (err) {
setError(err instanceof ApiError ? err.message : 'Registration failed')
} finally {
setSubmitting(false)
}
}
return (
<div className="auth-overlay" onClick={onClose}>
<div className="auth-modal" onClick={(e) => e.stopPropagation()}>
<button className="auth-close" onClick={onClose} aria-label="Close">×</button>
<div className="auth-header">
<div className="brand-mark" />
<h2 className="auth-title">
{tab === 'login' ? 'Welcome back' : 'Create account'}
</h2>
<p className="auth-subtitle">
{tab === 'login'
? 'Sign in to access your saved exams and results'
: 'Create an account to save your progress'}
</p>
</div>
<div className="auth-tabs">
<button
className={`auth-tab ${tab === 'login' ? 'active' : ''}`}
onClick={() => switchTab('login')}
>
Sign In
</button>
<button
className={`auth-tab ${tab === 'register' ? 'active' : ''}`}
onClick={() => switchTab('register')}
>
Register
</button>
</div>
{error && <div className="auth-error">{error}</div>}
{success && <div className="auth-success">{success}</div>}
{!success && tab === 'login' && (
<form onSubmit={(e) => void handleLogin(e)} className="auth-form">
<div className="auth-field">
<label className="auth-label" htmlFor="login-email">Email</label>
<input
id="login-email"
className="input"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
autoComplete="email"
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="login-password">Password</label>
<input
id="login-password"
className="input"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
autoComplete="current-password"
/>
</div>
<button className="btn btn-primary auth-submit" type="submit" disabled={submitting}>
{submitting ? 'Signing in…' : 'Sign In'}
</button>
<button
type="button"
className="auth-forgot"
onClick={() => { onClose(); onForgotPassword() }}
>
Forgot your password?
</button>
</form>
)}
{!success && tab === 'register' && (
<form onSubmit={(e) => void handleRegister(e)} className="auth-form">
<div className="auth-field">
<label className="auth-label" htmlFor="reg-name">Display Name</label>
<input
id="reg-name"
className="input"
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Your name"
required
autoComplete="name"
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="reg-email">Email</label>
<input
id="reg-email"
className="input"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
autoComplete="email"
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="reg-password">Password</label>
<input
id="reg-password"
className="input"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Min. 8 characters"
required
minLength={8}
autoComplete="new-password"
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="reg-confirm">Confirm Password</label>
<input
id="reg-confirm"
className="input"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="••••••••"
required
minLength={8}
autoComplete="new-password"
/>
</div>
<button className="btn btn-primary auth-submit" type="submit" disabled={submitting}>
{submitting ? 'Creating account…' : 'Create Account'}
</button>
</form>
)}
</div>
</div>
)
}