96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||
import { Play, UserCheck, UserPlus } from "lucide-react";
|
||
import { Link, useParams } from "react-router-dom";
|
||
import { artistsApi } from "../lib/api";
|
||
import { usePlayer } from "../store/player";
|
||
import { Cover } from "../components/Cover";
|
||
import { ShareButton } from "../components/ShareButton";
|
||
import { TrackRow } from "../components/TrackRow";
|
||
|
||
export function ArtistPage() {
|
||
const { id = "" } = useParams();
|
||
const qc = useQueryClient();
|
||
const playQueue = usePlayer((s) => s.playQueue);
|
||
|
||
const { data, isLoading } = useQuery({
|
||
queryKey: ["artist", id],
|
||
queryFn: () => artistsApi.get(id),
|
||
enabled: Boolean(id),
|
||
});
|
||
const { data: tracks = [] } = useQuery({
|
||
queryKey: ["artist-tracks", id],
|
||
queryFn: () => artistsApi.tracks(id),
|
||
enabled: Boolean(id),
|
||
});
|
||
|
||
const follow = useMutation({
|
||
mutationFn: () => (data?.following ? artistsApi.unfollow(id) : artistsApi.follow(id)),
|
||
onSuccess: () => {
|
||
void qc.invalidateQueries({ queryKey: ["artist", id] });
|
||
void qc.invalidateQueries({ queryKey: ["library"] });
|
||
},
|
||
});
|
||
|
||
if (isLoading) return <p className="text-sm text-zinc-500">Загрузка...</p>;
|
||
if (!data) return <p className="text-sm text-zinc-500">Артист не найден.</p>;
|
||
const { artist } = data;
|
||
|
||
return (
|
||
<div className="mx-auto max-w-5xl animate-fade-in">
|
||
<div className="card mb-6 flex flex-col items-center gap-5 p-6 text-center sm:flex-row sm:items-end sm:text-left">
|
||
<Cover src={artist.imageUrl} name={artist.name} rounded="rounded-full" className="h-36 w-36 shrink-0" />
|
||
<div className="min-w-0 flex-1">
|
||
<span className="chip">Артист</span>
|
||
<h1 className="mt-2 truncate text-3xl font-bold tracking-tight text-white">{artist.name}</h1>
|
||
<p className="mt-1 text-sm text-zinc-500">
|
||
{artist.followersCount} подписчиков
|
||
{artist.trackCount !== undefined && ` · ${artist.trackCount} треков`}
|
||
{artist.country && ` · ${artist.country}`}
|
||
</p>
|
||
{artist.description && (
|
||
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-zinc-400">{artist.description}</p>
|
||
)}
|
||
<div className="mt-4 flex flex-wrap justify-center gap-2 sm:justify-start">
|
||
<button className="btn-primary" onClick={() => playQueue(tracks, 0)} disabled={!tracks.length}>
|
||
<Play size={15} /> Слушать
|
||
</button>
|
||
<button className="btn-ghost" onClick={() => follow.mutate()}>
|
||
{data.following ? <UserCheck size={15} /> : <UserPlus size={15} />}
|
||
{data.following ? "Вы подписаны" : "Подписаться"}
|
||
</button>
|
||
<ShareButton path={`/artist/${id}`} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{data.albums.length > 0 && (
|
||
<section className="mb-8">
|
||
<h2 className="section-title mb-3">Альбомы</h2>
|
||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
|
||
{data.albums.map((al) => (
|
||
<Link key={al.id} to={`/album/${al.id}`} className="card p-3 hover:bg-white/[0.07]">
|
||
<Cover src={al.coverUrl} name={al.title} className="aspect-square w-full" />
|
||
<div className="mt-2 truncate text-sm text-white">{al.title}</div>
|
||
<div className="truncate text-xs text-zinc-500">{al.releaseDate?.slice(0, 4)}</div>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
<section>
|
||
<h2 className="section-title mb-3">Треки</h2>
|
||
{tracks.length ? (
|
||
<div className="card p-2">
|
||
{tracks.map((t, i) => (
|
||
<TrackRow key={t.id} track={t} list={tracks} index={i} showCover={false} showUploader />
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-zinc-500">Треков пока нет.</p>
|
||
)}
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|