diff --git a/client/src/lib/DTO/reset-dto.ts b/client/src/lib/DTO/reset-dto.ts new file mode 100644 index 0000000..9422bf1 --- /dev/null +++ b/client/src/lib/DTO/reset-dto.ts @@ -0,0 +1,4 @@ +export type ResetDTO = { + token: string; + password: string; +}; diff --git a/client/src/lib/services/auth-service.ts b/client/src/lib/services/auth-service.ts index 648bddb..a5a380c 100644 --- a/client/src/lib/services/auth-service.ts +++ b/client/src/lib/services/auth-service.ts @@ -4,6 +4,7 @@ import { UserResponse } from "@/lib/classes/user-response-class"; import { SignUpDTO } from "@/lib/DTO/sign-up-dto"; import { SignInDTO } from "@/lib/DTO/sign-in-dto"; import { AppRoutes } from "@/constants/routes"; +import { ResetDTO } from "../DTO/reset-dto"; const baseURL = import.meta.env.VITE_ENV === "development" @@ -158,6 +159,33 @@ export const AuthService = { }); } + return new GenericResponse({ + message: data.message, + }); + }, + resetPassword: async (reset: ResetDTO) => { + const res = await fetch( + `${baseURL}${AppRoutes.ResetPassword}/${reset.token}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + password: reset.password, + }), + } + ); + + const data = await res.json(); + + if (!res.ok) { + throw new ErrorResponse({ + status: res.status, + message: data.error, + }); + } + return new GenericResponse({ message: data.message, }); diff --git a/client/src/lib/stores/auth-store.ts b/client/src/lib/stores/auth-store.ts index 1f6860a..3581a56 100644 --- a/client/src/lib/stores/auth-store.ts +++ b/client/src/lib/stores/auth-store.ts @@ -1,5 +1,5 @@ -import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; +import { create } from "zustand"; interface AuthStoreState { email: string; diff --git a/client/src/pages/forgot-password/_components/forgot-password-form.tsx b/client/src/pages/forgot-password/_components/forgot-password-form.tsx index 3db235e..a841d7e 100644 --- a/client/src/pages/forgot-password/_components/forgot-password-form.tsx +++ b/client/src/pages/forgot-password/_components/forgot-password-form.tsx @@ -1,7 +1,6 @@ import { useMutation } from "@tanstack/react-query"; -import { useNavigate } from "react-router-dom"; +import { FormEvent, useState } from "react"; import { Mail } from "lucide-react"; -import { FormEvent } from "react"; import { toast } from "sonner"; import { GenericResponse } from "@/lib/classes/generic-response-class"; @@ -10,18 +9,17 @@ import { AuthService } from "@/lib/services/auth-service"; import { ValidateEmail } from "@/lib/utils/validations"; import { FORGOTPASSWORDKEY } from "@/constants/keys"; import { Button } from "@/components/text-button"; -import { AppRoutes } from "@/constants/routes"; import { Input } from "@/components/input"; const ForgotPasswordForm = () => { - const navigate = useNavigate(); - + const [submitted, setSubmitted] = useState(false); + const [email, setEmail] = useState(""); const { mutate, isPending } = useMutation({ mutationKey: [FORGOTPASSWORDKEY], mutationFn: AuthService.forgotPassword, onSuccess: (res: GenericResponse) => { toast.success(res.message); - navigate(AppRoutes.ResetPassword); + setSubmitted(true); }, onError: (error: ErrorResponse) => toast.error(error.message), }); @@ -33,10 +31,25 @@ const ForgotPasswordForm = () => { const forgotPasswordData = Object.fromEntries(formData.entries()); - mutate(forgotPasswordData["email"] as string); + const emailData = forgotPasswordData["email"] as string; + + setEmail(emailData); + + mutate(emailData); }; - return ( + return submitted ? ( +
+
+ +
+

+ If an account exists for{" "} + {email}, you will + receive a password reset link shortly. +

+
+ ) : (
{ + const navigate = useNavigate(); + const { token } = useParams(); const [confirmPassword, setConfirmPassword] = useState(""); - const { isPending } = useMutation({ + const { mutate, isPending } = useMutation({ mutationKey: [RESETPASSWORDKEY], + mutationFn: AuthService.resetPassword, + onSuccess: (res: GenericResponse) => { + toast.success(res.message); + navigate(AppRoutes.SignIn); + }, + onError: (error: ErrorResponse) => toast.error(error.message), }); const onSubmit = (e: FormEvent) => { e.preventDefault(); + + if (!token) return; + + const formData = new FormData(e.currentTarget); + + const resetPasswordData = Object.fromEntries( + formData.entries() + ) as ResetDTO; + + resetPasswordData.token = token; + + mutate(resetPasswordData); }; return ( diff --git a/client/src/pages/reset-password/page.tsx b/client/src/pages/reset-password/page.tsx index 05f0a65..83af043 100644 --- a/client/src/pages/reset-password/page.tsx +++ b/client/src/pages/reset-password/page.tsx @@ -1,3 +1,6 @@ +import { SwitchAuth } from "@/components/switch-auth"; +import { AppRoutes } from "@/constants/routes"; + import { ResetPasswordForm } from "./_components/reset-password-form"; const ResetPasswordPage = () => { @@ -11,6 +14,8 @@ const ResetPasswordPage = () => { + + ); }; diff --git a/controllers/auth_controller.go b/controllers/auth_controller.go index 0a66b35..bbcd928 100644 --- a/controllers/auth_controller.go +++ b/controllers/auth_controller.go @@ -256,14 +256,14 @@ func ResetPassword(c fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Either password is invalid or empty"}) } - id := c.Params("token") - if id == "" { + token := c.Params("token") + if token == "" { return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Missing token"}) } var user models.User - res := database.Instance.Where("reset_password_token = ?", id).First(&user) + res := database.Instance.Where("reset_password_token = ?", token).First(&user) if res.Error != nil { if res.Error == gorm.ErrRecordNotFound { return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{Error: "User not found"})