Merge backend + frontend #1
@@ -3,6 +3,12 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
 | 
				
			|||||||
import ScrollToTop from "./components/ScrollToTop";
 | 
					import ScrollToTop from "./components/ScrollToTop";
 | 
				
			||||||
import TopBar from "./components/TopBar";
 | 
					import TopBar from "./components/TopBar";
 | 
				
			||||||
import Register from "./pages/Register";
 | 
					import Register from "./pages/Register";
 | 
				
			||||||
 | 
					import ConfirmEmail from "./pages/ConfirmEmail";
 | 
				
			||||||
 | 
					import Login from "./pages/Login";
 | 
				
			||||||
 | 
					import RequireAuth from "./components/RequireAuth";
 | 
				
			||||||
 | 
					import ChangePassword from "./pages/ChangePassword";
 | 
				
			||||||
 | 
					import ForgottenPassword from "./pages/ForgottenPassword";
 | 
				
			||||||
 | 
					import ResetPassword from "./pages/ResetPassword";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Router = () => (
 | 
					const Router = () => (
 | 
				
			||||||
  <BrowserRouter>
 | 
					  <BrowserRouter>
 | 
				
			||||||
@@ -10,6 +16,18 @@ const Router = () => (
 | 
				
			|||||||
    <TopBar />
 | 
					    <TopBar />
 | 
				
			||||||
    <Routes>
 | 
					    <Routes>
 | 
				
			||||||
      <Route path="/register/" element={<Register />} />
 | 
					      <Route path="/register/" element={<Register />} />
 | 
				
			||||||
 | 
					      <Route path="/confirm-email/:token/" element={<ConfirmEmail />} />
 | 
				
			||||||
 | 
					      <Route path="/login/" element={<Login />} />
 | 
				
			||||||
 | 
					      <Route
 | 
				
			||||||
 | 
					        path="/change-password/"
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <RequireAuth>
 | 
				
			||||||
 | 
					            <ChangePassword />
 | 
				
			||||||
 | 
					          </RequireAuth>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <Route path="/forgotten-password/" element={<ForgottenPassword />} />
 | 
				
			||||||
 | 
					      <Route path="/reset-password/:token/" element={<ResetPassword />} />
 | 
				
			||||||
    </Routes>
 | 
					    </Routes>
 | 
				
			||||||
  </BrowserRouter>
 | 
					  </BrowserRouter>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								frontend/src/pages/ChangePassword.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								frontend/src/pages/ChangePassword.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { Form, Formik, FormikHelpers } from "formik";
 | 
				
			||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import * as yup from "yup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import FormActions from "src/components/FormActions";
 | 
				
			||||||
 | 
					import LazyPasswordWithStrengthField from "src/components/LazyPasswordWithStrengthField";
 | 
				
			||||||
 | 
					import PasswordField from "src/components/PasswordField";
 | 
				
			||||||
 | 
					import Title from "src/components/Title";
 | 
				
			||||||
 | 
					import { ToastContext } from "src/ToastContext";
 | 
				
			||||||
 | 
					import { useMutation } from "src/query";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IForm {
 | 
				
			||||||
 | 
					  currentPassword: string;
 | 
				
			||||||
 | 
					  newPassword: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useChangePassword = () => {
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					  const { mutateAsync: changePassword } = useMutation(
 | 
				
			||||||
 | 
					    async (data: IForm) => await axios.put("/members/password/", data),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return async (data: IForm, { setFieldError }: FormikHelpers<IForm>) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await changePassword(data);
 | 
				
			||||||
 | 
					      addToast("Changed", "success");
 | 
				
			||||||
 | 
					      navigate("/");
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (axios.isAxiosError(error)) {
 | 
				
			||||||
 | 
					        if (error.response?.status === 400) {
 | 
				
			||||||
 | 
					          setFieldError("newPassword", "Password is too weak");
 | 
				
			||||||
 | 
					        } else if (error.response?.status === 401) {
 | 
				
			||||||
 | 
					          setFieldError("currentPassword", "Incorrect password");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        addToast("Try again", "error");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validationSchema = yup.object({
 | 
				
			||||||
 | 
					  currentPassword: yup.string().required("Required"),
 | 
				
			||||||
 | 
					  newPassword: yup.string().required("Required"),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ChangePassword = () => {
 | 
				
			||||||
 | 
					  const onSubmit = useChangePassword();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Title title="Change Password" />
 | 
				
			||||||
 | 
					      <Formik<IForm>
 | 
				
			||||||
 | 
					        initialValues={{ currentPassword: "", newPassword: "" }}
 | 
				
			||||||
 | 
					        onSubmit={onSubmit}
 | 
				
			||||||
 | 
					        validationSchema={validationSchema}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {({ dirty, isSubmitting }) => (
 | 
				
			||||||
 | 
					          <Form>
 | 
				
			||||||
 | 
					            <PasswordField
 | 
				
			||||||
 | 
					              autoComplete="current-password"
 | 
				
			||||||
 | 
					              fullWidth
 | 
				
			||||||
 | 
					              label="Current password"
 | 
				
			||||||
 | 
					              name="currentPassword"
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <LazyPasswordWithStrengthField
 | 
				
			||||||
 | 
					              autoComplete="new-password"
 | 
				
			||||||
 | 
					              fullWidth
 | 
				
			||||||
 | 
					              label="New password"
 | 
				
			||||||
 | 
					              name="newPassword"
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <FormActions
 | 
				
			||||||
 | 
					              disabled={!dirty}
 | 
				
			||||||
 | 
					              isSubmitting={isSubmitting}
 | 
				
			||||||
 | 
					              label="Change"
 | 
				
			||||||
 | 
					              links={[{ label: "Back", to: "/" }]}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </Form>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Formik>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ChangePassword;
 | 
				
			||||||
							
								
								
									
										44
									
								
								frontend/src/pages/ConfirmEmail.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/src/pages/ConfirmEmail.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import LinearProgress from "@mui/material/LinearProgress";
 | 
				
			||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useParams } from "react-router";
 | 
				
			||||||
 | 
					import { Navigate } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useQuery } from "../query";
 | 
				
			||||||
 | 
					import { ToastContext } from "../ToastContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IParams {
 | 
				
			||||||
 | 
					  token?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ConfirmEmail = () => {
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					  const params = useParams() as IParams;
 | 
				
			||||||
 | 
					  const token = params.token ?? "";
 | 
				
			||||||
 | 
					  const { isLoading } = useQuery(
 | 
				
			||||||
 | 
					    ["Email"],
 | 
				
			||||||
 | 
					    async () => await axios.put("/members/email/", { token }),
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      onError: (error: any) => {
 | 
				
			||||||
 | 
					        if (error.response?.status === 400) {
 | 
				
			||||||
 | 
					          if (error.response?.data.code === "TOKEN_INVALID") {
 | 
				
			||||||
 | 
					            addToast("Invalid token", "error");
 | 
				
			||||||
 | 
					          } else if (error.response?.data.code === "TOKEN_EXPIRED") {
 | 
				
			||||||
 | 
					            addToast("Token expired", "error");
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          addToast("Try again", "error");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      onSuccess: () => addToast("Thanks", "success"),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isLoading) {
 | 
				
			||||||
 | 
					    return <LinearProgress />;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return <Navigate to="/" />;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ConfirmEmail;
 | 
				
			||||||
							
								
								
									
										82
									
								
								frontend/src/pages/ForgottenPassword.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								frontend/src/pages/ForgottenPassword.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { Form, Formik } from "formik";
 | 
				
			||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useLocation, useNavigate } from "react-router";
 | 
				
			||||||
 | 
					import * as yup from "yup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import EmailField from "src/components/EmailField";
 | 
				
			||||||
 | 
					import FormActions from "src/components/FormActions";
 | 
				
			||||||
 | 
					import Title from "src/components/Title";
 | 
				
			||||||
 | 
					import { useMutation } from "src/query";
 | 
				
			||||||
 | 
					import { ToastContext } from "src/ToastContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IForm {
 | 
				
			||||||
 | 
					  email: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useForgottenPassword = () => {
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { mutateAsync: forgottenPassword } = useMutation(
 | 
				
			||||||
 | 
					    async (data: IForm) =>
 | 
				
			||||||
 | 
					      await axios.post("/members/forgotten-password/", data),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return async (data: IForm) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await forgottenPassword(data);
 | 
				
			||||||
 | 
					      addToast("Reset link sent to your email", "success");
 | 
				
			||||||
 | 
					      navigate("/login/");
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      addToast("Try again", "error");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validationSchema = yup.object({
 | 
				
			||||||
 | 
					  email: yup.string().email("Email invalid").required("Required"),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ForgottenPassword = () => {
 | 
				
			||||||
 | 
					  const onSubmit = useForgottenPassword();
 | 
				
			||||||
 | 
					  const location = useLocation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Title title="Forgotten password" />
 | 
				
			||||||
 | 
					      <Formik<IForm>
 | 
				
			||||||
 | 
					        initialValues={{
 | 
				
			||||||
 | 
					          email: (location.state as any)?.email ?? "",
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					        onSubmit={onSubmit}
 | 
				
			||||||
 | 
					        validationSchema={validationSchema}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {({ dirty, isSubmitting, values }) => (
 | 
				
			||||||
 | 
					          <Form>
 | 
				
			||||||
 | 
					            <EmailField fullWidth label="Email" name="email" required />
 | 
				
			||||||
 | 
					            <FormActions
 | 
				
			||||||
 | 
					              disabled={!dirty}
 | 
				
			||||||
 | 
					              isSubmitting={isSubmitting}
 | 
				
			||||||
 | 
					              label="Send email"
 | 
				
			||||||
 | 
					              links={[
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  label: "Login",
 | 
				
			||||||
 | 
					                  to: "/login/",
 | 
				
			||||||
 | 
					                  state: { email: values.email },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  label: "Register",
 | 
				
			||||||
 | 
					                  to: "/register/",
 | 
				
			||||||
 | 
					                  state: { email: values.email },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ]}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </Form>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Formik>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ForgottenPassword;
 | 
				
			||||||
							
								
								
									
										99
									
								
								frontend/src/pages/Login.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								frontend/src/pages/Login.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { Form, Formik, FormikHelpers } from "formik";
 | 
				
			||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useLocation, useNavigate } from "react-router";
 | 
				
			||||||
 | 
					import * as yup from "yup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AuthContext } from "../AuthContext";
 | 
				
			||||||
 | 
					import EmailField from "../components/EmailField";
 | 
				
			||||||
 | 
					import FormActions from "../components/FormActions";
 | 
				
			||||||
 | 
					import PasswordField from "../components/PasswordField";
 | 
				
			||||||
 | 
					import Title from "../components/Title";
 | 
				
			||||||
 | 
					import { ToastContext } from "../ToastContext";
 | 
				
			||||||
 | 
					import { useMutation } from "../query";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IForm {
 | 
				
			||||||
 | 
					  email: string;
 | 
				
			||||||
 | 
					  password: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useLogin = () => {
 | 
				
			||||||
 | 
					  const location = useLocation();
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					  const { setAuthenticated } = useContext(AuthContext);
 | 
				
			||||||
 | 
					  const { mutateAsync: login } = useMutation(
 | 
				
			||||||
 | 
					    async (data: IForm) => await axios.post("/sessions/", data),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return async (data: IForm, { setFieldError }: FormikHelpers<IForm>) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await login(data);
 | 
				
			||||||
 | 
					      setAuthenticated(true);
 | 
				
			||||||
 | 
					      navigate((location.state as any)?.from ?? "/");
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (error.response?.status === 401) {
 | 
				
			||||||
 | 
					        setFieldError("email", "Invalid credentials");
 | 
				
			||||||
 | 
					        setFieldError("password", "Invalid credentials");
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        addToast("Try again", "error");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validationSchema = yup.object({
 | 
				
			||||||
 | 
					  email: yup.string().email("Email invalid").required("Required"),
 | 
				
			||||||
 | 
					  password: yup.string().required("Required"),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Login = () => {
 | 
				
			||||||
 | 
					  const onSubmit = useLogin();
 | 
				
			||||||
 | 
					  const location = useLocation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Title title="Login" />
 | 
				
			||||||
 | 
					      <Formik<IForm>
 | 
				
			||||||
 | 
					        initialValues={{
 | 
				
			||||||
 | 
					          email: (location.state as any)?.email ?? "",
 | 
				
			||||||
 | 
					          password: "",
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					        onSubmit={onSubmit}
 | 
				
			||||||
 | 
					        validationSchema={validationSchema}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {({ dirty, isSubmitting, values }) => (
 | 
				
			||||||
 | 
					          <Form>
 | 
				
			||||||
 | 
					            <EmailField fullWidth label="Email" name="email" required />
 | 
				
			||||||
 | 
					            <PasswordField
 | 
				
			||||||
 | 
					              autoComplete="password"
 | 
				
			||||||
 | 
					              fullWidth
 | 
				
			||||||
 | 
					              label="Password"
 | 
				
			||||||
 | 
					              name="password"
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <FormActions
 | 
				
			||||||
 | 
					              disabled={!dirty}
 | 
				
			||||||
 | 
					              isSubmitting={isSubmitting}
 | 
				
			||||||
 | 
					              label="Login"
 | 
				
			||||||
 | 
					              links={[
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  label: "Reset password",
 | 
				
			||||||
 | 
					                  to: "/forgotten-password/",
 | 
				
			||||||
 | 
					                  state: { email: values.email },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  label: "Register",
 | 
				
			||||||
 | 
					                  to: "/register/",
 | 
				
			||||||
 | 
					                  state: { email: values.email },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ]}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </Form>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Formik>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Login;
 | 
				
			||||||
@@ -5,12 +5,12 @@ import { useNavigate } from "react-router";
 | 
				
			|||||||
import { useLocation } from "react-router-dom";
 | 
					import { useLocation } from "react-router-dom";
 | 
				
			||||||
import * as yup from "yup";
 | 
					import * as yup from "yup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import EmailField from "../components/EmailField";
 | 
					import EmailField from "src/components/EmailField";
 | 
				
			||||||
import FormActions from "../components/FormActions";
 | 
					import FormActions from "src/components/FormActions";
 | 
				
			||||||
import LazyPasswordWithStrengthField from "../components/LazyPasswordWithStrengthField";
 | 
					import LazyPasswordWithStrengthField from "src/components/LazyPasswordWithStrengthField";
 | 
				
			||||||
import Title from "../components/Title";
 | 
					import Title from "src/components/Title";
 | 
				
			||||||
import { ToastContext } from "../ToastContext";
 | 
					import { ToastContext } from "src/ToastContext";
 | 
				
			||||||
import { useMutation } from "../query";
 | 
					import { useMutation } from "src/query";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IForm {
 | 
					interface IForm {
 | 
				
			||||||
  email: string;
 | 
					  email: string;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								frontend/src/pages/ResetPassword.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								frontend/src/pages/ResetPassword.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { Form, Formik, FormikHelpers } from "formik";
 | 
				
			||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useNavigate, useParams } from "react-router";
 | 
				
			||||||
 | 
					import * as yup from "yup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import LazyPasswordWithStrengthField from "src/components/LazyPasswordWithStrengthField";
 | 
				
			||||||
 | 
					import FormActions from "src/components/FormActions";
 | 
				
			||||||
 | 
					import Title from "src/components/Title";
 | 
				
			||||||
 | 
					import { useMutation } from "src/query";
 | 
				
			||||||
 | 
					import { ToastContext } from "src/ToastContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IForm {
 | 
				
			||||||
 | 
					  password: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IParams {
 | 
				
			||||||
 | 
					  token?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useResetPassword = () => {
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const params = useParams() as IParams;
 | 
				
			||||||
 | 
					  const token = params.token ?? "";
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { mutateAsync: reset } = useMutation(
 | 
				
			||||||
 | 
					    async (password: string) =>
 | 
				
			||||||
 | 
					      await axios.put("/members/reset-password/", { password, token }),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return async (data: IForm, { setFieldError }: FormikHelpers<IForm>) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await reset(data.password);
 | 
				
			||||||
 | 
					      addToast("Success", "success");
 | 
				
			||||||
 | 
					      navigate("/login/");
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      if (error.response?.status === 400) {
 | 
				
			||||||
 | 
					        if (error.response?.data.code === "WEAK_PASSWORD") {
 | 
				
			||||||
 | 
					          setFieldError("newPassword", "Password is too weak");
 | 
				
			||||||
 | 
					        } else if (error.response?.data.code === "TOKEN_INVALID") {
 | 
				
			||||||
 | 
					          addToast("Invalid token", "error");
 | 
				
			||||||
 | 
					        } else if (error.response?.data.code === "TOKEN_EXPIRED") {
 | 
				
			||||||
 | 
					          addToast("Token expired", "error");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        addToast("Try again", "error");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validationSchema = yup.object({
 | 
				
			||||||
 | 
					  email: yup.string().email("Email invalid").required("Required"),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ResetPassword = () => {
 | 
				
			||||||
 | 
					  const onSubmit = useResetPassword();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Title title="Reset password" />
 | 
				
			||||||
 | 
					      <Formik<IForm>
 | 
				
			||||||
 | 
					        initialValues={{ password: "" }}
 | 
				
			||||||
 | 
					        onSubmit={onSubmit}
 | 
				
			||||||
 | 
					        validationSchema={validationSchema}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {({ dirty, isSubmitting, values }) => (
 | 
				
			||||||
 | 
					          <Form>
 | 
				
			||||||
 | 
					            <LazyPasswordWithStrengthField
 | 
				
			||||||
 | 
					              autoComplete="new-password"
 | 
				
			||||||
 | 
					              fullWidth
 | 
				
			||||||
 | 
					              label="Password"
 | 
				
			||||||
 | 
					              name="password"
 | 
				
			||||||
 | 
					              required
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <FormActions
 | 
				
			||||||
 | 
					              disabled={!dirty}
 | 
				
			||||||
 | 
					              isSubmitting={isSubmitting}
 | 
				
			||||||
 | 
					              label="Reset password"
 | 
				
			||||||
 | 
					              links={[{ label: "Login", to: "/login/" }]}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </Form>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Formik>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ResetPassword;
 | 
				
			||||||
		Reference in New Issue
	
	Block a user