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

69 lines1,977 bytesCodeBlame
1import { Form, Link, redirect } from "react-router";
2
3import type { Route } from "./+types/login";
4import { AuthCard } from "../components/auth-card";
5import { Button, ErrorText, Field, Input } from "../components/ui";
6import { identity } from "../lib/services.server";
7import {
8 assertSameOrigin,
9 getViewer,
10 nextPath,
11 startSession,
12} from "../lib/session.server";
13
14export function meta({}: Route.MetaArgs) {
15 return [{ title: "Sign in · g1t" }];
16}
17
18export function loader({ request, context }: Route.LoaderArgs) {
19 if (getViewer(context)) throw redirect(nextPath(request));
20 return null;
21}
22
23export async function action({ request }: Route.ActionArgs) {
24 assertSameOrigin(request);
25 const form = await request.formData();
26 const result = await identity.signIn(
27 String(form.get("username") ?? "").trim(),
28 String(form.get("password") ?? ""),
29 );
30 if (!result.ok) return { error: result.error.message };
31 throw redirect(nextPath(request), {
32 headers: { "set-cookie": startSession(result.value.sessionToken) },
33 });
34}
35
36export default function Login({ actionData }: Route.ComponentProps) {
37 return (
38 <AuthCard
39 title="Welcome back"
40 subtitle="Sign in to g1t"
41 footer={
42 <>
43 New to g1t?{" "}
44 <Link to="/register" className="text-fg underline underline-offset-4">
45 Create an account
46 </Link>
47 </>
48 }
49 >
50 <Form method="post" className="space-y-4">
51 <Field label="Username">
52 <Input name="username" autoComplete="username" required autoFocus />
53 </Field>
54 <Field label="Password">
55 <Input
56 name="password"
57 type="password"
58 autoComplete="current-password"
59 required
60 />
61 </Field>
62 <ErrorText>{actionData?.error}</ErrorText>
63 <div className="pt-2 *:w-full">
64 <Button type="submit">Sign in</Button>
65 </div>
66 </Form>
67 </AuthCard>
68 );
69}