"use client"; import type { ReactNode } from "react"; import { AlertTriangle, RotateCw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useReadFailure } from "@/hooks/use-read-failure"; import { cn } from "@/lib/utils"; /** * Read-failure row — the compact sibling of ``. * * Two dense metric surfaces (the HealthKit metric sub-pages and the vitals * dashboard) had each hand-rolled the identical row: a one-line message with * a Retry button on the same baseline. The tall centred card is the right * shape for a primary list surface, but on a strip of tiles it dominates the * geometry and pushes everything the user came for below the fold — so this * shape stays, promoted to a primitive instead of being copied a third time. * * Pick by surface (UI-STANDARDS §6): * - primary / list surface → `QueryErrorCard` * - dense metric strip or tile where the tall card would dominate → * `QueryErrorRow` * - chart body → `ChartErrorState` * * The row reads the failure itself (`error`): a refusal draws `refused` * (nothing by default), a spent budget says so without a retry, an ended * session offers sign-in, and only a real failure offers `onRetry`. * * The message renders in `text-foreground`: an alert IS the content of a * failed surface, and §3 reserves muted for meta. * * `inset` drops the row's own frame for a failure inside a card or tile body * that already draws one; a framed row there would be a card inside a card. */ export function QueryErrorRow({ error, refused = null, message, onRetry, retryLabel, slot, retrySlot, inset = false, className, }: { /** The query's error. It decides what the row offers. */ error: unknown; /** What a refused (403) read draws instead of the row. */ refused?: ReactNode; /** The line for a real failure. Defaults to `common.loadFailed`. */ message?: ReactNode; onRetry?: () => void; retryLabel?: string; /** * Overrides the row's `data-slot`. The two converted surfaces keep their * shipped hooks (`healthkit-metric-error`, `vitals-dashboard-error`) so * their tests and any e2e selector stay stable. */ slot?: string; /** Same, for the Retry button's own `data-slot`. */ retrySlot?: string; /** Inside a card or tile body: no frame of its own. */ inset?: boolean; className?: string; }) { const failure = useReadFailure(error, { message, onRetry }); if (!failure) return <>{refused}; const { action } = failure; return (
{action?.kind === "retry" ? ( ) : action ? ( ) : null}
); }