@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,63 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ const ROLES = ['owner', 'admin', 'viewer'];
5
+ function roleBadgeClass(role) {
6
+ switch (role) {
7
+ case 'owner':
8
+ return 'bg-brand/20 text-brand';
9
+ case 'admin':
10
+ return 'bg-amber-500/20 text-amber-400';
11
+ case 'viewer':
12
+ return 'bg-text-dim/20 text-text-muted';
13
+ }
14
+ }
15
+ const INPUT_CLASS = 'rounded-lg border border-border bg-bg-input px-3 py-2 text-sm text-text outline-none transition focus:border-brand';
16
+ export function AccountMembers({ members, userRole, onInvite, onChangeRole, onRemove }) {
17
+ const canManage = userRole === 'owner' || userRole === 'admin';
18
+ const [inviteEmail, setInviteEmail] = React.useState('');
19
+ const [inviteRole, setInviteRole] = React.useState('viewer');
20
+ const [inviting, setInviting] = React.useState(false);
21
+ const [busyId, setBusyId] = React.useState(null);
22
+ const handleInvite = React.useCallback(async () => {
23
+ if (!inviteEmail.includes('@') || !onInvite)
24
+ return;
25
+ setInviting(true);
26
+ try {
27
+ await onInvite(inviteEmail, inviteRole);
28
+ setInviteEmail('');
29
+ setInviteRole('viewer');
30
+ }
31
+ finally {
32
+ setInviting(false);
33
+ }
34
+ }, [inviteEmail, inviteRole, onInvite]);
35
+ const handleChangeRole = React.useCallback(async (memberId, role) => {
36
+ if (!onChangeRole)
37
+ return;
38
+ setBusyId(memberId);
39
+ try {
40
+ await onChangeRole(memberId, role);
41
+ }
42
+ finally {
43
+ setBusyId(null);
44
+ }
45
+ }, [onChangeRole]);
46
+ const handleRemove = React.useCallback(async (memberId) => {
47
+ if (!onRemove)
48
+ return;
49
+ setBusyId(memberId);
50
+ try {
51
+ await onRemove(memberId);
52
+ }
53
+ finally {
54
+ setBusyId(null);
55
+ }
56
+ }, [onRemove]);
57
+ 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: "border-b border-border p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Team members" }), _jsx("p", { className: "text-sm text-text-muted", children: canManage
58
+ ? 'Manage who has access to this billing account.'
59
+ : 'View members of this billing account.' })] }), canManage && onInvite && (_jsxs("div", { className: "flex flex-wrap items-end gap-3 border-b border-border p-4", children: [_jsxs("div", { className: "flex-1 min-w-[200px]", children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-text-muted", children: "Email address" }), _jsx("input", { type: "email", value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), placeholder: "colleague@company.com", className: `${INPUT_CLASS} w-full` })] }), _jsxs("div", { className: "w-32", children: [_jsx("label", { className: "mb-1 block text-xs font-medium text-text-muted", children: "Role" }), _jsx("select", { value: inviteRole, onChange: (e) => setInviteRole(e.target.value), className: `${INPUT_CLASS} w-full`, children: ROLES.map((r) => (_jsx("option", { value: r, children: r.charAt(0).toUpperCase() + r.slice(1) }, r))) })] }), _jsx("button", { type: "button", disabled: !inviteEmail.includes('@') || inviting, onClick: handleInvite, 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: inviting ? 'Inviting...' : 'Invite' })] })), _jsx("div", { className: "divide-y divide-border", children: members.length === 0 ? (_jsx("div", { className: "p-6 text-sm text-text-muted", children: "No members yet." })) : (members.map((member) => {
60
+ const isBusy = busyId === member.id;
61
+ return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-brand/20 text-sm font-bold text-brand", children: (member.name || member.email).charAt(0).toUpperCase() }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-text", children: member.name || member.email }), _jsxs("div", { className: "flex items-center gap-2 text-xs text-text-muted", children: [_jsx("span", { children: member.email }), _jsx("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${roleBadgeClass(member.role)}`, children: member.role }), _jsxs("span", { children: ["Joined ", new Date(member.addedAt).toLocaleDateString('en-US')] })] })] })] }), canManage && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("select", { value: member.role, onChange: (e) => handleChangeRole(member.id, e.target.value), disabled: isBusy, className: "rounded-md border border-border bg-bg-input px-2 py-1 text-xs text-text outline-none transition focus:border-brand disabled:cursor-not-allowed disabled:opacity-60", children: ROLES.map((r) => (_jsx("option", { value: r, children: r.charAt(0).toUpperCase() + r.slice(1) }, r))) }), _jsx("button", { type: "button", disabled: isBusy, onClick: () => handleRemove(member.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...' : 'Remove' })] }))] }, member.id));
62
+ })) })] }) }));
63
+ }
@@ -0,0 +1,9 @@
1
+ import type { BillingAccount } from '../types';
2
+ export interface AccountSwitcherProps {
3
+ accounts: BillingAccount[];
4
+ activeAccountId: string | null;
5
+ onSwitch: (accountId: string) => void;
6
+ onCreate?: () => void;
7
+ }
8
+ export declare function AccountSwitcher({ accounts, activeAccountId, onSwitch, onCreate }: AccountSwitcherProps): import("react/jsx-runtime").JSX.Element | null;
9
+ //# sourceMappingURL=account-switcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-switcher.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/account-switcher.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,UAAU,CAAA;AAE3D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAyBD,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,oBAAoB,kDA8HtG"}
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ function roleBadgeClass(role) {
5
+ switch (role) {
6
+ case 'owner':
7
+ return 'bg-brand/20 text-brand';
8
+ case 'admin':
9
+ return 'bg-amber-500/20 text-amber-400';
10
+ case 'viewer':
11
+ return 'bg-text-dim/20 text-text-muted';
12
+ }
13
+ }
14
+ function planBadgeClass(_plan) {
15
+ return 'bg-text-dim/20 text-text-muted';
16
+ }
17
+ function formatBalance(balance, currency) {
18
+ return new Intl.NumberFormat('en-US', {
19
+ style: 'currency',
20
+ currency: currency.toUpperCase(),
21
+ minimumFractionDigits: 2,
22
+ }).format(balance / 100);
23
+ }
24
+ export function AccountSwitcher({ accounts, activeAccountId, onSwitch, onCreate }) {
25
+ const [open, setOpen] = React.useState(false);
26
+ const ref = React.useRef(null);
27
+ // Close dropdown on outside click
28
+ React.useEffect(() => {
29
+ function handleClick(e) {
30
+ if (ref.current && !ref.current.contains(e.target)) {
31
+ setOpen(false);
32
+ }
33
+ }
34
+ if (open) {
35
+ document.addEventListener('mousedown', handleClick);
36
+ return () => document.removeEventListener('mousedown', handleClick);
37
+ }
38
+ }, [open]);
39
+ const active = accounts.find((a) => a.id === activeAccountId) ?? accounts[0] ?? null;
40
+ if (accounts.length === 0 && !onCreate)
41
+ return null;
42
+ return (_jsxs("div", { ref: ref, className: "relative mb-4", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center justify-between rounded-xl border border-border bg-bg-card px-4 py-3 text-left transition hover:bg-bg-elevated", children: [active ? (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-brand/20 text-sm font-bold text-brand", children: active.orgName.charAt(0).toUpperCase() }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-semibold text-text", children: active.name }), _jsx("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${planBadgeClass(active.plan)}`, children: active.plan }), _jsx("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${roleBadgeClass(active.role)}`, children: active.role })] }), _jsxs("span", { className: "text-xs text-text-muted", children: [active.orgName, " \u00B7 ", formatBalance(active.balance, active.currency)] })] })] })) : (_jsx("span", { className: "text-sm text-text-muted", children: "No billing account" })), _jsx("svg", { className: `h-4 w-4 text-text-muted transition-transform ${open ? 'rotate-180' : ''}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }), open && (_jsxs("div", { className: "absolute left-0 right-0 z-50 mt-1 overflow-hidden rounded-xl border border-border bg-bg-card shadow-lg", children: [_jsx("div", { className: "max-h-72 overflow-y-auto divide-y divide-border", children: accounts.map((account) => {
43
+ const isActive = account.id === activeAccountId;
44
+ return (_jsxs("button", { type: "button", onClick: () => {
45
+ onSwitch(account.id);
46
+ setOpen(false);
47
+ }, className: `flex w-full items-center gap-3 px-4 py-3 text-left transition hover:bg-bg-elevated ${isActive ? 'bg-bg-elevated' : ''}`, children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-brand/20 text-sm font-bold text-brand", children: account.orgName.charAt(0).toUpperCase() }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium text-text truncate", children: account.name }), _jsx("span", { className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${planBadgeClass(account.plan)}`, children: account.plan }), _jsx("span", { className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${roleBadgeClass(account.role)}`, children: account.role })] }), _jsxs("span", { className: "text-xs text-text-muted", children: [account.orgName, " \u00B7 ", formatBalance(account.balance, account.currency)] })] }), isActive && (_jsx("svg", { className: "h-4 w-4 shrink-0 text-brand", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }))] }, account.id));
48
+ }) }), onCreate && (_jsx("div", { className: "border-t border-border p-2", children: _jsxs("button", { type: "button", onClick: () => {
49
+ onCreate();
50
+ setOpen(false);
51
+ }, className: "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-muted transition hover:bg-bg-elevated hover:text-text", children: [_jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" }) }), "Create billing account"] }) }))] }))] }));
52
+ }
@@ -0,0 +1,9 @@
1
+ export interface AnimatedCardProps {
2
+ number: string;
3
+ name: string;
4
+ expiry: string;
5
+ cvv: string;
6
+ cvvFocused: boolean;
7
+ }
8
+ export declare function AnimatedCard({ number, name, expiry, cvv, cvvFocused }: AnimatedCardProps): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=animated-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animated-card.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/animated-card.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,OAAO,CAAA;CACpB;AAqGD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,iBAAiB,2CAyKxF"}
@@ -0,0 +1,161 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ function detectNetwork(digits) {
4
+ if (digits.startsWith('4'))
5
+ return 'visa';
6
+ if (/^5[1-5]/.test(digits) || /^2[2-7]/.test(digits))
7
+ return 'mastercard';
8
+ if (/^3[47]/.test(digits))
9
+ return 'amex';
10
+ if (/^6(?:011|5)/.test(digits))
11
+ return 'discover';
12
+ return 'unknown';
13
+ }
14
+ // ── SVG logos (inline, no external deps) ─────────────────────────────────
15
+ function VisaLogo() {
16
+ return (_jsx("svg", { viewBox: "0 0 60 20", width: "60", height: "20", "aria-label": "Visa", children: _jsx("text", { x: "0", y: "16", fontFamily: "Arial Black, sans-serif", fontWeight: "900", fontSize: "18", fill: "white", letterSpacing: "-1", children: "VISA" }) }));
17
+ }
18
+ function MastercardLogo() {
19
+ return (_jsxs("svg", { viewBox: "0 0 48 30", width: "48", height: "30", "aria-label": "Mastercard", children: [_jsx("circle", { cx: "15", cy: "15", r: "14", fill: "#eb001b", opacity: "0.9" }), _jsx("circle", { cx: "33", cy: "15", r: "14", fill: "#f79e1b", opacity: "0.9" }), _jsx("path", { d: "M24 4.8a14 14 0 0 1 0 20.4A14 14 0 0 1 24 4.8z", fill: "#ff5f00", opacity: "0.9" })] }));
20
+ }
21
+ function AmexLogo() {
22
+ return (_jsxs("svg", { viewBox: "0 0 70 20", width: "70", height: "20", "aria-label": "American Express", children: [_jsx("text", { x: "0", y: "15", fontFamily: "Arial, sans-serif", fontWeight: "700", fontSize: "13", fill: "white", letterSpacing: "0.5", children: "AMERICAN" }), _jsx("text", { x: "0", y: "28", fontFamily: "Arial, sans-serif", fontWeight: "700", fontSize: "13", fill: "white", letterSpacing: "0.5", children: "EXPRESS" })] }));
23
+ }
24
+ function DiscoverLogo() {
25
+ return (_jsxs("svg", { viewBox: "0 0 80 22", width: "80", height: "22", "aria-label": "Discover", children: [_jsx("text", { x: "0", y: "16", fontFamily: "Arial, sans-serif", fontWeight: "700", fontSize: "14", fill: "white", children: "DISCOVER" }), _jsx("circle", { cx: "72", cy: "11", r: "9", fill: "#f76f20" })] }));
26
+ }
27
+ function NetworkLogo({ network }) {
28
+ if (network === 'visa')
29
+ return _jsx(VisaLogo, {});
30
+ if (network === 'mastercard')
31
+ return _jsx(MastercardLogo, {});
32
+ if (network === 'amex')
33
+ return _jsx(AmexLogo, {});
34
+ if (network === 'discover')
35
+ return _jsx(DiscoverLogo, {});
36
+ return null;
37
+ }
38
+ // ── Chip SVG ──────────────────────────────────────────────────────────────
39
+ function ChipIcon() {
40
+ return (_jsxs("svg", { width: "44", height: "34", viewBox: "0 0 44 34", fill: "none", "aria-hidden": "true", children: [_jsx("rect", { x: "0.5", y: "0.5", width: "43", height: "33", rx: "5.5", fill: "#c8a951", stroke: "#b8993e" }), _jsx("rect", { x: "14", y: "0.5", width: "16", height: "33", fill: "#b8993e", opacity: "0.5" }), _jsx("rect", { x: "0.5", y: "11", width: "43", height: "12", fill: "#b8993e", opacity: "0.5" }), _jsx("rect", { x: "14", y: "11", width: "16", height: "12", fill: "#c8a951", opacity: "0.8" }), _jsx("line", { x1: "14", y1: "0.5", x2: "14", y2: "33.5", stroke: "#a08030", strokeWidth: "0.5" }), _jsx("line", { x1: "30", y1: "0.5", x2: "30", y2: "33.5", stroke: "#a08030", strokeWidth: "0.5" }), _jsx("line", { x1: "0.5", y1: "11", x2: "43.5", y2: "11", stroke: "#a08030", strokeWidth: "0.5" }), _jsx("line", { x1: "0.5", y1: "23", x2: "43.5", y2: "23", stroke: "#a08030", strokeWidth: "0.5" })] }));
41
+ }
42
+ // ── Number masking ────────────────────────────────────────────────────────
43
+ function maskNumber(digits, network) {
44
+ const len = network === 'amex' ? 15 : 16;
45
+ const padded = digits.padEnd(len, '\u2022');
46
+ if (network === 'amex') {
47
+ // 4-6-5 format
48
+ return `${padded.slice(0, 4)} ${padded.slice(4, 10)} ${padded.slice(10, 15)}`;
49
+ }
50
+ // 4-4-4-4 format, mask all but last 4
51
+ const groups = [
52
+ padded.slice(0, 4),
53
+ padded.slice(4, 8),
54
+ padded.slice(8, 12),
55
+ padded.slice(12, 16),
56
+ ];
57
+ return groups
58
+ .map((g, i) => (i < 3 ? g.replace(/\d/g, '\u2022') : g))
59
+ .join(' ');
60
+ }
61
+ // ── Animated card ─────────────────────────────────────────────────────────
62
+ export function AnimatedCard({ number, name, expiry, cvv, cvvFocused }) {
63
+ const digits = number.replace(/\D/g, '');
64
+ const network = detectNetwork(digits);
65
+ const maskedNum = maskNumber(digits, network);
66
+ const displayName = name.trim() || 'YOUR NAME';
67
+ const displayExpiry = expiry || 'MM/YY';
68
+ const displayCvv = cvv ? cvv.replace(/./g, '\u2022') : '\u2022\u2022\u2022';
69
+ // Card dimensions: standard 85.6mm x 53.98mm ratio
70
+ const cardStyle = {
71
+ perspective: '1000px',
72
+ width: '340px',
73
+ height: '215px',
74
+ };
75
+ const innerStyle = {
76
+ position: 'relative',
77
+ width: '100%',
78
+ height: '100%',
79
+ transformStyle: 'preserve-3d',
80
+ transition: 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
81
+ transform: cvvFocused ? 'rotateY(180deg)' : 'rotateY(0deg)',
82
+ };
83
+ const faceBase = {
84
+ position: 'absolute',
85
+ inset: 0,
86
+ backfaceVisibility: 'hidden',
87
+ WebkitBackfaceVisibility: 'hidden',
88
+ borderRadius: '16px',
89
+ overflow: 'hidden',
90
+ };
91
+ const frontStyle = {
92
+ ...faceBase,
93
+ background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 40%, #0f3460 100%)',
94
+ boxShadow: '0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.08)',
95
+ };
96
+ const backStyle = {
97
+ ...faceBase,
98
+ background: 'linear-gradient(135deg, #12121f 0%, #1a1a2e 100%)',
99
+ boxShadow: '0 20px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.08)',
100
+ transform: 'rotateY(180deg)',
101
+ };
102
+ return (_jsx("div", { style: cardStyle, role: "img", "aria-label": "Payment card preview", children: _jsxs("div", { style: innerStyle, children: [_jsxs("div", { style: frontStyle, children: [_jsx("div", { style: {
103
+ position: 'absolute', inset: 0,
104
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.12) 0%, transparent 50%)',
105
+ pointerEvents: 'none',
106
+ } }), _jsxs("div", { style: { position: 'absolute', top: '22px', left: '22px', right: '22px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(ChipIcon, {}), _jsx(NetworkLogo, { network: network })] }), _jsx("div", { style: {
107
+ position: 'absolute',
108
+ top: '50%',
109
+ left: '22px',
110
+ right: '22px',
111
+ transform: 'translateY(-50%)',
112
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
113
+ fontSize: '18px',
114
+ fontWeight: '600',
115
+ letterSpacing: '0.18em',
116
+ color: 'rgba(255,255,255,0.92)',
117
+ textShadow: '0 1px 3px rgba(0,0,0,0.4)',
118
+ }, children: maskedNum }), _jsxs("div", { style: {
119
+ position: 'absolute',
120
+ bottom: '22px',
121
+ left: '22px',
122
+ right: '22px',
123
+ display: 'flex',
124
+ alignItems: 'flex-end',
125
+ justifyContent: 'space-between',
126
+ }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: '9px', color: 'rgba(255,255,255,0.5)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3px' }, children: "Card holder" }), _jsx("div", { style: { fontSize: '13px', fontWeight: '600', color: 'rgba(255,255,255,0.9)', textTransform: 'uppercase', letterSpacing: '0.05em', maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: displayName })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: '9px', color: 'rgba(255,255,255,0.5)', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '3px' }, children: "Expires" }), _jsx("div", { style: { fontFamily: 'ui-monospace, monospace', fontSize: '13px', fontWeight: '600', color: 'rgba(255,255,255,0.9)' }, children: displayExpiry })] })] })] }), _jsxs("div", { style: backStyle, children: [_jsx("div", { style: {
127
+ position: 'absolute',
128
+ top: '40px',
129
+ left: 0,
130
+ right: 0,
131
+ height: '46px',
132
+ background: 'linear-gradient(180deg, #1a1a1a 0%, #111 50%, #1a1a1a 100%)',
133
+ } }), _jsxs("div", { style: {
134
+ position: 'absolute',
135
+ top: '108px',
136
+ left: '22px',
137
+ right: '22px',
138
+ display: 'flex',
139
+ alignItems: 'center',
140
+ gap: '12px',
141
+ }, children: [_jsx("div", { style: {
142
+ flex: 1,
143
+ height: '40px',
144
+ background: 'repeating-linear-gradient(90deg, #e8e0d0 0px, #e8e0d0 8px, #d4cabb 8px, #d4cabb 10px)',
145
+ borderRadius: '4px',
146
+ display: 'flex',
147
+ alignItems: 'center',
148
+ paddingLeft: '8px',
149
+ }, children: _jsx("span", { style: { fontFamily: 'cursive, serif', fontSize: '12px', color: '#555', opacity: 0.6 }, children: "Authorized signature" }) }), _jsxs("div", { style: {
150
+ width: '60px',
151
+ height: '40px',
152
+ background: 'white',
153
+ borderRadius: '4px',
154
+ display: 'flex',
155
+ flexDirection: 'column',
156
+ alignItems: 'center',
157
+ justifyContent: 'center',
158
+ boxShadow: cvvFocused ? '0 0 0 2px #6366f1, 0 0 12px rgba(99,102,241,0.4)' : 'none',
159
+ transition: 'box-shadow 0.3s ease',
160
+ }, children: [_jsx("span", { style: { fontSize: '9px', color: '#888', marginBottom: '2px' }, children: "CVV" }), _jsx("span", { style: { fontFamily: 'ui-monospace, monospace', fontSize: '14px', fontWeight: '700', color: '#111', letterSpacing: '0.1em' }, children: displayCvv })] })] }), _jsx("div", { style: { position: 'absolute', bottom: '18px', right: '22px', opacity: 0.7 }, children: _jsx(NetworkLogo, { network: network }) })] })] }) }));
161
+ }
@@ -0,0 +1,26 @@
1
+ import type { SpendAlert, BillingAccountMember, BillingRole, BusinessProfile, TaxSettings, ComplianceItem, SubscriptionDiscount, DiscountCode } from '../types';
2
+ type SubTab = 'alerts' | 'team' | 'business' | 'tax' | 'support' | 'promotions';
3
+ export interface BillingSettingsProps {
4
+ spendAlerts: SpendAlert[];
5
+ onCreateSpendAlert?: (title: string, threshold: number) => Promise<void>;
6
+ onUpdateSpendAlert?: (id: string, title: string, threshold: number) => Promise<void>;
7
+ onDeleteSpendAlert?: (id: string) => Promise<void>;
8
+ accountMembers: BillingAccountMember[];
9
+ userRole: BillingRole;
10
+ onInviteMember?: (email: string, role: BillingRole) => Promise<void>;
11
+ onChangeMemberRole?: (memberId: string, role: BillingRole) => Promise<void>;
12
+ onRemoveMember?: (memberId: string) => Promise<void>;
13
+ businessProfile: BusinessProfile;
14
+ onSaveBusinessProfile?: (profile: BusinessProfile) => Promise<void>;
15
+ taxSettings: TaxSettings;
16
+ complianceItems: ComplianceItem[];
17
+ onToggleAutomaticTax?: (enabled: boolean) => Promise<void>;
18
+ currentSupportTier?: string;
19
+ onSubscribeSupport?: (tierId: string) => Promise<void>;
20
+ discount?: SubscriptionDiscount | null;
21
+ onApplyDiscount?: (code: string) => Promise<DiscountCode | null>;
22
+ defaultSubTab?: SubTab;
23
+ }
24
+ export declare function BillingSettings(props: BillingSettingsProps): import("react/jsx-runtime").JSX.Element;
25
+ export {};
26
+ //# sourceMappingURL=billing-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing-settings.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/billing-settings.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACpB,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,YAAY,EACb,MAAM,UAAU,CAAA;AAEjB,KAAK,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,CAAA;AAE/E,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpF,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,cAAc,EAAE,oBAAoB,EAAE,CAAA;IACtC,QAAQ,EAAE,WAAW,CAAA;IACrB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3E,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,eAAe,EAAE,eAAe,CAAA;IAChC,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,WAAW,EAAE,WAAW,CAAA;IACxB,eAAe,EAAE,cAAc,EAAE,CAAA;IACjC,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;IACtC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;IAChE,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAWD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAyD1D"}
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { SpendAlertsPanel } from './spend-alerts';
5
+ import { AccountMembers } from './account-members';
6
+ import { BusinessProfilePanel } from './business-profile-panel';
7
+ import { TaxCompliancePanel } from './tax-compliance-panel';
8
+ import { SupportTiersPanel } from './support-tiers-panel';
9
+ import { PromotionsPanel } from './promotions-panel';
10
+ const subTabs = [
11
+ { id: 'alerts', label: 'Spend Alerts' },
12
+ { id: 'team', label: 'Team' },
13
+ { id: 'business', label: 'Business Info' },
14
+ { id: 'tax', label: 'Tax & Compliance' },
15
+ { id: 'support', label: 'Support' },
16
+ { id: 'promotions', label: 'Promotions' },
17
+ ];
18
+ export function BillingSettings(props) {
19
+ const [active, setActive] = useState(props.defaultSubTab ?? 'alerts');
20
+ return (_jsxs("div", { children: [_jsx("div", { className: "mb-6 flex flex-wrap gap-1 rounded-xl bg-bg-card p-1 border border-border", children: subTabs.map(t => (_jsx("button", { type: "button", onClick: () => setActive(t.id), className: `rounded-lg px-3 py-2 text-sm font-medium transition ${active === t.id
21
+ ? 'bg-bg-elevated text-text'
22
+ : 'text-text-muted hover:text-text-secondary'}`, children: t.label }, t.id))) }), active === 'alerts' && (_jsx(SpendAlertsPanel, { alerts: props.spendAlerts, onCreateAlert: props.onCreateSpendAlert, onUpdateAlert: props.onUpdateSpendAlert, onDeleteAlert: props.onDeleteSpendAlert })), active === 'team' && (_jsx(AccountMembers, { members: props.accountMembers, userRole: props.userRole, onInvite: props.onInviteMember, onChangeRole: props.onChangeMemberRole, onRemove: props.onRemoveMember })), active === 'business' && (_jsx(BusinessProfilePanel, { profile: props.businessProfile, onSave: props.onSaveBusinessProfile })), active === 'tax' && (_jsx(TaxCompliancePanel, { taxSettings: props.taxSettings, complianceItems: props.complianceItems, onToggleAutomaticTax: props.onToggleAutomaticTax })), active === 'support' && (_jsx(SupportTiersPanel, { currentTier: props.currentSupportTier, onSubscribe: props.onSubscribeSupport })), active === 'promotions' && (_jsx(PromotionsPanel, { discount: props.discount ?? null, onApplyDiscount: props.onApplyDiscount }))] }));
23
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"business-profile-panel.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/business-profile-panel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE/C,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,eAAe,CAAA;IACxB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,2CAgLpE"}
1
+ {"version":3,"file":"business-profile-panel.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/business-profile-panel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE/C,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,eAAe,CAAA;IACxB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,2CAgLpE"}
@@ -1,6 +1,7 @@
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 INPUT_CLASS = 'w-full rounded-lg border border-border bg-bg-input px-3 py-2 text-sm text-text outline-none transition focus:border-brand';
4
5
  export function BusinessProfilePanel(props) {
5
6
  const { profile, onSave } = props;
6
7
  const [draft, setDraft] = React.useState(profile);
@@ -20,25 +21,25 @@ export function BusinessProfilePanel(props) {
20
21
  setSaving(false);
21
22
  }
22
23
  }, [draft, onSave]);
23
- 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: "border-b border-zinc-200 p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-zinc-900", children: "Business profile" }), _jsx("p", { className: "text-sm text-zinc-500", children: "Keep your legal and support details accurate for invoices, tax, and compliance workflows." })] }), _jsxs("div", { className: "grid gap-4 p-4 md:grid-cols-2", children: [_jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-zinc-700", children: "Legal name" }), _jsx("input", { value: draft.legalName, onChange: (e) => setDraft((prev) => ({ ...prev, legalName: e.target.value })), className: "w-full rounded-lg border border-zinc-300 px-3 py-2 outline-none transition focus:border-zinc-900" })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-zinc-700", children: "Display name" }), _jsx("input", { value: draft.displayName, onChange: (e) => setDraft((prev) => ({ ...prev, displayName: e.target.value })), className: "w-full rounded-lg border border-zinc-300 px-3 py-2 outline-none transition focus:border-zinc-900" })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-zinc-700", children: "Website" }), _jsx("input", { value: draft.website || '', onChange: (e) => setDraft((prev) => ({ ...prev, website: e.target.value })), className: "w-full rounded-lg border border-zinc-300 px-3 py-2 outline-none transition focus:border-zinc-900" })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-zinc-700", children: "Support email" }), _jsx("input", { value: draft.supportEmail || '', onChange: (e) => setDraft((prev) => ({ ...prev, supportEmail: e.target.value })), className: "w-full rounded-lg border border-zinc-300 px-3 py-2 outline-none transition focus:border-zinc-900" })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-zinc-700", children: "Statement descriptor" }), _jsx("input", { value: draft.statementDescriptor || '', onChange: (e) => setDraft((prev) => ({ ...prev, statementDescriptor: e.target.value })), className: "w-full rounded-lg border border-zinc-300 px-3 py-2 outline-none transition focus:border-zinc-900" })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-zinc-700", children: "Tax ID" }), _jsx("input", { value: draft.taxId || '', onChange: (e) => setDraft((prev) => ({ ...prev, taxId: e.target.value })), className: "w-full rounded-lg border border-zinc-300 px-3 py-2 outline-none transition focus:border-zinc-900" })] })] }), _jsxs("div", { className: "border-t border-zinc-200 bg-zinc-50 p-4", children: [_jsx("p", { className: "mb-2 text-sm font-medium text-zinc-700", children: "Registered address" }), _jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [_jsx("input", { value: draft.registeredAddress.line1, onChange: (e) => setDraft((prev) => ({
24
+ 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: "border-b border-border p-4", children: [_jsx("h3", { className: "text-lg font-semibold text-text", children: "Business profile" }), _jsx("p", { className: "text-sm text-text-muted", children: "Keep your legal and support details accurate for invoices, tax, and compliance workflows." })] }), _jsxs("div", { className: "grid gap-4 p-4 md:grid-cols-2", children: [_jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-text-secondary", children: "Legal name" }), _jsx("input", { value: draft.legalName, onChange: (e) => setDraft((prev) => ({ ...prev, legalName: e.target.value })), className: INPUT_CLASS })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-text-secondary", children: "Display name" }), _jsx("input", { value: draft.displayName, onChange: (e) => setDraft((prev) => ({ ...prev, displayName: e.target.value })), className: INPUT_CLASS })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-text-secondary", children: "Website" }), _jsx("input", { value: draft.website || '', onChange: (e) => setDraft((prev) => ({ ...prev, website: e.target.value })), className: INPUT_CLASS })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-text-secondary", children: "Support email" }), _jsx("input", { value: draft.supportEmail || '', onChange: (e) => setDraft((prev) => ({ ...prev, supportEmail: e.target.value })), className: INPUT_CLASS })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-text-secondary", children: "Statement descriptor" }), _jsx("input", { value: draft.statementDescriptor || '', onChange: (e) => setDraft((prev) => ({ ...prev, statementDescriptor: e.target.value })), className: INPUT_CLASS })] }), _jsxs("label", { className: "space-y-1 text-sm", children: [_jsx("span", { className: "font-medium text-text-secondary", children: "Tax ID" }), _jsx("input", { value: draft.taxId || '', onChange: (e) => setDraft((prev) => ({ ...prev, taxId: e.target.value })), className: INPUT_CLASS })] })] }), _jsxs("div", { className: "border-t border-border bg-bg-elevated p-4", children: [_jsx("p", { className: "mb-2 text-sm font-medium text-text-secondary", children: "Registered address" }), _jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [_jsx("input", { value: draft.registeredAddress.line1, onChange: (e) => setDraft((prev) => ({
24
25
  ...prev,
25
26
  registeredAddress: { ...prev.registeredAddress, line1: e.target.value },
26
- })), placeholder: "Line 1", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("input", { value: draft.registeredAddress.line2 || '', onChange: (e) => setDraft((prev) => ({
27
+ })), placeholder: "Line 1", className: `${INPUT_CLASS} text-sm` }), _jsx("input", { value: draft.registeredAddress.line2 || '', onChange: (e) => setDraft((prev) => ({
27
28
  ...prev,
28
29
  registeredAddress: { ...prev.registeredAddress, line2: e.target.value },
29
- })), placeholder: "Line 2", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("input", { value: draft.registeredAddress.city, onChange: (e) => setDraft((prev) => ({
30
+ })), placeholder: "Line 2", className: `${INPUT_CLASS} text-sm` }), _jsx("input", { value: draft.registeredAddress.city, onChange: (e) => setDraft((prev) => ({
30
31
  ...prev,
31
32
  registeredAddress: { ...prev.registeredAddress, city: e.target.value },
32
- })), placeholder: "City", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("input", { value: draft.registeredAddress.state || '', onChange: (e) => setDraft((prev) => ({
33
+ })), placeholder: "City", className: `${INPUT_CLASS} text-sm` }), _jsx("input", { value: draft.registeredAddress.state || '', onChange: (e) => setDraft((prev) => ({
33
34
  ...prev,
34
35
  registeredAddress: { ...prev.registeredAddress, state: e.target.value },
35
- })), placeholder: "State/Province", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("input", { value: draft.registeredAddress.zip, onChange: (e) => setDraft((prev) => ({
36
+ })), placeholder: "State/Province", className: `${INPUT_CLASS} text-sm` }), _jsx("input", { value: draft.registeredAddress.zip, onChange: (e) => setDraft((prev) => ({
36
37
  ...prev,
37
38
  registeredAddress: { ...prev.registeredAddress, zip: e.target.value },
38
- })), placeholder: "Postal code", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" }), _jsx("input", { value: draft.registeredAddress.country, onChange: (e) => setDraft((prev) => ({
39
+ })), placeholder: "Postal code", className: `${INPUT_CLASS} text-sm` }), _jsx("input", { value: draft.registeredAddress.country, onChange: (e) => setDraft((prev) => ({
39
40
  ...prev,
40
41
  registeredAddress: { ...prev.registeredAddress, country: e.target.value },
41
- })), placeholder: "Country", className: "rounded-lg border border-zinc-300 px-3 py-2 text-sm outline-none transition focus:border-zinc-900" })] })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t border-zinc-200 p-4", children: [_jsx("p", { className: "text-xs text-zinc-500", children: hasFinanceAndLegal
42
+ })), placeholder: "Country", className: `${INPUT_CLASS} text-sm` })] })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t border-border p-4", children: [_jsx("p", { className: "text-xs text-text-muted", children: hasFinanceAndLegal
42
43
  ? 'Finance and legal contacts are configured.'
43
- : 'Tip: add at least one finance or legal contact for smooth audits.' }), _jsx("button", { type: "button", onClick: handleSave, disabled: !onSave || saving, className: "rounded-lg bg-zinc-900 px-3 py-2 text-sm font-medium text-white transition hover:bg-zinc-700 disabled:cursor-not-allowed disabled:opacity-60", children: saving ? 'Saving' : 'Save profile' })] })] }) }));
44
+ : 'Tip: add at least one finance or legal contact for smooth audits.' }), _jsx("button", { type: "button", onClick: handleSave, disabled: !onSave || saving, className: "rounded-lg bg-text px-3 py-2 text-sm font-medium text-bg transition hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-60", children: saving ? 'Saving\u2026' : 'Save profile' })] })] }) }));
44
45
  }
@@ -0,0 +1,18 @@
1
+ export interface CardFormProps {
2
+ /** Called with tokenization result when card is saved */
3
+ onSuccess: (result: {
4
+ token: string;
5
+ brand: string;
6
+ last4: string;
7
+ expiryMonth: string;
8
+ expiryYear: string;
9
+ provider: string;
10
+ }) => Promise<void>;
11
+ /** Base URL for commerce API, e.g. https://api.hanzo.ai/api/v1 */
12
+ apiBaseUrl?: string;
13
+ /** Auth token for API calls */
14
+ authToken?: string;
15
+ disabled?: boolean;
16
+ }
17
+ export declare function CardForm({ onSuccess, apiBaseUrl, authToken, disabled }: CardFormProps): import("react/jsx-runtime").JSX.Element;
18
+ //# sourceMappingURL=card-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-form.d.ts","sourceRoot":"","sources":["../../../../src/billing/components/card-form.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,SAAS,EAAE,CAAC,MAAM,EAAE;QAClB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;KACjB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAyDD,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,aAAa,2CAmMrF"}
@@ -0,0 +1,139 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ // -- Card number formatting --
5
+ function formatCardNumber(value) {
6
+ const digits = value.replace(/\D/g, '').slice(0, 16);
7
+ return digits.replace(/(.{4})/g, '$1 ').trim();
8
+ }
9
+ function formatExpiry(value) {
10
+ const digits = value.replace(/\D/g, '').slice(0, 4);
11
+ if (digits.length >= 3)
12
+ return digits.slice(0, 2) + '/' + digits.slice(2);
13
+ return digits;
14
+ }
15
+ function detectBrand(digits) {
16
+ if (digits.startsWith('4'))
17
+ return 'visa';
18
+ if (/^5[1-5]/.test(digits) || /^2[2-7]/.test(digits))
19
+ return 'mastercard';
20
+ if (/^3[47]/.test(digits))
21
+ return 'amex';
22
+ if (/^6(?:011|5)/.test(digits))
23
+ return 'discover';
24
+ return '';
25
+ }
26
+ // -- Shared input style --
27
+ const inputStyle = {
28
+ width: '100%',
29
+ background: 'rgba(255,255,255,0.04)',
30
+ border: '1px solid rgba(255,255,255,0.10)',
31
+ borderRadius: '8px',
32
+ padding: '10px 12px',
33
+ color: '#fafafa',
34
+ fontSize: '14px',
35
+ fontFamily: 'ui-sans-serif, system-ui, sans-serif',
36
+ outline: 'none',
37
+ transition: 'border-color 0.2s',
38
+ boxSizing: 'border-box',
39
+ };
40
+ const inputFocusStyle = {
41
+ borderColor: 'rgba(255,255,255,0.35)',
42
+ };
43
+ const labelStyle = {
44
+ display: 'block',
45
+ fontSize: '12px',
46
+ fontWeight: 500,
47
+ color: 'rgba(255,255,255,0.5)',
48
+ marginBottom: '4px',
49
+ textTransform: 'uppercase',
50
+ letterSpacing: '0.05em',
51
+ };
52
+ // -- Component --
53
+ const COMMERCE_API = process.env.NEXT_PUBLIC_COMMERCE_API ?? 'https://api.hanzo.ai/api/v1';
54
+ export function CardForm({ onSuccess, apiBaseUrl, authToken, disabled }) {
55
+ const [number, setNumber] = React.useState('');
56
+ const [name, setName] = React.useState('');
57
+ const [expiry, setExpiry] = React.useState('');
58
+ const [cvc, setCvc] = React.useState('');
59
+ const [zip, setZip] = React.useState('');
60
+ const [error, setError] = React.useState(null);
61
+ const [loading, setLoading] = React.useState(false);
62
+ const [focusedField, setFocusedField] = React.useState(null);
63
+ const rawNumber = number.replace(/\D/g, '');
64
+ const brand = detectBrand(rawNumber);
65
+ const cvcLength = brand === 'amex' ? 4 : 3;
66
+ const handleSubmit = React.useCallback(async (e) => {
67
+ e.preventDefault();
68
+ setError(null);
69
+ if (rawNumber.length < 13) {
70
+ setError('Invalid card number');
71
+ return;
72
+ }
73
+ if (!expiry.includes('/')) {
74
+ setError('Invalid expiry (MM/YY)');
75
+ return;
76
+ }
77
+ const [mm, yy] = expiry.split('/');
78
+ if (!mm || !yy || mm.length !== 2 || yy.length !== 2) {
79
+ setError('Invalid expiry');
80
+ return;
81
+ }
82
+ if (cvc.length < 3) {
83
+ setError('Invalid CVC');
84
+ return;
85
+ }
86
+ if (!name.trim()) {
87
+ setError('Cardholder name is required');
88
+ return;
89
+ }
90
+ setLoading(true);
91
+ try {
92
+ const base = apiBaseUrl ?? COMMERCE_API;
93
+ const headers = { 'Content-Type': 'application/json' };
94
+ if (authToken)
95
+ headers['Authorization'] = `Bearer ${authToken}`;
96
+ const res = await fetch(`${base}/billing/card/tokenize`, {
97
+ method: 'POST',
98
+ headers,
99
+ body: JSON.stringify({
100
+ number: rawNumber,
101
+ expiry_month: mm,
102
+ expiry_year: `20${yy}`,
103
+ cvc,
104
+ name: name.trim(),
105
+ zip: zip.trim(),
106
+ }),
107
+ });
108
+ if (!res.ok) {
109
+ const data = await res.json().catch(() => ({}));
110
+ throw new Error(data.error ?? `Tokenization failed (${res.status})`);
111
+ }
112
+ const data = await res.json();
113
+ await onSuccess(data);
114
+ }
115
+ catch (err) {
116
+ setError(err instanceof Error ? err.message : 'Card processing error');
117
+ }
118
+ finally {
119
+ setLoading(false);
120
+ }
121
+ }, [rawNumber, expiry, cvc, name, zip, apiBaseUrl, authToken, onSuccess]);
122
+ const fieldStyle = (field) => ({
123
+ ...inputStyle,
124
+ ...(focusedField === field ? inputFocusStyle : {}),
125
+ });
126
+ const canSubmit = !disabled && !loading && rawNumber.length >= 13 && !!expiry && !!cvc && !!name.trim();
127
+ return (_jsxs("form", { onSubmit: handleSubmit, style: { display: 'flex', flexDirection: 'column', gap: '12px' }, children: [_jsxs("div", { children: [_jsx("label", { style: labelStyle, children: "Card number" }), _jsxs("div", { style: { position: 'relative' }, children: [_jsx("input", { type: "text", inputMode: "numeric", autoComplete: "cc-number", placeholder: "1234 5678 9012 3456", value: number, onChange: e => setNumber(formatCardNumber(e.target.value)), onFocus: () => setFocusedField('number'), onBlur: () => setFocusedField(null), style: { ...fieldStyle('number'), paddingRight: brand ? '44px' : '12px' }, disabled: disabled || loading, maxLength: 19 }), brand && (_jsx("span", { style: { position: 'absolute', right: '12px', top: '50%', transform: 'translateY(-50%)', fontSize: '11px', fontWeight: 600, color: 'rgba(255,255,255,0.5)', textTransform: 'uppercase' }, children: brand }))] })] }), _jsxs("div", { children: [_jsx("label", { style: labelStyle, children: "Cardholder name" }), _jsx("input", { type: "text", autoComplete: "cc-name", placeholder: "Jane Smith", value: name, onChange: e => setName(e.target.value), onFocus: () => setFocusedField('name'), onBlur: () => setFocusedField(null), style: fieldStyle('name'), disabled: disabled || loading })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }, children: [_jsxs("div", { children: [_jsx("label", { style: labelStyle, children: "Expiry" }), _jsx("input", { type: "text", inputMode: "numeric", autoComplete: "cc-exp", placeholder: "MM/YY", value: expiry, onChange: e => setExpiry(formatExpiry(e.target.value)), onFocus: () => setFocusedField('expiry'), onBlur: () => setFocusedField(null), style: fieldStyle('expiry'), disabled: disabled || loading, maxLength: 5 })] }), _jsxs("div", { children: [_jsx("label", { style: labelStyle, children: brand === 'amex' ? 'CID (4 digits)' : 'CVC' }), _jsx("input", { type: "password", inputMode: "numeric", autoComplete: "cc-csc", placeholder: brand === 'amex' ? '1234' : '123', value: cvc, onChange: e => setCvc(e.target.value.replace(/\D/g, '').slice(0, cvcLength)), onFocus: () => setFocusedField('cvc'), onBlur: () => setFocusedField(null), style: fieldStyle('cvc'), disabled: disabled || loading, maxLength: cvcLength })] })] }), _jsxs("div", { children: [_jsxs("label", { style: labelStyle, children: ["ZIP / Postal code", ' ', _jsx("span", { style: { color: 'rgba(255,255,255,0.25)' }, children: "(optional)" })] }), _jsx("input", { type: "text", autoComplete: "postal-code", placeholder: "10001", value: zip, onChange: e => setZip(e.target.value.slice(0, 10)), onFocus: () => setFocusedField('zip'), onBlur: () => setFocusedField(null), style: fieldStyle('zip'), disabled: disabled || loading })] }), error && (_jsx("p", { style: { margin: 0, fontSize: '13px', color: '#f87171' }, children: error })), _jsx("button", { type: "submit", disabled: !canSubmit, style: {
128
+ width: '100%',
129
+ borderRadius: '8px',
130
+ border: 'none',
131
+ backgroundColor: canSubmit ? 'rgb(250,250,250)' : 'rgba(250,250,250,0.3)',
132
+ color: canSubmit ? 'rgb(9,9,11)' : 'rgba(9,9,11,0.5)',
133
+ padding: '10px 16px',
134
+ fontSize: '14px',
135
+ fontWeight: 500,
136
+ cursor: canSubmit ? 'pointer' : 'not-allowed',
137
+ transition: 'opacity 0.15s',
138
+ }, 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" })] }), "Encrypted \u00B7 PCI-compliant \u00B7 Powered by Hanzo Commerce"] })] }));
139
+ }