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",