Introduce account preferences to remember user's previous location (#4126)
This commit is contained in:
@@ -28,6 +28,7 @@ import history from "~/utils/history";
|
|||||||
import {
|
import {
|
||||||
organizationSettingsPath,
|
organizationSettingsPath,
|
||||||
profileSettingsPath,
|
profileSettingsPath,
|
||||||
|
accountPreferencesPath,
|
||||||
homePath,
|
homePath,
|
||||||
searchPath,
|
searchPath,
|
||||||
draftsPath,
|
draftsPath,
|
||||||
@@ -104,6 +105,14 @@ export const navigateToProfileSettings = createAction({
|
|||||||
perform: () => history.push(profileSettingsPath()),
|
perform: () => history.push(profileSettingsPath()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const navigateToAccountPreferences = createAction({
|
||||||
|
name: ({ t }) => t("Preferences"),
|
||||||
|
section: NavigationSection,
|
||||||
|
iconInContextMenu: false,
|
||||||
|
icon: <SettingsIcon />,
|
||||||
|
perform: () => history.push(accountPreferencesPath()),
|
||||||
|
});
|
||||||
|
|
||||||
export const openAPIDocumentation = createAction({
|
export const openAPIDocumentation = createAction({
|
||||||
name: ({ t }) => t("API documentation"),
|
name: ({ t }) => t("API documentation"),
|
||||||
section: NavigationSection,
|
section: NavigationSection,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
BuildingBlocksIcon,
|
BuildingBlocksIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
WebhooksIcon,
|
WebhooksIcon,
|
||||||
|
SettingsIcon,
|
||||||
} from "outline-icons";
|
} from "outline-icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -23,6 +24,7 @@ import Groups from "~/scenes/Settings/Groups";
|
|||||||
import Import from "~/scenes/Settings/Import";
|
import Import from "~/scenes/Settings/Import";
|
||||||
import Members from "~/scenes/Settings/Members";
|
import Members from "~/scenes/Settings/Members";
|
||||||
import Notifications from "~/scenes/Settings/Notifications";
|
import Notifications from "~/scenes/Settings/Notifications";
|
||||||
|
import Preferences from "~/scenes/Settings/Preferences";
|
||||||
import Profile from "~/scenes/Settings/Profile";
|
import Profile from "~/scenes/Settings/Profile";
|
||||||
import Security from "~/scenes/Settings/Security";
|
import Security from "~/scenes/Settings/Security";
|
||||||
import Shares from "~/scenes/Settings/Shares";
|
import Shares from "~/scenes/Settings/Shares";
|
||||||
@@ -34,6 +36,7 @@ import SlackIcon from "~/components/SlackIcon";
|
|||||||
import ZapierIcon from "~/components/ZapierIcon";
|
import ZapierIcon from "~/components/ZapierIcon";
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import isCloudHosted from "~/utils/isCloudHosted";
|
import isCloudHosted from "~/utils/isCloudHosted";
|
||||||
|
import { accountPreferencesPath } from "~/utils/routeHelpers";
|
||||||
import useCurrentTeam from "./useCurrentTeam";
|
import useCurrentTeam from "./useCurrentTeam";
|
||||||
import usePolicy from "./usePolicy";
|
import usePolicy from "./usePolicy";
|
||||||
|
|
||||||
@@ -82,6 +85,14 @@ const useAuthorizedSettingsConfig = () => {
|
|||||||
group: t("Account"),
|
group: t("Account"),
|
||||||
icon: ProfileIcon,
|
icon: ProfileIcon,
|
||||||
},
|
},
|
||||||
|
Preferences: {
|
||||||
|
name: t("Preferences"),
|
||||||
|
path: accountPreferencesPath(),
|
||||||
|
component: Preferences,
|
||||||
|
enabled: true,
|
||||||
|
group: t("Account"),
|
||||||
|
icon: SettingsIcon,
|
||||||
|
},
|
||||||
Notifications: {
|
Notifications: {
|
||||||
name: t("Notifications"),
|
name: t("Notifications"),
|
||||||
path: "/settings/notifications",
|
path: "/settings/notifications",
|
||||||
|
|||||||
14
app/hooks/useLastVisitedPath.tsx
Normal file
14
app/hooks/useLastVisitedPath.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import usePersistedState from "~/hooks/usePersistedState";
|
||||||
|
|
||||||
|
export default function useLastVisitedPath() {
|
||||||
|
const [lastVisitedPath, setLastVisitedPath] = usePersistedState(
|
||||||
|
"lastVisitedPath",
|
||||||
|
"/"
|
||||||
|
);
|
||||||
|
|
||||||
|
const setPathAsLastVisitedPath = (path: string) => {
|
||||||
|
path !== lastVisitedPath && setLastVisitedPath(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [lastVisitedPath, setPathAsLastVisitedPath];
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import ContextMenu from "~/components/ContextMenu";
|
|||||||
import Template from "~/components/ContextMenu/Template";
|
import Template from "~/components/ContextMenu/Template";
|
||||||
import {
|
import {
|
||||||
navigateToProfileSettings,
|
navigateToProfileSettings,
|
||||||
|
navigateToAccountPreferences,
|
||||||
openKeyboardShortcuts,
|
openKeyboardShortcuts,
|
||||||
openChangelog,
|
openChangelog,
|
||||||
openAPIDocumentation,
|
openAPIDocumentation,
|
||||||
@@ -44,6 +45,7 @@ const AccountMenu: React.FC = ({ children }) => {
|
|||||||
openBugReportUrl,
|
openBugReportUrl,
|
||||||
changeTheme,
|
changeTheme,
|
||||||
navigateToProfileSettings,
|
navigateToProfileSettings,
|
||||||
|
navigateToAccountPreferences,
|
||||||
separator(),
|
separator(),
|
||||||
logout,
|
logout,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { subMinutes } from "date-fns";
|
import { subMinutes } from "date-fns";
|
||||||
import { computed, observable } from "mobx";
|
import { computed, observable } from "mobx";
|
||||||
import { now } from "mobx-utils";
|
import { now } from "mobx-utils";
|
||||||
import type { Role } from "@shared/types";
|
import type { Role, UserPreferences } from "@shared/types";
|
||||||
import ParanoidModel from "./ParanoidModel";
|
import ParanoidModel from "./ParanoidModel";
|
||||||
import Field from "./decorators/Field";
|
import Field from "./decorators/Field";
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ class User extends ParanoidModel {
|
|||||||
@observable
|
@observable
|
||||||
language: string;
|
language: string;
|
||||||
|
|
||||||
|
preferences: UserPreferences | null | undefined;
|
||||||
|
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Route,
|
Route,
|
||||||
useHistory,
|
useHistory,
|
||||||
useRouteMatch,
|
useRouteMatch,
|
||||||
|
useLocation,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import breakpoint from "styled-components-breakpoint";
|
import breakpoint from "styled-components-breakpoint";
|
||||||
@@ -30,6 +31,7 @@ import Tabs from "~/components/Tabs";
|
|||||||
import Tooltip from "~/components/Tooltip";
|
import Tooltip from "~/components/Tooltip";
|
||||||
import { editCollection } from "~/actions/definitions/collections";
|
import { editCollection } from "~/actions/definitions/collections";
|
||||||
import useCommandBarActions from "~/hooks/useCommandBarActions";
|
import useCommandBarActions from "~/hooks/useCommandBarActions";
|
||||||
|
import useLastVisitedPath from "~/hooks/useLastVisitedPath";
|
||||||
import usePolicy from "~/hooks/usePolicy";
|
import usePolicy from "~/hooks/usePolicy";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import { collectionUrl, updateCollectionUrl } from "~/utils/routeHelpers";
|
import { collectionUrl, updateCollectionUrl } from "~/utils/routeHelpers";
|
||||||
@@ -42,16 +44,23 @@ function CollectionScene() {
|
|||||||
const params = useParams<{ id?: string }>();
|
const params = useParams<{ id?: string }>();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
|
const location = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { documents, pins, collections, ui } = useStores();
|
const { documents, pins, collections, ui } = useStores();
|
||||||
const [isFetching, setFetching] = React.useState(false);
|
const [isFetching, setFetching] = React.useState(false);
|
||||||
const [error, setError] = React.useState<Error | undefined>();
|
const [error, setError] = React.useState<Error | undefined>();
|
||||||
|
const currentPath = location.pathname;
|
||||||
|
const [, setLastVisitedPath] = useLastVisitedPath();
|
||||||
|
|
||||||
const id = params.id || "";
|
const id = params.id || "";
|
||||||
const collection: Collection | null | undefined =
|
const collection: Collection | null | undefined =
|
||||||
collections.getByUrl(id) || collections.get(id);
|
collections.getByUrl(id) || collections.get(id);
|
||||||
const can = usePolicy(collection?.id || "");
|
const can = usePolicy(collection?.id || "");
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setLastVisitedPath(currentPath);
|
||||||
|
}, [currentPath, setLastVisitedPath]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (collection?.name) {
|
if (collection?.name) {
|
||||||
const canonicalUrl = updateCollectionUrl(match.url, collection);
|
const canonicalUrl = updateCollectionUrl(match.url, collection);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as React from "react";
|
|||||||
import { StaticContext } from "react-router";
|
import { StaticContext } from "react-router";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||||
|
import useLastVisitedPath from "~/hooks/useLastVisitedPath";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import DataLoader from "./components/DataLoader";
|
import DataLoader from "./components/DataLoader";
|
||||||
import Document from "./components/Document";
|
import Document from "./components/Document";
|
||||||
@@ -25,6 +26,12 @@ export default function DocumentScene(props: Props) {
|
|||||||
const { ui } = useStores();
|
const { ui } = useStores();
|
||||||
const team = useCurrentTeam();
|
const team = useCurrentTeam();
|
||||||
const { documentSlug, revisionId } = props.match.params;
|
const { documentSlug, revisionId } = props.match.params;
|
||||||
|
const currentPath = props.location.pathname;
|
||||||
|
const [, setLastVisitedPath] = useLastVisitedPath();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setLastVisitedPath(currentPath);
|
||||||
|
}, [currentPath, setLastVisitedPath]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return () => ui.clearActiveDocument();
|
return () => ui.clearActiveDocument();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import PageTitle from "~/components/PageTitle";
|
|||||||
import TeamLogo from "~/components/TeamLogo";
|
import TeamLogo from "~/components/TeamLogo";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
|
import useLastVisitedPath from "~/hooks/useLastVisitedPath";
|
||||||
import useQuery from "~/hooks/useQuery";
|
import useQuery from "~/hooks/useQuery";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import isCloudHosted from "~/utils/isCloudHosted";
|
import isCloudHosted from "~/utils/isCloudHosted";
|
||||||
@@ -62,6 +63,9 @@ function Login({ children }: Props) {
|
|||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
const [emailLinkSentTo, setEmailLinkSentTo] = React.useState("");
|
const [emailLinkSentTo, setEmailLinkSentTo] = React.useState("");
|
||||||
const isCreate = location.pathname === "/create";
|
const isCreate = location.pathname === "/create";
|
||||||
|
const rememberLastPath = !!auth.user?.preferences?.rememberLastPath;
|
||||||
|
const [lastVisitedPath] = useLastVisitedPath();
|
||||||
|
|
||||||
const handleReset = React.useCallback(() => {
|
const handleReset = React.useCallback(() => {
|
||||||
setEmailLinkSentTo("");
|
setEmailLinkSentTo("");
|
||||||
}, []);
|
}, []);
|
||||||
@@ -91,6 +95,14 @@ function Login({ children }: Props) {
|
|||||||
}
|
}
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
auth.authenticated &&
|
||||||
|
rememberLastPath &&
|
||||||
|
lastVisitedPath !== location.pathname
|
||||||
|
) {
|
||||||
|
return <Redirect to={lastVisitedPath} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (auth.authenticated && auth.team?.defaultCollectionId) {
|
if (auth.authenticated && auth.team?.defaultCollectionId) {
|
||||||
return <Redirect to={`/collection/${auth.team?.defaultCollectionId}`} />;
|
return <Redirect to={`/collection/${auth.team?.defaultCollectionId}`} />;
|
||||||
}
|
}
|
||||||
|
|||||||
57
app/scenes/Settings/Preferences.tsx
Normal file
57
app/scenes/Settings/Preferences.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { SettingsIcon } from "outline-icons";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Heading from "~/components/Heading";
|
||||||
|
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 SettingRow from "./components/SettingRow";
|
||||||
|
|
||||||
|
function Preferences() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { showToast } = useToasts();
|
||||||
|
const { auth } = useStores();
|
||||||
|
const user = useCurrentUser();
|
||||||
|
|
||||||
|
const handleChange = async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newPreferences = {
|
||||||
|
...user.preferences,
|
||||||
|
[ev.target.name]: ev.target.checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
await auth.updateUser({
|
||||||
|
preferences: newPreferences,
|
||||||
|
});
|
||||||
|
showToast(t("Preferences saved"), {
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Scene
|
||||||
|
title={t("Preferences")}
|
||||||
|
icon={<SettingsIcon color="currentColor" />}
|
||||||
|
>
|
||||||
|
<Heading>{t("Preferences")}</Heading>
|
||||||
|
<SettingRow
|
||||||
|
name="rememberLastPath"
|
||||||
|
label={t("Remember previous location")}
|
||||||
|
description={t(
|
||||||
|
"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}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
</Scene>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(Preferences);
|
||||||
@@ -221,6 +221,7 @@ export default class AuthStore {
|
|||||||
name?: string;
|
name?: string;
|
||||||
avatarUrl?: string | null;
|
avatarUrl?: string | null;
|
||||||
language?: string;
|
language?: string;
|
||||||
|
preferences?: Record<string, boolean>;
|
||||||
}) => {
|
}) => {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { sharedDocumentPath } from "./routeHelpers";
|
import { sharedDocumentPath, accountPreferencesPath } from "./routeHelpers";
|
||||||
|
|
||||||
describe("#sharedDocumentPath", () => {
|
describe("#sharedDocumentPath", () => {
|
||||||
test("should return share path for a document", () => {
|
test("should return share path for a document", () => {
|
||||||
@@ -12,3 +12,9 @@ describe("#sharedDocumentPath", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#accountPreferencesPath", () => {
|
||||||
|
test("should return account preferences path", () => {
|
||||||
|
expect(accountPreferencesPath()).toBe("/settings/preferences");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ export function profileSettingsPath(): string {
|
|||||||
return "/settings";
|
return "/settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function accountPreferencesPath(): string {
|
||||||
|
return "/settings/preferences";
|
||||||
|
}
|
||||||
|
|
||||||
export function groupSettingsPath(): string {
|
export function groupSettingsPath(): string {
|
||||||
return "/settings/groups";
|
return "/settings/groups";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ import {
|
|||||||
AllowNull,
|
AllowNull,
|
||||||
} from "sequelize-typescript";
|
} from "sequelize-typescript";
|
||||||
import { languages } from "@shared/i18n";
|
import { languages } from "@shared/i18n";
|
||||||
import { CollectionPermission } from "@shared/types";
|
import {
|
||||||
|
CollectionPermission,
|
||||||
|
UserPreference,
|
||||||
|
UserPreferences,
|
||||||
|
} from "@shared/types";
|
||||||
import { stringToColor } from "@shared/utils/color";
|
import { stringToColor } from "@shared/utils/color";
|
||||||
import env from "@server/env";
|
import env from "@server/env";
|
||||||
import { ValidationError } from "../errors";
|
import { ValidationError } from "../errors";
|
||||||
@@ -54,12 +58,6 @@ export enum UserRole {
|
|||||||
Viewer = "viewer",
|
Viewer = "viewer",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserPreference {
|
|
||||||
RememberLastPath = "rememberLastPath",
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UserPreferences = { [key in UserPreference]?: boolean };
|
|
||||||
|
|
||||||
@Scopes(() => ({
|
@Scopes(() => ({
|
||||||
withAuthentications: {
|
withAuthentications: {
|
||||||
include: [
|
include: [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { UserPreferences } from "@shared/types";
|
||||||
import env from "@server/env";
|
import env from "@server/env";
|
||||||
import { User } from "@server/models";
|
import { User } from "@server/models";
|
||||||
import { UserPreferences } from "@server/models/User";
|
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
includeDetails?: boolean;
|
includeDetails?: boolean;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
|
import { has } from "lodash";
|
||||||
import { Op, WhereOptions } from "sequelize";
|
import { Op, WhereOptions } from "sequelize";
|
||||||
|
import { UserPreference } from "@shared/types";
|
||||||
import { UserValidation } from "@shared/validations";
|
import { UserValidation } from "@shared/validations";
|
||||||
import { RateLimiterStrategy } from "@server/RateLimiter";
|
import { RateLimiterStrategy } from "@server/RateLimiter";
|
||||||
import userDemoter from "@server/commands/userDemoter";
|
import userDemoter from "@server/commands/userDemoter";
|
||||||
@@ -17,7 +19,7 @@ import logger from "@server/logging/Logger";
|
|||||||
import auth from "@server/middlewares/authentication";
|
import auth from "@server/middlewares/authentication";
|
||||||
import { rateLimiter } from "@server/middlewares/rateLimiter";
|
import { rateLimiter } from "@server/middlewares/rateLimiter";
|
||||||
import { Event, User, Team } from "@server/models";
|
import { Event, User, Team } from "@server/models";
|
||||||
import { UserFlag, UserRole, UserPreference } from "@server/models/User";
|
import { UserFlag, UserRole } from "@server/models/User";
|
||||||
import { can, authorize } from "@server/policies";
|
import { can, authorize } from "@server/policies";
|
||||||
import { presentUser, presentPolicies } from "@server/presenters";
|
import { presentUser, presentPolicies } from "@server/presenters";
|
||||||
import {
|
import {
|
||||||
@@ -188,7 +190,7 @@ router.post("users.update", auth(), async (ctx) => {
|
|||||||
}
|
}
|
||||||
if (preferences) {
|
if (preferences) {
|
||||||
assertKeysIn(preferences, UserPreference);
|
assertKeysIn(preferences, UserPreference);
|
||||||
if (preferences.rememberLastPath) {
|
if (has(preferences, UserPreference.RememberLastPath)) {
|
||||||
assertBoolean(preferences.rememberLastPath);
|
assertBoolean(preferences.rememberLastPath);
|
||||||
user.setPreference(
|
user.setPreference(
|
||||||
UserPreference.RememberLastPath,
|
UserPreference.RememberLastPath,
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
"Trash": "Trash",
|
"Trash": "Trash",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"Profile": "Profile",
|
"Profile": "Profile",
|
||||||
|
"Preferences": "Preferences",
|
||||||
"API documentation": "API documentation",
|
"API documentation": "API documentation",
|
||||||
"Send us feedback": "Send us feedback",
|
"Send us feedback": "Send us feedback",
|
||||||
"Report a bug": "Report a bug",
|
"Report a bug": "Report a bug",
|
||||||
@@ -683,6 +684,9 @@
|
|||||||
"Email address": "Email address",
|
"Email address": "Email address",
|
||||||
"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",
|
||||||
|
"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",
|
||||||
"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",
|
||||||
|
|||||||
@@ -43,3 +43,9 @@ export type IntegrationSettings<T> = T extends IntegrationType.Embed
|
|||||||
| { url: string }
|
| { url: string }
|
||||||
| { url: string; channel: string; channelId: string }
|
| { url: string; channel: string; channelId: string }
|
||||||
| { serviceTeamId: string };
|
| { serviceTeamId: string };
|
||||||
|
|
||||||
|
export enum UserPreference {
|
||||||
|
RememberLastPath = "rememberLastPath",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserPreferences = { [key in UserPreference]?: boolean };
|
||||||
|
|||||||
Reference in New Issue
Block a user