1/** Sign in or create an account. Sessions come from Better Auth. */
2import { useState } from "react";
3import { getConnection, signIn, signUp, type User } from "../client.ts";
4import { Button, ErrorBox, Input, RepoIcon } from "../components.tsx";
5
6export const SignInPage = ({
7 onSignedIn,
8 onCancel,
9}: {
10 onSignedIn: (user: User) => void;
11 onCancel: () => void;
12}) => {
13 const [mode, setMode] = useState<"sign-in" | "sign-up">("sign-in");
14 const [name, setName] = useState("");
15 const [email, setEmail] = useState("");
16 const [password, setPassword] = useState("");
17 const [error, setError] = useState<unknown>(null);
18 const [busy, setBusy] = useState(false);
19 const connection = getConnection();
20
21 const submit = async () => {
22 setBusy(true);
23 setError(null);
24 try {
25 const user =
26 mode === "sign-up"
27 ? await signUp(connection, { name: name.trim(), email, password })
28 : await signIn(connection, { email, password });
29 onSignedIn(user);
30 } catch (cause) {
31 setError(cause);
32 } finally {
33 setBusy(false);
34 }
35 };
36
37 return (
38 <div className="mx-auto mt-24 max-w-md px-4">
39 <div className="mb-6 flex items-center justify-center gap-2 text-xl font-semibold">
40 <RepoIcon className="size-6" />
41 git service
42 </div>
43 <form
44 className="space-y-4 rounded-lg border border-border-muted bg-canvas-subtle p-6"
45 onSubmit={(event) => {
46 event.preventDefault();
47 void submit();
48 }}
49 >
50 {mode === "sign-up" && (
51 <label className="block space-y-1.5">
52 <span className="text-sm font-medium">Name</span>
53 <Input value={name} onChange={setName} placeholder="Dana" />
54 </label>
55 )}
56 <label className="block space-y-1.5">
57 <span className="text-sm font-medium">Email</span>
58 <Input
59 value={email}
60 onChange={setEmail}
61 type="email"
62 placeholder="dana@example.com"
63 />
64 </label>
65 <label className="block space-y-1.5">
66 <span className="text-sm font-medium">Password</span>
67 <Input
68 value={password}
69 onChange={setPassword}
70 type="password"
71 placeholder="at least 8 characters"
72 />
73 </label>
74 {error != null && <ErrorBox error={error} />}
75 <div className="flex items-center justify-between gap-2">
76 <Button
77 kind="primary"
78 type="submit"
79 disabled={busy || !email || password.length < 8}
80 >
81 {busy
82 ? "Working…"
83 : mode === "sign-up"
84 ? "Create account"
85 : "Sign in"}
86 </Button>
87 <button
88 type="button"
89 className="cursor-pointer text-xs text-fg-muted hover:text-accent"
90 onClick={() => setMode(mode === "sign-up" ? "sign-in" : "sign-up")}
91 >
92 {mode === "sign-up"
93 ? "Have an account? Sign in"
94 : "New here? Create an account"}
95 </button>
96 </div>
97 <button
98 type="button"
99 className="cursor-pointer text-xs text-fg-muted hover:text-accent"
100 onClick={onCancel}
101 >
102 Browse public repositories without signing in
103 </button>
104 </form>
105 </div>
106 );
107};