{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-usage-quota",
  "title": "AI Usage Quota",
  "description": "Display AI usage statistics, rate limits, and quota information.",
  "registryDependencies": [
    "alert",
    "badge",
    "button",
    "card",
    "progress",
    "tooltip"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/ai/ai-usage-quota.tsx",
      "content": "\"use client\";\n\nimport { AlertTriangle, Clock, TrendingUp, Zap } from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Alert,\n  AlertDescription,\n  AlertTitle,\n} from \"@/registry/new-york/ui/alert\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport { Progress } from \"@/registry/new-york/ui/progress\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/registry/new-york/ui/tooltip\";\n\nexport interface TokenUsage {\n  input: number;\n  output: number;\n  total: number;\n}\n\nexport interface RateLimit {\n  remaining: number;\n  limit: number;\n  resetAt?: Date;\n  window: \"minute\" | \"hour\" | \"day\" | \"month\";\n}\n\nexport interface Quota {\n  used: number;\n  limit: number;\n  resetAt?: Date;\n  period: \"day\" | \"month\" | \"year\";\n}\n\nexport interface AIUsageQuotaProps {\n  tokenUsage?: TokenUsage;\n  rateLimit?: RateLimit;\n  quota?: Quota;\n  onUpgrade?: () => void;\n  className?: string;\n  showUpgradePrompt?: boolean;\n  upgradeThreshold?: number;\n}\n\nconst MONTHS = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n] as const;\n\nconst WINDOW_LABELS = {\n  minute: \"min\",\n  hour: \"hr\",\n  day: \"day\",\n  month: \"mo\",\n} as const;\n\nconst PERIOD_LABELS = {\n  day: \"Daily\",\n  month: \"Monthly\",\n  year: \"Yearly\",\n} as const;\n\nfunction formatNumber(num: number): string {\n  if (num >= 1_000_000) {\n    return `${(num / 1_000_000).toFixed(1)}M`;\n  }\n  if (num >= 1000) {\n    return `${(num / 1000).toFixed(1)}K`;\n  }\n  return num.toLocaleString();\n}\n\nfunction formatTimeUntil(date: Date): string {\n  const now = new Date();\n  const diff = date.getTime() - now.getTime();\n\n  if (diff <= 0) return \"Now\";\n\n  const minutes = Math.floor(diff / 60_000);\n  const hours = Math.floor(diff / 3_600_000);\n  const days = Math.floor(diff / 86_400_000);\n\n  if (days > 0) return `${days}d`;\n  if (hours > 0) return `${hours}h`;\n  if (minutes > 0) return `${minutes}m`;\n  return \"Soon\";\n}\n\nfunction formatTime(date: Date): string {\n  const hours = date.getHours();\n  const minutes = date.getMinutes();\n  const hour12 = hours % 12 || 12;\n  const ampm = hours >= 12 ? \"PM\" : \"AM\";\n  const minutesStr = minutes.toString().padStart(2, \"0\");\n  return `${hour12}:${minutesStr} ${ampm}`;\n}\n\nfunction formatDateTime(date: Date): string {\n  const month = MONTHS[date.getMonth()];\n  const day = date.getDate();\n  const hours = date.getHours();\n  const minutes = date.getMinutes();\n  const hour12 = hours % 12 || 12;\n  const ampm = hours >= 12 ? \"PM\" : \"AM\";\n  const minutesStr = minutes.toString().padStart(2, \"0\");\n  return `${month} ${day}, ${hour12}:${minutesStr} ${ampm}`;\n}\n\nfunction calculatePercentage(used: number, limit: number): number {\n  return (used / limit) * 100;\n}\n\nfunction getStatusVariant(percentage: number, isQuota = false) {\n  if (isQuota) {\n    if (percentage >= 90) return \"destructive\";\n    if (percentage >= 75) return \"outline\";\n    return \"secondary\";\n  }\n  if (percentage < 20) return \"destructive\";\n  if (percentage < 50) return \"outline\";\n  return \"secondary\";\n}\n\ninterface TimeUntilProps {\n  date: Date;\n  className?: string;\n  \"aria-label\"?: string;\n}\n\nfunction TimeUntil({\n  date,\n  className,\n  \"aria-label\": ariaLabel,\n}: TimeUntilProps) {\n  const [timeUntil, setTimeUntil] = useState(() => formatTimeUntil(date));\n\n  useEffect(() => {\n    const updateTime = () => {\n      setTimeUntil(formatTimeUntil(date));\n    };\n\n    const timeoutId = setTimeout(updateTime, 0);\n    const interval = setInterval(updateTime, 60_000);\n\n    return () => {\n      clearTimeout(timeoutId);\n      clearInterval(interval);\n    };\n  }, [date]);\n\n  return (\n    <span aria-label={ariaLabel} aria-live=\"polite\" className={className}>\n      {timeUntil}\n    </span>\n  );\n}\n\ninterface TokenUsageDisplayProps {\n  tokenUsage: TokenUsage;\n}\n\nfunction TokenUsageDisplay({ tokenUsage }: TokenUsageDisplayProps) {\n  return (\n    <section\n      aria-labelledby=\"token-usage-heading\"\n      className=\"flex flex-col gap-3\"\n    >\n      <div className=\"flex items-center justify-between\">\n        <h3 className=\"font-medium text-sm\" id=\"token-usage-heading\">\n          Token Usage\n        </h3>\n        <Badge\n          aria-label={`Total tokens: ${formatNumber(tokenUsage.total)}`}\n          className=\"font-mono text-xs\"\n          variant=\"outline\"\n        >\n          {formatNumber(tokenUsage.total)}\n        </Badge>\n      </div>\n      <div className=\"flex flex-col gap-2\" role=\"list\">\n        <div\n          className=\"flex items-center justify-between text-muted-foreground text-xs\"\n          role=\"listitem\"\n        >\n          <div className=\"flex items-center gap-1.5\">\n            <TrendingUp aria-hidden=\"true\" className=\"size-3.5\" />\n            <span>Input</span>\n          </div>\n          <span\n            aria-label={`Input tokens: ${formatNumber(tokenUsage.input)}`}\n            className=\"font-mono tabular-nums\"\n          >\n            {formatNumber(tokenUsage.input)}\n          </span>\n        </div>\n        <div\n          className=\"flex items-center justify-between text-muted-foreground text-xs\"\n          role=\"listitem\"\n        >\n          <div className=\"flex items-center gap-1.5\">\n            <Zap aria-hidden=\"true\" className=\"size-3\" />\n            <span>Output</span>\n          </div>\n          <span\n            aria-label={`Output tokens: ${formatNumber(tokenUsage.output)}`}\n            className=\"font-mono tabular-nums\"\n          >\n            {formatNumber(tokenUsage.output)}\n          </span>\n        </div>\n      </div>\n    </section>\n  );\n}\n\ninterface RateLimitIndicatorProps {\n  rateLimit: RateLimit;\n}\n\nfunction RateLimitIndicator({ rateLimit }: RateLimitIndicatorProps) {\n  const percentage = calculatePercentage(rateLimit.remaining, rateLimit.limit);\n  const usedPercentage = 100 - percentage;\n  const isLow = percentage < 20;\n  const isWarning = percentage < 50;\n  const variant = getStatusVariant(percentage);\n\n  const windowLabel = WINDOW_LABELS[rateLimit.window];\n\n  return (\n    <section\n      aria-labelledby=\"rate-limit-heading\"\n      className=\"flex flex-col gap-3\"\n    >\n      <div className=\"flex items-center justify-between\">\n        <h3 className=\"font-medium text-sm\" id=\"rate-limit-heading\">\n          Rate Limit\n        </h3>\n        <Badge\n          aria-label={`${rateLimit.remaining} of ${rateLimit.limit} requests remaining`}\n          className={cn(\n            \"font-mono text-xs\",\n            isLow && \"border-destructive/50 bg-destructive/10 text-destructive\"\n          )}\n          variant={variant}\n        >\n          {rateLimit.remaining}/{rateLimit.limit}\n        </Badge>\n      </div>\n      <div className=\"flex flex-col gap-2\">\n        <Progress\n          aria-label={`${rateLimit.remaining} of ${rateLimit.limit} requests remaining, ${usedPercentage.toFixed(1)}% used`}\n          aria-valuemax={100}\n          aria-valuemin={0}\n          aria-valuenow={percentage}\n          className=\"h-2\"\n          role=\"progressbar\"\n          value={percentage}\n        />\n        <div className=\"flex items-center justify-between text-muted-foreground text-xs\">\n          <div className=\"flex items-center gap-1.5\">\n            <Clock aria-hidden=\"true\" className=\"size-3\" />\n            <span>Per {windowLabel}</span>\n          </div>\n          {rateLimit.resetAt && (\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <span className=\"cursor-help touch-manipulation\" tabIndex={0}>\n                  <span className=\"sr-only\">Rate limit resets in </span>\n                  <TimeUntil\n                    aria-label={`Rate limit resets in ${formatTimeUntil(rateLimit.resetAt)}`}\n                    date={rateLimit.resetAt}\n                  />\n                </span>\n              </TooltipTrigger>\n              <TooltipContent>\n                <p>Resets at {formatTime(rateLimit.resetAt)}</p>\n              </TooltipContent>\n            </Tooltip>\n          )}\n        </div>\n      </div>\n    </section>\n  );\n}\n\ninterface QuotaProgressProps {\n  quota: Quota;\n}\n\nfunction QuotaProgress({ quota }: QuotaProgressProps) {\n  const percentage = calculatePercentage(quota.used, quota.limit);\n  const isLow = percentage >= 90;\n  const isWarning = percentage >= 75;\n  const variant = getStatusVariant(percentage, true);\n\n  const periodLabel = PERIOD_LABELS[quota.period];\n\n  return (\n    <section aria-labelledby=\"quota-heading\" className=\"flex flex-col gap-3\">\n      <div className=\"flex items-center justify-between\">\n        <h3 className=\"font-medium text-sm\" id=\"quota-heading\">\n          {periodLabel} Quota\n        </h3>\n        <Badge\n          aria-label={`${formatNumber(quota.used)} of ${formatNumber(quota.limit)} tokens used, ${percentage.toFixed(1)}%`}\n          className={cn(\n            \"font-mono text-xs\",\n            isLow && \"border-destructive/50 bg-destructive/10 text-destructive\"\n          )}\n          variant={variant}\n        >\n          {formatNumber(quota.used)}/{formatNumber(quota.limit)}\n        </Badge>\n      </div>\n      <div className=\"flex flex-col gap-2\">\n        <Progress\n          aria-label={`${formatNumber(quota.used)} of ${formatNumber(quota.limit)} tokens used, ${percentage.toFixed(1)}%`}\n          aria-valuemax={100}\n          aria-valuemin={0}\n          aria-valuenow={percentage}\n          className={cn(\n            \"h-2\",\n            isLow && \"[&>div]:bg-destructive\",\n            isWarning && !isLow && \"[&>div]:bg-yellow-500\"\n          )}\n          role=\"progressbar\"\n          value={percentage}\n        />\n        <div className=\"flex items-center justify-between text-muted-foreground text-xs\">\n          <span aria-live=\"polite\">{percentage.toFixed(1)}% used</span>\n          {quota.resetAt && (\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <span\n                  className=\"flex cursor-help touch-manipulation items-center gap-1\"\n                  tabIndex={0}\n                >\n                  <Clock aria-hidden=\"true\" className=\"size-3\" />\n                  <span>\n                    <span className=\"sr-only\">Quota resets in </span>\n                    <TimeUntil\n                      aria-label={`Quota resets in ${formatTimeUntil(quota.resetAt)}`}\n                      date={quota.resetAt}\n                    />\n                  </span>\n                </span>\n              </TooltipTrigger>\n              <TooltipContent>\n                <p>Resets at {formatDateTime(quota.resetAt)}</p>\n              </TooltipContent>\n            </Tooltip>\n          )}\n        </div>\n      </div>\n    </section>\n  );\n}\n\ninterface UpgradePromptProps {\n  message?: string;\n  onUpgrade?: () => void;\n}\n\nfunction UpgradePrompt({ message, onUpgrade }: UpgradePromptProps) {\n  const handleKeyDown = useCallback(\n    (e: React.KeyboardEvent) => {\n      if (e.key === \"Enter\" || e.key === \" \") {\n        e.preventDefault();\n        onUpgrade?.();\n      }\n    },\n    [onUpgrade]\n  );\n\n  return (\n    <Alert\n      className=\"rounded-xl border-primary/20 bg-primary/5 shadow-sm\"\n      live=\"assertive\"\n      role=\"alert\"\n    >\n      <AlertTriangle aria-hidden=\"true\" className=\"size-4 text-primary\" />\n      <AlertTitle className=\"text-sm\">Usage Warning!</AlertTitle>\n      <AlertDescription className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n        <span className=\"text-muted-foreground text-xs\">\n          {message ||\n            \"You're approaching your usage limits. Upgrade to continue using HextaAI without interruptions.\"}\n        </span>\n        {onUpgrade && (\n          <Button\n            className=\"min-h-[32px] w-full touch-manipulation sm:w-auto\"\n            onClick={onUpgrade}\n            onKeyDown={handleKeyDown}\n            size=\"sm\"\n            variant=\"default\"\n          >\n            Upgrade Now\n          </Button>\n        )}\n      </AlertDescription>\n    </Alert>\n  );\n}\n\nexport default function AIUsageQuota({\n  tokenUsage,\n  rateLimit,\n  quota,\n  onUpgrade,\n  className,\n  showUpgradePrompt = true,\n  upgradeThreshold = 80,\n}: AIUsageQuotaProps) {\n  const shouldShowUpgrade = useMemo(() => {\n    if (!showUpgradePrompt) return false;\n\n    const quotaPercentage = quota\n      ? calculatePercentage(quota.used, quota.limit)\n      : 0;\n    const rateLimitPercentage = rateLimit\n      ? calculatePercentage(\n          rateLimit.limit - rateLimit.remaining,\n          rateLimit.limit\n        )\n      : 0;\n\n    return (\n      quotaPercentage >= upgradeThreshold ||\n      rateLimitPercentage >= upgradeThreshold\n    );\n  }, [quota, rateLimit, showUpgradePrompt, upgradeThreshold]);\n\n  const upgradeMessage = useMemo(() => {\n    if (!shouldShowUpgrade) return;\n\n    if (quota) {\n      const quotaPercentage = calculatePercentage(quota.used, quota.limit);\n      if (quotaPercentage >= upgradeThreshold) {\n        return `You've used ${quotaPercentage.toFixed(0)}% of your ${quota.period}ly quota. Upgrade to get more tokens.`;\n      }\n    }\n\n    if (rateLimit) {\n      const rateLimitPercentage = calculatePercentage(\n        rateLimit.limit - rateLimit.remaining,\n        rateLimit.limit\n      );\n      if (rateLimitPercentage >= upgradeThreshold) {\n        return `You've used ${rateLimitPercentage.toFixed(0)}% of your rate limit. Upgrade for higher limits.`;\n      }\n    }\n\n    return;\n  }, [quota, rateLimit, shouldShowUpgrade, upgradeThreshold]);\n\n  const hasAnyData = tokenUsage || rateLimit || quota;\n\n  if (!hasAnyData) {\n    return null;\n  }\n\n  return (\n    <TooltipProvider>\n      <div\n        aria-label=\"AI usage and quota information\"\n        className={cn(\"flex w-full flex-col gap-4\", className)}\n        role=\"region\"\n      >\n        {shouldShowUpgrade && (\n          <UpgradePrompt message={upgradeMessage} onUpgrade={onUpgrade} />\n        )}\n\n        <Card className=\"gap-2 p-4 shadow-xs md:p-6\">\n          <CardHeader className=\"p-0\">\n            <CardTitle className=\"p-0 text-base\">Usage & Quota</CardTitle>\n          </CardHeader>\n          <CardContent className=\"flex flex-col gap-6 p-0\">\n            {tokenUsage && <TokenUsageDisplay tokenUsage={tokenUsage} />}\n            {rateLimit && <RateLimitIndicator rateLimit={rateLimit} />}\n            {quota && <QuotaProgress quota={quota} />}\n          </CardContent>\n        </Card>\n      </div>\n    </TooltipProvider>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "ai"
  ],
  "type": "registry:ui"
}