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.

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