import type { ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { AlertTriangle, Loader2 } from "lucide-react"; // Centered loading state: a spinning accent ring + an optional label. The single shared loading // visual for a page/section's first load, so all of them read the same (and can be swapped for // skeletons later in one place). `role=status`/`aria-live` announces it to screen readers. export function LoadingState({ label, className = "" }: { label?: ReactNode; className?: string }) { const { t } = useTranslation(); return (
{label ?? t("common.loading")}
); } // 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 (
{tone === "error" && } {children} {onRetry && ( )}
); } // 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". The `&& ` guard matters: a // transient refetch error while data is already on screen keeps the data rather than blanking it to // an error. `StateMessage` is the shared building block pages wire into their loading/empty ternary.