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.

74 linesCodeBlameRaw
1import { useEffect } from "react";
2import { data, redirect, useNavigate } from "react-router";
3
4import type { Route } from "./+types/home";
5import {
6 DESKTOP_COOKIE,
7 DESKTOP_COOKIE_SECONDS,
8 DESKTOP_QUERY,
9 DESKTOP_VALUE,
10 fromDesktopApp,
11 launchedFromDesktopApp,
12 rememberDesktopApp,
13} from "../lib/desktop";
14import { cookie } from "../lib/github";
15import { desktopBridge } from "../lib/notify-client";
16import { page } from "../lib/meta";
17import { application, organization } from "../lib/structured-data";
18import { WORKSPACE_COOKIE, chosenWorkspace } from "../lib/workspace-choice";
19import { readCookie } from "../lib/mission";
20import { Landing } from "../components/landing";
21import { getViewer } from "../lib/session.server";
22import { homePath } from "../lib/workspace-nav";
23
24export function meta(args: Route.MetaArgs) {
25 return [
26 ...page(args, {
27 title: "g1t · Where your team's code lands",
28 description:
29 // At most 200 characters, or a preview cuts it short (lib/meta.ts).
30 "Repositories, pull requests, required checks, a merge queue and deployments, for your team and the coding agents you bring over MCP and the API. Chat is free on every plan. Never per seat.",
31 }),
32 organization(),
33 application(20),
34 ];
35}
36
37/**
38 * g1t.sh: the landing page for a visitor. Someone signed in has a Home in
39 * their workspace (routes/workspace/home.tsx); the query goes with them.
40 *
41 * The desktop app is for using g1t, so a visitor there goes to the sign-in
42 * page instead (lib/desktop.ts). Its launcher opens `/?app=desktop`, which
43 * sets the cookie that says so.
44 */
45export async function loader({ context, request }: Route.LoaderArgs) {
46 const viewer = getViewer(context);
47 const url = new URL(request.url);
48 const launched = launchedFromDesktopApp(url);
49 const headers = new Headers();
50 if (launched) {
51 headers.set("set-cookie", cookie(DESKTOP_COOKIE, DESKTOP_VALUE, DESKTOP_COOKIE_SECONDS));
52 url.searchParams.delete(DESKTOP_QUERY);
53 }
54 if (!viewer) {
55 if (launched || fromDesktopApp(request)) throw redirect("/login", { headers });
56 return data({ signedIn: false as const });
57 }
58 const slug = chosenWorkspace(viewer.workspaces ?? [], readCookie(request.headers.get("cookie"), WORKSPACE_COOKIE))?.slug ?? null;
59 // A workspace-less account is sent to make one by the session (lib/session.server.ts).
60 if (!slug) throw redirect("/workspaces/new", { headers });
61 throw redirect(homePath(slug, url.search), { headers });
62}
63
64export default function Home() {
65 const navigate = useNavigate();
66 // A build of the desktop app older than `?app=desktop` opens `/` as it
67 // is: the page sets the cookie itself and moves on to signing in.
68 useEffect(() => {
69 if (!desktopBridge()) return;
70 rememberDesktopApp();
71 navigate("/login", { replace: true });
72 }, [navigate]);
73 return <Landing />;
74}