fix(ui): replace raw AxiosError dumps with QueryErrorState across routes

This commit is contained in:
Александр Зимин
2026-05-14 20:42:45 +00:00
parent 1a5049aac4
commit 9e6ddd1bce
5 changed files with 34 additions and 39 deletions
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Alert, Badge, Button, LoadingBlock, Modal, TextInput } from '@/ui' import { Alert, Badge, Button, LoadingBlock, Modal, QueryErrorState, TextInput } from '@/ui'
import { WarningIcon, XCircleIcon } from '@phosphor-icons/react' import { WarningIcon, XCircleIcon } from '@phosphor-icons/react'
import { useCascadePreview } from '@/api/queries' import { useCascadePreview } from '@/api/queries'
import { useCascadeCloseRecord } from '@/api/mutations' import { useCascadeCloseRecord } from '@/api/mutations'
@@ -116,9 +116,7 @@ export const CascadeConfirmDialog = ({
{preview.isLoading && <LoadingBlock size="md" label={t('loading')} />} {preview.isLoading && <LoadingBlock size="md" label={t('loading')} />}
{preview.error && ( {preview.error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState error={preview.error} onRetry={() => preview.refetch()} />
{String(preview.error)}
</Alert>
)} )}
{plan && ( {plan && (
@@ -261,9 +259,9 @@ export const CascadeConfirmDialog = ({
)} )}
{cascadeMut.error && ( {cascadeMut.error && (
<Alert variant="error" title={t('error.failed')}> /* Mutation error — no retry button (user triggers via Confirm
{String(cascadeMut.error)} again). QueryErrorState classifies 4xx/5xx code/message. */
</Alert> <QueryErrorState error={cascadeMut.error} />
)} )}
</> </>
)} )}
@@ -967,9 +967,10 @@ function DictionaryDetail() {
{recordsResult.isLoading && <LoadingBlock size="md" label={t('loading')} />} {recordsResult.isLoading && <LoadingBlock size="md" label={t('loading')} />}
{recordsResult.error && ( {recordsResult.error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState
{String(recordsResult.error)} error={recordsResult.error}
</Alert> onRetry={() => recordsResult.refetch()}
/>
)} )}
{recordsResult.data && recordsResult.data.length === 0 && ( {recordsResult.data && recordsResult.data.length === 0 && (
@@ -1279,9 +1280,10 @@ function DictionaryDetail() {
)} )}
{detailQuery.data && edit.kind === 'edit' && rawRecordQuery.error && ( {detailQuery.data && edit.kind === 'edit' && rawRecordQuery.error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState
{String(rawRecordQuery.error)} error={rawRecordQuery.error}
</Alert> onRetry={() => rawRecordQuery.refetch()}
/>
)} )}
{detailQuery.data && edit.kind === 'edit' && rawRecordQuery.data && ( {detailQuery.data && edit.kind === 'edit' && rawRecordQuery.data && (
@@ -2086,7 +2088,8 @@ function highlightJson(json: string): string {
*/ */
function EventsTabContent({ dictName }: { dictName: string }) { function EventsTabContent({ dictName }: { dictName: string }) {
const { t } = useTranslation() const { t } = useTranslation()
const { data, isLoading, error } = useAudit({ dictionaryName: dictName, size: 50 }) const auditQ = useAudit({ dictionaryName: dictName, size: 50 })
const { data, isLoading, error } = auditQ
// Filter chips per prototype: все / edit / publish / review / webhook / export. // Filter chips per prototype: все / edit / publish / review / webhook / export.
// Backend audit actions: CREATE / UPDATE / CLOSE / EXPORT etc. — map to display // Backend audit actions: CREATE / UPDATE / CLOSE / EXPORT etc. — map to display
// groups for chip filter. // groups for chip filter.
@@ -2094,11 +2097,7 @@ function EventsTabContent({ dictName }: { dictName: string }) {
if (isLoading) return <LoadingBlock size="md" label={t('loading')} /> if (isLoading) return <LoadingBlock size="md" label={t('loading')} />
if (error) if (error)
return ( return <QueryErrorState error={error} onRetry={() => auditQ.refetch()} />
<Alert variant="error" title={t('error.failed')}>
{String(error)}
</Alert>
)
const rows = data?.content ?? [] const rows = data?.content ?? []
if (rows.length === 0) if (rows.length === 0)
return ( return (
@@ -2298,7 +2297,8 @@ function EventsTabContent({ dictName }: { dictName: string }) {
function HistoryTabContent({ detail }: { detail?: import('@/api/client').DictionaryDetail }) { function HistoryTabContent({ detail }: { detail?: import('@/api/client').DictionaryDetail }) {
const { t } = useTranslation() const { t } = useTranslation()
const [diffEntryId, setDiffEntryId] = useState<number | undefined>(undefined) const [diffEntryId, setDiffEntryId] = useState<number | undefined>(undefined)
const { data, isLoading, error } = useChangelog(detail?.name) const changelogQ = useChangelog(detail?.name)
const { data, isLoading, error } = changelogQ
if (!detail) return null if (!detail) return null
@@ -2306,9 +2306,7 @@ function HistoryTabContent({ detail }: { detail?: import('@/api/client').Diction
<div className="space-y-3"> <div className="space-y-3">
{isLoading && <LoadingBlock size="md" label={t('loading')} />} {isLoading && <LoadingBlock size="md" label={t('loading')} />}
{error && ( {error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState error={error} onRetry={() => changelogQ.refetch()} />
{String(error)}
</Alert>
)} )}
{data && data.entries.length === 0 && ( {data && data.entries.length === 0 && (
<EmptyState <EmptyState
+10 -7
View File
@@ -2,13 +2,13 @@ import { useState } from 'react'
import { Link, createFileRoute } from '@tanstack/react-router' import { Link, createFileRoute } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
Alert,
Badge, Badge,
Button, Button,
EmptyState, EmptyState,
LoadingBlock, LoadingBlock,
PageHeader, PageHeader,
Panel, Panel,
QueryErrorState,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@@ -92,15 +92,18 @@ function MyDraftsPage() {
<LoadingBlock size="md" label={t('loading')} /> <LoadingBlock size="md" label={t('loading')} />
)} )}
{/* QueryErrorState classifies status code (502 → "Сервис временно
недоступен", timeout → "Запрос превысил время ожидания", etc.) и
показывает retry button. Раньше тут был <Alert>{String(error)}</Alert>
который дампил raw AxiosError юзеру — анти-паттерн UX. */}
{drafts.error && ( {drafts.error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState error={drafts.error} onRetry={() => drafts.refetch()} />
{String(drafts.error)}
</Alert>
)} )}
{schemaDrafts.error && ( {schemaDrafts.error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState
{String(schemaDrafts.error)} error={schemaDrafts.error}
</Alert> onRetry={() => schemaDrafts.refetch()}
/>
)} )}
{bothEmpty && <EmptyState title={t('myDrafts.empty')} />} {bothEmpty && <EmptyState title={t('myDrafts.empty')} />}
+1 -3
View File
@@ -715,9 +715,7 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
<div className="space-y-4"> <div className="space-y-4">
{draftQ.isLoading && <LoadingBlock size="md" label={t('loading')} />} {draftQ.isLoading && <LoadingBlock size="md" label={t('loading')} />}
{draftQ.error && ( {draftQ.error && (
<Alert variant="error" title={t('error.failed')}> <QueryErrorState error={draftQ.error} onRetry={() => draftQ.refetch()} />
{String(draftQ.error)}
</Alert>
)} )}
{draft && ( {draft && (
@@ -12,6 +12,7 @@ import {
MultiSelect, MultiSelect,
PageHeader, PageHeader,
Panel, Panel,
QueryErrorState,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@@ -79,7 +80,8 @@ const EVENT_TYPE_OPTIONS: SelectOption[] = [
function WebhooksPage() { function WebhooksPage() {
const { t } = useTranslation() const { t } = useTranslation()
const { data, isLoading, error } = useWebhookSubscriptions() const subsQ = useWebhookSubscriptions()
const { data, isLoading, error } = subsQ
const [createOpen, setCreateOpen] = useState(false) const [createOpen, setCreateOpen] = useState(false)
const [revealedSecret, setRevealedSecret] = useState<{ const [revealedSecret, setRevealedSecret] = useState<{
name: string name: string
@@ -94,11 +96,7 @@ function WebhooksPage() {
if (isLoading) return <LoadingBlock size="md" label={t('loading')} /> if (isLoading) return <LoadingBlock size="md" label={t('loading')} />
if (error) { if (error) {
return ( return <QueryErrorState error={error} onRetry={() => subsQ.refetch()} />
<Alert variant="error" title={t('error.failed')}>
{String(error)}
</Alert>
)
} }
return ( return (