import { useState, useEffect, useCallback } from "react"; import apiClient from "../lib/api-client"; import { toast } from "sonner"; import { useSettings } from "../contexts/settings-context"; import { Link } from "@tanstack/react-router"; import type { ArtistType, PlaylistType } from "../types/spotify"; import { FaRegTrashAlt, FaSearch } from "react-icons/fa"; // --- Type Definitions --- interface BaseWatched { itemType: "artist" | "playlist"; spotify_id: string; } type WatchedArtist = ArtistType & { itemType: "artist" }; type WatchedPlaylist = PlaylistType & { itemType: "playlist" }; type WatchedItem = WatchedArtist | WatchedPlaylist; export const Watchlist = () => { const { settings, isLoading: settingsLoading } = useSettings(); const [items, setItems] = useState([]); const [isLoading, setIsLoading] = useState(true); const [expectedCount, setExpectedCount] = useState(null); // Utility to batch fetch details async function batchFetch( ids: string[], fetchFn: (id: string) => Promise, batchSize: number, onBatch: (results: T[]) => void ) { for (let i = 0; i < ids.length; i += batchSize) { const batchIds = ids.slice(i, i + batchSize); const batchResults = await Promise.all( batchIds.map((id) => fetchFn(id).catch(() => null)) ); onBatch(batchResults.filter(Boolean) as T[]); } } const fetchWatchlist = useCallback(async () => { setIsLoading(true); setItems([]); // Clear previous items setExpectedCount(null); try { const [artistsRes, playlistsRes] = await Promise.all([ apiClient.get("/artist/watch/list"), apiClient.get("/playlist/watch/list"), ]); // Prepare lists of IDs const artistIds = artistsRes.data.map((artist) => artist.spotify_id); const playlistIds = playlistsRes.data.map((playlist) => playlist.spotify_id); setExpectedCount(artistIds.length + playlistIds.length); // Allow UI to render grid and skeletons immediately setIsLoading(false); // Helper to update state incrementally const appendItems = (newItems: WatchedItem[]) => { setItems((prev) => [...prev, ...newItems]); }; // Fetch artist details in batches await batchFetch( artistIds, (id) => apiClient.get(`/artist/info?id=${id}`).then(res => res.data), 5, // batch size (results) => { const items: WatchedArtist[] = results.map((data) => ({ ...data, itemType: "artist", })); appendItems(items); } ); // Fetch playlist details in batches await batchFetch( playlistIds, (id) => apiClient.get(`/playlist/info?id=${id}`).then(res => res.data), 5, // batch size (results) => { const items: WatchedPlaylist[] = results.map((data) => ({ ...data, itemType: "playlist", spotify_id: data.id, })); appendItems(items); } ); } catch { toast.error("Failed to load watchlist."); } }, []); useEffect(() => { if (!settingsLoading && settings?.watch?.enabled) { fetchWatchlist(); } else if (!settingsLoading) { setIsLoading(false); } }, [settings, settingsLoading, fetchWatchlist]); const handleUnwatch = async (item: WatchedItem) => { toast.promise(apiClient.delete(`/${item.itemType}/watch/${item.id}`), { loading: `Unwatching ${item.name}...`, success: () => { setItems((prev) => prev.filter((i) => i.id !== item.id)); return `${item.name} has been unwatched.`; }, error: `Failed to unwatch ${item.name}.`, }); }; const handleCheck = async (item: WatchedItem) => { toast.promise(apiClient.post(`/${item.itemType}/watch/trigger_check/${item.id}`), { loading: `Checking ${item.name} for updates...`, success: (res: { data: { message?: string } }) => res.data.message || `Check triggered for ${item.name}.`, error: `Failed to trigger check for ${item.name}.`, }); }; const handleCheckAll = () => { toast.promise( Promise.all([apiClient.post("/artist/watch/trigger_check"), apiClient.post("/playlist/watch/trigger_check")]), { loading: "Triggering checks for all watched items...", success: "Successfully triggered checks for all items.", error: "Failed to trigger one or more checks.", }, ); }; if (isLoading || settingsLoading) { return
Loading Watchlist...
; } if (!settings?.watch?.enabled) { return (

Watchlist Disabled

The watchlist feature is currently disabled. You can enable it in the settings.

Go to Settings
); } // Show "empty" only if not loading and nothing expected if (!isLoading && items.length === 0 && (!expectedCount || expectedCount === 0)) { return (

Watchlist is Empty

Start watching artists or playlists to see them here.

); } return (

Watched Artists & Playlists

{items.map((item) => (
{item.name}

{item.name}

{item.itemType}

))} {/* Skeletons for loading items */} {isLoading && expectedCount && items.length < expectedCount && Array.from({ length: expectedCount - items.length }).map((_, idx) => (
)) }
); };