import { useRef, useState, useEffect } from "react"; import { useNavigate, useLocation, matchPath } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { listBands } from "../api/bands"; import { api } from "../api/client"; import { logout } from "../api/auth"; import { getInitials } from "../utils"; import type { MemberRead } from "../api/auth"; import { usePlayerStore } from "../stores/playerStore"; // ── Icons (inline SVG) ────────────────────────────────────────────────────── function IconWaveform() { return ( ); } function IconLibrary() { return ( ); } function IconPlay() { return ( ); } function IconSettings() { return ( ); } function IconMembers() { return ( ); } function IconStorage() { return ( ); } function IconChevron() { return ( ); } function IconSignOut() { return ( ); } // ── NavItem ───────────────────────────────────────────────────────────────── interface NavItemProps { icon: React.ReactNode; label: string; active: boolean; onClick: () => void; disabled?: boolean; } function NavItem({ icon, label, active, onClick, disabled }: NavItemProps) { const [hovered, setHovered] = useState(false); const color = active ? "#e8a22a" : disabled ? "rgba(255,255,255,0.18)" : hovered ? "rgba(255,255,255,0.7)" : "rgba(255,255,255,0.35)"; const bg = active ? "rgba(232,162,42,0.12)" : hovered && !disabled ? "rgba(255,255,255,0.045)" : "transparent"; return ( setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ display: "flex", alignItems: "center", gap: 9, width: "100%", padding: "7px 10px", borderRadius: 7, border: "none", cursor: disabled ? "default" : "pointer", color, background: bg, fontSize: 12, textAlign: "left", marginBottom: 1, transition: "background 0.12s, color 0.12s", fontFamily: "inherit", }} > {icon} {label} ); } // ── Sidebar ──────────────────────────────────────────────────────────────── export function Sidebar({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); const location = useLocation(); const [dropdownOpen, setDropdownOpen] = useState(false); const dropdownRef = useRef(null); const { data: bands } = useQuery({ queryKey: ["bands"], queryFn: listBands }); const { data: me } = useQuery({ queryKey: ["me"], queryFn: () => api.get("/auth/me"), }); // Derive active band from the current URL const bandMatch = matchPath("/bands/:bandId/*", location.pathname) ?? matchPath("/bands/:bandId", location.pathname); const activeBandId = bandMatch?.params?.bandId ?? null; const activeBand = bands?.find((b) => b.id === activeBandId) ?? null; // Nav active states const isLibrary = !!( matchPath({ path: "/bands/:bandId", end: true }, location.pathname) || matchPath("/bands/:bandId/sessions/:sessionId", location.pathname) || matchPath("/bands/:bandId/sessions/:sessionId/*", location.pathname) ); const isPlayer = !!matchPath("/bands/:bandId/songs/:songId", location.pathname); const isSettings = location.pathname.startsWith("/settings"); const isBandSettings = !!matchPath("/bands/:bandId/settings/*", location.pathname); const bandSettingsPanel = matchPath("/bands/:bandId/settings/:panel", location.pathname)?.params?.panel ?? null; // Player state const { currentSongId, currentBandId: playerBandId, isPlaying: isPlayerPlaying } = usePlayerStore(); const hasActiveSong = !!currentSongId && !!playerBandId; // Close dropdown on outside click useEffect(() => { if (!dropdownOpen) return; function handleClick(e: MouseEvent) { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setDropdownOpen(false); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [dropdownOpen]); const border = "rgba(255,255,255,0.06)"; return ( {/* ── Sidebar ──────────────────────────────────────────────────── */} {/* ── Main content ──────────────────────────────────────────────── */} {children} ); } function SectionLabel({ children, style, }: { children: React.ReactNode; style?: React.CSSProperties; }) { return ( {children} ); }