'use client'; import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { Song } from '@/lib/navidrome'; import { useNavidrome } from '@/app/components/NavidromeContext'; import { useAudioPlayer } from '@/app/components/AudioPlayerContext'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Play, Heart, Music, Shuffle } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; interface SongRecommendationsProps { userName?: string; } export function SongRecommendations({ userName }: SongRecommendationsProps) { const { api, isConnected } = useNavidrome(); const { playTrack, shuffle, toggleShuffle } = useAudioPlayer(); const [recommendedSongs, setRecommendedSongs] = useState([]); const [loading, setLoading] = useState(true); const [songStates, setSongStates] = useState>({}); // Memoize the greeting to prevent recalculation const greeting = useMemo(() => { const hour = new Date().getHours(); return hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening'; }, []); // Memoized callbacks to prevent re-renders const handleImageLoad = useCallback(() => { // Image loaded - no state update needed to prevent re-renders }, []); const handleImageError = useCallback(() => { // Image error - no state update needed to prevent re-renders }, []); useEffect(() => { const loadRecommendations = async () => { if (!api || !isConnected) return; setLoading(true); try { // Get random albums and extract songs from them const randomAlbums = await api.getAlbums('random', 10); // Get 10 random albums const allSongs: Song[] = []; // Get songs from first few albums for (let i = 0; i < Math.min(3, randomAlbums.length); i++) { try { const albumSongs = await api.getAlbumSongs(randomAlbums[i].id); allSongs.push(...albumSongs); } catch (error) { console.error('Failed to get album songs:', error); } } // Shuffle and limit to 6 songs const shuffled = allSongs.sort(() => Math.random() - 0.5); const recommendations = shuffled.slice(0, 6); setRecommendedSongs(recommendations); // Initialize starred states only (removed image loading states) const states: Record = {}; recommendations.forEach((song: Song) => { states[song.id] = !!song.starred; }); setSongStates(states); } catch (error) { console.error('Failed to load song recommendations:', error); } finally { setLoading(false); } }; loadRecommendations(); }, [api, isConnected]); const handlePlaySong = async (song: Song) => { if (!api) return; try { const track = { id: song.id, name: song.title, url: api.getStreamUrl(song.id), artist: song.artist || 'Unknown Artist', artistId: song.artistId || '', album: song.album || 'Unknown Album', albumId: song.albumId || '', duration: song.duration || 0, coverArt: song.coverArt ? api.getCoverArtUrl(song.coverArt, 1200) : undefined, starred: !!song.starred }; await playTrack(track, true); } catch (error) { console.error('Failed to play song:', error); } }; const handleShuffleAll = async () => { if (recommendedSongs.length === 0) return; // Enable shuffle if not already on if (!shuffle) { toggleShuffle(); } // Play a random song from recommendations const randomSong = recommendedSongs[Math.floor(Math.random() * recommendedSongs.length)]; await handlePlaySong(randomSong); }; const formatDuration = (duration: number): string => { const minutes = Math.floor(duration / 60); const seconds = duration % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; }; if (loading) { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
); } return (

{greeting}{userName ? `, ${userName}` : ''}!

Here are some songs you might enjoy

{recommendedSongs.length > 0 && ( )}
{recommendedSongs.length > 0 ? (
{recommendedSongs.map((song) => ( handlePlaySong(song)} >
{song.coverArt && api ? ( <> {song.title}
) : (
)}

{song.title}

e.stopPropagation()} > {song.artist} {song.duration && ( <> {formatDuration(song.duration)} )}
{songStates[song.id] && ( )}
))}
) : (

No songs available for recommendations

)}
); }