Project started 🥂

This commit is contained in:
2026-06-02 01:13:22 +03:00
commit 612d0f0125
146 changed files with 15242 additions and 0 deletions
@@ -0,0 +1,75 @@
import { useParams, useNavigate } from 'react-router';
import { ScrollArea, IconButton, Button } from 'modern-sk';
import { useGetPlaylistQuery, useGetPlaylistTracksQuery } from '../../api/endpoints/playlists';
import { TrackRow } from '../../components/track/TrackRow';
import { LoadingSkeleton } from '../../components/common/LoadingSkeleton';
import { ErrorState } from '../../components/common/ErrorState';
import { EmptyState } from '../../components/common/EmptyState';
import { useAppDispatch } from '../../hooks/useAppDispatch';
import { setQueue } from '../../store/slices/queue';
import { formatDuration } from '../../lib/format';
export function PlaylistDetailPage() {
const { playlistId } = useParams<{ playlistId: string }>();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const playlistQuery = useGetPlaylistQuery(playlistId ?? '', { skip: !playlistId });
const tracksQuery = useGetPlaylistTracksQuery(playlistId ?? '', { skip: !playlistId });
if (playlistQuery.isLoading) {
return <div style={{ padding: '1.5rem' }}><LoadingSkeleton rows={10} /></div>;
}
if (playlistQuery.isError) {
return <ErrorState message="Failed to load playlist" onRetry={() => playlistQuery.refetch()} />;
}
const playlist = playlistQuery.data;
const tracks = tracksQuery.data ?? [];
const handlePlayAll = () => {
if (!tracks.length || !playlist) return;
dispatch(setQueue({
entries: tracks.map((t) => ({
trackId: t.id,
title: t.title,
artistName: t.artistName,
albumTitle: t.albumTitle,
durationMs: t.durationMs,
albumArtUrl: t.albumArtUrl,
})),
source: 'playlist',
sourceId: playlist.id,
sourceName: playlist.name,
}));
};
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={{ padding: '1.25rem 1.5rem', borderBottom: '1px solid var(--color-border)', display: 'flex', alignItems: 'center', gap: '1rem', flexShrink: 0 }}>
<IconButton variant="ghost" size="sm" onClick={() => navigate(-1)} aria-label="Back"></IconButton>
<div style={{ flex: 1 }}>
<p style={{ margin: 0, fontSize: '0.75rem', color: 'var(--color-text-3)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Playlist</p>
<h1 style={{ margin: '0.25rem 0', fontSize: '1.5rem', fontWeight: 700 }}>{playlist?.name}</h1>
{playlist?.description && <p style={{ margin: 0, color: 'var(--color-text-2)', fontSize: '0.875rem' }}>{playlist.description}</p>}
<p style={{ margin: '0.25rem 0 0', color: 'var(--color-text-3)', fontSize: '0.8125rem' }}>
{playlist && `${playlist.trackCount} tracks · ${formatDuration(playlist.totalDurationMs)}`}
</p>
</div>
<Button variant="primary" onClick={handlePlayAll} disabled={!tracks.length}> Play</Button>
</div>
<ScrollArea style={{ flex: 1 }}>
{tracksQuery.isLoading && <LoadingSkeleton rows={10} />}
{tracksQuery.isError && <ErrorState message="Failed to load tracks" onRetry={() => tracksQuery.refetch()} />}
{!tracksQuery.isLoading && !tracksQuery.isError && tracks.length === 0 && (
<EmptyState icon="♫" title="Empty playlist" description="This playlist has no tracks yet." />
)}
{tracks.map((track, i) => (
<TrackRow key={`${track.id}-${i}`} track={track} index={i} showAlbum />
))}
</ScrollArea>
</div>
);
}