diff --git a/packages/frontend/src/pages/auth/AcceptInvite.tsx b/packages/frontend/src/pages/auth/AcceptInvite.tsx new file mode 100644 index 0000000..c1e01e6 --- /dev/null +++ b/packages/frontend/src/pages/auth/AcceptInvite.tsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { authApi } from '../../api/auth.js'; +import { ApiClientError } from '../../api/client.js'; +import { useAuth } from '../../stores/authStore.js'; +import { AuthLayout, Field } from './Login.js'; + +export function AcceptInvitePage() { + const [params] = useSearchParams(); + const token = params.get('token') ?? ''; + const navigate = useNavigate(); + const refreshMe = useAuth((s) => s.refreshMe); + const [displayName, setDisplayName] = useState(''); + const [password, setPassword] = useState(''); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); setError(null); + try { + await authApi.acceptInvite({ token, displayName, password }); + await refreshMe(); + navigate('/', { replace: true }); + } catch (err) { + setError(err instanceof ApiClientError ? err.message : 'Accepteren mislukt.'); + } finally { setBusy(false); } + } + + return ( + + {!token &&

Geen token in URL.

} +
+ + setDisplayName(e.target.value)} autoComplete="name" /> + + + setPassword(e.target.value)} autoComplete="new-password" /> + + {error &&

{error}

} + +
+
+ ); +} diff --git a/packages/frontend/src/pages/auth/ForgotPassword.tsx b/packages/frontend/src/pages/auth/ForgotPassword.tsx new file mode 100644 index 0000000..53ec764 --- /dev/null +++ b/packages/frontend/src/pages/auth/ForgotPassword.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { authApi } from '../../api/auth.js'; +import { AuthLayout, Field } from './Login.js'; + +export function ForgotPasswordPage() { + const [email, setEmail] = useState(''); + const [busy, setBusy] = useState(false); + const [sent, setSent] = useState(false); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); + try { await authApi.forgotPassword({ email }); } + finally { setBusy(false); setSent(true); } + } + + if (sent) { + return ( + +

Als {email} bekend is, hebben we een reset-link gestuurd. De link is 1 uur geldig.

+ Terug naar inloggen +
+ ); + } + + return ( + +
+ + setEmail(e.target.value)} autoComplete="email" /> + + +
+
+ Terug naar inloggen +
+
+ ); +} diff --git a/packages/frontend/src/pages/auth/ResetPassword.tsx b/packages/frontend/src/pages/auth/ResetPassword.tsx new file mode 100644 index 0000000..6a71f0d --- /dev/null +++ b/packages/frontend/src/pages/auth/ResetPassword.tsx @@ -0,0 +1,46 @@ +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 { AuthLayout, Field } from './Login.js'; + +export function ResetPasswordPage() { + const [params] = useSearchParams(); + const token = params.get('token') ?? ''; + const navigate = useNavigate(); + 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.resetPassword({ token, password }); setDone(true); } + catch (err) { setError(err instanceof ApiClientError ? err.message : 'Reset mislukt.'); } + finally { setBusy(false); } + } + + if (done) { + return ( + +

Je kunt nu inloggen met je nieuwe wachtwoord.

+ +
+ ); + } + + return ( + + {!token &&

Geen token in URL.

} +
+ + setPassword(e.target.value)} autoComplete="new-password" /> + + {error &&

{error}

} + +
+ Terug naar inloggen +
+ ); +} diff --git a/packages/frontend/src/pages/auth/VerifyEmail.tsx b/packages/frontend/src/pages/auth/VerifyEmail.tsx new file mode 100644 index 0000000..86ff4ce --- /dev/null +++ b/packages/frontend/src/pages/auth/VerifyEmail.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; +import { authApi } from '../../api/auth.js'; +import { ApiClientError } from '../../api/client.js'; +import { AuthLayout } from './Login.js'; + +export function VerifyEmailPage() { + const [params] = useSearchParams(); + const token = params.get('token'); + const [state, setState] = useState<'pending' | 'ok' | 'err'>('pending'); + const [message, setMessage] = useState(''); + + useEffect(() => { + if (!token) { setState('err'); setMessage('Token ontbreekt.'); return; } + (async () => { + try { + await authApi.verifyEmail({ token }); + setState('ok'); + } catch (e) { + setState('err'); + setMessage(e instanceof ApiClientError ? e.message : 'Verificatie mislukt.'); + } + })(); + }, [token]); + + return ( + + {state === 'pending' &&

Bezig met verifiëren…

} + {state === 'ok' && ( + <> +

Je e-mailadres is bevestigd âś…

+ Naar inloggen + + )} + {state === 'err' && ( + <> +

{message}

+ Terug naar inloggen + + )} +
+ ); +}