@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.
- package/dist/assets/crypto.d.ts.map +1 -1
- package/dist/assets/file.d.ts.map +1 -1
- package/dist/assets/general.d.ts.map +1 -1
- package/dist/assets/hanzo-logo.d.ts.map +1 -1
- package/dist/assets/llm-provider.d.ts.map +1 -1
- package/dist/components/cal-embed.d.ts.map +1 -1
- package/dist/frameworks/react/hooks/index.d.ts.map +1 -1
- package/dist/frameworks/react-native/utils.d.ts.map +1 -1
- package/dist/frameworks/svelte/utils.d.ts.map +1 -1
- package/dist/frameworks/vue/utils.d.ts.map +1 -1
- package/dist/helpers/file.d.ts.map +1 -1
- package/dist/helpers/memoization.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/models/index.js +35 -0
- package/dist/models/index.mjs +35 -0
- package/dist/navigation/index.js +2 -1
- package/dist/navigation/index.mjs +2 -1
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/cal-embed.d.ts.map +1 -1
- package/dist/primitives/chart.d.ts.map +1 -1
- package/dist/primitives/chat/chat-input.d.ts.map +1 -1
- package/dist/primitives/combobox.d.ts.map +1 -1
- package/dist/primitives/command.d.ts.map +1 -1
- package/dist/primitives/copy-to-clipboard-icon.d.ts.map +1 -1
- package/dist/primitives/dots-loader.d.ts.map +1 -1
- package/dist/primitives/error-message.d.ts.map +1 -1
- package/dist/primitives/file-uploader.d.ts.map +1 -1
- package/dist/primitives/form.d.ts.map +1 -1
- package/dist/primitives/input-otp.d.ts.map +1 -1
- package/dist/primitives/markdown-preview.d.ts.map +1 -1
- package/dist/primitives/pretty-json-print.d.ts.map +1 -1
- package/dist/primitives/resizable.d.ts +4 -20
- package/dist/primitives/resizable.d.ts.map +1 -1
- package/dist/primitives/resizable.js +4 -4
- package/dist/primitives/sonner.d.ts.map +1 -1
- package/dist/primitives/text-link.d.ts.map +1 -1
- package/dist/primitives/textfield.d.ts.map +1 -1
- package/dist/primitives/toast.d.ts.map +1 -1
- package/dist/resizable.js +1 -1
- package/dist/resizable.mjs +1 -1
- package/dist/src/billing/components/account-members.d.ts +10 -0
- package/dist/src/billing/components/account-members.d.ts.map +1 -0
- package/dist/src/billing/components/account-members.js +63 -0
- package/dist/src/billing/components/account-switcher.d.ts +9 -0
- package/dist/src/billing/components/account-switcher.d.ts.map +1 -0
- package/dist/src/billing/components/account-switcher.js +52 -0
- package/dist/src/billing/components/animated-card.d.ts +9 -0
- package/dist/src/billing/components/animated-card.d.ts.map +1 -0
- package/dist/src/billing/components/animated-card.js +161 -0
- package/dist/src/billing/components/billing-settings.d.ts +26 -0
- package/dist/src/billing/components/billing-settings.d.ts.map +1 -0
- package/dist/src/billing/components/billing-settings.js +23 -0
- package/dist/src/billing/components/business-profile-panel.d.ts.map +1 -1
- package/dist/src/billing/components/business-profile-panel.js +9 -8
- package/dist/src/billing/components/card-form.d.ts +18 -0
- package/dist/src/billing/components/card-form.d.ts.map +1 -0
- package/dist/src/billing/components/card-form.js +139 -0
- package/dist/src/billing/components/cost-explorer.d.ts +7 -0
- package/dist/src/billing/components/cost-explorer.d.ts.map +1 -0
- package/dist/src/billing/components/cost-explorer.js +73 -0
- package/dist/src/billing/components/credits-panel.d.ts +8 -0
- package/dist/src/billing/components/credits-panel.d.ts.map +1 -0
- package/dist/src/billing/components/credits-panel.js +58 -0
- package/dist/src/billing/components/guided-setup.d.ts +13 -0
- package/dist/src/billing/components/guided-setup.d.ts.map +1 -0
- package/dist/src/billing/components/guided-setup.js +44 -0
- package/dist/src/billing/components/index.d.ts +34 -0
- package/dist/src/billing/components/index.d.ts.map +1 -1
- package/dist/src/billing/components/index.js +17 -0
- package/dist/src/billing/components/invoice-manager.js +13 -13
- package/dist/src/billing/components/invoices-payments.d.ts +7 -0
- package/dist/src/billing/components/invoices-payments.d.ts.map +1 -0
- package/dist/src/billing/components/invoices-payments.js +44 -0
- package/dist/src/billing/components/overview-dashboard.d.ts +31 -0
- package/dist/src/billing/components/overview-dashboard.d.ts.map +1 -0
- package/dist/src/billing/components/overview-dashboard.js +105 -0
- package/dist/src/billing/components/payment-manager.d.ts.map +1 -1
- package/dist/src/billing/components/payment-manager.js +267 -83
- package/dist/src/billing/components/promotions-panel.d.ts +7 -0
- package/dist/src/billing/components/promotions-panel.d.ts.map +1 -0
- package/dist/src/billing/components/promotions-panel.js +48 -0
- package/dist/src/billing/components/spend-alerts.d.ts +9 -0
- package/dist/src/billing/components/spend-alerts.d.ts.map +1 -0
- package/dist/src/billing/components/spend-alerts.js +99 -0
- package/dist/src/billing/components/square-card-form.d.ts +32 -0
- package/dist/src/billing/components/square-card-form.d.ts.map +1 -0
- package/dist/src/billing/components/square-card-form.js +179 -0
- package/dist/src/billing/components/status-bar.d.ts +12 -0
- package/dist/src/billing/components/status-bar.d.ts.map +1 -0
- package/dist/src/billing/components/status-bar.js +32 -0
- package/dist/src/billing/components/subscription-portal.d.ts +2 -1
- package/dist/src/billing/components/subscription-portal.d.ts.map +1 -1
- package/dist/src/billing/components/subscription-portal.js +123 -26
- package/dist/src/billing/components/support-tiers-panel.d.ts +6 -0
- package/dist/src/billing/components/support-tiers-panel.d.ts.map +1 -0
- package/dist/src/billing/components/support-tiers-panel.js +90 -0
- package/dist/src/billing/components/tax-compliance-panel.js +5 -5
- package/dist/src/billing/components/transactions-panel.d.ts +8 -0
- package/dist/src/billing/components/transactions-panel.d.ts.map +1 -0
- package/dist/src/billing/components/transactions-panel.js +64 -0
- package/dist/src/billing/components/usage-panel.d.ts +7 -0
- package/dist/src/billing/components/usage-panel.d.ts.map +1 -0
- package/dist/src/billing/components/usage-panel.js +64 -0
- package/dist/src/billing/types/index.d.ts +136 -1
- package/dist/src/billing/types/index.d.ts.map +1 -1
- package/dist/src/form/form.d.ts.map +1 -1
- package/dist/src/hooks/use-copy-clipboard.d.ts.map +1 -1
- package/dist/src/hooks/use-fill-ids.d.ts.map +1 -1
- package/dist/src/hooks/use-scroll-restoration.d.ts.map +1 -1
- package/dist/src/models/ModelCard.d.ts +25 -0
- package/dist/src/models/ModelCard.d.ts.map +1 -0
- package/dist/src/models/ModelCard.js +73 -0
- package/dist/src/models/ModelLibrary.d.ts +41 -0
- package/dist/src/models/ModelLibrary.d.ts.map +1 -0
- package/dist/src/models/ModelLibrary.js +63 -0
- package/dist/src/models/ModelTable.d.ts +17 -0
- package/dist/src/models/ModelTable.d.ts.map +1 -0
- package/dist/src/models/ModelTable.js +35 -0
- package/dist/src/models/ZenEnso.d.ts +17 -0
- package/dist/src/models/ZenEnso.d.ts.map +1 -0
- package/dist/src/models/ZenEnso.js +50 -0
- package/dist/src/models/index.d.ts +23 -0
- package/dist/src/models/index.d.ts.map +1 -0
- package/dist/src/models/index.js +18 -0
- package/dist/src/models/types.d.ts +40 -0
- package/dist/src/models/types.d.ts.map +1 -0
- package/dist/src/models/types.js +7 -0
- package/dist/src/navigation/hanzo-shell/AppSwitcher.d.ts +8 -0
- package/dist/src/navigation/hanzo-shell/AppSwitcher.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/AppSwitcher.js +19 -0
- package/dist/src/navigation/hanzo-shell/BeamAvatar.d.ts +9 -0
- package/dist/src/navigation/hanzo-shell/BeamAvatar.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/BeamAvatar.js +38 -0
- package/dist/src/navigation/hanzo-shell/HanzoCommandPalette.d.ts +28 -0
- package/dist/src/navigation/hanzo-shell/HanzoCommandPalette.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/HanzoCommandPalette.js +124 -0
- package/dist/src/navigation/hanzo-shell/HanzoHeader.d.ts +37 -0
- package/dist/src/navigation/hanzo-shell/HanzoHeader.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/HanzoHeader.js +43 -0
- package/dist/src/navigation/hanzo-shell/HanzoMark.d.ts +17 -0
- package/dist/src/navigation/hanzo-shell/HanzoMark.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/HanzoMark.js +57 -0
- package/dist/src/navigation/hanzo-shell/UserAvatar.d.ts +15 -0
- package/dist/src/navigation/hanzo-shell/UserAvatar.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/UserAvatar.js +44 -0
- package/dist/src/navigation/hanzo-shell/UserOrgDropdown.d.ts +11 -0
- package/dist/src/navigation/hanzo-shell/UserOrgDropdown.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/UserOrgDropdown.js +26 -0
- package/dist/src/navigation/hanzo-shell/index.d.ts +14 -0
- package/dist/src/navigation/hanzo-shell/index.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/index.js +9 -0
- package/dist/src/navigation/hanzo-shell/types.d.ts +49 -0
- package/dist/src/navigation/hanzo-shell/types.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/types.js +32 -0
- package/dist/src/navigation/hanzo-shell/useHanzoAuth.d.ts +16 -0
- package/dist/src/navigation/hanzo-shell/useHanzoAuth.d.ts.map +1 -0
- package/dist/src/navigation/hanzo-shell/useHanzoAuth.js +93 -0
- package/dist/src/navigation/index.d.ts +2 -0
- package/dist/src/navigation/index.d.ts.map +1 -1
- package/dist/src/navigation/index.js +2 -0
- package/dist/src/ui/banner.d.ts +1 -1
- package/dist/util/blob.d.ts.map +1 -1
- package/dist/util/blob.js +1 -1
- package/dist/util/date.d.ts.map +1 -1
- package/dist/util/debounce.d.ts.map +1 -1
- package/dist/util/file.d.ts.map +1 -1
- package/dist/util/format-and-abbreviate-as-currency.d.ts +7 -1
- package/dist/util/format-and-abbreviate-as-currency.d.ts.map +1 -1
- package/dist/util/format-text.d.ts.map +1 -1
- package/dist/util/format-to-max-char.d.ts +7 -1
- package/dist/util/format-to-max-char.d.ts.map +1 -1
- package/dist/util/index.mjs +1 -1
- package/dist/util/number-abbreviate.d.ts.map +1 -1
- package/dist/util/specifier.d.ts.map +1 -1
- package/dist/util/step-animation.d.ts.map +1 -1
- package/package.json +20 -7
- package/dist/tailwind/typo-plugin/get-plugin-styles.d.ts +0 -595
- package/dist/tailwind/typo-plugin/get-plugin-styles.d.ts.map +0 -1
- package/dist/tailwind/typo-plugin/get-plugin-styles.js +0 -661
- package/dist/tailwind/typo-plugin/index.d.ts +0 -3
- package/dist/tailwind/typo-plugin/index.d.ts.map +0 -1
- package/dist/tailwind/typo-plugin/index.js +0 -102
- package/dist/tailwind/typo-plugin/utils.d.ts +0 -6
- package/dist/tailwind/typo-plugin/utils.d.ts.map +0 -1
- 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;
|
|
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-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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-
|
|
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
|
+
}
|