"use client"; import { useEffect, useRef, useState } from "react"; import { X } from "lucide-react"; import { useOutboxStore } from "@/stores/offlineQueue"; /** A short, human description of a failed mutation (the dialog row label). */ function describeItem(item: { method: string; path: string }): string { const segs = item.path.split("?")[0]?.split("/").filter(Boolean) ?? []; const root = segs[0] ?? ""; const id = segs[1] ?? ""; const noun = root === "comments" ? "a comment" : root === "blocks" ? "a block" : root === "records" ? "a record" : root === "pages" ? "a page" : `a ${root}`; return `${item.method} ${id ? `to ${noun} ${id.slice(0, 8)}…` : `to create ${noun}`}`; } /** * The "N changes couldn't be synced — Review" dialog (TASK-0095 R1/R2): lists * the outbox's FAILED items with their error + a per-item Retry (a FRESH key — * a retry is a new attempt) / Discard (a confirm — the user's explicit choice, * the item is deleted). A Discard deletes the mutation (never silently). */ export function ReviewFailedDialog({ onClose }: { onClose: () => void }) { const failedItems = useOutboxStore((s) => s.failedItems); const retry = useOutboxStore((s) => s.retry); const discard = useOutboxStore((s) => s.discard); const [confirmKey, setConfirmKey] = useState(null); const closeRef = useRef(null); const items = failedItems(); // Escape closes the dialog; focus the close button on open (a11y). useEffect(() => { closeRef.current?.focus(); const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [onClose]); return (
{ if (e.target === e.currentTarget) onClose(); }} >

{items.length} change{items.length === 1 ? "" : "s"} couldn't be synced — Review

    {items.length === 0 ? (
  • No failed changes.
  • ) : ( items.map((item) => (
  • {describeItem(item)}
    {item.lastError ? (
    {item.lastError}
    ) : null}
    {confirmKey === item.key ? ( ) : ( )}
  • )) )}
); }