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}
+ />
+
+
+ {loading ? : "Link zum Zurücksetzen senden"}
+
+ >
+ ) : (
+
+
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}
/>
-
- {loading ? : "Anmelden"}
-
-
{
Beim nächsten Mal an mich erinnern
-
Passwort vergessen?
+
router.push("/forgot-password")}
+ >
+ Passwort vergessen?
+
+
+ {loading ? : "Anmelden"}
+
+
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}
+ />
+
+
+ {loading ? : "Passwort zurücksetzen"}
+
+ >
+ ) : (
+
+
+ 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;