kv-music/app/artist/[id]/page.tsx
Eduard Prigoana 2942669f53 main
2025-09-30 22:36:04 +03:00

186 lines
6.8 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { useParams, useRouter } from "next/navigation"
import { ArrowLeft, Play, ChevronDown, ChevronUp } from "lucide-react"
import { useSettingsStore } from "@/store/settings-store"
import { usePlayerStore } from "@/store/player-store"
import { Sidebar } from "@/components/app/sidebar"
import { PlayerBar } from "@/components/app/player-bar"
import { MobileNav } from "@/components/app/mobile-nav"
import { TrackList } from "@/components/app/track-list"
import { AlbumCard } from "@/components/app/album-card"
import { LoadingSpinner } from "@/components/app/loading-spinner"
import { EmptyState } from "@/components/app/empty-state"
import { Button } from "@/components/ui/button"
import { useArtist } from "@/hooks/use-artist"
import { topTracksToPlayerTracks } from "@/lib/api"
export default function ArtistPage() {
const params = useParams()
const router = useRouter()
const theme = useSettingsStore((state) => state.theme)
const { loadAndPlayQueue } = usePlayerStore((state) => state.actions)
const [isBioExpanded, setIsBioExpanded] = useState(false)
const artistId = Number.parseInt(params.id as string)
const { artist, isLoading, error } = useArtist(artistId)
useEffect(() => {
if (theme !== "custom") {
document.documentElement.setAttribute("data-theme", theme)
} else {
document.documentElement.removeAttribute("data-theme")
}
document.documentElement.classList.add("dark")
}, [theme])
const handlePlayTopTracks = () => {
if (artist && artist.top_tracks.length > 0) {
const tracks = topTracksToPlayerTracks(artist.top_tracks, artist.name.display)
loadAndPlayQueue(tracks, 0)
}
}
const handleAlbumClick = (albumId: string) => {
router.push(`/album/${albumId}`)
}
if (isLoading) {
return (
<div className="flex h-screen flex-col">
<div className="flex flex-1 overflow-hidden">
<Sidebar />
<main className="flex-1 overflow-y-auto pb-24 lg:pb-0">
<LoadingSpinner />
</main>
</div>
<PlayerBar />
<MobileNav />
</div>
)
}
if (error || !artist) {
return (
<div className="flex h-screen flex-col">
<div className="flex flex-1 overflow-hidden">
<Sidebar />
<main className="flex-1 overflow-y-auto pb-24 lg:pb-0">
<EmptyState title="Artist not found" description="The artist you're looking for doesn't exist." />
</main>
</div>
<PlayerBar />
<MobileNav />
</div>
)
}
const topTracks = topTracksToPlayerTracks(artist.top_tracks, artist.name.display)
const bioText = artist.biography?.content || ""
const bioLines = bioText.split("\n").filter((line) => line.trim())
const shouldTruncate = bioLines.length > 3
const displayBio = shouldTruncate && !isBioExpanded ? bioLines.slice(0, 3).join("\n") : bioText
return (
<div className="flex h-screen flex-col">
<div className="flex flex-1 overflow-hidden">
<Sidebar />
<main className="flex-1 overflow-y-auto pb-24 lg:pb-0">
<div className="p-6">
<Button variant="ghost" size="sm" onClick={() => router.back()} className="mb-6">
<ArrowLeft className="mr-2 h-4 w-4" />
Back
</Button>
<div className="mb-12">
<p className="text-sm font-medium uppercase tracking-wide text-muted-foreground">Artist</p>
<h1 className="mt-2 font-serif text-6xl font-bold tracking-tight">{artist.name.display}</h1>
{bioText && (
<div className="mt-6 max-w-3xl">
<div className={`relative ${shouldTruncate && !isBioExpanded ? "max-h-24 overflow-hidden" : ""}`}>
<p className="whitespace-pre-line text-pretty leading-relaxed text-muted-foreground">
{displayBio}
</p>
{shouldTruncate && !isBioExpanded && (
<div className="absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-background to-transparent" />
)}
</div>
{shouldTruncate && (
<Button variant="ghost" size="sm" onClick={() => setIsBioExpanded(!isBioExpanded)} className="mt-2">
{isBioExpanded ? (
<>
<ChevronUp className="mr-2 h-4 w-4" />
Show less
</>
) : (
<>
<ChevronDown className="mr-2 h-4 w-4" />
Read more
</>
)}
</Button>
)}
</div>
)}
</div>
{topTracks.length > 0 && (
<div className="mb-12">
<div className="mb-6 flex items-center justify-between">
<h2 className="text-2xl font-semibold">Top Tracks</h2>
<Button onClick={handlePlayTopTracks}>
<Play className="mr-2 h-4 w-4 fill-current" />
Play All
</Button>
</div>
<div className="rounded-lg bg-card">
<TrackList tracks={topTracks} showArtwork />
</div>
</div>
)}
{artist.releases.map((release) => {
if (release.items.length === 0) return null
return (
<div key={release.type} className="mb-12">
<h2 className="mb-6 text-2xl font-semibold capitalize">
{release.type}s {release.has_more && <span className="text-sm text-muted-foreground">(Top)</span>}
</h2>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
{release.items.map((item) => (
<AlbumCard
key={item.id}
album={{
id: item.id,
title: item.title,
artist: {
name: item.artist.name.display,
id: artistId,
},
image: {
small: item.image.large,
large: item.image.large,
},
release_date_original: item.dates.original,
}}
onClick={() => handleAlbumClick(item.id)}
/>
))}
</div>
</div>
)
})}
</div>
</main>
</div>
<PlayerBar />
<MobileNav />
</div>
)
}