From d7e21956db1f0de5a2bc161719d8afe8888fe2fe Mon Sep 17 00:00:00 2001 From: benya Date: Fri, 3 Apr 2026 02:19:12 +0300 Subject: [PATCH] fix: improve query loading and error states --- SUBSONIC_SERVER_BLUEPRINT.md | 2 +- apps/web/src/components/query-state.tsx | 34 +++++++++++++++++++++++ apps/web/src/pages/album-detail-page.tsx | 11 +++++++- apps/web/src/pages/albums-page.tsx | 9 ++++++ apps/web/src/pages/artist-detail-page.tsx | 11 +++++++- apps/web/src/pages/artists-page.tsx | 9 ++++++ apps/web/src/pages/favorites-page.tsx | 9 ++++++ apps/web/src/pages/home-page.tsx | 18 ++++++++++++ apps/web/src/pages/tracks-page.tsx | 9 ++++++ 9 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/components/query-state.tsx diff --git a/SUBSONIC_SERVER_BLUEPRINT.md b/SUBSONIC_SERVER_BLUEPRINT.md index 33eb8a7..676c09b 100644 --- a/SUBSONIC_SERVER_BLUEPRINT.md +++ b/SUBSONIC_SERVER_BLUEPRINT.md @@ -630,7 +630,7 @@ Responsibilities: - [ ] Responsive navigation - [ ] Toast/notification system - [ ] Error boundary -- [ ] Query loading/error patterns +- [x] Query loading/error patterns ## Frontend Music Views diff --git a/apps/web/src/components/query-state.tsx b/apps/web/src/components/query-state.tsx new file mode 100644 index 0000000..24c89f7 --- /dev/null +++ b/apps/web/src/components/query-state.tsx @@ -0,0 +1,34 @@ +export function LoadingPanel({ title = 'Загрузка...' }: { title?: string }) { + return ( +
+
+
{title}
+
Подтягиваю данные и подготавливаю экран.
+
+
+ ) +} + +export function ErrorPanel({ + title = 'Не удалось загрузить данные', + description = 'Проверь соединение с сервером и попробуй ещё раз.', + onRetry, +}: { + title?: string + description?: string + onRetry?: () => void +}) { + return ( +
+
+
{title}
+
{description}
+ {onRetry ? ( + + ) : null} +
+
+ ) +} diff --git a/apps/web/src/pages/album-detail-page.tsx b/apps/web/src/pages/album-detail-page.tsx index 2fa50ff..253e791 100644 --- a/apps/web/src/pages/album-detail-page.tsx +++ b/apps/web/src/pages/album-detail-page.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { MoreVertical, Play, Shuffle } from 'lucide-react' import { FavoriteToggle } from '@/components/favorite-toggle' import { useParams } from 'react-router-dom' @@ -20,8 +21,16 @@ export function AlbumDetailPage() { const album = albumQuery.data + if (albumQuery.isLoading) { + return + } + + if (albumQuery.isError) { + return void albumQuery.refetch()} title="Не получилось загрузить альбом" /> + } + if (!album) { - return
Загрузка альбома...
+ return } const totalDuration = album.tracks.reduce((sum, track) => sum + track.durationSeconds, 0) diff --git a/apps/web/src/pages/albums-page.tsx b/apps/web/src/pages/albums-page.tsx index 1cad891..9a263a9 100644 --- a/apps/web/src/pages/albums-page.tsx +++ b/apps/web/src/pages/albums-page.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { Search } from 'lucide-react' import { coverArtUrl, fetchAlbums } from '@/lib/api' import { Link, useNavigate } from 'react-router-dom' @@ -12,6 +13,14 @@ export function AlbumsPage() { const albums = albumsQuery.data?.items ?? [] + if (albumsQuery.isLoading) { + return + } + + if (albumsQuery.isError) { + return void albumsQuery.refetch()} title="Не получилось загрузить альбомы" /> + } + return (
diff --git a/apps/web/src/pages/artist-detail-page.tsx b/apps/web/src/pages/artist-detail-page.tsx index 2657dcd..460e089 100644 --- a/apps/web/src/pages/artist-detail-page.tsx +++ b/apps/web/src/pages/artist-detail-page.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { MoreVertical, Play, Shuffle } from 'lucide-react' import { useNavigate, useParams } from 'react-router-dom' import { FavoriteToggle } from '@/components/favorite-toggle' @@ -18,8 +19,16 @@ export function ArtistDetailPage() { const artist = artistQuery.data + if (artistQuery.isLoading) { + return + } + + if (artistQuery.isError) { + return void artistQuery.refetch()} title="Не получилось загрузить исполнителя" /> + } + if (!artist) { - return
Загрузка исполнителя...
+ return } const favoriteArtistIds = new Set((favoritesQuery.data?.artists ?? []).map((item) => item.id)) diff --git a/apps/web/src/pages/artists-page.tsx b/apps/web/src/pages/artists-page.tsx index 6566e57..43b106e 100644 --- a/apps/web/src/pages/artists-page.tsx +++ b/apps/web/src/pages/artists-page.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { Search, SlidersHorizontal } from 'lucide-react' import { coverArtUrl, fetchArtists } from '@/lib/api' import { Link, useNavigate } from 'react-router-dom' @@ -12,6 +13,14 @@ export function ArtistsPage() { const artists = artistsQuery.data?.items ?? [] + if (artistsQuery.isLoading) { + return + } + + if (artistsQuery.isError) { + return void artistsQuery.refetch()} title="Не получилось загрузить исполнителей" /> + } + return (
diff --git a/apps/web/src/pages/favorites-page.tsx b/apps/web/src/pages/favorites-page.tsx index b84c9e3..6d669fe 100644 --- a/apps/web/src/pages/favorites-page.tsx +++ b/apps/web/src/pages/favorites-page.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { Link } from 'react-router-dom' import { FavoriteToggle } from '@/components/favorite-toggle' import { coverArtUrl, fetchFavorites } from '@/lib/api' @@ -17,6 +18,14 @@ export function FavoritesPage() { const albums = favorites?.albums ?? [] const artists = favorites?.artists ?? [] + if (favoritesQuery.isLoading) { + return + } + + if (favoritesQuery.isError) { + return void favoritesQuery.refetch()} title="Не получилось загрузить избранное" /> + } + if (tracks.length === 0 && albums.length === 0 && artists.length === 0) { return (
diff --git a/apps/web/src/pages/home-page.tsx b/apps/web/src/pages/home-page.tsx index a29b96b..bc7730b 100644 --- a/apps/web/src/pages/home-page.tsx +++ b/apps/web/src/pages/home-page.tsx @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query' import { ChevronLeft, ChevronRight } from 'lucide-react' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { type Track, coverArtUrl, fetchHome, fetchRecentlyPlayed, fetchTracks } from '@/lib/api' import { Link } from 'react-router-dom' import { usePlayerStore } from '@/stores/player-store' @@ -24,6 +25,23 @@ export function HomePage() { const recentTracks = recentTracksQuery.data?.items ?? homeQuery.data?.recentTracks ?? [] const popularAlbums = [...recentAlbums].reverse() + if (homeQuery.isLoading || tracksQuery.isLoading) { + return + } + + if (homeQuery.isError || tracksQuery.isError) { + return ( + { + void homeQuery.refetch() + void tracksQuery.refetch() + void recentTracksQuery.refetch() + }} + title="Не получилось собрать главную" + /> + ) + } + return (
diff --git a/apps/web/src/pages/tracks-page.tsx b/apps/web/src/pages/tracks-page.tsx index 65b45cc..0d8ebde 100644 --- a/apps/web/src/pages/tracks-page.tsx +++ b/apps/web/src/pages/tracks-page.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' +import { ErrorPanel, LoadingPanel } from '@/components/query-state' import { Search } from 'lucide-react' import { FavoriteToggle } from '@/components/favorite-toggle' import { coverArtUrl, fetchFavorites, fetchTracks } from '@/lib/api' @@ -21,6 +22,14 @@ export function TracksPage() { const tracks = tracksQuery.data?.items ?? [] const favoriteTrackIds = new Set((favoritesQuery.data?.tracks ?? []).map((track) => track.id)) + if (tracksQuery.isLoading) { + return + } + + if (tracksQuery.isError) { + return void tracksQuery.refetch()} title="Не получилось загрузить треки" /> + } + return (
navigate('/search')} />