diff --git a/app/components/Modal.tsx b/app/components/Modal.tsx
index 1767905b9..99e392547 100644
--- a/app/components/Modal.tsx
+++ b/app/components/Modal.tsx
@@ -252,8 +252,9 @@ const Small = styled.div`
animation: ${fadeAndScaleIn} 250ms ease;
margin: auto auto;
+ width: 30vw;
min-width: 350px;
- max-width: 30vw;
+ max-width: 450px;
z-index: ${depths.modal};
display: flex;
justify-content: center;
diff --git a/app/components/UserRoleDialogs.tsx b/app/components/UserRoleDialogs.tsx
new file mode 100644
index 000000000..6ead42f0e
--- /dev/null
+++ b/app/components/UserRoleDialogs.tsx
@@ -0,0 +1,108 @@
+import * as React from "react";
+import { useTranslation } from "react-i18next";
+import User from "~/models/User";
+import ConfirmationDialog from "~/components/ConfirmationDialog";
+import useStores from "~/hooks/useStores";
+
+type Props = {
+ user: User;
+ onSubmit: () => void;
+};
+
+export function UserChangeToViewerDialog({ user, onSubmit }: Props) {
+ const { t } = useTranslation();
+ const { users } = useStores();
+
+ const handleSubmit = async () => {
+ await users.demote(user, "viewer");
+ onSubmit();
+ };
+
+ return (
+
+ {t(
+ "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content",
+ {
+ userName: user.name,
+ }
+ )}
+ .
+
+ );
+}
+
+export function UserChangeToMemberDialog({ user, onSubmit }: Props) {
+ const { t } = useTranslation();
+ const { users } = useStores();
+
+ const handleSubmit = async () => {
+ await users.demote(user, "member");
+ onSubmit();
+ };
+
+ return (
+
+ {t("Are you sure you want to make {{ userName }} a member?", {
+ userName: user.name,
+ })}
+
+ );
+}
+
+export function UserChangeToAdminDialog({ user, onSubmit }: Props) {
+ const { t } = useTranslation();
+ const { users } = useStores();
+
+ const handleSubmit = async () => {
+ await users.promote(user);
+ onSubmit();
+ };
+
+ return (
+
+ {t(
+ "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
+ {
+ userName: user.name,
+ }
+ )}
+
+ );
+}
+
+export function UserSuspendDialog({ user, onSubmit }: Props) {
+ const { t } = useTranslation();
+ const { users } = useStores();
+
+ const handleSubmit = async () => {
+ await users.suspend(user);
+ onSubmit();
+ };
+
+ return (
+
+ {t(
+ "Are you sure you want to suspend {{ userName }}? Suspended users will be prevented from logging in.",
+ {
+ userName: user.name,
+ }
+ )}
+
+ );
+}
diff --git a/app/menus/UserMenu.tsx b/app/menus/UserMenu.tsx
index 95048d287..3d18457f0 100644
--- a/app/menus/UserMenu.tsx
+++ b/app/menus/UserMenu.tsx
@@ -6,6 +6,12 @@ import User from "~/models/User";
import ContextMenu from "~/components/ContextMenu";
import OverflowMenuButton from "~/components/ContextMenu/OverflowMenuButton";
import Template from "~/components/ContextMenu/Template";
+import {
+ UserChangeToAdminDialog,
+ UserChangeToMemberDialog,
+ UserChangeToViewerDialog,
+ UserSuspendDialog,
+} from "~/components/UserRoleDialogs";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
@@ -15,7 +21,7 @@ type Props = {
};
function UserMenu({ user }: Props) {
- const { users } = useStores();
+ const { users, dialogs } = useStores();
const { t } = useTranslation();
const menu = useMenuState({
modal: true,
@@ -26,83 +32,66 @@ function UserMenu({ user }: Props) {
const handlePromote = React.useCallback(
(ev: React.SyntheticEvent) => {
ev.preventDefault();
-
- if (
- !window.confirm(
- t(
- "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
- {
- userName: user.name,
- }
- )
- )
- ) {
- return;
- }
-
- users.promote(user);
+ dialogs.openModal({
+ title: t("Change role to admin"),
+ isCentered: true,
+ content: (
+
+ ),
+ });
},
- [users, user, t]
+ [dialogs, t, user]
);
const handleMember = React.useCallback(
(ev: React.SyntheticEvent) => {
ev.preventDefault();
-
- if (
- !window.confirm(
- t("Are you sure you want to make {{ userName }} a member?", {
- userName: user.name,
- })
- )
- ) {
- return;
- }
-
- users.demote(user, "member");
+ dialogs.openModal({
+ title: t("Change role to member"),
+ isCentered: true,
+ content: (
+
+ ),
+ });
},
- [users, user, t]
+ [dialogs, t, user]
);
const handleViewer = React.useCallback(
(ev: React.SyntheticEvent) => {
ev.preventDefault();
-
- if (
- !window.confirm(
- t(
- "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content",
- {
- userName: user.name,
- }
- )
- )
- ) {
- return;
- }
-
- users.demote(user, "viewer");
+ dialogs.openModal({
+ title: t("Change role to viewer"),
+ isCentered: true,
+ content: (
+
+ ),
+ });
},
- [users, user, t]
+ [dialogs, t, user]
);
const handleSuspend = React.useCallback(
(ev: React.SyntheticEvent) => {
ev.preventDefault();
-
- if (
- !window.confirm(
- t(
- "Are you sure you want to suspend this account? Suspended users will be prevented from logging in."
- )
- )
- ) {
- return;
- }
-
- users.suspend(user);
+ dialogs.openModal({
+ title: t("Suspend account"),
+ isCentered: true,
+ content: (
+
+ ),
+ });
},
- [users, user, t]
+ [dialogs, t, user]
);
const handleRevoke = React.useCallback(
@@ -149,25 +138,19 @@ function UserMenu({ user }: Props) {
items={[
{
type: "button",
- title: t("Make {{ userName }} a member", {
- userName: user.name,
- }),
+ title: `${t("Change role to member")}…`,
onClick: handleMember,
visible: can.demote && user.role !== "member",
},
{
type: "button",
- title: t("Make {{ userName }} a viewer", {
- userName: user.name,
- }),
+ title: `${t("Change role to viewer")}…`,
onClick: handleViewer,
visible: can.demote && user.role !== "viewer",
},
{
type: "button",
- title: t("Make {{ userName }} an admin…", {
- userName: user.name,
- }),
+ title: `${t("Change role to admin")}…`,
onClick: handlePromote,
visible: can.promote && user.role !== "admin",
},
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index b763c98ae..2f314053f 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -205,6 +205,12 @@
"No results": "No results",
"Previous page": "Previous page",
"Next page": "Next page",
+ "Confirm": "Confirm",
+ "Saving": "Saving",
+ "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content": "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content",
+ "Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
+ "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
+ "Are you sure you want to suspend {{ userName }}? Suspended users will be prevented from logging in.": "Are you sure you want to suspend {{ userName }}? Suspended users will be prevented from logging in.",
"Account": "Account",
"Notifications": "Notifications",
"API Tokens": "API Tokens",
@@ -326,19 +332,15 @@
"Headings you add to the document will appear here": "Headings you add to the document will appear here",
"Table of contents": "Table of contents",
"By {{ author }}": "By {{ author }}",
- "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
- "Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
- "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content": "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content",
- "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.",
+ "Change role to admin": "Change role to admin",
+ "Change role to member": "Change role to member",
+ "Change role to viewer": "Change role to viewer",
+ "Suspend account": "Suspend account",
"An error occurred while sending the invite": "An error occurred while sending the invite",
"User options": "User options",
- "Make {{ userName }} a member": "Make {{ userName }} a member",
- "Make {{ userName }} a viewer": "Make {{ userName }} a viewer",
- "Make {{ userName }} an admin…": "Make {{ userName }} an admin…",
"Resend invite": "Resend invite",
"Revoke invite": "Revoke invite",
"Activate account": "Activate account",
- "Suspend account": "Suspend account",
"API token created": "API token created",
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
"The document archive is empty at the moment.": "The document archive is empty at the moment.",
@@ -367,7 +369,6 @@
"You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.",
"Name": "Name",
"Sort": "Sort",
- "Saving": "Saving",
"Save": "Save",
"Export started. If you have notifications enabled, you will receive an email when it's complete.": "Export started. If you have notifications enabled, you will receive an email when it's complete.",
"Exporting the collection {{collectionName}} may take some time.": "Exporting the collection {{collectionName}} may take some time.",