From 535004bced835e912e529caa71e74db17ba0fb87 Mon Sep 17 00:00:00 2001 From: augmentedpotato Date: Wed, 15 Apr 2026 00:17:26 -0600 Subject: [PATCH] password reset --- web/app/forgot-password/page.js | 92 ++++++++++++++++++++++ web/app/login/page.js | 5 ++ web/app/register/page.js | 5 ++ web/app/reset-password/page.js | 132 ++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 web/app/forgot-password/page.js create mode 100644 web/app/reset-password/page.js diff --git a/web/app/forgot-password/page.js b/web/app/forgot-password/page.js new file mode 100644 index 00000000..0b7adbea --- /dev/null +++ b/web/app/forgot-password/page.js @@ -0,0 +1,92 @@ +"use client"; + +import dynamic from "next/dynamic"; +import Link from "next/link"; +import { useState } from "react"; + +function ForgotPasswordPage() { + const [usernameOrEmail, setUsernameOrEmail] = useState(""); + const [message, setMessage] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const [submitted, setSubmitted] = useState(false); + + async function handleSubmit(e) { + e.preventDefault(); + setError(""); + setMessage(""); + setLoading(true); + + try { + const res = await fetch("/api/v1/auth/forgot-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ usernameOrEmail }), + }); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.message || "Something went wrong. Please try again."); + } + + setMessage(data.message || "If an account matches, a reset link has been sent to your email."); + setSubmitted(true); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + + return ( +
+
+

Forgot Password

+ + {!submitted ? ( + <> +

+ Enter your username or email address and we'll send you a link to reset your password. +

+ + {error &&

{error}

} + +
+ + + +
+ + ) : ( +

{message}

+ )} + +

+ Remember your password?{" "} + Log in here +

+

+ Don't have an account?{" "} + Register here +

+
+
+ ); +} + +export default dynamic(() => Promise.resolve(ForgotPasswordPage), { + ssr: false, +}); diff --git a/web/app/login/page.js b/web/app/login/page.js index 2e7d1fe1..c63d89a6 100644 --- a/web/app/login/page.js +++ b/web/app/login/page.js @@ -82,6 +82,11 @@ function LoginPage() { Don't have an account?{" "} Register here

+ +

+ Forgot your password?{" "} + Reset it here +

); diff --git a/web/app/register/page.js b/web/app/register/page.js index 3b3ba59b..674e4b9e 100644 --- a/web/app/register/page.js +++ b/web/app/register/page.js @@ -174,6 +174,11 @@ function RegisterPage() { Already have an account?{" "} Log in here

+ +

+ Forgot your password?{" "} + Reset it here +

); diff --git a/web/app/reset-password/page.js b/web/app/reset-password/page.js new file mode 100644 index 00000000..2b63b655 --- /dev/null +++ b/web/app/reset-password/page.js @@ -0,0 +1,132 @@ +"use client"; + +import dynamic from "next/dynamic"; +import Link from "next/link"; +import { useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; + +function ResetPasswordPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const token = searchParams.get("token") || ""; + + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + + if (!token) { + return ( +
+
+

Invalid Link

+

+ This password reset link is missing or invalid. Please request a new one. +

+

+ Request a new reset link +

+
+
+ ); + } + + async function handleSubmit(e) { + e.preventDefault(); + setError(""); + + if (newPassword !== confirmPassword) { + setError("Passwords do not match."); + return; + } + + setLoading(true); + try { + const res = await fetch("/api/v1/auth/reset-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token, newPassword }), + }); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.message || "Failed to reset password. The link may have expired."); + } + + setSuccess(true); + setTimeout(() => router.push("/login"), 3000); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + + if (success) { + return ( +
+
+

Password Reset

+

+ Your password has been reset successfully. Redirecting you to login… +

+

+ Go to login +

+
+
+ ); + } + + return ( +
+
+

Reset Password

+ + {error &&

{error}

} + +
+ + + + + +
+ +

+ Remember your password?{" "} + Log in here +

+
+
+ ); +} + +export default dynamic(() => Promise.resolve(ResetPasswordPage), { + ssr: false, +});