"use client" import { useCallback, useEffect, useId, useRef, useState } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { setSetupIdentity, resolveIdentity, type ResolveResult } from "@/lib/api" interface SetupConfigureProps { mode: string onComplete: (opts?: { attachedDid?: string; attachedHandle?: string | null }) => void onBack?: () => void } export function SetupConfigure({ mode, onComplete, onBack }: SetupConfigureProps) { if (mode === "attach_account") { return onComplete(opts)} onBack={onBack} /> } return null } function AttachAccountForm({ onComplete, onBack }: { onComplete: (opts: { attachedDid: string; attachedHandle: string | null }) => void onBack?: () => void }) { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [inputValue, setInputValue] = useState("") const [suggestions, setSuggestions] = useState([]) const [showSuggestions, setShowSuggestions] = useState(false) const [selectedProfile, setSelectedProfile] = useState(null) const [resolving, setResolving] = useState(false) const [focusedIndex, setFocusedIndex] = useState(-1) const debounceRef = useRef | null>(null) const containerRef = useRef(null) const listboxId = useId() useEffect(() => { function handleClickOutside(e: MouseEvent) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setShowSuggestions(false) } } document.addEventListener("mousedown", handleClickOutside) return () => document.removeEventListener("mousedown", handleClickOutside) }, []) const searchIdentity = useCallback(async (query: string) => { const q = query.trim() if (q.length < 2) { setSuggestions([]) setShowSuggestions(false) return } setResolving(true) try { const results = await resolveIdentity(q) setSuggestions(results) setShowSuggestions(true) setFocusedIndex(-1) } catch { setSuggestions([]) setShowSuggestions(false) setFocusedIndex(-1) } finally { setResolving(false) } }, []) function handleInputChange(value: string) { setInputValue(value) setSelectedProfile(null) if (debounceRef.current) clearTimeout(debounceRef.current) debounceRef.current = setTimeout(() => searchIdentity(value), 300) } function selectResult(result: ResolveResult) { setSelectedProfile(result) setInputValue(result.handle ?? result.did) setShowSuggestions(false) setSuggestions([]) } function clearSelection() { setSelectedProfile(null) setInputValue("") setSuggestions([]) } async function handleSubmit() { const did = selectedProfile?.did ?? inputValue.trim() if (!did) return setLoading(true) setError(null) try { await setSetupIdentity({ mode: "attach_account", attached_account_did: did }) onComplete({ attachedDid: did, attachedHandle: selectedProfile?.handle ?? null, }) } catch (e) { setError(e instanceof Error ? e.message : "Failed to link account. Check the identifier and try again.") } finally { setLoading(false) } } const trimmedInput = inputValue.trim() const looksValid = selectedProfile != null || /^did:[a-z]+:.+/.test(trimmedInput) || trimmedInput.includes(".") const showFormatHint = trimmedInput.length >= 2 && !looksValid && !resolving const displayName = selectedProfile?.display_name ?? selectedProfile?.handle ?? selectedProfile?.did const avatarFallback = displayName?.charAt(0).toUpperCase() ?? "?" const hasSuggestions = showSuggestions && suggestions.length > 0 const showEmpty = showSuggestions && suggestions.length === 0 && !resolving && trimmedInput.length >= 2 return ( Find your account Search for the AT Protocol account you want to link to this AppView.
handleInputChange(e.target.value)} onFocus={() => { if (suggestions.length > 0) setShowSuggestions(true) }} onKeyDown={(e) => { if (e.key === "Enter") { if (hasSuggestions && focusedIndex >= 0) { e.preventDefault() selectResult(suggestions[focusedIndex]) } else if (!hasSuggestions && looksValid && trimmedInput && !loading) { e.preventDefault() handleSubmit() } return } if (!hasSuggestions) return if (e.key === "ArrowDown") { e.preventDefault() setFocusedIndex((i) => (i + 1) % suggestions.length) } else if (e.key === "ArrowUp") { e.preventDefault() setFocusedIndex((i) => (i <= 0 ? suggestions.length - 1 : i - 1)) } else if (e.key === "Escape") { setShowSuggestions(false) setFocusedIndex(-1) } }} autoComplete="off" disabled={loading} aria-required="true" role="combobox" aria-expanded={hasSuggestions} aria-controls={listboxId} aria-autocomplete="list" aria-activedescendant={focusedIndex >= 0 ? `${listboxId}-option-${focusedIndex}` : undefined} /> {resolving && ( Resolving… )} {hasSuggestions && (
{suggestions.map((result, index) => { const name = result.display_name ?? result.handle ?? result.did const fallback = name.charAt(0).toUpperCase() return ( ) })}
)} {showEmpty && (

No accounts found. Try a full handle (e.g. alice.bsky.social) or a DID.

)}
{showFormatHint && (

Enter a handle (e.g. alice.bsky.social) or a DID (e.g. did:plc:...).

)}
{selectedProfile && (
{selectedProfile.avatar && } {avatarFallback}
{selectedProfile.display_name && (

{selectedProfile.display_name}

)}

{selectedProfile.handle ? `@${selectedProfile.handle}` : selectedProfile.did}

{selectedProfile.did}

)} {error &&

{error}

}
{onBack ? ( ) :
}
) }