feat(discover): Artists|Albums tabs on /discover
Suggested artists and Suggested albums were stacked sections; make them tabs like /lastfm (.tabs bar + count badge). Discover-now/freshness and Find-similar stay above the tabs. The Albums tab filters to full-length albums (isFullAlbum) — a client-side stopgap that hides any pre-D1 singles/EP suggestions still in the DB (new sweeps already filter server-side via discover.albumsOnly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,11 +15,22 @@ type Suggestion = {
|
||||
artistName: string;
|
||||
rgMbid: string | null;
|
||||
album: string | null;
|
||||
primaryType: string | null;
|
||||
secondaryTypes: string[];
|
||||
score: number;
|
||||
seedCount: number;
|
||||
sources: string[];
|
||||
};
|
||||
|
||||
type Tab = "Artists" | "Albums";
|
||||
|
||||
/** Only full-length albums belong in Suggested albums — hide singles/EPs and
|
||||
* secondary-type releases (live/comp). New sweeps already filter server-side
|
||||
* (discover.albumsOnly); this also sweeps any pre-filter rows still in the DB. */
|
||||
function isFullAlbum(s: Suggestion): boolean {
|
||||
return (s.primaryType ?? "Album") === "Album" && (s.secondaryTypes?.length ?? 0) === 0;
|
||||
}
|
||||
|
||||
/** "just now" / "3h ago" / "2d ago" — timeAgo has no suffix, so add one (except "just now"). */
|
||||
function agoLabel(value: string): string {
|
||||
const a = timeAgo(value, Date.now());
|
||||
@@ -43,6 +54,9 @@ export function DiscoverClient() {
|
||||
const [followedSimilar, setFollowedSimilar] = useState<Set<string>>(new Set());
|
||||
const [artistModal, setArtistModal] = useState<{ mbid: string; name: string } | null>(null);
|
||||
const [albumModal, setAlbumModal] = useState<Suggestion | null>(null);
|
||||
const [tab, setTab] = useState<Tab>("Artists");
|
||||
|
||||
const albums = feed.albums.filter(isFullAlbum);
|
||||
|
||||
const loadFeed = useCallback(async () => {
|
||||
setFeed(await (await fetch("/api/discover")).json());
|
||||
@@ -172,8 +186,19 @@ export function DiscoverClient() {
|
||||
</ul>
|
||||
) : null}
|
||||
|
||||
<SectionHeader title="Suggested artists" note={`${feed.artists.length}`} />
|
||||
{feed.artists.length === 0 ? (
|
||||
<div className="tabs">
|
||||
<button className={`tab${tab === "Artists" ? " active" : ""}`} onClick={() => setTab("Artists")}>
|
||||
Suggested artists
|
||||
<span className="n">{feed.artists.length}</span>
|
||||
</button>
|
||||
<button className={`tab${tab === "Albums" ? " active" : ""}`} onClick={() => setTab("Albums")}>
|
||||
Suggested albums
|
||||
<span className="n">{albums.length}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tab === "Artists" ? (
|
||||
feed.artists.length === 0 ? (
|
||||
<p className="muted-note">No suggestions yet — run Discover, or follow a few artists to seed it.</p>
|
||||
) : (
|
||||
<ul className="list">
|
||||
@@ -204,14 +229,12 @@ export function DiscoverClient() {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<SectionHeader title="Suggested albums" note={`${feed.albums.length}`} />
|
||||
{feed.albums.length === 0 ? (
|
||||
)
|
||||
) : albums.length === 0 ? (
|
||||
<p className="muted-note">No album suggestions yet.</p>
|
||||
) : (
|
||||
<ul className="list">
|
||||
{feed.albums.map((s) => (
|
||||
{albums.map((s) => (
|
||||
<li key={s.id} className="list-row">
|
||||
<div className="main">
|
||||
<button className="disco-open" onClick={() => setAlbumModal(s)} aria-label={`Open ${s.album ?? ""}`}>
|
||||
|
||||
Reference in New Issue
Block a user