diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsRow.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsRow.tsx
new file mode 100644
index 000000000..3e99d8e78
--- /dev/null
+++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsRow.tsx
@@ -0,0 +1,196 @@
+import { useCallback, useMemo, useState } from "react";
+import Link from "next/link";
+import { faFolder, faMagnifyingGlass, faTrash } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import { createNotification } from "@app/components/notifications";
+import { OrgPermissionCan } from "@app/components/permissions";
+import {
+ Button,
+ EmptyState,
+ IconButton,
+ Input,
+ Modal,
+ ModalContent,
+ Table,
+ TableContainer,
+ TBody,
+ Td,
+ Th,
+ THead,
+ Tooltip,
+ Tr
+} from "@app/components/v2";
+import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
+import { useDeleteGroupFromWorkspace } from "@app/hooks/api";
+import { TGroupWithProjectMemberships } from "@app/hooks/api/groups/types";
+import { UsePopUpState } from "@app/hooks/usePopUp";
+
+type Props = {
+ group: TGroupWithProjectMemberships;
+ handlePopUpOpen: (popUpName: keyof UsePopUpState<["removeUserFromGroup"]>, data?: {}) => void;
+};
+
+const GroupProjectsModal = ({
+ isOpen,
+ group,
+ groupSlug,
+ setIsOpen
+}: {
+ setIsOpen: (open: boolean) => void;
+ isOpen: boolean;
+ group: TGroupWithProjectMemberships;
+ groupSlug: string;
+}) => {
+ const [filteredProjectName, setFilteredProjectName] = useState("");
+
+ const { mutateAsync: deleteGroupFromProject } = useDeleteGroupFromWorkspace();
+
+ const filteredMemberships = useMemo(() => {
+ return group.projectMemberships.filter(({ project }) => {
+ return project.name.toLowerCase().includes(filteredProjectName.toLowerCase());
+ });
+ }, [filteredProjectName, group]);
+
+ const removeProjectFromGroup = useCallback(async (projectSlug: string) => {
+ await deleteGroupFromProject({
+ groupSlug,
+ projectSlug
+ });
+
+ createNotification({
+ type: "success",
+ text: "Project removed from group"
+ });
+ }, []);
+
+ return (
+ {
+ setIsOpen(open);
+ }}
+ >
+
+ setFilteredProjectName(e.target.value)}
+ leftIcon={}
+ placeholder="Search projects..."
+ />
+
+
+
+
+ | Project |
+
+
+
+ {filteredMemberships?.map(({ project, roles }) => {
+ return (
+
+ |
+
+
+ {project.name}
+
+
+ {/* If there's only one role, display the role.name. If more, display role.name (+X more) */}
+ role.role.charAt(0).toUpperCase() + role.role.slice(1))
+ .join(", ")}
+ >
+
+ {roles.length === 1
+ ? roles[0].role
+ : `${roles[0].role} (+${roles.length - 1} more)`}
+
+
+ |
+
+
+ {(isAllowed) => {
+ return (
+
+
+
+ );
+ }}
+
+ |
+
+ );
+ })}
+
+
+ {!filteredMemberships.length && }
+
+
+
+ );
+};
+
+export const UserGroupsRow = ({ group, handlePopUpOpen }: Props) => {
+ const [isProjectsModalOpen, setIsProjectsModalOpen] = useState(false);
+
+ return (
+ <>
+
setIsProjectsModalOpen(true)}
+ >
+ | {group.name} |
+
+ {group.projectMemberships.length} Project
+ {group.projectMemberships.length > 1 || group.projectMemberships.length === 0 ? "s" : ""}
+ |
+
+ {true && (
+
+
+ {
+ e.stopPropagation();
+ handlePopUpOpen("removeUserFromGroup", {
+ groupSlug: group.slug
+ });
+ }}
+ >
+
+
+
+
+ )}
+ |
+
+
+ >
+ );
+};
diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsSection.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsSection.tsx
new file mode 100644
index 000000000..41340aec4
--- /dev/null
+++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsSection.tsx
@@ -0,0 +1,68 @@
+import { useCallback } from "react";
+
+import { createNotification } from "@app/components/notifications";
+import { DeleteActionModal } from "@app/components/v2";
+import { useRemoveUserFromGroup } from "@app/hooks/api";
+import { OrgUser } from "@app/hooks/api/users/types";
+import { usePopUp } from "@app/hooks/usePopUp";
+
+import { UserGroupsTable } from "./UserGroupsTable";
+
+type Props = {
+ orgMembership: OrgUser;
+};
+
+export const UserGroupsSection = ({ orgMembership }: Props) => {
+ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
+ "removeUserFromGroup"
+ ] as const);
+
+ const { mutateAsync: removeUserFromGroup } = useRemoveUserFromGroup();
+
+ const handleRemoveUserFromGroup = useCallback(async (groupSlug: string) => {
+ try {
+ await removeUserFromGroup({
+ slug: groupSlug,
+ username: orgMembership.user.username
+ });
+
+ createNotification({
+ type: "success",
+ text: "User removed from group successfully"
+ });
+
+ handlePopUpClose("removeUserFromGroup");
+ } catch (error) {
+ createNotification({
+ type: "error",
+ text: "Failed to remove user from group"
+ });
+ }
+ }, []);
+
+ return (
+ <>
+
+
+ handlePopUpToggle("removeUserFromGroup", isOpen)}
+ deleteKey="confirm"
+ onDeleteApproved={() => {
+ const popupData = popUp?.removeUserFromGroup?.data as {
+ groupSlug: string;
+ };
+
+ return handleRemoveUserFromGroup(popupData.groupSlug);
+ }}
+ />
+ >
+ );
+};
diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx
new file mode 100644
index 000000000..914df9985
--- /dev/null
+++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx
@@ -0,0 +1,43 @@
+import { faFolder } from "@fortawesome/free-solid-svg-icons";
+
+import { EmptyState, Table, TableContainer, TBody, Th, THead, Tr } from "@app/components/v2";
+import { OrgUser } from "@app/hooks/api/types";
+import { useListUserGroupMemberships } from "@app/hooks/api/users/queries";
+import { UsePopUpState } from "@app/hooks/usePopUp";
+
+import { UserGroupsRow } from "./UserGroupsRow";
+
+type Props = {
+ orgMembership: OrgUser;
+ handlePopUpOpen: (popUpName: keyof UsePopUpState<["removeUserFromGroup"]>, data?: {}) => void;
+};
+
+export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => {
+ const { data: groups, isLoading } = useListUserGroupMemberships(orgMembership.user.username);
+
+ return (
+
+
+
+
+ | Name |
+ Projects |
+ |
+
+
+
+ {groups?.map((group) => (
+
+ ))}
+
+
+ {!isLoading && !groups?.length && (
+
+ )}
+
+ );
+};