Skip to content

Commit 45c0071

Browse files
selfcontainedAlexTugarev
authored andcommitted
update to use input/button components and mutation
1 parent a2df9d7 commit 45c0071

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
@@ -6,11 +6,15 @@
66

77
import React, { useCallback, useState } from "react";
88
import Alert from "../components/Alert";
9+
import { Button } from "../components/Button";
910
import ConfirmationModal from "../components/ConfirmationModal";
11+
import { TextInputField } from "../components/forms/TextInputField";
1012
import { Heading2, Subheading } from "../components/typography/headings";
1113
import { useCurrentOrg, useOrganizationsInvalidator } from "../data/organizations/orgs-query";
14+
import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation";
15+
import { useOnBlurError } from "../hooks/use-onblur-error";
1216
import { teamsService } from "../service/public-api";
13-
import { getGitpodService, gitpodHostUrl } from "../service/service";
17+
import { gitpodHostUrl } from "../service/service";
1418
import { useCurrentUser } from "../user-context";
1519
import { OrgSettingsPage } from "./OrgSettingsPage";
1620

@@ -22,63 +26,44 @@ export default function TeamSettings() {
2226
const [teamNameToDelete, setTeamNameToDelete] = useState("");
2327
const [teamName, setTeamName] = useState(org?.name || "");
2428
const [slug, setSlug] = useState(org?.slug || "");
25-
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
2629
const [updated, setUpdated] = useState(false);
30+
const updateOrg = useUpdateOrgMutation();
2731

2832
const close = () => setModal(false);
2933

30-
const updateTeamInformation = useCallback(async () => {
31-
if (!org || errorMessage) {
32-
return;
33-
}
34-
try {
35-
await getGitpodService().server.updateTeam(org.id, { name: teamName, slug });
36-
invalidateOrgs();
37-
setUpdated(true);
38-
setTimeout(() => setUpdated(false), 3000);
39-
} catch (error) {
40-
setErrorMessage(`Failed to update organization information: ${error.message}`);
41-
}
42-
}, [org, errorMessage, slug, 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+
);
4340

44-
const onNameChange = useCallback(
45-
async (event: React.ChangeEvent<HTMLInputElement>) => {
46-
if (!org) {
47-
return;
48-
}
49-
const newName = event.target.value || "";
50-
setTeamName(newName);
51-
if (newName.trim().length === 0) {
52-
setErrorMessage("Organization name can not be blank.");
53-
return;
54-
} else if (newName.trim().length > 32) {
55-
setErrorMessage("Organization name must not be longer than 32 characters.");
56-
return;
57-
} else {
58-
setErrorMessage(undefined);
59-
}
60-
},
61-
[org],
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,
6246
);
6347

64-
const onSlugChange = useCallback(
65-
async (event: React.ChangeEvent<HTMLInputElement>) => {
66-
if (!org) {
48+
const orgFormIsValid = teamNameError.isValid && slugError.isValid;
49+
50+
const updateTeamInformation = useCallback(
51+
async (e: React.FormEvent) => {
52+
e.preventDefault();
53+
54+
if (!orgFormIsValid) {
6755
return;
6856
}
69-
const newSlug = event.target.value || "";
70-
setSlug(newSlug);
71-
if (newSlug.trim().length === 0) {
72-
setErrorMessage("Organization slug can not be blank.");
73-
return;
74-
} else if (newSlug.trim().length > 100) {
75-
setErrorMessage("Organization slug must not be longer than 100 characters.");
76-
return;
77-
} else {
78-
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);
7964
}
8065
},
81-
[org],
66+
[orgFormIsValid, updateOrg, teamName, slug],
8267
);
8368

8469
const deleteTeam = useCallback(async () => {
@@ -94,45 +79,47 @@ export default function TeamSettings() {
9479
return (
9580
<>
9681
<OrgSettingsPage>
97-
<Heading2>Organization Name</Heading2>
98-
<Subheading className="max-w-2xl">
99-
This is your organization's visible name within Gitpod. For example, the name of your company.
100-
</Subheading>
101-
{errorMessage && (
82+
<Heading2>Organization Details</Heading2>
83+
<Subheading className="max-w-2xl">Details of your organization within Gitpod.</Subheading>
84+
85+
{updateOrg.isError && (
10286
<Alert type="error" closable={true} className="mb-2 max-w-xl rounded-md">
103-
{errorMessage}
87+
<span>Failed to update organization information: </span>
88+
<span>{updateOrg.error.message || "unknown error"}</span>
10489
</Alert>
10590
)}
10691
{updated && (
10792
<Alert type="message" closable={true} className="mb-2 max-w-xl rounded-md">
10893
Organization name has been updated.
10994
</Alert>
11095
)}
111-
<div className="flex flex-col lg:flex-row">
112-
<div>
113-
<div className="mt-4 mb-3">
114-
<h4>Name</h4>
115-
<input type="text" value={teamName} onChange={onNameChange} />
116-
</div>
117-
</div>
118-
</div>
119-
<div className="flex flex-col lg:flex-row">
120-
<div>
121-
<div className="mt-4 mb-3">
122-
<h4>Slug</h4>
123-
<input type="text" value={slug} onChange={onSlugChange} />
124-
</div>
125-
</div>
126-
</div>
127-
<div className="flex flex-row">
128-
<button
129-
className="primary"
130-
disabled={(org?.name === teamName && org?.slug === slug) || !!errorMessage}
131-
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}
132119
>
133-
Update Organization Name
134-
</button>
135-
</div>
120+
Update Organization
121+
</Button>
122+
</form>
136123

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

0 commit comments

Comments
 (0)