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,6 +1,7 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { PageHead } from "../../../_ui/page-head";
type Release = {
rgMbid: string;
@@ -105,52 +106,78 @@ export function PreviewClient({ mbid, initialName }: { mbid: string; initialName
if (error)
return (
<p>
Couldnt load this artist. <button onClick={load}>Retry</button>
<p className="empty">
Couldnt load this artist.{" "}
<button className="btn sm" onClick={load}>
Retry
</button>
</p>
);
if (!data) return <p>Loading</p>;
if (!data) return <p className="empty">Loading</p>;
return (
<div>
<h1>{data.artistName || "Artist"}</h1>
<p>
{followed ? <span>Following</span> : <button onClick={follow}>Follow</button>}
{" · "}
<PageHead title={data.artistName || "Artist"} eyebrow="Preview · discography" />
<div className="tool-row">
{followed ? (
<span className="following">Following</span>
) : (
<button className="btn accent" onClick={follow}>
Follow
</button>
)}
<a href={`https://musicbrainz.org/artist/${mbid}`} target="_blank" rel="noreferrer">
MusicBrainz
</a>
</p>
<label>
<input type="checkbox" checked={showAll} onChange={(e) => setShowAll(e.target.checked)} /> Show all release
types
</label>
<ul>
{data.releases.map((r) => (
<li key={r.rgMbid} id={`rg-${r.rgMbid}`}>
<strong>{r.album}</strong>{" "}
<small>
{r.primaryType}
{r.firstReleaseDate ? ` · ${r.firstReleaseDate.slice(0, 4)}` : ""}
</small>{" "}
{r.have ? <em>In library</em> : r.monitored ? <em>Monitored</em> : null}{" "}
<button onClick={() => want(r)} disabled={r.have || r.monitored}>
Want
</button>{" "}
<button onClick={() => toggleTracks(r.rgMbid)}>{tracks[r.rgMbid] ? "Hide tracks" : "Tracks"}</button>
{tracks[r.rgMbid] === "loading" && <p>Loading tracks</p>}
{tracks[r.rgMbid] === "error" && <p>Couldnt load tracks.</p>}
{Array.isArray(tracks[r.rgMbid]) && (
<ol>
{(tracks[r.rgMbid] as Track[]).map((t) => (
<li key={t.position}>
{t.title} {t.lengthMs != null && <small>({fmt(t.lengthMs)})</small>}
</li>
))}
</ol>
)}
</li>
))}
<label className="toggle">
<input type="checkbox" checked={showAll} onChange={(e) => setShowAll(e.target.checked)} />
Show all types
</label>
</div>
<ul className="list">
{data.releases.map((r) => {
const t = tracks[r.rgMbid];
return (
<li key={r.rgMbid} id={`rg-${r.rgMbid}`} className="list-row">
<div className="main">
<div className="rtitle">{r.album}</div>
<div className="rmeta">
{r.primaryType ? <span>{r.primaryType}</span> : null}
{r.firstReleaseDate ? (
<>
<span className="dot">·</span>
<span>{r.firstReleaseDate.slice(0, 4)}</span>
</>
) : null}
</div>
</div>
<div className="actions">
{r.have ? <span className="chip done">In library</span> : r.monitored ? <span className="chip working">Monitored</span> : null}
<button className="btn sm" onClick={() => want(r)} disabled={r.have || r.monitored}>
Want
</button>
<button className="btn sm ghost" onClick={() => toggleTracks(r.rgMbid)}>
{t ? "Hide tracks" : "Tracks"}
</button>
</div>
{t === "loading" ? <p className="muted-note" style={{ flexBasis: "100%" }}>Loading tracks</p> : null}
{t === "error" ? <p className="notice" style={{ flexBasis: "100%" }}>Couldnt load tracks.</p> : null}
{Array.isArray(t) ? (
<ol className="tracks" style={{ flexBasis: "100%" }}>
{t.map((tr) => (
<li key={tr.position}>
<span className="tnum">{tr.position}</span>
<span className="tname">{tr.title}</span>
{tr.lengthMs != null ? <span>{fmt(tr.lengthMs)}</span> : null}
</li>
))}
</ol>
) : null}
</li>
);
})}
</ul>
</div>
);