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}
); }