1import { createSignal, Show } from "solid-js";
2import "./oauth-config";
3import { useOAuthScopeFlow } from "./scope-flow";
4import { ScopeSelector } from "./scope-selector";
5
6interface LoginProps {
7 onCancel?: () => void;
8}
9
10export const Login = (props: LoginProps) => {
11 const [notice, setNotice] = createSignal("");
12 const [loginInput, setLoginInput] = createSignal("");
13
14 const scopeFlow = useOAuthScopeFlow({
15 onError: (e) => setNotice(`${e}`),
16 onRedirecting: () => {
17 setNotice(`Contacting your data server...`);
18 setTimeout(() => setNotice(`Redirecting...`), 0);
19 },
20 });
21
22 const initiateLogin = (handle: string) => {
23 setNotice("");
24 scopeFlow.initiate(handle);
25 };
26
27 const handleCancel = () => {
28 scopeFlow.cancel();
29 setLoginInput("");
30 setNotice("");
31 props.onCancel?.();
32 };
33
34 return (
35 <div class="flex flex-col gap-y-2 px-1">
36 <Show when={!scopeFlow.showScopeSelector()}>
37 <Show when={props.onCancel}>
38 <div class="mb-1 flex items-center gap-2">
39 <button
40 onclick={handleCancel}
41 class="flex items-center rounded-md p-1 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
42 >
43 <span class="iconify lucide--arrow-left"></span>
44 </button>
45 <div class="font-semibold">Add account</div>
46 </div>
47 </Show>
48 <form onsubmit={(e) => e.preventDefault()}>
49 <label for="username" class="hidden">
50 Add account
51 </label>
52 <div class="dark:bg-dark-100 dark:inset-shadow-dark-200 flex grow items-center gap-2 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 inset-shadow-xs focus-within:outline-[1px] focus-within:outline-neutral-600 dark:border-neutral-600 dark:focus-within:outline-neutral-400">
53 <label
54 for="username"
55 class="iconify lucide--user-round-plus shrink-0 text-neutral-500 dark:text-neutral-400"
56 ></label>
57 <input
58 type="text"
59 spellcheck={false}
60 placeholder="user.bsky.social"
61 id="username"
62 name="username"
63 autocomplete="username"
64 autofocus
65 aria-label="Your AT Protocol handle"
66 class="grow py-1 select-none placeholder:text-sm focus:outline-none"
67 onInput={(e) => setLoginInput(e.currentTarget.value)}
68 />
69 <button
70 onclick={() => initiateLogin(loginInput())}
71 class="flex items-center rounded-md p-1 hover:bg-neutral-100 active:bg-neutral-200 dark:hover:bg-neutral-600 dark:active:bg-neutral-500"
72 >
73 <span class="iconify lucide--log-in"></span>
74 </button>
75 </div>
76 </form>
77 </Show>
78
79 <Show when={scopeFlow.showScopeSelector()}>
80 <ScopeSelector onConfirm={scopeFlow.complete} onCancel={handleCancel} />
81 </Show>
82
83 <Show when={notice()}>
84 <div class="text-sm">{notice()}</div>
85 </Show>
86 </div>
87 );
88};