feat(ui): R3 S1 — honest error states across the pages
The error state didn't exist as a concept: a failed main query rendered as an "empty" list
("No channels" while the request actually died), so a backend restart read as "everything is
empty". Add the convention that an empty state may NEVER show while a query is erroring.
- New components/QueryState.tsx: shared StateMessage (loading / error+Retry, house style, with
role=status/aria-live — the app had zero live regions).
- DataTable gains loading/error/onRetry props (suppresses empty-text on error).
- isError branch (before the empty branch) added across the false-empty pages: Feed (retry),
Channels, ChannelDiscovery, AuditLog, NotificationsPanel, Messages (conversations + people),
Scheduler, Stats, PlexBrowse grid, PlexPlaylistView. ChannelPage's main content is Feed, now
covered. common.loadError/empty strings (en+hu).
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
|
||||
// Centered inline status line for a page/section's loading and error(+retry) states, in the house
|
||||
// style (mirrors Playlists' error block). Text-only by design — R3's goal is to stop pages
|
||||
// rendering an error or a still-loading fetch AS an "empty" state, not to add skeletons. The
|
||||
// `role=status`/`aria-live` also gives screen readers the state change (the app had zero live
|
||||
// regions before).
|
||||
export function StateMessage({
|
||||
children,
|
||||
onRetry,
|
||||
tone = "muted",
|
||||
className = "",
|
||||
}: {
|
||||
children: ReactNode;
|
||||
onRetry?: () => void;
|
||||
tone?: "muted" | "error";
|
||||
className?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center gap-3 py-10 text-sm text-muted ${className}`}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 text-center">
|
||||
{tone === "error" && <AlertTriangle className="w-4 h-4 shrink-0 text-red-400" />}
|
||||
{children}
|
||||
</span>
|
||||
{onRetry && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRetry}
|
||||
className="px-3 py-1.5 rounded-lg border border-border text-xs text-muted hover:text-fg hover:border-accent transition"
|
||||
>
|
||||
{t("common.retry")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// The honest-state convention every page now follows: check `isError` BEFORE the empty branch so a
|
||||
// failed fetch renders this message (with Retry) instead of the page's "empty" text — the R3 bug
|
||||
// where a backend restart read as "No channels". `StateMessage` is the shared building block;
|
||||
// pages wire it into their existing loading/empty ternary (and DataTable takes loading/error props).
|
||||
Reference in New Issue
Block a user