fix: improve query loading and error states

This commit is contained in:
2026-04-03 02:19:12 +03:00
parent 4d44632fbf
commit d7e21956db
9 changed files with 109 additions and 3 deletions

View File

@@ -630,7 +630,7 @@ Responsibilities:
- [ ] Responsive navigation
- [ ] Toast/notification system
- [ ] Error boundary
- [ ] Query loading/error patterns
- [x] Query loading/error patterns
## Frontend Music Views

View File

@@ -0,0 +1,34 @@
export function LoadingPanel({ title = 'Загрузка...' }: { title?: string }) {
return (
<div className="grid min-h-[320px] place-items-center rounded-[16px] border border-[#24314f] bg-[#121b2e]">
<div className="text-center">
<div className="text-3xl font-semibold tracking-tight text-white">{title}</div>
<div className="mt-3 text-base text-slate-400">Подтягиваю данные и подготавливаю экран.</div>
</div>
</div>
)
}
export function ErrorPanel({
title = 'Не удалось загрузить данные',
description = 'Проверь соединение с сервером и попробуй ещё раз.',
onRetry,
}: {
title?: string
description?: string
onRetry?: () => void
}) {
return (
<div className="grid min-h-[320px] place-items-center rounded-[16px] border border-dashed border-[#31405f] bg-[#121b2e]">
<div className="max-w-xl text-center">
<div className="text-3xl font-semibold tracking-tight text-white">{title}</div>
<div className="mt-3 text-base text-slate-400">{description}</div>
{onRetry ? (
<button className="mt-6 rounded-[10px] bg-[#15c98b] px-6 py-3 text-base font-medium text-[#081225]" onClick={onRetry} type="button">
Повторить
</button>
) : null}
</div>
</div>
)
}

View File

@@ -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 <LoadingPanel title="Загружаю альбом" />
}
if (albumQuery.isError) {
return <ErrorPanel onRetry={() => void albumQuery.refetch()} title="Не получилось загрузить альбом" />
}
if (!album) {
return <div className="text-slate-400">Загрузка альбома...</div>
return <ErrorPanel title="Альбом не найден" />
}
const totalDuration = album.tracks.reduce((sum, track) => sum + track.durationSeconds, 0)

View File

@@ -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 <LoadingPanel title="Загружаю альбомы" />
}
if (albumsQuery.isError) {
return <ErrorPanel onRetry={() => void albumsQuery.refetch()} title="Не получилось загрузить альбомы" />
}
return (
<div className="space-y-6">
<div className="flex items-center justify-end gap-3">

View File

@@ -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 <LoadingPanel title="Загружаю исполнителя" />
}
if (artistQuery.isError) {
return <ErrorPanel onRetry={() => void artistQuery.refetch()} title="Не получилось загрузить исполнителя" />
}
if (!artist) {
return <div className="text-slate-400">Загрузка исполнителя...</div>
return <ErrorPanel title="Исполнитель не найден" />
}
const favoriteArtistIds = new Set((favoritesQuery.data?.artists ?? []).map((item) => item.id))

View File

@@ -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 <LoadingPanel title="Загружаю исполнителей" />
}
if (artistsQuery.isError) {
return <ErrorPanel onRetry={() => void artistsQuery.refetch()} title="Не получилось загрузить исполнителей" />
}
return (
<div className="space-y-5">
<div className="flex items-center justify-between">

View File

@@ -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 <LoadingPanel title="Загружаю избранное" />
}
if (favoritesQuery.isError) {
return <ErrorPanel onRetry={() => void favoritesQuery.refetch()} title="Не получилось загрузить избранное" />
}
if (tracks.length === 0 && albums.length === 0 && artists.length === 0) {
return (
<div className="grid min-h-[520px] place-items-center rounded-[16px] border border-dashed border-[#24314f] bg-[#121b2e]">

View File

@@ -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 <LoadingPanel title="Загружаю домашнюю страницу" />
}
if (homeQuery.isError || tracksQuery.isError) {
return (
<ErrorPanel
onRetry={() => {
void homeQuery.refetch()
void tracksQuery.refetch()
void recentTracksQuery.refetch()
}}
title="Не получилось собрать главную"
/>
)
}
return (
<div className="space-y-10">
<section className="relative overflow-hidden rounded-[14px] bg-[#111b2e]">

View File

@@ -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 <LoadingPanel title="Загружаю треки" />
}
if (tracksQuery.isError) {
return <ErrorPanel onRetry={() => void tracksQuery.refetch()} title="Не получилось загрузить треки" />
}
return (
<div className="space-y-4">
<HeaderSearch onClick={() => navigate('/search')} />