6
6
7
7
import { Team } from "@gitpod/gitpod-protocol" ;
8
8
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode" ;
9
- import { useContext , useEffect , useState } from "react" ;
10
- import { Redirect , useLocation } from "react-router" ;
9
+ import React , { useCallback , useContext , useEffect , useState } from "react" ;
10
+ import { Redirect } from "react-router" ;
11
+ import Alert from "../components/Alert" ;
11
12
import ConfirmationModal from "../components/ConfirmationModal" ;
12
13
import { PageWithSubMenu } from "../components/PageWithSubMenu" ;
13
14
import { publicApiTeamMembersToProtocol , teamsService } from "../service/public-api" ;
14
15
import { getGitpodService , gitpodHostUrl } from "../service/service" ;
15
- import { UserContext } from "../user-context" ;
16
- import { getCurrentTeam , TeamsContext } from "./teams-context" ;
16
+ import { useCurrentUser } from "../user-context" ;
17
+ import { TeamsContext , useCurrentTeam } from "./teams-context" ;
17
18
18
19
export function getTeamSettingsMenu ( params : { team ?: Team ; billingMode ?: BillingMode } ) {
19
20
const { team, billingMode } = params ;
@@ -35,14 +36,15 @@ export function getTeamSettingsMenu(params: { team?: Team; billingMode?: Billing
35
36
}
36
37
37
38
export default function TeamSettings ( ) {
39
+ const user = useCurrentUser ( ) ;
40
+ const team = useCurrentTeam ( ) ;
41
+ const { teams, setTeams } = useContext ( TeamsContext ) ;
38
42
const [ modal , setModal ] = useState ( false ) ;
39
- const [ teamSlug , setTeamSlug ] = useState ( "" ) ;
43
+ const [ teamName , setTeamName ] = useState ( team ?. name || "" ) ;
44
+ const [ errorMessage , setErrorMessage ] = useState < string | undefined > ( undefined ) ;
40
45
const [ isUserOwner , setIsUserOwner ] = useState ( true ) ;
41
- const { teams } = useContext ( TeamsContext ) ;
42
- const { user } = useContext ( UserContext ) ;
43
46
const [ billingMode , setBillingMode ] = useState < BillingMode | undefined > ( undefined ) ;
44
- const location = useLocation ( ) ;
45
- const team = getCurrentTeam ( location , teams ) ;
47
+ const [ updated , setUpdated ] = useState ( false ) ;
46
48
47
49
const close = ( ) => setModal ( false ) ;
48
50
@@ -60,20 +62,57 @@ export default function TeamSettings() {
60
62
const billingMode = await getGitpodService ( ) . server . getBillingModeForTeam ( team . id ) ;
61
63
setBillingMode ( billingMode ) ;
62
64
} ) ( ) ;
63
- } , [ ] ) ;
65
+ } , [ team , user ] ) ;
64
66
65
- if ( ! isUserOwner ) {
66
- return < Redirect to = "/" /> ;
67
- }
68
- const deleteTeam = async ( ) => {
67
+ const updateTeamInformation = useCallback ( async ( ) => {
68
+ if ( ! team || errorMessage || ! teams ) {
69
+ return ;
70
+ }
71
+ try {
72
+ const updatedTeam = await getGitpodService ( ) . server . updateTeam ( team . id , { name : teamName } ) ;
73
+ const updatedTeams = [ ...teams ?. filter ( ( t ) => t . id !== team . id ) ] ;
74
+ updatedTeams . push ( updatedTeam ) ;
75
+ setTeams ( updatedTeams ) ;
76
+ setUpdated ( true ) ;
77
+ setTimeout ( ( ) => setUpdated ( false ) , 3000 ) ;
78
+ } catch ( error ) {
79
+ setErrorMessage ( `Failed to update team information: ${ error . message } ` ) ;
80
+ }
81
+ } , [ team , errorMessage , teams , teamName , setTeams ] ) ;
82
+
83
+ const onNameChange = useCallback (
84
+ async ( event : React . ChangeEvent < HTMLInputElement > ) => {
85
+ if ( ! team ) {
86
+ return ;
87
+ }
88
+ const newName = event . target . value || "" ;
89
+ setTeamName ( newName ) ;
90
+ if ( newName . trim ( ) . length === 0 ) {
91
+ setErrorMessage ( "Team name can not be blank." ) ;
92
+ return ;
93
+ } else if ( newName . trim ( ) . length > 32 ) {
94
+ setErrorMessage ( "Team name must not be longer than 32 characters." ) ;
95
+ return ;
96
+ } else {
97
+ setErrorMessage ( undefined ) ;
98
+ }
99
+ } ,
100
+ [ team ] ,
101
+ ) ;
102
+
103
+ const deleteTeam = useCallback ( async ( ) => {
69
104
if ( ! team || ! user ) {
70
105
return ;
71
106
}
72
107
73
108
await teamsService . deleteTeam ( { teamId : team . id } ) ;
74
109
75
110
document . location . href = gitpodHostUrl . asDashboard ( ) . toString ( ) ;
76
- } ;
111
+ } , [ team , user ] ) ;
112
+
113
+ if ( ! isUserOwner ) {
114
+ return < Redirect to = "/" /> ;
115
+ }
77
116
78
117
return (
79
118
< >
@@ -82,7 +121,39 @@ export default function TeamSettings() {
82
121
title = "Settings"
83
122
subtitle = "Manage general team settings."
84
123
>
85
- < h3 > Delete Team</ h3 >
124
+ < h3 > Team Name</ h3 >
125
+ < p className = "text-base text-gray-500 max-w-2xl" >
126
+ This is your team's visible name within Gitpod. For example, the name of your company.
127
+ </ p >
128
+ { errorMessage && (
129
+ < Alert type = "error" closable = { true } className = "mb-2 max-w-xl rounded-md" >
130
+ { errorMessage }
131
+ </ Alert >
132
+ ) }
133
+ { updated && (
134
+ < Alert type = "message" closable = { true } className = "mb-2 max-w-xl rounded-md" >
135
+ Team name has been updated.
136
+ </ Alert >
137
+ ) }
138
+ < div className = "flex flex-col lg:flex-row" >
139
+ < div >
140
+ < div className = "mt-4 mb-3" >
141
+ < h4 > Name</ h4 >
142
+ < input type = "text" value = { teamName } onChange = { onNameChange } />
143
+ </ div >
144
+ </ div >
145
+ </ div >
146
+ < div className = "flex flex-row" >
147
+ < button
148
+ className = "primary"
149
+ disabled = { team ?. name === teamName || ! ! errorMessage }
150
+ onClick = { updateTeamInformation }
151
+ >
152
+ Update Team Name
153
+ </ button >
154
+ </ div >
155
+
156
+ < h3 className = "pt-12" > Delete Team</ h3 >
86
157
< p className = "text-base text-gray-500 pb-4 max-w-2xl" >
87
158
Deleting this team will also remove all associated data with this team, including projects and
88
159
workspaces. Deleted teams cannot be restored!
@@ -95,7 +166,7 @@ export default function TeamSettings() {
95
166
< ConfirmationModal
96
167
title = "Delete Team"
97
168
buttonText = "Delete Team"
98
- buttonDisabled = { teamSlug !== team ! . slug }
169
+ buttonDisabled = { teamName !== team ! . name }
99
170
visible = { modal }
100
171
warningText = "Warning: This action cannot be reversed."
101
172
onClose = { close }
@@ -117,7 +188,7 @@ export default function TeamSettings() {
117
188
< p className = "pt-4 pb-2 text-gray-600 dark:text-gray-400 text-base font-semibold" >
118
189
Type < code > { team ?. slug } </ code > to confirm
119
190
</ p >
120
- < input autoFocus className = "w-full" type = "text" onChange = { ( e ) => setTeamSlug ( e . target . value ) } > </ input >
191
+ < input autoFocus className = "w-full" type = "text" onChange = { ( e ) => setTeamName ( e . target . value ) } > </ input >
121
192
</ ConfirmationModal >
122
193
</ >
123
194
) ;
0 commit comments