import { useId, type ReactNode } from "react"; import type { AsyncState } from "../../application/view-models/async-state.ts"; import type { AppFailure } from "../../contracts/errors.ts"; import { useLocale } from "../i18n/index.ts"; import { errorMessage } from "./error-copy.ts"; import { Button } from "./ui/button.ts"; export function LoadingSurface({ label }: Readonly<{ label?: string }>) { const { message } = useLocale(); const accessibleLabel = label ?? message("async.loading"); return ( {accessibleLabel} ); } export type EmptySurfaceProps = Readonly<{ title?: string; description?: string; action?: ReactNode; }>; export function EmptySurface({ title, description, action, }: EmptySurfaceProps) { const { message } = useLocale(); return ( {title ?? message("async.empty")} {description ? {description} : null} {action} ); } export type TerminalErrorSurfaceProps = Readonly<{ userMessageKey: string; action: AppFailure["action"]; onAction?: () => void; }>; export function TerminalErrorSurface({ userMessageKey, action, onAction, }: TerminalErrorSurfaceProps) { const { locale, message } = useLocale(); const messageId = useId(); const actionLabels: Readonly< Record, string> > = Object.freeze({ retry: message("action.retry"), reauth: message("action.reauth"), navigate: message("action.navigateSafe"), "reload-once": message("action.reloadOnce"), "contact-support": message("action.contactSupport"), }); return ( {errorMessage(userMessageKey, locale)} {action !== "none" && onAction && ( {actionLabels[action]} )} ); } export type AsyncSurfaceProps = Readonly<{ state: AsyncState; children?: ReactNode; onAction?: () => void; onRetry?: () => void; onResolveConflict?: () => void; onReconcileUnknownEffect?: ( resolution: "APPLIED" | "NOT_APPLIED", ) => void; }>; export function AsyncSurface({ state, children, onAction, onRetry, onResolveConflict, onReconcileUnknownEffect, }: AsyncSurfaceProps) { const { message } = useLocale(); if (state.base === "initial-loading") return ; if (state.base === "empty") return ; if (state.base === "terminal-error" && state.failure) { return ( ); } return ( {state.indicator ? ( {message( state.indicator === "stale-degraded" ? "async.staleDegraded" : state.indicator === "mutation-effect-unknown" ? "async.mutationEffectUnknown" : state.indicator === "mutation-conflict" ? "async.mutationConflict" : state.indicator === "mutation-pending" ? "async.mutationPending" : "async.refreshing", )} {state.indicator === "stale-degraded" && onRetry ? ( {message("action.retry")} ) : null} {state.indicator === "mutation-conflict" && onResolveConflict ? ( {message("action.resolveConflict")} ) : null} {state.indicator === "mutation-effect-unknown" && onReconcileUnknownEffect ? ( <> onReconcileUnknownEffect("APPLIED")} > {message("action.confirmMutationApplied")} onReconcileUnknownEffect("NOT_APPLIED")} > {message("action.confirmMutationNotApplied")} > ) : null} ) : null} {children} ); }
{description}