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

79 lines2,341 bytesCodeBlame
1import { env } from "cloudflare:workers";
2import { Link, NavLink, Outlet } from "react-router";
3
4import type { Route } from "./+types/layout";
5import { getViewer, unwrap } from "../../lib/session.server";
6
7export function meta({ params }: Route.MetaArgs) {
8 return [{ title: `${params.owner}/${params.repo} · g1t` }];
9}
10
11export async function loader({ params, context }: Route.LoaderArgs) {
12 const viewer = getViewer(context);
13 const path = { namespace: params.owner, name: params.repo };
14 const [repo, intents] = await Promise.all([
15 env.REPOS.get(path, viewer),
16 env.WORK.listIntents(path, viewer, "open"),
17 ]);
18 return {
19 repo: unwrap(repo),
20 openIntents: intents.ok ? intents.value.length : 0,
21 };
22}
23
24function Tab({ to, end, children }: { to: string; end?: boolean; children: React.ReactNode }) {
25 return (
26 <NavLink
27 to={to}
28 end={end}
29 className={({ isActive }) =>
30 `-mb-px border-b-2 px-1 pb-2.5 text-sm ${
31 isActive
32 ? "border-accent text-fg"
33 : "border-transparent text-muted hover:text-fg"
34 }`
35 }
36 >
37 {children}
38 </NavLink>
39 );
40}
41
42export default function RepoLayout({ loaderData }: Route.ComponentProps) {
43 const { repo, openIntents } = loaderData;
44 const base = `/${repo.namespace}/${repo.name}`;
45 return (
46 <div className="mx-auto max-w-5xl px-4 py-8">
47 <div className="flex items-center gap-3">
48 <h1 className="font-mono text-xl">
49 <span className="text-muted">{repo.namespace}/</span>
50 <Link to={base} className="font-semibold">
51 {repo.name}
52 </Link>
53 </h1>
54 {repo.isPrivate && (
55 <span className="rounded-full border border-line px-2 py-0.5 text-xs text-muted">
56 private
57 </span>
58 )}
59 </div>
60 {repo.description && (
61 <p className="mt-2 text-sm text-muted">{repo.description}</p>
62 )}
63 <nav className="mt-6 flex gap-6 border-b border-line">
64 <Tab to={base} end>
65 Code
66 </Tab>
67 <Tab to={`${base}/intents`}>
68 Intents
69 {openIntents > 0 && (
70 <span className="ml-1.5 rounded-full bg-surface px-1.5 py-0.5 text-xs">
71 {openIntents}
72 </span>
73 )}
74 </Tab>
75 </nav>
76 <Outlet />
77 </div>
78 );
79}