g1t/apps/web/app/routes/workspace/layout.tsx

131 lines5,405 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.

Integrations: your own model provider, alerts that open issues, tickets agents read1import { CreditCard, KeyRound, LayoutGrid, Plug, Plus, Settings, Users } from "lucide-react";
A catalogue of model providers, and settings that feel like settings2import { Outlet, data, useLocation, useRouteLoaderData } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell3
4import type { Route } from "./+types/layout";
5import { Avatar, ButtonLink, Pill, TabLink } from "../../components/ui";
6import { identity } from "../../lib/services.server";
7import { getViewer, roleIn } from "../../lib/session.server";
8
9export function meta({ loaderData, params }: Route.MetaArgs) {
10 return [{ title: `${loaderData?.workspace.name ?? params.owner} · g1t` }];
11}
12
13export async function loader({ params, context }: Route.LoaderArgs) {
14 const workspace = await identity.getWorkspace(params.owner);
15 if (!workspace) throw data(null, { status: 404 });
16 return { workspace, role: roleIn(getViewer(context), workspace.slug) };
17}
18
A catalogue of model providers, and settings that feel like settings19/** A workspace's own pages, each with its title and what it is for. */
20const PAGES: Record<string, { title: string; about: string }> = {
21 settings: { title: "General", about: "The workspace's name, address and description." },
22 people: {
23 title: "Members",
24 about: "Members create repositories, push, manage issues and merge pull requests. Owners also manage members, tokens, billing and integrations.",
25 },
26 tokens: {
27 title: "Access tokens",
28 about: "Tokens that belong to the workspace, not a person: for CI, integrations and agents that work for the whole team.",
29 },
30 usage: { title: "Usage", about: "What the workspace's agents cost, run by run, by repository, pull request and model." },
Secrets and variables: one list, rows per environment, for workflows and deployments31 billing: { title: "Billing and plans", about: "Paid plans, agent credit, and every charge against it." },
Webhooks: every event, to your own addresses, signed and retried32 webhooks: {
33 title: "Webhooks",
34 about: "Every repository's events, sent to your own addresses as they happen. A repository can also have its own, under its settings.",
35 },
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs36 secrets: {
37 title: "Secrets and variables",
Secrets and variables: one list, rows per environment, for workflows and deployments38 about: "Shared with every repository, or the ones you link: read by workflows, deployments, or both. A repository's own row of the same key wins.",
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs39 },
A catalogue of model providers, and settings that feel like settings40 integrations: {
41 title: "Integrations",
42 about: "Model providers, alerts and trackers. Secrets are sealed when saved, and agents never see them.",
43 },
44};
45
Agents as a team: lifecycle, merge queue, billing and a new shell46export default function WorkspaceLayout({ loaderData }: Route.ComponentProps) {
47 const { workspace, role } = loaderData;
48 const base = `/${workspace.slug}`;
49 const signedIn = useRouteLoaderData("root")?.user != null;
A catalogue of model providers, and settings that feel like settings50 // The sidebar finds the workspace's pages for someone signed in, so its
51 // pages need a title, not the workspace's whole header again.
52 const page = PAGES[useLocation().pathname.split("/-/")[1]?.split("/")[0] ?? ""];
53 if (signedIn && page) {
54 return (
55 <div className="mx-auto max-w-5xl px-4 py-10 sm:px-8">
56 <header className="mb-8 border-b border-line pb-6">
57 <h1 className="text-2xl font-semibold tracking-tight">{page.title}</h1>
58 <p className="mt-1.5 text-sm text-muted">{page.about}</p>
59 </header>
60 <Outlet />
61 </div>
62 );
63 }
Agents as a team: lifecycle, merge queue, billing and a new shell64 return (
65 <>
66 {/* The workspace's own header band, under the site header. */}
67 <div className="border-b border-line bg-surface/60">
68 <div className="mx-auto max-w-6xl px-4 pt-8">
69 <div className="flex flex-wrap items-center gap-4">
70 <Avatar name={workspace.slug} size={52} square />
71 <div className="min-w-0 grow">
72 <div className="flex flex-wrap items-center gap-2.5">
73 <h1 className="truncate text-2xl font-semibold tracking-tight">
74 {workspace.name}
75 </h1>
76 {role && <Pill>{role}</Pill>}
77 </div>
78 <p className="font-mono text-sm text-muted">g1t.sh/{workspace.slug}</p>
79 </div>
80 {role && (
81 <ButtonLink to={`/new?workspace=${workspace.slug}`}>
82 <Plus size={15} />
Projects live: fixes from walking them end to end83 New project
Agents as a team: lifecycle, merge queue, billing and a new shell84 </ButtonLink>
85 )}
86 </div>
87 {workspace.description && (
88 <p className="mt-4 max-w-2xl text-sm text-muted">{workspace.description}</p>
89 )}
90 {signedIn ? (
91 <div className="pb-8" />
92 ) : (
93 <nav className="mt-6 flex flex-wrap gap-x-6">
94 <TabLink to={base} end icon={<LayoutGrid size={15} />}>
95 Overview
96 </TabLink>
97 {role && (
98 <>
99 <TabLink
100 to={`${base}/-/people`}
101 icon={<Users size={15} />}
102 count={workspace.memberCount}
103 >
Secrets and variables: one list, rows per environment, for workflows and deployments104 Members
Agents as a team: lifecycle, merge queue, billing and a new shell105 </TabLink>
106 <TabLink to={`${base}/-/tokens`} icon={<KeyRound size={15} />}>
107 Access tokens
108 </TabLink>
109 <TabLink to={`${base}/-/billing`} icon={<CreditCard size={15} />}>
110 Billing
111 </TabLink>
Integrations: your own model provider, alerts that open issues, tickets agents read112 <TabLink to={`${base}/-/integrations`} icon={<Plug size={15} />}>
113 Integrations
114 </TabLink>
Agents as a team: lifecycle, merge queue, billing and a new shell115 </>
116 )}
117 {role === "owner" && (
118 <TabLink to={`${base}/-/settings`} icon={<Settings size={15} />}>
119 Settings
120 </TabLink>
121 )}
122 </nav>
123 )}
124 </div>
125 </div>
126 <div className="mx-auto max-w-6xl px-4 py-8">
127 <Outlet />
128 </div>
129 </>
130 );
131}