From 891793953f7ec2edc012300bad8debda173c028d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 12 Jul 2026 23:22:37 +0200 Subject: [PATCH] =?UTF-8?q?feat(web):=20artists=20page=20=E2=80=94=20add-a?= =?UTF-8?q?rtist=20modal=20+=20filter=20watched=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MusicBrainz search+follow moves into a modal opened by an "Add artist" button; the main view gets a filter that narrows the watched roster (with a "N of N" count). Follow keeps the modal open (toast per follow) so several can be added in a row. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/app/artists/artists-client.tsx | 137 ++++++++++++++++--------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/web/src/app/artists/artists-client.tsx b/web/src/app/artists/artists-client.tsx index 5b78757..d926f7e 100644 --- a/web/src/app/artists/artists-client.tsx +++ b/web/src/app/artists/artists-client.tsx @@ -1,8 +1,10 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { PageHead } from "../_ui/page-head"; import { SectionHeader } from "../_ui/section-header"; +import { Modal } from "../_ui/modal"; +import { toast } from "../_ui/toast"; type Artist = { id: string; @@ -17,9 +19,12 @@ type Hit = { mbid: string; name: string; disambiguation: string }; export function ArtistsClient() { const [artists, setArtists] = useState([]); + const [filter, setFilter] = useState(""); + const [showAdd, setShowAdd] = useState(false); const [query, setQuery] = useState(""); const [hits, setHits] = useState([]); const [searching, setSearching] = useState(false); + const [searched, setSearched] = useState(false); async function refresh() { const res = await fetch("/api/artists"); @@ -36,20 +41,30 @@ export function ArtistsClient() { try { const res = await fetch(`/api/mb/artists?q=${encodeURIComponent(query.trim())}`); setHits(res.ok ? (await res.json()).artists : []); + setSearched(true); } finally { setSearching(false); } } + function openAdd() { + setShowAdd(true); + } + function closeAdd() { + setShowAdd(false); + setQuery(""); + setHits([]); + setSearched(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(); + toast(`Following ${hit.name}`); + refresh(); // keep the modal open so several can be added; the hit flips to "Following" } async function toggleAuto(a: Artist) { @@ -67,60 +82,41 @@ export function ArtistsClient() { } const followedMbids = new Set(artists.map((a) => a.mbid)); + const shown = useMemo(() => { + const n = filter.trim().toLowerCase(); + return n ? artists.filter((a) => a.name.toLowerCase().includes(n)) : artists; + }, [artists, filter]); return (
-
+
+ - - {searching ? searching… : null} - +
- {hits.length > 0 ? ( - <> - -
    - {hits.map((h) => ( -
  • -
    -
    - {h.name} - {h.disambiguation ? — {h.disambiguation} : null} -
    -
    -
    - {followedMbids.has(h.mbid) ? ( - Following - ) : ( - - )} -
    -
  • - ))} -
- - ) : null} - - + {artists.length === 0 ? ( -

Not following anyone yet. Search above and follow an artist to start watching their releases.

+

Not following anyone yet. Use “Add artist” to search MusicBrainz and follow someone.

+ ) : shown.length === 0 ? ( +

No watched artists match “{filter}”.

) : (
    - {artists.map((a) => ( + {shown.map((a) => (
  • @@ -128,7 +124,8 @@ export function ArtistsClient() {
    - {a.haveCount} have · {a.monitoredCount} monitored · {a.releaseCount} releases + {a.haveCount} have · {a.monitoredCount} monitored{" "} + · {a.releaseCount} releases
    @@ -150,6 +147,54 @@ export function ArtistsClient() { ))}
)} + + {showAdd ? ( + +
+ + + {searching ? searching… : null} +
+ + {hits.length > 0 ? ( +
    + {hits.map((h) => ( +
  • +
    +
    + {h.name} + {h.disambiguation ? — {h.disambiguation} : null} +
    +
    +
    + {followedMbids.has(h.mbid) ? ( + Following + ) : ( + + )} +
    +
  • + ))} +
+ ) : searched && !searching ? ( +

No matches on MusicBrainz.

+ ) : null} +
+ ) : null}
); }