66
77import React , { useCallback , useState } from "react" ;
88import Alert from "../components/Alert" ;
9+ import { Button } from "../components/Button" ;
910import ConfirmationModal from "../components/ConfirmationModal" ;
11+ import { TextInputField } from "../components/forms/TextInputField" ;
1012import { Heading2 , Subheading } from "../components/typography/headings" ;
1113import { useCurrentOrg , useOrganizationsInvalidator } from "../data/organizations/orgs-query" ;
14+ import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation" ;
15+ import { useOnBlurError } from "../hooks/use-onblur-error" ;
1216import { teamsService } from "../service/public-api" ;
13- import { getGitpodService , gitpodHostUrl } from "../service/service" ;
17+ import { gitpodHostUrl } from "../service/service" ;
1418import { useCurrentUser } from "../user-context" ;
1519import { OrgSettingsPage } from "./OrgSettingsPage" ;
1620
@@ -21,43 +25,45 @@ export default function TeamSettings() {
2125 const [ modal , setModal ] = useState ( false ) ;
2226 const [ teamNameToDelete , setTeamNameToDelete ] = useState ( "" ) ;
2327 const [ teamName , setTeamName ] = useState ( org ?. name || "" ) ;
24- const [ errorMessage , setErrorMessage ] = useState < string | undefined > ( undefined ) ;
28+ const [ slug , setSlug ] = useState ( org ?. slug || "" ) ;
2529 const [ updated , setUpdated ] = useState ( false ) ;
30+ const updateOrg = useUpdateOrgMutation ( ) ;
2631
2732 const close = ( ) => setModal ( false ) ;
2833
29- const updateTeamInformation = useCallback ( async ( ) => {
30- if ( ! org || errorMessage ) {
31- return ;
32- }
33- try {
34- await getGitpodService ( ) . server . updateTeam ( org . id , { name : teamName } ) ;
35- invalidateOrgs ( ) ;
36- setUpdated ( true ) ;
37- setTimeout ( ( ) => setUpdated ( false ) , 3000 ) ;
38- } catch ( error ) {
39- setErrorMessage ( `Failed to update organization information: ${ error . message } ` ) ;
40- }
41- } , [ org , errorMessage , teamName , invalidateOrgs ] ) ;
34+ const teamNameError = useOnBlurError (
35+ teamName . length > 32
36+ ? "Organization name must not be longer than 32 characters"
37+ : "Organization name can not be blank" ,
38+ ! ! teamName && teamName . length <= 32 ,
39+ ) ;
40+
41+ const slugError = useOnBlurError (
42+ slug . length > 100
43+ ? "Organization slug must not be longer than 100 characters"
44+ : "Organization slug can not be blank." ,
45+ ! ! slug && slug . length <= 100 ,
46+ ) ;
47+
48+ const orgFormIsValid = teamNameError . isValid && slugError . isValid ;
49+
50+ const updateTeamInformation = useCallback (
51+ async ( e : React . FormEvent ) => {
52+ e . preventDefault ( ) ;
4253
43- const onNameChange = useCallback (
44- async ( event : React . ChangeEvent < HTMLInputElement > ) => {
45- if ( ! org ) {
54+ if ( ! orgFormIsValid ) {
4655 return ;
4756 }
48- const newName = event . target . value || "" ;
49- setTeamName ( newName ) ;
50- if ( newName . trim ( ) . length === 0 ) {
51- setErrorMessage ( "Organization name can not be blank." ) ;
52- return ;
53- } else if ( newName . trim ( ) . length > 32 ) {
54- setErrorMessage ( "Organization name must not be longer than 32 characters." ) ;
55- return ;
56- } else {
57- setErrorMessage ( undefined ) ;
57+
58+ try {
59+ await updateOrg . mutateAsync ( { name : teamName , slug } ) ;
60+ setUpdated ( true ) ;
61+ setTimeout ( ( ) => setUpdated ( false ) , 3000 ) ;
62+ } catch ( error ) {
63+ console . error ( error ) ;
5864 }
5965 } ,
60- [ org ] ,
66+ [ orgFormIsValid , updateOrg , teamName , slug ] ,
6167 ) ;
6268
6369 const deleteTeam = useCallback ( async ( ) => {
@@ -73,37 +79,47 @@ export default function TeamSettings() {
7379 return (
7480 < >
7581 < OrgSettingsPage >
76- < Heading2 > Organization Name</ Heading2 >
77- < Subheading className = "max-w-2xl" >
78- This is your organization's visible name within Gitpod. For example, the name of your company.
79- </ Subheading >
80- { errorMessage && (
82+ < Heading2 > Organization Details</ Heading2 >
83+ < Subheading className = "max-w-2xl" > Details of your organization within Gitpod.</ Subheading >
84+
85+ { updateOrg . isError && (
8186 < Alert type = "error" closable = { true } className = "mb-2 max-w-xl rounded-md" >
82- { errorMessage }
87+ < span > Failed to update organization information: </ span >
88+ < span > { updateOrg . error . message || "unknown error" } </ span >
8389 </ Alert >
8490 ) }
8591 { updated && (
8692 < Alert type = "message" closable = { true } className = "mb-2 max-w-xl rounded-md" >
8793 Organization name has been updated.
8894 </ Alert >
8995 ) }
90- < div className = "flex flex-col lg:flex-row" >
91- < div >
92- < div className = "mt-4 mb-3" >
93- < h4 > Name</ h4 >
94- < input type = "text" value = { teamName } onChange = { onNameChange } />
95- </ div >
96- </ div >
97- </ div >
98- < div className = "flex flex-row" >
99- < button
100- className = "primary"
101- disabled = { org ?. name === teamName || ! ! errorMessage }
102- onClick = { updateTeamInformation }
96+ < form onSubmit = { updateTeamInformation } >
97+ < TextInputField
98+ label = "Name"
99+ hint = "The name of your company or organization"
100+ value = { teamName }
101+ error = { teamNameError . message }
102+ onChange = { setTeamName }
103+ onBlur = { teamNameError . onBlur }
104+ />
105+
106+ < TextInputField
107+ label = "Slug"
108+ hint = "The slug will be used for easier signin and discovery"
109+ value = { slug }
110+ error = { slugError . message }
111+ onChange = { setSlug }
112+ onBlur = { slugError . onBlur }
113+ />
114+
115+ < Button
116+ className = "mt-4"
117+ htmlType = "submit"
118+ disabled = { ( org ?. name === teamName && org ?. slug === slug ) || ! orgFormIsValid }
103119 >
104- Update Organization Name
105- </ button >
106- </ div >
120+ Update Organization
121+ </ Button >
122+ </ form >
107123
108124 < Heading2 className = "pt-12" > Delete Organization</ Heading2 >
109125 < Subheading className = "pb-4 max-w-2xl" >
0 commit comments