mirror of
https://github.com/lemeow125/EquipmentTracker-Frontend.git
synced 2024-11-17 06:09:25 +08:00
Added reset password modal and confirm reset password page
This commit is contained in:
parent
742a1af9f8
commit
a8de8476e5
8 changed files with 335 additions and 18 deletions
10
src/App.tsx
10
src/App.tsx
|
@ -10,6 +10,7 @@ import ErrorPage from "./Pages/ErrorPage/ErrorPage";
|
||||||
import DashboardPage from "./Pages/DashboardPage/DashboardPage";
|
import DashboardPage from "./Pages/DashboardPage/DashboardPage";
|
||||||
import Revalidator from "./Components/Revalidator/Revalidator";
|
import Revalidator from "./Components/Revalidator/Revalidator";
|
||||||
import ActivationPage from "./Pages/ActivationPage/ActivationPage";
|
import ActivationPage from "./Pages/ActivationPage/ActivationPage";
|
||||||
|
import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
const router = createHashRouter([
|
const router = createHashRouter([
|
||||||
|
@ -42,6 +43,15 @@ const router = createHashRouter([
|
||||||
),
|
),
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/reset_password_confirm/:uid/:token",
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<ResetPasswordPage />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
errorElement: <ErrorPage />,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
/* eslint-disable react-refresh/only-export-components */
|
/* eslint-disable react-refresh/only-export-components */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ActivationType, LoginType, RegisterType } from "../Types/Types";
|
import {
|
||||||
|
ActivationType,
|
||||||
|
LoginType,
|
||||||
|
RegisterType,
|
||||||
|
ResetPasswordConfirmType,
|
||||||
|
} from "../Types/Types";
|
||||||
|
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: "http://localhost:8000/",
|
baseURL: "http://localhost:8000/",
|
||||||
|
@ -130,3 +135,28 @@ export function ActivationAPI(activation: ActivationType) {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export function ResetPasswordAPI(email: string) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/users/reset_password/", { email: email })
|
||||||
|
.then(() => {
|
||||||
|
console.log("Activation Success");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Activation failed");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResetPasswordConfirmAPI(info: ResetPasswordConfirmType) {
|
||||||
|
return instance
|
||||||
|
.post("api/v1/accounts/users/reset_password_confirm/", info)
|
||||||
|
.then(() => {
|
||||||
|
console.log("Reset Success");
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log("Reset failed");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -106,16 +106,6 @@ export default function LoginModal() {
|
||||||
/>
|
/>
|
||||||
<p style={{ ...styles.text_dark, ...styles.text_S }}>Remember me</p>
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>Remember me</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
...styles.text_dark,
|
|
||||||
...styles.text_S,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Forgot password?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
91
src/Components/ResetPasswordModal/ResetPasswordModal.tsx
Normal file
91
src/Components/ResetPasswordModal/ResetPasswordModal.tsx
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import styles from "../../styles";
|
||||||
|
import { colors } from "../../styles";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import NewReleasesIcon from "@mui/icons-material/NewReleases";
|
||||||
|
import Button from "../Button/Button";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { ResetPasswordAPI } from "../API/API";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
export default function ResetPasswordModal() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_row,
|
||||||
|
...{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NewReleasesIcon
|
||||||
|
style={{
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
fill: colors.font_dark,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>Forgot Password</p>
|
||||||
|
</div>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>
|
||||||
|
Enter your email to request a password reset
|
||||||
|
</p>
|
||||||
|
<div style={styles.flex_column}>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
label="Email"
|
||||||
|
style={styles.input_form}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setEmail(e.target.value);
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
value={email}
|
||||||
|
placeholder={"Enter email associated with account"}
|
||||||
|
/>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_S }}>{error}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.button_border,
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Send Reset Request"}
|
||||||
|
onClick={async () => {
|
||||||
|
const status = await ResetPasswordAPI(email);
|
||||||
|
if (status === true) {
|
||||||
|
await dispatch(auth_toggle());
|
||||||
|
navigate("/");
|
||||||
|
toast("Reset request sent", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
setError(
|
||||||
|
"Password reset request sent. Please follow your email for further instructions"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setError("Invalid email specified");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -16,6 +16,12 @@ export type ActivationType = {
|
||||||
token: string;
|
token: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ResetPasswordConfirmType = {
|
||||||
|
uid: string;
|
||||||
|
token: string;
|
||||||
|
new_password: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AddEquipmentType = {
|
export type AddEquipmentType = {
|
||||||
name: string;
|
name: string;
|
||||||
remarks: string;
|
remarks: string;
|
||||||
|
|
|
@ -27,6 +27,16 @@ export default function ActivationPage() {
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
theme: "light",
|
theme: "light",
|
||||||
});
|
});
|
||||||
|
toast("Please login to continue", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,11 @@ import RegisterModal from "../../Components/RegisterModal/RegisterModal";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { RootState } from "../../Components/Plugins/Redux/Store/Store";
|
import { RootState } from "../../Components/Plugins/Redux/Store/Store";
|
||||||
|
import ResetPasswordModal from "../../Components/ResetPasswordModal/ResetPasswordModal";
|
||||||
export default function LandingPage() {
|
export default function LandingPage() {
|
||||||
const [LoginModalOpen, SetLoginModalOpen] = useState(false);
|
const [loginmodalOpen, SetloginmodalOpen] = useState(false);
|
||||||
const [RegisterModalOpen, SetRegisterModalOpen] = useState(false);
|
const [registermodalOpen, SetRegisterModalOpen] = useState(false);
|
||||||
|
const [resetmodalOpen, SetResetModalOpen] = useState(false);
|
||||||
const authenticated = useSelector((state: RootState) => state.auth.value);
|
const authenticated = useSelector((state: RootState) => state.auth.value);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
@ -68,8 +70,9 @@ export default function LandingPage() {
|
||||||
type={"light"}
|
type={"light"}
|
||||||
label={"Login"}
|
label={"Login"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
SetLoginModalOpen(true);
|
SetloginmodalOpen(true);
|
||||||
SetRegisterModalOpen(false);
|
SetRegisterModalOpen(false);
|
||||||
|
SetResetModalOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
@ -77,12 +80,22 @@ export default function LandingPage() {
|
||||||
label={"Register"}
|
label={"Register"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
SetRegisterModalOpen(true);
|
SetRegisterModalOpen(true);
|
||||||
SetLoginModalOpen(false);
|
SetloginmodalOpen(false);
|
||||||
|
SetResetModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type={"light"}
|
||||||
|
label={"Forgot Password"}
|
||||||
|
onClick={() => {
|
||||||
|
SetResetModalOpen(true);
|
||||||
|
SetRegisterModalOpen(false);
|
||||||
|
SetloginmodalOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
open={LoginModalOpen}
|
open={loginmodalOpen}
|
||||||
onClose={() => SetLoginModalOpen(false)}
|
onClose={() => SetloginmodalOpen(false)}
|
||||||
modal
|
modal
|
||||||
position={"top center"}
|
position={"top center"}
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
|
@ -100,7 +113,7 @@ export default function LandingPage() {
|
||||||
<LoginModal />
|
<LoginModal />
|
||||||
</Popup>
|
</Popup>
|
||||||
<Popup
|
<Popup
|
||||||
open={RegisterModalOpen}
|
open={registermodalOpen}
|
||||||
onClose={() => SetRegisterModalOpen(false)}
|
onClose={() => SetRegisterModalOpen(false)}
|
||||||
modal
|
modal
|
||||||
position={"top center"}
|
position={"top center"}
|
||||||
|
@ -118,6 +131,25 @@ export default function LandingPage() {
|
||||||
>
|
>
|
||||||
<RegisterModal />
|
<RegisterModal />
|
||||||
</Popup>
|
</Popup>
|
||||||
|
<Popup
|
||||||
|
open={resetmodalOpen}
|
||||||
|
onClose={() => SetResetModalOpen(false)}
|
||||||
|
modal
|
||||||
|
position={"top center"}
|
||||||
|
contentStyle={{
|
||||||
|
width: "512px",
|
||||||
|
borderRadius: 16,
|
||||||
|
borderColor: "grey",
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 16,
|
||||||
|
alignContent: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResetPasswordModal />
|
||||||
|
</Popup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
148
src/Pages/ResetPasswordPage/ResetPasswordPage.tsx
Normal file
148
src/Pages/ResetPasswordPage/ResetPasswordPage.tsx
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import styles, { colors } from "../../styles";
|
||||||
|
import { ResetPasswordConfirmAPI } from "../../Components/API/API";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { VisibilityOff, Visibility } from "@mui/icons-material";
|
||||||
|
import { TextField, InputAdornment, IconButton } from "@mui/material";
|
||||||
|
import Button from "../../Components/Button/Button";
|
||||||
|
export default function ResetPasswordPage() {
|
||||||
|
const { uid, token } = useParams();
|
||||||
|
const [feedback, setFeedback] = useState("");
|
||||||
|
const [user, setUser] = useState({
|
||||||
|
password: "",
|
||||||
|
confirm_password: "",
|
||||||
|
});
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div style={styles.background}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...styles.flex_column,
|
||||||
|
...{
|
||||||
|
justifyContent: "center",
|
||||||
|
verticalAlign: "center",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_L }}>
|
||||||
|
Confirm Password Reset
|
||||||
|
</p>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
setFeedback("");
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="New Password"
|
||||||
|
placeholder={"Enter new password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, password: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.password}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="outlined-helperText"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
style={styles.input_form}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
aria-label="toggle password visibility"
|
||||||
|
onClick={() => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
setFeedback("");
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
label="Confirm Password"
|
||||||
|
placeholder={"Re-enter password"}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setUser({ ...user, password: e.target.value })
|
||||||
|
}
|
||||||
|
value={user.password}
|
||||||
|
/>
|
||||||
|
<div style={{ justifyContent: "center", display: "flex" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.header_color,
|
||||||
|
marginTop: "16px",
|
||||||
|
width: "80%",
|
||||||
|
height: "4px",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
/>{" "}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style={{ ...styles.text_dark, ...styles.text_M }}>{feedback}</p>
|
||||||
|
<Button
|
||||||
|
type={"dark"}
|
||||||
|
label={"Confirm"}
|
||||||
|
onClick={() => {
|
||||||
|
if (uid && token && feedback == "") {
|
||||||
|
ResetPasswordConfirmAPI({
|
||||||
|
uid,
|
||||||
|
token,
|
||||||
|
new_password: user.password,
|
||||||
|
}).then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setFeedback("Reset successful");
|
||||||
|
toast("Reset successful", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
toast("Please login to continue", {
|
||||||
|
position: "top-right",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate("/");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setFeedback("Invalid token specified for password reset");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!uid || !token) {
|
||||||
|
setFeedback("Missing token for password reset");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue