import { useEffect, useRef, useState } from 'react'; import { Search, X, Command, ChevronRight, Wifi, WifiOff, Menu } from 'lucide-react'; import { useNavigate, useRouterState } from '@tanstack/react-router'; import { useQuery } from '@tanstack/react-query'; import { fetchHealthStatus } from '../services/healthService'; interface TopBarProps { onToggleCommandPalette: () => void; onToggleNavigation: () => void; navigationOpen: boolean; } /** Page title map for breadcrumbs */ const PAGE_TITLES: Record = { '/': 'Home', '/tracks': 'Songs', '/albums': 'Albums', '/artists': 'Artists', '/genres': 'Genres', '/vibe': 'Vibe', '/discover': 'Discover', '/search': 'Search', '/settings': 'Settings', '/quarantine': 'Quarantine', '/jobs': 'Jobs', }; function Breadcrumbs({ pathname }: { pathname: string }) { // Handle detail pages const segments = pathname.split('/').filter(Boolean); if (segments.length <= 1) { const title = PAGE_TITLES[pathname] ?? 'Muzick'; return ( {title} ); } // For /albums/$id or /artists/$id const parentPath = `/${segments[0]}`; const parentTitle = PAGE_TITLES[parentPath] ?? segments[0]; return (
{parentTitle} Details
); } function ConnectionStatus() { const { data, isError } = useQuery({ queryKey: ['health'], queryFn: () => fetchHealthStatus(), refetchInterval: 30_000, staleTime: 10_000, retry: 1, }); const healthy = data?.postgres === 'ok'; return (
{healthy ? ( ) : ( )} {healthy ? 'Connected' : isError ? 'Offline' : '…'}
); } export function TopBar({ onToggleCommandPalette, onToggleNavigation, navigationOpen }: TopBarProps) { const navigate = useNavigate(); const inputRef = useRef(null); const { pathname, urlQuery } = useRouterState({ select: (s) => ({ pathname: s.location.pathname, urlQuery: ((s.location.search as Record)?.q as string | undefined) ?? '', }), }); const onSearchPage = pathname === '/search'; const [q, setQ] = useState(urlQuery); useEffect(() => { if (onSearchPage) setQ(urlQuery); }, [urlQuery, onSearchPage]); // Debounced URL push on search page useEffect(() => { if (!onSearchPage) return; const id = setTimeout(() => { const next = q.trim(); if (next !== urlQuery) { void navigate({ to: '/search', search: { q: next || undefined } as any, replace: true }); } }, 250); return () => clearTimeout(id); // eslint-disable-next-line react-hooks/exhaustive-deps }, [q, onSearchPage]); // Global "/" shortcut: focus search useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return; const el = document.activeElement; const tag = el?.tagName; if (tag === 'INPUT' || tag === 'TEXTAREA' || (el as HTMLElement)?.isContentEditable) return; e.preventDefault(); inputRef.current?.focus(); inputRef.current?.select(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, []); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (q.trim()) void navigate({ to: '/search', search: { q: q.trim() } as any }); }; return (
{/* Breadcrumbs */}
{/* Universal search */}
setQ(e.target.value)} placeholder="Search…" /* bg-bg2, not bg-surface0/70: Tailwind cannot alpha-modify these var() colors, so that class emitted nothing and the field fell back to the UA's white — a white pill in a warm dark room. */ className="w-full bg-bg2 border border-border rounded-md pl-8 pr-8 py-1.5 text-xs text-text placeholder:text-muted outline-none focus:border-accent focus:bg-surface1 transition-colors" /> {q ? ( ) : ( / )}
{/* Right section */}
{/* Connection status */} {/* Command palette toggle */}
); }