{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"c-trace-14","type":"registry:block","title":"Webhook delivery attempts","description":"Delivery attempts for one event, with failures opening to the response and Resend appending a new attempt.","dependencies":[],"registryDependencies":["button","@reui/code-block","trace"],"files":[{"path":"c-trace-14.tsx","type":"registry:block","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { CodeBlock } from \"@/components/reui/code-block/code-block\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Trace,\n  TraceCollapsible,\n  TraceCollapsibleContent,\n  TraceCollapsibleTrigger,\n  TraceDescription,\n  TraceIcon,\n  TraceLine,\n  TraceTitle,\n} from \"@/components/ui/trace\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\ntype Attempt = {\n  number: number\n  at: string\n  ok: boolean\n  title: string\n  body: string\n  language: \"json\" | \"bash\"\n}\n\nconst PAYLOAD = `{\n  \"id\": \"evt_3Q9xLm2\",\n  \"type\": \"invoice.paid\",\n  \"invoice\": \"in_1Q9xKt\",\n  \"amount_paid\": 18400\n}`\n\nconst HISTORY: Attempt[] = [\n  {\n    number: 1,\n    at: \"14:02:11\",\n    ok: false,\n    title: \"Attempt 1 timed out after 30s\",\n    body: \"No response from hooks.northwind.dev within 30000 ms\",\n    language: \"bash\",\n  },\n  {\n    number: 2,\n    at: \"14:03:12\",\n    ok: false,\n    title: \"Attempt 2 failed with 500\",\n    body: `{ \"error\": \"database is in recovery mode\" }`,\n    language: \"json\",\n  },\n  {\n    number: 3,\n    at: \"14:07:40\",\n    ok: true,\n    title: \"Attempt 3 delivered, 200 in 214 ms\",\n    body: PAYLOAD,\n    language: \"json\",\n  },\n]\n\n// Resend appends an attempt instead of replacing the last one.\nexport function Pattern() {\n  const [attempts, setAttempts] = React.useState(HISTORY)\n  const [sending, setSending] = React.useState(false)\n  const statusRef = React.useRef<HTMLParagraphElement>(null)\n\n  React.useEffect(() => {\n    if (!sending) return\n    const timer = window.setTimeout(() => {\n      // The clock is read in the timer, never during render.\n      const at = new Date().toLocaleTimeString(\"en-GB\")\n      setAttempts((current) => [\n        ...current,\n        {\n          number: current.length + 1,\n          at,\n          ok: true,\n          title: `Attempt ${current.length + 1} delivered, 200 in 188 ms`,\n          body: PAYLOAD,\n          language: \"json\",\n        },\n      ])\n      setSending(false)\n    }, 1400)\n    return () => window.clearTimeout(timer)\n  }, [sending])\n\n  return (\n    <div className=\"flex w-full max-w-md flex-col gap-4 text-sm\">\n      <Trace>\n        {/* The newest attempt starts open. defaultOpen applies on mount, so a\n            resend opens its own row without closing the previous one. */}\n        {attempts.map((attempt, index) => (\n          <TraceCollapsible\n            key={attempt.number}\n            defaultOpen={index === attempts.length - 1}\n          >\n            <TraceCollapsibleTrigger>\n              <TraceLine status={attempt.ok ? undefined : \"error\"}>\n                <TraceIcon>\n                  <IconPlaceholder\n                    lucide=\"WebhookIcon\"\n                    tabler=\"IconWebhook\"\n                    hugeicons=\"WebhookIcon\"\n                    phosphor=\"WebhooksLogoIcon\"\n                    remixicon=\"RiWebhookLine\"\n                  />\n                </TraceIcon>\n                <TraceTitle>{attempt.title}</TraceTitle>\n                <TraceDescription>{attempt.at}</TraceDescription>\n              </TraceLine>\n            </TraceCollapsibleTrigger>\n            <TraceCollapsibleContent>\n              <CodeBlock\n                code={attempt.body}\n                language={attempt.language}\n                highlight={attempt.language === \"json\"}\n                label={attempt.ok ? \"Request payload\" : \"Response\"}\n                wrap\n              />\n            </TraceCollapsibleContent>\n          </TraceCollapsible>\n        ))}\n        {sending ? (\n          <TraceLine status=\"running\">\n            <TraceIcon>\n              <IconPlaceholder\n                lucide=\"WebhookIcon\"\n                tabler=\"IconWebhook\"\n                hugeicons=\"WebhookIcon\"\n                phosphor=\"WebhooksLogoIcon\"\n                remixicon=\"RiWebhookLine\"\n              />\n            </TraceIcon>\n            <TraceTitle>Sending attempt {attempts.length + 1}</TraceTitle>\n          </TraceLine>\n        ) : null}\n      </Trace>\n      <div className=\"flex items-center gap-3\">\n        <Button\n          size=\"sm\"\n          variant=\"outline\"\n          disabled={sending}\n          onClick={() => {\n            setSending(true)\n            // The button disables itself, so focus moves to the status line.\n            statusRef.current?.focus()\n          }}\n        >\n          Resend event\n        </Button>\n        <p\n          ref={statusRef}\n          role=\"status\"\n          tabIndex={-1}\n          className=\"text-muted-foreground text-xs outline-none\"\n        >\n          {sending ? \"Sending\" : `${attempts.length} attempts`}\n        </p>\n      </div>\n    </div>\n  )\n}","target":"components/examples/c-trace-14.tsx"}],"meta":{"order":14,"gridSize":2,"label":"Webhook Retries"}}