feat: add warning dialog when removing member from organization (#14695)

resolves #14705 


<img width="684" alt="Screenshot 2024-09-27 at 4 34 02 PM"
src="https://github.com/user-attachments/assets/5c3b6c3e-2afc-4405-8bed-d9ea80607411">
This commit is contained in:
Jaayden Halko
2024-09-27 17:20:01 -04:00
committed by GitHub
parent b80550957f
commit e6cd3005d3
4 changed files with 100 additions and 53 deletions
@@ -87,7 +87,9 @@ export const CustomRolesPage: FC = () => {
onCancel={() => setRoleToDelete(undefined)}
onConfirm={async () => {
try {
await deleteRoleMutation.mutateAsync(roleToDelete!.name);
if (roleToDelete) {
await deleteRoleMutation.mutateAsync(roleToDelete.name);
}
setRoleToDelete(undefined);
await organizationRolesQuery.refetch();
displaySuccess("Custom role deleted successfully!");
@@ -50,6 +50,9 @@ const removeMember = async () => {
const removeButton = screen.getByText(/Remove/);
await user.click(removeButton);
const dialog = await within(document.body).findByRole("dialog");
await user.click(within(dialog).getByRole("button", { name: "Remove" }));
};
const updateUserRole = async (role: SlimRole) => {
@@ -80,7 +83,7 @@ describe("OrganizationMembersPage", () => {
it("shows a success message", async () => {
await renderPage();
await removeMember();
await screen.findByText("Member removed successfully.");
await screen.findByText("User removed from organization successfully!");
});
});
});
@@ -1,7 +1,6 @@
import {
groupsByUserId,
groupsByUserIdInOrganization,
} from "api/queries/groups";
import type { Interpolation, Theme } from "@emotion/react";
import { getErrorMessage } from "api/errors";
import { groupsByUserIdInOrganization } from "api/queries/groups";
import {
addOrganizationMember,
organizationMembers,
@@ -11,9 +10,12 @@ import {
} from "api/queries/organizations";
import { organizationRoles } from "api/queries/roles";
import type { OrganizationMemberWithUserData, User } from "api/typesGenerated";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
import { Loader } from "components/Loader/Loader";
import { Stack } from "components/Stack/Stack";
import { useAuthenticated } from "contexts/auth/RequireAuth";
import type { FC } from "react";
import { type FC, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { useParams } from "react-router-dom";
import { useOrganizationSettings } from "./ManagementSettingsLayout";
@@ -52,45 +54,97 @@ const OrganizationMembersPage: FC = () => {
const organization = organizations?.find((o) => o.name === organizationName);
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
const [memberToDelete, setMemberToDelete] =
useState<OrganizationMemberWithUserData>();
const permissions = permissionsQuery.data;
if (!permissions) {
return <Loader />;
}
return (
<OrganizationMembersPageView
allAvailableRoles={organizationRolesQuery.data}
canEditMembers={permissions.editMembers}
error={
membersQuery.error ??
addMemberMutation.error ??
removeMemberMutation.error ??
updateMemberRolesMutation.error
}
isAddingMember={addMemberMutation.isLoading}
isUpdatingMemberRoles={updateMemberRolesMutation.isLoading}
me={me}
members={members}
groupsByUserId={groupsByUserIdQuery.data}
addMember={async (user: User) => {
await addMemberMutation.mutateAsync(user.id);
void membersQuery.refetch();
}}
removeMember={async (member: OrganizationMemberWithUserData) => {
await removeMemberMutation.mutateAsync(member.user_id);
void membersQuery.refetch();
}}
updateMemberRoles={async (
member: OrganizationMemberWithUserData,
newRoles: string[],
) => {
await updateMemberRolesMutation.mutateAsync({
userId: member.user_id,
roles: newRoles,
});
}}
/>
<>
<OrganizationMembersPageView
allAvailableRoles={organizationRolesQuery.data}
canEditMembers={permissions.editMembers}
error={
membersQuery.error ??
addMemberMutation.error ??
removeMemberMutation.error ??
updateMemberRolesMutation.error
}
isAddingMember={addMemberMutation.isLoading}
isUpdatingMemberRoles={updateMemberRolesMutation.isLoading}
me={me}
members={members}
groupsByUserId={groupsByUserIdQuery.data}
addMember={async (user: User) => {
await addMemberMutation.mutateAsync(user.id);
void membersQuery.refetch();
}}
removeMember={setMemberToDelete}
updateMemberRoles={async (
member: OrganizationMemberWithUserData,
newRoles: string[],
) => {
await updateMemberRolesMutation.mutateAsync({
userId: member.user_id,
roles: newRoles,
});
}}
/>
<ConfirmDialog
type="delete"
open={memberToDelete !== undefined}
onClose={() => setMemberToDelete(undefined)}
title="Remove member"
confirmText="Remove"
onConfirm={async () => {
try {
if (memberToDelete) {
await removeMemberMutation.mutateAsync(memberToDelete?.user_id);
}
setMemberToDelete(undefined);
await membersQuery.refetch();
displaySuccess("User removed from organization successfully!");
} catch (error) {
setMemberToDelete(undefined);
displayError(
getErrorMessage(error, "Failed to remove user from organization"),
);
} finally {
setMemberToDelete(undefined);
}
}}
description={
<Stack>
<p>
Removing this member will:
<ul>
<li>Remove the member from all groups in this organization</li>
<li>Remove all user role assignments</li>
<li>
Orphan all the member's workspaces associated with this
organization
</li>
</ul>
</p>
<p css={styles.test}>
Are you sure you want to remove this member?
</p>
</Stack>
}
/>
</>
);
};
const styles = {
test: {
paddingBottom: 20,
},
} satisfies Record<string, Interpolation<Theme>>;
export default OrganizationMembersPage;
@@ -44,7 +44,7 @@ interface OrganizationMembersPageViewProps {
members: Array<OrganizationMemberTableEntry> | undefined;
groupsByUserId: GroupsByUserId | undefined;
addMember: (user: User) => Promise<void>;
removeMember: (member: OrganizationMemberWithUserData) => Promise<void>;
removeMember: (member: OrganizationMemberWithUserData) => void;
updateMemberRoles: (
member: OrganizationMemberWithUserData,
newRoles: string[],
@@ -134,19 +134,7 @@ export const OrganizationMembersPageView: FC<
<MoreMenuContent>
<MoreMenuItem
danger
onClick={async () => {
try {
await props.removeMember(member);
displaySuccess("Member removed successfully.");
} catch (error) {
displayError(
getErrorMessage(
error,
"Failed to remove member.",
),
);
}
}}
onClick={() => props.removeMember(member)}
>
Remove
</MoreMenuItem>