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 }) => (
+
+ )}
+
+);
+
+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 }) => (
+
+ )}
+
+);
+
+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 (
+
+
+
+
+ {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 (
+
+
+
+
+ {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;