mirror of
https://github.com/coder/coder.git
synced 2026-09-22 13:10:21 +08:00
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user