Refactor SongRecommendations and LibraryPage components for improved mobile responsiveness and code clarity
This commit is contained in:
@@ -196,7 +196,7 @@ export function SongRecommendations({ userName }: SongRecommendationsProps) {
|
|||||||
{isMobile ? 'Here are some albums you might enjoy' : 'Here are some songs you might enjoy'}
|
{isMobile ? 'Here are some albums you might enjoy' : 'Here are some songs you might enjoy'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && (
|
{(isMobile ? recommendedAlbums.length > 0 : recommendedSongs.length > 0) && !isMobile && (
|
||||||
<Button onClick={handleShuffleAll} variant="outline" size="sm">
|
<Button onClick={handleShuffleAll} variant="outline" size="sm">
|
||||||
<Shuffle className="w-4 h-4 mr-2" />
|
<Shuffle className="w-4 h-4 mr-2" />
|
||||||
Shuffle All
|
Shuffle All
|
||||||
@@ -209,15 +209,13 @@ export function SongRecommendations({ userName }: SongRecommendationsProps) {
|
|||||||
recommendedAlbums.length > 0 ? (
|
recommendedAlbums.length > 0 ? (
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
{recommendedAlbums.map((album) => (
|
{recommendedAlbums.map((album) => (
|
||||||
|
<div key={album.id} className="space-y-2">
|
||||||
<Link
|
<Link
|
||||||
key={album.id}
|
|
||||||
href={`/album/${album.id}`}
|
href={`/album/${album.id}`}
|
||||||
className="group cursor-pointer"
|
className="group cursor-pointer block"
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="relative aspect-square rounded-lg overflow-hidden bg-muted">
|
<div className="relative aspect-square rounded-lg overflow-hidden bg-muted">
|
||||||
{album.coverArt && api ? (
|
{album.coverArt && api ? (
|
||||||
<>
|
|
||||||
<Image
|
<Image
|
||||||
src={api.getCoverArtUrl(album.coverArt, 200)}
|
src={api.getCoverArtUrl(album.coverArt, 200)}
|
||||||
alt={album.name}
|
alt={album.name}
|
||||||
@@ -228,28 +226,28 @@ export function SongRecommendations({ userName }: SongRecommendationsProps) {
|
|||||||
onError={handleImageError}
|
onError={handleImageError}
|
||||||
loading="lazy"
|
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>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
<Music className="w-8 h-8 text-muted-foreground" />
|
<Music className="w-8 h-8 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="font-medium text-sm truncate">{album.name}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
</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>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import Image from 'next/image';
|
|||||||
import { Music, Users, Disc, ListMusic, Heart, Play } from 'lucide-react';
|
import { Music, Users, Disc, ListMusic, Heart, Play } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { getNavidromeAPI } from '@/lib/navidrome';
|
import { getNavidromeAPI } from '@/lib/navidrome';
|
||||||
|
import NavidromeAPI from '@/lib/navidrome';
|
||||||
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
|
||||||
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
|
||||||
interface Album {
|
interface Album {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
artist: string;
|
artist: string;
|
||||||
|
artistId?: string;
|
||||||
coverArt?: string;
|
coverArt?: string;
|
||||||
year?: number;
|
year?: number;
|
||||||
songCount: number;
|
songCount: number;
|
||||||
@@ -28,26 +31,29 @@ export default function LibraryPage() {
|
|||||||
const [recentAlbums, setRecentAlbums] = useState<Album[]>([]);
|
const [recentAlbums, setRecentAlbums] = useState<Album[]>([]);
|
||||||
const [stats, setStats] = useState<LibraryStats>({ albums: 0, artists: 0, songs: 0, playlists: 0 });
|
const [stats, setStats] = useState<LibraryStats>({ albums: 0, artists: 0, songs: 0, playlists: 0 });
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [api, setApi] = useState<NavidromeAPI | null>(null);
|
||||||
const { playAlbum } = useAudioPlayer();
|
const { playAlbum } = useAudioPlayer();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadLibraryData = async () => {
|
const loadLibraryData = async () => {
|
||||||
try {
|
try {
|
||||||
const api = getNavidromeAPI();
|
const navidromeApi = getNavidromeAPI();
|
||||||
if (!api) {
|
if (!navidromeApi) {
|
||||||
console.error('Navidrome API not available');
|
console.error('Navidrome API not available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setApi(navidromeApi);
|
||||||
|
|
||||||
// Load recent albums
|
// Load recent albums
|
||||||
const albumsData = await api.getAlbums('newest', 4, 0);
|
const albumsData = await navidromeApi.getAlbums('newest', 4, 0);
|
||||||
setRecentAlbums(albumsData || []);
|
setRecentAlbums(albumsData || []);
|
||||||
|
|
||||||
// Load library stats
|
// Load library stats
|
||||||
const [allAlbums, allArtists, allPlaylists] = await Promise.all([
|
const [allAlbums, allArtists, allPlaylists] = await Promise.all([
|
||||||
api.getAlbums('alphabeticalByName', 1, 0), // Just to get count
|
navidromeApi.getAlbums('alphabeticalByName', 1, 0), // Just to get count
|
||||||
api.getArtists(),
|
navidromeApi.getArtists(),
|
||||||
api.getPlaylists()
|
navidromeApi.getPlaylists()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
@@ -118,6 +124,18 @@ export default function LibraryPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h1 className="text-2xl font-bold">Your Library</h1>
|
<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 */}
|
{/* Loading skeleton for recent albums */}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold mb-3">Recently Added</h2>
|
<h2 className="text-lg font-semibold mb-3">Recently Added</h2>
|
||||||
@@ -131,18 +149,6 @@ export default function LibraryPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -153,41 +159,7 @@ export default function LibraryPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h1 className="text-2xl font-bold">Your Library</h1>
|
<h1 className="text-2xl font-bold">Your Library</h1>
|
||||||
|
|
||||||
{/* Recently Added Albums */}
|
{/* Library Navigation - Always at top */}
|
||||||
<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 */}
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold mb-3">Browse</h2>
|
<h2 className="text-lg font-semibold mb-3">Browse</h2>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -218,6 +190,53 @@ export default function LibraryPage() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user