diff --git a/src/features/album-detail/AlbumDetailPage.tsx b/src/features/album-detail/AlbumDetailPage.tsx
index 2044886..bb2390b 100644
--- a/src/features/album-detail/AlbumDetailPage.tsx
+++ b/src/features/album-detail/AlbumDetailPage.tsx
@@ -1,6 +1,6 @@
import { useParams, useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
-import { ScrollArea, IconButton, Button } from '@olly/modern-sk';
+import { ScrollArea, IconButton, Button, Callout } from '@olly/modern-sk';
import {
useGetAlbumQuery,
useGetAlbumTracksQuery,
@@ -10,6 +10,11 @@ import { LoadingSkeleton } from '../../components/common/LoadingSkeleton';
import { ErrorState } from '../../components/common/ErrorState';
import { EmptyState } from '../../components/common/EmptyState';
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
+import { useIsOffline } from '../../hooks/useConnectionStatus';
+import {
+ selectLocalAlbums,
+ selectLocalTracks,
+} from '../../store/selectors/localLibrary';
import { setQueue } from '../../store/slices/queue';
import { formatDuration } from '../../lib/format';
import { getCoverUrl, getTrackCoverUrl } from '../../api/endpoints/streaming';
@@ -24,15 +29,36 @@ export function AlbumDetailPage() {
const albumQuery = useGetAlbumQuery(albumId ?? '', { skip: !albumId });
const tracksQuery = useGetAlbumTracksQuery(albumId ?? '', { skip: !albumId });
- if (albumQuery.isLoading || tracksQuery.isLoading) {
- return (
-
-
-
- );
- }
+ // Offline fallback: resolve the album + its tracks from the locally-cached
+ // library when the backend is unreachable (same approach as LibraryPage).
+ const offline = useIsOffline();
+ const localAlbums = useAppSelector(selectLocalAlbums);
+ const localTracks = useAppSelector(selectLocalTracks);
- if (albumQuery.isError) {
+ const album =
+ albumQuery.data ??
+ (offline ? localAlbums.find((a) => a.id === albumId) : undefined);
+ const tracks =
+ tracksQuery.data ??
+ (offline ? localTracks.filter((tr) => tr.albumId === albumId) : []);
+
+ if (!album) {
+ if (albumQuery.isLoading && !offline) {
+ return (
+
+
+
+ );
+ }
+ if (offline) {
+ return (
+
+ );
+ }
return (
);
}
-
- const album = albumQuery.data;
- const tracks = tracksQuery.data ?? [];
// The album record itself carries no cover; fall back to a track's cover.
const coverTrack = tracks.find((t) => t.hasCover);
const artUrl =
@@ -72,6 +95,11 @@ export function AlbumDetailPage() {
return (
+ {offline && (
+
+ {t('common.offlineBanner')}
+
+ )}
- {tracksQuery.isLoading && }
- {tracksQuery.isError && (
+ {tracks.length === 0 && !offline && tracksQuery.isLoading && (
+
+ )}
+ {tracks.length === 0 && !offline && tracksQuery.isError && (
tracksQuery.refetch()}
/>
)}
- {!tracksQuery.isLoading &&
- !tracksQuery.isError &&
- tracks.length === 0 && (
+ {tracks.length === 0 &&
+ (offline || (!tracksQuery.isLoading && !tracksQuery.isError)) && (
-
-
- );
- }
+ // Offline fallback: resolve the artist + their albums/tracks from the
+ // locally-cached library when the backend is unreachable.
+ const offline = useIsOffline();
+ const localArtists = useAppSelector(selectLocalArtists);
+ const localAlbums = useAppSelector(selectLocalAlbums);
+ const localTracks = useAppSelector(selectLocalTracks);
- if (artistQuery.isError || !artistQuery.data) {
+ const artist =
+ artistQuery.data ??
+ (offline ? localArtists.find((a) => a.id === artistId) : undefined);
+ const albums =
+ albumsQuery.data ??
+ (offline ? localAlbums.filter((a) => a.artistId === artistId) : []);
+ const tracks =
+ tracksQuery.data ??
+ (offline ? localTracks.filter((tr) => tr.artistId === artistId) : []);
+
+ if (!artist) {
+ if (artistQuery.isLoading && !offline) {
+ return (
+
+
+
+ );
+ }
+ if (offline) {
+ return (
+
+ );
+ }
return (
{
if (!tracks.length) return;
dispatch(
@@ -73,6 +100,11 @@ export function ArtistDetailPage() {
return (
+ {offline && (
+
+ {t('common.offlineBanner')}
+
+ )}
{t('artist.albums')}
- {albumsQuery.isLoading &&
}
- {albumsQuery.isError && (
+ {albums.length === 0 && !offline && albumsQuery.isLoading && (
+
+ )}
+ {albums.length === 0 && !offline && albumsQuery.isError && (
albumsQuery.refetch()} />
)}
- {!albumsQuery.isLoading &&
- !albumsQuery.isError &&
- albums.length === 0 && (
+ {albums.length === 0 &&
+ (offline || (!albumsQuery.isLoading && !albumsQuery.isError)) && (
{t('artist.noAlbums')}
@@ -193,13 +226,14 @@ export function ArtistDetailPage() {
>
{t('artist.tracks')}
- {tracksQuery.isLoading && }
- {tracksQuery.isError && (
+ {tracks.length === 0 && !offline && tracksQuery.isLoading && (
+
+ )}
+ {tracks.length === 0 && !offline && tracksQuery.isError && (
tracksQuery.refetch()} />
)}
- {!tracksQuery.isLoading &&
- !tracksQuery.isError &&
- tracks.length === 0 && (
+ {tracks.length === 0 &&
+ (offline || (!tracksQuery.isLoading && !tracksQuery.isError)) && (