Skip to content
95 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'worktree-agent-a985c01f43538af01'1import { Form, Link, redirect } from "react-router";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms2
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers3import { USERNAME_PATTERN } from "@g1t/contracts";
4
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms5import type { Route } from "./+types/auth-github-username";
6import { AuthCard } from "../components/auth-card";
7import { GithubMark } from "../components/github";
Merge branch 'worktree-agent-a985c01f43538af01'8import { ErrorText, Field, Input, SubmitButton } from "../components/ui";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms9import { PENDING_COOKIE, cookie, readCookie } from "../lib/github";
10import { githubSignIn } from "../lib/github.server";
11import { page } from "../lib/meta";
12import { safeNext } from "../lib/next";
13import { assertSameOrigin, startSession } from "../lib/session.server";
14
15export function meta(args: Route.MetaArgs) {
16 return page(args, { title: "Choose a username · g1t" });
17}
18
19/** A new account from GitHub, once its username (and invite) are settled. */
20export async function loader({ request }: Route.LoaderArgs) {
21 const pending = readCookie(request.headers.get("cookie"), PENDING_COOKIE);
22 if (!pending) throw redirect("/register");
23 const found = await githubSignIn.pending(pending);
24 if (!found.ok || found.value.kind !== "username") {
25 throw redirect("/register", { headers: { "set-cookie": cookie(PENDING_COOKIE, "", 0) } });
26 }
27 return found.value;
28}
29
30export async function action({ request }: Route.ActionArgs) {
31 assertSameOrigin(request);
32 const pending = readCookie(request.headers.get("cookie"), PENDING_COOKIE);
33 if (!pending) throw redirect("/register");
34 const form = await request.formData();
35 // Where to go afterwards, read before the pending sign-in is used up.
36 const found = await githubSignIn.pending(pending).catch(() => null);
37 const result = await githubSignIn.signUp(
38 pending,
39 String(form.get("username") ?? ""),
40 String(form.get("invite") ?? "").trim() || null,
41 );
42 if (!result.ok) return { error: result.error.message };
43 const headers = new Headers({ "set-cookie": startSession(result.value.sessionToken) });
44 headers.append("set-cookie", cookie(PENDING_COOKIE, "", 0));
45 throw redirect(safeNext(found?.ok ? found.value.next : "/"), { headers });
46}
47
48export default function GithubUsername({ loaderData, actionData }: Route.ComponentProps) {
49 return (
50 <AuthCard
51 title="Create your account"
52 subtitle={loaderData.inviteRequired ? "g1t is invite-only for now" : "Choose your username"}
53 footer={
54 <>
55 Have an account already?{" "}
56 <Link to="/login?github=link" className="text-fg underline underline-offset-4">
57 Sign in to link GitHub
58 </Link>
59 </>
60 }
61 >
62 <p className="mb-6 flex items-center gap-2 text-sm text-muted">
63 <GithubMark />
64 Signed in to GitHub as <span className="font-mono text-fg">@{loaderData.login}</span>
65 </p>
66 <Form method="post" className="space-y-4">
67 <Field
68 label="Username"
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers69 hint="Letters, digits and single hyphens. It is how others see you on g1t, in the case you type it."
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms70 >
71 <Input
72 name="username"
73 autoComplete="username"
74 required
75 autoFocus
76 maxLength={39}
77 defaultValue={loaderData.suggestion ?? ""}
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers78 pattern={USERNAME_PATTERN}
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms79 />
80 </Field>
81 {loaderData.inviteRequired && (
82 <Field label="Invite code" hint="g1t is invite-only. Ask someone on g1t for one of their invites.">
83 <Input name="invite" required autoComplete="off" spellCheck={false} />
84 </Field>
85 )}
86 <ErrorText>{actionData?.error}</ErrorText>
87 <div className="pt-2 *:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'88 <SubmitButton pending="Creating account…">
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms89 Create account
Merge branch 'worktree-agent-a985c01f43538af01'90 </SubmitButton>
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms91 </div>
92 </Form>
93 </AuthCard>
94 );
95}

This file's history is long; its oldest lines are credited to the oldest commit read.