diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx
index 3b2094a..bf3af1b 100644
--- a/web/src/app/page.tsx
+++ b/web/src/app/page.tsx
@@ -1,3 +1,10 @@
+import { Queue } from "./queue";
+
export default function Home() {
- return Lyra;
+ return (
+
+ Lyra
+
+
+ );
}
diff --git a/web/src/app/queue.tsx b/web/src/app/queue.tsx
new file mode 100644
index 0000000..cc2b566
--- /dev/null
+++ b/web/src/app/queue.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+type Row = {
+ id: string;
+ artist: string;
+ album: string;
+ status: string;
+ job: { state: string; currentStage: string } | null;
+};
+
+export function Queue() {
+ const [rows, setRows] = useState([]);
+ const [artist, setArtist] = useState("");
+ const [album, setAlbum] = useState("");
+
+ async function refresh() {
+ const res = await fetch("/api/requests");
+ const body = await res.json();
+ setRows(body.requests);
+ }
+
+ useEffect(() => {
+ refresh();
+ const id = setInterval(refresh, 2000);
+ return () => clearInterval(id);
+ }, []);
+
+ async function submit(e: React.FormEvent) {
+ e.preventDefault();
+ if (!artist.trim() || !album.trim()) return;
+ await fetch("/api/requests", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ artist, album }),
+ });
+ setArtist("");
+ setAlbum("");
+ refresh();
+ }
+
+ return (
+
+
+
+ {rows.map((r) => (
+ -
+ {r.artist} — {r.album} · {r.job?.state ?? r.status}
+ {r.job ? ` (${r.job.currentStage})` : ""}
+
+ ))}
+
+
+ );
+}