Project started 🥂
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { ScrollArea, IconButton, Button } from 'modern-sk';
|
||||
import { useGetAlbumQuery, useGetAlbumTracksQuery } from '../../api/endpoints/library';
|
||||
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';
|
||||
import { getCoverUrl } from '../../api/endpoints/streaming';
|
||||
|
||||
export function AlbumDetailPage() {
|
||||
const { albumId } = useParams<{ albumId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const albumQuery = useGetAlbumQuery(albumId ?? '', { skip: !albumId });
|
||||
const tracksQuery = useGetAlbumTracksQuery(albumId ?? '', { skip: !albumId });
|
||||
|
||||
if (albumQuery.isLoading || tracksQuery.isLoading) {
|
||||
return <div style={{ padding: '1.5rem' }}><LoadingSkeleton rows={10} /></div>;
|
||||
}
|
||||
|
||||
if (albumQuery.isError) {
|
||||
return <ErrorState message="Failed to load album" onRetry={() => albumQuery.refetch()} />;
|
||||
}
|
||||
|
||||
const album = albumQuery.data;
|
||||
const tracks = tracksQuery.data ?? [];
|
||||
const artUrl = getCoverUrl(album?.artUrl);
|
||||
|
||||
const handlePlayAll = () => {
|
||||
if (!tracks.length || !album) 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: 'album',
|
||||
sourceId: album.id,
|
||||
sourceName: album.title,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{/* header */}
|
||||
<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={{ display: 'flex', gap: '1.5rem', alignItems: 'flex-end', flex: 1 }}>
|
||||
{artUrl ? (
|
||||
<img src={artUrl} alt={album?.title} width={96} height={96} style={{ borderRadius: 8, objectFit: 'cover', flexShrink: 0 }} />
|
||||
) : (
|
||||
<div style={{ width: 96, height: 96, borderRadius: 8, background: 'var(--color-surface-3)', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '2.5rem' }}>💿</div>
|
||||
)}
|
||||
<div>
|
||||
<p style={{ margin: 0, fontSize: '0.75rem', color: 'var(--color-text-3)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Album</p>
|
||||
<h1 style={{ margin: '0.25rem 0', fontSize: '1.5rem', fontWeight: 700 }}>{album?.title}</h1>
|
||||
<p style={{ margin: 0, color: 'var(--color-text-2)', fontSize: '0.875rem' }}>
|
||||
{album?.artistName}
|
||||
{album?.year && ` · ${album.year}`}
|
||||
{album && ` · ${album.trackCount} tracks · ${formatDuration(album.totalDurationMs)}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="primary" onClick={handlePlayAll} disabled={!tracks.length}>▶ Play</Button>
|
||||
</div>
|
||||
|
||||
{/* tracks */}
|
||||
<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="No tracks" description="This album has no tracks." />
|
||||
)}
|
||||
{tracks.map((track, i) => (
|
||||
<TrackRow key={track.id} track={track} index={i} />
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user