feat: auth & admin
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { ScrollArea, IconButton, Button } from 'modern-sk';
|
||||
import { useGetPlaylistQuery, useGetPlaylistTracksQuery } from '../../api/endpoints/playlists';
|
||||
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';
|
||||
@@ -14,15 +17,28 @@ export function PlaylistDetailPage() {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const playlistQuery = useGetPlaylistQuery(playlistId ?? '', { skip: !playlistId });
|
||||
const tracksQuery = useGetPlaylistTracksQuery(playlistId ?? '', { skip: !playlistId });
|
||||
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>;
|
||||
return (
|
||||
<div style={{ padding: '1.5rem' }}>
|
||||
<LoadingSkeleton rows={10} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (playlistQuery.isError) {
|
||||
return <ErrorState message="Failed to load playlist" onRetry={() => playlistQuery.refetch()} />;
|
||||
return (
|
||||
<ErrorState
|
||||
message="Failed to load playlist"
|
||||
onRetry={() => playlistQuery.refetch()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const playlist = playlistQuery.data;
|
||||
@@ -30,44 +46,115 @@ export function PlaylistDetailPage() {
|
||||
|
||||
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,
|
||||
}));
|
||||
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={{
|
||||
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
|
||||
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>
|
||||
<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." />
|
||||
{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 />
|
||||
<TrackRow
|
||||
key={`${track.id}-${i}`}
|
||||
track={track}
|
||||
index={i}
|
||||
showAlbum
|
||||
/>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user