chore: Move language and account delete from Profile -> Preferences

This commit is contained in:
Tom Moor
2022-09-18 16:43:18 -04:00
parent 6502b108e3
commit d16a0365d7
4 changed files with 132 additions and 140 deletions

View File

@@ -1,55 +1,119 @@
import { observer } from "mobx-react";
import { SettingsIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Trans, useTranslation } from "react-i18next";
import { languageOptions } from "@shared/i18n";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import InputSelect from "~/components/InputSelect";
import Scene from "~/components/Scene";
import Switch from "~/components/Switch";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import UserDelete from "../UserDelete";
import SettingRow from "./components/SettingRow";
function Preferences() {
const { t } = useTranslation();
const { showToast } = useToasts();
const { auth } = useStores();
const { dialogs, auth } = useStores();
const user = useCurrentUser();
const handleChange = async (ev: React.ChangeEvent<HTMLInputElement>) => {
const newPreferences = {
const handlePreferenceChange = async (
ev: React.ChangeEvent<HTMLInputElement>
) => {
const preferences = {
...user.preferences,
[ev.target.name]: ev.target.checked,
};
await auth.updateUser({
preferences: newPreferences,
});
await auth.updateUser({ preferences });
showToast(t("Preferences saved"), {
type: "success",
});
};
const handleLanguageChange = async (language: string) => {
await auth.updateUser({ language });
showToast(t("Preferences saved"), {
type: "success",
});
};
const showDeleteAccount = () => {
dialogs.openModal({
title: t("Delete account"),
content: <UserDelete />,
});
};
return (
<Scene
title={t("Preferences")}
icon={<SettingsIcon color="currentColor" />}
>
<Heading>{t("Preferences")}</Heading>
<SettingRow
label={t("Language")}
name="language"
description={
<>
<Trans>
Choose the interface language. Community translations are accepted
though our{" "}
<a
href="https://translate.getoutline.com"
target="_blank"
rel="noreferrer"
>
translation portal
</a>
.
</Trans>
</>
}
>
<InputSelect
id="language"
options={languageOptions}
value={user.language}
onChange={handleLanguageChange}
ariaLabel={t("Language")}
/>
</SettingRow>
<SettingRow
border={false}
name="rememberLastPath"
label={t("Remember previous location")}
description={t(
"Automatically return to the document you were last viewing when the app is re-opened"
"Automatically return to the document you were last viewing when the app is re-opened."
)}
>
<Switch
id="rememberLastPath"
name="rememberLastPath"
checked={!!user.preferences?.rememberLastPath}
onChange={handleChange}
onChange={handlePreferenceChange}
/>
</SettingRow>
<p>&nbsp;</p>
<SettingRow
name="delete"
label={t("Delete account")}
description={t(
"You may delete your account at any time, note that this is unrecoverable"
)}
>
<span>
<Button onClick={showDeleteAccount} neutral>
{t("Delete account")}
</Button>
</span>
</SettingRow>
</Scene>
);
}

View File

@@ -1,16 +1,11 @@
import { observer } from "mobx-react";
import { ProfileIcon } from "outline-icons";
import * as React from "react";
import { Trans, useTranslation } from "react-i18next";
import styled from "styled-components";
import { languageOptions } from "@shared/i18n";
import UserDelete from "~/scenes/UserDelete";
import { useTranslation } from "react-i18next";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import Input from "~/components/Input";
import InputSelect from "~/components/InputSelect";
import Scene from "~/components/Scene";
import Text from "~/components/Text";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
@@ -23,8 +18,6 @@ const Profile = () => {
const form = React.useRef<HTMLFormElement>(null);
const [name, setName] = React.useState<string>(user.name || "");
const [avatarUrl, setAvatarUrl] = React.useState<string>(user.avatarUrl);
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
const [language, setLanguage] = React.useState(user.language);
const { showToast } = useToasts();
const { t } = useTranslation();
@@ -35,7 +28,6 @@ const Profile = () => {
await auth.updateUser({
name,
avatarUrl,
language,
});
showToast(t("Profile saved"), {
type: "success",
@@ -67,14 +59,6 @@ const Profile = () => {
});
};
const handleLanguageChange = (value: string) => {
setLanguage(value);
};
const toggleDeleteAccount = () => {
setShowDeleteModal((prev) => !prev);
};
const isValid = form.current?.checkValidity();
const { isSaving } = auth;
@@ -95,6 +79,7 @@ const Profile = () => {
/>
</SettingRow>
<SettingRow
border={false}
label={t("Full name")}
name="name"
description={t(
@@ -110,60 +95,12 @@ const Profile = () => {
/>
</SettingRow>
<SettingRow
border={false}
label={t("Language")}
name="language"
description={
<>
<Trans>
Please note that translations are currently in early access.
Community contributions are accepted though our{" "}
<a
href="https://translate.getoutline.com"
target="_blank"
rel="noreferrer"
>
translation portal
</a>
.
</Trans>
</>
}
>
<InputSelect
id="language"
options={languageOptions}
value={language}
onChange={handleLanguageChange}
ariaLabel={t("Language")}
/>
</SettingRow>
<Button type="submit" disabled={isSaving || !isValid}>
{isSaving ? `${t("Saving")}` : t("Save")}
</Button>
</form>
<DangerZone>
<h2>{t("Delete Account")}</h2>
<Text type="secondary">
<Trans>
You may delete your account at any time, note that this is
unrecoverable
</Trans>
</Text>
<Button onClick={toggleDeleteAccount} neutral>
{t("Delete account")}
</Button>
</DangerZone>
{showDeleteModal && <UserDelete onRequestClose={toggleDeleteAccount} />}
</Scene>
);
};
const DangerZone = styled.div`
margin-top: 60px;
`;
export default observer(Profile);

View File

@@ -5,7 +5,6 @@ import { useTranslation, Trans } from "react-i18next";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import { ReactHookWrappedInput as Input } from "~/components/Input";
import Modal from "~/components/Modal";
import Text from "~/components/Text";
import env from "~/env";
import useStores from "~/hooks/useStores";
@@ -15,11 +14,7 @@ type FormData = {
code: string;
};
type Props = {
onRequestClose: () => void;
};
function UserDelete({ onRequestClose }: Props) {
function UserDelete() {
const [isWaitingCode, setWaitingCode] = React.useState(false);
const { auth } = useStores();
const { showToast } = useToasts();
@@ -63,61 +58,58 @@ function UserDelete({ onRequestClose }: Props) {
});
return (
<Modal isOpen title={t("Delete Account")} onRequestClose={onRequestClose}>
<Flex column>
<form onSubmit={formHandleSubmit(handleSubmit)}>
{isWaitingCode ? (
<>
<Text type="secondary">
<Trans>
A confirmation code has been sent to your email address,
please enter the code below to permanantly destroy your
account.
</Trans>
</Text>
<Text type="secondary">
<Trans
defaults="<em>Note:</em> Signing back in will cause a new account to be automatically reprovisioned."
components={{
em: <strong />,
}}
/>
</Text>
<Input
placeholder="CODE"
autoComplete="off"
autoFocus
maxLength={8}
required
{...inputProps}
<Flex column>
<form onSubmit={formHandleSubmit(handleSubmit)}>
{isWaitingCode ? (
<>
<Text type="secondary">
<Trans>
A confirmation code has been sent to your email address, please
enter the code below to permanantly destroy your account.
</Trans>
</Text>
<Text type="secondary">
<Trans
defaults="<em>Note:</em> Signing back in will cause a new account to be automatically reprovisioned."
components={{
em: <strong />,
}}
/>
</>
) : (
<>
<Text type="secondary">
<Trans>
Are you sure? Deleting your account will destroy identifying
data associated with your user and cannot be undone. You will
be immediately logged out of Outline and all your API tokens
will be revoked.
</Trans>
</Text>
</>
)}
{env.EMAIL_ENABLED && !isWaitingCode ? (
<Button type="submit" onClick={handleRequestDelete} neutral>
{t("Continue")}
</Button>
) : (
<Button type="submit" disabled={formState.isSubmitting} danger>
{formState.isSubmitting
? `${t("Deleting")}`
: t("Delete My Account")}
</Button>
)}
</form>
</Flex>
</Modal>
</Text>
<Input
placeholder="CODE"
autoComplete="off"
autoFocus
maxLength={8}
required
{...inputProps}
/>
</>
) : (
<>
<Text type="secondary">
<Trans>
Are you sure? Deleting your account will destroy identifying
data associated with your user and cannot be undone. You will be
immediately logged out of Outline and all your API tokens will
be revoked.
</Trans>
</Text>
</>
)}
{env.EMAIL_ENABLED && !isWaitingCode ? (
<Button type="submit" onClick={handleRequestDelete} neutral>
{t("Continue")}
</Button>
) : (
<Button type="submit" disabled={formState.isSubmitting} danger>
{formState.isSubmitting
? `${t("Deleting")}`
: t("Delete My Account")}
</Button>
)}
</form>
</Flex>
);
}

View File

@@ -685,19 +685,18 @@
"Your email address should be updated in your SSO provider.": "Your email address should be updated in your SSO provider.",
"The email integration is currently disabled. Please set the associated environment variables and restart the server to enable notifications.": "The email integration is currently disabled. Please set the associated environment variables and restart the server to enable notifications.",
"Preferences saved": "Preferences saved",
"Delete account": "Delete account",
"Language": "Language",
"Choose the interface language. Community translations are accepted though our <2>translation portal</2>.": "Choose the interface language. Community translations are accepted though our <2>translation portal</2>.",
"Remember previous location": "Remember previous location",
"Automatically return to the document you were last viewing when the app is re-opened": "Automatically return to the document you were last viewing when the app is re-opened",
"Automatically return to the document you were last viewing when the app is re-opened.": "Automatically return to the document you were last viewing when the app is re-opened.",
"You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",
"Profile saved": "Profile saved",
"Profile picture updated": "Profile picture updated",
"Unable to upload new profile picture": "Unable to upload new profile picture",
"Photo": "Photo",
"Choose a photo or image to represent yourself.": "Choose a photo or image to represent yourself.",
"This could be your real name, or a nickname — however youd like people to refer to you.": "This could be your real name, or a nickname — however youd like people to refer to you.",
"Language": "Language",
"Please note that translations are currently in early access. Community contributions are accepted though our <2>translation portal</2>.": "Please note that translations are currently in early access. Community contributions are accepted though our <2>translation portal</2>.",
"Delete Account": "Delete Account",
"You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",
"Delete account": "Delete account",
"Are you sure you want to require invites?": "Are you sure you want to require invites?",
"Im sure": "Im sure",
"New users will first need to be invited to create an account. <em>Default role</em> and <em>Allowed domains</em> will no longer apply.": "New users will first need to be invited to create an account. <em>Default role</em> and <em>Allowed domains</em> will no longer apply.",