Skip to content

Commit 86b3d99

Browse files
selfcontainedAlexTugarev
authored andcommitted
update to use input/button components and mutation
1 parent 0f0d2d2 commit 86b3d99

File tree

2 files changed

+95
-77
lines changed

2 files changed

+95
-77
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { Organization } from "@gitpod/gitpod-protocol";
8+
import { useMutation } from "@tanstack/react-query";
9+
import { getGitpodService } from "../../service/service";
10+
import { useCurrentOrg, useOrganizationsInvalidator } from "./orgs-query";
11+
12+
type UpdateOrgArgs = Pick<Organization, "name" | "slug">;
13+
14+
export const useUpdateOrgMutation = () => {
15+
const org = useCurrentOrg().data;
16+
const invalidateOrgs = useOrganizationsInvalidator();
17+
18+
return useMutation<Organization, Error, UpdateOrgArgs>({
19+
mutationFn: async ({ name, slug }) => {
20+
if (!org) {
21+
throw new Error("No current organization selected");
22+
}
23+
24+
return await getGitpodService().server.updateTeam(org.id, { name, slug });
25+
},
26+
onSuccess(updatedOrg) {
27+
// TODO: Update query cache with new org prior to invalidation so it's reflected immediately
28+
invalidateOrgs();
29+
},
30+
});
31+
};

components/dashboard/src/teams/TeamSettings.tsx

Lines changed: 64 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { Team } from "@gitpod/gitpod-protocol";
88
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
99
import React, { useCallback, useState } from "react";
1010
import Alert from "../components/Alert";
11+
import { Button } from "../components/Button";
1112
import ConfirmationModal from "../components/ConfirmationModal";
13+
import { TextInputField } from "../components/forms/TextInputField";
1214
import { Heading2, Subheading } from "../components/typography/headings";
1315
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";
1418
import { teamsService } from "../service/public-api";
15-
import { getGitpodService, gitpodHostUrl } from "../service/service";
19+
import { gitpodHostUrl } from "../service/service";
1620
import { useCurrentUser } from "../user-context";
1721
import { OrgSettingsPage } from "./OrgSettingsPage";
1822

@@ -59,63 +63,44 @@ export default function TeamSettings() {
5963
const [teamNameToDelete, setTeamNameToDelete] = useState("");
6064
const [teamName, setTeamName] = useState(org?.name || "");
6165
const [slug, setSlug] = useState(org?.slug || "");
62-
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
6366
const [updated, setUpdated] = useState(false);
67+
const updateOrg = useUpdateOrgMutation();
6468

6569
const close = () => setModal(false);
6670

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+
);
8077

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,
9983
);
10084

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) {
10492
return;
10593
}
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);
116101
}
117102
},
118-
[org],
103+
[orgFormIsValid, updateOrg, teamName, slug],
119104
);
120105

121106
const deleteTeam = useCallback(async () => {
@@ -131,45 +116,47 @@ export default function TeamSettings() {
131116
return (
132117
<>
133118
<OrgSettingsPage>
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 && (
139123
<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>
141126
</Alert>
142127
)}
143128
{updated && (
144129
<Alert type="message" closable={true} className="mb-2 max-w-xl rounded-md">
145130
Organization name has been updated.
146131
</Alert>
147132
)}
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}
169156
>
170-
Update Organization Name
171-
</button>
172-
</div>
157+
Update Organization
158+
</Button>
159+
</form>
173160

174161
<Heading2 className="pt-12">Delete Organization</Heading2>
175162
<Subheading className="pb-4 max-w-2xl">

0 commit comments

Comments
 (0)