import { useEffect, useState } from "react"; import * as stylex from "@stylexjs/stylex"; import { ApiError } from "@/lib/api"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; const styles = stylex.create({ message: { marginTop: "0.5rem", fontSize: "0.875rem", lineHeight: "1.25rem", color: colors.danger, }, retry: { borderStyle: "none", backgroundColor: "transparent", padding: 0, color: "inherit", fontSize: "inherit", fontWeight: 500, textDecorationLine: "underline", }, }); /** * Inline mutation error per ruling 17: 400/409 messages verbatim, 429 with * countdown. Pass `onRetry` to append a retry button for a failed query. */ export default function InlineError({ error, onRetry }: { error: unknown; onRetry?: () => void }) { const retryAfter = error instanceof ApiError && error.status === 429 ? (error.retryAfter ?? null) : null; const [remaining, setRemaining] = useState(retryAfter); useEffect(() => { setRemaining(retryAfter); if (retryAfter === null) return; const timer = setInterval(() => setRemaining((s) => (s === null || s <= 1 ? 0 : s - 1)), 1000); return () => clearInterval(timer); }, [error, retryAfter]); if (error === null || error === undefined) return null; let message: string; if (error instanceof ApiError) { if (error.status === 429) { message = remaining !== null && remaining > 0 ? `Rate limited. Try again in ${remaining}s.` : "Rate limited. Try again."; } else if (error.status === 503) { message = "The server is starting or degraded. Try again shortly."; } else { message = error.message; } } else { message = "Could not reach the server."; } return (

{message} {onRetry !== undefined && ( <> {" "} )}

); }