"use client"; import { useRef, useState, useCallback } from "react"; import { motion, AnimatePresence } from "motion/react"; import { Satellite, X, ChevronUp, Circle } from "lucide-react"; import { getCircuitState, getProviderOverride, type CircuitState, } from "@/lib/flight-api-client"; import type { ProviderName } from "@/lib/flight-api"; import { useDropdownDismiss } from "@/hooks/use-dropdown-dismiss"; // ── Provider definitions ─────────────────────────────────────────────── interface ProviderInfo { id: ProviderName; label: string; description: string; } const PROVIDERS: ProviderInfo[] = [ { id: "adsb", label: "adsb.lol", description: "Primary — server proxy" }, { id: "opensky", label: "OpenSky", description: "Fallback — limited credits", }, { id: "airplanes", label: "Airplanes.live", description: "Direct — CORS restricted", }, ]; const SOURCE_LABELS: Record = { adsb: "adsb.lol", opensky: "OpenSky", airplanes: "Airplanes.live", none: "Unavailable", }; const SOURCE_COLORS: Record = { adsb: "rgb(52, 211, 153)", // emerald opensky: "rgb(251, 191, 36)", // amber airplanes: "rgb(96, 165, 250)", // blue none: "rgb(248, 113, 113)", // red }; function circuitBadge( state: CircuitState, cooldownMs: number, ): { label: string; color: string } { switch (state) { case "closed": return { label: "OK", color: "rgb(52, 211, 153)" }; case "open": return { label: `DOWN ${Math.ceil(cooldownMs / 1000)}s`, color: "rgb(248, 113, 113)", }; case "half-open": return { label: "PROBING", color: "rgb(251, 191, 36)" }; } } function setProviderOverride(provider: ProviderName | "auto"): void { const url = new URL(window.location.href); if (provider === "auto") { url.searchParams.delete("provider"); } else { url.searchParams.set("provider", provider); } window.history.replaceState({}, "", url.toString()); } // ── Provider Dropdown ────────────────────────────────────────────────── export type ProviderDropdownProps = { open: boolean; onClose: () => void; currentSource: string | null; }; export function ProviderDropdown({ open, onClose, currentSource, }: ProviderDropdownProps) { const dropdownRef = useRef(null); useDropdownDismiss(dropdownRef, open, onClose); const [override, setOverride] = useState(() => getProviderOverride()); const isAutoMode = override === "auto"; const isDev = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"); const handleSelect = useCallback( (provider: ProviderName | "auto") => { setProviderOverride(provider); setOverride(provider === "auto" ? "auto" : provider); onClose(); }, [onClose], ); return ( {open && ( {/* Header */}
Providers
{/* Provider list */}
{/* Auto option */} {/* Individual providers */} {PROVIDERS.map((provider) => { const isSelected = override === provider.id; const isActive = currentSource === provider.id; const circuit = getCircuitState(provider.id); const badge = circuitBadge( circuit.state, circuit.cooldownRemaining, ); const isAvailable = provider.id !== "airplanes" || isDev; return ( ); })}
)}
); } // ── Provider Trigger (for status bar) ────────────────────────────────── export type ProviderTriggerProps = { source: string | null; loading: boolean; rateLimited: boolean; onClick: () => void; }; export function ProviderTrigger({ source, loading, rateLimited, onClick, }: ProviderTriggerProps) { const label = rateLimited ? "Paused" : loading && !source ? "Connecting…" : source ? (SOURCE_LABELS[source] ?? source) : "Connecting…"; const dotColor = rateLimited ? "text-amber-400/80" : source === "none" ? "text-red-400/80" : source === "opensky" ? "text-amber-400/80" : source === "airplanes" ? "text-blue-400/80" : "text-emerald-400/80"; return ( ); }