Skip to content
86 linesCodeBlameRaw
1import { Form, Link, redirect } from "react-router";
2
3import { USERNAME_PATTERN } from "@g1t/contracts";
4
5import type { Route } from "./+types/auth-github-username";
6import { AuthCard } from "../components/auth-card";
7import { GithubMark } from "../components/github";
8import { ErrorText, Field, Input, SubmitButton } from "../components/ui";
9import { 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 is 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(pending, String(form.get("username") ?? ""));
38 if (!result.ok) return { error: result.error.message };
39 const headers = new Headers({ "set-cookie": startSession(result.value.sessionToken) });
40 headers.append("set-cookie", cookie(PENDING_COOKIE, "", 0));
41 throw redirect(safeNext(found?.ok ? found.value.next : "/"), { headers });
42}
43
44export default function GithubUsername({ loaderData, actionData }: Route.ComponentProps) {
45 return (
46 <AuthCard
47 title="Create your account"
48 subtitle="Choose your username"
49 footer={
50 <>
51 Have an account already?{" "}
52 <Link to="/login?github=link" className="text-fg underline underline-offset-4">
53 Sign in to link GitHub
54 </Link>
55 </>
56 }
57 >
58 <p className="mb-6 flex items-center gap-2 text-sm text-muted">
59 <GithubMark />
60 Signed in to GitHub as <span className="font-mono text-fg">@{loaderData.login}</span>
61 </p>
62 <Form method="post" className="space-y-4">
63 <Field
64 label="Username"
65 hint="Letters, digits and single hyphens. It is how others see you on g1t, in the case you type it."
66 >
67 <Input
68 name="username"
69 autoComplete="username"
70 required
71 autoFocus
72 maxLength={39}
73 defaultValue={loaderData.suggestion ?? ""}
74 pattern={USERNAME_PATTERN}
75 />
76 </Field>
77 <ErrorText>{actionData?.error}</ErrorText>
78 <div className="pt-2 *:w-full">
79 <SubmitButton pending="Creating account…">
80 Create account
81 </SubmitButton>
82 </div>
83 </Form>
84 </AuthCard>
85 );
86}