forked from
juliet.paris/streamplace-spa
minimal streamplace frontend
1import { Show, createEffect, createSignal, onCleanup } from "solid-js";
2
3import { signIn } from "../auth/login";
4import { showLoginModal, setShowLoginModal } from "../auth/login-modal";
5import { signOut } from "../auth/session-manager";
6import { agent, loggedInHandle } from "../auth/state";
7
8export function LoginButton() {
9 const [handle, setHandle] = createSignal("");
10 const [loading, setLoading] = createSignal(false);
11
12 const handleSignIn = async () => {
13 const h = handle().trim();
14 if (!h) return;
15 setLoading(true);
16 try {
17 await signIn(h);
18 } catch (err) {
19 console.error("Sign in failed:", err);
20 setLoading(false);
21 }
22 };
23
24 const handleKeyDown = (e: KeyboardEvent) => {
25 if (e.key === "Enter") handleSignIn();
26 };
27
28 createEffect(() => {
29 if (!showLoginModal()) return;
30 const onEsc = (e: KeyboardEvent) => {
31 if (e.key === "Escape") setShowLoginModal(false);
32 };
33 document.addEventListener("keydown", onEsc);
34 onCleanup(() => document.removeEventListener("keydown", onEsc));
35 });
36
37 return (
38 <div class="flex items-center gap-3">
39 <Show
40 when={agent()}
41 fallback={
42 <>
43 <button
44 class="bg-sp-accent text-sp-bg hover:bg-sp-accent/80 rounded-sm border border-transparent px-3 py-1.5 text-sm font-medium transition-colors"
45 onClick={() => setShowLoginModal(true)}
46 >
47 Sign in
48 </button>
49 <Show when={showLoginModal()}>
50 <div
51 class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
52 onClick={(e) => {
53 if (e.target === e.currentTarget) setShowLoginModal(false);
54 }}
55 >
56 <div class="bg-sp-surface border-sp-border mx-4 flex w-full max-w-md flex-col gap-4 rounded-lg border p-5 shadow-lg">
57 <h2 class="text-sp-text text-lg font-semibold">Sign in</h2>
58 <input
59 type="text"
60 placeholder="handle.bsky.social"
61 class="border-sp-border bg-sp-bg text-sp-text placeholder:text-sp-dim focus:border-sp-accent rounded-sm border px-3 py-1.5 text-sm focus:outline-none"
62 value={handle()}
63 onInput={(e) => setHandle(e.currentTarget.value)}
64 onKeyDown={handleKeyDown}
65 ref={(el: HTMLInputElement) => setTimeout(() => el.focus())}
66 />
67 <div class="flex justify-end gap-2">
68 <button
69 class="text-sp-dim hover:text-sp-text rounded-sm px-3 py-1.5 text-sm transition-colors"
70 onClick={() => setShowLoginModal(false)}
71 >
72 Cancel
73 </button>
74 <button
75 class="bg-sp-accent text-sp-bg hover:bg-sp-accent/80 rounded-sm px-3 py-1.5 text-sm font-medium transition-colors disabled:opacity-50"
76 onClick={handleSignIn}
77 disabled={loading() || !handle().trim()}
78 >
79 {loading() ? "..." : "Go"}
80 </button>
81 </div>
82 </div>
83 </div>
84 </Show>
85 </>
86 }
87 >
88 <span class="text-sp-dim hidden text-sm sm:inline">@{loggedInHandle() || "..."}</span>
89 <button
90 class="border-sp-border text-sp-dim hover:border-sp-red hover:text-sp-red rounded-sm border px-3 py-1.5 text-sm transition-colors"
91 onClick={() => signOut()}
92 >
93 Sign out
94 </button>
95 </Show>
96 </div>
97 );
98}