1- import { useState } from "react" ;
1+ import { useState , useEffect } from "react" ;
22import { Title , SimpleGrid , Select , Stack , Text } from "@mantine/core" ;
33import { AuthGuard } from "@ui/components/AuthGuard" ;
44import { useApi } from "@ui/util/api" ;
55import { AppRoles } from "@common/roles" ;
66import UserInvitePanel from "./UserInvitePanel" ;
77import GroupMemberManagement from "./GroupMemberManagement" ;
8- import { EntraActionResponse , GroupMemberGetResponse } from "@common/types/iam" ;
8+ import {
9+ EntraActionResponse ,
10+ GroupMemberGetResponse ,
11+ GroupGetResponse ,
12+ } from "@common/types/iam" ;
913import { transformCommaSeperatedName } from "@common/utils" ;
10- import { getRunEnvironmentConfig , KnownGroups } from "@ui/config" ;
11-
12- const userGroupMappings : KnownGroups = {
13- Exec : "Executive Council" ,
14- CommChairs : "Committee Chairs" ,
15- StripeLinkCreators : "Stripe Link Creators" ,
16- InfraTeam : "Infrastructure Team" ,
17- InfraLeads : "Infrastructure Leads" ,
18- } ;
14+ import { notifications } from "@mantine/notifications" ;
15+ import { IconAlertCircle } from "@tabler/icons-react" ;
1916
2017export const ManageIamPage = ( ) => {
2118 const api = useApi ( "core" ) ;
22- const groupMappings = getRunEnvironmentConfig ( ) . KnownGroupMappings ;
23- const groupOptions = Object . entries ( groupMappings ) . map ( ( [ key , value ] ) => ( {
24- label : userGroupMappings [ key as keyof KnownGroups ] || key ,
25- value : `${ key } _${ value } ` , // to ensure that the same group for multiple roles still renders
26- } ) ) ;
19+ const [ groupOptions , setGroupOptions ] = useState <
20+ { label : string ; value : string } [ ]
21+ > ( [ ] ) ;
22+ const [ selectedGroup , setSelectedGroup ] = useState < string | null > ( null ) ;
2723
28- const [ selectedGroup , setSelectedGroup ] = useState (
29- groupOptions [ 0 ] ?. value || "" ,
30- ) ;
24+ // Fetch groups from the API on component mount
25+ useEffect ( ( ) => {
26+ const fetchGroups = async ( ) => {
27+ try {
28+ const response = await api . get < GroupGetResponse > ( "/api/v1/iam/groups" ) ;
29+ const options = response . data
30+ . map ( ( { id, displayName } ) => ( {
31+ label : displayName ,
32+ value : id ,
33+ } ) )
34+ . sort ( ( a , b ) => a . label . localeCompare ( b . label ) ) ; // Sort alphabetically
35+ setGroupOptions ( options ) ;
36+ } catch ( error ) {
37+ console . error ( "Failed to fetch groups:" , error ) ;
38+ notifications . show ( {
39+ title : "Failed to get groups." ,
40+ message : "Please try again or contact support." ,
41+ color : "red" ,
42+ icon : < IconAlertCircle size = { 16 } /> ,
43+ } ) ;
44+ }
45+ } ;
46+
47+ fetchGroups ( ) ;
48+ } , [ api ] ) ; // Dependency array ensures this runs once
3149
3250 const handleInviteSubmit = async ( emailList : string [ ] ) => {
3351 try {
@@ -47,44 +65,49 @@ export const ManageIamPage = () => {
4765 }
4866 } ;
4967
50- const getGroupMembers = async ( selectedGroup : string ) => {
68+ const getGroupMembers = async ( groupId : string | null ) => {
69+ if ( ! groupId ) {
70+ return [ ] ;
71+ } // Do not fetch if no group is selected
5172 try {
52- const response = await api . get (
53- `/api/v1/iam/groups/${ selectedGroup . split ( "_" ) [ 1 ] } ` ,
54- ) ;
73+ const response = await api . get ( `/api/v1/iam/groups/${ groupId } ` ) ;
5574 const data = response . data as GroupMemberGetResponse ;
56- const responseMapped = data
75+ return data
5776 . map ( ( x ) => ( {
5877 ...x ,
5978 name : transformCommaSeperatedName ( x . name ) ,
6079 } ) )
6180 . sort ( ( a , b ) => ( a . name > b . name ? 1 : a . name < b . name ? - 1 : 0 ) ) ;
62- return responseMapped ;
6381 } catch ( error ) {
6482 console . error ( "Failed to get users:" , error ) ;
6583 return [ ] ;
6684 }
6785 } ;
6886
6987 const updateGroupMembers = async ( toAdd : string [ ] , toRemove : string [ ] ) => {
70- const allMembers = [ ...toAdd , ...toRemove ] ;
88+ if ( ! selectedGroup ) {
89+ const errorMessage = "No group selected for update." ;
90+ console . error ( errorMessage ) ;
91+ return {
92+ success : [ ] ,
93+ failure : [ ...toAdd , ...toRemove ] . map ( ( email ) => ( {
94+ email,
95+ message : errorMessage ,
96+ } ) ) ,
97+ } ;
98+ }
99+
71100 try {
72- const response = await api . patch (
73- `/api/v1/iam/groups/${ selectedGroup . split ( "_" ) [ 1 ] } ` ,
74- {
75- remove : toRemove ,
76- add : toAdd ,
77- } ,
78- ) ;
101+ const response = await api . patch ( `/api/v1/iam/groups/${ selectedGroup } ` , {
102+ remove : toRemove ,
103+ add : toAdd ,
104+ } ) ;
79105 return response . data ;
80- } catch ( error ) {
81- if ( ! ( error instanceof Error ) ) {
82- throw error ;
83- }
106+ } catch ( error : any ) {
84107 console . error ( "Failed to modify group members:" , error ) ;
85108 return {
86109 success : [ ] ,
87- failure : allMembers . map ( ( email ) => ( {
110+ failure : [ ... toAdd , ... toRemove ] . map ( ( email ) => ( {
88111 email,
89112 message : error . message || "Failed to modify group member" ,
90113 } ) ) ,
@@ -115,15 +138,21 @@ export const ManageIamPage = () => {
115138 data = { groupOptions }
116139 value = { selectedGroup }
117140 clearable = { false }
118- onChange = { ( value ) => value && setSelectedGroup ( value ) }
119- placeholder = "Choose a group to manage"
120- />
121- < GroupMemberManagement
122- fetchMembers = { ( ) => {
123- return getGroupMembers ( selectedGroup ) ;
124- } }
125- updateMembers = { updateGroupMembers }
141+ onChange = { ( value ) => setSelectedGroup ( value ) }
142+ placeholder = {
143+ groupOptions . length > 0
144+ ? "Choose a group to manage"
145+ : "Loading groups..."
146+ }
147+ disabled = { groupOptions . length === 0 }
126148 />
149+ { selectedGroup && (
150+ < GroupMemberManagement
151+ key = { selectedGroup } // Re-mounts component on group change to trigger fetch
152+ fetchMembers = { ( ) => getGroupMembers ( selectedGroup ) }
153+ updateMembers = { updateGroupMembers }
154+ />
155+ ) }
127156 </ Stack >
128157 </ AuthGuard >
129158 </ SimpleGrid >
0 commit comments