import { useEffect, useRef, useState } from 'react' type Suggestion = { did: string handle: string displayName?: string avatar?: string } export function LoginForm() { const [handle, setHandle] = useState('') const [suggestions, setSuggestions] = useState([]) const [open, setOpen] = useState(false) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const abortRef = useRef(null) useEffect(() => { const q = handle.trim() if (q.length < 1) { setSuggestions([]) return } abortRef.current?.abort() const ctrl = new AbortController() abortRef.current = ctrl const timer = setTimeout(async () => { try { const url = `https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead?q=${encodeURIComponent(q)}&limit=8` const res = await fetch(url, { signal: ctrl.signal }) if (!res.ok) return const data = (await res.json()) as { actors: Suggestion[] } setSuggestions(data.actors ?? []) } catch (err) { if ((err as Error).name !== 'AbortError') { console.error('typeahead failed', err) } } }, 200) return () => { clearTimeout(timer) ctrl.abort() } }, [handle]) function onSubmit(e: React.FormEvent) { e.preventDefault() const value = handle.trim() if (!value) return setSubmitting(true) setError(null) // Hand off to the server's OAuth flow: it redirects to the PDS and lands back on /oauth/callback. window.location.href = `/oauth/login?handle=${encodeURIComponent(value)}` } return (
{error &&

{error}

}
) }