{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cancel-subscription-dialog",
  "title": "Cancel Subscription Dialog",
  "description": "A cancel subscription dialog component",
  "dependencies": ["lucide-react"],
  "registryDependencies": ["button", "badge", "dialog", "utils"],
  "files": [
    {
      "path": "src/registry/billingsdk/cancel-subscription-dialog.tsx",
      "content": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { type Plan } from \"@/lib/billingsdk-config\";\nimport { cn } from \"@/lib/utils\";\nimport { X, Circle } from \"lucide-react\";\nimport { useTheme } from \"@/contexts/theme-context\";\nimport { getThemeStyles } from \"@/lib/themes\";\n\nexport interface CancelSubscriptionDialogProps {\n  title: string;\n  description: string;\n  plan: Plan;\n  triggerButtonText?: string;\n  leftPanelImageUrl?: string;\n  warningTitle?: string;\n  warningText?: string;\n  keepButtonText?: string;\n  continueButtonText?: string;\n  finalTitle?: string;\n  finalSubtitle?: string;\n  finalWarningText?: string;\n  goBackButtonText?: string;\n  confirmButtonText?: string;\n  onCancel: (planId: string) => Promise<void> | void;\n  onKeepSubscription?: (planId: string) => Promise<void> | void;\n  onDialogClose?: () => void;\n  className?: string;\n}\n\nexport function CancelSubscriptionDialog({\n  title,\n  description,\n  plan,\n  triggerButtonText,\n  leftPanelImageUrl,\n  warningTitle,\n  warningText,\n  keepButtonText,\n  continueButtonText,\n  finalTitle,\n  finalSubtitle,\n  finalWarningText,\n  goBackButtonText,\n  confirmButtonText,\n  onCancel,\n  onKeepSubscription,\n  onDialogClose,\n  className,\n}: CancelSubscriptionDialogProps) {\n  const [showConfirmation, setShowConfirmation] = useState(false);\n  const [isOpen, setIsOpen] = useState(false);\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n  const { currentTheme, previewDarkMode } = useTheme();\n  const themeStyles = getThemeStyles(currentTheme, previewDarkMode);\n\n  const handleContinueCancellation = () => {\n    setShowConfirmation(true);\n    setError(null);\n  };\n\n  const handleConfirmCancellation = async () => {\n    try {\n      setIsLoading(true);\n      setError(null);\n      await onCancel(plan.id);\n      handleDialogClose();\n    } catch (err) {\n      setError(\n        err instanceof Error ? err.message : \"Failed to cancel subscription\",\n      );\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleKeepSubscription = async () => {\n    try {\n      setIsLoading(true);\n      setError(null);\n      if (onKeepSubscription) {\n        await onKeepSubscription(plan.id);\n      }\n      handleDialogClose();\n    } catch (err) {\n      setError(\n        err instanceof Error ? err.message : \"Failed to keep subscription\",\n      );\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleDialogClose = () => {\n    setIsOpen(false);\n    setShowConfirmation(false);\n    setError(null);\n    setIsLoading(false);\n    onDialogClose?.();\n  };\n\n  const handleGoBack = () => {\n    setShowConfirmation(false);\n    setError(null);\n  };\n\n  // Handle keyboard shortcuts\n  useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (!isOpen) return;\n\n      if (event.key === \"Escape\") {\n        event.preventDefault();\n        handleDialogClose();\n      }\n    };\n\n    document.addEventListener(\"keydown\", handleKeyDown);\n    return () => document.removeEventListener(\"keydown\", handleKeyDown);\n  }, [isOpen]);\n\n  return (\n    <Dialog\n      open={isOpen}\n      onOpenChange={(open) => {\n        if (open) {\n          setIsOpen(true);\n        } else {\n          handleDialogClose();\n        }\n      }}\n    >\n      <DialogTrigger asChild>\n        <Button variant=\"outline\">\n          {triggerButtonText || \"Cancel Subscription\"}\n        </Button>\n      </DialogTrigger>\n      <DialogContent\n        className={cn(\n          \"text-foreground flex w-[95%] flex-col overflow-hidden p-0 sm:max-w-[1000px] md:w-[100%] md:flex-row\",\n          leftPanelImageUrl ? \"\" : \"sm:max-w-[500px]\",\n          className,\n        )}\n        style={themeStyles}\n      >\n        <DialogTitle className=\"sr-only\">{title}</DialogTitle>\n        <DialogClose\n          className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 z-10 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none\"\n          onClick={handleDialogClose}\n        >\n          <X className=\"h-4 w-4\" />\n          <span className=\"sr-only\">Close</span>\n        </DialogClose>\n        {leftPanelImageUrl && (\n          <div className=\"relative hidden min-h-[500px] w-full overflow-hidden md:block md:w-1/2\">\n            {/* eslint-disable-next-line @next/next/no-img-element */}\n            <img\n              src={leftPanelImageUrl}\n              alt=\"Cancel Subscription\"\n              className=\"absolute inset-0 h-full w-full object-cover\"\n            />\n            <div className=\"via-background/30 to-background/90 absolute inset-0 hidden bg-gradient-to-r from-transparent dark:block\"></div>\n            <div className=\"from-background/80 to-background/20 absolute inset-0 hidden bg-gradient-to-t via-transparent dark:block\"></div>\n          </div>\n        )}\n        <div\n          className={cn(\n            \"flex flex-col gap-4 px-4 py-6\",\n            leftPanelImageUrl ? \"w-full md:w-1/2\" : \"w-full\",\n          )}\n        >\n          <div className=\"flex flex-col gap-2 text-center md:text-left\">\n            <h2 className=\"text-xl font-semibold md:text-2xl\">{title}</h2>\n            <p className=\"text-muted-foreground text-xs md:text-sm\">\n              {description}\n            </p>\n            {error && (\n              <div className=\"bg-destructive/10 border-destructive/20 rounded-md border p-3\">\n                <p className=\"text-destructive text-sm\">{error}</p>\n              </div>\n            )}\n          </div>\n\n          {/* Plan Details */}\n          {!showConfirmation && (\n            <div className=\"bg-muted/50 flex flex-col gap-4 rounded-lg p-4\">\n              <div className=\"flex items-center justify-between\">\n                <div className=\"flex flex-col gap-1\">\n                  <span className=\"text-lg font-semibold\">\n                    {plan.title} Plan\n                  </span>\n                  <span className=\"text-muted-foreground text-sm\">\n                    Current subscription\n                  </span>\n                </div>\n                <Badge variant=\"secondary\">\n                  {parseFloat(plan.monthlyPrice) >= 0\n                    ? `${plan.currency}${plan.monthlyPrice}/monthly`\n                    : `${plan.monthlyPrice}/monthly`}\n                </Badge>\n              </div>\n              <div className=\"flex flex-col gap-2\">\n                {plan.features.slice(0, 4).map((feature, index) => (\n                  <div key={index} className=\"flex items-center gap-2\">\n                    <Circle className=\"fill-primary text-primary h-2 w-2\" />\n                    <span className=\"text-muted-foreground text-sm\">\n                      {feature.name}\n                    </span>\n                  </div>\n                ))}\n              </div>\n            </div>\n          )}\n\n          {/* Warning Section */}\n          {!showConfirmation && (warningTitle || warningText) && (\n            <div className=\"bg-muted/30 border-border rounded-lg border p-4\">\n              {warningTitle && (\n                <h3 className=\"text-foreground mb-2 font-semibold\">\n                  {warningTitle}\n                </h3>\n              )}\n              {warningText && (\n                <p className=\"text-muted-foreground text-sm\">{warningText}</p>\n              )}\n            </div>\n          )}\n          {/* Action Buttons */}\n          {!showConfirmation ? (\n            <div className=\"mt-auto flex flex-col gap-3 lg:flex-row\">\n              <Button\n                className=\"flex-1\"\n                onClick={handleKeepSubscription}\n                disabled={isLoading}\n              >\n                {isLoading\n                  ? \"Processing...\"\n                  : keepButtonText || \"Keep My Subscription\"}\n              </Button>\n              <Button\n                variant=\"destructive\"\n                className=\"flex-1\"\n                onClick={handleContinueCancellation}\n                disabled={isLoading}\n              >\n                {continueButtonText || \"Continue Cancellation\"}\n              </Button>\n            </div>\n          ) : (\n            <div className=\"mt-auto flex flex-col gap-4\">\n              <div className=\"bg-muted/50 rounded-lg p-4 text-center\">\n                <h3 className=\"text-foreground mb-2 font-semibold\">\n                  {finalTitle || \"Final Confirmation\"}\n                </h3>\n                <p className=\"text-muted-foreground mb-2 text-sm\">\n                  {finalSubtitle ||\n                    \"Are you sure you want to cancel your subscription?\"}\n                </p>\n                <p className=\"text-destructive text-sm\">\n                  {finalWarningText ||\n                    \"This action cannot be undone and you'll lose access to all premium features.\"}\n                </p>\n              </div>\n              <div className=\"flex flex-col gap-3 lg:flex-row\">\n                <Button\n                  variant=\"outline\"\n                  className=\"flex-1\"\n                  onClick={handleGoBack}\n                  disabled={isLoading}\n                >\n                  {goBackButtonText || \"Go Back\"}\n                </Button>\n                <Button\n                  variant=\"destructive\"\n                  className=\"flex-1\"\n                  onClick={handleConfirmCancellation}\n                  disabled={isLoading}\n                >\n                  {isLoading\n                    ? \"Cancelling...\"\n                    : confirmButtonText || \"Yes, Cancel Subscription\"}\n                </Button>\n              </div>\n            </div>\n          )}\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/billingsdk/cancel-subscription-dialog.tsx"
    },
    {
      "path": "src/registry/billingsdk/demo/cancel-subscription-dialog-demo.tsx",
      "content": "\"use client\";\n\nimport { CancelSubscriptionDialog } from \"@/components/billingsdk/cancel-subscription-dialog\";\nimport { plans } from \"@/lib/billingsdk-config\";\n\nexport function CancelSubscriptionDialogDemo() {\n  return (\n    <div className=\"mx-auto flex min-h-[300px] flex-1 flex-col justify-center p-4 text-center\">\n      <CancelSubscriptionDialog\n        title=\"We're sorry to see you go...\"\n        description={`Before you cancel, we hope you'll consider upgrading to a ${plans[1].title} plan again.`}\n        plan={plans[1]}\n        triggerButtonText=\"Cancel Subscription\"\n        leftPanelImageUrl=\"https://framerusercontent.com/images/GWE8vop9hubsuh3uWWn0vyuxEg.webp\"\n        warningTitle=\"You will lose access to your account\"\n        warningText=\"If you cancel your subscription, you will lose access to your account and all your data will be deleted.\"\n        keepButtonText={`Keep My ${plans[1].title} Plan`}\n        continueButtonText=\"Continue with Cancellation\"\n        finalTitle=\"Final Step - Confirm Cancellation\"\n        finalSubtitle=\"This action will immediately cancel your subscription\"\n        finalWarningText=\"You'll lose access to all Pro features and your data will be permanently deleted after 30 days.\"\n        goBackButtonText=\"Wait, Go Back\"\n        confirmButtonText=\"Yes, Cancel My Subscription\"\n        onCancel={async (planId) => {\n          console.log(\"Cancelling subscription for plan:\", planId);\n          return new Promise((resolve) => {\n            setTimeout(() => {\n              resolve(void 0);\n            }, 1000);\n          });\n        }}\n        onKeepSubscription={async (planId) => {\n          console.log(\"Keeping subscription for plan:\", planId);\n        }}\n        onDialogClose={() => {\n          console.log(\"Dialog closed\");\n        }}\n        className=\"max-w-4xl\"\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/cancel-subscription-dialog-demo.tsx"
    },
    {
      "path": "src/registry/lib/billingsdk-config.ts",
      "content": "export interface Plan {\n  id: string;\n  title: string;\n  description: string;\n  highlight?: boolean;\n  type?: \"monthly\" | \"yearly\";\n  currency?: string;\n  monthlyPrice: string;\n  yearlyPrice: string;\n  buttonText: string;\n  badge?: string;\n  features: {\n    name: string;\n    icon: string;\n    iconColor?: string;\n  }[];\n}\n\nexport interface CurrentPlan {\n  plan: Plan;\n  type: \"monthly\" | \"yearly\" | \"custom\";\n  price?: string;\n  nextBillingDate: string;\n  paymentMethod: string;\n  status: \"active\" | \"inactive\" | \"past_due\" | \"cancelled\";\n}\n\nexport const plans: Plan[] = [\n  {\n    id: \"starter\",\n    title: \"Starter\",\n    description: \"For developers testing out Liveblocks locally.\",\n    currency: \"$\",\n    monthlyPrice: \"0\",\n    yearlyPrice: \"0\",\n    buttonText: \"Start today for free\",\n    features: [\n      {\n        name: \"Presence\",\n        icon: \"check\",\n        iconColor: \"text-green-500\",\n      },\n      {\n        name: \"Comments\",\n        icon: \"check\",\n        iconColor: \"text-orange-500\",\n      },\n      {\n        name: \"Notifications\",\n        icon: \"check\",\n        iconColor: \"text-teal-500\",\n      },\n      {\n        name: \"Text Editor\",\n        icon: \"check\",\n        iconColor: \"text-blue-500\",\n      },\n      {\n        name: \"Sync Datastore\",\n        icon: \"check\",\n        iconColor: \"text-zinc-500\",\n      },\n    ],\n  },\n  {\n    id: \"pro\",\n    title: \"Pro\",\n    description: \"For companies adding collaboration in production.\",\n    currency: \"$\",\n    monthlyPrice: \"20\",\n    yearlyPrice: \"199\",\n    buttonText: \"Sign up\",\n    badge: \"Most popular\",\n    highlight: true,\n    features: [\n      {\n        name: \"Presence\",\n        icon: \"check\",\n        iconColor: \"text-green-500\",\n      },\n      {\n        name: \"Comments\",\n        icon: \"check\",\n        iconColor: \"text-orange-500\",\n      },\n      {\n        name: \"Notifications\",\n        icon: \"check\",\n        iconColor: \"text-teal-500\",\n      },\n      {\n        name: \"Text Editor\",\n        icon: \"check\",\n        iconColor: \"text-blue-500\",\n      },\n      {\n        name: \"Sync Datastore\",\n        icon: \"check\",\n        iconColor: \"text-zinc-500\",\n      },\n    ],\n  },\n  {\n    id: \"enterprise\",\n    title: \"Enterprise\",\n    description:\n      \"For organizations that need more support and compliance features.\",\n    currency: \"$\",\n    monthlyPrice: \"Custom\",\n    yearlyPrice: \"Custom\",\n    buttonText: \"Contact sales\",\n    features: [\n      {\n        name: \"Presence\",\n        icon: \"check\",\n        iconColor: \"text-green-500\",\n      },\n      {\n        name: \"Comments\",\n        icon: \"check\",\n        iconColor: \"text-orange-500\",\n      },\n      {\n        name: \"Notifications\",\n        icon: \"check\",\n        iconColor: \"text-teal-500\",\n      },\n      {\n        name: \"Text Editor\",\n        icon: \"check\",\n        iconColor: \"text-blue-500\",\n      },\n      {\n        name: \"Sync Datastore\",\n        icon: \"check\",\n        iconColor: \"text-zinc-500\",\n      },\n    ],\n  },\n];\n",
      "type": "registry:lib",
      "target": "lib/billingsdk-config.ts"
    }
  ],
  "type": "registry:block"
}
