5
5
*/
6
6
7
7
import { OrganizationSettings } from "@gitpod/gitpod-protocol" ;
8
- import React , { useCallback , useState } from "react" ;
8
+ import React , { useCallback , useState , useEffect } from "react" ;
9
9
import Alert from "../components/Alert" ;
10
10
import { Button } from "../components/Button" ;
11
11
import { CheckboxInputField } from "../components/forms/CheckboxInputField" ;
@@ -14,13 +14,14 @@ import { TextInputField } from "../components/forms/TextInputField";
14
14
import { Heading2 , Subheading } from "../components/typography/headings" ;
15
15
import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation" ;
16
16
import { useOrgSettingsQuery } from "../data/organizations/org-settings-query" ;
17
- import { useCurrentOrg , useOrganizationsInvalidator } from "../data/organizations/orgs-query" ;
17
+ import { OrganizationInfo , useCurrentOrg , useOrganizationsInvalidator } from "../data/organizations/orgs-query" ;
18
18
import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation" ;
19
19
import { useOnBlurError } from "../hooks/use-onblur-error" ;
20
20
import { teamsService } from "../service/public-api" ;
21
21
import { gitpodHostUrl } from "../service/service" ;
22
22
import { useCurrentUser } from "../user-context" ;
23
23
import { OrgSettingsPage } from "./OrgSettingsPage" ;
24
+ import { useToast } from "../components/toasts/Toasts" ;
24
25
25
26
export default function TeamSettingsPage ( ) {
26
27
const user = useCurrentUser ( ) ;
@@ -31,21 +32,6 @@ export default function TeamSettingsPage() {
31
32
const [ teamName , setTeamName ] = useState ( org ?. name || "" ) ;
32
33
const [ updated , setUpdated ] = useState ( false ) ;
33
34
const updateOrg = useUpdateOrgMutation ( ) ;
34
- const { data : settings , isLoading } = useOrgSettingsQuery ( ) ;
35
- const updateTeamSettings = useUpdateOrgSettingsMutation ( ) ;
36
-
37
- const handleUpdateTeamSettings = useCallback (
38
- ( newSettings : Partial < OrganizationSettings > ) => {
39
- if ( ! org ?. id ) {
40
- throw new Error ( "no organization selected" ) ;
41
- }
42
- updateTeamSettings . mutate ( {
43
- ...settings ,
44
- ...newSettings ,
45
- } ) ;
46
- } ,
47
- [ updateTeamSettings , org ?. id , settings ] ,
48
- ) ;
49
35
50
36
const close = ( ) => setModal ( false ) ;
51
37
@@ -60,6 +46,9 @@ export default function TeamSettingsPage() {
60
46
61
47
const updateTeamInformation = useCallback (
62
48
async ( e : React . FormEvent ) => {
49
+ if ( ! org ?. isOwner ) {
50
+ return ;
51
+ }
63
52
e . preventDefault ( ) ;
64
53
65
54
if ( ! orgFormIsValid ) {
@@ -74,7 +63,7 @@ export default function TeamSettingsPage() {
74
63
console . error ( error ) ;
75
64
}
76
65
} ,
77
- [ orgFormIsValid , updateOrg , teamName ] ,
66
+ [ orgFormIsValid , updateOrg , teamName , org ] ,
78
67
) ;
79
68
80
69
const deleteTeam = useCallback ( async ( ) => {
@@ -99,12 +88,6 @@ export default function TeamSettingsPage() {
99
88
< span > { updateOrg . error . message || "unknown error" } </ span >
100
89
</ Alert >
101
90
) }
102
- { updateTeamSettings . isError && (
103
- < Alert type = "error" closable = { true } className = "mb-2 max-w-xl rounded-md" >
104
- < span > Failed to update organization settings: </ span >
105
- < span > { updateTeamSettings . error . message || "unknown error" } </ span >
106
- </ Alert >
107
- ) }
108
91
{ updated && (
109
92
< Alert type = "message" closable = { true } className = "mb-2 max-w-xl rounded-md" >
110
93
Organization name has been updated.
@@ -117,30 +100,27 @@ export default function TeamSettingsPage() {
117
100
value = { teamName }
118
101
error = { teamNameError . message }
119
102
onChange = { setTeamName }
103
+ disabled = { ! org ?. isOwner }
120
104
onBlur = { teamNameError . onBlur }
121
105
/>
122
106
123
- < Button className = "mt-4" htmlType = "submit" disabled = { org ?. name === teamName || ! orgFormIsValid } >
124
- Update Organization
125
- </ Button >
126
-
127
- < Heading2 className = "pt-12" > Collaboration & Sharing </ Heading2 >
128
- < CheckboxInputField
129
- label = "Workspace Sharing"
130
- hint = "Allow workspaces created within an Organization to share the workspace with any authenticated user."
131
- checked = { ! settings ?. workspaceSharingDisabled }
132
- onChange = { ( checked ) => handleUpdateTeamSettings ( { workspaceSharingDisabled : ! checked } ) }
133
- disabled = { isLoading }
134
- />
107
+ { org ?. isOwner && (
108
+ < Button className = "mt-4" htmlType = "submit" disabled = { org ?. name === teamName || ! orgFormIsValid } >
109
+ Update Organization
110
+ </ Button >
111
+ ) }
135
112
</ form >
136
113
137
- { user ?. organizationId !== org ?. id && (
114
+ < OrgSettingsForm org = { org } />
115
+
116
+ { user ?. organizationId !== org ?. id && org ?. isOwner && (
138
117
< >
139
118
< Heading2 className = "pt-12" > Delete Organization</ Heading2 >
140
119
< Subheading className = "pb-4 max-w-2xl" >
141
120
Deleting this organization will also remove all associated data, including projects and
142
121
workspaces. Deleted organizations cannot be restored!
143
122
</ Subheading >
123
+
144
124
< button className = "danger secondary" onClick = { ( ) => setModal ( true ) } >
145
125
Delete Organization
146
126
</ button >
@@ -185,3 +165,92 @@ export default function TeamSettingsPage() {
185
165
</ >
186
166
) ;
187
167
}
168
+
169
+ function OrgSettingsForm ( props : { org ?: OrganizationInfo } ) {
170
+ const { org } = props ;
171
+ const { data : settings , isLoading } = useOrgSettingsQuery ( ) ;
172
+ const updateTeamSettings = useUpdateOrgSettingsMutation ( ) ;
173
+ const [ defaultWorkspaceImage , setDefaultWorkspaceImage ] = useState ( settings ?. defaultWorkspaceImage ?? "" ) ;
174
+ const { toast } = useToast ( ) ;
175
+
176
+ useEffect ( ( ) => {
177
+ if ( ! settings ) {
178
+ return ;
179
+ }
180
+ setDefaultWorkspaceImage ( settings . defaultWorkspaceImage ?? "" ) ;
181
+ } , [ settings ] ) ;
182
+
183
+ const handleUpdateTeamSettings = useCallback (
184
+ async ( newSettings : Partial < OrganizationSettings > ) => {
185
+ if ( ! org ?. id ) {
186
+ throw new Error ( "no organization selected" ) ;
187
+ }
188
+ if ( ! org . isOwner ) {
189
+ throw new Error ( "no organization settings change permission" ) ;
190
+ }
191
+ try {
192
+ await updateTeamSettings . mutateAsync ( {
193
+ // We don't want to have original setting passed, since defaultWorkspaceImage could be undefined
194
+ // to bring compatibility when we're going to change Gitpod install value's defaultImage setting
195
+ ...newSettings ,
196
+ } ) ;
197
+ if ( newSettings . defaultWorkspaceImage ) {
198
+ toast ( "Default workspace image has been updated." ) ;
199
+ }
200
+ } catch ( error ) {
201
+ console . error ( error ) ;
202
+ toast (
203
+ error . message
204
+ ? "Failed to update organization settings: " + error . message
205
+ : "Oh no, there was a problem with our service." ,
206
+ ) ;
207
+ }
208
+ } ,
209
+ [ updateTeamSettings , org ?. id , org ?. isOwner , toast ] ,
210
+ ) ;
211
+
212
+ return (
213
+ < form
214
+ onSubmit = { ( e ) => {
215
+ e . preventDefault ( ) ;
216
+ handleUpdateTeamSettings ( { defaultWorkspaceImage } ) ;
217
+ } }
218
+ >
219
+ < Heading2 className = "pt-12" > Collaboration & Sharing </ Heading2 >
220
+ < Subheading className = "max-w-2xl" >
221
+ Choose which workspace images you want to use for your workspaces.
222
+ </ Subheading >
223
+
224
+ { updateTeamSettings . isError && (
225
+ < Alert type = "error" closable = { true } className = "mb-2 max-w-xl rounded-md" >
226
+ < span > Failed to update organization settings: </ span >
227
+ < span > { updateTeamSettings . error . message || "unknown error" } </ span >
228
+ </ Alert >
229
+ ) }
230
+
231
+ < CheckboxInputField
232
+ label = "Workspace Sharing"
233
+ hint = "Allow workspaces created within an Organization to share the workspace with any authenticated user."
234
+ checked = { ! settings ?. workspaceSharingDisabled }
235
+ onChange = { ( checked ) => handleUpdateTeamSettings ( { workspaceSharingDisabled : ! checked } ) }
236
+ disabled = { isLoading || ! org ?. isOwner }
237
+ />
238
+
239
+ < Heading2 className = "pt-12" > Workspace Settings</ Heading2 >
240
+ < TextInputField
241
+ label = "Default Image"
242
+ // TODO: Provide document links
243
+ hint = "Use any official Gitpod Docker image, or Docker image reference"
244
+ value = { defaultWorkspaceImage }
245
+ onChange = { setDefaultWorkspaceImage }
246
+ disabled = { isLoading || ! org ?. isOwner }
247
+ />
248
+
249
+ { org ?. isOwner && (
250
+ < Button htmlType = "submit" className = "mt-4" disabled = { ! org . isOwner } >
251
+ Update Default Image
252
+ </ Button >
253
+ ) }
254
+ </ form >
255
+ ) ;
256
+ }
0 commit comments