From b2cfc4e53097223d48574bab5a628e390890bc16 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Sat, 8 Aug 2026 18:03:43 +1000 Subject: [PATCH] refactor(site): move `Add users` into `` (#27906) Move the Add users action into SettingsHeader on the organization Members page and group detail page, matching the Users page layout. It no longer sits inline with the filters. | Old | New | | --- | --- | | ORGANIZATION_MEMBERS_OLD | ORGANIZATION_MEMBERS_NEW | --- .../src/pages/GroupsPage/GroupMembersPage.tsx | 122 +---------- site/src/pages/GroupsPage/GroupPage.tsx | 192 ++++++++++++++---- .../OrganizationMembersPageView.tsx | 11 +- 3 files changed, 163 insertions(+), 162 deletions(-) diff --git a/site/src/pages/GroupsPage/GroupMembersPage.tsx b/site/src/pages/GroupsPage/GroupMembersPage.tsx index 66fecd2e0e..33ff7b2237 100644 --- a/site/src/pages/GroupsPage/GroupMembersPage.tsx +++ b/site/src/pages/GroupsPage/GroupMembersPage.tsx @@ -1,12 +1,11 @@ import dayjs from "dayjs"; -import { EllipsisVerticalIcon, UserPlusIcon } from "lucide-react"; +import { EllipsisVerticalIcon } from "lucide-react"; import { type FC, useEffect, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { useOutletContext } from "react-router"; import { toast } from "sonner"; import { getErrorDetail, getErrorMessage } from "#/api/errors"; import { - addMembers, groupAIBudget, groupMembersAISpend, removeMember, @@ -15,18 +14,11 @@ import { meAISpend } from "#/api/queries/users"; import type { Group, GroupMemberAISpend, - OrganizationMemberWithUserData, ReducedUser, } from "#/api/typesGenerated"; import { Avatar } from "#/components/Avatar/Avatar"; import { AvatarData } from "#/components/Avatar/AvatarData"; import { Button } from "#/components/Button/Button"; -import { - Dialog, - DialogContent, - DialogFooter, - DialogTitle, -} from "#/components/Dialog/Dialog"; import { DropdownMenu, DropdownMenuContent, @@ -35,9 +27,7 @@ import { } from "#/components/DropdownMenu/DropdownMenu"; import { UsersFilter } from "#/components/Filter/UsersFilter"; import { LastSeen } from "#/components/LastSeen/LastSeen"; -import { MultiMemberSelect } from "#/components/MultiUserSelect/MultiUserSelect"; import { PaginationContainer } from "#/components/PaginationWidget/PaginationContainer"; -import { Spinner } from "#/components/Spinner/Spinner"; import { Table, TableBody, @@ -49,7 +39,6 @@ import { import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; import { useAuthenticated } from "#/hooks/useAuthenticated"; import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility"; -import { isEveryoneGroup } from "#/modules/groups"; import { cn } from "#/utils/cn"; import { formatBudgetUSD } from "#/utils/currency"; import { SpendEstimateDocsLink } from "./AICostControl"; @@ -75,7 +64,6 @@ const GroupMembersPage: FC = () => { filterProps, } = useOutletContext(); const queryClient = useQueryClient(); - const addMembersMutation = useMutation(addMembers(queryClient, organization)); const removeMemberMutation = useMutation( removeMember(queryClient, organization), ); @@ -137,21 +125,7 @@ const GroupMembersPage: FC = () => { return (
-
- - - {canUpdateGroup && groupData && !isEveryoneGroup(groupData) && ( - { - await addMembersMutation.mutateAsync({ - groupId: groupData.id, - userIds: users.map((u) => u.user_id), - }); - }} - /> - )} -
+ @@ -247,98 +221,6 @@ const GroupMembersPage: FC = () => { ); }; -interface AddUsersDialogProps { - onSubmit: (users: OrganizationMemberWithUserData[]) => Promise; - organizationId: string; -} - -const AddUsersDialog: FC = ({ - onSubmit, - organizationId, -}) => { - const [addUserDialogOpen, setAddUserDialogOpen] = useState(false); - const [submitting, setSubmitting] = useState(false); - const [filter, setFilter] = useState(""); - const [selected, setSelected] = useState( - [], - ); - const closeDialog = () => { - setAddUserDialogOpen(false); - setFilter(""); - setSelected([]); - }; - - return ( - <> - - { - if (!open) { - closeDialog(); - } - }} - > - - - Add user(s) - - { - if (checked) { - setSelected([...selected, user]); - } else { - setSelected(selected.filter((s) => s.user_id !== user.user_id)); - } - }} - selected={selected} - /> - - - - - - - - ); -}; - interface GroupMemberRowProps { member: MemberWithSpend; group: Group; diff --git a/site/src/pages/GroupsPage/GroupPage.tsx b/site/src/pages/GroupsPage/GroupPage.tsx index 331730132a..ebd17f3068 100644 --- a/site/src/pages/GroupsPage/GroupPage.tsx +++ b/site/src/pages/GroupsPage/GroupPage.tsx @@ -1,4 +1,4 @@ -import { TrashIcon } from "lucide-react"; +import { TrashIcon, UserPlusIcon } from "lucide-react"; import { type ComponentProps, type FC, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { @@ -11,28 +11,42 @@ import { import { toast } from "sonner"; import { getErrorDetail, getErrorMessage } from "#/api/errors"; import { + addMembers, deleteGroup, group, groupMembers, groupPermissions, } from "#/api/queries/groups"; -import type { Group, ReducedUser } from "#/api/typesGenerated"; +import type { + Group, + OrganizationMemberWithUserData, + ReducedUser, +} from "#/api/typesGenerated"; import { ErrorAlert } from "#/components/Alert/ErrorAlert"; import { Avatar } from "#/components/Avatar/Avatar"; import { AvatarData } from "#/components/Avatar/AvatarData"; import { Button } from "#/components/Button/Button"; import { DeleteDialog } from "#/components/Dialog/DeleteDialog/DeleteDialog"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogTitle, +} from "#/components/Dialog/Dialog"; import { useFilter } from "#/components/Filter/Filter"; import type { UsersFilter } from "#/components/Filter/UsersFilter"; import { Loader } from "#/components/Loader/Loader"; +import { MultiMemberSelect } from "#/components/MultiUserSelect/MultiUserSelect"; import type { PaginationResult } from "#/components/PaginationWidget/PaginationContainer"; import { SettingsHeader, SettingsHeaderDescription, SettingsHeaderTitle, } from "#/components/SettingsHeader/SettingsHeader"; +import { Spinner } from "#/components/Spinner/Spinner"; import { LinkTabs, LinkTabsList, TabLink } from "#/components/Tabs/Tabs"; import { usePaginatedQuery } from "#/hooks/usePaginatedQuery"; +import { isEveryoneGroup } from "#/modules/groups"; import { pageTitle } from "#/utils/page"; import { AIBudgetPeriod } from "./AIBudgetPeriod"; @@ -75,6 +89,7 @@ const GroupPage: FC = () => { const deleteGroupMutation = useMutation( deleteGroup(queryClient, organization), ); + const addMembersMutation = useMutation(addMembers(queryClient, organization)); const [isDeletingGroup, setIsDeletingGroup] = useState(false); const isLoading = groupQuery.isLoading || @@ -113,40 +128,53 @@ const GroupPage: FC = () => { <> {title} -
- - - } - title={ - - {groupData.display_name || groupData.name || "Unknown Group"} - - } - /> - - Manage members for this group. - - - - {canUpdateGroup && ( - - )} -
+ + {!isEveryoneGroup(groupData) && ( + { + await addMembersMutation.mutateAsync({ + groupId: groupData.id, + userIds: users.map((u) => u.user_id), + }); + }} + /> + )} + + + ) + } + > + + } + title={ + + {groupData.display_name || groupData.name || "Unknown Group"} + + } + /> + + Manage members for this group. + +
{canUpdateGroup && ( { ); }; +interface AddUsersDialogProps { + onSubmit: (users: OrganizationMemberWithUserData[]) => Promise; + organizationId: string; +} + +const AddUsersDialog: FC = ({ + onSubmit, + organizationId, +}) => { + const [addUserDialogOpen, setAddUserDialogOpen] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [filter, setFilter] = useState(""); + const [selected, setSelected] = useState( + [], + ); + const closeDialog = () => { + setAddUserDialogOpen(false); + setFilter(""); + setSelected([]); + }; + + return ( + <> + + { + if (!open) { + closeDialog(); + } + }} + > + + + Add user(s) + + { + if (checked) { + setSelected([...selected, user]); + } else { + setSelected(selected.filter((s) => s.user_id !== user.user_id)); + } + }} + selected={selected} + /> + + + + + + + + ); +}; + export default GroupPage; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx index 3a6b2e1654..7afa6e8518 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.tsx @@ -50,17 +50,16 @@ export const OrganizationMembersPageView: React.FC< return (
- + } + > Members
{Boolean(error) && } -
- - {canEditMembers && } -
+ {!canViewMembers && (
@@ -94,7 +93,7 @@ const AddUsersDialog: React.FC = ({ onSubmit }) => { return ( <> -