"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Search, Loader2, Music2, Link2, X, ChevronRight, Info } from "lucide-react"; import { api } from "@/lib/api"; import { useToast } from "@/lib/toast-context"; // Types interface PlaylistPreview { id: string; source: "deezer" | "spotify"; type: "playlist" | "radio"; title: string; description: string | null; creator: string; imageUrl: string | null; trackCount: number; url: string; } interface Genre { id: number; name: string; imageUrl: string | null; } // Deezer icon component const DeezerIcon = ({ className }: { className?: string }) => ( ); // Tab type (radios removed - now personal library content at /radio) type BrowseTab = "playlists" | "genres"; // Loading skeleton for cards const CardSkeleton = () => (
); export default function BrowsePlaylistsPage() { const router = useRouter(); const { toast } = useToast(); // UI State const [activeTab, setActiveTab] = useState("playlists"); const [searchQuery, setSearchQuery] = useState(""); const [isLoading, setIsLoading] = useState(true); const [isSearching, setIsSearching] = useState(false); const [hasSearched, setHasSearched] = useState(false); const [showUrlModal, setShowUrlModal] = useState(false); const [urlInput, setUrlInput] = useState(""); const [isParsing, setIsParsing] = useState(false); const [loadError, setLoadError] = useState(null); // Data State const [playlists, setPlaylists] = useState([]); const [genres, setGenres] = useState([]); const [selectedGenre, setSelectedGenre] = useState(null); const [genrePlaylists, setGenrePlaylists] = useState([]); // Fetch all browse content on mount const fetchAllContent = useCallback(async () => { setIsLoading(true); setLoadError(null); try { const response = await api.get<{ playlists: PlaylistPreview[]; genres: Genre[]; }>("/browse/all"); setPlaylists(response.playlists); setGenres(response.genres); } catch (error) { console.error("Failed to fetch browse content:", error); setLoadError( "Couldn't load playlists. Check your connection and try again." ); } finally { setIsLoading(false); } }, []); useEffect(() => { fetchAllContent(); }, [fetchAllContent]); // Search playlists const handleSearch = async (e?: React.FormEvent) => { e?.preventDefault(); if (!searchQuery.trim() || searchQuery.length < 2) { if (!searchQuery.trim()) { setHasSearched(false); } return; } setIsSearching(true); setHasSearched(true); setActiveTab("playlists"); // Switch to playlists for search results try { const response = await api.get<{ playlists: PlaylistPreview[]; }>( `/browse/playlists/search?q=${encodeURIComponent( searchQuery )}&limit=100` ); setPlaylists(response.playlists); } catch (error) { console.error("Search failed:", error); toast.error("Failed to search playlists"); } finally { setIsSearching(false); } }; // Clear search const clearSearch = () => { setSearchQuery(""); setHasSearched(false); fetchAllContent(); }; // Parse URL and redirect to import const handleUrlSubmit = async () => { if (!urlInput.trim()) return; setIsParsing(true); try { const response = await api.post<{ source: string; id: string; url: string; }>("/browse/playlists/parse", { url: urlInput.trim() }); setShowUrlModal(false); setUrlInput(""); router.push( `/import/spotify?url=${encodeURIComponent(response.url)}` ); } catch (error: unknown) { const message = error instanceof Error ? error.message : "Invalid playlist URL"; toast.error(message); } finally { setIsParsing(false); } }; // Handle playlist click - navigate to detail page const handleItemClick = (item: PlaylistPreview) => { router.push(`/browse/playlists/${item.id}`); }; // Handle genre click const handleGenreClick = async (genre: Genre) => { setSelectedGenre(genre); setIsLoading(true); try { const response = await api.get<{ playlists: PlaylistPreview[]; }>(`/browse/genres/${genre.id}/playlists?limit=50`); setGenrePlaylists(response.playlists); } catch (error) { console.error("Failed to fetch genre playlists:", error); toast.error("Failed to load genre playlists"); } finally { setIsLoading(false); } }; // Back from genre view const handleBackFromGenre = () => { setSelectedGenre(null); setGenrePlaylists([]); }; // Render playlist card const renderCard = ( item: PlaylistPreview, index: number, context?: string ) => (
handleItemClick(item)} className="group cursor-pointer" >
{item.imageUrl ? ( {item.title} ) : (
)} {/* Import button on hover */}

{item.title}

{item.trackCount} songs • {item.creator}

); // Render genre card const renderGenreCard = (genre: Genre) => (
handleGenreClick(genre)} className="group cursor-pointer relative aspect-square rounded-lg overflow-hidden" > {genre.imageUrl ? ( {genre.name} ) : (
)}

{genre.name}

); return (
{/* Gradient - Same as home page (yellow → purple) */}
{/* Header */}

Browse

Beta

Discover and import playlists from Deezer

{/* Beta Notice */}

Beta feature:{" "} Importing from Spotify and Deezer relies on matching tracks through Soulseek and your configured indexers. Results may vary depending on track availability and metadata quality.

{/* Search Bar & Import URL */}
setSearchQuery(e.target.value)} placeholder="Search playlists..." className="w-full bg-white rounded-full pl-11 pr-10 py-3 text-sm text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-white" /> {searchQuery && ( )}
{/* Tabs */} {!selectedGenre && !hasSearched && (
)} {/* Genre Breadcrumb */} {selectedGenre && (
{selectedGenre.name}
)} {/* Loading State with Skeleton Cards */} {(isLoading || isSearching) && !loadError && (
{Array.from({ length: 14 }).map((_, i) => ( ))}
)} {/* Error State */} {loadError && !isLoading && (

Couldn't load content

{loadError}

)} {/* Search Results */} {!isLoading && !isSearching && hasSearched && ( <>

Results for “{searchQuery}”

{playlists.length === 0 ? (

No playlists found

Try a different search or import a URL directly

) : (
{playlists.map((item, idx) => renderCard(item, idx, "search") )}
)} )} {/* Genre Playlists View */} {!isLoading && selectedGenre && ( <>

{selectedGenre.name} Playlists

{genrePlaylists.length === 0 ? (

No playlists found for this genre

) : (
{genrePlaylists.map((item, idx) => renderCard(item, idx, "genre") )}
)} )} {/* Main Content (no search, no genre selected) */} {!isLoading && !isSearching && !hasSearched && !selectedGenre && ( <> {/* Playlists Tab */} {activeTab === "playlists" && ( <>

Featured Playlists

{playlists.map((item, idx) => renderCard(item, idx, "featured") )}
{playlists.length >= 20 && (

Showing {playlists.length} playlists • Search for more or import by URL

)} )} {/* Genres Tab */} {activeTab === "genres" && ( <>

Browse by Genre

Explore playlists organized by musical genre

{genres.map(renderGenreCard)}
)} )}
{/* URL Import Modal - Modern Spotify-style */} {showUrlModal && (
setShowUrlModal(false)} >
e.stopPropagation()} > {/* Header with gradient accent */}

Import Playlist

Paste a link to get started

{/* Supported platforms */}
Spotify
Deezer
Supported
{/* Input area */}
setUrlInput(e.target.value) } placeholder="Paste playlist URL here..." className="w-full bg-black/40 border border-white/[0.06] rounded-xl px-4 py-4 text-white placeholder:text-white/30 focus:outline-none focus:border-[#ecb200]/40 focus:ring-1 focus:ring-[#ecb200]/20 transition-all text-sm" onKeyDown={(e) => e.key === "Enter" && handleUrlSubmit() } autoFocus /> {urlInput && ( )}

Example: https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M

{/* Actions */}
)}
); }