import { useEffect, useRef, useState } from "react"; import { api } from "./api.js"; type CrawlState = | { state: "idle" } | { state: "requesting" } | { state: "done"; relay?: string } | { state: "failed"; message: string }; export function RequestCrawl({ className = "", onRequested, }: { className?: string; onRequested?: () => void; }) { const [crawl, setCrawl] = useState({ state: "idle" }); const [menuOpen, setMenuOpen] = useState(false); const [showInput, setShowInput] = useState(false); const [relayInput, setRelayInput] = useState(""); const inputRef = useRef(null); useEffect(() => { if (!menuOpen) return; const close = (e: MouseEvent | KeyboardEvent) => { if (e instanceof KeyboardEvent && e.key !== "Escape") return; setMenuOpen(false); }; document.addEventListener("click", close); document.addEventListener("keydown", close); return () => { document.removeEventListener("click", close); document.removeEventListener("keydown", close); }; }, [menuOpen]); useEffect(() => { if (showInput) inputRef.current?.focus(); }, [showInput]); const handleRequestCrawl = async (relay?: string) => { setMenuOpen(false); setCrawl({ state: "requesting" }); try { await api.requestCrawl(relay); setCrawl({ state: "done", relay }); onRequested?.(); setTimeout(() => setCrawl({ state: "idle" }), 8000); } catch (e) { setCrawl({ state: "failed", message: (e as Error).message }); } }; return (
e.stopPropagation()}> {menuOpen && (
{showInput ? (
{ e.preventDefault(); const relay = relayInput.trim(); if (relay) handleRequestCrawl(relay); }} > setRelayInput(e.target.value)} placeholder="relay.example.com" aria-label="relay hostname" spellCheck={false} autoCapitalize="none" />
) : ( )}
)}
{crawl.state === "done" && ( {crawl.relay ? `crawl requested from ${crawl.relay}.` : "crawl requested."} the relay status will refresh shortly. )} {crawl.state === "failed" && ( crawl request failed: {crawl.message} )}
); }