diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6b9421f..75e3a4b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,8 @@ import { Avatar } from './pages/ProfileSelect' import SetupScreen from './pages/SetupScreen' import { ResetPassword } from './pages/PasswordRecovery' import LoginScreen from './pages/LoginScreen' +import Register from './pages/Register' +import Verify from './pages/Verify' import Dashboard from './pages/Dashboard' import CaptureHub from './pages/CaptureHub' import WeightScreen from './pages/WeightScreen' @@ -59,9 +61,26 @@ function AppShell() { } }, [session?.profile_id]) - // Handle password reset link + // Handle public pages (register, verify, reset-password) const urlParams = new URLSearchParams(window.location.search) - const resetToken = urlParams.get('reset-password') || (window.location.pathname === '/reset-password' ? urlParams.get('token') : null) + const currentPath = window.location.pathname + + // Register page + if (currentPath === '/register') return ( +
+ +
+ ) + + // Verify email page + if (currentPath === '/verify') return ( +
+ +
+ ) + + // Password reset page + const resetToken = urlParams.get('reset-password') || (currentPath === '/reset-password' ? urlParams.get('token') : null) if (resetToken) return (
diff --git a/frontend/src/pages/LoginScreen.jsx b/frontend/src/pages/LoginScreen.jsx index 2e3779a..446968a 100644 --- a/frontend/src/pages/LoginScreen.jsx +++ b/frontend/src/pages/LoginScreen.jsx @@ -105,6 +105,22 @@ export default function LoginScreen() { textAlign:'center',padding:'4px 0',textDecoration:'underline'}}> Passwort vergessen? + +
+ + Noch kein Account?{' '} + + + Jetzt registrieren + +
diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx new file mode 100644 index 0000000..14c49c8 --- /dev/null +++ b/frontend/src/pages/Register.jsx @@ -0,0 +1,182 @@ +import { useState } from 'react' +import { Link } from 'react-router-dom' +import { api } from '../utils/api' + +export default function Register() { + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [passwordConfirm, setPasswordConfirm] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [success, setSuccess] = useState(false) + + const handleSubmit = async (e) => { + e.preventDefault() + setError(null) + + // Validation + if (!name || name.length < 2) { + setError('Name muss mindestens 2 Zeichen lang sein') + return + } + if (!email || !email.includes('@')) { + setError('Ungültige E-Mail-Adresse') + return + } + if (password.length < 8) { + setError('Passwort muss mindestens 8 Zeichen lang sein') + return + } + if (password !== passwordConfirm) { + setError('Passwörter stimmen nicht überein') + return + } + + setLoading(true) + try { + await api.register(name, email, password) + setSuccess(true) + } catch (err) { + setError(err.message || 'Registrierung fehlgeschlagen') + } finally { + setLoading(false) + } + } + + if (success) { + return ( +
+
+
+

+ Registrierung erfolgreich! +

+

+ Wir haben dir eine E-Mail mit einem Bestätigungslink gesendet. + Bitte prüfe dein Postfach und bestätige deine E-Mail-Adresse. +

+
+ + + Zum Login + + +

+ Keine E-Mail erhalten? Prüfe auch deinen Spam-Ordner. +

+
+ ) + } + + return ( +
+

Registrierung

+

+ Erstelle deinen Mitai Jinkendo Account +

+ +
+ {error && ( +
+ {error} +
+ )} + +
+ + setName(e.target.value)} + placeholder="Dein Name" + disabled={loading} + required + /> +
+ +
+ + setEmail(e.target.value)} + placeholder="deine@email.de" + disabled={loading} + required + /> +
+ +
+ + setPassword(e.target.value)} + placeholder="Mindestens 8 Zeichen" + disabled={loading} + required + /> +
+ +
+ + setPasswordConfirm(e.target.value)} + placeholder="Passwort wiederholen" + disabled={loading} + required + /> +
+ + + +
+ + Bereits registriert?{' '} + + + Zum Login + +
+
+ +

+ Mit der Registrierung akzeptierst du unsere Nutzungsbedingungen + und Datenschutzerklärung. +

+
+ ) +} diff --git a/frontend/src/pages/Verify.jsx b/frontend/src/pages/Verify.jsx new file mode 100644 index 0000000..32fc2ca --- /dev/null +++ b/frontend/src/pages/Verify.jsx @@ -0,0 +1,115 @@ +import { useState, useEffect, useContext } from 'react' +import { useSearchParams, useNavigate } from 'react-router-dom' +import { AuthContext } from '../context/AuthContext' +import { api } from '../utils/api' + +export default function Verify() { + const [searchParams] = useSearchParams() + const token = searchParams.get('token') + const navigate = useNavigate() + const { login } = useContext(AuthContext) + + const [status, setStatus] = useState('loading') // loading | success | error + const [error, setError] = useState(null) + + useEffect(() => { + const verify = async () => { + if (!token) { + setStatus('error') + setError('Kein Verifikations-Token gefunden') + return + } + + try { + const result = await api.verifyEmail(token) + + // Auto-login with returned token + if (result.token) { + login(result.token, result.profile) + setStatus('success') + + // Redirect to dashboard after 2 seconds + setTimeout(() => { + navigate('/dashboard') + }, 2000) + } else { + setStatus('error') + setError('Verifizierung erfolgreich, aber Login fehlgeschlagen') + } + } catch (err) { + setStatus('error') + setError(err.message || 'Verifizierung fehlgeschlagen') + } + } + + verify() + }, [token, login, navigate]) + + if (status === 'loading') { + return ( +
+
+

E-Mail wird bestätigt...

+

Einen Moment bitte

+
+ ) + } + + if (status === 'error') { + return ( +
+
+
+

+ Verifizierung fehlgeschlagen +

+

+ {error} +

+
+ + +
+ ) + } + + // Success + return ( +
+
+
+

+ E-Mail bestätigt! +

+

+ Dein Account wurde erfolgreich aktiviert. + Du wirst gleich zum Dashboard weitergeleitet... +

+
+ +
+
+ ) +} diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index bc4701e..0204445 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -142,6 +142,8 @@ export const api = { adminDeleteProfile: (id) => req(`/admin/profiles/${id}`,{method:'DELETE'}), adminSetPermissions: (id,d) => req(`/admin/profiles/${id}/permissions`,jput(d)), changePin: (pin) => req('/auth/pin',json({pin})), + register: (name,email,password) => req('/auth/register',json({name,email,password})), + verifyEmail: (token) => req(`/auth/verify/${token}`), // v9c Subscription System // User-facing