Skip to content

Commit 3ea4b4f

Browse files
committed
Adding repo config name form
1 parent 36071e6 commit 3ea4b4f

File tree

3 files changed

+144
-1
lines changed

3 files changed

+144
-1
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 { Project } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
8+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
9+
import { useCurrentOrg } from "../organizations/orgs-query";
10+
import { listAllProjects } from "../../service/public-api";
11+
import { PartialProject } from "@gitpod/gitpod-protocol";
12+
import { getGitpodService } from "../../service/service";
13+
14+
type UseProjectArgs = {
15+
id: string;
16+
};
17+
export const useProject = ({ id }: UseProjectArgs) => {
18+
const { data: org } = useCurrentOrg();
19+
20+
return useQuery<Project | null, Error>(
21+
getProjectQueryKey(org?.id || "", id),
22+
async () => {
23+
if (!org) {
24+
throw new Error("No current org");
25+
}
26+
27+
// TODO: This is temporary until we create a project by id endpoint
28+
// Waiting to tackle that once we have the new grpc setup for server
29+
const projects = await listAllProjects({ orgId: org.id });
30+
const project = projects.find((p) => p.id === id);
31+
32+
return project || null;
33+
},
34+
{
35+
enabled: !!org,
36+
},
37+
);
38+
};
39+
40+
const getProjectQueryKey = (orgId: string, id: string) => {
41+
return ["project", { orgId, id }];
42+
};
43+
44+
export const useUpdateProject = () => {
45+
const { data: org } = useCurrentOrg();
46+
const client = useQueryClient();
47+
48+
return useMutation<void, Error, PartialProject>(async ({ id, name }) => {
49+
await getGitpodService().server.updateProjectPartial({ id, name });
50+
51+
// Invalidate project
52+
await client.invalidateQueries(getProjectQueryKey(org?.id || "", id));
53+
54+
// TODO: Invalidate new list projects query
55+
});
56+
};

components/dashboard/src/repositories/detail/RepositoryDetail.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,43 @@
77
import { FC } from "react";
88
import Header from "../../components/Header";
99
import { useParams } from "react-router";
10+
import { useProject } from "../../data/projects/project-query";
11+
import { Button } from "../../components/Button";
12+
import { RepositoryNameForm } from "./RepositoryName";
13+
import { Loader2 } from "lucide-react";
14+
import Alert from "../../components/Alert";
1015

1116
type PageRouteParams = {
1217
id: string;
1318
};
1419
const RepositoryDetailPage: FC = () => {
1520
const { id } = useParams<PageRouteParams>();
21+
const { data, error, isLoading, refetch } = useProject({ id });
1622

1723
return (
1824
<>
1925
<Header title="Repository Detail" subtitle="" />
2026
<div className="app-container">
21-
<span>id: {id}</span>
27+
{isLoading && <Loader2 className="animate-spin" />}
28+
{error && (
29+
<div className="gap-4">
30+
<Alert type="error">
31+
<span>Failed to load repository configuration</span>
32+
<pre>{error.message}</pre>
33+
</Alert>
34+
35+
<Button type="danger" onClick={refetch}>
36+
Retry
37+
</Button>
38+
</div>
39+
)}
40+
{!isLoading &&
41+
(!data ? (
42+
// TODO: add a better not-found UI w/ link back to repositories
43+
<div>Sorry, we couldn't find that repository configuration.</div>
44+
) : (
45+
<RepositoryNameForm project={data} />
46+
))}
2247
</div>
2348
</>
2449
);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
// TODO: fix mismatched project types when we build repo configuration apis
8+
import { Project } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
9+
import { Button } from "../../components/Button";
10+
import { TextInputField } from "../../components/forms/TextInputField";
11+
import { FC, useCallback, useState } from "react";
12+
import { useUpdateProject } from "../../data/projects/project-query";
13+
import { useToast } from "../../components/toasts/Toasts";
14+
import { useOnBlurError } from "../../hooks/use-onblur-error";
15+
16+
type Props = {
17+
project: Project;
18+
};
19+
20+
export const RepositoryNameForm: FC<Props> = ({ project }) => {
21+
const { toast } = useToast();
22+
const updateProject = useUpdateProject();
23+
const [projectName, setProjectName] = useState(project.name);
24+
25+
const nameError = useOnBlurError("Sorry, this name is too long.", projectName.length > 32);
26+
27+
const updateName = useCallback(
28+
async (e: React.FormEvent) => {
29+
e.preventDefault();
30+
31+
updateProject.mutate(
32+
{
33+
id: project.id,
34+
name: projectName,
35+
},
36+
{
37+
onSuccess: () => {
38+
toast(`Configuration name set to "${projectName}".`);
39+
},
40+
},
41+
);
42+
},
43+
[updateProject, project.id, projectName, toast],
44+
);
45+
46+
return (
47+
<form onSubmit={updateName}>
48+
<TextInputField
49+
hint="The name can be up to 32 characters long."
50+
value={projectName}
51+
error={nameError.message}
52+
onChange={setProjectName}
53+
onBlur={nameError.onBlur}
54+
/>
55+
56+
{/* Don't disable button, just handle error and message */}
57+
<Button className="mt-4" htmlType="submit" disabled={project.name === projectName || !nameError.isValid}>
58+
Update Name
59+
</Button>
60+
</form>
61+
);
62+
};

0 commit comments

Comments
 (0)