From de7da6f56b5c089f3e705ce8708bcbbfc31965ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Wed, 23 Mar 2022 13:14:52 +0100 Subject: [PATCH] feat(dashboard-v2): implement account recovery flow --- .../src/components/forms/RecoveryForm.js | 57 +++++++++++++++ .../src/components/forms/ResetPasswordForm.js | 72 +++++++++++++++++++ .../src/pages/auth/reset-password.js | 50 +++++++++++++ .../dashboard-v2/src/pages/user/recover.js | 56 +++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 packages/dashboard-v2/src/components/forms/RecoveryForm.js create mode 100644 packages/dashboard-v2/src/components/forms/ResetPasswordForm.js create mode 100644 packages/dashboard-v2/src/pages/auth/reset-password.js create mode 100644 packages/dashboard-v2/src/pages/user/recover.js diff --git a/packages/dashboard-v2/src/components/forms/RecoveryForm.js b/packages/dashboard-v2/src/components/forms/RecoveryForm.js new file mode 100644 index 00000000..50564b34 --- /dev/null +++ b/packages/dashboard-v2/src/components/forms/RecoveryForm.js @@ -0,0 +1,57 @@ +import PropTypes from "prop-types"; +import { Formik, Form } from "formik"; +import * as Yup from "yup"; + +import { TextField } from "../Form/TextField"; +import { Button } from "../Button"; + +import accountsService from "../../services/accountsService"; + +const recoverySchema = Yup.object().shape({ + email: Yup.string().required("Email is required").email("Please provide a valid email address"), +}); + +export const RecoveryForm = ({ onSuccess, onFailure }) => ( + { + try { + await accountsService.post("user/recover/request", { + json: values, + }); + + onSuccess(); + } catch { + onFailure(); + } + }} + > + {({ errors, touched }) => ( +
+

Request account recovery

+ + +
+ +
+ + )} +
+); + +RecoveryForm.propTypes = { + onFailure: PropTypes.func.isRequired, + onSuccess: PropTypes.func.isRequired, +}; diff --git a/packages/dashboard-v2/src/components/forms/ResetPasswordForm.js b/packages/dashboard-v2/src/components/forms/ResetPasswordForm.js new file mode 100644 index 00000000..5f3fbf0b --- /dev/null +++ b/packages/dashboard-v2/src/components/forms/ResetPasswordForm.js @@ -0,0 +1,72 @@ +import PropTypes from "prop-types"; +import { Formik, Form } from "formik"; +import * as Yup from "yup"; + +import { TextField } from "../Form/TextField"; +import { Button } from "../Button"; + +import accountsService from "../../services/accountsService"; + +const resetPasswordSchema = Yup.object().shape({ + password: Yup.string().required("Password is required").min(6, "Password has to be at least 6 characters long"), + confirmPassword: Yup.string().oneOf([Yup.ref("password"), null], "Passwords must match"), +}); + +export const ResetPasswordForm = ({ token, onSuccess, onFailure }) => ( + { + try { + await accountsService.post("user/recover", { + json: { + token, + password, + confirmPassword, + }, + }); + + onSuccess(); + } catch { + onFailure(); + } + }} + > + {({ errors, touched }) => ( +
+

Set your new password

+ + + +
+ +
+ + )} +
+); + +ResetPasswordForm.propTypes = { + token: PropTypes.string.isRequired, + onFailure: PropTypes.func.isRequired, + onSuccess: PropTypes.func.isRequired, +}; diff --git a/packages/dashboard-v2/src/pages/auth/reset-password.js b/packages/dashboard-v2/src/pages/auth/reset-password.js new file mode 100644 index 00000000..9151f8b4 --- /dev/null +++ b/packages/dashboard-v2/src/pages/auth/reset-password.js @@ -0,0 +1,50 @@ +import { useState } from "react"; + +import AuthLayout from "../../layouts/AuthLayout"; + +import { RecoveryForm } from "../../components/forms/RecoveryForm"; +import HighlightedLink from "../../components/HighlightedLink"; + +const State = { + Pure: "PURE", + Success: "SUCCESS", + Failure: "FAILURE", +}; + +const ResetPasswordPage = () => { + const [state, setState] = useState(State.Pure); + + return ( +
+
+ Skynet +
+ {state !== State.Success && ( + setState(State.Success)} onFailure={() => setState(State.Failure)} /> + )} + + {state === State.Success && ( +

+ Please check your email inbox for further instructions. +

+ )} + + {state === State.Failure && ( +

Something went wrong, please try again later.

+ )} + +
+

+ Suddenly remembered your password? Sign in +

+

+ Don't actually have an account? Create one! +

+
+
+ ); +}; + +ResetPasswordPage.Layout = AuthLayout; + +export default ResetPasswordPage; diff --git a/packages/dashboard-v2/src/pages/user/recover.js b/packages/dashboard-v2/src/pages/user/recover.js new file mode 100644 index 00000000..6f419fdb --- /dev/null +++ b/packages/dashboard-v2/src/pages/user/recover.js @@ -0,0 +1,56 @@ +import { useState } from "react"; +import { navigate } from "gatsby"; + +import AuthLayout from "../../layouts/AuthLayout"; + +import { ResetPasswordForm } from "../../components/forms/ResetPasswordForm"; +import HighlightedLink from "../../components/HighlightedLink"; + +const State = { + Pure: "PURE", + Success: "SUCCESS", + Failure: "FAILURE", +}; + +const RecoverPage = ({ location }) => { + const query = new URLSearchParams(location.search); + const token = query.get("token"); + + const [state, setState] = useState(State.Pure); + + return ( +
+
+ Skynet +
+ {state !== State.Success && ( + { + setState(State.Success); + navigate("/"); + }} + onFailure={() => setState(State.Failure)} + /> + )} + + {state === State.Success && ( +

+ All done! You will be redirected to your dashboard shortly. +

+ )} + + {state === State.Failure && ( +

Something went wrong, please try again later.

+ )} + +

+ Suddenly remembered your old password? Sign in +

+
+ ); +}; + +RecoverPage.Layout = AuthLayout; + +export default RecoverPage;