import { useEffect, useState } from 'react'; import { Play, ArrowUpDown, Clock, Music2, User } from "lucide-react"; import { Link } from 'react-router-dom'; import { usePlayer } from '../context/PlayerContext'; import { libraryService } from '../services/library'; import { Track, StaticPlaylist } from '../types'; import CoverImage from '../components/CoverImage'; import Skeleton from '../components/Skeleton'; type SortOption = 'recent' | 'alpha-asc' | 'alpha-desc' | 'artist'; export default function Home() { const [timeOfDay, setTimeOfDay] = useState("Good evening"); const [browseData, setBrowseData] = useState>({}); const [loading, setLoading] = useState(true); const [sortBy, setSortBy] = useState('recent'); const [showSortMenu, setShowSortMenu] = useState(false); const { playTrack, playHistory } = usePlayer(); useEffect(() => { const hour = new Date().getHours(); if (hour < 12) setTimeOfDay("Good morning"); else if (hour < 18) setTimeOfDay("Good afternoon"); else setTimeOfDay("Good evening"); // Cache First Strategy for "Super Fast" loading const cached = localStorage.getItem('ytm_browse_cache_v4'); if (cached) { setBrowseData(JSON.parse(cached)); setLoading(false); } setLoading(true); libraryService.getBrowseContent() .then(data => { setBrowseData(data); setLoading(false); // Update Cache localStorage.setItem('ytm_browse_cache_v4', JSON.stringify(data)); }) .catch(err => { console.error("Error fetching browse:", err); setLoading(false); }); }, []); const sortPlaylists = (playlists: StaticPlaylist[]) => { const sorted = [...playlists]; switch (sortBy) { case 'alpha-asc': return sorted.sort((a, b) => (a.title || '').localeCompare(b.title || '')); case 'alpha-desc': return sorted.sort((a, b) => (b.title || '').localeCompare(a.title || '')); case 'artist': return sorted.sort((a, b) => (a.creator || '').localeCompare(b.creator || '')); case 'recent': default: return sorted; } }; const firstCategory = Object.keys(browseData)[0]; const heroPlaylist = firstCategory && browseData[firstCategory].length > 0 ? browseData[firstCategory][0] : null; const sortOptions = [ { value: 'recent', label: 'Recently Added', icon: Clock }, { value: 'alpha-asc', label: 'Alphabetical (A-Z)', icon: ArrowUpDown }, { value: 'alpha-desc', label: 'Alphabetical (Z-A)', icon: ArrowUpDown }, { value: 'artist', label: 'Artist Name', icon: User }, ]; return (
{/* Header */}

{timeOfDay}

{/* Sort Dropdown */}
{showSortMenu && (
{sortOptions.map((option) => ( ))}
)}
{/* Hero Section */} {loading ? (
) : heroPlaylist && (
Featured Playlist

{heroPlaylist.title}

{heroPlaylist.description}

Play Now
)} {/* Recently Listened */} {/* Made For You */} {/* Artist Vietnam */} {/* Top Albums Section (NEW) */} {loading ? (
{[1, 2, 3, 4, 5].map(j => (
))}
) : browseData["Top Albums"] && browseData["Top Albums"].length > 0 && (

Top Albums

{browseData["Top Albums"].slice(0, 15).map((album) => (

{album.title}

{album.description}

))}
)} {/* Browse Lists */} {loading ? (
{[1, 2].map(i => (
{[1, 2, 3, 4, 5].map(j => (
))}
))}
) : Object.keys(browseData).length > 0 ? ( Object.entries(browseData) .filter(([category]) => category !== "Top Albums") // Filter out albums since we showed them above .map(([category, playlists]) => (

{category}

Show all
{/* USER REQUEST: Bigger Grid, Smaller Text, Smaller Gap */}
{sortPlaylists(playlists).slice(0, 15).map((playlist) => (

{playlist.title}

{playlist.description}

))}
)) ) : (

Ready to explore?

Browse content is loading or empty. Try searching for music.

)}
); } // Recently Listened Section function RecentlyListenedSection({ playHistory, playTrack }: { playHistory: Track[], playTrack: (track: Track, queue?: Track[]) => void }) { if (playHistory.length === 0) return null; return (

Recently Listened

{playHistory.slice(0, 10).map((track, i) => (
playTrack(track, playHistory)} className="flex-shrink-0 w-40 bg-spotify-card rounded-xl overflow-hidden hover:bg-spotify-card-hover transition duration-300 group cursor-pointer" >

{track.title}

{track.artist}

))}
); } // Made For You Section function MadeForYouSection() { const { playHistory, playTrack } = usePlayer(); const [recommendations, setRecommendations] = useState([]); const [seedTrack, setSeedTrack] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { if (playHistory.length > 0) { const seed = playHistory[0]; setSeedTrack(seed); setLoading(true); libraryService.getRecommendations(seed.artist) .then(tracks => { setRecommendations(tracks); setLoading(false); }) .catch(() => setLoading(false)); } }, [playHistory.length > 0 ? playHistory[0]?.id : null]); if (playHistory.length === 0) return null; if (!loading && recommendations.length === 0) return null; return (

Made For You

{seedTrack ? <>Because you listened to {seedTrack.artist} : "Recommended for you"}

{loading ? (
{[1, 2, 3, 4, 5].map(i => (
))}
) : (
{recommendations.slice(0, 10).map((track, i) => (
playTrack(track, recommendations)} className="bg-transparent md:bg-spotify-card p-0 md:p-4 rounded-xl hover:bg-spotify-card-hover transition duration-300 group cursor-pointer relative h-full flex flex-col">

{track.title}

{track.artist}

))}
)}
); } // Artist Vietnam Section // Artist Vietnam Section (Optimized & Personalized) function ArtistVietnamSection() { const { playHistory } = usePlayer(); // Expanded pool of popular artists for discovery/fallback const POPULAR_ARTISTS = [ "Sơn Tùng M-TP", "HIEUTHUHAI", "Đen Vâu", "Hoàng Dũng", "Vũ.", "MONO", "Tlinh", "Erik", "Binz", "JustaTee", "Rhymastic", "Low G", "MCK", "Min", "Amee", "Karik", "Suboi", "Bích Phương", "Trúc Nhân", "Đức Phúc" ]; const [artists, setArtists] = useState([]); const [artistPhotos, setArtistPhotos] = useState>({}); const [loading, setLoading] = useState(true); useEffect(() => { // 1. Determine Artist List (Personalized + Discovery) const deriveArtists = () => { const historyArtists = new Set(); playHistory.forEach(track => { if (track.artist) historyArtists.add(track.artist); }); const recent = Array.from(historyArtists).slice(0, 5); // Take top 5 recent // Fill rest with random popular artists that aren't in recent const needed = 20 - recent.length; const available = POPULAR_ARTISTS.filter(a => !historyArtists.has(a)); const shuffled = available.sort(() => 0.5 - Math.random()).slice(0, needed); return [...recent, ...shuffled]; }; const targetArtists = deriveArtists(); setArtists(targetArtists); // 2. Load Photos (Cache First Strategy) const loadPhotos = async () => { // v3: Progressive Loading + Smaller Thumbnails const cacheKey = 'artist_photos_cache_v3'; const cached = JSON.parse(localStorage.getItem(cacheKey) || '{}'); // Initialize with cache immediately setArtistPhotos(cached); setLoading(false); // Show names immediately // Identify missing photos const missing = targetArtists.filter(name => !cached[name]); if (missing.length > 0) { // Fetch missing incrementally for (const name of missing) { try { // Fetch one by one and update state immediately // This prevents "batch waiting" feeling libraryService.getArtistInfo(name).then(data => { if (data.photo) { setArtistPhotos(prev => { const next: Record = { ...prev, [name]: data.photo || "" }; localStorage.setItem(cacheKey, JSON.stringify(next)); return next; }); } }); } catch { /* ignore */ } } } }; loadPhotos(); }, [playHistory.length]); // Re-run when history changes significantly return (

Suggested Artists

Based on your recent listening

{artists.length === 0 && loading ? ( [1, 2, 3, 4, 5, 6].map(i => (
)) ) : ( artists.map((name, i) => (

{name}

Artist

)) )}
); }