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";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2
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
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look5import 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";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look9import { 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."
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look70 >
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}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look79 />
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…">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look89 Create account
Merge branch 'worktree-agent-a985c01f43538af01'90 </SubmitButton>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look91 </div>
92 </Form>
93 </AuthCard>
94 );
95}

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