Files
brainwave/frontend/src/components/AuthModal.tsx
T
LaurensBartelds 6277826472 Add internationalization support with English and Dutch translations
- Integrated i18next for language detection and translation support.
- Added English and Dutch JSON translation files for various app sections.
- Updated TweaksPanel and Workspace components to use translated strings.
- Imported i18n configuration in main entry file.
2026-04-26 21:19:47 +02:00

204 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
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 { t } = useTranslation()
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 : t('auth.loginFailed'))
} finally {
setSubmitting(false)
}
}
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
if (password !== confirmPassword) {
setError(t('auth.passwordsDoNotMatch'))
return
}
setSubmitting(true)
try {
const message = await register(email, password, displayName)
setSuccess(message)
} catch (err) {
setError(err instanceof ApiError ? err.message : t('auth.registrationFailed'))
} 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={t('common.close')}>×</button>
<div className="auth-header">
<div className="brand-mark" />
<h2 className="auth-title">
{tab === 'login' ? t('auth.welcomeBack') : t('auth.createAccount')}
</h2>
<p className="auth-subtitle">
{tab === 'login'
? t('auth.signInSubtitle')
: t('auth.registerSubtitle')}
</p>
</div>
<div className="auth-tabs">
<button
className={`auth-tab ${tab === 'login' ? 'active' : ''}`}
onClick={() => switchTab('login')}
>
{t('auth.signIn')}
</button>
<button
className={`auth-tab ${tab === 'register' ? 'active' : ''}`}
onClick={() => switchTab('register')}
>
{t('auth.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">{t('auth.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">{t('auth.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 ? t('auth.signingIn') : t('auth.signIn')}
</button>
<button
type="button"
className="auth-forgot"
onClick={() => { onClose(); onForgotPassword() }}
>
{t('auth.forgotPassword')}
</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">{t('auth.displayName')}</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">{t('auth.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">{t('auth.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">{t('auth.confirmPassword')}</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 ? t('auth.creatingAccount') : t('auth.register')}
</button>
</form>
)}
</div>
</div>
)
}