Skip to content

Commit 6936b10

Browse files
committed
[dashboard] allow changing team name
fixes #5067
1 parent 1c90cb2 commit 6936b10

File tree

1 file changed

+89
-18
lines changed

1 file changed

+89
-18
lines changed

components/dashboard/src/teams/TeamSettings.tsx

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
import { Team } from "@gitpod/gitpod-protocol";
88
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";
1112
import ConfirmationModal from "../components/ConfirmationModal";
1213
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1314
import { publicApiTeamMembersToProtocol, teamsService } from "../service/public-api";
1415
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";
1718

1819
export function getTeamSettingsMenu(params: { team?: Team; billingMode?: BillingMode }) {
1920
const { team, billingMode } = params;
@@ -35,14 +36,15 @@ export function getTeamSettingsMenu(params: { team?: Team; billingMode?: Billing
3536
}
3637

3738
export default function TeamSettings() {
39+
const user = useCurrentUser();
40+
const team = useCurrentTeam();
41+
const { teams, setTeams } = useContext(TeamsContext);
3842
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);
4045
const [isUserOwner, setIsUserOwner] = useState(true);
41-
const { teams } = useContext(TeamsContext);
42-
const { user } = useContext(UserContext);
4346
const [billingMode, setBillingMode] = useState<BillingMode | undefined>(undefined);
44-
const location = useLocation();
45-
const team = getCurrentTeam(location, teams);
47+
const [updated, setUpdated] = useState(false);
4648

4749
const close = () => setModal(false);
4850

@@ -60,20 +62,57 @@ export default function TeamSettings() {
6062
const billingMode = await getGitpodService().server.getBillingModeForTeam(team.id);
6163
setBillingMode(billingMode);
6264
})();
63-
}, []);
65+
}, [team, user]);
6466

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 () => {
69104
if (!team || !user) {
70105
return;
71106
}
72107

73108
await teamsService.deleteTeam({ teamId: team.id });
74109

75110
document.location.href = gitpodHostUrl.asDashboard().toString();
76-
};
111+
}, [team, user]);
112+
113+
if (!isUserOwner) {
114+
return <Redirect to="/" />;
115+
}
77116

78117
return (
79118
<>
@@ -82,7 +121,39 @@ export default function TeamSettings() {
82121
title="Settings"
83122
subtitle="Manage general team settings."
84123
>
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>
86157
<p className="text-base text-gray-500 pb-4 max-w-2xl">
87158
Deleting this team will also remove all associated data with this team, including projects and
88159
workspaces. Deleted teams cannot be restored!
@@ -95,7 +166,7 @@ export default function TeamSettings() {
95166
<ConfirmationModal
96167
title="Delete Team"
97168
buttonText="Delete Team"
98-
buttonDisabled={teamSlug !== team!.slug}
169+
buttonDisabled={teamName !== team!.name}
99170
visible={modal}
100171
warningText="Warning: This action cannot be reversed."
101172
onClose={close}
@@ -117,7 +188,7 @@ export default function TeamSettings() {
117188
<p className="pt-4 pb-2 text-gray-600 dark:text-gray-400 text-base font-semibold">
118189
Type <code>{team?.slug}</code> to confirm
119190
</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>
121192
</ConfirmationModal>
122193
</>
123194
);

0 commit comments

Comments
 (0)