This commit is contained in:
2025-10-30 16:32:04 +11:00
parent af4a3bb50e
commit 574d0c7ffd
5 changed files with 554 additions and 25 deletions
@@ -77,15 +77,19 @@ export default function ArtistDetailPage({ onPlayMusic }: ArtistDetailProps) {
</Button>
<div className="flex items-end gap-6">
{artistInfo?.image && (
<div className="hidden md:block">
<div className="hidden md:block">
{artistInfo?.image ? (
<img
src={artistInfo.image}
src={artistInfo.image.startsWith('http') ? artistInfo.image : `/${artistInfo.image}`}
alt={decodedArtistName}
className="w-48 h-48 rounded-lg shadow-2xl object-cover"
/>
</div>
)}
) : (
<div className="w-48 h-48 rounded-lg bg-gradient-to-br from-white/20 to-white/5 backdrop-blur-sm shadow-2xl flex items-center justify-center">
<Music2 className="h-20 w-20 text-white/80" />
</div>
)}
</div>
<div className="flex-1 text-white">
<p className="text-sm font-medium mb-2 opacity-90">Artist</p>
+59 -20
View File
@@ -1,11 +1,13 @@
import { useQuery } from '@tanstack/react-query'
import { artistApi } from '@/api/client'
import { Artist } from '@/types'
import { Artist, ArtistInfo } from '@/types'
import { useNavigate } from 'react-router-dom'
import { Music2, Loader2 } from 'lucide-react'
import { useState, useEffect } from 'react'
export default function ArtistsPage() {
const navigate = useNavigate()
const [artistsWithImages, setArtistsWithImages] = useState<Map<string, string>>(new Map())
const { data: artists, isLoading } = useQuery({
queryKey: ['artists'],
@@ -15,6 +17,33 @@ export default function ArtistsPage() {
},
})
// Fetch artist images
useEffect(() => {
if (!artists) return
const fetchArtistImages = async () => {
const imageMap = new Map<string, string>()
// Fetch images for all artists in parallel
const promises = artists.map(async (artist) => {
try {
const response = await artistApi.getArtistInfo(artist.name)
const info = response.data as ArtistInfo
if (info.image) {
imageMap.set(artist.name, info.image)
}
} catch (error) {
// Silently fail for individual artists
}
})
await Promise.all(promises)
setArtistsWithImages(imageMap)
}
fetchArtistImages()
}, [artists])
if (isLoading) {
return (
<div className="max-w-screen-xl mx-auto p-4">
@@ -30,26 +59,36 @@ export default function ArtistsPage() {
<h2 className="text-2xl font-bold mb-6">Artists</h2>
{artists && artists.length > 0 ? (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{artists.map((artist) => (
<button
key={artist.name}
onClick={() => navigate(`/artists/${encodeURIComponent(artist.name)}`)}
className="p-4 rounded-lg bg-card hover:bg-accent transition-colors text-left"
>
<div className="flex items-center gap-3 mb-2">
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<Music2 className="h-6 w-6 text-primary" />
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
{artists.map((artist) => {
const artistImage = artistsWithImages.get(artist.name)
return (
<button
key={artist.name}
onClick={() => navigate(`/artists/${encodeURIComponent(artist.name)}`)}
className="group p-4 rounded-lg bg-card hover:bg-accent transition-all text-center"
>
<div className="mb-3">
{artistImage ? (
<img
src={artistImage.startsWith('http') ? artistImage : `/${artistImage}`}
alt={artist.name}
className="w-32 h-32 mx-auto rounded-full object-cover shadow-lg group-hover:shadow-xl transition-shadow"
/>
) : (
<div className="w-32 h-32 mx-auto rounded-full bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center">
<Music2 className="h-12 w-12 text-primary" />
</div>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold truncate">{artist.name}</h3>
<p className="text-sm text-muted-foreground">
{artist.song_count} {artist.song_count === 1 ? 'song' : 'songs'}
</p>
</div>
</div>
</button>
))}
<h3 className="font-semibold truncate mb-1">{artist.name}</h3>
<p className="text-sm text-muted-foreground">
{artist.song_count} {artist.song_count === 1 ? 'song' : 'songs'}
</p>
</button>
)
})}
</div>
) : (
<div className="text-center py-12">