@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
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { useEffect, useRef } from 'react';
5
+ function fmt(amount, currency = 'usd') {
6
+ return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);
7
+ }
8
+ function fmtTokens(n) {
9
+ if (n >= 1000000000)
10
+ return `${(n / 1000000000).toFixed(1)}B`;
11
+ if (n >= 1000000)
12
+ return `${(n / 1000000).toFixed(1)}M`;
13
+ if (n >= 1000)
14
+ return `${(n / 1000).toFixed(0)}K`;
15
+ return String(n);
16
+ }
17
+ function daysUntil(d) {
18
+ const diff = new Date(d).getTime() - Date.now();
19
+ return Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24)));
20
+ }
21
+ /* -- Fade-in animation wrapper -- */
22
+ function FadeIn({ children, delay = 0, className = '' }) {
23
+ const ref = useRef(null);
24
+ useEffect(() => {
25
+ const el = ref.current;
26
+ if (!el)
27
+ return;
28
+ el.style.opacity = '0';
29
+ el.style.transform = 'translateY(10px)';
30
+ const timer = setTimeout(() => {
31
+ el.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
32
+ el.style.opacity = '1';
33
+ el.style.transform = 'translateY(0)';
34
+ }, delay);
35
+ return () => clearTimeout(timer);
36
+ }, [delay]);
37
+ return _jsx("div", { ref: ref, className: className, children: children });
38
+ }
39
+ /* -- Animated number counter -- */
40
+ function AnimatedNumber({ value, duration = 1200 }) {
41
+ const [display, setDisplay] = React.useState(0);
42
+ const prev = useRef(0);
43
+ useEffect(() => {
44
+ const start = prev.current;
45
+ const end = value;
46
+ if (start === end)
47
+ return;
48
+ const startTime = Date.now();
49
+ const tick = () => {
50
+ const elapsed = Date.now() - startTime;
51
+ const progress = Math.min(elapsed / duration, 1);
52
+ // Ease out cubic
53
+ const eased = 1 - Math.pow(1 - progress, 3);
54
+ setDisplay(Math.round(start + (end - start) * eased));
55
+ if (progress < 1)
56
+ requestAnimationFrame(tick);
57
+ else
58
+ prev.current = end;
59
+ };
60
+ requestAnimationFrame(tick);
61
+ }, [value, duration]);
62
+ return _jsx(_Fragment, { children: fmtTokens(display) });
63
+ }
64
+ /* -- Plan tier badge -- */
65
+ const PLAN_TIERS = {
66
+ 'developer': { label: 'Developer', color: 'text-white/50', glow: '' },
67
+ 'pro': { label: 'Pro', color: 'text-sky-400', glow: 'shadow-[0_0_20px_rgba(56,189,248,0.15)]' },
68
+ 'team': { label: 'Team', color: 'text-violet-400', glow: 'shadow-[0_0_20px_rgba(167,139,250,0.15)]' },
69
+ 'enterprise': { label: 'Enterprise', color: 'text-amber-400', glow: 'shadow-[0_0_20px_rgba(251,191,36,0.15)]' },
70
+ 'custom': { label: 'Custom', color: 'text-emerald-400', glow: 'shadow-[0_0_20px_rgba(52,211,153,0.15)]' },
71
+ };
72
+ /* -- Main component -- */
73
+ export function OverviewDashboard(props) {
74
+ const currentPlan = props.plans.find(p => p.id === props.subscription.planId);
75
+ const recommendedPlan = props.plans.find(p => p.id === 'pro') ?? props.plans[1];
76
+ const mtdSpend = props.usage?.totalCost ?? 0;
77
+ const currency = props.usage?.currency ?? 'usd';
78
+ const periodEnd = props.subscription.currentPeriodEnd;
79
+ const daysLeft = daysUntil(periodEnd);
80
+ const totalCreditsRemaining = (props.balance?.available ?? 0) / 100;
81
+ const defaultPm = props.paymentMethods.find(m => m.is_default || m.isDefault) ?? props.paymentMethods[0];
82
+ const hasActivePlan = !!props.subscription.id && props.subscription.status === 'active';
83
+ // Trial status
84
+ const isTrialing = props.subscription.status === 'trialing';
85
+ const trialDaysLeft = isTrialing ? daysUntil(props.subscription.currentPeriodEnd) : 0;
86
+ // Token stats from usage
87
+ const aiRecord = props.usage?.records.find(r => r.meterId === 'ai' || r.unit?.toLowerCase().includes('token'));
88
+ const tokensThisPeriod = aiRecord?.current ?? 0;
89
+ const apiCallsRecord = props.usage?.records.find(r => r.meterId === 'api_calls');
90
+ const apiCallsThisPeriod = apiCallsRecord?.current ?? 0;
91
+ const tier = currentPlan ? (PLAN_TIERS[currentPlan.id] ?? PLAN_TIERS['developer']) : PLAN_TIERS['developer'];
92
+ // Spend bar heights (deterministic)
93
+ const barHeights = React.useMemo(() => Array.from({ length: 28 }, (_, i) => mtdSpend > 0 ? Math.max((i * 137 + 17) % 100, 4) : (i % 3 === 0 ? 2 : 1)), [mtdSpend]);
94
+ return (_jsxs("div", { className: "space-y-5", children: [isTrialing && (_jsx(FadeIn, { delay: 0, children: _jsxs("div", { className: "rounded-2xl border border-sky-500/25 bg-sky-500/[0.05] p-5 flex items-center justify-between gap-4", children: [_jsxs("div", { children: [_jsxs("p", { className: "text-sm font-semibold text-sky-400", children: ["Free trial active ", '\u00B7', " ", trialDaysLeft, " day", trialDaysLeft !== 1 ? 's' : '', " left"] }), _jsx("p", { className: "text-[13px] text-white/50 mt-1", children: "Add a payment method to continue after your trial ends." })] }), _jsx("button", { type: "button", onClick: () => props.onNavigate('payment'), className: "rounded-full bg-sky-500 px-5 py-2 text-sm font-semibold text-white transition hover:bg-sky-400 active:scale-[0.97] flex-shrink-0", children: "Add payment" })] }) })), _jsx(FadeIn, { delay: 0, children: _jsx("div", { className: `rounded-2xl border border-white/[0.08] bg-[#141419] overflow-hidden transition-all ${tier.glow}`, children: _jsxs("div", { className: "p-6", children: [_jsxs("div", { className: "flex items-start justify-between mb-5", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2.5 mb-1", children: [_jsxs("span", { className: `text-[11px] font-semibold uppercase tracking-widest ${tier.color}`, children: [tier.label, " Plan"] }), hasActivePlan && (_jsx("span", { className: "rounded-full bg-emerald-500/15 px-2 py-0.5 text-[10px] font-semibold text-emerald-400", children: "Active" }))] }), _jsx("h2", { className: "text-2xl font-bold text-white", children: tokensThisPeriod > 0
95
+ ? _jsxs(_Fragment, { children: [_jsx(AnimatedNumber, { value: tokensThisPeriod }), " tokens"] })
96
+ : props.usageLoading ? 'Loading\u2026' : '\u2014 tokens' }), _jsx("p", { className: "text-[13px] text-white/40 mt-1", children: "processed this billing period" })] }), _jsxs("div", { className: "text-right", children: [_jsx("p", { className: "text-[11px] text-white/30 mb-1", children: "API spend" }), _jsx("p", { className: "text-xl font-semibold text-white tabular-nums", children: fmt(mtdSpend, currency) }), _jsxs("p", { className: "text-[12px] text-white/30 mt-1", children: [daysLeft, "d until renewal"] })] })] }), _jsx("div", { className: "h-10 flex items-end gap-[2px]", children: barHeights.map((h, i) => (_jsx("div", { className: `flex-1 rounded-sm transition-all duration-700 ${mtdSpend > 0 ? 'bg-white/[0.12]' : 'bg-white/[0.04]'}`, style: { height: `${h}%`, transitionDelay: `${i * 20}ms` } }, i))) }), _jsxs("div", { className: "mt-5 grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "rounded-xl border border-white/[0.06] bg-white/[0.02] p-3", children: [_jsx("p", { className: "text-[11px] text-white/30 mb-1", children: "Tokens / period" }), _jsx("p", { className: "text-base font-semibold text-white tabular-nums", children: tokensThisPeriod > 0 ? fmtTokens(tokensThisPeriod) : '\u2014' })] }), _jsxs("div", { className: "rounded-xl border border-white/[0.06] bg-white/[0.02] p-3", children: [_jsx("p", { className: "text-[11px] text-white/30 mb-1", children: "API calls" }), _jsx("p", { className: "text-base font-semibold text-white tabular-nums", children: apiCallsThisPeriod > 0 ? fmtTokens(apiCallsThisPeriod) : '\u2014' })] }), _jsxs("div", { className: "rounded-xl border border-white/[0.06] bg-white/[0.02] p-3", children: [_jsx("p", { className: "text-[11px] text-white/30 mb-1", children: "Credits left" }), _jsx("p", { className: "text-base font-semibold text-white tabular-nums", children: fmt(totalCreditsRemaining) })] })] })] }) }) }), !hasActivePlan && !isTrialing && recommendedPlan && (_jsx(FadeIn, { delay: 60, children: _jsxs("div", { className: "relative overflow-hidden rounded-2xl border border-white/[0.12] bg-gradient-to-br from-[#1a1a2e] to-[#141419] p-6", children: [_jsx("div", { className: "pointer-events-none absolute -top-12 left-1/2 h-32 w-64 -translate-x-1/2 rounded-full bg-sky-500/10 blur-2xl" }), _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className: "text-sky-400", children: _jsx("path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" }) }), _jsx("span", { className: "text-sm font-semibold text-sky-400", children: "Unlock the full Hanzo AI suite" })] }), _jsxs("p", { className: "text-[22px] font-bold text-white mb-1", children: ["All AI models. One plan. ", _jsx("span", { className: "text-sky-400", children: "$20/mo." })] }), _jsxs("p", { className: "text-[13px] text-white/50 mb-5", children: ["14+ Zen AI models ", '\u00B7', " 1M+ token context ", '\u00B7', " Unlimited chat ", '\u00B7', " MCP tools ", '\u00B7', " Priority inference", recommendedPlan.trialDays ? ` \u00B7 ${recommendedPlan.trialDays}-day free trial` : ''] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("button", { type: "button", onClick: () => props.onUpgrade?.(recommendedPlan.id), className: "rounded-full bg-white px-6 py-2.5 text-sm font-bold text-black transition hover:bg-white/90 active:scale-[0.97]", children: recommendedPlan.trialDays ? `Start ${recommendedPlan.trialDays}-day free trial` : 'Upgrade to Unified AI' }), _jsxs("button", { type: "button", onClick: () => props.onNavigate('pricing'), className: "text-sm text-white/40 transition hover:text-white/70", children: ["View all plans ", '\u2192'] })] }), (recommendedPlan.annualPrice ?? 0) > 0 && (_jsxs("p", { className: "mt-3 text-[12px] text-emerald-400", children: ["Or go annual for $", recommendedPlan.annualPrice, "/yr ", '\u2014', " save $", (recommendedPlan.price * 12) - recommendedPlan.annualPrice] }))] })] }) })), _jsx(FadeIn, { delay: 80, children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("button", { type: "button", onClick: () => props.onNavigate('credits'), className: "rounded-2xl border border-white/[0.08] bg-[#141419] p-5 text-left transition-all hover:border-white/[0.14] hover:bg-[#16161c] group active:scale-[0.99]", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-[11px] uppercase tracking-widest text-white/30", children: "Credit Balance" }), _jsx("div", { className: "text-white/20 group-hover:text-white/40 transition-colors", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "8", cy: "8", r: "6" }), _jsx("path", { d: "M18.09 10.37A6 6 0 1 1 10.34 18" })] }) })] }), _jsx("p", { className: "text-2xl font-bold text-white tabular-nums", children: fmt(totalCreditsRemaining) }), _jsxs("p", { className: "text-[13px] text-white/35 mt-1.5 group-hover:text-white/50 transition-colors", children: [props.creditGrants.filter(g => g.active && !g.voided).length, " active grant", props.creditGrants.filter(g => g.active && !g.voided).length !== 1 ? 's' : ''] })] }), _jsxs("button", { type: "button", onClick: () => props.onNavigate('pricing'), className: "rounded-2xl border border-white/[0.08] bg-[#141419] p-5 text-left transition-all hover:border-white/[0.14] hover:bg-[#16161c] group active:scale-[0.99]", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-[11px] uppercase tracking-widest text-white/30", children: "AI Plan" }), _jsx("div", { className: "text-white/20 group-hover:text-white/40 transition-colors", children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" }) }) })] }), _jsx("p", { className: `text-2xl font-bold ${tier.color || 'text-white'}`, children: currentPlan?.name ?? 'Developer' }), _jsx("p", { className: "text-[13px] text-white/35 mt-1.5 group-hover:text-white/50 transition-colors", children: isTrialing ? `Trial \u00B7 ${trialDaysLeft}d left` : hasActivePlan ? 'Active' : 'Upgrade to unlock all \u2192' })] }), _jsxs("button", { type: "button", onClick: () => props.onNavigate('payment'), className: "rounded-2xl border border-white/[0.08] bg-[#141419] p-5 text-left transition-all hover:border-white/[0.14] hover:bg-[#16161c] group active:scale-[0.99]", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-[11px] uppercase tracking-widest text-white/30", children: "Payment" }), _jsx("div", { className: "text-white/20 group-hover:text-white/40 transition-colors", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "2", y: "5", width: "20", height: "14", rx: "2" }), _jsx("path", { d: "M2 10h20" })] }) })] }), _jsx("p", { className: "text-2xl font-bold text-white", children: defaultPm?.card ? `\u2022\u2022\u2022\u2022 ${defaultPm.card.last4}` : defaultPm ? defaultPm.type : '\u2014' }), _jsx("p", { className: "text-[13px] text-white/35 mt-1.5 group-hover:text-white/50 transition-colors", children: defaultPm?.card ? defaultPm.card.brand : defaultPm ? 'Active' : 'Add a payment method' })] })] }) }), _jsx(FadeIn, { delay: 120, children: _jsxs("div", { className: "rounded-2xl border border-white/[0.08] bg-[#141419] overflow-hidden", children: [_jsx("div", { className: "px-6 py-4 border-b border-white/[0.06]", children: _jsx("h3", { className: "text-[14px] font-semibold text-white", children: "Quick access" }) }), _jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 divide-x divide-y divide-white/[0.05]", children: [
97
+ { label: 'Usage', icon: _jsx("path", { d: "M3 3v18h18" }), section: 'usage' },
98
+ { label: 'Invoices', icon: _jsxs(_Fragment, { children: [_jsx("path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }), _jsx("path", { d: "M8 7h8" }), _jsx("path", { d: "M8 11h8" })] }), section: 'invoices' },
99
+ { label: 'Plans', icon: _jsx("path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" }), section: 'pricing' },
100
+ { label: 'Credits', icon: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6" }), _jsx("path", { d: "M18.09 10.37A6 6 0 1 1 10.34 18" })] }), section: 'credits' },
101
+ { label: 'Alerts', icon: _jsxs(_Fragment, { children: [_jsx("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), _jsx("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })] }), section: 'alerts' },
102
+ { label: 'Team', icon: _jsxs(_Fragment, { children: [_jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }), _jsx("circle", { cx: "9", cy: "7", r: "4" })] }), section: 'team' },
103
+ ].map((item) => (_jsxs("button", { type: "button", onClick: () => props.onNavigate(item.section), className: "flex flex-col items-center gap-2 px-4 py-5 text-left transition-colors hover:bg-white/[0.03] group", children: [_jsx("div", { className: "text-white/25 group-hover:text-white/50 transition-colors", children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: item.icon }) }), _jsx("p", { className: "text-[12px] font-medium text-white/35 group-hover:text-white/60 transition-colors", children: item.label })] }, item.section))) })] }) }), props.invoices.length > 0 && (_jsx(FadeIn, { delay: 160, children: _jsxs("div", { className: "rounded-2xl border border-white/[0.08] bg-[#141419] overflow-hidden", children: [_jsxs("div", { className: "px-6 py-4 border-b border-white/[0.06] flex items-center justify-between", children: [_jsx("h3", { className: "text-[14px] font-semibold text-white", children: "Recent invoices" }), _jsxs("button", { type: "button", onClick: () => props.onNavigate('invoices'), className: "text-[12px] text-white/35 hover:text-white/60 transition-colors", children: ["View all ", '\u2192'] })] }), _jsx("div", { className: "divide-y divide-white/[0.04]", children: props.invoices.slice(0, 3).map((inv) => (_jsxs("div", { className: "flex items-center justify-between px-6 py-3.5 hover:bg-white/[0.02] transition-colors", children: [_jsxs("div", { children: [_jsxs("p", { className: "text-[13px] text-white/70", children: ["#", inv.invoiceNumber || inv.number || inv.id.slice(0, 10)] }), _jsx("p", { className: "text-[12px] text-white/30", children: new Date(inv.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) })] }), _jsxs("div", { className: "text-right", children: [_jsx("p", { className: "text-[13px] font-semibold text-white tabular-nums", children: fmt(inv.total, inv.currency) }), _jsx("span", { className: `text-[11px] font-medium ${inv.status === 'paid' ? 'text-emerald-400' :
104
+ inv.status === 'failed' ? 'text-red-400' : 'text-amber-400'}`, children: inv.status.charAt(0).toUpperCase() + inv.status.slice(1) })] })] }, inv.id))) })] }) }))] }));
105
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"payment-manager.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/payment-manager.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE7C,MAAM,WAAW,yBAAyB;IACxC,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;IAChC,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;IAChC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IAC/C,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAqFD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,2CAyNpE"}
1
+ {"version":3,"file":"payment-manager.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/payment-manager.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAoC,MAAM,UAAU,CAAA;AAG/E,MAAM,WAAW,yBAAyB;IACxC,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;IAChC,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;IAChC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;IAC/C,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AA+FD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,2CAyKpE"}
@@ -1,66 +1,58 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
- const defaultDraft = {
5
- holderName: '',
6
- type: 'card',
7
- cardBrand: 'visa',
8
- last4: '',
9
- expMonth: '',
10
- expYear: '',
4
+ import { ConnectButton } from '@rainbow-me/rainbowkit';
5
+ import { useAccount, useChainId } from 'wagmi';
6
+ import { AnimatedCard } from './animated-card';
7
+ // ── Display helpers ──────────────────────────────────────────────────────
8
+ const CRYPTO_NETWORK_LABELS = {
9
+ bitcoin: 'Bitcoin (BTC)',
10
+ ethereum: 'Ethereum (ETH)',
11
+ solana: 'Solana (SOL)',
12
+ usdc: 'USDC',
13
+ };
14
+ const CHAIN_NAMES = {
15
+ 1: 'Ethereum',
16
+ 137: 'Polygon',
17
+ 10: 'Optimism',
18
+ 42161: 'Arbitrum',
19
+ 8453: 'Base',
20
+ 43114: 'Avalanche',
21
+ 56: 'BSC',
11
22
  };
12
23
  function formatCardBrand(brand) {
13
24
  if (!brand)
14
25
  return 'Card';
15
26
  return brand.charAt(0).toUpperCase() + brand.slice(1);
16
27
  }
17
- function buildPaymentMethodFromDraft(draft) {
18
- const now = new Date().toISOString();
19
- const id = `pm_${Math.random().toString(36).slice(2, 12)}`;
20
- if (draft.type === 'card') {
21
- return {
22
- id,
23
- type: 'card',
24
- is_default: false,
25
- created_at: now,
26
- isDefault: false,
27
- card: {
28
- brand: draft.cardBrand,
29
- last4: draft.last4,
30
- exp_month: Number(draft.expMonth),
31
- exp_year: Number(draft.expYear),
32
- funding: 'credit',
33
- },
34
- billing_details: {
35
- name: draft.holderName,
36
- },
37
- };
38
- }
39
- return {
40
- id,
41
- type: draft.type,
42
- is_default: false,
43
- created_at: now,
44
- isDefault: false,
45
- billing_details: {
46
- name: draft.holderName,
47
- },
48
- };
49
- }
50
28
  function methodDisplay(method) {
51
29
  if (method.type === 'card') {
52
- return `${formatCardBrand(method.card?.brand || '')} •••• ${method.card?.last4 || '••••'}`;
30
+ return `${formatCardBrand(method.card?.brand || '')} \u2022\u2022\u2022\u2022 ${method.card?.last4 || '\u2022\u2022\u2022\u2022'}`;
53
31
  }
54
- if (method.type === 'paypal') {
55
- return `PayPal ${method.paypal?.email ? `(${method.paypal.email})` : ''}`;
32
+ if (method.type === 'crypto') {
33
+ const network = method.crypto?.network ?? 'ethereum';
34
+ const label = CRYPTO_NETWORK_LABELS[network] ?? network;
35
+ const addr = method.crypto?.walletAddress ?? '';
36
+ const shortAddr = addr.length > 12 ? `${addr.slice(0, 6)}\u2026${addr.slice(-4)}` : addr;
37
+ return `${label} ${shortAddr}`;
38
+ }
39
+ if (method.type === 'wire') {
40
+ return method.wire?.bankName ?? 'Wire transfer';
56
41
  }
57
42
  if (method.type === 'bank_account' || method.type === 'sepa_debit') {
58
- return `${method.bank_account?.bankName || 'Bank account'} •••• ${method.bank_account?.last4 || '••••'}`;
43
+ return `${method.bank_account?.bankName || 'Bank account'} \u2022\u2022\u2022\u2022 ${method.bank_account?.last4 || '\u2022\u2022\u2022\u2022'}`;
59
44
  }
60
- if (method.type === 'apple_pay') {
61
- return 'Apple Pay';
45
+ return method.type;
46
+ }
47
+ function methodIcon(type) {
48
+ switch (type) {
49
+ case 'card': return '\uD83D\uDCB3';
50
+ case 'crypto': return '\uD83D\uDD17';
51
+ case 'wire': return '\uD83C\uDFE6';
52
+ case 'bank_account':
53
+ case 'sepa_debit': return '\uD83C\uDFDB\uFE0F';
54
+ default: return '\uD83D\uDCB0';
62
55
  }
63
- return 'Google Pay';
64
56
  }
65
57
  function methodExpiration(method) {
66
58
  if (method.type !== 'card')
@@ -71,43 +63,63 @@ function methodExpiration(method) {
71
63
  return null;
72
64
  return `${String(month).padStart(2, '0')}/${String(year).slice(-2)}`;
73
65
  }
66
+ function methodSubtext(method) {
67
+ if (method.type === 'crypto')
68
+ return method.crypto?.label || 'Crypto wallet';
69
+ if (method.type === 'wire') {
70
+ const parts = [];
71
+ if (method.wire?.swift)
72
+ parts.push(`SWIFT: ${method.wire.swift}`);
73
+ if (method.wire?.country)
74
+ parts.push(method.wire.country);
75
+ return parts.join(' \u00b7 ') || 'Wire transfer';
76
+ }
77
+ return null;
78
+ }
79
+ function copyToClipboard(text, label, setMsg) {
80
+ navigator.clipboard.writeText(text).then(() => {
81
+ setMsg(`${label} copied`);
82
+ setTimeout(() => setMsg(''), 2000);
83
+ });
84
+ }
85
+ // ── Styling ──────────────────────────────────────────────────────────────
86
+ const TAB_CLASS = 'flex-1 rounded-lg px-3 py-2 text-sm font-medium transition';
87
+ const TAB_ACTIVE = 'bg-bg-elevated text-text';
88
+ const TAB_INACTIVE = 'text-text-muted hover:text-text-secondary';
89
+ const INPUT_CLASS = 'w-full rounded-lg border border-border bg-bg-input px-3 py-2 text-sm text-text outline-none transition placeholder:text-text-dim focus:border-brand focus:ring-1 focus:ring-brand';
90
+ const BTN_PRIMARY = 'w-full rounded-lg bg-text px-4 py-2.5 text-sm font-medium text-bg transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60';
91
+ // Main component
74
92
  export function PaymentMethodManager(props) {
75
93
  const { paymentMethods, initialMethods, onAdd, onRemove, onSetDefault, onMethodAdded, onMethodRemoved, onDefaultChanged, } = props;
76
- const [methods, setMethods] = React.useState(paymentMethods && paymentMethods.length > 0
77
- ? paymentMethods
78
- : initialMethods || []);
79
- const [draft, setDraft] = React.useState(defaultDraft);
80
- const [isAdding, setIsAdding] = React.useState(false);
94
+ const [methods, setMethods] = React.useState(paymentMethods && paymentMethods.length > 0 ? paymentMethods : initialMethods || []);
81
95
  const [busyMethodId, setBusyMethodId] = React.useState(null);
82
96
  const [showForm, setShowForm] = React.useState(false);
97
+ const [activeTab, setActiveTab] = React.useState('card');
98
+ const [status, setStatus] = React.useState(null);
99
+ const [error, setError] = React.useState(null);
83
100
  React.useEffect(() => {
84
- if (paymentMethods) {
101
+ if (paymentMethods)
85
102
  setMethods(paymentMethods);
86
- }
87
103
  }, [paymentMethods]);
88
- const canCreate = draft.holderName.trim().length > 1 &&
89
- (draft.type !== 'card' || (draft.last4.length === 4 && draft.expMonth && draft.expYear));
90
- const handleCreate = React.useCallback(async () => {
91
- if (!canCreate)
92
- return;
93
- setIsAdding(true);
94
- const method = buildPaymentMethodFromDraft(draft);
104
+ const addMethod = React.useCallback(async (method) => {
95
105
  try {
96
106
  await onAdd?.(method);
97
- setMethods((prev) => [...prev, method]);
107
+ setMethods(prev => [...prev, method]);
98
108
  onMethodAdded?.(method);
99
- setDraft(defaultDraft);
100
109
  setShowForm(false);
110
+ setError(null);
111
+ setStatus('Payment method added');
112
+ setTimeout(() => setStatus(null), 3000);
101
113
  }
102
- finally {
103
- setIsAdding(false);
114
+ catch (err) {
115
+ setError(err instanceof Error ? err.message : 'Failed to add payment method');
104
116
  }
105
- }, [canCreate, draft, onAdd, onMethodAdded]);
117
+ }, [onAdd, onMethodAdded]);
106
118
  const handleRemove = React.useCallback(async (id) => {
107
119
  setBusyMethodId(id);
108
120
  try {
109
121
  await onRemove?.(id);
110
- setMethods((prev) => prev.filter((m) => m.id !== id));
122
+ setMethods(prev => prev.filter(m => m.id !== id));
111
123
  onMethodRemoved?.(id);
112
124
  }
113
125
  finally {
@@ -118,29 +130,201 @@ export function PaymentMethodManager(props) {
118
130
  setBusyMethodId(id);
119
131
  try {
120
132
  await onSetDefault?.(id);
121
- setMethods((prev) => prev.map((method) => ({
122
- ...method,
123
- is_default: method.id === id,
124
- isDefault: method.id === id,
125
- })));
133
+ setMethods(prev => prev.map(m => ({ ...m, is_default: m.id === id, isDefault: m.id === id })));
126
134
  onDefaultChanged?.(id);
127
135
  }
128
136
  finally {
129
137
  setBusyMethodId(null);
130
138
  }
131
139
  }, [onDefaultChanged, onSetDefault]);
132
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-zinc-200 bg-white", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b border-zinc-200 p-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold text-zinc-900", children: "Payment methods" }), _jsx("p", { className: "text-sm text-zinc-500", children: "Securely manage cards, wallets, and bank methods" })] }), _jsx("button", { type: "button", onClick: () => setShowForm((v) => !v), className: "rounded-lg bg-zinc-900 px-3 py-2 text-sm font-medium text-white transition hover:bg-zinc-700", children: showForm ? 'Cancel' : 'Add method' })] }), showForm ? (_jsxs("div", { className: "grid gap-3 border-b border-zinc-200 p-4 md:grid-cols-2 xl:grid-cols-5", children: [_jsx("input", { value: draft.holderName, onChange: (e) => setDraft((prev) => ({ ...prev, holderName: e.target.value })), placeholder: "Cardholder name", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsxs("select", { value: draft.type, onChange: (e) => setDraft((prev) => ({ ...prev, type: e.target.value })), className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900", children: [_jsx("option", { value: "card", children: "Card" }), _jsx("option", { value: "paypal", children: "PayPal" }), _jsx("option", { value: "apple_pay", children: "Apple Pay" }), _jsx("option", { value: "google_pay", children: "Google Pay" }), _jsx("option", { value: "bank_account", children: "Bank account" })] }), _jsx("input", { value: draft.last4, onChange: (e) => setDraft((prev) => ({
133
- ...prev,
134
- last4: e.target.value.replace(/\D/g, '').slice(0, 4),
135
- })), placeholder: "Last 4 digits", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("input", { value: draft.expMonth, onChange: (e) => setDraft((prev) => ({
136
- ...prev,
137
- expMonth: e.target.value.replace(/\D/g, '').slice(0, 2),
138
- })), placeholder: "MM", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { value: draft.expYear, onChange: (e) => setDraft((prev) => ({
139
- ...prev,
140
- expYear: e.target.value.replace(/\D/g, '').slice(0, 4),
141
- })), placeholder: "YYYY", className: "w-full rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("button", { type: "button", disabled: !canCreate || isAdding, onClick: handleCreate, className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm font-medium text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60", children: isAdding ? 'Saving…' : 'Save' })] })] })) : null, _jsx("div", { className: "divide-y divide-zinc-200", children: methods.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-zinc-500", children: "No payment methods on file." })) : (methods.map((method) => {
140
+ 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: "Payment methods" }), _jsx("p", { className: "text-sm text-text-muted", children: "Add a card, crypto wallet, or bank wire transfer" })] }), _jsx("button", { type: "button", onClick: () => { setShowForm(v => !v); setError(null); }, className: "rounded-lg bg-text px-3 py-2 text-sm font-medium text-bg transition hover:opacity-80", children: showForm ? 'Cancel' : 'Add method' })] }), showForm && (_jsxs("div", { className: "border-b border-border p-4", children: [_jsx("div", { className: "mb-4 flex gap-1 rounded-xl bg-bg p-1", children: [
141
+ ['card', 'Card'],
142
+ ['crypto', 'Crypto'],
143
+ ['wire', 'Bank Wire'],
144
+ ].map(([id, label]) => (_jsx("button", { type: "button", onClick: () => { setActiveTab(id); setError(null); }, className: `${TAB_CLASS} ${activeTab === id ? TAB_ACTIVE : TAB_INACTIVE}`, children: label }, id))) }), activeTab === 'card' && (_jsx(CardForm, { onAdd: addMethod, setError: setError })), activeTab === 'crypto' && _jsx(CryptoForm, { onAdd: addMethod, setError: setError }), activeTab === 'wire' && _jsx(WireForm, { onAdd: addMethod }), error && _jsx("p", { className: "mt-3 text-sm text-danger", children: error })] })), status && (_jsx("div", { className: "border-b border-border px-4 py-2", children: _jsx("p", { className: "text-sm text-success", children: status }) })), _jsx("div", { className: "divide-y divide-border", children: methods.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-text-muted", children: "No payment methods on file." })) : (methods.map(method => {
142
145
  const isDefault = method.is_default || method.isDefault;
143
146
  const isBusy = busyMethodId === method.id;
144
- return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 p-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-900", children: methodDisplay(method) }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2 text-xs text-zinc-500", children: [_jsxs("span", { children: ["Added ", new Date(method.created_at).toLocaleDateString('en-US')] }), methodExpiration(method) ? _jsxs("span", { children: ["Expires ", methodExpiration(method)] }) : null, isDefault ? (_jsx("span", { className: "rounded-full bg-zinc-900 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-white", children: "Default" })) : null] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [!isDefault ? (_jsx("button", { type: "button", disabled: isBusy, onClick: () => handleSetDefault(method.id), className: "rounded-md border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60", children: isBusy ? 'Saving…' : 'Set default' })) : null, _jsx("button", { type: "button", disabled: isBusy, onClick: () => handleRemove(method.id), className: "rounded-md border border-rose-200 px-3 py-1.5 text-xs font-medium text-rose-700 transition hover:bg-rose-50 disabled:cursor-not-allowed disabled:opacity-60", children: isBusy ? 'Removing…' : 'Remove' })] })] }, method.id));
147
+ const sub = methodSubtext(method);
148
+ return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 p-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { className: "mt-0.5 text-lg", children: methodIcon(method.type) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-text", children: methodDisplay(method) }), _jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2 text-xs text-text-muted", children: [_jsxs("span", { children: ["Added ", new Date(method.created_at).toLocaleDateString('en-US')] }), methodExpiration(method) ? _jsxs("span", { children: ["Expires ", methodExpiration(method)] }) : null, sub ? _jsx("span", { children: sub }) : null, isDefault && (_jsx("span", { className: "rounded-full bg-brand px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-foreground", children: "Default" }))] })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [!isDefault && (_jsx("button", { type: "button", disabled: isBusy, onClick: () => handleSetDefault(method.id), 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: isBusy ? 'Saving\u2026' : 'Set default' })), _jsx("button", { type: "button", disabled: isBusy, onClick: () => handleRemove(method.id), className: "rounded-md border border-rose-500/30 px-3 py-1.5 text-xs font-medium text-rose-500 transition hover:bg-rose-500/10 disabled:cursor-not-allowed disabled:opacity-60", children: isBusy ? 'Removing\u2026' : 'Remove' })] })] }, method.id));
145
149
  })) })] }) }));
146
150
  }
151
+ function detectBrandPM(digits) {
152
+ if (digits.startsWith('4'))
153
+ return 'visa';
154
+ if (/^5[1-5]/.test(digits) || /^2[2-7]/.test(digits))
155
+ return 'mastercard';
156
+ if (/^3[47]/.test(digits))
157
+ return 'amex';
158
+ if (/^6(?:011|5)/.test(digits))
159
+ return 'discover';
160
+ return '';
161
+ }
162
+ function CardForm({ onAdd, setError }) {
163
+ const [number, setNumberRaw] = React.useState('');
164
+ const [name, setName] = React.useState('');
165
+ const [expiry, setExpiryRaw] = React.useState('');
166
+ const [cvc, setCvc] = React.useState('');
167
+ const [zip, setZip] = React.useState('');
168
+ const [loading, setLoading] = React.useState(false);
169
+ const [cvvFocused, setCvvFocused] = React.useState(false);
170
+ const rawNumber = number.replace(/\D/g, '');
171
+ const brand = detectBrandPM(rawNumber);
172
+ const cvcLength = brand === 'amex' ? 4 : 3;
173
+ const authToken = React.useMemo(() => {
174
+ return typeof window !== 'undefined' ? (localStorage.getItem('hanzo-auth-token') ?? undefined) : undefined;
175
+ }, []);
176
+ const handleSubmit = React.useCallback(async (e) => {
177
+ e.preventDefault();
178
+ setError('');
179
+ if (rawNumber.length < 13) {
180
+ setError('Invalid card number');
181
+ return;
182
+ }
183
+ if (!expiry.includes('/')) {
184
+ setError('Invalid expiry');
185
+ return;
186
+ }
187
+ const [mm, yy] = expiry.split('/');
188
+ if (!mm || !yy || mm.length !== 2 || yy.length !== 2) {
189
+ setError('Invalid expiry');
190
+ return;
191
+ }
192
+ if (cvc.length < 3) {
193
+ setError('Invalid CVC');
194
+ return;
195
+ }
196
+ if (!name.trim()) {
197
+ setError('Name required');
198
+ return;
199
+ }
200
+ setLoading(true);
201
+ try {
202
+ const base = process.env.NEXT_PUBLIC_COMMERCE_API ?? 'https://api.hanzo.ai/api/v1';
203
+ const headers = { 'Content-Type': 'application/json' };
204
+ if (authToken)
205
+ headers['Authorization'] = `Bearer ${authToken}`;
206
+ const res = await fetch(`${base}/billing/card/tokenize`, {
207
+ method: 'POST',
208
+ headers,
209
+ body: JSON.stringify({
210
+ number: rawNumber,
211
+ expiry_month: mm,
212
+ expiry_year: `20${yy}`,
213
+ cvc,
214
+ name: name.trim(),
215
+ zip: zip.trim(),
216
+ }),
217
+ });
218
+ if (!res.ok) {
219
+ const d = await res.json().catch(() => ({}));
220
+ throw new Error(d.error ?? `Error ${res.status}`);
221
+ }
222
+ const data = await res.json();
223
+ const method = {
224
+ id: `pm_${Math.random().toString(36).slice(2, 12)}`,
225
+ type: 'card',
226
+ is_default: false,
227
+ isDefault: false,
228
+ created_at: new Date().toISOString(),
229
+ card: {
230
+ brand: data.brand,
231
+ last4: data.last4,
232
+ exp_month: parseInt(mm, 10),
233
+ exp_year: parseInt(`20${yy}`, 10),
234
+ funding: 'credit',
235
+ },
236
+ billing_details: { name: name.trim() },
237
+ _sourceToken: data.token,
238
+ };
239
+ await onAdd(method);
240
+ }
241
+ catch (err) {
242
+ setError(err instanceof Error ? err.message : 'Card error');
243
+ }
244
+ finally {
245
+ setLoading(false);
246
+ }
247
+ }, [rawNumber, expiry, cvc, name, zip, authToken, onAdd, setError]);
248
+ const canSubmit = !loading && rawNumber.length >= 13 && !!expiry && !!cvc && !!name.trim();
249
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx("div", { style: { display: 'flex', justifyContent: 'center', padding: '8px 0' }, children: _jsx(AnimatedCard, { number: rawNumber, name: name, expiry: expiry.replace('/', ''), cvv: cvc, cvvFocused: cvvFocused }) }), _jsx("input", { type: "text", inputMode: "numeric", autoComplete: "cc-number", placeholder: "Card number", value: number, onChange: e => {
250
+ const d = e.target.value.replace(/\D/g, '').slice(0, 16);
251
+ setNumberRaw(d.replace(/(.{4})/g, '$1 ').trim());
252
+ }, className: INPUT_CLASS, disabled: loading, maxLength: 19 }), _jsx("input", { type: "text", autoComplete: "cc-name", placeholder: "Cardholder name", value: name, onChange: e => setName(e.target.value), className: INPUT_CLASS, disabled: loading }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsx("input", { type: "text", inputMode: "numeric", autoComplete: "cc-exp", placeholder: "MM/YY", value: expiry, onChange: e => {
253
+ const d = e.target.value.replace(/\D/g, '').slice(0, 4);
254
+ setExpiryRaw(d.length >= 3 ? d.slice(0, 2) + '/' + d.slice(2) : d);
255
+ }, className: INPUT_CLASS, disabled: loading, maxLength: 5 }), _jsx("input", { type: "password", inputMode: "numeric", autoComplete: "cc-csc", placeholder: "CVC", value: cvc, onChange: e => setCvc(e.target.value.replace(/\D/g, '').slice(0, cvcLength)), onFocus: () => setCvvFocused(true), onBlur: () => setCvvFocused(false), className: INPUT_CLASS, disabled: loading, maxLength: cvcLength })] }), _jsx("input", { type: "text", autoComplete: "postal-code", placeholder: "ZIP (optional)", value: zip, onChange: e => setZip(e.target.value.slice(0, 10)), className: INPUT_CLASS, disabled: loading }), _jsx("button", { type: "submit", className: BTN_PRIMARY, disabled: !canSubmit, children: loading ? 'Processing...' : 'Save card' }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '11px', color: 'rgba(255,255,255,0.3)' }, children: [_jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] }), "Secured by Hanzo Commerce ", '\u00b7', " PCI DSS compliant"] })] }));
256
+ }
257
+ // Crypto form -- wagmi + viem + RainbowKit
258
+ function CryptoForm({ onAdd, setError }) {
259
+ const { address, isConnected } = useAccount();
260
+ const chainId = useChainId();
261
+ const [label, setLabel] = React.useState('');
262
+ const [saving, setSaving] = React.useState(false);
263
+ const chainName = CHAIN_NAMES[chainId] ?? `Chain ${chainId}`;
264
+ const saveWallet = React.useCallback(async () => {
265
+ if (!address)
266
+ return;
267
+ setSaving(true);
268
+ try {
269
+ const method = {
270
+ id: `pm_${Math.random().toString(36).slice(2, 12)}`,
271
+ type: 'crypto',
272
+ is_default: false,
273
+ isDefault: false,
274
+ created_at: new Date().toISOString(),
275
+ crypto: {
276
+ network: 'ethereum',
277
+ walletAddress: address,
278
+ label: label || `${chainName} wallet`,
279
+ },
280
+ billing_details: {},
281
+ };
282
+ await onAdd(method);
283
+ }
284
+ catch (err) {
285
+ setError(err instanceof Error ? err.message : 'Failed to save wallet');
286
+ }
287
+ finally {
288
+ setSaving(false);
289
+ }
290
+ }, [address, chainId, chainName, label, onAdd, setError]);
291
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-text-muted", children: "Connect your wallet to save it as a payment method. Supports Ethereum, Polygon, Arbitrum, Optimism, Base, Avalanche, BSC, and more." }), _jsx("div", { className: "flex justify-center", children: _jsx(ConnectButton, { showBalance: false }) }), isConnected && address && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "rounded-lg border border-border bg-bg p-3", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("p", { className: "text-xs text-text-dim", children: ["Connected on ", chainName] }) }), _jsx("p", { className: "mt-1 font-mono text-sm text-text break-all", children: address })] }), _jsx("input", { value: label, onChange: e => setLabel(e.target.value), placeholder: `Label (e.g. "My ${chainName} wallet")`, className: INPUT_CLASS }), _jsx("button", { type: "button", disabled: saving, onClick: saveWallet, className: BTN_PRIMARY, children: saving ? 'Saving wallet\u2026' : 'Save wallet as payment method' })] })), _jsx("p", { className: "text-xs text-text-dim", children: "Non-EVM chains (Solana, TON, Bitcoin) coming soon." })] }));
292
+ }
293
+ // Wire form -- bank details with copy-to-clipboard
294
+ const BANK_DETAILS = [
295
+ { label: 'Beneficiary Bank', value: 'Bank of America, NA 222 Broadway, New York, NY 10038' },
296
+ { label: 'Beneficiary', value: 'Hanzo, Inc, 4811 Mastin Street, Merriam, KS 66203' },
297
+ { label: 'Routing \u2014 ACH', value: '113000023 / 111000025' },
298
+ { label: 'Routing \u2014 Wire', value: '026009593' },
299
+ { label: 'SWIFT', value: 'BOFAUS3N' },
300
+ { label: 'Reference', value: 'Hanzo' },
301
+ ];
302
+ function WireForm({ onAdd }) {
303
+ const [copyMsg, setCopyMsg] = React.useState('');
304
+ const [saving, setSaving] = React.useState(false);
305
+ const saveWire = React.useCallback(async () => {
306
+ setSaving(true);
307
+ try {
308
+ const method = {
309
+ id: `pm_${Math.random().toString(36).slice(2, 12)}`,
310
+ type: 'wire',
311
+ is_default: false,
312
+ isDefault: false,
313
+ created_at: new Date().toISOString(),
314
+ wire: {
315
+ bankName: 'Bank of America',
316
+ accountHolder: 'Hanzo, Inc',
317
+ routingNumber: '026009593',
318
+ swift: 'BOFAUS3N',
319
+ country: 'US',
320
+ },
321
+ billing_details: {},
322
+ };
323
+ await onAdd(method);
324
+ }
325
+ finally {
326
+ setSaving(false);
327
+ }
328
+ }, [onAdd]);
329
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-text-muted", children: "Send a wire transfer to the account below. Click any field to copy." }), _jsx("div", { className: "space-y-2", children: BANK_DETAILS.map(({ label, value }) => (_jsxs("button", { type: "button", onClick: () => copyToClipboard(value, label, setCopyMsg), className: "w-full rounded-lg border border-border bg-bg p-3 text-left transition hover:border-border-strong group", children: [_jsx("p", { className: "text-[11px] uppercase tracking-wide text-text-dim", children: label }), _jsxs("div", { className: "mt-0.5 flex items-center justify-between", children: [_jsx("p", { className: "text-sm text-text", children: value }), _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "shrink-0 text-text-dim group-hover:text-text-muted", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })] })] }, label))) }), copyMsg && _jsx("p", { className: "text-xs text-success", children: copyMsg }), _jsx("button", { type: "button", disabled: saving, onClick: saveWire, className: BTN_PRIMARY, children: saving ? 'Saving\u2026' : 'Confirm wire sent' })] }));
330
+ }
@@ -0,0 +1,7 @@
1
+ import type { DiscountCode, SubscriptionDiscount } from '../types';
2
+ export interface PromotionsPanelProps {
3
+ discount?: SubscriptionDiscount | null;
4
+ onApplyDiscount?: (code: string) => Promise<DiscountCode | null>;
5
+ }
6
+ export declare function PromotionsPanel({ discount, onApplyDiscount }: PromotionsPanelProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=promotions-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotions-panel.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/promotions-panel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAElE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;IACtC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;CACjE;AAOD,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,oBAAoB,2CAoGlF"}
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ function formatDiscount(d) {
5
+ if (d.kind === 'percent')
6
+ return `${d.value}% off`;
7
+ return `$${d.value} off`;
8
+ }
9
+ export function PromotionsPanel({ discount, onApplyDiscount }) {
10
+ const [code, setCode] = React.useState('');
11
+ const [applying, setApplying] = React.useState(false);
12
+ const [result, setResult] = React.useState(null);
13
+ const handleApply = React.useCallback(async () => {
14
+ if (!onApplyDiscount || !code.trim())
15
+ return;
16
+ setApplying(true);
17
+ setResult(null);
18
+ try {
19
+ const disc = await onApplyDiscount(code.trim());
20
+ if (disc && disc.valid) {
21
+ setResult({
22
+ success: true,
23
+ message: `Applied: ${disc.name || disc.code} (${formatDiscount(disc)})`,
24
+ });
25
+ setCode('');
26
+ }
27
+ else {
28
+ setResult({ success: false, message: 'Invalid or expired code' });
29
+ }
30
+ }
31
+ catch (err) {
32
+ setResult({ success: false, message: err instanceof Error ? err.message : 'Failed to apply code' });
33
+ }
34
+ finally {
35
+ setApplying(false);
36
+ }
37
+ }, [onApplyDiscount, code]);
38
+ return (_jsxs("div", { className: "space-y-4", children: [discount && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-emerald-500/30 bg-emerald-500/5", children: [_jsx("div", { className: "border-b border-emerald-500/20 px-4 py-3", children: _jsx("h4", { className: "text-sm font-semibold text-emerald-500", children: "Active discount" }) }), _jsxs("div", { className: "p-4", children: [_jsx("p", { className: "text-lg font-semibold text-text", children: discount.name || discount.code || 'Discount' }), _jsxs("p", { className: "mt-1 text-sm text-text-secondary", children: [formatDiscount(discount), discount.duration === 'repeating' && discount.durationInMonths
39
+ ? ` for ${discount.durationInMonths} months`
40
+ : discount.duration === 'forever'
41
+ ? ' forever'
42
+ : discount.duration === 'once'
43
+ ? ' (one-time)'
44
+ : ''] })] })] })), _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: "Promotions & Discounts" }), _jsx("p", { className: "text-sm text-text-muted", children: "Enter a promo code, referral code, or discount code to apply to your subscription." })] }), _jsxs("div", { className: "p-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx("input", { value: code, onChange: (e) => {
45
+ setCode(e.target.value.toUpperCase());
46
+ setResult(null);
47
+ }, 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 placeholder:text-text-dim focus:border-brand" }), _jsx("button", { type: "button", disabled: !code.trim() || applying || !onApplyDiscount, onClick: handleApply, className: "rounded-lg bg-text px-5 py-2 text-sm font-medium text-bg transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60", children: applying ? 'Applying...' : 'Apply' })] }), result && (_jsx("p", { className: `mt-2 text-sm ${result.success ? 'text-success' : 'text-danger'}`, children: result.message }))] })] }), _jsxs("div", { className: "rounded-xl border border-dashed border-border p-6 text-center", children: [_jsx("p", { className: "text-sm text-text-muted", children: "Watch for promotional offers, seasonal discounts, and referral bonuses." }), _jsx("p", { className: "mt-1 text-xs text-text-dim", children: "Credits and promotions are applied automatically at billing time." })] })] }));
48
+ }