flagon-io/g1t

public

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

g1t/apps/sudo/app/root.tsx

102 lines4,320 bytesCodeBlame
1import { isRouteErrorResponse, Links, Meta, Outlet, useRouteLoaderData } from "react-router";
2
3import type { Route } from "./+types/root";
4import "./app.css";
5import displayFont from "@g1t/theme/fonts/bricolage-grotesque-latin.woff2?url";
6import sansFont from "@g1t/theme/fonts/hanken-grotesk-latin.woff2?url";
7import { MobileBar, Sidebar } from "./components/shell";
8import { ButtonLink } from "./components/ui";
9import type { NavCounts } from "./lib/nav";
10import { admin, identity, statusAdmin } from "./lib/services.server";
11import { settle } from "./lib/settle";
12import { requireStaff, zoneContext } from "./lib/staff";
13
14export const links: Route.LinksFunction = () => [
15 { rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
16 // The faces are served by sudo itself (@g1t/theme); these two are on every page.
17 { rel: "preload", href: sansFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
18 { rel: "preload", href: displayFont, as: "font", type: "font/woff2", crossOrigin: "anonymous" },
19];
20
21export const meta: Route.MetaFunction = () => [
22 { title: "sudo · g1t" },
23 { name: "robots", content: "noindex, nofollow" },
24];
25
26export async function loader({ context }: Route.LoaderArgs) {
27 const { email } = requireStaff(context);
28 // The sidebar's counts: a service that does not answer shows none.
29 const [waitlist, incidents, alerts] = await Promise.all([
30 settle(identity.waitlistPending()),
31 settle(statusAdmin.openCount()),
32 settle(admin.costAlerts()),
33 ]);
34 const counts: NavCounts = { waitlist: waitlist.ok ? waitlist.value : 0, incidents: incidents.ok ? incidents.value : 0 };
35 // Every page says times in this zone (components/ui.tsx `When`).
36 const { zone, chosen } = context.get(zoneContext);
37 // Margin alerts (billing's margin guard): a red bar on every page until they clear.
38 const rank = ["overall", "margin", "leak"];
39 const margin = alerts.ok
40 ? alerts.value.filter((alert) => rank.includes(alert.kind)).sort((a, b) => rank.indexOf(a.kind) - rank.indexOf(b.kind))
41 : [];
42 return { email, counts, zone, zoneChosen: chosen, margin };
43}
44
45export function Layout({ children }: { children: React.ReactNode }) {
46 const root = useRouteLoaderData<typeof loader>("root");
47 return (
48 <html lang="en">
49 <head>
50 <meta charSet="utf-8" />
51 <meta name="viewport" content="width=device-width, initial-scale=1" />
52 <meta name="theme-color" content="#0f0f11" />
53 <Meta />
54 <Links />
55 </head>
56 <body className="min-h-screen">
57 <Sidebar email={root?.email} counts={root?.counts} />
58 <MobileBar email={root?.email} counts={root?.counts} />
59 <div className="lg:pl-60">
60 {root?.margin && root.margin.length > 0 && (
61 <div role="alert" className="border-b border-danger/40 bg-danger/12 px-4 py-2 text-sm text-danger">
62 <span className="font-medium">Margin alert:</span> {root.margin[0]!.detail}
63 {root.margin.length > 1 && <span className="text-danger/80"> And {root.margin.length - 1} more.</span>}{" "}
64 <a href="/costs" className="underline underline-offset-2">
65 Costs &amp; margin
66 </a>
67 </div>
68 )}
69 {children}
70 </div>
71 {/* No <Scripts />: sudo ships no JavaScript, and its policy allows none. */}
72 </body>
73 </html>
74 );
75}
76
77export default function App() {
78 return <Outlet />;
79}
80
81export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
82 let title = "Something went wrong";
83 let details = "An unexpected error occurred.";
84 if (isRouteErrorResponse(error)) {
85 title = error.status === 404 ? "Not found" : `Error ${error.status}`;
86 details = typeof error.data === "string" && error.data ? error.data : error.statusText || details;
87 } else if (error instanceof Error) {
88 // Staff only: the real reason helps more than a polite one.
89 details = error.message;
90 }
91 return (
92 <main className="mx-auto max-w-xl px-4 py-24 text-center">
93 <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
94 <p className="mt-3 break-words text-muted">{details}</p>
95 <div className="mt-8">
96 <ButtonLink to="/" variant="quiet">
97 Back to the overview
98 </ButtonLink>
99 </div>
100 </main>
101 );
102}