diff --git a/packages/dashboard-v2/src/components/forms/AccountRemovalForm.js b/packages/dashboard-v2/src/components/forms/AccountRemovalForm.js
new file mode 100644
index 00000000..bdd7196e
--- /dev/null
+++ b/packages/dashboard-v2/src/components/forms/AccountRemovalForm.js
@@ -0,0 +1,78 @@
+import * as Yup from "yup";
+import { useState } from "react";
+import PropTypes from "prop-types";
+import { Formik, Form } from "formik";
+
+import { Button } from "../Button";
+import { TextField } from "../Form/TextField";
+import accountsService from "../../services/accountsService";
+
+const accountRemovalSchema = Yup.object().shape({
+ confirm: Yup.string().oneOf(["delete"], `Type "delete" to confirm`),
+});
+
+export const AccountRemovalForm = ({ abort, onSuccess }) => {
+ const [error, setError] = useState(false);
+
+ return (
+ {
+ try {
+ setError(false);
+ await accountsService.delete("user");
+ onSuccess();
+ } catch {
+ setError(true);
+ }
+ }}
+ >
+ {({ errors, touched, isValid, dirty }) => (
+
+ )}
+
+ );
+};
+
+AccountRemovalForm.propTypes = {
+ abort: PropTypes.func.isRequired,
+ onSuccess: PropTypes.func.isRequired,
+};
diff --git a/packages/dashboard-v2/src/components/forms/AccountSettingsForm.js b/packages/dashboard-v2/src/components/forms/AccountSettingsForm.js
new file mode 100644
index 00000000..f979dd31
--- /dev/null
+++ b/packages/dashboard-v2/src/components/forms/AccountSettingsForm.js
@@ -0,0 +1,106 @@
+import * as Yup from "yup";
+import PropTypes from "prop-types";
+import { Formik, Form } from "formik";
+
+import { Button } from "../Button";
+import { TextField } from "../Form/TextField";
+import accountsService from "../../services/accountsService";
+
+const isPopulated = (value) => value?.length > 0;
+
+const emailUpdateSchema = Yup.object().shape({
+ email: Yup.string().email("Please provide a valid email address"),
+ confirmEmail: Yup.string()
+ .oneOf([Yup.ref("email"), null], "Emails must match")
+ .when("email", {
+ is: isPopulated,
+ then: (schema) => schema.required("Please confirm new email address"),
+ }),
+ password: Yup.string().min(6, "Password has to be at least 6 characters long"),
+ confirmPassword: Yup.string()
+ .oneOf([Yup.ref("password"), null], "Passwords must match")
+ .when("password", {
+ is: isPopulated,
+ then: (schema) => schema.required("Please confirm new password"),
+ }),
+});
+
+export const AccountSettingsForm = ({ user, onSuccess, onFailure }) => {
+ return (
+ {
+ try {
+ await accountsService.put("user", {
+ json: { email, password },
+ });
+
+ resetForm();
+ onSuccess();
+ } catch {
+ onFailure();
+ }
+ }}
+ >
+ {({ errors, touched, isValid, dirty }) => (
+
+ )}
+
+ );
+};
+
+AccountSettingsForm.propTypes = {
+ onSuccess: PropTypes.func.isRequired,
+ onFailure: PropTypes.func.isRequired,
+};
diff --git a/packages/dashboard-v2/src/pages/settings/index.js b/packages/dashboard-v2/src/pages/settings/index.js
index 459cf8c0..41ccf6b4 100644
--- a/packages/dashboard-v2/src/pages/settings/index.js
+++ b/packages/dashboard-v2/src/pages/settings/index.js
@@ -1,19 +1,31 @@
-import * as React from "react";
+import { useState } from "react";
import { useMedia } from "react-use";
-import styled from "styled-components";
+import { navigate } from "gatsby";
+import { useUser } from "../../contexts/user";
import theme from "../../lib/theme";
import UserSettingsLayout from "../../layouts/UserSettingsLayout";
-import { TextInputBasic } from "../../components/TextInputBasic/TextInputBasic";
-import { Button } from "../../components/Button";
import { AvatarUploader } from "../../components/AvatarUploader";
+import { AccountSettingsForm } from "../../components/forms/AccountSettingsForm";
+import { Modal } from "../../components/Modal/Modal";
+import { AccountRemovalForm } from "../../components/forms/AccountRemovalForm";
+import { Alert } from "../../components/Alert";
-const FormGroup = styled.div.attrs({
- className: "grid sm:grid-cols-[1fr_min-content] w-full gap-y-2 gap-x-4 items-end",
-})``;
+const State = {
+ Pure: "PURE",
+ Success: "SUCCESS",
+ Failure: "FAILURE",
+};
const AccountPage = () => {
const isLargeScreen = useMedia(`(min-width: ${theme.screens.xl})`);
+ const { user, mutate: reloadUser } = useUser();
+ const [state, setState] = useState(State.Pure);
+ const [removalInitiated, setRemovalInitiated] = useState(false);
+
+ const prompt = () => setRemovalInitiated(true);
+ const abort = () => setRemovalInitiated(false);
+
return (
<>
@@ -32,28 +44,18 @@ const AccountPage = () => {
)}
-
-
-
- Update
-
-
-
-
-
- Update
-
-
-
-
-
- Update
-
-
- The password must be at least 6 characters long. Significantly different from the email and old
- password.
-
-
+ {state === State.Failure && (
+ There was an error processing your request. Please try again later.
+ )}
+ {state === State.Success && Changes saved successfully. }
+ {
+ reloadUser();
+ setState(State.Success);
+ }}
+ onFailure={() => setState(State.Failure)}
+ />
@@ -61,7 +63,7 @@ const AccountPage = () => {
This will completely delete your account. This process can't be undone.
window.confirm("TODO: confirmation modal")}
+ onClick={prompt}
className="text-error underline decoration-1 hover:decoration-dashed"
>
Delete account
@@ -71,6 +73,11 @@ const AccountPage = () => {
+ {removalInitiated && (
+
+ navigate("/auth/login")} />
+
+ )}
>
);