Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

134 linesCodeBlameRaw
1import { privacy } from "./lib/services.server";
2import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration, useRouteLoaderData } from "react-router";
3
4import type { Route } from "./+types/root";
5import "./app.css";
6import displayFont from "@g1t/theme/fonts/bricolage-grotesque-latin.woff2?url";
7import sansFont from "@g1t/theme/fonts/hanken-grotesk-latin.woff2?url";
8import { Progress, useLeaving, useSettledForms } from "./components/progress";
9import { MobileBar, Sidebar } from "./components/shell";
10import { ButtonLink } from "./components/ui";
11import type { NavCounts } from "./lib/nav";
12import { pauseBanner, spendBanner } from "./lib/costs";
13import { admin, statusAdmin } from "./lib/services.server";
14import { settle } from "./lib/settle";
15import { requireStaff, zoneContext } from "./lib/staff";
16
17export const links: Route.LinksFunction = () => [
18 { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
19 // The faces are served by sudo itself (@g1t/theme); these two are on every page.
20 { rel: "preload", href: sansFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
21 { rel: "preload", href: displayFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
22];
23
24export const meta: Route.MetaFunction = () => [
25 { title: "sudo · g1t" },
26 { name: "robots", content: "noindex, nofollow" },
27];
28
29export async function loader({ context }: Route.LoaderArgs) {
30 const { email } = requireStaff(context);
31 // The sidebar's counts: a service that does not answer shows none.
32 const [incidents, alerts, caps, guard, erasures] = await Promise.all([
33 settle(statusAdmin.openCount()),
34 settle(admin.costAlerts()),
35 settle(admin.spendCaps()),
36 settle(admin.platformGuard()),
37 settle(privacy.requests()),
38 ]);
39 const counts: NavCounts = { incidents: incidents.ok ? incidents.value : 0, privacy: erasures.ok ? erasures.value.filter(r => !r.closed_at).length : 0 };
40 // Every page says times in this zone (components/ui.tsx `When`).
41 const { zone, chosen } = context.get(zoneContext);
42 // Margin alerts (billing's margin guard): a red bar on every page until they clear.
43 const rank = ["overall", "margin", "leak"];
44 const margin = alerts.ok
45 ? alerts.value.filter((alert) => rank.includes(alert.kind)).sort((a, b) => rank.indexOf(a.kind) - rank.indexOf(b.kind))
46 : [];
47 // g1t's own spend (billing's budget): the daily breaker open, or a comped
48 // account's monthly budget used up. Red until it clears or staff act.
49 const spend = caps.ok ? spendBanner(caps.value) : null;
50 // A platform pause (billing's platform.rs): red on every page while any
51 // level is paused, by staff or by the usage watcher.
52 const paused = guard.ok ? pauseBanner(guard.value) : null;
53 return { email, counts, zone, zoneChosen: chosen, margin, spend, paused };
54}
55
56export function Layout({ children }: { children: React.ReactNode }) {
57 const root = useRouteLoaderData<typeof loader>("root");
58 const leaving = useLeaving();
59 useSettledForms();
60 return (
61 <html lang="en">
62 <head>
63 <meta charSet="utf-8" />
64 <meta name="viewport" content="width=device-width, initial-scale=1" />
65 <meta name="theme-color" content="#0f0f11" />
66 <Meta />
67 <Links />
68 </head>
69 <body className="min-h-screen">
70 <Progress />
71 <Sidebar email={root?.email} counts={root?.counts} />
72 <MobileBar email={root?.email} counts={root?.counts} />
73 <div className="lg:pl-60">
74 {root?.margin && root.margin.length > 0 && (
75 <div role="alert" className="border-b border-danger/40 bg-danger/12 px-4 py-2 text-sm text-danger">
76 <span className="font-medium">Margin alert:</span> {root.margin[0]!.detail}
77 {root.margin.length > 1 && <span className="text-danger/80"> And {root.margin.length - 1} more.</span>}{" "}
78 <a href="/costs" className="underline underline-offset-2">
79 Costs &amp; margin
80 </a>
81 </div>
82 )}
83 {root?.spend && (
84 <div role="alert" className="border-b border-danger/40 bg-danger/12 px-4 py-2 text-sm text-danger">
85 <span className="font-medium">Spend cap:</span> {root.spend}{" "}
86 <a href="/costs#spend" className="underline underline-offset-2">
87 g1t's own spend
88 </a>
89 </div>
90 )}
91 {root?.paused && (
92 <div role="alert" className="border-b border-danger/40 bg-danger/12 px-4 py-2 text-sm text-danger">
93 <span className="font-medium">Platform pause:</span> {root.paused}{" "}
94 <a href="/costs#platform" className="underline underline-offset-2">
95 Platform pause
96 </a>
97 </div>
98 )}
99 <div {...leaving}>{children}</div>
100 </div>
101 {/* Each takes the page's nonce from ServerRouter (entry.server.tsx). */}
102 <ScrollRestoration />
103 <Scripts />
104 </body>
105 </html>
106 );
107}
108
109export default function App() {
110 return <Outlet />;
111}
112
113export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
114 let title = "Something went wrong";
115 let details = "An unexpected error occurred.";
116 if (isRouteErrorResponse(error)) {
117 title = error.status === 404 ? "Not found" : `Error ${error.status}`;
118 details = typeof error.data === "string" && error.data ? error.data : error.statusText || details;
119 } else if (error instanceof Error) {
120 // Staff only: the real reason helps more than a polite one.
121 details = error.message;
122 }
123 return (
124 <main className="mx-auto max-w-xl px-4 py-24 text-center">
125 <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
126 <p className="mt-3 wrap-break-word text-muted">{details}</p>
127 <div className="mt-8">
128 <ButtonLink to="/" variant="quiet">
129 Back to the overview
130 </ButtonLink>
131 </div>
132 </main>
133 );
134}