@@ -8,11 +8,15 @@ import { Team } from "@gitpod/gitpod-protocol";
8
8
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode" ;
9
9
import React , { useCallback , useState } from "react" ;
10
10
import Alert from "../components/Alert" ;
11
+ import { Button } from "../components/Button" ;
11
12
import ConfirmationModal from "../components/ConfirmationModal" ;
13
+ import { TextInputField } from "../components/forms/TextInputField" ;
12
14
import { Heading2 , Subheading } from "../components/typography/headings" ;
13
15
import { useCurrentOrg , useOrganizationsInvalidator } from "../data/organizations/orgs-query" ;
16
+ import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation" ;
17
+ import { useOnBlurError } from "../hooks/use-onblur-error" ;
14
18
import { teamsService } from "../service/public-api" ;
15
- import { getGitpodService , gitpodHostUrl } from "../service/service" ;
19
+ import { gitpodHostUrl } from "../service/service" ;
16
20
import { useCurrentUser } from "../user-context" ;
17
21
import { OrgSettingsPage } from "./OrgSettingsPage" ;
18
22
@@ -59,63 +63,44 @@ export default function TeamSettings() {
59
63
const [ teamNameToDelete , setTeamNameToDelete ] = useState ( "" ) ;
60
64
const [ teamName , setTeamName ] = useState ( org ?. name || "" ) ;
61
65
const [ slug , setSlug ] = useState ( org ?. slug || "" ) ;
62
- const [ errorMessage , setErrorMessage ] = useState < string | undefined > ( undefined ) ;
63
66
const [ updated , setUpdated ] = useState ( false ) ;
67
+ const updateOrg = useUpdateOrgMutation ( ) ;
64
68
65
69
const close = ( ) => setModal ( false ) ;
66
70
67
- const updateTeamInformation = useCallback ( async ( ) => {
68
- if ( ! org || errorMessage ) {
69
- return ;
70
- }
71
- try {
72
- await getGitpodService ( ) . server . updateTeam ( org . id , { name : teamName , slug } ) ;
73
- invalidateOrgs ( ) ;
74
- setUpdated ( true ) ;
75
- setTimeout ( ( ) => setUpdated ( false ) , 3000 ) ;
76
- } catch ( error ) {
77
- setErrorMessage ( `Failed to update organization information: ${ error . message } ` ) ;
78
- }
79
- } , [ org , errorMessage , slug , teamName , invalidateOrgs ] ) ;
71
+ const teamNameError = useOnBlurError (
72
+ teamName . length > 32
73
+ ? "Organization name must not be longer than 32 characters"
74
+ : "Organization name can not be blank" ,
75
+ ! ! teamName && teamName . length <= 32 ,
76
+ ) ;
80
77
81
- const onNameChange = useCallback (
82
- async ( event : React . ChangeEvent < HTMLInputElement > ) => {
83
- if ( ! org ) {
84
- return ;
85
- }
86
- const newName = event . target . value || "" ;
87
- setTeamName ( newName ) ;
88
- if ( newName . trim ( ) . length === 0 ) {
89
- setErrorMessage ( "Organization name can not be blank." ) ;
90
- return ;
91
- } else if ( newName . trim ( ) . length > 32 ) {
92
- setErrorMessage ( "Organization name must not be longer than 32 characters." ) ;
93
- return ;
94
- } else {
95
- setErrorMessage ( undefined ) ;
96
- }
97
- } ,
98
- [ org ] ,
78
+ const slugError = useOnBlurError (
79
+ slug . length > 100
80
+ ? "Organization slug must not be longer than 100 characters"
81
+ : "Organization slug can not be blank." ,
82
+ ! ! slug && slug . length <= 100 ,
99
83
) ;
100
84
101
- const onSlugChange = useCallback (
102
- async ( event : React . ChangeEvent < HTMLInputElement > ) => {
103
- if ( ! org ) {
85
+ const orgFormIsValid = teamNameError . isValid && slugError . isValid ;
86
+
87
+ const updateTeamInformation = useCallback (
88
+ async ( e : React . FormEvent ) => {
89
+ e . preventDefault ( ) ;
90
+
91
+ if ( ! orgFormIsValid ) {
104
92
return ;
105
93
}
106
- const newSlug = event . target . value || "" ;
107
- setSlug ( newSlug ) ;
108
- if ( newSlug . trim ( ) . length === 0 ) {
109
- setErrorMessage ( "Organization slug can not be blank." ) ;
110
- return ;
111
- } else if ( newSlug . trim ( ) . length > 100 ) {
112
- setErrorMessage ( "Organization slug must not be longer than 100 characters." ) ;
113
- return ;
114
- } else {
115
- setErrorMessage ( undefined ) ;
94
+
95
+ try {
96
+ await updateOrg . mutateAsync ( { name : teamName , slug } ) ;
97
+ setUpdated ( true ) ;
98
+ setTimeout ( ( ) => setUpdated ( false ) , 3000 ) ;
99
+ } catch ( error ) {
100
+ console . error ( error ) ;
116
101
}
117
102
} ,
118
- [ org ] ,
103
+ [ orgFormIsValid , updateOrg , teamName , slug ] ,
119
104
) ;
120
105
121
106
const deleteTeam = useCallback ( async ( ) => {
@@ -131,45 +116,47 @@ export default function TeamSettings() {
131
116
return (
132
117
< >
133
118
< OrgSettingsPage title = "Organization Settings" subtitle = "Manage your organization's settings." >
134
- < Heading2 > Organization Name</ Heading2 >
135
- < Subheading className = "max-w-2xl" >
136
- This is your organization's visible name within Gitpod. For example, the name of your company.
137
- </ Subheading >
138
- { errorMessage && (
119
+ < Heading2 > Organization Details</ Heading2 >
120
+ < Subheading className = "max-w-2xl" > Details of your organization within Gitpod.</ Subheading >
121
+
122
+ { updateOrg . isError && (
139
123
< Alert type = "error" closable = { true } className = "mb-2 max-w-xl rounded-md" >
140
- { errorMessage }
124
+ < span > Failed to update organization information: </ span >
125
+ < span > { updateOrg . error . message || "unknown error" } </ span >
141
126
</ Alert >
142
127
) }
143
128
{ updated && (
144
129
< Alert type = "message" closable = { true } className = "mb-2 max-w-xl rounded-md" >
145
130
Organization name has been updated.
146
131
</ Alert >
147
132
) }
148
- < div className = "flex flex-col lg:flex-row" >
149
- < div >
150
- < div className = "mt-4 mb-3" >
151
- < h4 > Name</ h4 >
152
- < input type = "text" value = { teamName } onChange = { onNameChange } />
153
- </ div >
154
- </ div >
155
- </ div >
156
- < div className = "flex flex-col lg:flex-row" >
157
- < div >
158
- < div className = "mt-4 mb-3" >
159
- < h4 > Slug</ h4 >
160
- < input type = "text" value = { slug } onChange = { onSlugChange } />
161
- </ div >
162
- </ div >
163
- </ div >
164
- < div className = "flex flex-row" >
165
- < button
166
- className = "primary"
167
- disabled = { ( org ?. name === teamName && org ?. slug === slug ) || ! ! errorMessage }
168
- onClick = { updateTeamInformation }
133
+ < form onSubmit = { updateTeamInformation } >
134
+ < TextInputField
135
+ label = "Name"
136
+ hint = "The name of your company or organization"
137
+ value = { teamName }
138
+ error = { teamNameError . message }
139
+ onChange = { setTeamName }
140
+ onBlur = { teamNameError . onBlur }
141
+ />
142
+
143
+ < TextInputField
144
+ label = "Slug"
145
+ hint = "The slug will be used for easier signin and discovery"
146
+ value = { slug }
147
+ error = { slugError . message }
148
+ onChange = { setSlug }
149
+ onBlur = { slugError . onBlur }
150
+ />
151
+
152
+ < Button
153
+ className = "mt-4"
154
+ htmlType = "submit"
155
+ disabled = { ( org ?. name === teamName && org ?. slug === slug ) || ! orgFormIsValid }
169
156
>
170
- Update Organization Name
171
- </ button >
172
- </ div >
157
+ Update Organization
158
+ </ Button >
159
+ </ form >
173
160
174
161
< Heading2 className = "pt-12" > Delete Organization</ Heading2 >
175
162
< Subheading className = "pb-4 max-w-2xl" >
0 commit comments