diff --git a/src/App.tsx b/src/App.tsx index 3eca904..d6f8d27 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,9 @@ import RaidGroupPage from "./pages/protected/RaidGroupPage"; import RaidGroupsPage from "./pages/protected/RaidGroupsPage"; import RaidInstancePage from "./pages/protected/RaidInstancePage"; import RaidLayoutPage from "./pages/protected/RaidLayoutPage"; +import ConfirmPage from "./pages/public/ConfirmPage"; import ForgotPasswordPage from "./pages/public/ForgotPassword"; +import ForgotTokenPage from "./pages/public/ForgotTokenPage"; import HomePage from "./pages/public/HomePage"; import LoginPage from "./pages/public/LoginPage"; import SignupPage from "./pages/public/SignupPage"; @@ -41,6 +43,14 @@ const routes = createBrowserRouter([ path: "/forgotPassword", element: }, + { + path: "/forgotPassword/:forgotToken", + element: + }, + { + path: "/confirm/:confirmToken", + element: + }, { element: , children: [ diff --git a/src/hooks/AuthHooks.ts b/src/hooks/AuthHooks.ts index ceb59a2..0c22d44 100644 --- a/src/hooks/AuthHooks.ts +++ b/src/hooks/AuthHooks.ts @@ -1,6 +1,7 @@ import { Account } from "@/interface/Account"; import { api } from "@/util/AxiosUtil"; import { useMutation } from "@tanstack/react-query"; +import { AxiosError } from "axios"; export function useSignup(){ @@ -18,3 +19,77 @@ export function useSignup(){ } }); } + + +export function useConfirm(){ + return useMutation({ + mutationKey: ["confirm"], + mutationFn: async (confirmToken: string) => { + try{ + const response = await api.post(`/auth/confirm/${confirmToken}`); + + if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + } + catch(error){ + if(error instanceof AxiosError && error.response?.data.errors){ + throw new Error(error.response?.data.errors.join(", ")); + } + else{ + throw error; + } + } + } + }); +} + +export function useForgotPassword(){ + return useMutation({ + mutationKey: ["forgotPassword"], + mutationFn: async (username: string) => { + const params = new URLSearchParams(); + params.append("username", username); + + + try{ + const response = await api.post(`/auth/forgot?${params}`); + + if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + } + catch(error){ + if(error instanceof AxiosError && error.response?.data.errors){ + throw new Error(error.response?.data.errors.join(", ")); + } + else{ + throw error; + } + } + } + }); +} + +export function useForgotResetPassword(forgotToken: string){ + return useMutation({ + mutationKey: ["forgotResetPassword"], + mutationFn: async (password: string) => { + try{ + const response = await api.post(`/auth/forgot/${forgotToken}`, {password}); + + if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + } + catch(error){ + if(error instanceof AxiosError && error.response?.data.errors){ + throw new Error(error.response?.data.errors.join(", ")); + } + else{ + throw error; + } + } + } + }); +} diff --git a/src/pages/public/ConfirmPage.tsx b/src/pages/public/ConfirmPage.tsx new file mode 100644 index 0000000..bfd178e --- /dev/null +++ b/src/pages/public/ConfirmPage.tsx @@ -0,0 +1,41 @@ +import DangerMessage from "@/components/message/DangerMessage"; +import SuccessMessage from "@/components/message/SuccessMessage"; +import { useConfirm } from "@/hooks/AuthHooks"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import { useEffect } from "react"; +import { useNavigate, useParams } from "react-router"; + + +export default function ConfirmPage(){ + const { confirmToken } = useParams(); + const navigate = useNavigate(); + const { addErrorMessage, addSuccessMessage } = useTimedModal(); + + + const { mutate: confirmQueryMutate, status: confirmQueryStatus, error: confirmQueryError, reset: confirmQueryReset } = useConfirm(); + + useEffect(() => { + if(confirmQueryStatus === "idle"){ + confirmQueryMutate(confirmToken ?? ""); + } + else if(confirmQueryStatus === "error"){ + addErrorMessage(confirmQueryError.message); + confirmQueryReset(); + } + else if(confirmQueryStatus === "success"){ + addSuccessMessage("Email confirmed. Please Login."); + navigate("/login"); + } + }, [ confirmToken, confirmQueryMutate, confirmQueryStatus, confirmQueryError, confirmQueryReset, navigate, addSuccessMessage, addErrorMessage ]); + + + if(confirmQueryStatus === "pending"){ + return
Confirming...
; + } + else if(confirmQueryStatus === "error"){ + return
Error: {confirmQueryError.message}
; + } + else if(confirmQueryStatus === "success"){ + return
Confirmed
; + } +} diff --git a/src/pages/public/ForgotPassword.tsx b/src/pages/public/ForgotPassword.tsx index 0d71478..2cf2020 100644 --- a/src/pages/public/ForgotPassword.tsx +++ b/src/pages/public/ForgotPassword.tsx @@ -1,7 +1,55 @@ +import PrimaryButton from "@/components/button/PrimaryButton"; +import TextInput from "@/components/input/TextInput"; +import { useForgotPassword } from "@/hooks/AuthHooks"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router"; + + export default function ForgotPasswordPage(){ - return ( + const { mutate: forgotEmailMutate, status: forgotEmailStatus, error: forgotEmailError, reset: forgotEmailReset } = useForgotPassword(); + const { addErrorMessage, addSuccessMessage } = useTimedModal(); + const navigate = useNavigate(); + + const [ username, setUsername ] = useState(""); + + + useEffect(() => { + if(forgotEmailStatus === "success"){ + addSuccessMessage("Email sent successfully"); + forgotEmailReset(); + navigate("/login"); + } + else if(forgotEmailStatus === "error"){ + addErrorMessage("Failed to send email: " + forgotEmailError?.message); + forgotEmailReset(); + } + }, [addErrorMessage, addSuccessMessage, forgotEmailError, forgotEmailReset, forgotEmailStatus, navigate]); + + const sendForgotEmail = () => { + forgotEmailMutate(username); + } + + + return(
- Under Construction +
+
+ setUsername(e.target.value)} + /> +
+ + Send Recovery Email + +
); } diff --git a/src/pages/public/ForgotTokenPage.tsx b/src/pages/public/ForgotTokenPage.tsx new file mode 100644 index 0000000..36cf43d --- /dev/null +++ b/src/pages/public/ForgotTokenPage.tsx @@ -0,0 +1,52 @@ +import PrimaryButton from "@/components/button/PrimaryButton"; +import PasswordInput from "@/components/input/PasswordInput"; +import { useForgotResetPassword } from "@/hooks/AuthHooks"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router"; + + +export default function ForgotTokenPage(){ + const { forgotToken } = useParams(); + const navigate = useNavigate(); + const { addErrorMessage, addSuccessMessage } = useTimedModal(); + + const [ newPassword, setNewPassword ] = useState(""); + + + const { mutate: forgotQueryMutate, status: forgotQueryStatus, error: forgotQueryError, reset: forgotQueryReset } = useForgotResetPassword(forgotToken ?? ""); + + + useEffect(() => { + if(forgotQueryStatus === "success"){ + addSuccessMessage("Password reset. Please login."); + forgotQueryReset(); + navigate("/login"); + } + else if(forgotQueryStatus === "error"){ + addErrorMessage("Error resetting password: " + forgotQueryError.message); + forgotQueryReset(); + } + }, [addErrorMessage, addSuccessMessage, forgotQueryError, forgotQueryReset, forgotQueryStatus, navigate]); + + + return ( +
+
+ setNewPassword(e.target.value)} + /> +
+ forgotQueryMutate(newPassword)} + > + Reset Password + +
+ ); +}