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

90 lines3,426 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";
Agents as a team: lifecycle, merge queue, billing and a new shell2import { Outlet, data, useRouteLoaderData } from "react-router";
3
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
19export default function WorkspaceLayout({ loaderData }: Route.ComponentProps) {
20 const { workspace, role } = loaderData;
21 const base = `/${workspace.slug}`;
22 const signedIn = useRouteLoaderData("root")?.user != null;
23 return (
24 <>
25 {/* The workspace's own header band, under the site header. */}
26 <div className="border-b border-line bg-surface/60">
27 <div className="mx-auto max-w-6xl px-4 pt-8">
28 <div className="flex flex-wrap items-center gap-4">
29 <Avatar name={workspace.slug} size={52} square />
30 <div className="min-w-0 grow">
31 <div className="flex flex-wrap items-center gap-2.5">
32 <h1 className="truncate text-2xl font-semibold tracking-tight">
33 {workspace.name}
34 </h1>
35 {role && <Pill>{role}</Pill>}
36 </div>
37 <p className="font-mono text-sm text-muted">g1t.sh/{workspace.slug}</p>
38 </div>
39 {role && (
40 <ButtonLink to={`/new?workspace=${workspace.slug}`}>
41 <Plus size={15} />
42 New repository
43 </ButtonLink>
44 )}
45 </div>
46 {workspace.description && (
47 <p className="mt-4 max-w-2xl text-sm text-muted">{workspace.description}</p>
48 )}
49 {signedIn ? (
50 <div className="pb-8" />
51 ) : (
52 <nav className="mt-6 flex flex-wrap gap-x-6">
53 <TabLink to={base} end icon={<LayoutGrid size={15} />}>
54 Overview
55 </TabLink>
56 {role && (
57 <>
58 <TabLink
59 to={`${base}/-/people`}
60 icon={<Users size={15} />}
61 count={workspace.memberCount}
62 >
63 People
64 </TabLink>
65 <TabLink to={`${base}/-/tokens`} icon={<KeyRound size={15} />}>
66 Access tokens
67 </TabLink>
68 <TabLink to={`${base}/-/billing`} icon={<CreditCard size={15} />}>
69 Billing
70 </TabLink>
Integrations: your own model provider, alerts that open issues, tickets agents read71 <TabLink to={`${base}/-/integrations`} icon={<Plug size={15} />}>
72 Integrations
73 </TabLink>
Agents as a team: lifecycle, merge queue, billing and a new shell74 </>
75 )}
76 {role === "owner" && (
77 <TabLink to={`${base}/-/settings`} icon={<Settings size={15} />}>
78 Settings
79 </TabLink>
80 )}
81 </nav>
82 )}
83 </div>
84 </div>
85 <div className="mx-auto max-w-6xl px-4 py-8">
86 <Outlet />
87 </div>
88 </>
89 );
90}