finished resend email verification, reset password, and forgot password logic
This commit is contained in:
@@ -57,3 +57,33 @@ export const changePasswordAPI = async (
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const resendVerifyEmailAPI = async () => {
|
||||
const response = await axios.patch("/user-service/resend-verify-email");
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const verifyEmailAPI = async (emailToken: string) => {
|
||||
const response = await axios.patch("/user-service/verify-email", {
|
||||
emailToken,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const sendPasswordResetAPI = async (email: string) => {
|
||||
const response = await axios.patch("/user-service/send-password-reset", {
|
||||
email,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const resetPasswordAPI = async (
|
||||
password: string,
|
||||
passwordToken: string
|
||||
) => {
|
||||
const response = await axios.patch("/user-service/reset-password", {
|
||||
passwordToken,
|
||||
password,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createAccountAPI, getUserAPI, loginAPI } from "../../api/user";
|
||||
import {
|
||||
createAccountAPI,
|
||||
getUserAPI,
|
||||
loginAPI,
|
||||
sendPasswordResetAPI,
|
||||
} from "../../api/user";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { setUser } from "../../reducers/user";
|
||||
import { useAppDispatch } from "../../hooks/store";
|
||||
@@ -8,6 +13,7 @@ import AlertIcon from "../../icons/AlertIcon";
|
||||
import Spinner from "../Spinner/Spinner";
|
||||
import classNames from "classnames";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const LoginPage = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -20,6 +26,7 @@ const LoginPage = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const lastSentPassowordReset = useRef(0);
|
||||
|
||||
const attemptLoginWithToken = async () => {
|
||||
setAttemptingLogin(true);
|
||||
@@ -75,6 +82,37 @@ const LoginPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const resetPassword = async () => {
|
||||
try {
|
||||
const currentDate = Date.now();
|
||||
if (currentDate - lastSentPassowordReset.current < 1000 * 60 * 1) {
|
||||
await Swal.fire({
|
||||
title: "Please wait 1 minute before resending",
|
||||
icon: "warning",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Okay",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
lastSentPassowordReset.current = Date.now();
|
||||
|
||||
setLoadingLogin(true);
|
||||
|
||||
await toast.promise(sendPasswordResetAPI(email), {
|
||||
pending: "Sending password reset...",
|
||||
success: "Password Reset Sent",
|
||||
error: "Error Sending Password Reset",
|
||||
});
|
||||
|
||||
setLoadingLogin(false);
|
||||
} catch (e) {
|
||||
console.log("Create Account Error", e);
|
||||
setLoadingLogin(false);
|
||||
setError("Create Account Failed");
|
||||
}
|
||||
};
|
||||
|
||||
const isSubmitDisabled = useMemo(() => {
|
||||
switch (mode) {
|
||||
case "login":
|
||||
@@ -94,6 +132,8 @@ const LoginPage = () => {
|
||||
login();
|
||||
} else if (mode === "create") {
|
||||
createAccount();
|
||||
} else if (mode === "reset") {
|
||||
resetPassword();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,12 +184,7 @@ const LoginPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-[#F4F4F6] w-screen h-screen flex justify-center items-center">
|
||||
<div
|
||||
className={classNames(
|
||||
"rounded-md shadow-lg bg-white p-10 relative w-[90%] sm:w-[500px] animate-height",
|
||||
{}
|
||||
)}
|
||||
>
|
||||
<div className="rounded-md shadow-lg bg-white p-10 relative w-[90%] sm:w-[500px] animate-height">
|
||||
<div className="absolute -top-10 left-0 right-0 flex justify-center items-center">
|
||||
<div className="flex items-center justify-center rounded-full bg-white p-3 shadow-md">
|
||||
{!loadingLogin && (
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const ResetPasswordPage = (props) => (
|
||||
<div className="modal__wrap">
|
||||
<div className="inner__modal">
|
||||
<div className="password__modal">
|
||||
<div className="head__password">
|
||||
<h2>{"Reset Password"}</h2>
|
||||
<div className="close__modal"></div>
|
||||
</div>
|
||||
<div className="password__content">
|
||||
<form onSubmit={props.resetPassword}>
|
||||
<div
|
||||
style={props.state.passwordChanged ? { display: "none" } : {}}
|
||||
className="group__password"
|
||||
>
|
||||
<input
|
||||
value={props.state.password}
|
||||
onChange={props.passwordOnChange}
|
||||
placeholder="New Password"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={props.state.passwordChanged ? { display: "none" } : {}}
|
||||
className="group__password"
|
||||
>
|
||||
<input
|
||||
value={props.state.verifyPassword}
|
||||
onChange={props.verifyPasswordOnChange}
|
||||
placeholder="Verify New Password"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="password__submit">
|
||||
<input
|
||||
type="submit"
|
||||
value={props.state.passwordChanged ? "Go Back" : "Submit"}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ResetPasswordPage;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import Spinner from "../Spinner/Spinner";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import { useState } from "react";
|
||||
import { resetPasswordAPI } from "../../api/user";
|
||||
import AlertIcon from "../../icons/AlertIcon";
|
||||
|
||||
const ResetPasswordPage = () => {
|
||||
const token = useParams().token!;
|
||||
const [password, setPassword] = useState("");
|
||||
const [verifyPassword, setVerifyPassword] = useState("");
|
||||
const [loadingLogin, setLoadingLogin] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isSubmitDisabled = password !== verifyPassword;
|
||||
|
||||
const onSubmit = async (e: any) => {
|
||||
try {
|
||||
e.preventDefault();
|
||||
setLoadingLogin(true);
|
||||
await toast.promise(resetPasswordAPI(password, token), {
|
||||
pending: "Resetting password...",
|
||||
success: "Password Reset",
|
||||
error: "Error Resetting Password",
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate("/");
|
||||
}, 1500);
|
||||
} catch (e) {
|
||||
console.log("Reset Password Error", e);
|
||||
setLoadingLogin(false);
|
||||
setError("Reset Password Failed");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-[#F4F4F6] w-screen h-screen flex justify-center items-center">
|
||||
<div className="rounded-md shadow-lg bg-white p-10 relative w-[90%] sm:w-[500px] animate-height">
|
||||
<div className="absolute -top-10 left-0 right-0 flex justify-center items-center">
|
||||
<div className="flex items-center justify-center rounded-full bg-white p-3 shadow-md">
|
||||
{!loadingLogin && (
|
||||
<img src="/images/icon.png" alt="logo" className="w-[45px]" />
|
||||
)}
|
||||
{loadingLogin && <Spinner />}
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={onSubmit}>
|
||||
<p className="text-[#212B36] font-medium text-[25px] mt-0 mb-[15px] text-center">
|
||||
Reset password
|
||||
</p>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
className="w-full h-[48px] pl-[12px] pr-[70px] text-black border border-[#637381] rounded-[5px] outline-none text-[15px] mt-4"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
value={password}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Verify Password"
|
||||
className="w-full h-[48px] pl-[12px] pr-[12px] text-black border border-[#637381] rounded-[5px] outline-none text-[15px] mt-4"
|
||||
onChange={(e) => setVerifyPassword(e.target.value)}
|
||||
value={verifyPassword}
|
||||
/>
|
||||
|
||||
<div className="flex justify-center items-center mt-4">
|
||||
<input
|
||||
type="submit"
|
||||
value="Reset"
|
||||
disabled={isSubmitDisabled || loadingLogin}
|
||||
className="bg-[#3c85ee] border border-[#3c85ee] hover:bg-[#326bcc] rounded-[5px] text-white text-[15px] font-medium cursor-pointer py-2 px-4 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(isSubmitDisabled || error) && (
|
||||
<div className="mt-4">
|
||||
<div className="flex justify-center items-center">
|
||||
<AlertIcon className="w-[20px] text-red-600 mr-2" />
|
||||
<p className="text-[#637381] text-[15px]">
|
||||
{isSubmitDisabled ? "Passwords do not match" : error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<ToastContainer position="bottom-left" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordPage;
|
||||
@@ -1,107 +0,0 @@
|
||||
import React from "react";
|
||||
import Swal from "sweetalert2";
|
||||
import axios from "../../axiosInterceptor";
|
||||
import env from "../../enviroment/envFrontEnd";
|
||||
import ResetPasswordPage from "./ResetPasswordPage";
|
||||
|
||||
class ResetPasswordPageContainer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
password: "",
|
||||
verifyPassword: "",
|
||||
passwordChanged: false,
|
||||
}
|
||||
}
|
||||
|
||||
passwordOnChange = (e) => {
|
||||
|
||||
const value = e.target.value;
|
||||
|
||||
this.setState(() => {
|
||||
|
||||
return {
|
||||
...this.state,
|
||||
password: value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
verifyPasswordOnChange = (e) => {
|
||||
|
||||
const value = e.target.value;
|
||||
|
||||
this.setState(() => {
|
||||
|
||||
return {
|
||||
...this.state,
|
||||
verifyPassword : value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
resetPassword = (e) => {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if (this.state.passwordChanged) {
|
||||
|
||||
window.location.assign(env.url);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = this.props.match.params.id;
|
||||
|
||||
if (this.state.password === this.state.verifyPassword) {
|
||||
|
||||
const data = {
|
||||
passwordToken: id,
|
||||
password: this.state.password
|
||||
}
|
||||
|
||||
axios.post("/user-service/reset-password", data).then((response) => {
|
||||
|
||||
Swal.fire(
|
||||
'Password Successfully Reset',
|
||||
'You Have Successfully Resetted Your Password, All Sessions Have Been Logged Out.',
|
||||
'success'
|
||||
)
|
||||
|
||||
this.setState(() => {
|
||||
return {
|
||||
...this.state,
|
||||
passwordChanged: true
|
||||
}
|
||||
})
|
||||
|
||||
}).catch((err) => {
|
||||
console.log("reset password err", err);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Passwords Reset Error',
|
||||
})
|
||||
});
|
||||
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Passwords do not match',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ResetPasswordPage
|
||||
resetPassword={this.resetPassword}
|
||||
passwordOnChange={this.passwordOnChange}
|
||||
verifyPasswordOnChange={this.verifyPasswordOnChange}
|
||||
state={this.state}
|
||||
{...this.props}/>
|
||||
}
|
||||
}
|
||||
|
||||
export default ResetPasswordPageContainer;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import SettingsChangePasswordPopup from "./SettingsChangePasswordPopup";
|
||||
import { toast } from "react-toastify";
|
||||
import { logoutAPI } from "../../api/user";
|
||||
import { logoutAPI, resendVerifyEmailAPI } from "../../api/user";
|
||||
import Swal from "sweetalert2";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@@ -9,11 +9,17 @@ interface SettingsPageAccountProps {
|
||||
user: {
|
||||
_id: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
getUser: () => void;
|
||||
}
|
||||
|
||||
const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({
|
||||
user,
|
||||
getUser,
|
||||
}) => {
|
||||
const [showChangePasswordPopup, setShowChangePasswordPopup] = useState(false);
|
||||
const lastSentEmailVerifiation = useRef(0);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const logoutClick = async () => {
|
||||
@@ -59,7 +65,7 @@ const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
await toast.promise(logoutAPI(), {
|
||||
pending: "Logging out all...",
|
||||
success: "Logged out all",
|
||||
error: "Error Logging Out Al",
|
||||
error: "Error Logging Out all",
|
||||
});
|
||||
|
||||
window.localStorage.removeItem("hasPreviouslyLoggedIn");
|
||||
@@ -70,6 +76,32 @@ const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const resendEmailVerification = async () => {
|
||||
try {
|
||||
const currentDate = Date.now();
|
||||
if (currentDate - lastSentEmailVerifiation.current < 1000 * 60 * 1) {
|
||||
await Swal.fire({
|
||||
title: "Please wait 1 minute before resending",
|
||||
icon: "warning",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Okay",
|
||||
});
|
||||
return;
|
||||
}
|
||||
lastSentEmailVerifiation.current = Date.now();
|
||||
|
||||
await toast.promise(resendVerifyEmailAPI(), {
|
||||
pending: "Resending email verification...",
|
||||
success: "Email Verification Resent",
|
||||
error: "Error Resending Email Verification",
|
||||
});
|
||||
|
||||
getUser();
|
||||
} catch (e) {
|
||||
console.log("Error resending email verification", e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showChangePasswordPopup && (
|
||||
@@ -86,6 +118,21 @@ const SettingsPageAccount: React.FC<SettingsPageAccountProps> = ({ user }) => {
|
||||
<p className="text-gray-primary">Email</p>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
{"emailVerified" in user && (
|
||||
<div className="px-3 py-4 flex flex-row justify-between items-center border-b border-gray-secondary">
|
||||
<p className="text-gray-primary">
|
||||
{user.emailVerified ? "Email verified" : "Email unverified"}
|
||||
</p>
|
||||
{!user.emailVerified && (
|
||||
<button
|
||||
className="text-primary hover:text-primary-hover cursor-pointer"
|
||||
onClick={resendEmailVerification}
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3 py-4 flex flex-row justify-between items-center border-b border-gray-secondary">
|
||||
<p className="text-gray-primary">Change password</p>
|
||||
<button
|
||||
|
||||
@@ -6,13 +6,14 @@ import AccountIcon from "../../icons/AccountIcon";
|
||||
import TuneIcon from "../../icons/TuneIcon";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import SettingsAccountSection from "./SettingsAccountSection";
|
||||
import { getUserDetailedAPI } from "../../api/user";
|
||||
import { getUserDetailedAPI, logoutAPI } from "../../api/user";
|
||||
import Spinner from "../Spinner/Spinner";
|
||||
import Swal from "sweetalert2";
|
||||
import SettingsGeneralSection from "./SettingsGeneralSection";
|
||||
import { useClickOutOfBounds } from "../../hooks/utils";
|
||||
import MenuIcon from "../../icons/MenuIcon";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const SettingsPage = () => {
|
||||
const [user, setUser] = useState(null);
|
||||
@@ -36,6 +37,15 @@ const SettingsPage = () => {
|
||||
confirmButtonText: "Yes, logout",
|
||||
});
|
||||
if (result.value) {
|
||||
await toast.promise(logoutAPI(), {
|
||||
pending: "Logging out...",
|
||||
success: "Logged out",
|
||||
error: "Error Logging Out",
|
||||
});
|
||||
|
||||
window.localStorage.removeItem("hasPreviouslyLoggedIn");
|
||||
|
||||
navigate("/");
|
||||
} else {
|
||||
navigate("/home");
|
||||
}
|
||||
@@ -111,7 +121,9 @@ const SettingsPage = () => {
|
||||
onClick={() => setShowSidebarMobile(!showSidebarMobile)}
|
||||
/>
|
||||
</div>
|
||||
{tab === "account" && <SettingsAccountSection user={user} />}
|
||||
{tab === "account" && (
|
||||
<SettingsAccountSection user={user} getUser={getUser} />
|
||||
)}
|
||||
{tab === "general" && <SettingsGeneralSection />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const VerifyEmailPage = (props) => (
|
||||
<div className="verify-page__wrapper">
|
||||
<p className="verify-page__title">{props.state.error ? "Verification Error" : props.state.verified ? "Email Verified" : "Verifying Email..."}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default VerifyEmailPage;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { verifyEmailAPI } from "../../api/user";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const VerifyEmailPage = () => {
|
||||
const token = useParams().token!;
|
||||
|
||||
const verifyEmail = async () => {
|
||||
try {
|
||||
await toast.promise(verifyEmailAPI(token), {
|
||||
pending: "Verifying email...",
|
||||
success: "Email Verified",
|
||||
error: "Error verifying email",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.assign("/");
|
||||
}, 1500);
|
||||
} catch (e) {
|
||||
console.log("Error verifying email", e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
verifyEmail();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen flex justify-center items-center flex-col">
|
||||
<h1>Verifying email...</h1>
|
||||
<ToastContainer position="bottom-left" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyEmailPage;
|
||||
@@ -1,73 +0,0 @@
|
||||
import React from "react";
|
||||
import axios from "../../axiosInterceptor";
|
||||
import Swal from "sweetalert2";
|
||||
import VerifyEmailPage from "./VerifyEmailPage";
|
||||
|
||||
class VerifiyEmailPageContainer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
verified: false,
|
||||
error: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount = () => {
|
||||
this.checkEmailVerification();
|
||||
}
|
||||
|
||||
checkEmailVerification = () => {
|
||||
|
||||
const id = this.props.match.params.id
|
||||
|
||||
if (id) {
|
||||
|
||||
const data = {
|
||||
emailToken: id
|
||||
}
|
||||
|
||||
axios.post("/user-service/verify-email", data).then((response) => {
|
||||
|
||||
this.setState(() => {
|
||||
return {
|
||||
...this.state,
|
||||
verified: true,
|
||||
error: false
|
||||
}
|
||||
})
|
||||
|
||||
Swal.fire(
|
||||
'Email Address Verified',
|
||||
'You Have Successfully verified your email address, you may now log into myDrive.',
|
||||
'success'
|
||||
)
|
||||
|
||||
}).catch((err) => {
|
||||
|
||||
this.setState(() => {
|
||||
return {
|
||||
...this.state,
|
||||
error: true
|
||||
}
|
||||
})
|
||||
|
||||
Swal.fire(
|
||||
'Email Address Verifation Error',
|
||||
'Could Not Verifiy Email Address',
|
||||
'error'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return <VerifyEmailPage
|
||||
state={this.state}/>
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifiyEmailPageContainer;
|
||||
@@ -11,9 +11,9 @@ import HomePage from "../components/Homepage/Homepage";
|
||||
import PrivateRoute from "./PrivateRoute";
|
||||
import PublicRoute from "./PublicRoute";
|
||||
import DownloadPage from "../components/DownloadPage/DownloadPage";
|
||||
import VerifyEmailPage from "../components/VerifyEmailPage";
|
||||
import VerifyEmailPage from "../components/VerifyEmailPage/VerifyEmailPage";
|
||||
import uuid from "uuid";
|
||||
import ResetPasswordPage from "../components/ResetPasswordPage";
|
||||
import ResetPasswordPage from "../components/ResetPasswordPage/ResetPasswordPage";
|
||||
import SettingsPage from "../components/SettingsPage/SettingsPage";
|
||||
import Homepage from "../components/Homepage/Homepage";
|
||||
import { usePreferenceSetter } from "../hooks/preferenceSetter";
|
||||
@@ -95,8 +95,8 @@ const AppRouter = () => {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/verify-email/:id" element={<VerifyEmailPage />} />
|
||||
<Route path="/reset-password/:id" element={<ResetPasswordPage />} />
|
||||
<Route path="/verify-email/:token" element={<VerifyEmailPage />} />
|
||||
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
|
||||
Reference in New Issue
Block a user