g1t/apps/web/app/routes/login.tsx

63 lines2,105 bytesCodeBlame

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.

Initial g1t: services, event bus, intents and attempts1import { env } from "cloudflare:workers";
2import { Form, redirect } from "react-router";
3
4import type { Route } from "./+types/login";
5import { Button, ErrorText, Field, Input } from "../components/ui";
6import {
7 assertSameOrigin,
8 getViewer,
9 startSession,
10} from "../lib/session.server";
11
12export function meta({}: Route.MetaArgs) {
13 return [{ title: "Sign in · g1t" }];
14}
15
16/** Only same-site paths, so `next` cannot redirect off g1t. */
17function nextPath(request: Request): string {
18 const next = new URL(request.url).searchParams.get("next") ?? "/";
19 return next.startsWith("/") && !next.startsWith("//") ? next : "/";
20}
21
22export function loader({ request, context }: Route.LoaderArgs) {
23 if (getViewer(context)) throw redirect(nextPath(request));
24 return null;
25}
26
27export async function action({ request }: Route.ActionArgs) {
28 assertSameOrigin(request);
29 const form = await request.formData();
30 const username = String(form.get("username") ?? "").trim();
31 const password = String(form.get("password") ?? "");
32 const result = await env.IDENTITY.signIn(username, password);
33 if (!result.ok) return { error: result.error.message };
34 throw redirect(nextPath(request), {
35 headers: { "set-cookie": startSession(result.value.sessionToken) },
36 });
37}
38
39export default function Login({ actionData }: Route.ComponentProps) {
40 return (
41 <main className="mx-auto max-w-sm px-4 pt-32">
42 <h1 className="text-xl font-semibold">Welcome to g1t</h1>
43 <p className="text-xl text-muted">Many attempts. One ships.</p>
44 <Form method="post" className="mt-10 space-y-4">
45 <Field label="Username">
46 <Input name="username" autoComplete="username" required autoFocus />
47 </Field>
48 <Field label="Password">
49 <Input
50 name="password"
51 type="password"
52 autoComplete="current-password"
53 required
54 />
55 </Field>
56 <ErrorText>{actionData?.error}</ErrorText>
57 <div className="pt-2 *:w-full">
58 <Button type="submit">Sign in</Button>
59 </div>
60 </Form>
61 </main>
62 );
63}