feat(web): restyle Artists, Discover, Wanted, discography + preview (Phase 2)

Convert the remaining pages onto the Pressing Plant system: PageHead
component, shared list-row / toggle / small+ghost button / tracklist classes,
and page wrappers trimmed now that the shell provides masthead + nav. All
logic and test hooks (aria-labels, button text) preserved. Verified in the
real app across both themes (Playwright): artists/discover/wanted/discography
render cohesively with real data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-07-12 14:27:43 +02:00
parent 7f1fe52a7c
commit ead5f33bcc
12 changed files with 404 additions and 204 deletions
+1 -7
View File
@@ -1,11 +1,5 @@
import { WantedClient } from "./wanted-client";
export default function WantedPage() {
return (
<main>
<h1>Wanted</h1>
<p><a href="/">Home</a> · <a href="/artists">Artists</a></p>
<WantedClient />
</main>
);
return <WantedClient />;
}
+49 -15
View File
@@ -1,6 +1,8 @@
"use client";
import { useEffect, useState } from "react";
import { PageHead } from "../_ui/page-head";
import { SectionHeader } from "../_ui/section-header";
type Wanted = {
id: string;
@@ -11,6 +13,11 @@ type Wanted = {
lastSearchedAt: string | null;
};
function stateLabel(w: Wanted): string {
if (w.currentQualityClass !== null) return `Have q${w.currentQualityClass} · upgrading`;
return w.state === "wanted" ? "Wanted" : w.state;
}
export function WantedClient() {
const [rows, setRows] = useState<Wanted[]>([]);
const [artist, setArtist] = useState("");
@@ -35,7 +42,7 @@ export function WantedClient() {
body: JSON.stringify({ artist, album }),
});
if (!res.ok) {
setError((await res.json()).error ?? "failed to add");
setError((await res.json()).error ?? "Couldnt add that — no MusicBrainz match?");
return;
}
setArtist("");
@@ -50,21 +57,48 @@ export function WantedClient() {
return (
<div>
<form onSubmit={add}>
<input aria-label="wanted artist" placeholder="Artist" value={artist} onChange={(e) => setArtist(e.target.value)} />
<input aria-label="wanted album" placeholder="Album" value={album} onChange={(e) => setAlbum(e.target.value)} />
<button type="submit">Add to wanted</button>
{error ? <span> {error}</span> : null}
<PageHead title="Wanted" eyebrow="Cutting list · acquire & upgrade" />
<form className="request-form" onSubmit={add}>
<label className="field">
<span>Artist</span>
<input aria-label="wanted artist" placeholder="Artist" value={artist} onChange={(e) => setArtist(e.target.value)} />
</label>
<label className="field">
<span>Album</span>
<input aria-label="wanted album" placeholder="Album" value={album} onChange={(e) => setAlbum(e.target.value)} />
</label>
<button type="submit" className="btn accent">
Add to wanted
</button>
{error ? <span className="notice">{error}</span> : null}
</form>
<ul>
{rows.map((w) => (
<li key={w.id}>
{w.artistName} {w.album} · <em>{w.currentQualityClass !== null ? `have q${w.currentQualityClass}, upgrading` : w.state}</em>
{w.lastSearchedAt ? ` · last tried ${new Date(w.lastSearchedAt).toLocaleString()}` : " · never tried"}{" "}
<button onClick={() => searchNow(w)}>Search now</button>
</li>
))}
</ul>
<SectionHeader title="Wanted" note={`${rows.length} on the list`} />
{rows.length === 0 ? (
<p className="empty">Nothing wanted yet. Add a release above, or send one over from an artists discography.</p>
) : (
<ul className="list">
{rows.map((w) => (
<li key={w.id} className="list-row">
<div className="main">
<div className="rtitle">
{w.album} <span className="artist">· {w.artistName}</span>
</div>
<div className="rmeta">
<span>{w.lastSearchedAt ? `last tried ${new Date(w.lastSearchedAt).toLocaleDateString()}` : "never tried"}</span>
</div>
</div>
<div className="actions">
<span className="chip">{stateLabel(w)}</span>
<button className="btn sm ghost" onClick={() => searchNow(w)}>
Search now
</button>
</div>
</li>
))}
</ul>
)}
</div>
);
}