From 936835eea2903522389b65ee3065969e6917b9ad Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Tue, 14 Apr 2026 22:47:39 -0400 Subject: [PATCH] web: revamp login page with bsky actor search --- web/src/hooks/useHandleSearch.ts | 31 ++++++++ web/src/lib/bsky.ts | 32 +++++++++ web/src/pages/Login.tsx | 120 +++++++++++++++++++++++++------ 3 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 web/src/hooks/useHandleSearch.ts create mode 100644 web/src/lib/bsky.ts diff --git a/web/src/hooks/useHandleSearch.ts b/web/src/hooks/useHandleSearch.ts new file mode 100644 index 0000000..87222d8 --- /dev/null +++ b/web/src/hooks/useHandleSearch.ts @@ -0,0 +1,31 @@ +/** Debounced handle typeahead using Bluesky's public API. */ + +import { useEffect, useState } from "react"; +import { searchHandles, type HandleMatch } from "../lib/bsky"; + +const DEBOUNCE_MS = 300; + +export function useHandleSearch(query: string): HandleMatch[] { + const [matches, setMatches] = useState([]); + + useEffect(() => { + const trimmed = query.trim(); + if (trimmed.length < 2) { + setMatches([]); + return; + } + + let cancelled = false; + const timeout = setTimeout(async () => { + const results = await searchHandles(trimmed); + if (!cancelled) setMatches(results); + }, DEBOUNCE_MS); + + return () => { + cancelled = true; + clearTimeout(timeout); + }; + }, [query]); + + return matches; +} diff --git a/web/src/lib/bsky.ts b/web/src/lib/bsky.ts new file mode 100644 index 0000000..da6ce24 --- /dev/null +++ b/web/src/lib/bsky.ts @@ -0,0 +1,32 @@ +/** Bluesky public API helpers. */ + +const BSKY_PUBLIC = "https://public.api.bsky.app"; + +export interface HandleMatch { + handle: string; + displayName: string; + avatar?: string; +} + +export async function searchHandles( + query: string, + limit = 5, +): Promise { + const url = + `${BSKY_PUBLIC}/xrpc/app.bsky.actor.searchActorsTypeahead` + + `?q=${encodeURIComponent(query)}&limit=${limit}`; + const resp = await fetch(url); + if (!resp.ok) return []; + const data = (await resp.json()) as { + actors: { + handle: string; + displayName?: string; + avatar?: string; + }[]; + }; + return data.actors.map((actor) => ({ + handle: actor.handle, + displayName: actor.displayName ?? actor.handle, + avatar: actor.avatar, + })); +} diff --git a/web/src/pages/Login.tsx b/web/src/pages/Login.tsx index 80878ab..c18a7fd 100644 --- a/web/src/pages/Login.tsx +++ b/web/src/pages/Login.tsx @@ -1,6 +1,7 @@ -import { useState, type SyntheticEvent } from "react"; +import { useRef, useState, type SyntheticEvent } from "react"; import { useAuth } from "../lib/auth"; import { usePageTitle } from "../hooks/usePageTitle"; +import { useHandleSearch } from "../hooks/useHandleSearch"; import HandleInput from "../components/form/HandleInput"; import { Button } from "../components/form/Form"; @@ -9,10 +10,13 @@ export default function Login() { const [handle, setHandle] = useState(""); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); + const [focused, setFocused] = useState(false); + const blurTimeout = useRef>(undefined); + const matches = useHandleSearch(handle); usePageTitle("Login — atbbs"); - async function onSubmit(e: SyntheticEvent) { - e.preventDefault(); + async function onSubmit(event: SyntheticEvent) { + event.preventDefault(); setError(null); setBusy(true); try { @@ -23,25 +27,99 @@ export default function Login() { } } + function selectHandle(selected: string) { + setHandle(selected); + setFocused(false); + } + + function onFocus() { + clearTimeout(blurTimeout.current); + setFocused(true); + } + + function onBlur() { + blurTimeout.current = setTimeout(() => setFocused(false), 150); + } + return ( - <> -

log in

-

- Sign in with your atproto handle to post threads and replies. +

+
+ + + @bbs + +

Log in to atbbs

+

Use any atproto account.

+
+ + {error &&

{error}

} + +
+
+ + + + {focused && matches.length > 0 && ( +
+
+ {matches.map((match) => ( + + ))} +
+
+ )} +
+ +
+

Once signed in, you can:

+
    +
  • Post threads and replies
  • +
  • Pin boards you like
  • +
  • Set up a profile
  • +
  • Start your own community
  • +
+
+ +

+ You'll be redirected to your hosting provider to continue.

- {error &&

{error}

} -
- - - - +
); } -- 2.51.2