1
- import { useTheme } from "@emotion/react" ;
2
- import { Tooltip } from "@mui/material" ;
1
+ import { type Interpolation , type Theme , useTheme } from "@emotion/react" ;
3
2
import Table from "@mui/material/Table" ;
4
- import { UserAvatar } from "components/UserAvatar/UserAvatar" ;
5
- import { PageHeader , PageHeaderTitle } from "components/PageHeader/PageHeader" ;
6
- import { AvatarData } from "components/AvatarData/AvatarData" ;
7
3
import TableBody from "@mui/material/TableBody" ;
8
4
import TableCell from "@mui/material/TableCell" ;
9
5
import TableContainer from "@mui/material/TableContainer" ;
10
6
import TableHead from "@mui/material/TableHead" ;
11
7
import TableRow from "@mui/material/TableRow" ;
12
- import type { FC } from "react" ;
8
+ import Tooltip from "@mui/material/Tooltip" ;
9
+ import PersonAdd from "@mui/icons-material/PersonAdd" ;
10
+ import { type FC , useState } from "react" ;
13
11
import { useMutation , useQuery , useQueryClient } from "react-query" ;
14
12
import { useParams } from "react-router-dom" ;
15
13
import {
16
14
addOrganizationMember ,
17
15
organizationMembers ,
18
16
removeOrganizationMember ,
19
17
} from "api/queries/organizations" ;
20
- import { roles } from "api/queries/roles" ;
21
- import type { OrganizationMemberWithUserData } from "api/typesGenerated" ;
18
+ import type { OrganizationMemberWithUserData , User } from "api/typesGenerated" ;
22
19
import { ErrorAlert } from "components/Alert/ErrorAlert" ;
20
+ import { AvatarData } from "components/AvatarData/AvatarData" ;
21
+ import { PageHeader , PageHeaderTitle } from "components/PageHeader/PageHeader" ;
23
22
import { Pill } from "components/Pill/Pill" ;
24
- import { useOrganizationSettings } from "./ManagementSettingsLayout" ;
23
+ import { UserAvatar } from "components/UserAvatar/UserAvatar" ;
24
+ import {
25
+ MoreMenu ,
26
+ MoreMenuTrigger ,
27
+ MoreMenuContent ,
28
+ MoreMenuItem ,
29
+ ThreeDotsButton ,
30
+ } from "components/MoreMenu/MoreMenu" ;
31
+ import Divider from "@mui/material/Divider" ;
32
+ import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete" ;
33
+ import { displayError } from "components/GlobalSnackbar/utils" ;
34
+ import { getErrorMessage } from "api/errors" ;
35
+ import LoadingButton from "@mui/lab/LoadingButton" ;
36
+ import { Stack } from "components/Stack/Stack" ;
37
+
38
+ function doNothingTemporarily ( ) {
39
+ console . log ( "ok" ) ;
40
+ }
25
41
26
42
const OrganizationMembersPage : FC = ( ) => {
27
43
const queryClient = useQueryClient ( ) ;
44
+ const theme = useTheme ( ) ;
28
45
const { organization } = useParams ( ) as { organization : string } ;
29
46
30
- const rolesQuery = useQuery ( roles ( ) ) ;
31
47
const membersQuery = useQuery ( organizationMembers ( organization ) ) ;
32
48
const addMemberMutation = useMutation (
33
49
addOrganizationMember ( queryClient , organization ) ,
@@ -36,76 +52,113 @@ const OrganizationMembersPage: FC = () => {
36
52
removeOrganizationMember ( queryClient , organization ) ,
37
53
) ;
38
54
39
- const { currentOrganizationId, organizations } = useOrganizationSettings ( ) ;
40
-
41
- const error = rolesQuery . error ?? membersQuery . error ;
42
-
43
- const theme = useTheme ( ) ;
55
+ const error =
56
+ membersQuery . error ?? addMemberMutation . error ?? removeMemberMutation . error ;
44
57
45
58
return (
46
59
< div >
47
60
< PageHeader >
48
61
< PageHeaderTitle > Organization members</ PageHeaderTitle >
49
62
</ PageHeader >
50
63
51
- { Boolean ( error ) && (
52
- < div css = { { marginBottom : 32 } } >
53
- < ErrorAlert error = { error } />
54
- </ div >
55
- ) }
56
-
57
- < TableContainer >
58
- < Table >
59
- < TableHead >
60
- < TableRow >
61
- < TableCell width = "50%" > User</ TableCell >
62
- < TableCell width = "49%" > Roles</ TableCell >
63
- < TableCell width = "1%" > </ TableCell >
64
- </ TableRow >
65
- </ TableHead >
66
- < TableBody >
67
- { membersQuery . data ?. map ( ( member ) => (
68
- < TableRow key = { member . user_id } >
69
- < TableCell >
70
- < AvatarData
71
- avatar = {
72
- < UserAvatar
73
- username = { member . username }
74
- avatarURL = { member . avatar_url }
75
- />
76
- }
77
- title = { member . name }
78
- subtitle = { member . username }
79
- />
80
- </ TableCell >
81
- < TableCell >
82
- { getMemberRoles ( member ) . map ( ( role ) => (
83
- < Pill
84
- key = { role . name }
85
- css = { {
86
- backgroundColor : role . global
87
- ? theme . roles . info . background
88
- : theme . roles . inactive . background ,
89
- borderColor : role . global
90
- ? theme . roles . info . outline
91
- : theme . roles . inactive . outline ,
92
- } }
93
- >
94
- { role . name }
95
- { role . global && (
96
- < Tooltip title = "This user has blah blah permissions for all organziations." >
97
- < span > *</ span >
98
- </ Tooltip >
99
- ) }
100
- </ Pill >
101
- ) ) }
102
- </ TableCell >
103
- < TableCell > </ TableCell >
64
+ < Stack >
65
+ { Boolean ( error ) && < ErrorAlert error = { error } /> }
66
+
67
+ < AddGroupMember
68
+ isLoading = { addMemberMutation . isLoading }
69
+ onSubmit = { async ( user ) => {
70
+ try {
71
+ await addMemberMutation . mutateAsync ( user . id ) ;
72
+ void membersQuery . refetch ( ) ;
73
+ } catch ( error ) {
74
+ displayError ( getErrorMessage ( error , "Failed to add member." ) ) ;
75
+ }
76
+ } }
77
+ />
78
+
79
+ < TableContainer >
80
+ < Table >
81
+ < TableHead >
82
+ < TableRow >
83
+ < TableCell width = "50%" > User</ TableCell >
84
+ < TableCell width = "49%" > Roles</ TableCell >
85
+ < TableCell width = "1%" > </ TableCell >
104
86
</ TableRow >
105
- ) ) }
106
- </ TableBody >
107
- </ Table >
108
- </ TableContainer >
87
+ </ TableHead >
88
+ < TableBody >
89
+ { membersQuery . data ?. map ( ( member ) => (
90
+ < TableRow key = { member . user_id } >
91
+ < TableCell >
92
+ < AvatarData
93
+ avatar = {
94
+ < UserAvatar
95
+ username = { member . username }
96
+ avatarURL = { member . avatar_url }
97
+ />
98
+ }
99
+ title = { member . name }
100
+ subtitle = { member . username }
101
+ />
102
+ </ TableCell >
103
+ < TableCell >
104
+ { getMemberRoles ( member ) . map ( ( role ) => (
105
+ < Pill
106
+ key = { role . name }
107
+ css = { {
108
+ backgroundColor : role . global
109
+ ? theme . roles . info . background
110
+ : theme . roles . inactive . background ,
111
+ borderColor : role . global
112
+ ? theme . roles . info . outline
113
+ : theme . roles . inactive . outline ,
114
+ } }
115
+ >
116
+ { role . global ? (
117
+ < Tooltip title = "This user has this role for all organziations." >
118
+ < span > { role . name } *</ span >
119
+ </ Tooltip >
120
+ ) : (
121
+ role . name
122
+ ) }
123
+ </ Pill >
124
+ ) ) }
125
+ </ TableCell >
126
+ < TableCell >
127
+ < MoreMenu >
128
+ < MoreMenuTrigger >
129
+ < ThreeDotsButton />
130
+ </ MoreMenuTrigger >
131
+ < MoreMenuContent >
132
+ < MoreMenuItem onClick = { ( ) => doNothingTemporarily ( ) } >
133
+ View workspaces
134
+ </ MoreMenuItem >
135
+ < MoreMenuItem onClick = { ( ) => doNothingTemporarily ( ) } >
136
+ View activity
137
+ </ MoreMenuItem >
138
+ < MoreMenuItem onClick = { ( ) => doNothingTemporarily ( ) } >
139
+ Reset password…
140
+ </ MoreMenuItem >
141
+ < Divider />
142
+ < MoreMenuItem
143
+ onClick = { async ( ) => {
144
+ await removeMemberMutation . mutateAsync (
145
+ member . user_id ,
146
+ ) ;
147
+ membersQuery . refetch ( ) ;
148
+ } }
149
+ danger
150
+ >
151
+ Delete…
152
+ </ MoreMenuItem >
153
+ </ MoreMenuContent >
154
+ </ MoreMenu >
155
+ </ TableCell >
156
+ </ TableRow >
157
+ ) ) }
158
+ </ TableBody >
159
+ </ Table >
160
+ </ TableContainer >
161
+ </ Stack >
109
162
</ div >
110
163
) ;
111
164
} ;
@@ -133,3 +186,53 @@ function getMemberRoles(member: OrganizationMemberWithUserData) {
133
186
}
134
187
135
188
export default OrganizationMembersPage ;
189
+
190
+ interface AddGroupMemberProps {
191
+ isLoading : boolean ;
192
+ onSubmit : ( user : User ) => void ;
193
+ }
194
+
195
+ const AddGroupMember : FC < AddGroupMemberProps > = ( { isLoading, onSubmit } ) => {
196
+ const [ selectedUser , setSelectedUser ] = useState < User | null > ( null ) ;
197
+
198
+ return (
199
+ < form
200
+ onSubmit = { async ( e ) => {
201
+ e . preventDefault ( ) ;
202
+
203
+ if ( selectedUser ) {
204
+ try {
205
+ await onSubmit ( selectedUser ) ;
206
+ setSelectedUser ( null ) ;
207
+ } catch { }
208
+ }
209
+ } }
210
+ >
211
+ < Stack direction = "row" alignItems = "center" spacing = { 1 } >
212
+ < UserAutocomplete
213
+ css = { styles . autoComplete }
214
+ value = { selectedUser }
215
+ onChange = { ( newValue ) => {
216
+ setSelectedUser ( newValue ) ;
217
+ } }
218
+ />
219
+
220
+ < LoadingButton
221
+ loadingPosition = "start"
222
+ disabled = { ! selectedUser }
223
+ type = "submit"
224
+ startIcon = { < PersonAdd /> }
225
+ loading = { isLoading }
226
+ >
227
+ Add user
228
+ </ LoadingButton >
229
+ </ Stack >
230
+ </ form >
231
+ ) ;
232
+ } ;
233
+
234
+ const styles = {
235
+ autoComplete : {
236
+ width : 300 ,
237
+ } ,
238
+ } satisfies Record < string , Interpolation < Theme > > ;
0 commit comments