From fc95c4a5775cf00bc8ade9332b8963d2e7fddcfb Mon Sep 17 00:00:00 2001 From: Rajpreet0 Date: Sun, 30 Nov 2025 18:20:57 +0100 Subject: [PATCH] added reset-password feature --- src/app/(auth)/forgot-password/page.tsx | 7 + src/app/(auth)/reset-password/page.tsx | 5 + src/middleware.ts | 10 +- .../auth/ui/views/forgot-password-view.tsx | 110 ++++++++++++++ src/modules/auth/ui/views/login-view.tsx | 15 +- .../auth/ui/views/reset-password-view.tsx | 141 ++++++++++++++++++ 6 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 src/app/(auth)/forgot-password/page.tsx create mode 100644 src/app/(auth)/reset-password/page.tsx create mode 100644 src/modules/auth/ui/views/forgot-password-view.tsx create mode 100644 src/modules/auth/ui/views/reset-password-view.tsx diff --git a/src/app/(auth)/forgot-password/page.tsx b/src/app/(auth)/forgot-password/page.tsx new file mode 100644 index 0000000..350dbf6 --- /dev/null +++ b/src/app/(auth)/forgot-password/page.tsx @@ -0,0 +1,7 @@ +import { requireGuest } from "@/src/lib/auth/getUser"; +import ForgotPasswordView from "@/src/modules/auth/ui/views/forgot-password-view"; + +export default async function ForgotPasswordPage() { + await requireGuest(); + return ; +} diff --git a/src/app/(auth)/reset-password/page.tsx b/src/app/(auth)/reset-password/page.tsx new file mode 100644 index 0000000..7ef5733 --- /dev/null +++ b/src/app/(auth)/reset-password/page.tsx @@ -0,0 +1,5 @@ +import ResetPasswordView from "@/src/modules/auth/ui/views/reset-password-view"; + +export default async function ResetPasswordPage() { + return ; +} diff --git a/src/middleware.ts b/src/middleware.ts index eba9544..155f9a0 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -5,15 +5,16 @@ import { NextResponse, type NextRequest } from "next/server"; export async function middleware(request: NextRequest) { // Protected routes const protectedRoutes = ["/dashboard", "/profile", "/trainings", "/courses"]; - const authRoutes = ["/login", "/register"]; + const authRoutes = ["/login", "/register", "/forgot-password"]; const isProtectedRoute = protectedRoutes.some((route) => request.nextUrl.pathname.startsWith(route) ); const isAuthRoute = authRoutes.some((route) => request.nextUrl.pathname.startsWith(route)); + const isPasswordReset = request.nextUrl.pathname.startsWith("/reset-password"); - // Skip auth check if not a protected or auth route - if (!isProtectedRoute && !isAuthRoute) { + // Skip auth check if not a protected, auth, or reset route + if (!isProtectedRoute && !isAuthRoute && !isPasswordReset) { return NextResponse.next({ request }); } @@ -48,7 +49,8 @@ export async function middleware(request: NextRequest) { return NextResponse.redirect(url); } - if (user && isAuthRoute) { + // Redirect logged-in users away from auth routes (except password reset) + if (user && isAuthRoute && !isPasswordReset) { const url = request.nextUrl.clone(); url.pathname = "/dashboard"; return NextResponse.redirect(url); diff --git a/src/modules/auth/ui/views/forgot-password-view.tsx b/src/modules/auth/ui/views/forgot-password-view.tsx new file mode 100644 index 0000000..1cb7590 --- /dev/null +++ b/src/modules/auth/ui/views/forgot-password-view.tsx @@ -0,0 +1,110 @@ +"use client"; +import InputComponent from "@/src/components/layout/InputComponent"; +import AuthSidebarComponent from "../components/AuthSidebarComponent"; +import { Button } from "@/src/components/ui/button"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Spinner } from "@/src/components/ui/spinner"; +import { createClient } from "@/src/lib/supabase/client"; + +const ForgotPasswordView = () => { + const [email, setEmail] = useState(""); + const [loading, setLoading] = useState(false); + const [emailError, setEmailError] = useState(""); + const [emailSent, setEmailSent] = useState(false); + const router = useRouter(); + const supabase = createClient(); + + const handleReset = async () => { + setEmailError(""); + setLoading(true); + + if (!email) { + setEmailError("Bitte E-Mail-Adresse eingeben"); + setLoading(false); + return; + } + + const { error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: `${window.location.origin}/reset-password`, + }); + + if (error) { + setEmailError("E-Mail konnte nicht gesendet werden"); + } else { + setEmailSent(true); + } + + setLoading(false); + }; + + return ( +
+ {/* Sidebar */} +
+
+ +
+
+ + {/* Forgot Password Formular */} +
+
+

Passwort vergessen?

+ + {!emailSent ? ( + <> +

+ Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen einen Link zum Zurücksetzen + Ihres Passworts. +

+ + setEmail(e.target.value)} + error={emailError} + /> + + + + ) : ( +
+

E-Mail wurde versendet!

+

+ Wir haben Ihnen einen Link zum Zurücksetzen Ihres Passworts an{" "} + {email} gesendet. Bitte überprüfen Sie Ihr Postfach und folgen Sie + den Anweisungen in der E-Mail. +

+

+ Hinweis: Der Link ist 60 Minuten gültig. Falls Sie keine E-Mail erhalten haben, + überprüfen Sie bitte Ihren Spam-Ordner. +

+
+ )} + +
+

Zurück zum

+

router.push("/login")} + > + Login +

+
+
+
+
+ ); +}; + +export default ForgotPasswordView; diff --git a/src/modules/auth/ui/views/login-view.tsx b/src/modules/auth/ui/views/login-view.tsx index d32858f..bac4da2 100644 --- a/src/modules/auth/ui/views/login-view.tsx +++ b/src/modules/auth/ui/views/login-view.tsx @@ -62,10 +62,6 @@ const LoginView = () => { error={errors.password} /> - -
{ Beim nächsten Mal an mich erinnern
-

Passwort vergessen?

+

router.push("/forgot-password")} + > + Passwort vergessen? +

+ +
diff --git a/src/modules/auth/ui/views/reset-password-view.tsx b/src/modules/auth/ui/views/reset-password-view.tsx new file mode 100644 index 0000000..69b30b9 --- /dev/null +++ b/src/modules/auth/ui/views/reset-password-view.tsx @@ -0,0 +1,141 @@ +"use client"; +import InputComponent from "@/src/components/layout/InputComponent"; +import AuthSidebarComponent from "../components/AuthSidebarComponent"; +import { Button } from "@/src/components/ui/button"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Spinner } from "@/src/components/ui/spinner"; +import { createClient } from "@/src/lib/supabase/client"; + +const ResetPasswordView = () => { + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [passwordError, setPasswordError] = useState(""); + const [confirmPasswordError, setConfirmPasswordError] = useState(""); + const [resetSuccess, setResetSuccess] = useState(false); + const router = useRouter(); + const supabase = createClient(); + + useEffect(() => { + supabase.auth.getUser().then(({ data }) => { + if (!data.user) { + router.replace("/login"); + } + }); + }, [router, supabase]); + + const handleResetPassword = async () => { + setLoading(true); + setPasswordError(""); + setConfirmPasswordError(""); + + if (password.length < 8) { + setPasswordError("Mindestens 8 Zeichen"); + setLoading(false); + return; + } + + if (password !== confirmPassword) { + setConfirmPasswordError("Passwörter stimmen nicht überein"); + setLoading(false); + return; + } + + const { error } = await supabase.auth.updateUser({ password }); + + if (error) { + setPasswordError("Passwort konnte nicht gesetzt werden"); + setLoading(false); + return; + } + + setResetSuccess(true); + + try { + await supabase.auth.signOut(); + } catch (signOutError) { + console.error("Sign out error:", signOutError); + } + + setTimeout(() => { + router.push("/login"); + }, 2500); + }; + + return ( +
+ {/* Sidebar */} +
+
+ +
+
+ + {/* Reset Password Formular */} +
+
+

Passwort zurücksetzen

+ + {!resetSuccess ? ( + <> +

+ Bitte geben Sie Ihr neues Passwort ein. Es sollte mindestens 8 Zeichen lang sein. +

+ + setPassword(e.target.value)} + error={passwordError} + /> + + setConfirmPassword(e.target.value)} + error={confirmPasswordError} + /> + + + + ) : ( +
+

+ Passwort erfolgreich zurückgesetzt! +

+

+ Ihr Passwort wurde erfolgreich geändert. Sie werden in Kürze zum Login + weitergeleitet. +

+
+ )} + +
+

Zurück zum

+

router.push("/login")} + > + Login +

+
+
+
+
+ ); +}; + +export default ResetPasswordView;