From 67007f8965194144eef45b6993ff33eab5f438af Mon Sep 17 00:00:00 2001 From: Divyanshi Awasthi Date: Sun, 17 May 2026 21:57:11 +0530 Subject: [PATCH 1/2] fix(auth): add forgot password page integratethe changes from backend and frontend for forgot password page --- .../src/pages/public/ForgotPassword.jsx | 550 +++++++++++++++++- 1 file changed, 536 insertions(+), 14 deletions(-) diff --git a/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx b/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx index dae57ed..7d962f5 100644 --- a/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx +++ b/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx @@ -1,20 +1,542 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; +import { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { useFormik } from "formik"; +import * as Yup from "yup"; +import toast from "react-hot-toast"; +import api from "@services/common/api"; +import { useTheme } from "@context/ThemeContext"; + +const forgotPasswordSchema = Yup.object({ + email: Yup.string() + .transform((value) => value?.trim()) + .email("Enter a valid email address") + .required("Email is required"), +}); + +const resetPasswordSchema = Yup.object({ + token: Yup.string() + .transform((value) => value?.trim()) + .min(5, "Reset code must be at least 5 characters") + .max(64, "Reset code is too long") + .required("Reset code is required"), + newPassword: Yup.string() + .min(6, "Password must be at least 6 characters") + .required("New password is required"), + confirmPassword: Yup.string() + .oneOf([Yup.ref("newPassword"), null], "Passwords must match") + .required("Confirm password is required"), +}); + +const getApiMessage = (payload, fallback) => { + if (typeof payload === "string") { + return payload; + } + + return payload?.message || payload?.data?.message || fallback; +}; + +const EmailIcon = () => ( + + + +); + +const LockIcon = () => ( + + + +); + +const SpinnerIcon = () => ( + + + + +); + +const Illustration = () => ( + + + + + + + + + + + + +); const ForgotPassword = () => { + const navigate = useNavigate(); + const { isDark, toggle } = useTheme(); + const [step, setStep] = useState("request"); + const [requestedEmail, setRequestedEmail] = useState(""); + const [submitError, setSubmitError] = useState(""); + const [successMessage, setSuccessMessage] = useState(""); + const [isRequestLoading, setIsRequestLoading] = useState(false); + const [isResetLoading, setIsResetLoading] = useState(false); + + const forgotPasswordFormik = useFormik({ + initialValues: { email: "" }, + validationSchema: forgotPasswordSchema, + onSubmit: async ({ email }) => { + setIsRequestLoading(true); + setSubmitError(""); + setSuccessMessage(""); + const normalizedEmail = email.trim(); + + try { + const res = await api.post("/public/api/v1/auth/forgot-password", null, { + params: { email: normalizedEmail }, + }); + + if (res.data?.success === false) { + throw new Error( + getApiMessage(res.data, "Unable to send reset instructions.") + ); + } + + const message = getApiMessage( + res.data, + "Reset code sent successfully. Please check your email." + ); + + setRequestedEmail(normalizedEmail); + setSuccessMessage(message); + setStep("reset"); + toast.success(message); + } catch (err) { + const message = + getApiMessage(err.response?.data) || + err.message || + "Unable to send reset instructions. Please try again."; + + setSubmitError(message); + toast.error(message); + } finally { + setIsRequestLoading(false); + } + }, + }); + + const resetPasswordFormik = useFormik({ + initialValues: { + token: "", + newPassword: "", + confirmPassword: "", + }, + validationSchema: resetPasswordSchema, + onSubmit: async ({ token, newPassword }) => { + setIsResetLoading(true); + setSubmitError(""); + + try { + const resetPasswordData = new URLSearchParams({ + email: requestedEmail, + token: token.trim(), + newPassword, + }); + + const res = await api.post( + "/public/api/v1/auth/reset-password", + resetPasswordData, + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + } + ); + + if (res.data?.success === false) { + throw new Error(getApiMessage(res.data, "Password reset failed.")); + } + + const message = getApiMessage( + res.data, + "Password reset successful. Please sign in with your new password." + ); + + setSuccessMessage(message); + setStep("complete"); + toast.success(message); + } catch (err) { + const message = + getApiMessage(err.response?.data) || + err.message || + "Password reset failed."; + + setSubmitError(message); + toast.error(message); + } finally { + setIsResetLoading(false); + } + }, + }); + + const inputClass = (formik, field) => + `w-full rounded-lg border ${ + formik.touched[field] && formik.errors[field] + ? "border-red-500 focus:ring-red-500" + : "border-gray-300 focus:ring-blue-500 dark:border-gray-600" + } bg-transparent py-4 pl-6 pr-12 text-gray-800 placeholder-gray-400 outline-none transition focus:border-transparent focus:ring-2 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500`; + + const renderFieldError = (formik, field) => + formik.touched[field] && + formik.errors[field] && ( +

{formik.errors[field]}

+ ); + + const resetToEmailStep = () => { + setStep("request"); + setRequestedEmail(""); + setSuccessMessage(""); + setSubmitError(""); + resetPasswordFormik.resetForm(); + }; + return ( -
-
-

Forgot Password?

-

- The password recovery feature is currently under development. Please contact support or try again later! -

- - Back to Login - +
+
+
+
+
+ + + Restroly + + + +

+ Reset your password and get back to managing your restaurant. +

+ + +
+
+ +
+
+
+ +
+ +
+ + Restroly + +
+ +

+ Forgot your password? +

+

+ {step === "complete" ? "Password Updated" : "Reset Password"} +

+

+ {step === "request" + ? "Enter your registered email address and we will send a reset code." + : step === "complete" + ? "Your password has been updated successfully. You can now sign in with your new password." + : "Use the reset code sent to your email and choose a new password."} +

+ + {submitError && ( +
+ {submitError} +
+ )} + + {successMessage && step !== "complete" && ( +
+ {successMessage} +
+ )} + + {step === "request" && ( +
+
+ +
+ + + + +
+ {renderFieldError(forgotPasswordFormik, "email")} +
+ + +
+ )} + + {step === "reset" && ( +
+
+ +
+ + + + +
+ {renderFieldError(resetPasswordFormik, "token")} +
+ +
+ +
+ + + + +
+ {renderFieldError(resetPasswordFormik, "newPassword")} +
+ +
+ +
+ + + + +
+ {renderFieldError(resetPasswordFormik, "confirmPassword")} +
+ + + + +
+ )} + + {step === "complete" && ( +
+

Password reset successful

+

{successMessage}

+ +
+ )} + + {step !== "complete" && ( +

+ Remember your password?{" "} + + Sign In + +

+ )} +
+
+
); From e9cf92907e9fb61ae5b76d5d943428e05c2959fd Mon Sep 17 00:00:00 2001 From: Divyanshi Awasthi Date: Mon, 25 May 2026 16:05:54 +0530 Subject: [PATCH 2/2] fix(auth): clear forgot password messages --- .../src/pages/public/ForgotPassword.jsx | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx b/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx index 7d962f5..dc753b3 100644 --- a/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx +++ b/RestroHub-FrontEnd/src/pages/public/ForgotPassword.jsx @@ -35,6 +35,10 @@ const getApiMessage = (payload, fallback) => { return payload?.message || payload?.data?.message || fallback; }; +const PASSWORD_RESET_SUCCESS_MESSAGE = "Password reset successful"; +const DEFAULT_COMPLETION_MESSAGE = + "You can now sign in with your new password."; + const EmailIcon = () => ( { setRequestedEmail(normalizedEmail); setSuccessMessage(message); + setSubmitError(""); setStep("reset"); toast.success(message); } catch (err) { @@ -171,6 +176,7 @@ const ForgotPassword = () => { err.message || "Unable to send reset instructions. Please try again."; + setSuccessMessage(""); setSubmitError(message); toast.error(message); } finally { @@ -189,6 +195,7 @@ const ForgotPassword = () => { onSubmit: async ({ token, newPassword }) => { setIsResetLoading(true); setSubmitError(""); + setSuccessMessage(""); try { const resetPasswordData = new URLSearchParams({ @@ -216,7 +223,12 @@ const ForgotPassword = () => { "Password reset successful. Please sign in with your new password." ); - setSuccessMessage(message); + setSuccessMessage( + message === PASSWORD_RESET_SUCCESS_MESSAGE + ? DEFAULT_COMPLETION_MESSAGE + : message + ); + setSubmitError(""); setStep("complete"); toast.success(message); } catch (err) { @@ -225,6 +237,7 @@ const ForgotPassword = () => { err.message || "Password reset failed."; + setSuccessMessage(""); setSubmitError(message); toast.error(message); } finally { @@ -254,6 +267,16 @@ const ForgotPassword = () => { resetPasswordFormik.resetForm(); }; + const handleResetFieldChange = (event) => { + if (successMessage) { + setSuccessMessage(""); + } + if (submitError) { + setSubmitError(""); + } + resetPasswordFormik.handleChange(event); + }; + return (
@@ -413,7 +436,7 @@ const ForgotPassword = () => { placeholder="Enter code from email" disabled={isResetLoading} value={resetPasswordFormik.values.token} - onChange={resetPasswordFormik.handleChange} + onChange={handleResetFieldChange} onBlur={resetPasswordFormik.handleBlur} className={inputClass(resetPasswordFormik, "token")} /> @@ -440,7 +463,7 @@ const ForgotPassword = () => { placeholder="Enter new password" disabled={isResetLoading} value={resetPasswordFormik.values.newPassword} - onChange={resetPasswordFormik.handleChange} + onChange={handleResetFieldChange} onBlur={resetPasswordFormik.handleBlur} className={inputClass( resetPasswordFormik, @@ -470,7 +493,7 @@ const ForgotPassword = () => { placeholder="Confirm new password" disabled={isResetLoading} value={resetPasswordFormik.values.confirmPassword} - onChange={resetPasswordFormik.handleChange} + onChange={handleResetFieldChange} onBlur={resetPasswordFormik.handleBlur} className={inputClass( resetPasswordFormik,