diff --git a/app/actions/definitions/users.tsx b/app/actions/definitions/users.tsx
index 07ddcc276..c2ba44611 100644
--- a/app/actions/definitions/users.tsx
+++ b/app/actions/definitions/users.tsx
@@ -2,6 +2,7 @@ import { PlusIcon } from "outline-icons";
import * as React from "react";
import stores from "~/stores";
import Invite from "~/scenes/Invite";
+import { UserDeleteDialog } from "~/components/UserDialogs";
import { createAction } from "~/actions";
import { UserSection } from "~/actions/sections";
@@ -21,4 +22,31 @@ export const inviteUser = createAction({
},
});
+export const deleteUserActionFactory = (userId: string) =>
+ createAction({
+ name: ({ t }) => `${t("Delete user")}…`,
+ analyticsName: "Delete user",
+ keywords: "leave",
+ dangerous: true,
+ section: UserSection,
+ visible: ({ stores }) => stores.policies.abilities(userId).delete,
+ perform: ({ t }) => {
+ const user = stores.users.get(userId);
+ if (!user) {
+ return;
+ }
+
+ stores.dialogs.openModal({
+ title: t("Delete user"),
+ isCentered: true,
+ content: (
+
+ ),
+ });
+ },
+ });
+
export const rootUserActions = [inviteUser];
diff --git a/app/components/UserRoleDialogs.tsx b/app/components/UserDialogs.tsx
similarity index 85%
rename from app/components/UserRoleDialogs.tsx
rename to app/components/UserDialogs.tsx
index ceaf76205..afc283ade 100644
--- a/app/components/UserRoleDialogs.tsx
+++ b/app/components/UserDialogs.tsx
@@ -58,6 +58,31 @@ export function UserChangeToMemberDialog({ user, onSubmit }: Props) {
);
}
+export function UserDeleteDialog({ user, onSubmit }: Props) {
+ const { t } = useTranslation();
+
+ const handleSubmit = async () => {
+ await user.delete();
+ onSubmit();
+ };
+
+ return (
+
+ {t(
+ "Are you sure you want to permanently delete {{ userName }}? This operation is unrecoverable, consider suspending the user instead.",
+ {
+ userName: user.name,
+ }
+ )}
+
+ );
+}
+
export function UserChangeToAdminDialog({ user, onSubmit }: Props) {
const { t } = useTranslation();
const { users } = useStores();
diff --git a/app/menus/UserMenu.tsx b/app/menus/UserMenu.tsx
index 1d8192f7f..76fbd91bb 100644
--- a/app/menus/UserMenu.tsx
+++ b/app/menus/UserMenu.tsx
@@ -12,7 +12,10 @@ import {
UserChangeToViewerDialog,
UserSuspendDialog,
UserChangeNameDialog,
-} from "~/components/UserRoleDialogs";
+} from "~/components/UserDialogs";
+import { actionToMenuItem } from "~/actions";
+import { deleteUserActionFactory } from "~/actions/definitions/users";
+import useActionContext from "~/hooks/useActionContext";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
@@ -29,6 +32,9 @@ function UserMenu({ user }: Props) {
});
const can = usePolicy(user.id);
const { showToast } = useToasts();
+ const context = useActionContext({
+ isContextMenu: true,
+ });
const handlePromote = React.useCallback(
(ev: React.SyntheticEvent) => {
@@ -99,7 +105,7 @@ function UserMenu({ user }: Props) {
(ev: React.SyntheticEvent) => {
ev.preventDefault();
dialogs.openModal({
- title: t("Suspend account"),
+ title: t("Suspend user"),
isCentered: true,
content: (
@@ -199,11 +205,14 @@ function UserMenu({ user }: Props) {
},
{
type: "button",
- title: `${t("Suspend account")}…`,
- dangerous: true,
+ title: `${t("Suspend user")}…`,
onClick: handleSuspend,
visible: !user.isInvited && !user.isSuspended,
},
+ {
+ type: "separator",
+ },
+ actionToMenuItem(deleteUserActionFactory(user.id), context),
]}
/>
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index 88f47bb86..5fd3fd5dc 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -83,6 +83,7 @@
"New workspace": "New workspace",
"Create a workspace": "Create a workspace",
"Invite people": "Invite people",
+ "Delete user": "Delete user",
"Collection": "Collection",
"Debug": "Debug",
"Document": "Document",
@@ -227,6 +228,8 @@
"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?",
+ "I understand, delete": "I understand, delete",
+ "Are you sure you want to permanently delete {{ userName }}? This operation is unrecoverable, consider suspending the user instead.": "Are you sure you want to permanently delete {{ userName }}? This operation is unrecoverable, consider suspending the user instead.",
"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.",
"Save": "Save",
@@ -360,7 +363,7 @@
"Change role to member": "Change role to member",
"Change role to viewer": "Change role to viewer",
"Change name": "Change name",
- "Suspend account": "Suspend account",
+ "Suspend user": "Suspend user",
"An error occurred while sending the invite": "An error occurred while sending the invite",
"User options": "User options",
"Resend invite": "Resend invite",