import { useRef, useState, useEffect } from "react";
import { useNavigate, useLocation, matchPath } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { listBands, createBand } from "../api/bands";
import { getInitials } from "../utils";
import { useBandStore } from "../stores/bandStore";
// ── Shared primitives ──────────────────────────────────────────────────────────
const inputStyle: React.CSSProperties = {
width: "100%",
padding: "8px 11px",
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.1)",
borderRadius: 7,
color: "#e8e9f0",
fontSize: 13,
fontFamily: "inherit",
outline: "none",
boxSizing: "border-box",
};
const labelStyle: React.CSSProperties = {
display: "block",
fontSize: 10,
fontWeight: 600,
letterSpacing: "0.06em",
color: "rgba(232,233,240,0.4)",
marginBottom: 5,
};
// ── Error banner ───────────────────────────────────────────────────────────────
function ErrorBanner({ msg }: { msg: string }) {
return (
{msg}
);
}
// ── Band creation form ─────────────────────────────────────────────────────────
function BandStep({ onClose }: { onClose: () => void }) {
const navigate = useNavigate();
const qc = useQueryClient();
const [name, setName] = useState("");
const [slug, setSlug] = useState("");
const [error, setError] = useState(null);
const nameRef = useRef(null);
useEffect(() => { nameRef.current?.focus(); }, []);
const mutation = useMutation({
mutationFn: () => createBand({ name, slug }),
onSuccess: (band) => {
qc.invalidateQueries({ queryKey: ["bands"] });
onClose();
navigate(`/bands/${band.id}`);
},
onError: (err) => setError(err instanceof Error ? err.message : "Failed to create band"),
});
const handleNameChange = (v: string) => {
setName(v);
setSlug(v.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""));
};
return (
<>
{error && }
handleNameChange(e.target.value)}
style={inputStyle}
placeholder="e.g. The Midnight Trio"
onKeyDown={(e) => { if (e.key === "Enter" && name && slug) mutation.mutate(); }}
/>
setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))}
style={{ ...inputStyle, fontFamily: "monospace" }}
placeholder="the-midnight-trio"
/>
Connect storage after creating the band via Settings → Storage.
>
);
}
// ── Create Band Modal ──────────────────────────────────────────────────────────
function CreateBandModal({ onClose }: { onClose: () => void }) {
// Close on Escape
useEffect(() => {
const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); };
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [onClose]);
return (
e.stopPropagation()}
style={{ background: "#112018", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 14, padding: 28, width: 420, boxShadow: "0 24px 64px rgba(0,0,0,0.6)" }}
>
New band
Create a workspace for your recordings.
);
}
// ── TopBandBar ─────────────────────────────────────────────────────────────────
export function TopBandBar() {
const navigate = useNavigate();
const location = useLocation();
const [open, setOpen] = useState(false);
const [showCreate, setShowCreate] = useState(false);
const ref = useRef(null);
const { data: bands } = useQuery({ queryKey: ["bands"], queryFn: listBands });
const { activeBandId, setActiveBandId } = useBandStore();
// Sync store from URL when on a band page
const urlMatch =
matchPath("/bands/:bandId/*", location.pathname) ??
matchPath("/bands/:bandId", location.pathname);
const urlBandId = urlMatch?.params?.bandId ?? null;
useEffect(() => {
if (urlBandId) setActiveBandId(urlBandId);
}, [urlBandId, setActiveBandId]);
const currentBandId = urlBandId ?? activeBandId;
const activeBand = bands?.find((b) => b.id === currentBandId) ?? null;
// Close dropdown on outside click
useEffect(() => {
if (!open) return;
const handler = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [open]);
const border = "rgba(255,255,255,0.06)";
return (
<>
{showCreate && setShowCreate(false)} />}
{/* Band switcher */}
{open && (
{bands?.map((band) => (
))}
)}
>
);
}