@hanzo/ui 5.3.36 → 5.3.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/assets/crypto.d.ts.map +1 -1
  2. package/dist/assets/file.d.ts.map +1 -1
  3. package/dist/assets/general.d.ts.map +1 -1
  4. package/dist/assets/hanzo-logo.d.ts.map +1 -1
  5. package/dist/assets/llm-provider.d.ts.map +1 -1
  6. package/dist/components/cal-embed.d.ts.map +1 -1
  7. package/dist/frameworks/react/hooks/index.d.ts.map +1 -1
  8. package/dist/frameworks/react-native/utils.d.ts.map +1 -1
  9. package/dist/frameworks/svelte/utils.d.ts.map +1 -1
  10. package/dist/frameworks/vue/utils.d.ts.map +1 -1
  11. package/dist/helpers/file.d.ts.map +1 -1
  12. package/dist/helpers/memoization.d.ts.map +1 -1
  13. package/dist/index.js +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/models/index.js +35 -0
  16. package/dist/models/index.mjs +35 -0
  17. package/dist/navigation/index.js +2 -1
  18. package/dist/navigation/index.mjs +2 -1
  19. package/dist/primitives/alert.d.ts +1 -1
  20. package/dist/primitives/cal-embed.d.ts.map +1 -1
  21. package/dist/primitives/chart.d.ts.map +1 -1
  22. package/dist/primitives/chat/chat-input.d.ts.map +1 -1
  23. package/dist/primitives/combobox.d.ts.map +1 -1
  24. package/dist/primitives/command.d.ts.map +1 -1
  25. package/dist/primitives/copy-to-clipboard-icon.d.ts.map +1 -1
  26. package/dist/primitives/dots-loader.d.ts.map +1 -1
  27. package/dist/primitives/error-message.d.ts.map +1 -1
  28. package/dist/primitives/file-uploader.d.ts.map +1 -1
  29. package/dist/primitives/form.d.ts.map +1 -1
  30. package/dist/primitives/input-otp.d.ts.map +1 -1
  31. package/dist/primitives/markdown-preview.d.ts.map +1 -1
  32. package/dist/primitives/pretty-json-print.d.ts.map +1 -1
  33. package/dist/primitives/resizable.d.ts +4 -20
  34. package/dist/primitives/resizable.d.ts.map +1 -1
  35. package/dist/primitives/resizable.js +4 -4
  36. package/dist/primitives/sonner.d.ts.map +1 -1
  37. package/dist/primitives/text-link.d.ts.map +1 -1
  38. package/dist/primitives/textfield.d.ts.map +1 -1
  39. package/dist/primitives/toast.d.ts.map +1 -1
  40. package/dist/resizable.js +1 -1
  41. package/dist/resizable.mjs +1 -1
  42. package/dist/src/billing/components/account-members.d.ts +10 -0
  43. package/dist/src/billing/components/account-members.d.ts.map +1 -0
  44. package/dist/src/billing/components/account-members.js +63 -0
  45. package/dist/src/billing/components/account-switcher.d.ts +9 -0
  46. package/dist/src/billing/components/account-switcher.d.ts.map +1 -0
  47. package/dist/src/billing/components/account-switcher.js +52 -0
  48. package/dist/src/billing/components/animated-card.d.ts +9 -0
  49. package/dist/src/billing/components/animated-card.d.ts.map +1 -0
  50. package/dist/src/billing/components/animated-card.js +161 -0
  51. package/dist/src/billing/components/billing-settings.d.ts +26 -0
  52. package/dist/src/billing/components/billing-settings.d.ts.map +1 -0
  53. package/dist/src/billing/components/billing-settings.js +23 -0
  54. package/dist/src/billing/components/business-profile-panel.d.ts.map +1 -1
  55. package/dist/src/billing/components/business-profile-panel.js +9 -8
  56. package/dist/src/billing/components/card-form.d.ts +18 -0
  57. package/dist/src/billing/components/card-form.d.ts.map +1 -0
  58. package/dist/src/billing/components/card-form.js +139 -0
  59. package/dist/src/billing/components/cost-explorer.d.ts +7 -0
  60. package/dist/src/billing/components/cost-explorer.d.ts.map +1 -0
  61. package/dist/src/billing/components/cost-explorer.js +73 -0
  62. package/dist/src/billing/components/credits-panel.d.ts +8 -0
  63. package/dist/src/billing/components/credits-panel.d.ts.map +1 -0
  64. package/dist/src/billing/components/credits-panel.js +58 -0
  65. package/dist/src/billing/components/guided-setup.d.ts +13 -0
  66. package/dist/src/billing/components/guided-setup.d.ts.map +1 -0
  67. package/dist/src/billing/components/guided-setup.js +44 -0
  68. package/dist/src/billing/components/index.d.ts +34 -0
  69. package/dist/src/billing/components/index.d.ts.map +1 -1
  70. package/dist/src/billing/components/index.js +17 -0
  71. package/dist/src/billing/components/invoice-manager.js +13 -13
  72. package/dist/src/billing/components/invoices-payments.d.ts +7 -0
  73. package/dist/src/billing/components/invoices-payments.d.ts.map +1 -0
  74. package/dist/src/billing/components/invoices-payments.js +44 -0
  75. package/dist/src/billing/components/overview-dashboard.d.ts +31 -0
  76. package/dist/src/billing/components/overview-dashboard.d.ts.map +1 -0
  77. package/dist/src/billing/components/overview-dashboard.js +105 -0
  78. package/dist/src/billing/components/payment-manager.d.ts.map +1 -1
  79. package/dist/src/billing/components/payment-manager.js +267 -83
  80. package/dist/src/billing/components/promotions-panel.d.ts +7 -0
  81. package/dist/src/billing/components/promotions-panel.d.ts.map +1 -0
  82. package/dist/src/billing/components/promotions-panel.js +48 -0
  83. package/dist/src/billing/components/spend-alerts.d.ts +9 -0
  84. package/dist/src/billing/components/spend-alerts.d.ts.map +1 -0
  85. package/dist/src/billing/components/spend-alerts.js +99 -0
  86. package/dist/src/billing/components/square-card-form.d.ts +32 -0
  87. package/dist/src/billing/components/square-card-form.d.ts.map +1 -0
  88. package/dist/src/billing/components/square-card-form.js +179 -0
  89. package/dist/src/billing/components/status-bar.d.ts +12 -0
  90. package/dist/src/billing/components/status-bar.d.ts.map +1 -0
  91. package/dist/src/billing/components/status-bar.js +32 -0
  92. package/dist/src/billing/components/subscription-portal.d.ts +2 -1
  93. package/dist/src/billing/components/subscription-portal.d.ts.map +1 -1
  94. package/dist/src/billing/components/subscription-portal.js +123 -26
  95. package/dist/src/billing/components/support-tiers-panel.d.ts +6 -0
  96. package/dist/src/billing/components/support-tiers-panel.d.ts.map +1 -0
  97. package/dist/src/billing/components/support-tiers-panel.js +90 -0
  98. package/dist/src/billing/components/tax-compliance-panel.js +5 -5
  99. package/dist/src/billing/components/transactions-panel.d.ts +8 -0
  100. package/dist/src/billing/components/transactions-panel.d.ts.map +1 -0
  101. package/dist/src/billing/components/transactions-panel.js +64 -0
  102. package/dist/src/billing/components/usage-panel.d.ts +7 -0
  103. package/dist/src/billing/components/usage-panel.d.ts.map +1 -0
  104. package/dist/src/billing/components/usage-panel.js +64 -0
  105. package/dist/src/billing/types/index.d.ts +136 -1
  106. package/dist/src/billing/types/index.d.ts.map +1 -1
  107. package/dist/src/form/form.d.ts.map +1 -1
  108. package/dist/src/hooks/use-copy-clipboard.d.ts.map +1 -1
  109. package/dist/src/hooks/use-fill-ids.d.ts.map +1 -1
  110. package/dist/src/hooks/use-scroll-restoration.d.ts.map +1 -1
  111. package/dist/src/models/ModelCard.d.ts +25 -0
  112. package/dist/src/models/ModelCard.d.ts.map +1 -0
  113. package/dist/src/models/ModelCard.js +73 -0
  114. package/dist/src/models/ModelLibrary.d.ts +41 -0
  115. package/dist/src/models/ModelLibrary.d.ts.map +1 -0
  116. package/dist/src/models/ModelLibrary.js +63 -0
  117. package/dist/src/models/ModelTable.d.ts +17 -0
  118. package/dist/src/models/ModelTable.d.ts.map +1 -0
  119. package/dist/src/models/ModelTable.js +35 -0
  120. package/dist/src/models/ZenEnso.d.ts +17 -0
  121. package/dist/src/models/ZenEnso.d.ts.map +1 -0
  122. package/dist/src/models/ZenEnso.js +50 -0
  123. package/dist/src/models/index.d.ts +23 -0
  124. package/dist/src/models/index.d.ts.map +1 -0
  125. package/dist/src/models/index.js +18 -0
  126. package/dist/src/models/types.d.ts +40 -0
  127. package/dist/src/models/types.d.ts.map +1 -0
  128. package/dist/src/models/types.js +7 -0
  129. package/dist/src/navigation/hanzo-shell/AppSwitcher.d.ts +8 -0
  130. package/dist/src/navigation/hanzo-shell/AppSwitcher.d.ts.map +1 -0
  131. package/dist/src/navigation/hanzo-shell/AppSwitcher.js +19 -0
  132. package/dist/src/navigation/hanzo-shell/BeamAvatar.d.ts +9 -0
  133. package/dist/src/navigation/hanzo-shell/BeamAvatar.d.ts.map +1 -0
  134. package/dist/src/navigation/hanzo-shell/BeamAvatar.js +38 -0
  135. package/dist/src/navigation/hanzo-shell/HanzoCommandPalette.d.ts +28 -0
  136. package/dist/src/navigation/hanzo-shell/HanzoCommandPalette.d.ts.map +1 -0
  137. package/dist/src/navigation/hanzo-shell/HanzoCommandPalette.js +124 -0
  138. package/dist/src/navigation/hanzo-shell/HanzoHeader.d.ts +37 -0
  139. package/dist/src/navigation/hanzo-shell/HanzoHeader.d.ts.map +1 -0
  140. package/dist/src/navigation/hanzo-shell/HanzoHeader.js +43 -0
  141. package/dist/src/navigation/hanzo-shell/HanzoMark.d.ts +17 -0
  142. package/dist/src/navigation/hanzo-shell/HanzoMark.d.ts.map +1 -0
  143. package/dist/src/navigation/hanzo-shell/HanzoMark.js +57 -0
  144. package/dist/src/navigation/hanzo-shell/UserAvatar.d.ts +15 -0
  145. package/dist/src/navigation/hanzo-shell/UserAvatar.d.ts.map +1 -0
  146. package/dist/src/navigation/hanzo-shell/UserAvatar.js +44 -0
  147. package/dist/src/navigation/hanzo-shell/UserOrgDropdown.d.ts +11 -0
  148. package/dist/src/navigation/hanzo-shell/UserOrgDropdown.d.ts.map +1 -0
  149. package/dist/src/navigation/hanzo-shell/UserOrgDropdown.js +26 -0
  150. package/dist/src/navigation/hanzo-shell/index.d.ts +14 -0
  151. package/dist/src/navigation/hanzo-shell/index.d.ts.map +1 -0
  152. package/dist/src/navigation/hanzo-shell/index.js +9 -0
  153. package/dist/src/navigation/hanzo-shell/types.d.ts +49 -0
  154. package/dist/src/navigation/hanzo-shell/types.d.ts.map +1 -0
  155. package/dist/src/navigation/hanzo-shell/types.js +32 -0
  156. package/dist/src/navigation/hanzo-shell/useHanzoAuth.d.ts +16 -0
  157. package/dist/src/navigation/hanzo-shell/useHanzoAuth.d.ts.map +1 -0
  158. package/dist/src/navigation/hanzo-shell/useHanzoAuth.js +93 -0
  159. package/dist/src/navigation/index.d.ts +2 -0
  160. package/dist/src/navigation/index.d.ts.map +1 -1
  161. package/dist/src/navigation/index.js +2 -0
  162. package/dist/src/ui/banner.d.ts +1 -1
  163. package/dist/util/blob.d.ts.map +1 -1
  164. package/dist/util/blob.js +1 -1
  165. package/dist/util/date.d.ts.map +1 -1
  166. package/dist/util/debounce.d.ts.map +1 -1
  167. package/dist/util/file.d.ts.map +1 -1
  168. package/dist/util/format-and-abbreviate-as-currency.d.ts +7 -1
  169. package/dist/util/format-and-abbreviate-as-currency.d.ts.map +1 -1
  170. package/dist/util/format-text.d.ts.map +1 -1
  171. package/dist/util/format-to-max-char.d.ts +7 -1
  172. package/dist/util/format-to-max-char.d.ts.map +1 -1
  173. package/dist/util/index.mjs +1 -1
  174. package/dist/util/number-abbreviate.d.ts.map +1 -1
  175. package/dist/util/specifier.d.ts.map +1 -1
  176. package/dist/util/step-animation.d.ts.map +1 -1
  177. package/package.json +20 -7
  178. package/dist/tailwind/typo-plugin/get-plugin-styles.d.ts +0 -595
  179. package/dist/tailwind/typo-plugin/get-plugin-styles.d.ts.map +0 -1
  180. package/dist/tailwind/typo-plugin/get-plugin-styles.js +0 -661
  181. package/dist/tailwind/typo-plugin/index.d.ts +0 -3
  182. package/dist/tailwind/typo-plugin/index.d.ts.map +0 -1
  183. package/dist/tailwind/typo-plugin/index.js +0 -102
  184. package/dist/tailwind/typo-plugin/utils.d.ts +0 -6
  185. package/dist/tailwind/typo-plugin/utils.d.ts.map +0 -1
  186. package/dist/tailwind/typo-plugin/utils.js +0 -47
@@ -18,40 +18,53 @@ function formatDate(date) {
18
18
  const statusTone = {
19
19
  active: {
20
20
  label: 'Active',
21
- className: 'bg-emerald-50 text-emerald-700 ring-emerald-600/20',
21
+ className: 'bg-emerald-500/10 text-emerald-500 ring-emerald-500/20',
22
22
  },
23
23
  canceled: {
24
24
  label: 'Canceled',
25
- className: 'bg-rose-50 text-rose-700 ring-rose-600/20',
25
+ className: 'bg-rose-500/10 text-rose-500 ring-rose-500/20',
26
26
  },
27
27
  past_due: {
28
28
  label: 'Past due',
29
- className: 'bg-amber-50 text-amber-800 ring-amber-600/20',
29
+ className: 'bg-amber-500/10 text-amber-500 ring-amber-500/20',
30
30
  },
31
31
  trialing: {
32
32
  label: 'Trialing',
33
- className: 'bg-sky-50 text-sky-700 ring-sky-600/20',
33
+ className: 'bg-sky-500/10 text-sky-500 ring-sky-500/20',
34
34
  },
35
35
  unpaid: {
36
36
  label: 'Unpaid',
37
- className: 'bg-rose-50 text-rose-700 ring-rose-600/20',
37
+ className: 'bg-rose-500/10 text-rose-500 ring-rose-500/20',
38
38
  },
39
39
  incomplete: {
40
40
  label: 'Incomplete',
41
- className: 'bg-zinc-100 text-zinc-700 ring-zinc-600/20',
41
+ className: 'bg-text-dim/10 text-text-muted ring-text-dim/20',
42
42
  },
43
43
  };
44
44
  export function SubscriptionPortal(props) {
45
- const { subscription, availablePlans, usageMetrics = [], retentionOffers = [], subscriptionHistory = [], onUpgrade, onDowngrade, defaultView = 'overview', } = props;
45
+ const { subscription, availablePlans, usageMetrics = [], retentionOffers = [], subscriptionHistory = [], onUpgrade, onDowngrade, onCancel, onApplyDiscount, defaultView = 'overview', } = props;
46
46
  const [view, setView] = React.useState(defaultView);
47
47
  const [busyPlanId, setBusyPlanId] = React.useState(null);
48
+ // Discount code state
49
+ const [discountCode, setDiscountCode] = React.useState('');
50
+ const [applyingDiscount, setApplyingDiscount] = React.useState(false);
51
+ const [discountResult, setDiscountResult] = React.useState(null);
52
+ // Cancel confirmation
53
+ const [showCancelConfirm, setShowCancelConfirm] = React.useState(false);
54
+ const [cancelReason, setCancelReason] = React.useState('');
55
+ const [canceling, setCanceling] = React.useState(false);
48
56
  const currentPlan = React.useMemo(() => availablePlans.find((p) => p.id === subscription.planId), [availablePlans, subscription.planId]);
49
57
  const handlePlanChange = React.useCallback(async (plan) => {
50
- if (!currentPlan || plan.id === currentPlan.id)
58
+ // External / contact-sales plans
59
+ if (plan.externalUrl) {
60
+ window.open(plan.externalUrl, '_blank', 'noopener,noreferrer');
61
+ return;
62
+ }
63
+ if (plan.id === currentPlan?.id)
51
64
  return;
52
65
  setBusyPlanId(plan.id);
53
66
  try {
54
- if (plan.price >= currentPlan.price) {
67
+ if (!currentPlan || plan.price >= (currentPlan?.price ?? 0)) {
55
68
  await onUpgrade?.(plan.id);
56
69
  }
57
70
  else {
@@ -62,27 +75,111 @@ export function SubscriptionPortal(props) {
62
75
  setBusyPlanId(null);
63
76
  }
64
77
  }, [currentPlan, onDowngrade, onUpgrade]);
65
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "overflow-hidden rounded-2xl border border-zinc-200 bg-gradient-to-b from-zinc-50 to-white p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-[0.14em] text-zinc-500", children: "Subscription Control" }), _jsx("h2", { className: "text-2xl font-semibold text-zinc-900", children: currentPlan?.name ?? 'No active plan' }), _jsxs("p", { className: "text-sm text-zinc-600", children: ["Billing period ends on ", formatDate(subscription.currentPeriodEnd)] })] }), _jsx("span", { className: `inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold ring-1 ${statusTone[subscription.status].className}`, children: statusTone[subscription.status].label })] }), _jsxs("div", { className: "mt-6 flex flex-wrap gap-2 rounded-xl border border-zinc-200 bg-white p-1", children: [_jsx("button", { type: "button", onClick: () => setView('overview'), className: `rounded-lg px-3 py-1.5 text-sm transition ${view === 'overview'
66
- ? 'bg-zinc-900 text-white'
67
- : 'text-zinc-600 hover:bg-zinc-100'}`, children: "Overview" }), _jsx("button", { type: "button", onClick: () => setView('plans'), className: `rounded-lg px-3 py-1.5 text-sm transition ${view === 'plans'
68
- ? 'bg-zinc-900 text-white'
69
- : 'text-zinc-600 hover:bg-zinc-100'}`, children: "Plans" }), _jsx("button", { type: "button", onClick: () => setView('history'), className: `rounded-lg px-3 py-1.5 text-sm transition ${view === 'history'
70
- ? 'bg-zinc-900 text-white'
71
- : 'text-zinc-600 hover:bg-zinc-100'}`, children: "History" })] })] }), view === 'overview' && (_jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [_jsxs("div", { className: "rounded-xl border border-zinc-200 bg-white p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-zinc-500", children: "Current spend" }), _jsxs("p", { className: "mt-2 text-2xl font-semibold text-zinc-900", children: [formatCurrency(currentPlan?.price ?? 0), _jsxs("span", { className: "text-sm font-normal text-zinc-500", children: ["/", currentPlan?.billingPeriod ?? 'monthly'] })] })] }), _jsxs("div", { className: "rounded-xl border border-zinc-200 bg-white p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-zinc-500", children: "Next invoice" }), _jsx("p", { className: "mt-2 text-lg font-semibold text-zinc-900", children: formatDate(subscription.currentPeriodEnd) })] }), _jsxs("div", { className: "rounded-xl border border-zinc-200 bg-white p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-zinc-500", children: "Cancellation" }), _jsx("p", { className: "mt-2 text-lg font-semibold text-zinc-900", children: subscription.cancelAtPeriodEnd ? 'At period end' : 'Auto-renewing' })] }), usageMetrics.length > 0 && (_jsxs("div", { className: "md:col-span-3 rounded-xl border border-zinc-200 bg-white p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-zinc-500", children: "Usage metrics" }), _jsx("div", { className: "mt-3 grid gap-3 md:grid-cols-2 xl:grid-cols-4", children: usageMetrics.map((metric) => {
78
+ const handleApplyDiscount = React.useCallback(async () => {
79
+ if (!onApplyDiscount || !discountCode.trim())
80
+ return;
81
+ setApplyingDiscount(true);
82
+ setDiscountResult(null);
83
+ try {
84
+ const result = await onApplyDiscount(discountCode.trim());
85
+ if (result && result.valid) {
86
+ const desc = result.kind === 'percent'
87
+ ? `${result.value}% off`
88
+ : `${formatCurrency(result.value)} off`;
89
+ setDiscountResult({ success: true, message: `Applied: ${result.name || result.code} (${desc})` });
90
+ setDiscountCode('');
91
+ }
92
+ else {
93
+ setDiscountResult({ success: false, message: 'Invalid or expired discount code' });
94
+ }
95
+ }
96
+ catch (err) {
97
+ setDiscountResult({ success: false, message: err instanceof Error ? err.message : 'Failed to apply code' });
98
+ }
99
+ finally {
100
+ setApplyingDiscount(false);
101
+ }
102
+ }, [onApplyDiscount, discountCode]);
103
+ const handleCancel = React.useCallback(async () => {
104
+ if (!onCancel || !cancelReason.trim())
105
+ return;
106
+ setCanceling(true);
107
+ try {
108
+ await onCancel(cancelReason.trim());
109
+ setShowCancelConfirm(false);
110
+ setCancelReason('');
111
+ }
112
+ finally {
113
+ setCanceling(false);
114
+ }
115
+ }, [onCancel, cancelReason]);
116
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "overflow-hidden rounded-2xl border border-border bg-bg-card p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-[0.14em] text-text-muted", children: "Subscription" }), _jsx("h2", { className: "text-2xl font-semibold text-text", children: currentPlan?.name ?? 'No active plan' }), _jsx("p", { className: "text-sm text-text-secondary", children: subscription.id
117
+ ? `Billing period ends on ${formatDate(subscription.currentPeriodEnd)}`
118
+ : 'Select a plan to get started' }), subscription.discount && (_jsxs("p", { className: "text-sm text-emerald-500", children: ["Discount: ", subscription.discount.name || subscription.discount.code, " (", subscription.discount.kind === 'percent'
119
+ ? `${subscription.discount.value}% off`
120
+ : `${formatCurrency(subscription.discount.value)} off`, subscription.discount.duration === 'repeating' && subscription.discount.durationInMonths
121
+ ? ` for ${subscription.discount.durationInMonths} months`
122
+ : subscription.discount.duration === 'forever'
123
+ ? ' forever'
124
+ : '', ")"] }))] }), subscription.id && (_jsx("span", { className: `inline-flex items-center rounded-full px-3 py-1 text-xs font-semibold ring-1 ${statusTone[subscription.status].className}`, children: statusTone[subscription.status].label }))] }), _jsxs("div", { className: "mt-6 flex flex-wrap gap-2 rounded-xl border border-border bg-bg-elevated p-1", children: [_jsx("button", { type: "button", onClick: () => setView('overview'), className: `rounded-lg px-3 py-1.5 text-sm transition ${view === 'overview'
125
+ ? 'bg-text text-bg'
126
+ : 'text-text-muted hover:bg-bg-card hover:text-text'}`, children: "Overview" }), _jsx("button", { type: "button", onClick: () => setView('plans'), className: `rounded-lg px-3 py-1.5 text-sm transition ${view === 'plans'
127
+ ? 'bg-text text-bg'
128
+ : 'text-text-muted hover:bg-bg-card hover:text-text'}`, children: "Plans" }), _jsx("button", { type: "button", onClick: () => setView('history'), className: `rounded-lg px-3 py-1.5 text-sm transition ${view === 'history'
129
+ ? 'bg-text text-bg'
130
+ : 'text-text-muted hover:bg-bg-card hover:text-text'}`, children: "History" })] })] }), view === 'overview' && (_jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [_jsxs("div", { className: "rounded-xl border border-border bg-bg-card p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-text-muted", children: "Current spend" }), _jsxs("p", { className: "mt-2 text-2xl font-semibold text-text", children: [formatCurrency(currentPlan?.price ?? 0), _jsxs("span", { className: "text-sm font-normal text-text-muted", children: ["/", currentPlan?.billingPeriod ?? 'monthly'] })] })] }), _jsxs("div", { className: "rounded-xl border border-border bg-bg-card p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-text-muted", children: "Next invoice" }), _jsx("p", { className: "mt-2 text-lg font-semibold text-text", children: subscription.id ? formatDate(subscription.currentPeriodEnd) : '\u2014' })] }), _jsxs("div", { className: "rounded-xl border border-border bg-bg-card p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-text-muted", children: "Status" }), _jsx("p", { className: "mt-2 text-lg font-semibold text-text", children: subscription.cancelAtPeriodEnd
131
+ ? 'Cancels at period end'
132
+ : subscription.id
133
+ ? 'Auto-renewing'
134
+ : 'No subscription' })] }), onApplyDiscount && subscription.id && (_jsxs("div", { className: "md:col-span-3 rounded-xl border border-border bg-bg-card p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-text-muted", children: "Discount code" }), _jsxs("div", { className: "mt-3 flex gap-2", children: [_jsx("input", { value: discountCode, onChange: (e) => {
135
+ setDiscountCode(e.target.value.toUpperCase());
136
+ setDiscountResult(null);
137
+ }, placeholder: "Enter promo code", className: "flex-1 rounded-lg border border-border bg-bg-input px-3 py-2 text-sm font-mono uppercase text-text outline-none transition focus:border-brand" }), _jsx("button", { type: "button", disabled: !discountCode.trim() || applyingDiscount, onClick: handleApplyDiscount, className: "rounded-lg bg-text px-4 py-2 text-sm font-medium text-bg transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60", children: applyingDiscount ? 'Applying\u2026' : 'Apply' })] }), discountResult && (_jsx("p", { className: `mt-2 text-sm ${discountResult.success ? 'text-success' : 'text-danger'}`, children: discountResult.message }))] })), usageMetrics.length > 0 && (_jsxs("div", { className: "md:col-span-3 rounded-xl border border-border bg-bg-card p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-text-muted", children: "Usage metrics" }), _jsx("div", { className: "mt-3 grid gap-3 md:grid-cols-2 xl:grid-cols-4", children: usageMetrics.map((metric) => {
72
138
  const utilization = metric.limit && metric.limit > 0
73
139
  ? Math.min((metric.current / metric.limit) * 100, 100)
74
140
  : 0;
75
- return (_jsxs("div", { className: "rounded-lg border border-zinc-200 p-3", children: [_jsx("p", { className: "text-sm text-zinc-600", children: metric.label }), _jsxs("p", { className: "mt-1 text-lg font-semibold text-zinc-900", children: [metric.current.toLocaleString(), metric.unit ? ` ${metric.unit}` : ''] }), metric.limit ? (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-xs text-zinc-500", children: ["of ", metric.limit.toLocaleString(), " ", metric.unit ?? ''] }), _jsx("div", { className: "mt-2 h-2 rounded-full bg-zinc-100", children: _jsx("div", { className: "h-2 rounded-full bg-zinc-900", style: { width: `${utilization}%` } }) })] })) : null] }, metric.id));
76
- }) })] })), retentionOffers.length > 0 && (_jsxs("div", { className: "md:col-span-3 rounded-xl border border-amber-200 bg-amber-50/60 p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-amber-700", children: "Retention offers" }), _jsx("div", { className: "mt-3 grid gap-3 md:grid-cols-2", children: retentionOffers.map((offer) => (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-white p-3", children: [_jsx("p", { className: "font-medium text-zinc-900", children: offer.title }), _jsx("p", { className: "mt-1 text-sm text-zinc-600", children: offer.description }), _jsxs("p", { className: "mt-2 text-sm font-semibold text-amber-700", children: ["Save ", offer.discount, "% for ", offer.durationMonths, " months"] })] }, offer.id))) })] }))] })), view === 'plans' && (_jsx("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3", children: availablePlans.map((plan) => {
141
+ return (_jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsx("p", { className: "text-sm text-text-secondary", children: metric.label }), _jsxs("p", { className: "mt-1 text-lg font-semibold text-text", children: [metric.current.toLocaleString(), metric.unit ? ` ${metric.unit}` : ''] }), metric.limit ? (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-xs text-text-muted", children: ["of ", metric.limit.toLocaleString(), " ", metric.unit ?? ''] }), _jsx("div", { className: "mt-2 h-2 rounded-full bg-bg-elevated", children: _jsx("div", { className: "h-2 rounded-full bg-brand transition-all", style: { width: `${utilization}%` } }) })] })) : null] }, metric.id));
142
+ }) })] })), retentionOffers.length > 0 && (_jsxs("div", { className: "md:col-span-3 rounded-xl border border-warning/30 bg-warning/5 p-4", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-warning", children: "Retention offers" }), _jsx("div", { className: "mt-3 grid gap-3 md:grid-cols-2", children: retentionOffers.map((offer) => (_jsxs("div", { className: "rounded-lg border border-warning/30 bg-bg-card p-3", children: [_jsx("p", { className: "font-medium text-text", children: offer.title }), _jsx("p", { className: "mt-1 text-sm text-text-secondary", children: offer.description }), _jsxs("p", { className: "mt-2 text-sm font-semibold text-warning", children: ["Save ", offer.discount, "% for ", offer.durationMonths, " months"] })] }, offer.id))) })] })), subscription.id && subscription.status === 'active' && !subscription.cancelAtPeriodEnd && onCancel && (_jsx("div", { className: "md:col-span-3", children: showCancelConfirm ? (_jsxs("div", { className: "rounded-xl border border-rose-500/30 bg-rose-500/5 p-4", children: [_jsx("p", { className: "text-sm font-medium text-rose-400", children: "Cancel subscription" }), _jsxs("p", { className: "mt-1 text-sm text-text-muted", children: ["Your subscription will remain active until ", formatDate(subscription.currentPeriodEnd), "."] }), _jsx("textarea", { value: cancelReason, onChange: (e) => setCancelReason(e.target.value), placeholder: "Why are you canceling? (required)", rows: 2, className: "mt-3 w-full rounded-lg border border-rose-500/30 bg-bg-input px-3 py-2 text-sm text-text outline-none transition focus:border-rose-500" }), _jsxs("div", { className: "mt-3 flex gap-2", children: [_jsx("button", { type: "button", disabled: !cancelReason.trim() || canceling, onClick: handleCancel, className: "rounded-lg bg-rose-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-rose-500 disabled:cursor-not-allowed disabled:opacity-60", children: canceling ? 'Canceling\u2026' : 'Confirm cancellation' }), _jsx("button", { type: "button", onClick: () => {
143
+ setShowCancelConfirm(false);
144
+ setCancelReason('');
145
+ }, className: "rounded-lg border border-border px-4 py-2 text-sm font-medium text-text-secondary transition hover:bg-bg-elevated", children: "Keep subscription" })] })] })) : (_jsx("button", { type: "button", onClick: () => setShowCancelConfirm(true), className: "text-sm text-text-muted transition hover:text-rose-500", children: "Cancel subscription" })) }))] })), view === 'plans' && (_jsx("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4", children: availablePlans.map((plan) => {
77
146
  const isCurrent = plan.id === subscription.planId;
78
147
  const isBusy = busyPlanId === plan.id;
79
- const periodPrice = `${formatCurrency(plan.price)}/${plan.billingPeriod}`;
80
- return (_jsxs("div", { className: `rounded-xl border p-5 ${isCurrent
81
- ? 'border-zinc-900 bg-zinc-900 text-white'
82
- : 'border-zinc-200 bg-white text-zinc-900'}`, children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("p", { className: "text-lg font-semibold", children: plan.name }), _jsx("p", { className: `text-sm ${isCurrent ? 'text-zinc-300' : 'text-zinc-600'}`, children: plan.description })] }), plan.badge ? (_jsx("span", { className: `rounded-full px-2 py-1 text-xs font-semibold ${isCurrent ? 'bg-zinc-700 text-zinc-100' : 'bg-zinc-100 text-zinc-700'}`, children: plan.badge })) : null] }), _jsx("p", { className: "mt-4 text-2xl font-semibold", children: periodPrice }), _jsx("ul", { className: `mt-4 space-y-2 text-sm ${isCurrent ? 'text-zinc-200' : 'text-zinc-700'}`, children: plan.features.map((feature) => (_jsxs("li", { children: ["\u2022 ", feature] }, feature))) }), _jsx("button", { type: "button", disabled: isCurrent || isBusy, onClick: () => handlePlanChange(plan), className: `mt-5 w-full rounded-lg px-3 py-2 text-sm font-medium transition ${isCurrent
83
- ? 'cursor-not-allowed bg-zinc-700 text-zinc-300'
148
+ const isFree = plan.price === 0;
149
+ const hasTrial = (plan.trialDays ?? 0) > 0;
150
+ const isExternal = !!plan.externalUrl || plan.contactSales;
151
+ const periodPrice = isFree ? 'Free' : `$${plan.price}/mo`;
152
+ const buttonLabel = isCurrent
153
+ ? 'Current plan'
154
+ : isBusy
155
+ ? 'Applying\u2026'
156
+ : isExternal
157
+ ? plan.contactSales ? 'Contact Sales \u2192' : 'Get started \u2192'
158
+ : hasTrial && !subscription.id
159
+ ? `Start ${plan.trialDays}-day free trial`
160
+ : currentPlan && plan.price < (currentPlan?.price ?? 0)
161
+ ? 'Downgrade'
162
+ : 'Upgrade';
163
+ return (_jsxs("div", { className: `relative flex flex-col rounded-2xl border p-5 transition ${plan.highlighted && !isCurrent
164
+ ? 'border-white/[0.2] bg-[#141419]'
165
+ : isCurrent
166
+ ? 'border-white/[0.25] bg-[#141419]'
167
+ : 'border-white/[0.08] bg-[#141419]'}`, children: [plan.highlighted && !isCurrent && (_jsx("div", { className: "absolute -top-px left-0 right-0 h-px rounded-t-2xl bg-gradient-to-r from-transparent via-white/40 to-transparent" })), _jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("p", { className: "text-[15px] font-semibold text-white", children: plan.name }), plan.badge ? (_jsx("span", { className: `rounded-full px-2 py-1 text-[11px] font-semibold flex-shrink-0 ${isCurrent
168
+ ? 'bg-white/10 text-white/60'
169
+ : plan.contactSales
170
+ ? 'bg-amber-500/15 text-amber-400'
171
+ : hasTrial
172
+ ? 'bg-sky-500/15 text-sky-400'
173
+ : 'bg-white/[0.07] text-white/40'}`, children: plan.badge })) : null] }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-2xl font-bold text-white", children: [isExternal && !isFree ? `$${formatCurrency(plan.price).replace('$', '').split('.')[0]}` : periodPrice, !isFree && !isExternal && _jsx("span", { className: "text-sm font-normal text-white/40", children: "/mo" }), isExternal && !isFree && _jsx("span", { className: "text-sm font-normal text-white/40", children: "/mo+" })] }), plan.annualPrice && !isCurrent && (_jsxs("p", { className: "mt-0.5 text-[12px] text-emerald-400", children: ["$", plan.annualPrice, "/yr ", '\u2014', " save $", plan.price * 12 - plan.annualPrice] })), (plan.trialCreditCents ?? 0) > 0 && !isCurrent && (_jsxs("p", { className: "mt-0.5 text-[12px] text-emerald-400", children: ["+$", (plan.trialCreditCents / 100).toFixed(0), " credit included"] }))] }), _jsx("p", { className: "mt-3 text-[13px] leading-relaxed text-white/40", children: plan.description }), _jsx("ul", { className: "mt-4 flex-1 space-y-1.5", children: plan.features.map((feature) => (_jsxs("li", { className: "flex items-start gap-2 text-[13px] text-white/50", children: [_jsx("span", { className: "mt-0.5 flex-shrink-0 text-white/30", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M20 6L9 17l-5-5" }) }) }), feature] }, feature))) }), _jsx("button", { type: "button", disabled: isCurrent || isBusy, onClick: () => handlePlanChange(plan), className: `mt-5 w-full rounded-xl px-3 py-2.5 text-sm font-semibold transition active:scale-[0.98] ${isCurrent
174
+ ? 'cursor-not-allowed bg-white/[0.06] text-white/30'
84
175
  : isBusy
85
- ? 'cursor-wait bg-zinc-300 text-zinc-600'
86
- : 'bg-zinc-900 text-white hover:bg-zinc-700'}`, children: isCurrent ? 'Current plan' : isBusy ? 'Applying…' : 'Switch plan' })] }, plan.id));
87
- }) })), view === 'history' && (_jsxs("div", { className: "rounded-xl border border-zinc-200 bg-white", children: [_jsx("div", { className: "border-b border-zinc-200 px-4 py-3", children: _jsx("h3", { className: "text-sm font-semibold text-zinc-900", children: "Subscription timeline" }) }), subscriptionHistory.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-zinc-500", children: "No history entries yet." })) : (_jsx("div", { className: "divide-y divide-zinc-200", children: subscriptionHistory.map((entry) => (_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2 px-4 py-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-900", children: entry.action }), _jsx("p", { className: "text-sm text-zinc-600", children: entry.details }), entry.fromPlan || entry.toPlan ? (_jsxs("p", { className: "text-xs text-zinc-500", children: [entry.fromPlan ?? '—', " \u2192 ", entry.toPlan ?? '—'] })) : null] }), _jsx("p", { className: "text-xs text-zinc-500", children: formatDate(entry.date) })] }, entry.id))) }))] }))] }));
176
+ ? 'cursor-wait bg-white/[0.06] text-white/30'
177
+ : plan.highlighted
178
+ ? 'bg-white text-black hover:bg-white/90'
179
+ : isExternal
180
+ ? 'bg-white/[0.08] text-white/70 hover:bg-white/[0.12] hover:text-white'
181
+ : hasTrial && !subscription.id
182
+ ? 'bg-sky-500/20 text-sky-300 hover:bg-sky-500/30'
183
+ : 'bg-white/[0.08] text-white/70 hover:bg-white/[0.12] hover:text-white'}`, children: buttonLabel })] }, plan.id));
184
+ }) })), view === 'history' && (_jsxs("div", { className: "rounded-xl border border-border bg-bg-card", children: [_jsx("div", { className: "border-b border-border px-4 py-3", children: _jsx("h3", { className: "text-sm font-semibold text-text", children: "Subscription timeline" }) }), subscriptionHistory.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-text-muted", children: "No history entries yet." })) : (_jsx("div", { className: "divide-y divide-border", children: subscriptionHistory.map((entry) => (_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2 px-4 py-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-text", children: entry.action }), _jsx("p", { className: "text-sm text-text-secondary", children: entry.details }), entry.fromPlan || entry.toPlan ? (_jsxs("p", { className: "text-xs text-text-muted", children: [entry.fromPlan ?? '\u2014', " ", '\u2192', " ", entry.toPlan ?? '\u2014'] })) : null] }), _jsx("p", { className: "text-xs text-text-muted", children: formatDate(entry.date) })] }, entry.id))) }))] }))] }));
88
185
  }
@@ -0,0 +1,6 @@
1
+ export interface SupportTiersPanelProps {
2
+ currentTier?: string;
3
+ onSubscribe?: (tierId: string) => Promise<void>;
4
+ }
5
+ export declare function SupportTiersPanel({ currentTier, onSubscribe }: SupportTiersPanelProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=support-tiers-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"support-tiers-panel.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/support-tiers-panel.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAsDD,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,sBAAsB,2CA6FrF"}
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ const SUPPORT_TIERS = [
5
+ {
6
+ id: 'community',
7
+ name: 'Community',
8
+ price: 0,
9
+ billingPeriod: 'monthly',
10
+ features: [
11
+ 'Community forum access',
12
+ 'Public documentation',
13
+ 'GitHub issues',
14
+ '72-hour response SLA',
15
+ ],
16
+ },
17
+ {
18
+ id: 'priority',
19
+ name: 'Priority',
20
+ price: 99,
21
+ billingPeriod: 'monthly',
22
+ highlighted: true,
23
+ features: [
24
+ 'Email support',
25
+ 'Dedicated support channel',
26
+ '24-hour response SLA',
27
+ 'Priority bug fixes',
28
+ 'Quarterly architecture review',
29
+ ],
30
+ },
31
+ {
32
+ id: 'enterprise',
33
+ name: 'Enterprise',
34
+ price: 499,
35
+ billingPeriod: 'monthly',
36
+ features: [
37
+ 'Dedicated support engineer',
38
+ 'Slack/Teams integration',
39
+ '4-hour response SLA',
40
+ 'Custom SLAs available',
41
+ 'Onboarding assistance',
42
+ 'Monthly strategy calls',
43
+ ],
44
+ },
45
+ ];
46
+ function formatCurrency(value) {
47
+ if (value === 0)
48
+ return 'Free';
49
+ return new Intl.NumberFormat('en-US', {
50
+ style: 'currency',
51
+ currency: 'USD',
52
+ maximumFractionDigits: 0,
53
+ }).format(value);
54
+ }
55
+ export function SupportTiersPanel({ currentTier, onSubscribe }) {
56
+ const [busyTierId, setBusyTierId] = React.useState(null);
57
+ const handleSubscribe = React.useCallback(async (tierId) => {
58
+ if (!onSubscribe)
59
+ return;
60
+ setBusyTierId(tierId);
61
+ try {
62
+ await onSubscribe(tierId);
63
+ }
64
+ finally {
65
+ setBusyTierId(null);
66
+ }
67
+ }, [onSubscribe]);
68
+ const activeTier = currentTier ?? 'community';
69
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "overflow-hidden rounded-xl border border-border bg-bg-card", children: _jsxs("div", { className: "border-b border-border p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Support plans" }), _jsx("p", { className: "text-sm text-text-muted", children: "Choose the level of support that fits your needs. Upgrade anytime." })] }) }), _jsx("div", { className: "grid gap-4 md:grid-cols-3", children: SUPPORT_TIERS.map((tier) => {
70
+ const isCurrent = tier.id === activeTier;
71
+ const isBusy = busyTierId === tier.id;
72
+ return (_jsxs("div", { className: `relative rounded-xl border p-5 transition ${tier.highlighted && !isCurrent
73
+ ? 'border-2 border-brand bg-bg-card'
74
+ : isCurrent
75
+ ? 'border-brand bg-brand/10'
76
+ : 'border-border bg-bg-card'}`, children: [tier.highlighted && !isCurrent && (_jsx("span", { className: "absolute -top-3 left-4 rounded-full bg-brand px-3 py-0.5 text-xs font-semibold text-brand-foreground", children: "Recommended" })), _jsx("p", { className: "text-lg font-semibold text-text", children: tier.name }), _jsxs("p", { className: "mt-2 text-2xl font-bold text-text", children: [formatCurrency(tier.price), tier.price > 0 && (_jsxs("span", { className: "text-sm font-normal text-text-muted", children: ["/", tier.billingPeriod] }))] }), _jsx("ul", { className: "mt-4 space-y-2 text-sm text-text-secondary", children: tier.features.map((feature) => (_jsxs("li", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 text-emerald-500", children: '\u2713' }), feature] }, feature))) }), _jsx("button", { type: "button", disabled: isCurrent || isBusy || !onSubscribe, onClick: () => handleSubscribe(tier.id), className: `mt-5 w-full rounded-lg px-3 py-2 text-sm font-medium transition ${isCurrent
77
+ ? 'cursor-not-allowed bg-bg-elevated text-text-dim'
78
+ : isBusy
79
+ ? 'cursor-wait bg-bg-elevated text-text-muted'
80
+ : tier.highlighted
81
+ ? 'bg-brand text-brand-foreground hover:bg-brand-hover active:scale-[0.97]'
82
+ : 'bg-text text-bg hover:opacity-80'}`, children: isCurrent
83
+ ? 'Current plan'
84
+ : isBusy
85
+ ? 'Subscribing...'
86
+ : tier.price === 0
87
+ ? 'Downgrade'
88
+ : 'Upgrade' })] }, tier.id));
89
+ }) })] }));
90
+ }
@@ -4,19 +4,19 @@ import * as React from 'react';
4
4
  const complianceTone = {
5
5
  complete: {
6
6
  label: 'Complete',
7
- className: 'bg-emerald-50 text-emerald-700 ring-emerald-600/20',
7
+ className: 'bg-emerald-500/10 text-emerald-500 ring-emerald-500/20',
8
8
  },
9
9
  required: {
10
10
  label: 'Required',
11
- className: 'bg-rose-50 text-rose-700 ring-rose-600/20',
11
+ className: 'bg-rose-500/10 text-rose-500 ring-rose-500/20',
12
12
  },
13
13
  review: {
14
14
  label: 'Review',
15
- className: 'bg-amber-50 text-amber-700 ring-amber-600/20',
15
+ className: 'bg-amber-500/10 text-amber-500 ring-amber-500/20',
16
16
  },
17
17
  blocked: {
18
18
  label: 'Blocked',
19
- className: 'bg-zinc-100 text-zinc-700 ring-zinc-600/20',
19
+ className: 'bg-text-dim/10 text-text-muted ring-text-dim/20',
20
20
  },
21
21
  };
22
22
  export function TaxCompliancePanel(props) {
@@ -37,5 +37,5 @@ export function TaxCompliancePanel(props) {
37
37
  setSaving(false);
38
38
  }
39
39
  }, [automaticTax, onToggleAutomaticTax]);
40
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "grid gap-4 xl:grid-cols-5", children: [_jsxs("div", { className: "xl:col-span-2 overflow-hidden rounded-xl border border-zinc-200 bg-white", children: [_jsxs("div", { className: "border-b border-zinc-200 p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-zinc-900", children: "Tax settings" }), _jsx("p", { className: "text-sm text-zinc-500", children: "Manage registrations, nexus, and tax collection behavior" })] }), _jsxs("div", { className: "space-y-4 p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 rounded-lg border border-zinc-200 p-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-900", children: "Automatic tax" }), _jsx("p", { className: "text-xs text-zinc-500", children: "Calculate and collect tax where required" })] }), _jsx("button", { type: "button", onClick: handleToggle, disabled: saving, className: `relative inline-flex h-6 w-11 items-center rounded-full transition ${automaticTax ? 'bg-zinc-900' : 'bg-zinc-300'}`, children: _jsx("span", { className: `inline-block h-4 w-4 transform rounded-full bg-white transition ${automaticTax ? 'translate-x-6' : 'translate-x-1'}` }) })] }), _jsxs("div", { children: [_jsx("p", { className: "mb-2 text-sm font-medium text-zinc-900", children: "Registrations" }), _jsx("div", { className: "space-y-2", children: taxSettings.registrations.length === 0 ? (_jsx("p", { className: "rounded-lg border border-dashed border-zinc-300 p-3 text-sm text-zinc-500", children: "No tax registrations configured." })) : (taxSettings.registrations.map((registration) => (_jsxs("div", { className: "rounded-lg border border-zinc-200 p-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: "text-sm font-medium text-zinc-900", children: registration.region }), _jsx("span", { className: "rounded-full bg-zinc-100 px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-zinc-700", children: registration.status })] }), _jsxs("p", { className: "mt-1 text-xs text-zinc-500", children: [registration.type.toUpperCase(), " \u00B7 ", registration.registrationId] })] }, registration.id)))) })] })] })] }), _jsxs("div", { className: "xl:col-span-3 overflow-hidden rounded-xl border border-zinc-200 bg-white", children: [_jsxs("div", { className: "border-b border-zinc-200 p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-zinc-900", children: "Compliance checklist" }), _jsx("p", { className: "text-sm text-zinc-500", children: "Track KYC/KYB, tax filings, and policy requirements" })] }), _jsx("div", { className: "divide-y divide-zinc-200", children: complianceItems.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-zinc-500", children: "No compliance tasks yet." })) : (complianceItems.map((item) => (_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3 p-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-900", children: item.title }), _jsx("p", { className: "mt-1 text-sm text-zinc-600", children: item.description }), _jsxs("p", { className: "mt-1 text-xs text-zinc-500", children: ["Category: ", item.category.toUpperCase(), item.owner ? ` · Owner: ${item.owner}` : ''] })] }), _jsxs("div", { className: "flex flex-col items-end gap-2", children: [_jsx("span", { className: `inline-flex rounded-full px-2 py-1 text-xs font-semibold ring-1 ${complianceTone[item.status].className}`, children: complianceTone[item.status].label }), item.actionLabel ? (_jsx("button", { type: "button", className: "rounded-md border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 transition hover:bg-zinc-100", children: item.actionLabel })) : null] })] }, item.id)))) })] })] }) }));
40
+ return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "grid gap-4 xl:grid-cols-5", children: [_jsxs("div", { className: "xl:col-span-2 overflow-hidden rounded-xl border border-border bg-bg-card", children: [_jsxs("div", { className: "border-b border-border p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Tax settings" }), _jsx("p", { className: "text-sm text-text-muted", children: "Manage registrations, nexus, and tax collection behavior" })] }), _jsxs("div", { className: "space-y-4 p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 rounded-lg border border-border p-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-text", children: "Automatic tax" }), _jsx("p", { className: "text-xs text-text-muted", children: "Calculate and collect tax where required" })] }), _jsx("button", { type: "button", onClick: handleToggle, disabled: saving, className: `relative inline-flex h-6 w-11 items-center rounded-full transition ${automaticTax ? 'bg-brand' : 'bg-border-strong'}`, children: _jsx("span", { className: `inline-block h-4 w-4 transform rounded-full bg-white transition ${automaticTax ? 'translate-x-6' : 'translate-x-1'}` }) })] }), _jsxs("div", { children: [_jsx("p", { className: "mb-2 text-sm font-medium text-text", children: "Registrations" }), _jsx("div", { className: "space-y-2", children: taxSettings.registrations.length === 0 ? (_jsx("p", { className: "rounded-lg border border-dashed border-border p-3 text-sm text-text-muted", children: "No tax registrations configured." })) : (taxSettings.registrations.map((registration) => (_jsxs("div", { className: "rounded-lg border border-border p-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: "text-sm font-medium text-text", children: registration.region }), _jsx("span", { className: "rounded-full bg-bg-elevated px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-text-secondary", children: registration.status })] }), _jsxs("p", { className: "mt-1 text-xs text-text-muted", children: [registration.type.toUpperCase(), " ", '\u00b7', " ", registration.registrationId] })] }, registration.id)))) })] })] })] }), _jsxs("div", { className: "xl:col-span-3 overflow-hidden rounded-xl border border-border bg-bg-card", children: [_jsxs("div", { className: "border-b border-border p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Compliance checklist" }), _jsx("p", { className: "text-sm text-text-muted", children: "Track KYC/KYB, tax filings, and policy requirements" })] }), _jsx("div", { className: "divide-y divide-border", children: complianceItems.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-text-muted", children: "No compliance tasks yet." })) : (complianceItems.map((item) => (_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3 p-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-text", children: item.title }), _jsx("p", { className: "mt-1 text-sm text-text-secondary", children: item.description }), _jsxs("p", { className: "mt-1 text-xs text-text-muted", children: ["Category: ", item.category.toUpperCase(), item.owner ? ` \u00b7 Owner: ${item.owner}` : ''] })] }), _jsxs("div", { className: "flex flex-col items-end gap-2", children: [_jsx("span", { className: `inline-flex rounded-full px-2 py-1 text-xs font-semibold ring-1 ${complianceTone[item.status].className}`, children: complianceTone[item.status].label }), item.actionLabel ? (_jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-xs font-medium text-text-secondary transition hover:bg-bg-elevated", children: item.actionLabel })) : null] })] }, item.id)))) })] })] }) }));
41
41
  }
@@ -0,0 +1,8 @@
1
+ import type { TransactionRecord } from '../types';
2
+ export interface TransactionsPanelProps {
3
+ transactions: TransactionRecord[];
4
+ loading?: boolean;
5
+ pageSize?: number;
6
+ }
7
+ export declare function TransactionsPanel({ transactions, loading, pageSize }: TransactionsPanelProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=transactions-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions-panel.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/transactions-panel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,UAAU,CAAA;AAElE,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAqCD,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,OAAe,EAAE,QAAa,EAAE,EAAE,sBAAsB,2CAqIzG"}
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ function formatCurrency(value, currency = 'USD') {
5
+ return new Intl.NumberFormat('en-US', {
6
+ style: 'currency',
7
+ currency,
8
+ minimumFractionDigits: 2,
9
+ }).format(value);
10
+ }
11
+ function formatDate(dateStr) {
12
+ return new Intl.DateTimeFormat('en-US', {
13
+ month: 'short',
14
+ day: 'numeric',
15
+ year: 'numeric',
16
+ hour: 'numeric',
17
+ minute: '2-digit',
18
+ }).format(new Date(dateStr));
19
+ }
20
+ const TYPE_LABELS = {
21
+ deposit: { label: 'Deposit', color: 'text-emerald-500' },
22
+ withdraw: { label: 'Withdrawal', color: 'text-rose-500' },
23
+ hold: { label: 'Hold', color: 'text-amber-500' },
24
+ 'hold-removed': { label: 'Hold Released', color: 'text-sky-500' },
25
+ transfer: { label: 'Transfer', color: 'text-violet-500' },
26
+ };
27
+ const typeFilterOptions = [
28
+ 'all',
29
+ 'deposit',
30
+ 'withdraw',
31
+ 'hold',
32
+ 'hold-removed',
33
+ 'transfer',
34
+ ];
35
+ export function TransactionsPanel({ transactions, loading = false, pageSize = 20 }) {
36
+ const [typeFilter, setTypeFilter] = React.useState('all');
37
+ const [page, setPage] = React.useState(1);
38
+ const filtered = React.useMemo(() => {
39
+ if (typeFilter === 'all')
40
+ return transactions;
41
+ return transactions.filter((t) => t.type === typeFilter);
42
+ }, [transactions, typeFilter]);
43
+ const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
44
+ const pageRows = React.useMemo(() => {
45
+ const safePage = Math.min(page, totalPages);
46
+ const start = (safePage - 1) * pageSize;
47
+ return filtered.slice(start, start + pageSize);
48
+ }, [filtered, page, pageSize, totalPages]);
49
+ React.useEffect(() => {
50
+ if (page > totalPages)
51
+ setPage(totalPages);
52
+ }, [page, totalPages]);
53
+ if (loading) {
54
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-bg-card p-8 text-center", children: [_jsx("div", { className: "mx-auto h-6 w-6 animate-spin rounded-full border-2 border-text-dim border-t-brand" }), _jsx("p", { className: "mt-3 text-sm text-text-muted", children: "Loading transactions..." })] }));
55
+ }
56
+ return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-bg-card", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b border-border p-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Transactions" }), _jsxs("p", { className: "text-sm text-text-muted", children: [filtered.length, " transaction(s)"] })] }), _jsx("select", { value: typeFilter, onChange: (e) => {
57
+ setTypeFilter(e.target.value);
58
+ setPage(1);
59
+ }, className: "rounded-lg border border-border bg-bg-input px-3 py-2 text-sm text-text-secondary outline-none transition focus:border-brand", children: typeFilterOptions.map((t) => (_jsx("option", { value: t, children: t === 'all' ? 'All types' : TYPE_LABELS[t].label }, t))) })] }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[640px]", children: [_jsx("thead", { className: "bg-bg-elevated", children: _jsxs("tr", { children: [_jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-muted", children: "Date" }), _jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-muted", children: "Type" }), _jsx("th", { className: "px-4 py-3 text-right text-xs font-semibold uppercase tracking-wide text-text-muted", children: "Amount" }), _jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-muted", children: "Description" }), _jsx("th", { className: "px-4 py-3 text-right text-xs font-semibold uppercase tracking-wide text-text-muted", children: "Balance" })] }) }), _jsx("tbody", { className: "divide-y divide-border", children: pageRows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: 5, className: "px-4 py-10 text-center text-sm text-text-muted", children: "No transactions found." }) })) : (pageRows.map((tx) => {
60
+ const typeInfo = TYPE_LABELS[tx.type] ?? { label: tx.type, color: 'text-text-muted' };
61
+ const isPositive = tx.type === 'deposit' || tx.type === 'hold-removed';
62
+ return (_jsxs("tr", { children: [_jsx("td", { className: "px-4 py-3 text-sm text-text-secondary", children: formatDate(tx.createdAt) }), _jsx("td", { className: "px-4 py-3 text-sm", children: _jsx("span", { className: `font-medium ${typeInfo.color}`, children: typeInfo.label }) }), _jsxs("td", { className: `px-4 py-3 text-right text-sm font-medium ${isPositive ? 'text-emerald-500' : 'text-rose-500'}`, children: [isPositive ? '+' : '-', formatCurrency(Math.abs(tx.amountCents) / 100, tx.currency)] }), _jsx("td", { className: "px-4 py-3 text-sm text-text-muted", children: tx.description || '\u2014' }), _jsx("td", { className: "px-4 py-3 text-right text-sm text-text-secondary", children: tx.balanceAfter != null ? formatCurrency(tx.balanceAfter / 100, tx.currency) : '\u2014' })] }, tx.id));
63
+ })) })] }) }), totalPages > 1 && (_jsxs("div", { className: "flex items-center justify-between border-t border-border p-4", children: [_jsxs("p", { className: "text-xs text-text-muted", children: ["Page ", Math.min(page, totalPages), " of ", totalPages] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1, className: "rounded-md border border-border px-3 py-1.5 text-xs font-medium text-text-secondary transition hover:bg-bg-elevated disabled:cursor-not-allowed disabled:opacity-60", children: "Previous" }), _jsx("button", { type: "button", onClick: () => setPage((p) => Math.min(totalPages, p + 1)), disabled: page >= totalPages, className: "rounded-md border border-border px-3 py-1.5 text-xs font-medium text-text-secondary transition hover:bg-bg-elevated disabled:cursor-not-allowed disabled:opacity-60", children: "Next" })] })] }))] }) }));
64
+ }
@@ -0,0 +1,7 @@
1
+ import type { UsageSummary } from '../types';
2
+ export interface UsagePanelProps {
3
+ usage: UsageSummary | null;
4
+ loading?: boolean;
5
+ }
6
+ export declare function UsagePanel(props: UsagePanelProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=usage-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-panel.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/usage-panel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,UAAU,CAAA;AAEzD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAwGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAoDhD"}
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ function formatCurrency(value, currency = 'USD') {
4
+ return new Intl.NumberFormat('en-US', {
5
+ style: 'currency',
6
+ currency,
7
+ minimumFractionDigits: 2,
8
+ maximumFractionDigits: 2,
9
+ }).format(value);
10
+ }
11
+ function formatNumber(value) {
12
+ if (value >= 1000000)
13
+ return `${(value / 1000000).toFixed(1)}M`;
14
+ if (value >= 1000)
15
+ return `${(value / 1000).toFixed(1)}k`;
16
+ return value.toLocaleString();
17
+ }
18
+ function formatDate(dateStr) {
19
+ return new Intl.DateTimeFormat('en-US', {
20
+ month: 'short',
21
+ day: 'numeric',
22
+ }).format(new Date(dateStr));
23
+ }
24
+ const METER_ICONS = {
25
+ ai: '\u{1F916}',
26
+ storage: '\u{1F4BE}',
27
+ network: '\u{1F310}',
28
+ network_egress: '\u{1F4E4}',
29
+ gpu: '\u26A1',
30
+ api_calls: '\u{1F4E1}',
31
+ };
32
+ const METER_COLORS = {
33
+ ai: 'bg-violet-500',
34
+ storage: 'bg-blue-500',
35
+ network: 'bg-emerald-500',
36
+ network_egress: 'bg-teal-500',
37
+ gpu: 'bg-amber-500',
38
+ api_calls: 'bg-indigo-500',
39
+ };
40
+ function UsageMeter({ record }) {
41
+ const utilization = record.limit && record.limit > 0
42
+ ? Math.min((record.current / record.limit) * 100, 100)
43
+ : null;
44
+ const icon = METER_ICONS[record.meterId] ?? '\u{1F4CA}';
45
+ const barColor = METER_COLORS[record.meterId] ?? 'bg-brand';
46
+ const isOverage = utilization !== null && utilization >= 100;
47
+ const isWarning = utilization !== null && utilization >= 80 && utilization < 100;
48
+ return (_jsxs("div", { className: "rounded-lg border border-border bg-bg-card p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-lg", children: icon }), _jsx("p", { className: "text-sm font-medium text-text", children: record.label })] }), record.cost > 0 && (_jsx("span", { className: "rounded-full bg-bg-elevated px-2 py-0.5 text-xs font-medium text-text-secondary", children: formatCurrency(record.cost) }))] }), _jsxs("div", { className: "mt-3", children: [_jsxs("div", { className: "flex items-end justify-between", children: [_jsxs("p", { className: "text-2xl font-semibold text-text", children: [formatNumber(record.current), _jsx("span", { className: "ml-1 text-sm font-normal text-text-muted", children: record.unit })] }), record.limit && record.limit > 0 && (_jsxs("p", { className: "text-xs text-text-muted", children: ["of ", formatNumber(record.limit), " ", record.unit] }))] }), utilization !== null && (_jsxs("div", { className: "mt-2", children: [_jsx("div", { className: "h-2 rounded-full bg-bg-elevated", children: _jsx("div", { className: `h-2 rounded-full transition-all ${isOverage ? 'bg-danger' : isWarning ? 'bg-warning' : barColor}`, style: { width: `${Math.min(utilization, 100)}%` } }) }), _jsx("p", { className: `mt-1 text-xs ${isOverage ? 'text-danger font-medium' : isWarning ? 'text-warning' : 'text-text-muted'}`, children: isOverage
49
+ ? `${(utilization - 100).toFixed(0)}% over limit`
50
+ : `${utilization.toFixed(0)}% used` })] }))] })] }));
51
+ }
52
+ export function UsagePanel(props) {
53
+ const { usage, loading = false } = props;
54
+ if (loading) {
55
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-bg-card p-8 text-center", children: [_jsx("div", { className: "mx-auto h-6 w-6 animate-spin rounded-full border-2 border-text-dim border-t-brand" }), _jsx("p", { className: "mt-3 text-sm text-text-muted", children: "Loading usage data..." })] }));
56
+ }
57
+ if (!usage || usage.records.length === 0) {
58
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-bg-card", children: [_jsxs("div", { className: "border-b border-border p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Usage" }), _jsx("p", { className: "text-sm text-text-muted", children: "Current billing period resource consumption" })] }), _jsx("div", { className: "p-6 text-sm text-text-muted", children: "No usage data available for this period." })] }));
59
+ }
60
+ const periodLabel = usage.period.start && usage.period.end
61
+ ? `${formatDate(usage.period.start)} \u2013 ${formatDate(usage.period.end)}`
62
+ : 'Current period';
63
+ return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-bg-card", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3 border-b border-border p-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Usage" }), _jsx("p", { className: "text-sm text-text-muted", children: periodLabel })] }), _jsxs("div", { className: "text-right", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-text-muted", children: "Total cost" }), _jsx("p", { className: "mt-1 text-xl font-semibold text-text", children: formatCurrency(usage.totalCost, usage.currency) })] })] }), _jsx("div", { className: "grid gap-4 p-4 md:grid-cols-2 xl:grid-cols-3", children: usage.records.map((record) => (_jsx(UsageMeter, { record: record }, record.meterId))) })] }) }));
64
+ }