Refactor SongRecommendations and LibraryPage components for improved mobile responsiveness and code clarity

This commit is contained in:
2025-07-25 04:52:46 +00:00
committed by GitHub
parent 940ed94579
commit fe40c0264c
2 changed files with 103 additions and 86 deletions

View File

@@ -196,7 +196,7 @@ export function SongRecommendations({ userName }: SongRecommendationsProps) {
{isMobile ? 'Here are some albums you might enjoy' : 'Here are some songs you might enjoy'}
</p>
</div>
{(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && (
{(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && !isMobile && (
<Button onClick={handleShuffleAll} variant="outline" size="sm">
<Shuffle className="w-4 h-4 mr-2" />
Shuffle All
@@ -209,47 +209,45 @@ export function SongRecommendations({ userName }: SongRecommendationsProps) {
recommendedAlbums.length > 0 ? (
<div className="grid grid-cols-3 gap-3">
{recommendedAlbums.map((album) => (
<Link
key={album.id}
href={`/album/${album.id}`}
className="group cursor-pointer"
>
<div className="space-y-2">
<div key={album.id} className="space-y-2">
<Link
href={`/album/${album.id}`}
className="group cursor-pointer block"
>
<div className="relative aspect-square rounded-lg overflow-hidden bg-muted">
{album.coverArt && api ? (
<>
<Image
src={api.getCoverArtUrl(album.coverArt, 200)}
alt={album.name}
fill
className="object-cover"
sizes="(max-width: 768px) 33vw, 200px"
onLoad={handleImageLoad}
onError={handleImageError}
loading="lazy"
/>
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<Play
className="w-8 h-8 text-white"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handlePlayAlbum(album);
}}
/>
</div>
</>
<Image
src={api.getCoverArtUrl(album.coverArt, 200)}
alt={album.name}
fill
className="object-cover"
sizes="(max-width: 768px) 33vw, 200px"
onLoad={handleImageLoad}
onError={handleImageError}
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Music className="w-8 h-8 text-muted-foreground" />
</div>
)}
</div>
<div className="space-y-1">
<p className="font-medium text-sm truncate">{album.name}</p>
</div>
</Link>
<div className="space-y-1">
<Link
href={`/album/${album.id}`}
className="font-medium text-sm truncate hover:underline block"
>
{album.name}
</Link>
<Link
href={`/artist/${album.artistId || album.artist}`}
className="text-xs text-muted-foreground truncate hover:underline block"
>
{album.artist}
</Link>
</div>
</Link>
</div>
))}
</div>
) : (

View File

@@ -6,12 +6,15 @@ import Image from 'next/image';
import { Music, Users, Disc, ListMusic, Heart, Play } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { getNavidromeAPI } from '@/lib/navidrome';
import NavidromeAPI from '@/lib/navidrome';
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
import { useIsMobile } from '@/hooks/use-mobile';
interface Album {
id: string;
name: string;
artist: string;
artistId?: string;
coverArt?: string;
year?: number;
songCount: number;
@@ -28,26 +31,29 @@ export default function LibraryPage() {
const [recentAlbums, setRecentAlbums] = useState<Album[]>([]);
const [stats, setStats] = useState<LibraryStats>({ albums: 0, artists: 0, songs: 0, playlists: 0 });
const [loading, setLoading] = useState(true);
const [api, setApi] = useState<NavidromeAPI | null>(null);
const { playAlbum } = useAudioPlayer();
const isMobile = useIsMobile();
useEffect(() => {
const loadLibraryData = async () => {
try {
const api = getNavidromeAPI();
if (!api) {
const navidromeApi = getNavidromeAPI();
if (!navidromeApi) {
console.error('Navidrome API not available');
return;
}
setApi(navidromeApi);
// Load recent albums
const albumsData = await api.getAlbums('newest', 4, 0);
const albumsData = await navidromeApi.getAlbums('newest', 4, 0);
setRecentAlbums(albumsData || []);
// Load library stats
const [allAlbums, allArtists, allPlaylists] = await Promise.all([
api.getAlbums('alphabeticalByName', 1, 0), // Just to get count
api.getArtists(),
api.getPlaylists()
navidromeApi.getAlbums('alphabeticalByName', 1, 0), // Just to get count
navidromeApi.getArtists(),
navidromeApi.getPlaylists()
]);
setStats({
@@ -118,6 +124,18 @@ export default function LibraryPage() {
<div className="space-y-4">
<h1 className="text-2xl font-bold">Your Library</h1>
{/* Loading skeleton for library links */}
<div>
<h2 className="text-lg font-semibold mb-3">Browse</h2>
<div className="space-y-3">
{[...Array(5)].map((_, i) => (
<div key={i} className="animate-pulse">
<div className="bg-muted rounded-lg h-16"></div>
</div>
))}
</div>
</div>
{/* Loading skeleton for recent albums */}
<div>
<h2 className="text-lg font-semibold mb-3">Recently Added</h2>
@@ -131,18 +149,6 @@ export default function LibraryPage() {
))}
</div>
</div>
{/* Loading skeleton for library links */}
<div>
<h2 className="text-lg font-semibold mb-3">Browse</h2>
<div className="space-y-3">
{[...Array(5)].map((_, i) => (
<div key={i} className="animate-pulse">
<div className="bg-muted rounded-lg h-16"></div>
</div>
))}
</div>
</div>
</div>
</div>
);
@@ -153,41 +159,7 @@ export default function LibraryPage() {
<div className="space-y-4">
<h1 className="text-2xl font-bold">Your Library</h1>
{/* Recently Added Albums */}
<div>
<h2 className="text-lg font-semibold mb-3">Recently Added</h2>
<div className="grid grid-cols-2 gap-4">
{recentAlbums.map((album) => (
<Card key={album.id} className="group cursor-pointer hover:bg-muted/50 transition-colors">
<CardContent className="p-3">
<div className="relative aspect-square mb-2">
<Image
src={album.coverArt || '/default-user.jpg'}
alt={album.name}
fill
className="object-cover rounded-lg"
/>
<button
onClick={() => handlePlayAlbum(album)}
className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg flex items-center justify-center"
>
<Play className="w-8 h-8 text-white fill-white" />
</button>
</div>
<Link href={`/album/${album.id}`}>
<h3 className="font-medium text-sm truncate hover:underline">{album.name}</h3>
<p className="text-xs text-muted-foreground truncate">{album.artist}</p>
{album.year && (
<p className="text-xs text-muted-foreground">{album.year}</p>
)}
</Link>
</CardContent>
</Card>
))}
</div>
</div>
{/* Library Navigation */}
{/* Library Navigation - Always at top */}
<div>
<h2 className="text-lg font-semibold mb-3">Browse</h2>
<div className="space-y-3">
@@ -218,6 +190,53 @@ export default function LibraryPage() {
})}
</div>
</div>
{/* Recently Added Albums - At bottom on mobile, after Browse on desktop */}
<div>
<h2 className="text-lg font-semibold mb-3">Recently Added</h2>
<div className="grid grid-cols-2 gap-4">
{recentAlbums.map((album) => (
<Card key={album.id} className="group cursor-pointer hover:bg-muted/50 transition-colors">
<CardContent className="p-3">
<Link href={`/album/${album.id}`}>
<div className="relative aspect-square mb-2">
<Image
src={album.coverArt && api ? api.getCoverArtUrl(album.coverArt, 200) : '/default-user.jpg'}
alt={album.name}
fill
className="object-cover rounded-lg"
sizes="(max-width: 768px) 50vw, 200px"
/>
{!isMobile && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handlePlayAlbum(album);
}}
className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg flex items-center justify-center"
>
<Play className="w-8 h-8 text-white fill-white" />
</button>
)}
</div>
<h3 className="font-medium text-sm truncate hover:underline">{album.name}</h3>
<Link
href={`/artist/${album.artistId || album.artist}`}
className="text-xs text-muted-foreground truncate hover:underline block"
onClick={(e) => e.stopPropagation()}
>
{album.artist}
</Link>
{album.year && (
<p className="text-xs text-muted-foreground">{album.year}</p>
)}
</Link>
</CardContent>
</Card>
))}
</div>
</div>
</div>
</div>
);