{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "billing-coupon-code",
  "title": "Billing Coupon Code",
  "description": "Apply and manage discount coupon codes.",
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "field",
    "input-group",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/billing/billing-coupon-code.tsx",
      "content": "\"use client\";\n\nimport { Check, Loader2, Tag, X } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  Field,\n  FieldContent,\n  FieldError,\n  FieldLabel,\n} from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport interface AppliedCoupon {\n  code: string;\n  type: \"percentage\" | \"fixed\";\n  value: number;\n  label?: string;\n  expiresAt?: Date;\n  description?: string;\n}\n\nexport interface BillingCouponCodeProps {\n  onApply?: (code: string) => Promise<void>;\n  onRemove?: () => Promise<void>;\n  appliedCoupon?: AppliedCoupon | null;\n  className?: string;\n  currency?: string;\n  isLoading?: boolean;\n  error?: string;\n}\n\nfunction formatPrice(amount: number, currency = \"USD\"): string {\n  return new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency,\n    minimumFractionDigits: 2,\n    maximumFractionDigits: 2,\n  }).format(amount);\n}\n\nfunction formatDate(date: Date): string {\n  const year = date.getFullYear();\n  const month = date.toLocaleString(\"en-US\", { month: \"short\" });\n  const day = date.getDate();\n  return `${month} ${day}, ${year}`;\n}\n\nexport default function BillingCouponCode({\n  onApply,\n  onRemove,\n  appliedCoupon,\n  className,\n  currency = \"USD\",\n  isLoading: externalLoading,\n  error: externalError,\n}: BillingCouponCodeProps) {\n  const [code, setCode] = useState(\"\");\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  const handleApply = async () => {\n    if (!code.trim()) {\n      setError(\"Please enter a coupon code\");\n      return;\n    }\n\n    if (!onApply) return;\n\n    setIsLoading(true);\n    setError(null);\n\n    try {\n      await onApply(code.trim().toUpperCase());\n      setCode(\"\");\n    } catch (err) {\n      setError(\n        err instanceof Error ? err.message : \"Failed to apply coupon code\"\n      );\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleRemove = async () => {\n    if (!onRemove) return;\n\n    setIsLoading(true);\n    setError(null);\n\n    try {\n      await onRemove();\n    } catch (err) {\n      setError(\n        err instanceof Error ? err.message : \"Failed to remove coupon code\"\n      );\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === \"Enter\" && !isLoading && !externalLoading) {\n      handleApply();\n    }\n  };\n\n  const displayError = externalError || error;\n  const displayLoading = isLoading || externalLoading;\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-1\">\n          <CardTitle>Discount Code</CardTitle>\n          <CardDescription>\n            Apply a promotional code to your subscription\n          </CardDescription>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {appliedCoupon ? (\n            <div className=\"flex flex-col gap-4 rounded-lg border bg-muted/50 p-4\">\n              <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n                <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                  <div className=\"flex flex-wrap items-center gap-2\">\n                    <Tag className=\"size-4 shrink-0 text-primary\" />\n                    <span className=\"font-medium text-sm\">\n                      {appliedCoupon.code}\n                    </span>\n                    <Badge className=\"shrink-0 text-xs\" variant=\"secondary\">\n                      Applied\n                    </Badge>\n                  </div>\n                  {appliedCoupon.label && (\n                    <p className=\"wrap-break-word text-muted-foreground text-sm\">\n                      {appliedCoupon.label}\n                    </p>\n                  )}\n                  {appliedCoupon.description && (\n                    <p className=\"wrap-break-word text-muted-foreground text-xs\">\n                      {appliedCoupon.description}\n                    </p>\n                  )}\n                  {appliedCoupon.expiresAt && (\n                    <p className=\"text-muted-foreground text-xs\">\n                      Expires: {formatDate(appliedCoupon.expiresAt)}\n                    </p>\n                  )}\n                </div>\n                {onRemove && (\n                  <Button\n                    aria-label={`Remove coupon ${appliedCoupon.code}`}\n                    className=\"w-full sm:w-auto\"\n                    onClick={handleRemove}\n                    type=\"button\"\n                    variant=\"ghost\"\n                  >\n                    <X className=\"size-4\" />\n                    Remove\n                  </Button>\n                )}\n              </div>\n\n              <Separator />\n\n              <div className=\"flex flex-col gap-2\">\n                <div className=\"flex items-center justify-between text-sm\">\n                  <span className=\"text-muted-foreground\">Discount</span>\n                  <div className=\"flex items-center gap-2\">\n                    {appliedCoupon.type === \"percentage\" ? (\n                      <span className=\"font-medium text-green-600\">\n                        -{appliedCoupon.value}%\n                      </span>\n                    ) : (\n                      <span className=\"font-medium text-green-600\">\n                        -{formatPrice(appliedCoupon.value, currency)}\n                      </span>\n                    )}\n                    <Check className=\"size-4 text-green-600\" />\n                  </div>\n                </div>\n              </div>\n            </div>\n          ) : (\n            <div className=\"flex flex-col gap-4\">\n              <Field>\n                <FieldLabel htmlFor=\"coupon-code\">Coupon Code</FieldLabel>\n                <FieldContent>\n                  <InputGroup>\n                    <InputGroupAddon>\n                      <Tag className=\"size-4\" />\n                    </InputGroupAddon>\n                    <InputGroupInput\n                      aria-describedby={\n                        displayError ? \"coupon-error\" : undefined\n                      }\n                      aria-invalid={!!displayError}\n                      autoComplete=\"off\"\n                      disabled={displayLoading}\n                      id=\"coupon-code\"\n                      onChange={(e) => {\n                        setCode(e.target.value.toUpperCase());\n                        setError(null);\n                      }}\n                      onKeyDown={handleKeyDown}\n                      placeholder=\"Enter code\"\n                      type=\"text\"\n                      value={code}\n                    />\n                  </InputGroup>\n                  {displayError && (\n                    <FieldError id=\"coupon-error\">{displayError}</FieldError>\n                  )}\n                </FieldContent>\n              </Field>\n\n              <Button\n                aria-busy={displayLoading}\n                className=\"w-full sm:w-auto\"\n                data-loading={displayLoading}\n                disabled={!code.trim() || displayLoading}\n                onClick={handleApply}\n                type=\"button\"\n              >\n                {displayLoading ? (\n                  <>\n                    <Loader2 className=\"size-4 animate-spin\" />\n                    Applying…\n                  </>\n                ) : (\n                  <>\n                    <Tag className=\"size-4\" />\n                    Apply Code\n                  </>\n                )}\n              </Button>\n            </div>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "billing"
  ],
  "type": "registry:ui"
}