From 57e0f4ecac6f80b6b95d8a58a707bd372c3b2228 Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Sat, 4 Oct 2025 00:05:33 +0200 Subject: [PATCH] feature: add password reset page --- src/app/password-reset/page.tsx | 314 ++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 src/app/password-reset/page.tsx diff --git a/src/app/password-reset/page.tsx b/src/app/password-reset/page.tsx new file mode 100644 index 0000000..f1acaae --- /dev/null +++ b/src/app/password-reset/page.tsx @@ -0,0 +1,314 @@ +'use client' + +import { useState, useEffect } from 'react' +import { useSearchParams, useRouter } from 'next/navigation' +import PageLayout from '../components/PageLayout' + +export default function PasswordResetPage() { + const searchParams = useSearchParams() + const router = useRouter() + const token = searchParams.get('token') + + // Email request state + const [email, setEmail] = useState('') + const [requestLoading, setRequestLoading] = useState(false) + const [requestSuccess, setRequestSuccess] = useState(false) + const [requestError, setRequestError] = useState('') + + // Reset-with-token state + const [password, setPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + const [showPassword, setShowPassword] = useState(false) + const [resetLoading, setResetLoading] = useState(false) + const [resetSuccess, setResetSuccess] = useState(false) + const [resetError, setResetError] = useState('') + + // Basic validators + const validEmail = (val: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val) + const validPassword = (val: string) => + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/.test(val) + + // Auto-redirect after successful password reset + useEffect(() => { + if (resetSuccess) { + const t = setTimeout(() => router.push('/login'), 1800) + return () => clearTimeout(t) + } + }, [resetSuccess, router]) + + const handleRequestSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (requestLoading) return + if (!validEmail(email)) { + setRequestError('Bitte eine gültige E-Mail eingeben.') + return + } + setRequestError('') + setRequestLoading(true) + try { + // TODO: call API endpoint: POST /auth/password-reset/request + await new Promise(r => setTimeout(r, 1100)) + setRequestSuccess(true) + } catch { + setRequestError('Anfrage fehlgeschlagen. Bitte erneut versuchen.') + } finally { + setRequestLoading(false) + } + } + + const handleResetSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (resetLoading) return + if (!validPassword(password)) { + setResetError('Passwort erfüllt nicht die Anforderungen.') + return + } + if (password !== confirmPassword) { + setResetError('Passwörter stimmen nicht überein.') + return + } + setResetError('') + setResetLoading(true) + try { + // TODO: call API endpoint: POST /auth/password-reset/confirm { token, password } + await new Promise(r => setTimeout(r, 1200)) + setResetSuccess(true) + } catch { + setResetError('Zurücksetzen fehlgeschlagen. Bitte erneut versuchen.') + } finally { + setResetLoading(false) + } + } + + const passwordHints = [ + { label: 'Mindestens 8 Zeichen', pass: password.length >= 8 }, + { label: 'Großbuchstabe (A-Z)', pass: /[A-Z]/.test(password) }, + { label: 'Kleinbuchstabe (a-z)', pass: /[a-z]/.test(password) }, + { label: 'Ziffer (0-9)', pass: /\d/.test(password) }, + { label: 'Sonderzeichen (!@#$...)', pass: /[\W_]/.test(password) } + ] + + return ( + +
+ {/* Background Pattern */} + + {/* Colored Blur Effect */} +
+
+ ) +} \ No newline at end of file