From 7c3ade476bb102fc86fb879d0ce54dd21913082b Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 6 Jan 2026 14:54:35 -0800 Subject: [PATCH 1/2] improvement(frontend): update org/sub-org group membership ui to v3 components --- .../GroupDetailsByIDPage.tsx | 105 +++--- .../AddGroupIdentitiesTab.tsx | 6 +- .../AddGroupUsersTab.tsx | 8 +- .../components/GroupDetailsSection.tsx | 167 ++++++---- .../GroupMembersSection.tsx | 81 +++-- .../GroupMembersSection/GroupMembersTable.tsx | 306 ++++++++---------- .../GroupMembershipIdentityRow.tsx | 109 +++---- .../GroupMembershipUserRow.tsx | 131 ++++---- .../GroupProjectsSection/GroupProjectRow.tsx | 165 ++++++---- .../GroupProjectsSection.tsx | 79 +++-- .../GroupProjectsTable.tsx | 199 ++++++------ .../PamRequestAccountAccessModal.tsx | 2 +- .../GroupMembersSection/GroupMembersTable.tsx | 1 - 13 files changed, 693 insertions(+), 666 deletions(-) diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx index 0b12fdb752..7edae7fbba 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx @@ -1,23 +1,18 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; -import { faChevronLeft } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useParams } from "@tanstack/react-router"; -import { twMerge } from "tailwind-merge"; +import { ChevronLeftIcon, EllipsisIcon } from "lucide-react"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; +import { DeleteActionModal, PageHeader, Spinner } from "@app/components/v2"; import { - Button, - DeleteActionModal, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - PageHeader, - Spinner, - Tooltip -} from "@app/components/v2"; + UnstableButton, + UnstableDropdownMenu, + UnstableDropdownMenuContent, + UnstableDropdownMenuItem, + UnstableDropdownMenuTrigger +} from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useDeleteGroup } from "@app/hooks/api"; @@ -73,47 +68,60 @@ const Page = () => { handlePopUpClose("deleteGroup"); }; - if (isPending) return ; + if (isPending) + return ( +
+ +
+ ); return ( -
+
{data && ( -
+ <> - + {isSubOrganization ? "Sub-" : ""}Organization Groups - - -
- - - -
-
- + + + + Options + + + + + { + navigator.clipboard.writeText(groupId); + createNotification({ + text: "Group ID copied to clipboard", + type: "info" + }); + }} + > + Copy Group ID + {(isAllowed) => ( - { + { handlePopUpOpen("groupCreateUpdate", { groupId, name: data.group.name, @@ -121,10 +129,9 @@ const Page = () => { role: data.group.role }); }} - disabled={!isAllowed} > Edit Group - + )} { a={OrgPermissionSubjects.Groups} > {(isAllowed) => ( - { + { handlePopUpOpen("deleteGroup", { id: groupId, name: data.group.name }); }} - disabled={!isAllowed} > Delete Group - + )} - -
+ +
-
-
- -
-
+
+ +
-
+ )} - + @@ -90,9 +90,7 @@ export const AddGroupIdentitiesTab = ({ groupId, groupSlug, search }: Props) => data?.machineIdentities?.map((identity: TGroupMachineIdentity) => { return ( - +
Machine IdentityMachine Identity
-

{identity.name}

-
{identity.name} { - + @@ -87,9 +87,9 @@ export const AddGroupUsersTab = ({ groupId, groupSlug, search }: Props) => { data?.users?.map(({ id, firstName, lastName, username }) => { return ( - - - - - - + + + + + {name} + {format(new Date(joinedGroupAt), "yyyy-MM-dd")} + + + + + + + + + + {(isAllowed) => ( + + handlePopUpOpen("removeMemberFromGroup", { + memberType: GroupMemberType.MACHINE_IDENTITY, + identityId: id, + name + }) + } + isDisabled={!isAllowed} + > + Remove Identity From Group + + )} + + + + + ); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipUserRow.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipUserRow.tsx index eb5fc45e56..1fa7c7b229 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipUserRow.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipUserRow.tsx @@ -1,18 +1,17 @@ -import { faEllipsisV, faUserMinus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { UserIcon } from "lucide-react"; +import { format } from "date-fns"; +import { MoreHorizontalIcon, UserIcon } from "lucide-react"; import { OrgPermissionCan } from "@app/components/permissions"; +import { Tooltip } from "@app/components/v2"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - IconButton, - Td, - Tooltip, - Tr -} from "@app/components/v2"; + UnstableDropdownMenu, + UnstableDropdownMenuContent, + UnstableDropdownMenuItem, + UnstableDropdownMenuTrigger, + UnstableIconButton, + UnstableTableCell, + UnstableTableRow +} from "@app/components/v3"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useOidcManageGroupMembershipsEnabled } from "@app/hooks/api"; import { GroupMemberType, TGroupMemberUser } from "@app/hooks/api/groups/types"; @@ -40,68 +39,50 @@ export const GroupMembershipUserRow = ({ useOidcManageGroupMembershipsEnabled(currentOrg.id); return ( - - - - - - + + + + + + {`${firstName ?? "-"} ${lastName ?? ""}`} ({email}) + + {format(new Date(joinedGroupAt), "yyyy-MM-dd")} + + + + + + + + + + {(isAllowed) => ( + + + handlePopUpOpen("removeMemberFromGroup", { + memberType: GroupMemberType.USER, + username + }) + } + isDisabled={!isAllowed || isOidcManageGroupMembershipsEnabled} + > + Remove User From Group + + + )} + + + + + ); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectRow.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectRow.tsx index f86fb78871..1df4fb45ac 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectRow.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectRow.tsx @@ -1,19 +1,22 @@ -import { faEllipsisV, faFolderMinus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useMemo } from "react"; +import { useNavigate } from "@tanstack/react-router"; +import { format } from "date-fns"; +import { MoreHorizontalIcon } from "lucide-react"; +import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - IconButton, - Td, - Tooltip, - Tr -} from "@app/components/v2"; -import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context"; -import { getProjectTitle } from "@app/helpers/project"; + UnstableDropdownMenu, + UnstableDropdownMenuContent, + UnstableDropdownMenuItem, + UnstableDropdownMenuTrigger, + UnstableIconButton, + UnstableTableCell, + UnstableTableRow +} from "@app/components/v3"; +import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { getProjectBaseURL, getProjectTitle } from "@app/helpers/project"; +import { useGetUserProjects } from "@app/hooks/api"; import { TGroupProject } from "@app/hooks/api/groups/types"; import { ProjectType } from "@app/hooks/api/projects/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -27,55 +30,93 @@ type Props = { }; export const GroupProjectRow = ({ project, handlePopUpOpen }: Props) => { + const { data: workspaces } = useGetUserProjects(); + const navigate = useNavigate(); + const { currentOrg } = useOrganization(); + + const isAccessible = useMemo(() => { + const workspaceIds = new Map(); + + workspaces?.forEach((workspace) => { + workspaceIds.set(workspace.id, true); + }); + + return workspaceIds.has(project.id); + }, [workspaces, project]); + return ( - - - - - - + { + if (isAccessible) { + navigate({ + to: `${getProjectBaseURL(project.type as ProjectType)}/access-management` as const, + params: { + orgId: currentOrg?.id || "", + projectId: project.id + }, + search: { + selectedTab: "groups" + } + }); + return; + } + + createNotification({ + text: "Unable to access project", + type: "error" + }); + }} + > + {project.name} + {getProjectTitle(project.type as ProjectType)} + {format(new Date(project.joinedGroupAt), "yyyy-MM-dd")} + + + + + + + + + { + e.stopPropagation(); + navigate({ + to: `${getProjectBaseURL(project.type as ProjectType)}/access-management` as const, + params: { + orgId: currentOrg?.id || "", + projectId: project.id + }, + search: { + selectedTab: "groups" + } + }); + }} + > + Access Project + + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("removeProjectFromGroup", { + projectId: project.id, + projectName: project.name + }); + }} + > + Remove From Project + + )} + + + + + ); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsSection.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsSection.tsx index 1ef653dc2a..03240be010 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsSection.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsSection.tsx @@ -1,9 +1,17 @@ -import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { PlusIcon } from "lucide-react"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; -import { DeleteActionModal, IconButton } from "@app/components/v2"; +import { DeleteActionModal } from "@app/components/v2"; +import { + UnstableButton, + UnstableCard, + UnstableCardAction, + UnstableCardContent, + UnstableCardDescription, + UnstableCardHeader, + UnstableCardTitle +} from "@app/components/v3"; import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context"; import { useDeleteGroupFromWorkspace as useRemoveProjectFromGroup } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -39,35 +47,40 @@ export const GroupProjectsSection = ({ groupId, groupSlug }: Props) => { }; return ( -
-
-

Group Projects

- - {(isAllowed) => ( - { - handlePopUpOpen("addGroupProjects", { - groupId, - slug: groupSlug - }); - }} - > - - - )} - -
-
- -
+ <> + + + Projects + Manage group project memberships + + + {(isAllowed) => ( + { + handlePopUpOpen("addGroupProjects", { + groupId, + slug: groupSlug + }); + }} + size="xs" + variant="outline" + > + + Add to Project + + )} + + + + + + + { return handleRemoveProjectFromGroup(projectData.projectId, projectData.projectName); }} /> -
+ ); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsTable.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsTable.tsx index 3e55e41291..4f53013a24 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsTable.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupProjectsSection/GroupProjectsTable.tsx @@ -1,28 +1,23 @@ -import { - faArrowDown, - faArrowUp, - faFolder, - faMagnifyingGlass, - faSearch -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ChevronDownIcon, PlusIcon } from "lucide-react"; +import { twMerge } from "tailwind-merge"; -import { OrgPermissionCan } from "@app/components/permissions"; +import { Lottie } from "@app/components/v2"; import { - Button, - EmptyState, - IconButton, - Input, - Pagination, - Table, - TableContainer, - TableSkeleton, - TBody, - Th, - THead, - Tr -} from "@app/components/v2"; -import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context"; + UnstableButton, + UnstableEmpty, + UnstableEmptyContent, + UnstableEmptyDescription, + UnstableEmptyHeader, + UnstableEmptyTitle, + UnstableInput, + UnstablePagination, + UnstableTable, + UnstableTableBody, + UnstableTableHead, + UnstableTableHeader, + UnstableTableRow +} from "@app/components/v3"; +import { useOrganization } from "@app/context"; import { getUserTablePreference, PreferenceKey, @@ -50,6 +45,8 @@ enum GroupProjectsOrderBy { } export const GroupProjectsTable = ({ groupId, groupSlug, handlePopUpOpen }: Props) => { + const { isSubOrganization } = useOrganization(); + const { search, debouncedSearch, @@ -82,7 +79,6 @@ export const GroupProjectsTable = ({ groupId, groupSlug, handlePopUpOpen }: Prop }); const totalCount = groupMemberships?.totalCount ?? 0; - const isEmpty = !isPending && totalCount === 0; const projects = groupMemberships?.projects ?? []; useResetPageHelper({ @@ -91,93 +87,92 @@ export const GroupProjectsTable = ({ groupId, groupSlug, handlePopUpOpen }: Prop setPage }); + if (isPending) { + return ( +
+ +
+ ); + } + return ( -
- + setSearch(e.target.value)} - leftIcon={} placeholder="Search projects..." /> - -
UserUser
-

{`${firstName ?? "-"} ${lastName ?? ""}`}

-

{username}

+
+

{`${firstName ?? "-"} ${lastName ?? ""}`}

+

{username}

{ - const { data, isPending } = useGetGroupById(groupId); + const { data } = useGetGroupById(groupId); - if (isPending) return ; + const [, isCopyingId, setCopyTextId] = useTimedReset({ + initialState: "Copy ID to clipboard" + }); + + const [, isCopyingSlug, setCopyTextSlug] = useTimedReset({ + initialState: "Copy slug to clipboard" + }); return data ? ( -
-
-

Group Details

- - {(isAllowed) => { - return ( - - + + Details + Group details + + + {(isAllowed) => ( + { + handlePopUpOpen("groupCreateUpdate", { + groupId, + name: data.group.name, + slug: data.group.slug, + role: data.group.role + }); + }} + size="xs" + variant="outline" + > + + + )} + + + + + + + Name + {data.group.name} + + + ID + + {data.group.id} + + { - handlePopUpOpen("groupCreateUpdate", { - groupId, - name: data.group.name, - slug: data.group.slug, - role: data.group.role - }); + navigator.clipboard.writeText(data.group.id); + setCopyTextId("Copied"); }} + variant="ghost" + size="xs" > - - + {isCopyingId ? : } + - ); - }} - -
-
-
-

Group ID

-
-

{data.group.id}

- -
-
-
-

Name

-

{data.group.name}

-
-
-

Slug

-
-

{data.group.slug}

- -
-
-
-

Organization Role

-

{data.group.role}

-
-
-

Created At

-

- {new Date(data.group.createdAt).toLocaleString()} -

-
-
-
+ + + + Slug + + {data.group.slug} + + { + navigator.clipboard.writeText(data.group.slug); + setCopyTextSlug("Copied"); + }} + variant="ghost" + size="xs" + > + {isCopyingSlug ? : } + + + + + + Organization Role + {data.group.role} + + + Created + {format(data.group.createdAt, "PPpp")} + + + + ) : ( -
-
-

Group data not found

-
-
+
); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx index da1a627f63..05e4c55791 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx @@ -1,9 +1,17 @@ -import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { PlusIcon } from "lucide-react"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; -import { DeleteActionModal, IconButton } from "@app/components/v2"; +import { DeleteActionModal } from "@app/components/v2"; +import { + UnstableButton, + UnstableCard, + UnstableCardAction, + UnstableCardContent, + UnstableCardDescription, + UnstableCardHeader, + UnstableCardTitle +} from "@app/components/v3"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useOidcManageGroupMembershipsEnabled, @@ -76,37 +84,40 @@ export const GroupMembersSection = ({ groupId, groupSlug }: Props) => { }; return ( -
-
-

Group Members

- - {(isAllowed) => ( -
- { - handlePopUpOpen("addGroupMembers", { - groupId, - slug: groupSlug - }); - }} - > - - -
- )} -
-
-
- -
+ <> + + + Group Members + Manage members of this group + + + {(isAllowed) => ( + { + handlePopUpOpen("addGroupMembers", { + groupId, + slug: groupSlug + }); + }} + size="xs" + variant="outline" + > + + Add Member + + )} + + + + + + + { return handleRemoveMemberFromGroup(memberData); }} /> -
+ ); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx index 0902b01d2f..508cb37fe3 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -1,38 +1,30 @@ import { useState } from "react"; -import { - faArrowDown, - faArrowUp, - faCheckCircle, - faFilter, - faFolder, - faMagnifyingGlass, - faSearch -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { HardDriveIcon, UserIcon } from "lucide-react"; +import { ChevronDownIcon, FilterIcon, HardDriveIcon, PlusIcon, UserIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { OrgPermissionCan } from "@app/components/permissions"; +import { Lottie } from "@app/components/v2"; import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, - EmptyState, - IconButton, - Input, - Pagination, - Table, - TableContainer, - TableSkeleton, - TBody, - Th, - THead, - Tooltip, - Tr -} from "@app/components/v2"; + UnstableButton, + UnstableDropdownMenu, + UnstableDropdownMenuCheckboxItem, + UnstableDropdownMenuContent, + UnstableDropdownMenuLabel, + UnstableDropdownMenuTrigger, + UnstableEmpty, + UnstableEmptyContent, + UnstableEmptyDescription, + UnstableEmptyHeader, + UnstableEmptyTitle, + UnstableIconButton, + UnstableInput, + UnstablePagination, + UnstableTable, + UnstableTableBody, + UnstableTableHead, + UnstableTableHeader, + UnstableTableRow +} from "@app/components/v3"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { getUserTablePreference, @@ -40,7 +32,6 @@ import { setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; -import { useOidcManageGroupMembershipsEnabled } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useListGroupMembers } from "@app/hooks/api/groups/queries"; import { @@ -85,10 +76,7 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props setUserTablePreference("groupMembersTable", PreferenceKey.PerPage, newPerPage); }; - const { currentOrg } = useOrganization(); - - const { data: isOidcManageGroupMembershipsEnabled = false } = - useOidcManageGroupMembershipsEnabled(currentOrg.id); + const { isSubOrganization } = useOrganization(); const { data: groupMemberships, isPending } = useListGroupMembers({ id: groupId, @@ -122,40 +110,42 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props } ]; + if (isPending) { + return ( +
+ +
+ ); + } + + const isFiltered = search || memberTypeFilter.length; + return ( -
-
- +
+ setSearch(e.target.value)} - leftIcon={} placeholder="Search members..." + className="flex-1" /> - - - 0 && "border-primary/50 text-primary" - )} + + + - - - - - Filter by Member Type + + + + + Filter by Member Type {filterOptions.map((option) => ( - { e.preventDefault(); setMemberTypeFilter((prev) => { @@ -166,115 +156,97 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props }); setPage(1); }} - icon={ - memberTypeFilter.includes(option.value) && ( - - ) - } > -
- {option.icon} - {option.label} -
-
+ {option.label} + ))} -
-
+ +
- - - - - - - - - - {isPending && } - {!isPending && - groupMemberships?.members?.map((userGroupMembership) => { - return userGroupMembership.type === GroupMemberType.USER ? ( - - ) : ( - - ); - })} - -
- -
- Name - - - -
-
Added On -
- {Boolean(totalCount) && ( - - )} - {!isPending && !members.length && ( - - )} - {!groupMemberships?.members.length && ( - - {(isAllowed) => ( - -
- -
-
+ {members.length ? ( + + + + + + Name + + + Added On + + + + + {members.map((userGroupMembership) => + userGroupMembership.type === GroupMemberType.USER ? ( + + ) : ( + + ) )} -
- )} -
-
+ + + ) : ( + + + + {isFiltered ? "No members match this search" : "This group does not have any members"} + + + {isFiltered + ? "Adjust search filters to view members." + : "Add users or machine identities to this group."} + + {!isFiltered && ( + + + {(isAllowed) => ( + + handlePopUpOpen("addGroupMembers", { + groupId, + slug: groupSlug + }) + } + > + + Add Member + + )} + + + )} + + + )} + {Boolean(members.length) && ( + + )} + ); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipIdentityRow.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipIdentityRow.tsx index 9af1ba09f0..6b4b586e74 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipIdentityRow.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipIdentityRow.tsx @@ -1,18 +1,16 @@ -import { faEllipsisV, faUserMinus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { HardDriveIcon } from "lucide-react"; +import { format } from "date-fns"; +import { HardDriveIcon, MoreHorizontalIcon } from "lucide-react"; import { OrgPermissionCan } from "@app/components/permissions"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - IconButton, - Td, - Tooltip, - Tr -} from "@app/components/v2"; + UnstableDropdownMenu, + UnstableDropdownMenuContent, + UnstableDropdownMenuItem, + UnstableDropdownMenuTrigger, + UnstableIconButton, + UnstableTableCell, + UnstableTableRow +} from "@app/components/v3"; import { OrgPermissionGroupActions, OrgPermissionSubjects } from "@app/context"; import { GroupMemberType, TGroupMemberMachineIdentity } from "@app/hooks/api/groups/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -34,57 +32,40 @@ export const GroupMembershipIdentityRow = ({ handlePopUpOpen }: Props) => { return ( -
- - -

{name}

-
- -

{new Date(joinedGroupAt).toLocaleDateString()}

-
-
- - - - - - - - - - {(isAllowed) => { - return ( -
- } - onClick={() => - handlePopUpOpen("removeMemberFromGroup", { - memberType: GroupMemberType.MACHINE_IDENTITY, - identityId: id, - name - }) - } - isDisabled={!isAllowed} - > - Remove Identity From Group - -
- ); - }} -
-
-
-
-
- - -

- {`${firstName ?? "-"} ${lastName ?? ""}`}{" "} - ({email}) -

-
- -

{new Date(joinedGroupAt).toLocaleDateString()}

-
-
- - - - - - - - - - {(isAllowed) => { - return ( - -
- } - onClick={() => - handlePopUpOpen("removeMemberFromGroup", { - memberType: GroupMemberType.USER, - username - }) - } - isDisabled={!isAllowed || isOidcManageGroupMembershipsEnabled} - > - Remove User From Group - -
-
- ); - }} -
-
-
-
-
-

{project.name}

-
-

{getProjectTitle(project.type as ProjectType)}

-
- -

{new Date(project.joinedGroupAt).toLocaleDateString()}

-
-
- - - - - - - - - - {(isAllowed) => { - return ( - } - onClick={() => - handlePopUpOpen("removeProjectFromGroup", { - projectId: project.id, - projectName: project.name - }) - } - isDisabled={!isAllowed} - > - Remove group from project - - ); - }} - - - - -
- - - - - - - - - {isPending && } - {!isPending && - projects.map((project) => { - return ( - - ); - })} - -
-
- Name - - - -
-
TypeAdded On -
- {!isEmpty && ( - - )} - {isEmpty && ( - - )} - {isEmpty && ( - - {(isAllowed) => ( -
- -
+ + Add to Project + + )} -
- )} - - + + + )} + {Boolean(projects.length) && ( + + )} + ); }; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx index f50df38f9a..6cd87ea721 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx @@ -15,10 +15,10 @@ import { ModalContent, TextArea } from "@app/components/v2"; +import { UnstableAlert, UnstableAlertDescription, UnstableAlertTitle } from "@app/components/v3"; import { useProject } from "@app/context"; import { ApprovalPolicyType } from "@app/hooks/api/approvalPolicies"; import { useCreateApprovalRequest } from "@app/hooks/api/approvalRequests/mutations"; -import { UnstableAlert, UnstableAlertDescription, UnstableAlertTitle } from "@app/components/v3"; type Props = { accountPath?: string; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx index 1fb276efc2..4743b42bc5 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -201,7 +201,6 @@ export const GroupMembersTable = ({ groupMembership }: Props) => { setPage(1); }} > - {option.icon} {option.label} ))} From 20f1363b2ef1e31a7509f96e24b0a2925009e67b Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 6 Jan 2026 18:41:24 -0800 Subject: [PATCH 2/2] fix(frontend): handle custom roles for group membership role edit --- frontend/src/hooks/api/groups/types.ts | 1 + .../organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx | 2 +- .../GroupDetailsByIDPage/components/GroupDetailsSection.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/api/groups/types.ts b/frontend/src/hooks/api/groups/types.ts index f5549a3c1d..a9656c5a58 100644 --- a/frontend/src/hooks/api/groups/types.ts +++ b/frontend/src/hooks/api/groups/types.ts @@ -14,6 +14,7 @@ export type TGroup = { createdAt: string; updatedAt: string; role: string; + roleId: string; }; export type TGroupMembership = { diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx index 7edae7fbba..3f0ca0592b 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx @@ -126,7 +126,7 @@ const Page = () => { groupId, name: data.group.name, slug: data.group.slug, - role: data.group.role + role: data.group.roleId || data.group.role }); }} > diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupDetailsSection.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupDetailsSection.tsx index 6a245a79d6..0389fb542e 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupDetailsSection.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupDetailsSection.tsx @@ -52,7 +52,7 @@ export const GroupDetailsSection = ({ groupId, handlePopUpOpen }: Props) => { groupId, name: data.group.name, slug: data.group.slug, - role: data.group.role + role: data.group.roleId || data.group.role }); }} size="xs"