55 */
66
77import { OrganizationSettings } from "@gitpod/gitpod-protocol" ;
8- import React , { useCallback , useState } from "react" ;
8+ import React , { useCallback , useState , useEffect } from "react" ;
99import Alert from "../components/Alert" ;
1010import { Button } from "../components/Button" ;
1111import { CheckboxInputField } from "../components/forms/CheckboxInputField" ;
@@ -14,13 +14,14 @@ import { TextInputField } from "../components/forms/TextInputField";
1414import { Heading2 , Subheading } from "../components/typography/headings" ;
1515import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation" ;
1616import { 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" ;
1818import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation" ;
1919import { useOnBlurError } from "../hooks/use-onblur-error" ;
2020import { teamsService } from "../service/public-api" ;
2121import { gitpodHostUrl } from "../service/service" ;
2222import { useCurrentUser } from "../user-context" ;
2323import { OrgSettingsPage } from "./OrgSettingsPage" ;
24+ import { useToast } from "../components/toasts/Toasts" ;
2425
2526export default function TeamSettingsPage ( ) {
2627 const user = useCurrentUser ( ) ;
@@ -31,21 +32,6 @@ export default function TeamSettingsPage() {
3132 const [ teamName , setTeamName ] = useState ( org ?. name || "" ) ;
3233 const [ updated , setUpdated ] = useState ( false ) ;
3334 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- ) ;
4935
5036 const close = ( ) => setModal ( false ) ;
5137
@@ -60,6 +46,9 @@ export default function TeamSettingsPage() {
6046
6147 const updateTeamInformation = useCallback (
6248 async ( e : React . FormEvent ) => {
49+ if ( ! org ?. isOwner ) {
50+ return ;
51+ }
6352 e . preventDefault ( ) ;
6453
6554 if ( ! orgFormIsValid ) {
@@ -74,7 +63,7 @@ export default function TeamSettingsPage() {
7463 console . error ( error ) ;
7564 }
7665 } ,
77- [ orgFormIsValid , updateOrg , teamName ] ,
66+ [ orgFormIsValid , updateOrg , teamName , org ] ,
7867 ) ;
7968
8069 const deleteTeam = useCallback ( async ( ) => {
@@ -99,12 +88,6 @@ export default function TeamSettingsPage() {
9988 < span > { updateOrg . error . message || "unknown error" } </ span >
10089 </ Alert >
10190 ) }
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- ) }
10891 { updated && (
10992 < Alert type = "message" closable = { true } className = "mb-2 max-w-xl rounded-md" >
11093 Organization name has been updated.
@@ -117,30 +100,27 @@ export default function TeamSettingsPage() {
117100 value = { teamName }
118101 error = { teamNameError . message }
119102 onChange = { setTeamName }
103+ disabled = { ! org ?. isOwner }
120104 onBlur = { teamNameError . onBlur }
121105 />
122106
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+ ) }
135112 </ form >
136113
137- { user ?. organizationId !== org ?. id && (
114+ < OrgSettingsForm org = { org } />
115+
116+ { user ?. organizationId !== org ?. id && org ?. isOwner && (
138117 < >
139118 < Heading2 className = "pt-12" > Delete Organization</ Heading2 >
140119 < Subheading className = "pb-4 max-w-2xl" >
141120 Deleting this organization will also remove all associated data, including projects and
142121 workspaces. Deleted organizations cannot be restored!
143122 </ Subheading >
123+
144124 < button className = "danger secondary" onClick = { ( ) => setModal ( true ) } >
145125 Delete Organization
146126 </ button >
@@ -185,3 +165,92 @@ export default function TeamSettingsPage() {
185165 </ >
186166 ) ;
187167}
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