162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useEffect, useState } from "react";
|
|
import { loadBasket } from "../lib/basket";
|
|
|
|
const NAV = [
|
|
{ href: "/", label: "Suche" },
|
|
{ href: "/discover", label: "Entdecken" },
|
|
{ href: "/queue", label: "Queue" },
|
|
{ href: "/history", label: "Verlauf" },
|
|
{ href: "/basket", label: "Korb" },
|
|
{ href: "/files", label: "Dateien" },
|
|
{ href: "/settings", label: "Einstellungen" },
|
|
];
|
|
|
|
export default function Header() {
|
|
const pathname = usePathname();
|
|
const [backendOk, setBackendOk] = useState<boolean | null>(null);
|
|
const [appVersion, setAppVersion] = useState("");
|
|
const [theme, setTheme] = useState<"dark" | "light">("dark");
|
|
const [basketCount, setBasketCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const update = () => {
|
|
try {
|
|
setBasketCount(loadBasket().length);
|
|
} catch {}
|
|
};
|
|
update();
|
|
window.addEventListener("basket-change", update);
|
|
return () => window.removeEventListener("basket-change", update);
|
|
}, [pathname]);
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const stored = localStorage.getItem("srip-theme");
|
|
if (stored === "light" || stored === "dark") {
|
|
setTheme(stored);
|
|
document.documentElement.dataset.theme = stored === "light" ? "light" : "";
|
|
} else if (window.matchMedia("(prefers-color-scheme: light)").matches) {
|
|
setTheme("light");
|
|
document.documentElement.dataset.theme = "light";
|
|
}
|
|
} catch {}
|
|
}, []);
|
|
|
|
function toggleTheme() {
|
|
const next = theme === "dark" ? "light" : "dark";
|
|
setTheme(next);
|
|
try {
|
|
localStorage.setItem("srip-theme", next);
|
|
} catch {}
|
|
document.documentElement.dataset.theme = next === "light" ? "light" : "";
|
|
}
|
|
|
|
useEffect(() => {
|
|
let alive = true;
|
|
fetch("/api/health")
|
|
.then((r) => {
|
|
if (alive) setBackendOk(r.ok);
|
|
})
|
|
.catch(() => {
|
|
if (alive) setBackendOk(false);
|
|
});
|
|
fetch("/api/version")
|
|
.then((r) => r.json())
|
|
.then((b) => {
|
|
if (alive && b?.version) setAppVersion(b.version);
|
|
})
|
|
.catch(() => {});
|
|
return () => {
|
|
alive = false;
|
|
};
|
|
}, [pathname]);
|
|
|
|
return (
|
|
<header
|
|
style={{
|
|
position: "sticky",
|
|
top: 0,
|
|
zIndex: 50,
|
|
background: "color-mix(in srgb, var(--bg) 92%, transparent)",
|
|
backdropFilter: "blur(8px)",
|
|
borderBottom: "1px solid var(--border)",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
maxWidth: 1200,
|
|
margin: "0 auto",
|
|
padding: "12px 20px",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 24,
|
|
}}
|
|
>
|
|
<Link href="/" style={{ textDecoration: "none", color: "var(--text)", display: "flex", alignItems: "center", gap: 10 }}>
|
|
<span
|
|
aria-hidden
|
|
style={{
|
|
width: 28,
|
|
height: 28,
|
|
borderRadius: "50%",
|
|
background: "radial-gradient(circle at center, var(--accent) 0 3px, #2a2a31 3px 5px, #0c0c0e 5px)",
|
|
border: "2px solid var(--accent)",
|
|
flexShrink: 0,
|
|
}}
|
|
/>
|
|
<span style={{ fontWeight: 700, fontSize: 18, letterSpacing: "-0.01em" }}>streamrip-ui</span>
|
|
{appVersion && (
|
|
<span className="muted" style={{ fontSize: 12 }} title="App-Version">
|
|
v{appVersion}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
<nav style={{ display: "flex", gap: 4, flex: 1 }}>
|
|
{NAV.map((item) => {
|
|
const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
|
|
const label = item.href === "/basket" && basketCount > 0 ? `Korb (${basketCount})` : item.label;
|
|
return (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
style={{
|
|
textDecoration: "none",
|
|
color: active ? "var(--text)" : "var(--muted)",
|
|
padding: "6px 12px",
|
|
borderRadius: 6,
|
|
borderBottom: active ? "2px solid var(--accent)" : "2px solid transparent",
|
|
fontWeight: active ? 600 : 400,
|
|
}}
|
|
>
|
|
{label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
<button
|
|
onClick={toggleTheme}
|
|
className="btn"
|
|
title={theme === "dark" ? "Hellmodus einschalten" : "Dunkelmodus einschalten"}
|
|
style={{ padding: "6px 10px" }}
|
|
>
|
|
{theme === "dark" ? "☀" : "☾"}
|
|
</button>
|
|
<span
|
|
title={backendOk === null ? "Backend-Status wird geprüft" : backendOk ? "Backend erreichbar" : "Backend nicht erreichbar — Server prüfen"}
|
|
style={{
|
|
width: 10,
|
|
height: 10,
|
|
borderRadius: "50%",
|
|
background: backendOk === null ? "var(--muted)" : backendOk ? "var(--ok)" : "var(--danger)",
|
|
flexShrink: 0,
|
|
}}
|
|
/>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|