feat: artists page with live-search follow picker
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Artist = {
|
||||||
|
id: string;
|
||||||
|
mbid: string;
|
||||||
|
name: string;
|
||||||
|
autoMonitorFuture: boolean;
|
||||||
|
releaseCount: number;
|
||||||
|
monitoredCount: number;
|
||||||
|
haveCount: number;
|
||||||
|
};
|
||||||
|
type Hit = { mbid: string; name: string; disambiguation: string };
|
||||||
|
|
||||||
|
export function ArtistsClient() {
|
||||||
|
const [artists, setArtists] = useState<Artist[]>([]);
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const [hits, setHits] = useState<Hit[]>([]);
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
const res = await fetch("/api/artists");
|
||||||
|
setArtists((await res.json()).artists);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function search(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!query.trim()) return;
|
||||||
|
setSearching(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/mb/artists?q=${encodeURIComponent(query.trim())}`);
|
||||||
|
setHits(res.ok ? (await res.json()).artists : []);
|
||||||
|
} finally {
|
||||||
|
setSearching(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function follow(hit: Hit) {
|
||||||
|
await fetch("/api/artists", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ mbid: hit.mbid, name: hit.name }),
|
||||||
|
});
|
||||||
|
setHits([]);
|
||||||
|
setQuery("");
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleAuto(a: Artist) {
|
||||||
|
await fetch(`/api/artists/${a.id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ autoMonitorFuture: !a.autoMonitorFuture }),
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unfollow(a: Artist) {
|
||||||
|
await fetch(`/api/artists/${a.id}`, { method: "DELETE" });
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={search}>
|
||||||
|
<input aria-label="artist search" placeholder="Search an artist…" value={query} onChange={(e) => setQuery(e.target.value)} />
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
{searching ? <p>Searching…</p> : null}
|
||||||
|
{hits.length > 0 ? (
|
||||||
|
<ul>
|
||||||
|
{hits.map((h) => (
|
||||||
|
<li key={h.mbid}>
|
||||||
|
{h.name}
|
||||||
|
{h.disambiguation ? ` (${h.disambiguation})` : ""}{" "}
|
||||||
|
<button onClick={() => follow(h)}>Follow</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{artists.map((a) => (
|
||||||
|
<li key={a.id}>
|
||||||
|
<a href={`/artists/${a.id}`}>{a.name}</a> · {a.haveCount}/{a.monitoredCount} have/monitored of {a.releaseCount}{" "}
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" aria-label={`auto-monitor ${a.name}`} checked={a.autoMonitorFuture} onChange={() => toggleAuto(a)} /> auto-monitor future
|
||||||
|
</label>{" "}
|
||||||
|
<button onClick={() => unfollow(a)}>Unfollow</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ArtistsClient } from "./artists-client";
|
||||||
|
|
||||||
|
export default function ArtistsPage() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<h1>Watched artists</h1>
|
||||||
|
<p><a href="/">Home</a> · <a href="/wanted">Wanted</a></p>
|
||||||
|
<ArtistsClient />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<h1>Lyra</h1>
|
<h1>Lyra</h1>
|
||||||
<p><a href="/settings">Settings</a></p>
|
<p><a href="/artists">Artists</a> · <a href="/wanted">Wanted</a> · <a href="/settings">Settings</a></p>
|
||||||
<Queue />
|
<Queue />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user