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.

65 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 "Repositories, pull requests, required checks, a merge queue and deployments, with chat for your team. Connect the coding agents you already use through MCP and the API. Chat is free on every plan, never per seat.",
22 }),
23 organization(),
24 application(20),
25 ];
26}
27
28/**
29 * g1t.sh: the landing page for a visitor. Someone signed in has a Home in
30 * their workspace (routes/workspace/home.tsx); the query goes with them.
31 *
32 * The desktop app is for using g1t, so a visitor there goes to the sign-in
33 * page instead (lib/desktop.ts). Its launcher opens `/?app=desktop`, which
34 * sets the cookie that says so.
35 */
36export async function loader({ context, request }: Route.LoaderArgs) {
37 const viewer = getViewer(context);
38 const url = new URL(request.url);
39 const launched = launchedFromDesktopApp(url);
40 const headers = new Headers();
41 if (launched) {
42 headers.set("set-cookie", cookie(DESKTOP_COOKIE, DESKTOP_VALUE, DESKTOP_COOKIE_SECONDS));
43 url.searchParams.delete(DESKTOP_QUERY);
44 }
45 if (!viewer) {
46 if (launched || fromDesktopApp(request)) throw redirect("/login", { headers });
47 return data({ signedIn: false as const });
48 }
49 const slug = chosenWorkspace(viewer.workspaces ?? [], readCookie(request.headers.get("cookie"), WORKSPACE_COOKIE))?.slug ?? null;
50 // A workspace-less account is sent to make one by the session (lib/session.server.ts).
51 if (!slug) throw redirect("/workspaces/new", { headers });
52 throw redirect(homePath(slug, url.search), { headers });
53}
54
55export default function Home() {
56 const navigate = useNavigate();
57 // A build of the desktop app older than `?app=desktop` opens `/` as it
58 // is: the page sets the cookie itself and moves on to signing in.
59 useEffect(() => {
60 if (!desktopBridge()) return;
61 rememberDesktopApp();
62 navigate("/login", { replace: true });
63 }, [navigate]);
64 return <Landing />;
65}