chore: Move language and account delete from Profile -> Preferences
This commit is contained in:
@@ -1,55 +1,119 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { SettingsIcon } from "outline-icons";
|
import { SettingsIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
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 Heading from "~/components/Heading";
|
||||||
|
import InputSelect from "~/components/InputSelect";
|
||||||
import Scene from "~/components/Scene";
|
import Scene from "~/components/Scene";
|
||||||
import Switch from "~/components/Switch";
|
import Switch from "~/components/Switch";
|
||||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import useToasts from "~/hooks/useToasts";
|
import useToasts from "~/hooks/useToasts";
|
||||||
|
import UserDelete from "../UserDelete";
|
||||||
import SettingRow from "./components/SettingRow";
|
import SettingRow from "./components/SettingRow";
|
||||||
|
|
||||||
function Preferences() {
|
function Preferences() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const { auth } = useStores();
|
const { dialogs, auth } = useStores();
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
|
|
||||||
const handleChange = async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
const handlePreferenceChange = async (
|
||||||
const newPreferences = {
|
ev: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const preferences = {
|
||||||
...user.preferences,
|
...user.preferences,
|
||||||
[ev.target.name]: ev.target.checked,
|
[ev.target.name]: ev.target.checked,
|
||||||
};
|
};
|
||||||
|
|
||||||
await auth.updateUser({
|
await auth.updateUser({ preferences });
|
||||||
preferences: newPreferences,
|
|
||||||
});
|
|
||||||
showToast(t("Preferences saved"), {
|
showToast(t("Preferences saved"), {
|
||||||
type: "success",
|
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 (
|
return (
|
||||||
<Scene
|
<Scene
|
||||||
title={t("Preferences")}
|
title={t("Preferences")}
|
||||||
icon={<SettingsIcon color="currentColor" />}
|
icon={<SettingsIcon color="currentColor" />}
|
||||||
>
|
>
|
||||||
<Heading>{t("Preferences")}</Heading>
|
<Heading>{t("Preferences")}</Heading>
|
||||||
|
|
||||||
<SettingRow
|
<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"
|
name="rememberLastPath"
|
||||||
label={t("Remember previous location")}
|
label={t("Remember previous location")}
|
||||||
description={t(
|
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
|
<Switch
|
||||||
id="rememberLastPath"
|
id="rememberLastPath"
|
||||||
name="rememberLastPath"
|
name="rememberLastPath"
|
||||||
checked={!!user.preferences?.rememberLastPath}
|
checked={!!user.preferences?.rememberLastPath}
|
||||||
onChange={handleChange}
|
onChange={handlePreferenceChange}
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
|
<p> </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>
|
</Scene>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ProfileIcon } from "outline-icons";
|
import { ProfileIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styled from "styled-components";
|
|
||||||
import { languageOptions } from "@shared/i18n";
|
|
||||||
import UserDelete from "~/scenes/UserDelete";
|
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Heading from "~/components/Heading";
|
import Heading from "~/components/Heading";
|
||||||
import Input from "~/components/Input";
|
import Input from "~/components/Input";
|
||||||
import InputSelect from "~/components/InputSelect";
|
|
||||||
import Scene from "~/components/Scene";
|
import Scene from "~/components/Scene";
|
||||||
import Text from "~/components/Text";
|
|
||||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import useToasts from "~/hooks/useToasts";
|
import useToasts from "~/hooks/useToasts";
|
||||||
@@ -23,8 +18,6 @@ const Profile = () => {
|
|||||||
const form = React.useRef<HTMLFormElement>(null);
|
const form = React.useRef<HTMLFormElement>(null);
|
||||||
const [name, setName] = React.useState<string>(user.name || "");
|
const [name, setName] = React.useState<string>(user.name || "");
|
||||||
const [avatarUrl, setAvatarUrl] = React.useState<string>(user.avatarUrl);
|
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 { showToast } = useToasts();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -35,7 +28,6 @@ const Profile = () => {
|
|||||||
await auth.updateUser({
|
await auth.updateUser({
|
||||||
name,
|
name,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
language,
|
|
||||||
});
|
});
|
||||||
showToast(t("Profile saved"), {
|
showToast(t("Profile saved"), {
|
||||||
type: "success",
|
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 isValid = form.current?.checkValidity();
|
||||||
const { isSaving } = auth;
|
const { isSaving } = auth;
|
||||||
|
|
||||||
@@ -95,6 +79,7 @@ const Profile = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
|
border={false}
|
||||||
label={t("Full name")}
|
label={t("Full name")}
|
||||||
name="name"
|
name="name"
|
||||||
description={t(
|
description={t(
|
||||||
@@ -110,60 +95,12 @@ const Profile = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</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}>
|
<Button type="submit" disabled={isSaving || !isValid}>
|
||||||
{isSaving ? `${t("Saving")}…` : t("Save")}
|
{isSaving ? `${t("Saving")}…` : t("Save")}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</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>
|
</Scene>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DangerZone = styled.div`
|
|
||||||
margin-top: 60px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default observer(Profile);
|
export default observer(Profile);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useTranslation, Trans } from "react-i18next";
|
|||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import { ReactHookWrappedInput as Input } from "~/components/Input";
|
import { ReactHookWrappedInput as Input } from "~/components/Input";
|
||||||
import Modal from "~/components/Modal";
|
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
@@ -15,11 +14,7 @@ type FormData = {
|
|||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
function UserDelete() {
|
||||||
onRequestClose: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
function UserDelete({ onRequestClose }: Props) {
|
|
||||||
const [isWaitingCode, setWaitingCode] = React.useState(false);
|
const [isWaitingCode, setWaitingCode] = React.useState(false);
|
||||||
const { auth } = useStores();
|
const { auth } = useStores();
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
@@ -63,61 +58,58 @@ function UserDelete({ onRequestClose }: Props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen title={t("Delete Account")} onRequestClose={onRequestClose}>
|
<Flex column>
|
||||||
<Flex column>
|
<form onSubmit={formHandleSubmit(handleSubmit)}>
|
||||||
<form onSubmit={formHandleSubmit(handleSubmit)}>
|
{isWaitingCode ? (
|
||||||
{isWaitingCode ? (
|
<>
|
||||||
<>
|
<Text type="secondary">
|
||||||
<Text type="secondary">
|
<Trans>
|
||||||
<Trans>
|
A confirmation code has been sent to your email address, please
|
||||||
A confirmation code has been sent to your email address,
|
enter the code below to permanantly destroy your account.
|
||||||
please enter the code below to permanantly destroy your
|
</Trans>
|
||||||
account.
|
</Text>
|
||||||
</Trans>
|
<Text type="secondary">
|
||||||
</Text>
|
<Trans
|
||||||
<Text type="secondary">
|
defaults="<em>Note:</em> Signing back in will cause a new account to be automatically reprovisioned."
|
||||||
<Trans
|
components={{
|
||||||
defaults="<em>Note:</em> Signing back in will cause a new account to be automatically reprovisioned."
|
em: <strong />,
|
||||||
components={{
|
}}
|
||||||
em: <strong />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Text>
|
|
||||||
<Input
|
|
||||||
placeholder="CODE"
|
|
||||||
autoComplete="off"
|
|
||||||
autoFocus
|
|
||||||
maxLength={8}
|
|
||||||
required
|
|
||||||
{...inputProps}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</Text>
|
||||||
) : (
|
<Input
|
||||||
<>
|
placeholder="CODE"
|
||||||
<Text type="secondary">
|
autoComplete="off"
|
||||||
<Trans>
|
autoFocus
|
||||||
Are you sure? Deleting your account will destroy identifying
|
maxLength={8}
|
||||||
data associated with your user and cannot be undone. You will
|
required
|
||||||
be immediately logged out of Outline and all your API tokens
|
{...inputProps}
|
||||||
will be revoked.
|
/>
|
||||||
</Trans>
|
</>
|
||||||
</Text>
|
) : (
|
||||||
</>
|
<>
|
||||||
)}
|
<Text type="secondary">
|
||||||
{env.EMAIL_ENABLED && !isWaitingCode ? (
|
<Trans>
|
||||||
<Button type="submit" onClick={handleRequestDelete} neutral>
|
Are you sure? Deleting your account will destroy identifying
|
||||||
{t("Continue")}…
|
data associated with your user and cannot be undone. You will be
|
||||||
</Button>
|
immediately logged out of Outline and all your API tokens will
|
||||||
) : (
|
be revoked.
|
||||||
<Button type="submit" disabled={formState.isSubmitting} danger>
|
</Trans>
|
||||||
{formState.isSubmitting
|
</Text>
|
||||||
? `${t("Deleting")}…`
|
</>
|
||||||
: t("Delete My Account")}
|
)}
|
||||||
</Button>
|
{env.EMAIL_ENABLED && !isWaitingCode ? (
|
||||||
)}
|
<Button type="submit" onClick={handleRequestDelete} neutral>
|
||||||
</form>
|
{t("Continue")}…
|
||||||
</Flex>
|
</Button>
|
||||||
</Modal>
|
) : (
|
||||||
|
<Button type="submit" disabled={formState.isSubmitting} danger>
|
||||||
|
{formState.isSubmitting
|
||||||
|
? `${t("Deleting")}…`
|
||||||
|
: t("Delete My Account")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -685,19 +685,18 @@
|
|||||||
"Your email address should be updated in your SSO provider.": "Your email address should be updated in your SSO provider.",
|
"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.",
|
"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",
|
"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",
|
"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 saved": "Profile saved",
|
||||||
"Profile picture updated": "Profile picture updated",
|
"Profile picture updated": "Profile picture updated",
|
||||||
"Unable to upload new profile picture": "Unable to upload new profile picture",
|
"Unable to upload new profile picture": "Unable to upload new profile picture",
|
||||||
"Photo": "Photo",
|
"Photo": "Photo",
|
||||||
"Choose a photo or image to represent yourself.": "Choose a photo or image to represent yourself.",
|
"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 you’d like people to refer to you.": "This could be your real name, or a nickname — however you’d like people to refer to you.",
|
"This could be your real name, or a nickname — however you’d like people to refer to you.": "This could be your real name, or a nickname — however you’d 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?",
|
"Are you sure you want to require invites?": "Are you sure you want to require invites?",
|
||||||
"I’m sure": "I’m sure",
|
"I’m sure": "I’m 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.",
|
"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.",
|
||||||
|
|||||||
Reference in New Issue
Block a user