{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "swaps-fx",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Locked rate with a countdown and expiry behavior.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { formatRate } from \"@/lib/ledger/format-money\";\nimport { Button } from \"@/components/ledger/button\";\nimport { RateLine } from \"@/components/ledger/rate-line\";\n\nexport interface RateLockProps {\n  className?: string;\n  /** Seconds the lock is valid for. */\n  durationSeconds?: number;\n  from: string;\n  lockedAt: Date;\n  onRefresh: () => number;\n  rate: number;\n  to: string;\n}\n\n/**\n * On expiry the rate refreshes and the confirm action stays disabled until\n * the user acknowledges the new rate. Silently executing at a rate the user\n * didn't see is the worst failure this family can have.\n *\n * The countdown is a rule-line progress bar, linear — consistent with the\n * statement grammar rather than a rounded pill.\n */\nexport function RateLock({\n  from,\n  to,\n  rate,\n  lockedAt,\n  durationSeconds = 30,\n  onRefresh,\n  className,\n}: RateLockProps) {\n  const [remaining, setRemaining] = React.useState(durationSeconds);\n  const [pendingRate, setPendingRate] = React.useState<number | null>(null);\n  const [currentRate, setCurrentRate] = React.useState(rate);\n  const [lockTime, setLockTime] = React.useState(lockedAt);\n\n  /* The latest callback, read at expiry: an inline `onRefresh` would otherwise\n     restart the countdown on every parent render. */\n  const refresh = React.useRef(onRefresh);\n  refresh.current = onRefresh;\n  const expired = remaining <= 0;\n\n  React.useEffect(() => {\n    if (pendingRate !== null || expired) return;\n    const id = setInterval(() => setRemaining((r) => Math.max(0, r - 1)), 1000);\n    return () => clearInterval(id);\n  }, [pendingRate, expired]);\n\n  /* Out of the state updater: an updater must be pure, and StrictMode runs it\n     twice — which fetched two quotes per expiry. */\n  React.useEffect(() => {\n    if (expired && pendingRate === null) setPendingRate(refresh.current());\n  }, [expired, pendingRate]);\n\n  const fraction = remaining / durationSeconds;\n  const warning = fraction <= 0.2 && pendingRate === null;\n\n  const acknowledge = () => {\n    if (pendingRate === null) return;\n    setCurrentRate(pendingRate);\n    setLockTime(new Date());\n    setPendingRate(null);\n    setRemaining(durationSeconds);\n  };\n\n  return (\n    <div className={cn(\"space-y-2\", className)}>\n      <RateLine asOf={lockTime} from={from} locked={pendingRate === null} rate={currentRate} to={to} />\n\n      <div\n        aria-label=\"Rate lock remaining\"\n        aria-valuemax={durationSeconds}\n        aria-valuemin={0}\n        aria-valuenow={remaining}\n        className=\"relative h-[3px] w-full bg-border-subtle\"\n        role=\"progressbar\"\n      >\n        <div\n          className={cn(\n            // scaleX rather than width: the bar ticks every second, and a width\n            // change relayouts the row each tick. Linear, because it is a clock.\n            \"absolute inset-0 origin-left transition-transform duration-[1000ms] ease-linear\",\n            warning ? \"bg-[var(--warning)]\" : \"bg-accent\",\n          )}\n          style={{ transform: `scaleX(${fraction})` }}\n        />\n      </div>\n\n      {pendingRate !== null ? (\n        <div className=\"ledger-crossfade flex flex-wrap items-center gap-3 rounded-sm bg-warning-subtle p-3\">\n          <p className=\"flex-1 text-caption text-text-primary\">\n            The rate expired and has been refreshed to{\" \"}\n            <span className=\"numeric font-medium\">{formatRate(pendingRate)}</span>. Review it before\n            continuing.\n          </p>\n          <Button onClick={acknowledge} size=\"sm\" variant=\"primary\">\n            Accept new rate\n          </Button>\n        </div>\n      ) : (\n        <p className={cn(\"numeric text-caption\", warning ? \"text-warning\" : \"text-text-tertiary\")}>\n          {warning ? \"Expiring in \" : \"Rate held for \"}\n          {remaining}s\n        </p>\n      )}\n    </div>\n  );\n}\n",
      "path": "components/ledger/rate-lock.tsx",
      "target": "components/ledger/rate-lock.tsx",
      "type": "registry:component"
    }
  ],
  "name": "rate-lock",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/button",
    "@lopifi/cn",
    "@lopifi/format-money",
    "@lopifi/rate-line"
  ],
  "title": "RateLock",
  "type": "registry:component"
}
