Confirm and password emails sending

This commit is contained in:
2025-03-22 23:46:27 -04:00
parent 4d7585c770
commit 3b738c337b
5 changed files with 228 additions and 2 deletions

View File

@@ -10,7 +10,9 @@ import RaidGroupPage from "./pages/protected/RaidGroupPage";
import RaidGroupsPage from "./pages/protected/RaidGroupsPage"; import RaidGroupsPage from "./pages/protected/RaidGroupsPage";
import RaidInstancePage from "./pages/protected/RaidInstancePage"; import RaidInstancePage from "./pages/protected/RaidInstancePage";
import RaidLayoutPage from "./pages/protected/RaidLayoutPage"; import RaidLayoutPage from "./pages/protected/RaidLayoutPage";
import ConfirmPage from "./pages/public/ConfirmPage";
import ForgotPasswordPage from "./pages/public/ForgotPassword"; import ForgotPasswordPage from "./pages/public/ForgotPassword";
import ForgotTokenPage from "./pages/public/ForgotTokenPage";
import HomePage from "./pages/public/HomePage"; import HomePage from "./pages/public/HomePage";
import LoginPage from "./pages/public/LoginPage"; import LoginPage from "./pages/public/LoginPage";
import SignupPage from "./pages/public/SignupPage"; import SignupPage from "./pages/public/SignupPage";
@@ -41,6 +43,14 @@ const routes = createBrowserRouter([
path: "/forgotPassword", path: "/forgotPassword",
element: <ForgotPasswordPage/> element: <ForgotPasswordPage/>
}, },
{
path: "/forgotPassword/:forgotToken",
element: <ForgotTokenPage/>
},
{
path: "/confirm/:confirmToken",
element: <ConfirmPage/>
},
{ {
element: <ProtectedRoute/>, element: <ProtectedRoute/>,
children: [ children: [

View File

@@ -1,6 +1,7 @@
import { Account } from "@/interface/Account"; import { Account } from "@/interface/Account";
import { api } from "@/util/AxiosUtil"; import { api } from "@/util/AxiosUtil";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
export function useSignup(){ 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;
}
}
}
});
}

View File

@@ -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 <main>Confirming...</main>;
}
else if(confirmQueryStatus === "error"){
return <main><DangerMessage>Error: {confirmQueryError.message}</DangerMessage></main>;
}
else if(confirmQueryStatus === "success"){
return <main><SuccessMessage>Confirmed</SuccessMessage></main>;
}
}

View File

@@ -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(){ 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(
<main> <main>
Under Construction <div
className="flex flex-col items-center justify-center gap-y-8"
>
<div>
<TextInput
id="forgotPasswordUsername"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<PrimaryButton
onClick={sendForgotEmail}
>
Send Recovery Email
</PrimaryButton>
</div>
</main> </main>
); );
} }

View File

@@ -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 (
<main
className="flex flex-col items-center justify-center gap-y-8"
>
<div>
<PasswordInput
id="newPassword"
placeholder="New Password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
<PrimaryButton
onClick={() => forgotQueryMutate(newPassword)}
>
Reset Password
</PrimaryButton>
</main>
);
}