89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { clearBasket, loadBasket, removeFromBasket, type BasketItem } from "../lib/basket";
|
|
import { useEffect, useState } from "react";
|
|
|
|
export default function BasketPage() {
|
|
const [items, setItems] = useState<BasketItem[]>([]);
|
|
const [msg, setMsg] = useState("");
|
|
const [sending, setSending] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setItems(loadBasket());
|
|
}, []);
|
|
|
|
function remove(item: BasketItem) {
|
|
removeFromBasket(item);
|
|
setItems(loadBasket());
|
|
}
|
|
|
|
async function checkout() {
|
|
if (items.length === 0) return;
|
|
setSending(true);
|
|
setMsg("");
|
|
try {
|
|
const r = await fetch("/api/downloads/batch", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
items: items.map((i) => ({ media_type: i.media_type, id: i.id })),
|
|
}),
|
|
});
|
|
if (!r.ok) throw new Error(`Fehler (${r.status})`);
|
|
const body = await r.json();
|
|
clearBasket();
|
|
setItems([]);
|
|
setMsg(
|
|
`${body.job_ids.length} angelegt — siehe Queue.` +
|
|
(body.rejected.length > 0 ? ` ${body.rejected.length} abgelehnt.` : "")
|
|
);
|
|
} catch (e: unknown) {
|
|
setMsg(e instanceof Error ? e.message : "Fehler.");
|
|
} finally {
|
|
setSending(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<h1>Sammelkorb ({items.length})</h1>
|
|
{msg && <p>{msg}</p>}
|
|
{items.length === 0 && (
|
|
<p className="muted">
|
|
Leer — lege Treffer über „+ Korb“ auf Suche, Artist-, Album- oder Playlist-Seite hier ab.
|
|
</p>
|
|
)}
|
|
{items.length > 0 && (
|
|
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
|
|
<button onClick={checkout} disabled={sending} className="btn btn-primary">
|
|
{sending ? "…" : `Alle ${items.length} laden`}
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
clearBasket();
|
|
setItems([]);
|
|
}}
|
|
className="btn"
|
|
>
|
|
Leeren
|
|
</button>
|
|
<Link href="/queue" style={{ alignSelf: "center" }}>Zur Queue →</Link>
|
|
</div>
|
|
)}
|
|
<ol style={{ listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 8 }}>
|
|
{items.map((i) => (
|
|
<li key={`${i.media_type}-${i.id}`} className="card" style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 14px" }}>
|
|
<span className="muted" style={{ fontSize: 13, width: 60, flexShrink: 0 }}>{i.media_type}</span>
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
<div style={{ fontWeight: 600 }}>{i.title}</div>
|
|
<div className="muted" style={{ fontSize: 13 }}>{i.artist}</div>
|
|
</div>
|
|
<button onClick={() => remove(i)} className="btn">Entfernen</button>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
);
|
|
}
|