From ed7d709a70a37a1cf7dc35800d5e510742281394 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Fri, 27 Jun 2025 15:15:12 -0700 Subject: [PATCH 1/4] improvement: standardize and improve org access control --- .../src/services/identity/identity-org-dal.ts | 24 +- .../src/services/identity/identity-types.ts | 4 +- .../components/v2/Pagination/Pagination.tsx | 2 +- frontend/src/hooks/api/identities/queries.tsx | 3 +- frontend/src/hooks/api/organization/types.ts | 4 +- .../OrgGroupsSection/OrgGroupsSection.tsx | 4 +- .../OrgGroupsSection/OrgGroupsTable.tsx | 48 ++-- .../IdentitySection/IdentitySection.tsx | 26 +- .../IdentitySection/IdentityTable.tsx | 200 ++++++------- .../OrgMembersSection/OrgMembersSection.tsx | 4 +- .../OrgMembersSection/OrgMembersTable.tsx | 265 ++++++++++-------- .../OrgRoleTabSection/OrgRoleTabSection.tsx | 6 +- .../OrgRoleTabSection/OrgRoleTable.tsx | 232 ++++++++++++--- .../MembersTab/components/MembersTable.tsx | 7 +- .../ProjectRoleList/ProjectRoleList.tsx | 2 +- 15 files changed, 527 insertions(+), 304 deletions(-) diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 5ca6cbbc1..3c5ca8ffe 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -392,7 +392,12 @@ export const identityOrgDALFactory = (db: TDbClient) => { .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityOrgMembership}.identityId`) .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) - .orderBy(`${TableName.Identity}.${orderBy}`, orderDirection) + .orderBy( + orderBy === OrgIdentityOrderBy.Role + ? `${TableName.IdentityOrgMembership}.${orderBy}` + : `${TableName.Identity}.${orderBy}`, + orderDirection + ) .select(`${TableName.IdentityOrgMembership}.id`) .select<{ id: string; total_count: string }>( db.raw( @@ -523,6 +528,23 @@ export const identityOrgDALFactory = (db: TDbClient) => { if (orderBy === OrgIdentityOrderBy.Name) { void query.orderBy("identityName", orderDirection); + } else if (orderBy === OrgIdentityOrderBy.Role) { + void query.orderByRaw( + ` + CASE + WHEN ??.role = ? + THEN ??.slug + ELSE ??.role + END ? + `, + [ + TableName.IdentityOrgMembership, + "custom", + TableName.OrgRoles, + TableName.IdentityOrgMembership, + db.raw(orderDirection) + ] + ); } const docs = await query; diff --git a/backend/src/services/identity/identity-types.ts b/backend/src/services/identity/identity-types.ts index 8d23f34fe..4380f6f41 100644 --- a/backend/src/services/identity/identity-types.ts +++ b/backend/src/services/identity/identity-types.ts @@ -46,8 +46,8 @@ export type TListOrgIdentitiesByOrgIdDTO = { } & TOrgPermission; export enum OrgIdentityOrderBy { - Name = "name" - // Role = "role" + Name = "name", + Role = "role" } export type TSearchOrgIdentitiesByOrgIdDAL = { diff --git a/frontend/src/components/v2/Pagination/Pagination.tsx b/frontend/src/components/v2/Pagination/Pagination.tsx index c1f9306fa..d1f2e28aa 100644 --- a/frontend/src/components/v2/Pagination/Pagination.tsx +++ b/frontend/src/components/v2/Pagination/Pagination.tsx @@ -64,7 +64,7 @@ export const Pagination = ({ - + {perPageList.map((perPageOption) => ( { search }); return data; - } + }, + placeholderData: (previousData) => previousData }); }; diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 068cfad6d..73267f0f6 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -154,6 +154,6 @@ export type TOrgIdentitiesList = { }; export enum OrgIdentityOrderBy { - Name = "name" - // Role = "role" + Name = "name", + Role = "role" } diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx index fbe2d88f0..93af6583a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx @@ -56,12 +56,12 @@ export const OrgGroupsSection = () => { return (
-
+

Groups

{(isAllowed) => ( - {Boolean(filteredRoles.length) && ( - - )} -
- - - -
@@ -251,8 +258,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { - - {/* */} + @@ -303,7 +309,8 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { + @@ -398,7 +431,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro isActive }) => { const name = - u && u.firstName ? `${u.firstName} ${u.lastName ?? ""}`.trim() : "-"; + u && u.firstName ? `${u.firstName} ${u.lastName ?? ""}`.trim() : null; const email = u?.email || inviteEmail; const username = u?.username ?? inviteEmail ?? "-"; return ( @@ -415,7 +448,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro } > ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx index 8e6c5d591..57353e8d9 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx @@ -6,10 +6,10 @@ export const OrgRoleTabSection = () => { return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx index 54b257a72..e7426cdc9 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -1,4 +1,17 @@ -import { faEllipsis, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faCopy, + faEdit, + faEllipsisV, + faEye, + faIdBadge, + faMagnifyingGlass, + faPlus, + faSearch, + faTrash +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; @@ -14,6 +27,10 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + EmptyState, + IconButton, + Input, + Pagination, Table, TableContainer, TableSkeleton, @@ -30,13 +47,25 @@ import { useOrganization, useSubscription } from "@app/context"; -import { isCustomOrgRole } from "@app/helpers/roles"; -import { usePopUp } from "@app/hooks"; +import { isCustomOrgRole, isCustomProjectRole } from "@app/helpers/roles"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; +import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useDeleteOrgRole, useGetOrgRoles, useUpdateOrg } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { TOrgRole } from "@app/hooks/api/roles/types"; import { DuplicateOrgRoleModal } from "@app/pages/organization/RoleByIDPage/components/DuplicateOrgRoleModal"; import { RoleModal } from "@app/pages/organization/RoleByIDPage/components/RoleModal"; +enum RolesOrderBy { + Name = "name", + Slug = "slug", + Type = "type" +} + export const OrgRoleTable = () => { const navigate = useNavigate(); const { currentOrg } = useOrganization(); @@ -93,14 +122,89 @@ export const OrgRoleTable = () => { } }; + const { + orderDirection, + toggleOrderDirection, + orderBy, + setOrderDirection, + setOrderBy, + search, + setSearch, + page, + perPage, + setPerPage, + setPage, + offset + } = usePagination(RolesOrderBy.Type, { + initPerPage: getUserTablePreference("orgRolesTable", PreferenceKey.PerPage, 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("orgRolesTable", PreferenceKey.PerPage, newPerPage); + }; + + const filteredRoles = useMemo( + () => + roles + ?.filter((role) => { + const { slug, name } = role; + + const searchValue = search.trim().toLowerCase(); + + return ( + name.toLowerCase().includes(searchValue) || slug.toLowerCase().includes(searchValue) + ); + }) + .sort((a, b) => { + const [roleOne, roleTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + switch (orderBy) { + case RolesOrderBy.Slug: + return roleOne.slug.toLowerCase().localeCompare(roleTwo.slug.toLowerCase()); + case RolesOrderBy.Type: { + const roleOneValue = isCustomOrgRole(roleOne.slug) ? -1 : 1; + const roleTwoValue = isCustomOrgRole(roleTwo.slug) ? -1 : 1; + + return roleTwoValue - roleOneValue; + } + case RolesOrderBy.Name: + default: + return roleOne.name.toLowerCase().localeCompare(roleTwo.name.toLowerCase()); + } + }) ?? [], + [roles, orderDirection, search, orderBy] + ); + + useResetPageHelper({ + totalCount: filteredRoles.length, + offset, + setPage + }); + + const handleSort = (column: RolesOrderBy) => { + if (column === orderBy) { + toggleOrderDirection(); + return; + } + + setOrderBy(column); + setOrderDirection(OrderByDirection.ASC); + }; + + const getClassName = (col: RolesOrderBy) => twMerge("ml-2", orderBy === col ? "" : "opacity-30"); + + const getColSortIcon = (col: RolesOrderBy) => + orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown; + return (
-
+

Organization Roles

{(isAllowed) => (
+ setSearch(e.target.value)} + leftIcon={} + placeholder="Search roles..." + className="flex-1" + containerClassName="mb-4" + />
Role +
Role { />
-
{isFetching ? : null}
- -
- -
+ + + + - + {(isAllowed) => ( } onClick={(e) => { e.stopPropagation(); navigate({ @@ -348,7 +358,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { } }); }} - disabled={!isAllowed} + isDisabled={!isAllowed} > Edit Identity @@ -360,11 +370,6 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { > {(isAllowed) => ( { e.stopPropagation(); handlePopUpOpen("deleteIdentity", { @@ -372,7 +377,8 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { name }); }} - disabled={!isAllowed} + isDisabled={!isAllowed} + icon={} > Delete Identity @@ -398,7 +404,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { {!isPending && data && data?.identities.length === 0 && ( 0 || filteredRoles?.length > 0 + debouncedSearch.trim().length > 0 || filter.roles?.length > 0 ? "No identities match search filter" : "No identities have been created in this organization" } diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index f88ef5131..87e55c145 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -115,12 +115,12 @@ export const OrgMembersSection = () => { return (
-
+

Users

{(isAllowed) => (
-
Role +
+ Role + handleSort(OrgMembersOrderBy.Role)} + > + + +
+
- {name} + {name ?? Not Set} {u.superAdmin && ( Server Admin @@ -445,7 +478,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro - {userId !== u?.id && ( - - -
- -
-
- - - {(isAllowed) => ( - { - e.stopPropagation(); - navigate({ - to: "/organization/members/$membershipId" as const, - params: { - membershipId: orgMembershipId - } + + + + + + + + + {(isAllowed) => ( + { + e.stopPropagation(); + navigate({ + to: "/organization/members/$membershipId" as const, + params: { + membershipId: orgMembershipId + } + }); + }} + isDisabled={!isAllowed} + icon={} + > + Edit User + + )} + + + {(isAllowed) => ( + } + onClick={async (e) => { + e.stopPropagation(); + + if (currentOrg?.scimEnabled) { + createNotification({ + text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", + type: "error" }); - }} - disabled={!isAllowed} - > - Edit User - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - if (currentOrg?.scimEnabled) { - createNotification({ - text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", - type: "error" - }); - return; - } - - if (!isActive) { - // activate user - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: true - }); - - return; - } - - // deactivate user - handlePopUpOpen("deactivateMember", { - orgMembershipId, - username + if (!isActive) { + // activate user + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: true }); - }} - disabled={!isAllowed} - > - {`${isActive ? "Deactivate" : "Activate"} User`} - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - if (currentOrg?.scimEnabled && isActive) { - createNotification({ - text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", - type: "error" - }); - return; - } + return; + } - handlePopUpOpen("removeMember", { - orgMembershipId, - username + // deactivate user + handlePopUpOpen("deactivateMember", { + orgMembershipId, + username + }); + }} + isDisabled={!isAllowed} + > + {`${isActive ? "Deactivate" : "Activate"} User`} + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); + + if (currentOrg?.scimEnabled && isActive) { + createNotification({ + text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", + type: "error" }); - }} - disabled={!isAllowed} - > - Remove User - - )} - - - - )} + return; + } + + handlePopUpOpen("removeMember", { + orgMembershipId, + username + }); + }} + isDisabled={!isAllowed} + icon={} + > + Remove User + + )} + + +
- - + + + - {isRolesLoading && } - {roles?.map((role) => { + {isRolesLoading && } + {filteredRoles?.slice(offset, perPage * page).map((role) => { const { id, name, slug } = role; const isNonMutatable = ["owner", "admin", "member", "no-access"].includes(slug); const isDefaultOrgRole = isCustomOrgRole(slug) @@ -162,23 +311,30 @@ export const OrgRoleTable = () => { +
NameSlug +
+ Name + handleSort(RolesOrderBy.Name)} + > + + +
+
+
+ Slug + handleSort(RolesOrderBy.Slug)} + > + + +
+
+
+ Type + handleSort(RolesOrderBy.Type)} + > + + +
+
{slug} + + {isCustomProjectRole(slug) ? "Custom" : "Default"} + + - -
- -
+ + + + - + {(isAllowed) => ( { e.stopPropagation(); navigate({ @@ -188,7 +344,8 @@ export const OrgRoleTable = () => { } }); }} - disabled={!isAllowed} + isDisabled={!isAllowed} + icon={} > {`${isNonMutatable ? "View" : "Edit"} Role`} @@ -200,14 +357,12 @@ export const OrgRoleTable = () => { > {(isAllowed) => ( { e.stopPropagation(); handlePopUpOpen("duplicateRole", role); }} - disabled={!isAllowed} + isDisabled={!isAllowed} + icon={} > Duplicate Role @@ -220,14 +375,12 @@ export const OrgRoleTable = () => { > {(isAllowed) => ( { e.stopPropagation(); handleSetRoleAsDefault(slug); }} + icon={} > Set as Default Role @@ -250,16 +403,12 @@ export const OrgRoleTable = () => { > {(isAllowed) => ( { e.stopPropagation(); handlePopUpOpen("deleteRole", role); }} - disabled={!isAllowed || isDefaultOrgRole} + icon={} + isDisabled={!isAllowed || isDefaultOrgRole} > Delete Role @@ -276,6 +425,25 @@ export const OrgRoleTable = () => { })}
+ {Boolean(filteredRoles?.length) && ( + + )} + {!filteredRoles?.length && !isRolesLoading && ( + + )}
{ variant="plain" size="sm" className={twMerge( - "flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10", + "flex h-[2.375rem] w-[2.6rem] items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10", isTableFiltered && "border-primary/50 text-primary" )} > @@ -298,7 +298,8 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { {!isMembersLoading && filteredUsers.slice(offset, perPage * page).map((projectMember) => { const { user: u, inviteEmail, id: membershipId, roles } = projectMember; - const name = u.firstName || u.lastName ? `${u.firstName} ${u.lastName || ""}` : "-"; + const name = + u.firstName || u.lastName ? `${u.firstName} ${u.lastName || ""}` : null; const email = u?.email || inviteEmail; return ( @@ -328,7 +329,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { }) } > - {name} + {name ?? Not Set} {email}
diff --git a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx index 01c97060f..b29bf469b 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx @@ -236,7 +236,7 @@ export const ProjectRoleList = () => { - {isRolesLoading && } + {isRolesLoading && } {filteredRoles?.slice(offset, perPage * page).map((role) => { const { id, name, slug } = role; const isNonMutatable = Object.values(ProjectMembershipRole).includes( From 48f40ff9389f6864e9ed02d57a1a6bf52cc26302 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Fri, 27 Jun 2025 21:00:48 -0700 Subject: [PATCH 2/4] improvement: address feedback --- .../OrgMembersSection/OrgMembersTable.tsx | 288 +++++++++--------- 1 file changed, 141 insertions(+), 147 deletions(-) diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index 446b12638..bc2480c53 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -392,7 +392,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
- +
Role {(isAllowed) => ( - <> - {!isActive && ( - - )} - {isActive && status === "accepted" && ( - - )} - {isActive && - (status === "invited" || status === "verified") && - email && - serverDetails?.emailConfigured && ( + + )} + + + +
+ {isActive && + (status === "invited" || status === "verified") && + email && + serverDetails?.emailConfigured && ( + + {(isAllowed) => ( )} - - )} - - - - - - - - - - - - {(isAllowed) => ( - { - e.stopPropagation(); - navigate({ - to: "/organization/members/$membershipId" as const, - params: { - membershipId: orgMembershipId + + )} + + + + + + + + + {(isAllowed) => ( + { + e.stopPropagation(); + navigate({ + to: "/organization/members/$membershipId" as const, + params: { + membershipId: orgMembershipId + } + }); + }} + isDisabled={!isAllowed} + icon={} + > + Edit User + + )} + + + {(isAllowed) => ( + } + onClick={async (e) => { + e.stopPropagation(); + + if (currentOrg?.scimEnabled) { + createNotification({ + text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", + type: "error" + }); + return; } - }); - }} - isDisabled={!isAllowed} - icon={} - > - Edit User - - )} - - - {(isAllowed) => ( - } - onClick={async (e) => { - e.stopPropagation(); - if (currentOrg?.scimEnabled) { - createNotification({ - text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", - type: "error" + if (!isActive) { + // activate user + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: true + }); + + return; + } + + // deactivate user + handlePopUpOpen("deactivateMember", { + orgMembershipId, + username }); - return; - } + }} + isDisabled={!isAllowed} + > + {`${isActive ? "Deactivate" : "Activate"} User`} + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); - if (!isActive) { - // activate user - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: true + if (currentOrg?.scimEnabled && isActive) { + createNotification({ + text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", + type: "error" + }); + return; + } + + handlePopUpOpen("removeMember", { + orgMembershipId, + username }); - - return; - } - - // deactivate user - handlePopUpOpen("deactivateMember", { - orgMembershipId, - username - }); - }} - isDisabled={!isAllowed} - > - {`${isActive ? "Deactivate" : "Activate"} User`} - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - - if (currentOrg?.scimEnabled && isActive) { - createNotification({ - text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", - type: "error" - }); - return; - } - - handlePopUpOpen("removeMember", { - orgMembershipId, - username - }); - }} - isDisabled={!isAllowed} - icon={} - > - Remove User - - )} - - - + }} + isDisabled={!isAllowed} + icon={} + > + Remove User + + )} + + + +
); From 85023916e4d47aa9b7f96aa70ea2352752fb4670 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 30 Jun 2025 09:12:47 -0700 Subject: [PATCH 3/4] improvement: address feedback --- .../components/OrgMembersSection/OrgMembersTable.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index bc2480c53..c323c4057 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -103,8 +103,10 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro const { data: serverDetails } = useFetchServerStatus(); const { data: members = [], isPending: isMembersLoading } = useGetOrgUsers(orgId); - const { mutateAsync: resendOrgMemberInvitation } = useResendOrgMemberInvitation(); + const { mutateAsync: resendOrgMemberInvitation, isPending: isResendInvitePending } = + useResendOrgMemberInvitation(); const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership(); + const [resendInviteId, setResendInviteId] = useState(null); const onRoleChange = async (membershipId: string, role: string) => { if (!currentOrg?.id) return; @@ -140,6 +142,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro }; const onResendInvite = async (membershipId: string) => { + setResendInviteId(membershipId); try { const signupToken = await resendOrgMemberInvitation({ membershipId @@ -160,6 +163,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro text: "Failed to resend org invitation", type: "error" }); + } finally { + setResendInviteId(null); } }; @@ -497,10 +502,13 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro > {(isAllowed) => ( + + remove(rootIndex)} + isDisabled={isDisabled} + > + + + )} {!isDisabled && ( @@ -271,16 +274,19 @@ export const GeneralPermissionPolicies =
Actions
{!isDisabled && !isConditionalSubjects(subject) && ( - + + remove(rootIndex)} + isDisabled={isDisabled} + > + + + )}