g1t/apps/web/app/routes/repo/settings.tsx

133 lines6,029 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Projects: what a workspace builds and runs, first on every page1import { Box, Code2, GitBranch } from "lucide-react";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2import { Form, Link, useNavigation } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell3
4import type { Route } from "./+types/settings";
Social cards for every page: og.g1t.sh5import { page } from "../../lib/meta";
Project settings move into the sidebar, grouped, with a heading per page6import { RepoSettingsHeading } from "../../components/repo-settings-heading";
Agents as a team: lifecycle, merge queue, billing and a new shell7import { Button, ErrorText, Field, Input, TimeAgo } from "../../components/ui";
Projects: what a workspace builds and runs, first on every page8import { projects } from "../../lib/services.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look9import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
10import { requireCapability, requireInsider } from "../../lib/access.server";
Agents as a team: lifecycle, merge queue, billing and a new shell11
Social cards for every page: og.g1t.sh12export function meta({ params, ...args }: Route.MetaArgs) {
13 return page(args, { title: `Settings · ${params.owner}/${params.repo} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell14}
15
16export async function loader({ params, context }: Route.LoaderArgs) {
17 const viewer = getViewer(context);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look18 // Maintain and up; to anyone without a role here the page does not exist.
19 await requireInsider(context, params, "manage_settings");
Projects: what a workspace builds and runs, first on every page20 return { project: unwrap(await projects.get(params.owner, params.repo, viewer)) };
Agents as a team: lifecycle, merge queue, billing and a new shell21}
22
23export async function action({ request, params, context }: Route.ActionArgs) {
24 assertSameOrigin(request);
25 const user = requireUser(context, request);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look26 await requireCapability(context, params, "manage_settings");
Agents as a team: lifecycle, merge queue, billing and a new shell27 const form = await request.formData();
Projects: what a workspace builds and runs, first on every page28 const saved = await projects.update(user, params.owner, params.repo, {
29 name: String(form.get("name") ?? ""),
Fast pages, required checks on the branch, self-hosted runners, honest incidents30 // Blank, or the reset, and it follows the repository's description again.
31 description: form.get("inherit") === "description" ? null : String(form.get("description") ?? ""),
Projects: what a workspace builds and runs, first on every page32 rootDir: String(form.get("rootDir") ?? ""),
Agents as a team: lifecycle, merge queue, billing and a new shell33 });
Projects: what a workspace builds and runs, first on every page34 return saved.ok ? { saved: true as const } : { error: saved.error.message };
Agents as a team: lifecycle, merge queue, billing and a new shell35}
36
Projects: what a workspace builds and runs, first on every page37export default function ProjectSettings({ loaderData, actionData, params }: Route.ComponentProps) {
38 const { project } = loaderData;
Agents as a team: lifecycle, merge queue, billing and a new shell39 const saving = useNavigation().state === "submitting";
Projects: what a workspace builds and runs, first on every page40 const base = `/${params.owner}/${params.repo}`;
41 const source = project.source.kind === "hosted" ? project.source : null;
Agents as a team: lifecycle, merge queue, billing and a new shell42 return (
Projects: what a workspace builds and runs, first on every page43 <div className="max-w-4xl">
Project settings move into the sidebar, grouped, with a heading per page44 <RepoSettingsHeading base={base} />
Projects: what a workspace builds and runs, first on every page45 <Form method="post" className="space-y-8">
46 <section className="space-y-4">
47 <h2 className="flex items-center gap-2 text-sm font-medium">
48 <Box size={15} className="text-accent" />
49 Project
50 </h2>
51 <div className="grid gap-4 md:grid-cols-2">
52 <Field label="Name" hint={`Its address stays g1t.sh/${project.workspace}/${project.slug}.`}>
53 <Input name="name" defaultValue={project.name} required maxLength={100} />
54 </Field>
Fast pages, required checks on the branch, self-hosted runners, honest incidents55 <div>
56 <Field
57 label="Description"
58 hint={
59 project.descriptionInherited
60 ? "Follows the repository's description. Write one to give the project its own."
61 : "The project's own. Empty follows the repository's description."
62 }
63 >
64 <Input
65 key={`${project.descriptionInherited}:${project.description ?? ""}`}
66 name="description"
67 defaultValue={project.descriptionInherited ? "" : (project.description ?? "")}
68 maxLength={200}
69 placeholder={(project.descriptionInherited && project.description) || "What it is, in a line"}
70 />
71 </Field>
72 {!project.descriptionInherited && (
73 <button
74 type="submit"
75 name="inherit"
76 value="description"
77 disabled={saving}
78 className="mt-1.5 text-xs text-muted hover:text-fg disabled:opacity-50"
79 >
80 Use the repository's description
81 </button>
82 )}
83 </div>
Projects: what a workspace builds and runs, first on every page84 </div>
85 </section>
Agents as a team: lifecycle, merge queue, billing and a new shell86
Projects: what a workspace builds and runs, first on every page87 <section className="space-y-4">
88 <h2 className="flex items-center gap-2 text-sm font-medium">
89 <Code2 size={15} className="text-accent" />
90 Source
91 </h2>
92 {source && (
93 <div className="rounded-xl border border-line bg-surface p-4 text-sm">
94 <div className="flex flex-wrap items-center justify-between gap-3">
95 <div>
96 <Link to={`${base}/code`} className="font-mono hover:text-accent">
97 {source.repo.namespace}/{source.repo.name}
98 </Link>
99 <p className="mt-0.5 text-xs text-muted">
100 Hosted on g1t · <GitBranch size={11} className="inline" /> {source.defaultBranch}
101 </p>
102 </div>
103 <Link to={`${base}/settings/repository`} className="text-xs text-muted hover:text-fg">
104 Repository settings
105 </Link>
106 </div>
107 <p className="mt-3 text-xs text-faint">
108 Mirroring a repository from GitHub, GitLab or Bitbucket as a project's source is coming next.
109 </p>
110 </div>
111 )}
112 <Field
113 label="Root directory"
114 hint="Where in the repository this project lives, such as apps/web. Builds run there. Empty for the whole repository."
Webhooks: every event, to your own addresses, signed and retried115 >
Projects: what a workspace builds and runs, first on every page116 <Input name="rootDir" defaultValue={source?.rootDir ?? ""} placeholder="apps/web" className="font-mono" />
117 </Field>
118 </section>
Agents as a team: lifecycle, merge queue, billing and a new shell119
Projects: what a workspace builds and runs, first on every page120 <div className="flex items-center gap-3">
Webhooks: every event, to your own addresses, signed and retried121 <Button type="submit" disabled={saving}>
Projects: what a workspace builds and runs, first on every page122 Save
Webhooks: every event, to your own addresses, signed and retried123 </Button>
Projects: what a workspace builds and runs, first on every page124 {actionData && "saved" in actionData && <span className="text-sm text-accent">Saved.</span>}
125 <span className="ml-auto text-xs text-faint">
126 Created by {project.createdBy} <TimeAgo at={project.createdAt} />
127 </span>
Webhooks: every event, to your own addresses, signed and retried128 </div>
Projects: what a workspace builds and runs, first on every page129 <ErrorText>{actionData && "error" in actionData ? actionData.error : null}</ErrorText>
Webhooks: every event, to your own addresses, signed and retried130 </Form>
Projects: what a workspace builds and runs, first on every page131 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell132 );
133}