From 4e15d8b59d13de4cd797fb1f0bac3a2ba6ce2671 Mon Sep 17 00:00:00 2001 From: Bert Hausmans Date: Wed, 20 May 2026 23:07:19 +0200 Subject: [PATCH] feat(frontend): Login + Register pages --- packages/frontend/src/pages/auth/Login.tsx | 81 +++++++++++++++++++ packages/frontend/src/pages/auth/Register.tsx | 54 +++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 packages/frontend/src/pages/auth/Login.tsx create mode 100644 packages/frontend/src/pages/auth/Register.tsx diff --git a/packages/frontend/src/pages/auth/Login.tsx b/packages/frontend/src/pages/auth/Login.tsx new file mode 100644 index 0000000..8f57c19 --- /dev/null +++ b/packages/frontend/src/pages/auth/Login.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; +import { authApi } from '../../api/auth.js'; +import { ApiClientError } from '../../api/client.js'; +import { useAuth } from '../../stores/authStore.js'; + +export function LoginPage() { + const [searchParams] = useSearchParams(); + const next = searchParams.get('next') ?? '/'; + const navigate = useNavigate(); + const login = useAuth((s) => s.login); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [needsVerification, setNeedsVerification] = useState(false); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); setError(null); setNeedsVerification(false); + try { + await login(email, password); + navigate(next, { replace: true }); + } catch (err) { + if (err instanceof ApiClientError) { + if (err.code === 'EMAIL_NOT_VERIFIED') setNeedsVerification(true); + else setError(err.message); + } else setError('Onbekende fout'); + } finally { setBusy(false); } + } + + async function resend() { + try { await authApi.resendVerification({ email }); setError('Bevestigingsmail opnieuw verstuurd.'); } + catch { setError('Kon mail niet versturen.'); } + } + + return ( + +
+ + setEmail(e.target.value)} autoComplete="email" /> + + + setPassword(e.target.value)} autoComplete="current-password" /> + + {error &&

{error}

} + {needsVerification && ( +
+ Je e-mailadres is nog niet bevestigd. + +
+ )} + +
+
+ Wachtwoord vergeten? + Account aanmaken +
+
+ ); +} + +export function AuthLayout({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+
+

{title}

+ {children} +
+
+ ); +} + +export function Field({ label, children }: { label: string; children: React.ReactNode }) { + return ( + + ); +} diff --git a/packages/frontend/src/pages/auth/Register.tsx b/packages/frontend/src/pages/auth/Register.tsx new file mode 100644 index 0000000..f9be9b7 --- /dev/null +++ b/packages/frontend/src/pages/auth/Register.tsx @@ -0,0 +1,54 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { authApi } from '../../api/auth.js'; +import { ApiClientError } from '../../api/client.js'; +import { AuthLayout, Field } from './Login.js'; + +export function RegisterPage() { + const [email, setEmail] = useState(''); + const [displayName, setDisplayName] = useState(''); + const [password, setPassword] = useState(''); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [done, setDone] = useState(false); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); setError(null); + try { + await authApi.register({ email, displayName, password }); + setDone(true); + } catch (err) { + if (err instanceof ApiClientError) setError(err.message); + else setError('Onbekende fout'); + } finally { setBusy(false); } + } + + if (done) { + return ( + +

+ We hebben een bevestigingsmail gestuurd naar {email}. + Klik op de link om je account te activeren. +

+ Terug naar inloggen +
+ ); + } + + return ( + +
+ setDisplayName(e.target.value)} autoComplete="name" /> + setEmail(e.target.value)} autoComplete="email" /> + setPassword(e.target.value)} autoComplete="new-password" /> + {error &&

{error}

} + +

De eerste registratie wordt automatisch beheerder.

+
+
+ Heb je al een account? Inloggen +
+
+ ); +}