Initial commit
This commit is contained in:
108
apps/web/src/pages/TrackPage.tsx
Normal file
108
apps/web/src/pages/TrackPage.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Heart, Play, Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { tracksApi } from "../lib/api";
|
||||
import { formatDate, formatDuration } from "../lib/format";
|
||||
import { usePlayer } from "../store/player";
|
||||
import { AddToPlaylistModal } from "../components/AddToPlaylistModal";
|
||||
import { ArtistLinks } from "../components/ArtistLinks";
|
||||
import { Comments } from "../components/Comments";
|
||||
import { Cover } from "../components/Cover";
|
||||
import { LyricsView } from "../components/LyricsView";
|
||||
import { ShareButton } from "../components/ShareButton";
|
||||
|
||||
export function TrackPage() {
|
||||
const { id = "" } = useParams();
|
||||
const playNow = usePlayer((s) => s.playNow);
|
||||
const addToQueue = usePlayer((s) => s.addToQueue);
|
||||
const qc = useQueryClient();
|
||||
const [addOpen, setAddOpen] = useState(false);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["track", id],
|
||||
queryFn: () => tracksApi.get(id),
|
||||
enabled: Boolean(id),
|
||||
});
|
||||
|
||||
const like = useMutation({
|
||||
mutationFn: () => (data?.liked ? tracksApi.unlike(id) : tracksApi.like(id)),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ["track", 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 { track } = data;
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl animate-fade-in">
|
||||
<div className="card mb-6 flex flex-col gap-5 p-5 sm:flex-row sm:items-end">
|
||||
<Cover src={track.coverUrl} name={track.title} className="h-40 w-40 shrink-0" rounded="rounded-2xl" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="chip">Трек</span>
|
||||
<h1 className="mt-2 truncate text-3xl font-bold tracking-tight text-white">{track.title}</h1>
|
||||
<p className="mt-1 text-sm text-zinc-400">
|
||||
<ArtistLinks artists={track.artists} fallback={track.artist?.name} linkClassName="link" />
|
||||
{track.album && (
|
||||
<>
|
||||
{" · "}
|
||||
<Link to={`/album/${track.album.id}`} className="link">
|
||||
{track.album.title}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-zinc-500">
|
||||
{formatDuration(track.durationMs)}
|
||||
{track.releaseDate && ` · ${formatDate(track.releaseDate)}`}
|
||||
{` · ${track.playCount} прослушиваний`}
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-zinc-500">
|
||||
Добавил в библиотеку:{" "}
|
||||
{track.uploadedBy ? (
|
||||
<Link to={`/u/${track.uploadedBy.username}`} className="link">
|
||||
{track.uploadedBy.displayName || track.uploadedBy.username}
|
||||
</Link>
|
||||
) : (
|
||||
"неизвестно"
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<button className="btn-primary" onClick={() => playNow(track, [track])}>
|
||||
<Play size={15} /> Играть
|
||||
</button>
|
||||
<button className="btn-ghost" onClick={() => addToQueue(track)}>
|
||||
<Plus size={15} /> В очередь
|
||||
</button>
|
||||
<button className={data.liked ? "btn-danger" : "btn-ghost"} onClick={() => like.mutate()}>
|
||||
<Heart size={15} fill={data.liked ? "currentColor" : "none"} />
|
||||
{data.liked ? "В избранном" : "В избранное"}
|
||||
</button>
|
||||
<button className="btn-ghost" onClick={() => setAddOpen(true)}>
|
||||
В плейлист
|
||||
</button>
|
||||
<a className="btn-ghost" href={`/api/tracks/${id}/stream?download=1`}>
|
||||
Скачать
|
||||
</a>
|
||||
<ShareButton path={`/track/${id}`} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<section className="card max-h-[60vh] overflow-hidden p-2">
|
||||
<h2 className="section-title px-3 pb-2 pt-3">Текст песни</h2>
|
||||
<LyricsView trackId={id} />
|
||||
</section>
|
||||
|
||||
<Comments trackId={id} />
|
||||
</div>
|
||||
|
||||
<AddToPlaylistModal track={track} open={addOpen} onClose={() => setAddOpen(false)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user