@burdenoff/microfe-billing 2026.526.1 → 2026.528.1
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/billing/BillingAdminRoutes.js +1 -1
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingUserRoutes.js +1 -1
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js +15 -8
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +13 -6
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +8 -4
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -2
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +44 -23
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +60 -31
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +9 -9
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +8 -8
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +16 -10
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +24 -16
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +5 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +46 -29
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +34 -22
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +10 -10
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js +3 -3
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js +7 -0
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +13 -13
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/PromoCodeInput.js +5 -5
- package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +4 -4
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
- package/dist/billing/modules/checkout/components/StripePaymentForm.js +11 -11
- package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +3 -3
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +8 -8
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +61 -52
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +22 -15
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +30 -21
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +9 -8
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +11 -8
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +22 -17
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +15 -8
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +17 -11
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +15 -11
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +21 -17
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +14 -11
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +32 -26
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +14 -10
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +18 -11
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +44 -32
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +35 -26
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +15 -15
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +11 -11
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +5 -5
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +9 -4
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +13 -8
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -2
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +14 -7
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +23 -9
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +11 -7
- package/dist/billing/modules/quota/pages/QuotaPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +6 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +5 -4
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +26 -12
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +356 -349
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +157 -123
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +13 -8
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +15 -7
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +30 -20
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +25 -16
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +50 -34
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/components/AccessDenied.js +2 -2
- package/dist/billing/shared/components/AccessDenied.js.map +1 -1
- package/dist/billing/shared/components/ActorIdentity.js +67 -0
- package/dist/billing/shared/components/ActorIdentity.js.map +1 -0
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +6 -2
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
- package/dist/billing/shared/components/EmptyState.js +7 -21
- package/dist/billing/shared/components/EmptyState.js.map +1 -1
- package/dist/billing/shared/components/PageHeader.js +7 -19
- package/dist/billing/shared/components/PageHeader.js.map +1 -1
- package/dist/billing/shared/components/ServerError.js +5 -4
- package/dist/billing/shared/components/ServerError.js.map +1 -1
- package/dist/billing/shared/components/index.js +1 -0
- package/dist/billing/shared/hooks/index.js +1 -0
- package/dist/billing/shared/hooks/useActorProfiles.js +113 -0
- package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -0
- package/package.json +2 -2
|
@@ -12,11 +12,11 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
12
12
|
return n === e ? t : n;
|
|
13
13
|
}, L = (e) => {
|
|
14
14
|
switch (e) {
|
|
15
|
-
case "PLAN": return /* @__PURE__ */ m(c, { className: "
|
|
16
|
-
case "ADDON": return /* @__PURE__ */ m(i, { className: "
|
|
17
|
-
case "CREDITS": return /* @__PURE__ */ m(o, { className: "
|
|
18
|
-
case "ORDER": return /* @__PURE__ */ m(d, { className: "
|
|
19
|
-
default: return /* @__PURE__ */ m(c, { className: "
|
|
15
|
+
case "PLAN": return /* @__PURE__ */ m(c, { className: "size-5" });
|
|
16
|
+
case "ADDON": return /* @__PURE__ */ m(i, { className: "size-5" });
|
|
17
|
+
case "CREDITS": return /* @__PURE__ */ m(o, { className: "size-5" });
|
|
18
|
+
case "ORDER": return /* @__PURE__ */ m(d, { className: "size-5" });
|
|
19
|
+
default: return /* @__PURE__ */ m(c, { className: "size-5" });
|
|
20
20
|
}
|
|
21
21
|
}, R = (t) => ({
|
|
22
22
|
PLAN: {
|
|
@@ -59,7 +59,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
59
59
|
className: "space-y-2",
|
|
60
60
|
children: U(e).map((e) => /* @__PURE__ */ h("li", {
|
|
61
61
|
className: "flex items-start gap-2",
|
|
62
|
-
children: [/* @__PURE__ */ m(a, { className: `
|
|
62
|
+
children: [/* @__PURE__ */ m(a, { className: `size-4 ${t.success.icon} shrink-0 mt-0.5` }), /* @__PURE__ */ h("div", {
|
|
63
63
|
className: "flex-1 min-w-0",
|
|
64
64
|
children: [/* @__PURE__ */ m("span", {
|
|
65
65
|
className: "text-sm text-foreground",
|
|
@@ -85,7 +85,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
85
85
|
className: "px-4 py-3 border-b border-border bg-muted/30",
|
|
86
86
|
children: /* @__PURE__ */ h("div", {
|
|
87
87
|
className: "flex items-center gap-2",
|
|
88
|
-
children: [/* @__PURE__ */ m(u, { className: "
|
|
88
|
+
children: [/* @__PURE__ */ m(u, { className: "size-4 text-muted-foreground" }), /* @__PURE__ */ m("h3", {
|
|
89
89
|
className: "font-semibold text-foreground",
|
|
90
90
|
children: I("billing.checkout.orderSummary", "Order Summary")
|
|
91
91
|
})]
|
|
@@ -95,7 +95,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
95
95
|
className: "divide-y divide-border",
|
|
96
96
|
children: S.length === 0 ? /* @__PURE__ */ h("div", {
|
|
97
97
|
className: "p-6 text-center",
|
|
98
|
-
children: [/* @__PURE__ */ m(c, { className: "mx-auto
|
|
98
|
+
children: [/* @__PURE__ */ m(c, { className: "mx-auto size-8 text-muted-foreground" }), /* @__PURE__ */ m("p", {
|
|
99
99
|
className: "mt-2 text-sm text-muted-foreground",
|
|
100
100
|
children: "No items in checkout"
|
|
101
101
|
})]
|
|
@@ -136,7 +136,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
136
136
|
disabled: e.quantity <= 1,
|
|
137
137
|
className: "p-2 rounded border border-border hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[36px] min-h-[36px] flex items-center justify-center",
|
|
138
138
|
"aria-label": `Decrease ${e.name} quantity`,
|
|
139
|
-
children: /* @__PURE__ */ m(s, { className: "
|
|
139
|
+
children: /* @__PURE__ */ m(s, { className: "size-3.5" })
|
|
140
140
|
}),
|
|
141
141
|
/* @__PURE__ */ m("span", {
|
|
142
142
|
className: "w-8 text-center text-sm font-medium",
|
|
@@ -147,7 +147,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
147
147
|
onClick: () => M?.(e.id, e.quantity + 1),
|
|
148
148
|
className: "p-2 rounded border border-border hover:bg-muted min-w-[36px] min-h-[36px] flex items-center justify-center",
|
|
149
149
|
"aria-label": `Increase ${e.name} quantity`,
|
|
150
|
-
children: /* @__PURE__ */ m(l, { className: "
|
|
150
|
+
children: /* @__PURE__ */ m(l, { className: "size-3.5" })
|
|
151
151
|
})
|
|
152
152
|
]
|
|
153
153
|
}) : /* @__PURE__ */ h("span", {
|
|
@@ -163,7 +163,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
163
163
|
onClick: () => N(e.id),
|
|
164
164
|
className: "p-1.5 rounded text-muted-foreground hover:text-destructive hover:bg-destructive/10",
|
|
165
165
|
"aria-label": `Remove ${e.name}`,
|
|
166
|
-
children: /* @__PURE__ */ m(f, { className: "
|
|
166
|
+
children: /* @__PURE__ */ m(f, { className: "size-4" })
|
|
167
167
|
})]
|
|
168
168
|
})]
|
|
169
169
|
}),
|
|
@@ -181,7 +181,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
181
181
|
children: /* @__PURE__ */ h("span", {
|
|
182
182
|
className: "flex items-center gap-1.5",
|
|
183
183
|
children: [
|
|
184
|
-
/* @__PURE__ */ m(a, { className: "
|
|
184
|
+
/* @__PURE__ */ m(a, { className: "size-3.5" }),
|
|
185
185
|
t,
|
|
186
186
|
" ",
|
|
187
187
|
t === 1 ? "feature" : "features",
|
|
@@ -234,7 +234,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
|
|
|
234
234
|
children: "Tax"
|
|
235
235
|
}), /* @__PURE__ */ m("span", {
|
|
236
236
|
className: "text-muted-foreground animate-pulse",
|
|
237
|
-
children: "Calculating
|
|
237
|
+
children: "Calculating…"
|
|
238
238
|
})]
|
|
239
239
|
}) : T > 0 && E > 0 ? /* @__PURE__ */ h("div", {
|
|
240
240
|
className: "flex items-center justify-between text-sm",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderSummary.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/OrderSummary.tsx"],"sourcesContent":["/**\n * Order Summary Component\n * Displays checkout items, discounts, taxes, and total\n */\n\nimport { type FC, useMemo } from 'react';\nimport {\n Package,\n Blocks,\n Coins,\n Minus,\n Plus,\n Trash2,\n Receipt,\n Check,\n ShoppingCart,\n} from 'lucide-react';\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '@burdenoff/fe-libs/ui';\nimport type { CheckoutItem, Currency, CheckoutFeature } from '../types';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { categoryTokens, statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\nconst CREDITS_PER_USD = 100;\n\ninterface OrderSummaryProps {\n items: CheckoutItem[];\n subtotal: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType?: string;\n total: number;\n currency: Currency;\n showInCredits?: boolean;\n /** Whether tax is being calculated */\n isCalculatingTax?: boolean;\n onUpdateQuantity?: (itemId: string, quantity: number) => void;\n onRemoveItem?: (itemId: string) => void;\n isEditable?: boolean;\n}\n\nexport const OrderSummary: FC<OrderSummaryProps> = ({\n items,\n subtotal,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n currency,\n showInCredits = false,\n isCalculatingTax = false,\n onUpdateQuantity,\n onRemoveItem,\n isEditable = true,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const getItemIcon = (type: CheckoutItem['type']) => {\n switch (type) {\n case 'PLAN':\n return <Package className=\"h-5 w-5\" />;\n case 'ADDON':\n return <Blocks className=\"h-5 w-5\" />;\n case 'CREDITS':\n return <Coins className=\"h-5 w-5\" />;\n case 'ORDER':\n return <ShoppingCart className=\"h-5 w-5\" />;\n default:\n return <Package className=\"h-5 w-5\" />;\n }\n };\n\n const getItemTypeBadge = (type: CheckoutItem['type']) => {\n const badges = {\n PLAN: {\n label: 'Plan',\n className: `${categoryTokens.plan.bg} ${categoryTokens.plan.text}`,\n },\n ADDON: {\n label: 'Add-on',\n className: `${categoryTokens.addon.bg} ${categoryTokens.addon.text}`,\n },\n CREDITS: {\n label: 'Credits',\n className: `${categoryTokens.credits.bg} ${categoryTokens.credits.text}`,\n },\n ORDER: {\n label: 'Store Order',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n };\n return badges[type];\n };\n\n // Convert USD amount to credits\n const usdToCredits = (usdAmount: number) => usdAmount * CREDITS_PER_USD;\n\n // Subtotal in USD — used for credit conversion (items may be priced in local currency)\n const subtotalUSD = useMemo(\n () => items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0),\n [items]\n );\n\n // Format amount - show in credits if paying with credits, otherwise in selected currency\n // Items now have their prices stored in the selected currency (from currencyPrices)\n // For credit conversion always use priceUSD (base USD price) — not the localized price.\n const formatAmount = (amount: number, item?: CheckoutItem) => {\n if (showInCredits) {\n const usdAmount = item?.priceUSD ?? amount;\n const creditsAmount = usdToCredits(usdAmount);\n return `${creditsAmount.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 })} Credits`;\n }\n // Format in selected currency (items now have localized prices)\n return formatCurrency(amount, currency);\n };\n\n // Format limit value for display\n const formatLimitValue = (_key: string, value: unknown): string => {\n if (typeof value === 'boolean') {\n return value ? 'Included' : 'Not included';\n }\n if (typeof value === 'number') {\n if (value === -1 || value === Infinity) return 'Unlimited';\n return value.toLocaleString();\n }\n return String(value);\n };\n\n // Merge duplicate features by name, combining numeric limits\n const mergeFeatures = (features: CheckoutFeature[]): CheckoutFeature[] => {\n const merged = new Map<string, CheckoutFeature>();\n\n for (const feature of features) {\n const existing = merged.get(feature.name);\n if (existing) {\n // Merge limits - add numeric values together\n const mergedLimits: Record<string, number | string | boolean> = { ...existing.limits };\n if (feature.limits) {\n for (const [key, value] of Object.entries(feature.limits)) {\n const existingValue = mergedLimits[key];\n if (typeof value === 'number' && typeof existingValue === 'number') {\n // Both are numbers - add them (unless unlimited)\n if (existingValue === -1 || value === -1) {\n mergedLimits[key] = -1; // Unlimited stays unlimited\n } else {\n mergedLimits[key] = existingValue + value;\n }\n } else {\n // Non-numeric or first occurrence - just set\n mergedLimits[key] = value;\n }\n }\n }\n existing.limits = mergedLimits;\n } else {\n // Clone the feature to avoid mutating original\n merged.set(feature.name, {\n ...feature,\n limits: feature.limits ? { ...feature.limits } : undefined,\n });\n }\n }\n\n return Array.from(merged.values());\n };\n\n // Render features list\n const renderFeatures = (features: CheckoutFeature[]) => {\n if (!features || features.length === 0) return null;\n\n const mergedFeatures = mergeFeatures(features);\n\n return (\n <ul className=\"space-y-2\">\n {mergedFeatures.map((feature) => (\n <li key={feature.id} className=\"flex items-start gap-2\">\n <Check className={`h-4 w-4 ${statusTokens.success.icon} shrink-0 mt-0.5`} />\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-sm text-foreground\">{feature.name}</span>\n {feature.limits && Object.keys(feature.limits).length > 0 && (\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n {Object.entries(feature.limits).map(([key, value]) => (\n <span\n key={key}\n className=\"inline-flex items-center text-xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground\"\n >\n {key}: {formatLimitValue(key, value)}\n </span>\n ))}\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n };\n\n return (\n <div className=\"rounded-lg border border-border bg-card overflow-hidden\">\n {/* Header */}\n <div className=\"px-4 py-3 border-b border-border bg-muted/30\">\n <div className=\"flex items-center gap-2\">\n <Receipt className=\"h-4 w-4 text-muted-foreground\" />\n <h3 className=\"font-semibold text-foreground\">\n {tr('billing.checkout.orderSummary', 'Order Summary')}\n </h3>\n </div>\n </div>\n\n {/* Items */}\n <div className=\"divide-y divide-border\">\n {items.length === 0 ? (\n <div className=\"p-6 text-center\">\n <Package className=\"mx-auto h-8 w-8 text-muted-foreground\" />\n <p className=\"mt-2 text-sm text-muted-foreground\">No items in checkout</p>\n </div>\n ) : (\n items.map((item) => {\n const badge = getItemTypeBadge(item.type);\n const hasFeatures = item.features && item.features.length > 0;\n\n return (\n <div key={item.id} className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n {/* Icon */}\n <div className=\"p-2 rounded-lg bg-muted text-muted-foreground shrink-0\">\n {getItemIcon(item.type)}\n </div>\n\n {/* Item details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"font-medium text-foreground\">{item.name}</span>\n <span className={`text-xs px-1.5 py-0.5 rounded ${badge.className}`}>\n {badge.label}\n </span>\n </div>\n {item.description && (\n <p className=\"text-sm text-muted-foreground mt-0.5 line-clamp-2\">\n {item.description}\n </p>\n )}\n\n {/* Quantity controls */}\n <div className=\"flex items-center justify-between mt-3\">\n {isEditable && item.type !== 'PLAN' ? (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity - 1)}\n disabled={item.quantity <= 1}\n className=\"p-2 rounded border border-border hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Decrease ${item.name} quantity`}\n >\n <Minus className=\"h-3.5 w-3.5\" />\n </button>\n <span className=\"w-8 text-center text-sm font-medium\">\n {item.quantity}\n </span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity + 1)}\n className=\"p-2 rounded border border-border hover:bg-muted min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Increase ${item.name} quantity`}\n >\n <Plus className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n ) : (\n <span className=\"text-sm text-muted-foreground\">Qty: {item.quantity}</span>\n )}\n\n <div className=\"flex items-center gap-3\">\n <span className=\"font-medium text-foreground\">\n {formatAmount(item.price * item.quantity, item)}\n </span>\n {isEditable && onRemoveItem && (\n <button\n type=\"button\"\n onClick={() => onRemoveItem(item.id)}\n className=\"p-1.5 rounded text-muted-foreground hover:text-destructive hover:bg-destructive/10\"\n aria-label={`Remove ${item.name}`}\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Features - Collapsible */}\n {hasFeatures &&\n (() => {\n const mergedCount = mergeFeatures(item.features!).length;\n return (\n <Accordion type=\"single\" collapsible className=\"mt-3\">\n <AccordionItem value=\"features\" className=\"border-0\">\n <AccordionTrigger className=\"py-2 px-0 text-sm text-muted-foreground hover:text-foreground hover:no-underline\">\n <span className=\"flex items-center gap-1.5\">\n <Check className=\"h-3.5 w-3.5\" />\n {mergedCount} {mergedCount === 1 ? 'feature' : 'features'}{' '}\n included\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pt-2 pb-0\">\n {renderFeatures(item.features!)}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n );\n })()}\n </div>\n </div>\n </div>\n );\n })\n )}\n </div>\n\n {/* Totals */}\n {items.length > 0 && (\n <div className=\"border-t border-border bg-muted/20\">\n <div className=\"p-4 space-y-2\">\n {/* Subtotal - already in selected currency (from currencyPrices) */}\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {tr('billing.checkout.subtotal', 'Subtotal')}\n </span>\n <span className=\"text-foreground\">\n {showInCredits ? formatAmount(subtotalUSD) : formatAmount(subtotal)}\n </span>\n </div>\n\n {/* Discount */}\n {discountAmount > 0 && (\n <div className=\"flex items-center justify-between text-sm\">\n <span className={statusTokens.success.text}>Discount</span>\n <span className={statusTokens.success.text}>-{formatAmount(discountAmount)}</span>\n </div>\n )}\n\n {/* Tax - calculated on the subtotal in selected currency */}\n {isCalculatingTax ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">Tax</span>\n <span className=\"text-muted-foreground animate-pulse\">Calculating...</span>\n </div>\n ) : taxAmount > 0 && taxRate > 0 ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {taxType || 'Tax'} ({(taxRate * 100).toFixed(0)}%)\n </span>\n <span className=\"text-foreground\">{formatAmount(taxAmount)}</span>\n </div>\n ) : null}\n\n {/* Divider */}\n <div className=\"border-t border-border my-2\" />\n\n {/* Total - show in credits or selected currency */}\n {showInCredits ? (\n <>\n {/* Credit payment: Show total in credits */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-foreground\">\n {tr('billing.checkout.total', 'Total')}\n </span>\n <span className=\"text-xl font-bold text-foreground\">\n {usdToCredits(subtotalUSD).toLocaleString(undefined, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n })}{' '}\n Credits\n </span>\n </div>\n <p className=\"text-xs text-center text-muted-foreground pt-2\">\n Paying with account credits\n </p>\n </>\n ) : (\n <>\n {/* Card payment: Show total in selected currency */}\n {/* Items already have localized prices from currencyPrices - no conversion needed */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-foreground\">Total in {currency}</span>\n <span className=\"text-xl font-bold text-foreground\">\n {formatCurrency(total, currency)}\n </span>\n </div>\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrderSummary.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/OrderSummary.tsx"],"sourcesContent":["/**\n * Order Summary Component\n * Displays checkout items, discounts, taxes, and total\n */\n\nimport { type FC, useMemo } from 'react';\nimport {\n Package,\n Blocks,\n Coins,\n Minus,\n Plus,\n Trash2,\n Receipt,\n Check,\n ShoppingCart,\n} from 'lucide-react';\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '@burdenoff/fe-libs/ui';\nimport type { CheckoutItem, Currency, CheckoutFeature } from '../types';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { categoryTokens, statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\nconst CREDITS_PER_USD = 100;\n\ninterface OrderSummaryProps {\n items: CheckoutItem[];\n subtotal: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType?: string;\n total: number;\n currency: Currency;\n showInCredits?: boolean;\n /** Whether tax is being calculated */\n isCalculatingTax?: boolean;\n onUpdateQuantity?: (itemId: string, quantity: number) => void;\n onRemoveItem?: (itemId: string) => void;\n isEditable?: boolean;\n}\n\nexport const OrderSummary: FC<OrderSummaryProps> = ({\n items,\n subtotal,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n currency,\n showInCredits = false,\n isCalculatingTax = false,\n onUpdateQuantity,\n onRemoveItem,\n isEditable = true,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const getItemIcon = (type: CheckoutItem['type']) => {\n switch (type) {\n case 'PLAN':\n return <Package className=\"size-5\" />;\n case 'ADDON':\n return <Blocks className=\"size-5\" />;\n case 'CREDITS':\n return <Coins className=\"size-5\" />;\n case 'ORDER':\n return <ShoppingCart className=\"size-5\" />;\n default:\n return <Package className=\"size-5\" />;\n }\n };\n\n const getItemTypeBadge = (type: CheckoutItem['type']) => {\n const badges = {\n PLAN: {\n label: 'Plan',\n className: `${categoryTokens.plan.bg} ${categoryTokens.plan.text}`,\n },\n ADDON: {\n label: 'Add-on',\n className: `${categoryTokens.addon.bg} ${categoryTokens.addon.text}`,\n },\n CREDITS: {\n label: 'Credits',\n className: `${categoryTokens.credits.bg} ${categoryTokens.credits.text}`,\n },\n ORDER: {\n label: 'Store Order',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n };\n return badges[type];\n };\n\n // Convert USD amount to credits\n const usdToCredits = (usdAmount: number) => usdAmount * CREDITS_PER_USD;\n\n // Subtotal in USD — used for credit conversion (items may be priced in local currency)\n const subtotalUSD = useMemo(\n () => items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0),\n [items]\n );\n\n // Format amount - show in credits if paying with credits, otherwise in selected currency\n // Items now have their prices stored in the selected currency (from currencyPrices)\n // For credit conversion always use priceUSD (base USD price) — not the localized price.\n const formatAmount = (amount: number, item?: CheckoutItem) => {\n if (showInCredits) {\n const usdAmount = item?.priceUSD ?? amount;\n const creditsAmount = usdToCredits(usdAmount);\n return `${creditsAmount.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 })} Credits`;\n }\n // Format in selected currency (items now have localized prices)\n return formatCurrency(amount, currency);\n };\n\n // Format limit value for display\n const formatLimitValue = (_key: string, value: unknown): string => {\n if (typeof value === 'boolean') {\n return value ? 'Included' : 'Not included';\n }\n if (typeof value === 'number') {\n if (value === -1 || value === Infinity) return 'Unlimited';\n return value.toLocaleString();\n }\n return String(value);\n };\n\n // Merge duplicate features by name, combining numeric limits\n const mergeFeatures = (features: CheckoutFeature[]): CheckoutFeature[] => {\n const merged = new Map<string, CheckoutFeature>();\n\n for (const feature of features) {\n const existing = merged.get(feature.name);\n if (existing) {\n // Merge limits - add numeric values together\n const mergedLimits: Record<string, number | string | boolean> = { ...existing.limits };\n if (feature.limits) {\n for (const [key, value] of Object.entries(feature.limits)) {\n const existingValue = mergedLimits[key];\n if (typeof value === 'number' && typeof existingValue === 'number') {\n // Both are numbers - add them (unless unlimited)\n if (existingValue === -1 || value === -1) {\n mergedLimits[key] = -1; // Unlimited stays unlimited\n } else {\n mergedLimits[key] = existingValue + value;\n }\n } else {\n // Non-numeric or first occurrence - just set\n mergedLimits[key] = value;\n }\n }\n }\n existing.limits = mergedLimits;\n } else {\n // Clone the feature to avoid mutating original\n merged.set(feature.name, {\n ...feature,\n limits: feature.limits ? { ...feature.limits } : undefined,\n });\n }\n }\n\n return Array.from(merged.values());\n };\n\n // Render features list\n const renderFeatures = (features: CheckoutFeature[]) => {\n if (!features || features.length === 0) return null;\n\n const mergedFeatures = mergeFeatures(features);\n\n return (\n <ul className=\"space-y-2\">\n {mergedFeatures.map((feature) => (\n <li key={feature.id} className=\"flex items-start gap-2\">\n <Check className={`size-4 ${statusTokens.success.icon} shrink-0 mt-0.5`} />\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-sm text-foreground\">{feature.name}</span>\n {feature.limits && Object.keys(feature.limits).length > 0 && (\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n {Object.entries(feature.limits).map(([key, value]) => (\n <span\n key={key}\n className=\"inline-flex items-center text-xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground\"\n >\n {key}: {formatLimitValue(key, value)}\n </span>\n ))}\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n };\n\n return (\n <div className=\"rounded-lg border border-border bg-card overflow-hidden\">\n {/* Header */}\n <div className=\"px-4 py-3 border-b border-border bg-muted/30\">\n <div className=\"flex items-center gap-2\">\n <Receipt className=\"size-4 text-muted-foreground\" />\n <h3 className=\"font-semibold text-foreground\">\n {tr('billing.checkout.orderSummary', 'Order Summary')}\n </h3>\n </div>\n </div>\n\n {/* Items */}\n <div className=\"divide-y divide-border\">\n {items.length === 0 ? (\n <div className=\"p-6 text-center\">\n <Package className=\"mx-auto size-8 text-muted-foreground\" />\n <p className=\"mt-2 text-sm text-muted-foreground\">No items in checkout</p>\n </div>\n ) : (\n items.map((item) => {\n const badge = getItemTypeBadge(item.type);\n const hasFeatures = item.features && item.features.length > 0;\n\n return (\n <div key={item.id} className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n {/* Icon */}\n <div className=\"p-2 rounded-lg bg-muted text-muted-foreground shrink-0\">\n {getItemIcon(item.type)}\n </div>\n\n {/* Item details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"font-medium text-foreground\">{item.name}</span>\n <span className={`text-xs px-1.5 py-0.5 rounded ${badge.className}`}>\n {badge.label}\n </span>\n </div>\n {item.description && (\n <p className=\"text-sm text-muted-foreground mt-0.5 line-clamp-2\">\n {item.description}\n </p>\n )}\n\n {/* Quantity controls */}\n <div className=\"flex items-center justify-between mt-3\">\n {isEditable && item.type !== 'PLAN' ? (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity - 1)}\n disabled={item.quantity <= 1}\n className=\"p-2 rounded border border-border hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Decrease ${item.name} quantity`}\n >\n <Minus className=\"size-3.5\" />\n </button>\n <span className=\"w-8 text-center text-sm font-medium\">\n {item.quantity}\n </span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity + 1)}\n className=\"p-2 rounded border border-border hover:bg-muted min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Increase ${item.name} quantity`}\n >\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n ) : (\n <span className=\"text-sm text-muted-foreground\">Qty: {item.quantity}</span>\n )}\n\n <div className=\"flex items-center gap-3\">\n <span className=\"font-medium text-foreground\">\n {formatAmount(item.price * item.quantity, item)}\n </span>\n {isEditable && onRemoveItem && (\n <button\n type=\"button\"\n onClick={() => onRemoveItem(item.id)}\n className=\"p-1.5 rounded text-muted-foreground hover:text-destructive hover:bg-destructive/10\"\n aria-label={`Remove ${item.name}`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Features - Collapsible */}\n {hasFeatures &&\n (() => {\n const mergedCount = mergeFeatures(item.features!).length;\n return (\n <Accordion type=\"single\" collapsible className=\"mt-3\">\n <AccordionItem value=\"features\" className=\"border-0\">\n <AccordionTrigger className=\"py-2 px-0 text-sm text-muted-foreground hover:text-foreground hover:no-underline\">\n <span className=\"flex items-center gap-1.5\">\n <Check className=\"size-3.5\" />\n {mergedCount} {mergedCount === 1 ? 'feature' : 'features'}{' '}\n included\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pt-2 pb-0\">\n {renderFeatures(item.features!)}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n );\n })()}\n </div>\n </div>\n </div>\n );\n })\n )}\n </div>\n\n {/* Totals */}\n {items.length > 0 && (\n <div className=\"border-t border-border bg-muted/20\">\n <div className=\"p-4 space-y-2\">\n {/* Subtotal - already in selected currency (from currencyPrices) */}\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {tr('billing.checkout.subtotal', 'Subtotal')}\n </span>\n <span className=\"text-foreground\">\n {showInCredits ? formatAmount(subtotalUSD) : formatAmount(subtotal)}\n </span>\n </div>\n\n {/* Discount */}\n {discountAmount > 0 && (\n <div className=\"flex items-center justify-between text-sm\">\n <span className={statusTokens.success.text}>Discount</span>\n <span className={statusTokens.success.text}>-{formatAmount(discountAmount)}</span>\n </div>\n )}\n\n {/* Tax - calculated on the subtotal in selected currency */}\n {isCalculatingTax ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">Tax</span>\n <span className=\"text-muted-foreground animate-pulse\">Calculating…</span>\n </div>\n ) : taxAmount > 0 && taxRate > 0 ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-muted-foreground\">\n {taxType || 'Tax'} ({(taxRate * 100).toFixed(0)}%)\n </span>\n <span className=\"text-foreground\">{formatAmount(taxAmount)}</span>\n </div>\n ) : null}\n\n {/* Divider */}\n <div className=\"border-t border-border my-2\" />\n\n {/* Total - show in credits or selected currency */}\n {showInCredits ? (\n <>\n {/* Credit payment: Show total in credits */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-foreground\">\n {tr('billing.checkout.total', 'Total')}\n </span>\n <span className=\"text-xl font-bold text-foreground\">\n {usdToCredits(subtotalUSD).toLocaleString(undefined, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n })}{' '}\n Credits\n </span>\n </div>\n <p className=\"text-xs text-center text-muted-foreground pt-2\">\n Paying with account credits\n </p>\n </>\n ) : (\n <>\n {/* Card payment: Show total in selected currency */}\n {/* Items already have localized prices from currencyPrices - no conversion needed */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-foreground\">Total in {currency}</span>\n <span className=\"text-xl font-bold text-foreground\">\n {formatCurrency(total, currency)}\n </span>\n </div>\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ var u = ({ value: u, onChange: d, onValidate: f, onClear: p, isApplied: m, disco
|
|
|
29
29
|
className: "flex items-center gap-2",
|
|
30
30
|
children: [/* @__PURE__ */ s("div", {
|
|
31
31
|
className: `p-1.5 rounded-full ${e.success.bg} ${e.success.icon}`,
|
|
32
|
-
children: /* @__PURE__ */ s(r, { className: "
|
|
32
|
+
children: /* @__PURE__ */ s(r, { className: "size-4" })
|
|
33
33
|
}), /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ c("div", {
|
|
34
34
|
className: "flex items-center gap-1.5",
|
|
35
35
|
children: [/* @__PURE__ */ s("span", {
|
|
@@ -50,7 +50,7 @@ var u = ({ value: u, onChange: d, onValidate: f, onClear: p, isApplied: m, disco
|
|
|
50
50
|
type: "button",
|
|
51
51
|
onClick: w,
|
|
52
52
|
className: `p-1.5 rounded-full hover:${e.success.bg} ${e.success.icon}`,
|
|
53
|
-
children: /* @__PURE__ */ s(o, { className: "
|
|
53
|
+
children: /* @__PURE__ */ s(o, { className: "size-4" })
|
|
54
54
|
})]
|
|
55
55
|
})
|
|
56
56
|
}) : /* @__PURE__ */ c("div", {
|
|
@@ -64,7 +64,7 @@ var u = ({ value: u, onChange: d, onValidate: f, onClear: p, isApplied: m, disco
|
|
|
64
64
|
className: "flex gap-2",
|
|
65
65
|
children: [/* @__PURE__ */ c("div", {
|
|
66
66
|
className: "relative flex-1",
|
|
67
|
-
children: [/* @__PURE__ */ s(a, { className: "absolute left-3 top-1/2 -translate-y-1/2
|
|
67
|
+
children: [/* @__PURE__ */ s(a, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" }), /* @__PURE__ */ s("input", {
|
|
68
68
|
type: "text",
|
|
69
69
|
value: u,
|
|
70
70
|
onChange: (e) => {
|
|
@@ -86,12 +86,12 @@ var u = ({ value: u, onChange: d, onValidate: f, onClear: p, isApplied: m, disco
|
|
|
86
86
|
onClick: C,
|
|
87
87
|
disabled: !u.trim() || _,
|
|
88
88
|
className: "px-4 py-2.5 rounded-lg border border-border bg-muted text-foreground font-medium\n hover:bg-muted/80 disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors duration-200",
|
|
89
|
-
children: _ ? /* @__PURE__ */ s(i, { className: "
|
|
89
|
+
children: _ ? /* @__PURE__ */ s(i, { className: "size-4 animate-spin" }) : "Apply"
|
|
90
90
|
})]
|
|
91
91
|
}),
|
|
92
92
|
E && /* @__PURE__ */ c("p", {
|
|
93
93
|
className: "text-sm text-destructive flex items-center gap-1.5",
|
|
94
|
-
children: [/* @__PURE__ */ s(o, { className: "
|
|
94
|
+
children: [/* @__PURE__ */ s(o, { className: "size-3.5" }), E]
|
|
95
95
|
})
|
|
96
96
|
]
|
|
97
97
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromoCodeInput.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/PromoCodeInput.tsx"],"sourcesContent":["/**\n * Promo Code Input Component\n * Input field for entering and validating discount codes\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { Tag, Check, X, Loader2 } from 'lucide-react';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface PromoCodeInputProps {\n value: string;\n onChange: (value: string) => void;\n onValidate: () => Promise<{ valid: boolean; error?: string }>;\n onClear: () => void;\n isApplied: boolean;\n discountValue?: number;\n discountType?: 'PERCENTAGE' | 'FIXED';\n isValidating?: boolean;\n error?: string;\n}\n\nexport const PromoCodeInput: FC<PromoCodeInputProps> = ({\n value,\n onChange,\n onValidate,\n onClear,\n isApplied,\n discountValue,\n discountType: _discountType,\n isValidating = false,\n error,\n}) => {\n void _discountType;\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [localError, setLocalError] = useState<string | null>(null);\n\n const handleValidate = useCallback(async () => {\n if (!value.trim()) {\n setLocalError('Please enter a promo code');\n return;\n }\n setLocalError(null);\n const result = await onValidate();\n if (!result.valid && result.error) {\n setLocalError(result.error);\n }\n }, [value, onValidate]);\n\n const handleClear = useCallback(() => {\n setLocalError(null);\n onClear();\n }, [onClear]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleValidate();\n }\n },\n [handleValidate]\n );\n\n const displayError = error || localError;\n\n if (isApplied && discountValue) {\n // Show applied discount\n return (\n <div\n className={`rounded-lg border ${statusTokens.success.border} ${statusTokens.success.bg} p-3`}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div\n className={`p-1.5 rounded-full ${statusTokens.success.bg} ${statusTokens.success.icon}`}\n >\n <Check className=\"
|
|
1
|
+
{"version":3,"file":"PromoCodeInput.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/PromoCodeInput.tsx"],"sourcesContent":["/**\n * Promo Code Input Component\n * Input field for entering and validating discount codes\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { Tag, Check, X, Loader2 } from 'lucide-react';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface PromoCodeInputProps {\n value: string;\n onChange: (value: string) => void;\n onValidate: () => Promise<{ valid: boolean; error?: string }>;\n onClear: () => void;\n isApplied: boolean;\n discountValue?: number;\n discountType?: 'PERCENTAGE' | 'FIXED';\n isValidating?: boolean;\n error?: string;\n}\n\nexport const PromoCodeInput: FC<PromoCodeInputProps> = ({\n value,\n onChange,\n onValidate,\n onClear,\n isApplied,\n discountValue,\n discountType: _discountType,\n isValidating = false,\n error,\n}) => {\n void _discountType;\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [localError, setLocalError] = useState<string | null>(null);\n\n const handleValidate = useCallback(async () => {\n if (!value.trim()) {\n setLocalError('Please enter a promo code');\n return;\n }\n setLocalError(null);\n const result = await onValidate();\n if (!result.valid && result.error) {\n setLocalError(result.error);\n }\n }, [value, onValidate]);\n\n const handleClear = useCallback(() => {\n setLocalError(null);\n onClear();\n }, [onClear]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleValidate();\n }\n },\n [handleValidate]\n );\n\n const displayError = error || localError;\n\n if (isApplied && discountValue) {\n // Show applied discount\n return (\n <div\n className={`rounded-lg border ${statusTokens.success.border} ${statusTokens.success.bg} p-3`}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div\n className={`p-1.5 rounded-full ${statusTokens.success.bg} ${statusTokens.success.icon}`}\n >\n <Check className=\"size-4\" />\n </div>\n <div>\n <div className=\"flex items-center gap-1.5\">\n <span className={`font-medium ${statusTokens.success.text}`}>\n {value.toUpperCase()}\n </span>\n <span className={`text-sm ${statusTokens.success.text}`}>applied</span>\n </div>\n <p className={`text-sm ${statusTokens.success.text}`}>\n {/* discountValue now contains the actual discount amount, not percentage */}\n <span className=\"flex items-center gap-0.5\">\n {discountValue.toFixed(2)} off your order\n </span>\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={handleClear}\n className={`p-1.5 rounded-full hover:${statusTokens.success.bg} ${statusTokens.success.icon}`}\n >\n <X className=\"size-4\" />\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium text-foreground\">\n {tr('billing.checkout.promoCode', 'Promo Code')}\n </label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <Tag className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n <input\n type=\"text\"\n value={value}\n onChange={(e) => {\n onChange(e.target.value.toUpperCase());\n setLocalError(null);\n }}\n onKeyDown={handleKeyDown}\n placeholder=\"Enter code\"\n disabled={isValidating}\n className={`\n w-full pl-[3.25rem] pr-4 py-2.5 rounded-lg border bg-background text-foreground\n placeholder:text-muted-foreground\n focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary\n disabled:opacity-50 disabled:cursor-not-allowed\n ${\n displayError\n ? 'border-destructive focus:ring-destructive/20 focus:border-destructive'\n : 'border-border'\n }\n `}\n />\n </div>\n <button\n type=\"button\"\n onClick={handleValidate}\n disabled={!value.trim() || isValidating}\n className=\"px-4 py-2.5 rounded-lg border border-border bg-muted text-foreground font-medium\n hover:bg-muted/80 disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors duration-200\"\n >\n {isValidating ? <Loader2 className=\"size-4 animate-spin\" /> : 'Apply'}\n </button>\n </div>\n\n {/* Error message */}\n {displayError && (\n <p className=\"text-sm text-destructive flex items-center gap-1.5\">\n <X className=\"size-3.5\" />\n {displayError}\n </p>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;AAsBA,IAAa,KAA2C,EACtD,UACA,aACA,eACA,YACA,cACA,kBACA,cAAc,GACd,kBAAe,IACf,eACI;CAEJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAiB,EAAY,YAAY;AAC7C,MAAI,CAAC,EAAM,MAAM,EAAE;AACjB,KAAc,4BAA4B;AAC1C;;AAEF,IAAc,KAAK;EACnB,IAAM,IAAS,MAAM,GAAY;AACjC,EAAI,CAAC,EAAO,SAAS,EAAO,SAC1B,EAAc,EAAO,MAAM;IAE5B,CAAC,GAAO,EAAW,CAAC,EAEjB,IAAc,QAAkB;AAEpC,EADA,EAAc,KAAK,EACnB,GAAS;IACR,CAAC,EAAQ,CAAC,EAEP,IAAgB,GACnB,MAA2B;AAC1B,EAAI,EAAE,QAAQ,YACZ,EAAE,gBAAgB,EAClB,GAAgB;IAGpB,CAAC,EAAe,CACjB,EAEK,IAAe,KAAS;AA0C9B,QAxCI,KAAa,IAGb,kBAAC,OAAD;EACE,WAAW,qBAAqB,EAAa,QAAQ,OAAO,GAAG,EAAa,QAAQ,GAAG;YAEvF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KACE,WAAW,sBAAsB,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;eAEjF,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;KACxB,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAW,eAAe,EAAa,QAAQ;gBAClD,EAAM,aAAa;MACf,CAAA,EACP,kBAAC,QAAD;MAAM,WAAW,WAAW,EAAa,QAAQ;gBAAQ;MAAc,CAAA,CACnE;QACN,kBAAC,KAAD;KAAG,WAAW,WAAW,EAAa,QAAQ;eAE5C,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAc,QAAQ,EAAE,EAAC,kBACrB;;KACL,CAAA,CACA,EAAA,CAAA,CACF;OACN,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAW,4BAA4B,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;cAEvF,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IACjB,CAAA,CACL;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,SAAD;IAAO,WAAU;cACd,EAAG,8BAA8B,aAAa;IACzC,CAAA;GACR,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAK,WAAU,yEAA0E,CAAA,EACzF,kBAAC,SAAD;MACE,MAAK;MACE;MACP,WAAW,MAAM;AAEf,OADA,EAAS,EAAE,OAAO,MAAM,aAAa,CAAC,EACtC,EAAc,KAAK;;MAErB,WAAW;MACX,aAAY;MACZ,UAAU;MACV,WAAW;;;;;gBAMP,IACI,0EACA,gBACL;;MAEH,CAAA,CACE;QACN,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU,CAAC,EAAM,MAAM,IAAI;KAC3B,WAAU;eAIT,IAAe,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,GAAG;KACvD,CAAA,CACL;;GAGL,KACC,kBAAC,KAAD;IAAG,WAAU;cAAb,CACE,kBAAC,GAAD,EAAG,WAAU,YAAa,CAAA,EACzB,EACC;;GAEF"}
|
|
@@ -83,7 +83,7 @@ var f = ({ orderId: f, keyId: p, amount: m, currency: h, name: g = "Burdenoff",
|
|
|
83
83
|
className: "flex items-center gap-3",
|
|
84
84
|
children: [/* @__PURE__ */ l("div", {
|
|
85
85
|
className: "p-2 rounded-full bg-status-success-bg-subtle text-status-success-text",
|
|
86
|
-
children: /* @__PURE__ */ l(a, { className: "
|
|
86
|
+
children: /* @__PURE__ */ l(a, { className: "size-6" })
|
|
87
87
|
}), /* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("h3", {
|
|
88
88
|
className: "font-semibold text-status-success-text",
|
|
89
89
|
children: "Payment Successful!"
|
|
@@ -97,15 +97,15 @@ var f = ({ orderId: f, keyId: p, amount: m, currency: h, name: g = "Burdenoff",
|
|
|
97
97
|
children: [
|
|
98
98
|
/* @__PURE__ */ u("div", {
|
|
99
99
|
className: "flex items-center justify-center gap-1.5 text-xs text-muted-foreground",
|
|
100
|
-
children: [/* @__PURE__ */ l(s, { className: "
|
|
100
|
+
children: [/* @__PURE__ */ l(s, { className: "size-3" }), "Secured by Razorpay"]
|
|
101
101
|
}),
|
|
102
102
|
k && /* @__PURE__ */ u("div", {
|
|
103
103
|
className: "flex items-center gap-2 p-3 rounded-lg bg-destructive/10 text-destructive text-sm",
|
|
104
|
-
children: [/* @__PURE__ */ l(i, { className: "
|
|
104
|
+
children: [/* @__PURE__ */ l(i, { className: "size-4 shrink-0" }), k]
|
|
105
105
|
}),
|
|
106
106
|
!j && /* @__PURE__ */ u("div", {
|
|
107
107
|
className: "flex items-center justify-center gap-2 py-4 text-muted-foreground",
|
|
108
|
-
children: [/* @__PURE__ */ l(o, { className: "
|
|
108
|
+
children: [/* @__PURE__ */ l(o, { className: "size-4 animate-spin" }), /* @__PURE__ */ l("span", {
|
|
109
109
|
className: "text-sm",
|
|
110
110
|
children: E("billing.checkout.loadingGateway", "Loading payment gateway...")
|
|
111
111
|
})]
|
|
@@ -123,7 +123,7 @@ var f = ({ orderId: f, keyId: p, amount: m, currency: h, name: g = "Burdenoff",
|
|
|
123
123
|
onClick: N,
|
|
124
124
|
disabled: !j || D === "processing" || w,
|
|
125
125
|
className: "flex-1 px-4 py-3 rounded-lg bg-action-primary-bg text-action-primary-text font-medium\n hover:bg-action-primary-bg/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\n flex items-center justify-center gap-2",
|
|
126
|
-
children: D === "processing" ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(o, { className: "
|
|
126
|
+
children: D === "processing" ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(o, { className: "size-4 animate-spin" }), "Processing…"] }) : /* @__PURE__ */ u(c, { children: ["Pay ", P] })
|
|
127
127
|
})]
|
|
128
128
|
}),
|
|
129
129
|
/* @__PURE__ */ l("p", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RazorpayPaymentButton.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/RazorpayPaymentButton.tsx"],"sourcesContent":["/**\n * {tr('billing.checkout.razorpayPayment', 'Razorpay Payment')} Button Component\n * Integrates with Razorpay checkout for INR payments\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport { Lock, Loader2, Check, AlertCircle } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type {\n RazorpaySuccessResponse,\n CheckoutRazorpayOptions,\n CheckoutRazorpayInstance,\n} from '../types';\nimport { getPaymentProviderThemeColor } from '../../../shared/utils';\n\ntype RazorpayCheckoutOptions = CheckoutRazorpayOptions & {\n customer_id?: string;\n remember_customer?: boolean;\n handler: (response: RazorpaySuccessResponse) => void;\n};\n\ninterface RazorpayPaymentButtonProps {\n orderId: string;\n keyId: string;\n amount: number; // In paise (smallest currency unit)\n currency: string;\n name?: string;\n description?: string;\n customerId?: string; // Razorpay customer ID for saved cards\n prefill?: {\n name?: string;\n email?: string;\n contact?: string;\n };\n notes?: Record<string, string>;\n onSuccess: (response: RazorpaySuccessResponse) => void;\n onError: (error: string) => void;\n onCancel?: () => void;\n disabled?: boolean;\n}\n\ntype PaymentState = 'idle' | 'loading' | 'processing' | 'success' | 'error';\n\nexport const RazorpayPaymentButton: FC<RazorpayPaymentButtonProps> = ({\n orderId,\n keyId,\n amount,\n currency,\n name = 'Burdenoff',\n description = 'Purchase',\n customerId,\n prefill,\n notes,\n onSuccess,\n onError,\n onCancel,\n disabled = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [paymentState, setPaymentState] = useState<PaymentState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isRazorpayLoaded, setIsRazorpayLoaded] = useState(false);\n\n // Load Razorpay SDK dynamically\n useEffect(() => {\n if (typeof window !== 'undefined' && !window.Razorpay) {\n const script = document.createElement('script');\n script.src = 'https://checkout.razorpay.com/v1/checkout.js';\n script.async = true;\n script.onload = () => {\n setIsRazorpayLoaded(true);\n };\n script.onerror = () => {\n setError('Failed to load payment gateway');\n };\n document.body.appendChild(script);\n } else if (window.Razorpay) {\n setIsRazorpayLoaded(true);\n }\n }, []);\n\n const handlePayment = useCallback(() => {\n if (!isRazorpayLoaded || !window.Razorpay) {\n setError('Payment gateway not ready');\n return;\n }\n\n setPaymentState('processing');\n setError(null);\n\n // Build Razorpay options - customer_id enables saved cards feature\n const options: RazorpayCheckoutOptions = {\n key: keyId,\n amount: amount, // Amount in paise\n currency: currency.toUpperCase(),\n name,\n description,\n order_id: orderId,\n prefill: prefill || {},\n notes: notes || {},\n handler: (response: RazorpaySuccessResponse) => {\n setPaymentState('success');\n onSuccess(response);\n },\n modal: {\n ondismiss: () => {\n setPaymentState('idle');\n onCancel?.();\n },\n escape: true,\n confirm_close: true,\n },\n };\n\n const providerThemeColor = getPaymentProviderThemeColor();\n if (providerThemeColor) {\n options.theme = { color: providerThemeColor };\n }\n\n // Add customer_id for saved cards feature\n if (customerId) {\n options.customer_id = customerId;\n options.remember_customer = true;\n }\n\n try {\n const RazorpayConstructor = (\n window as unknown as {\n Razorpay?: new (options: RazorpayCheckoutOptions) => CheckoutRazorpayInstance;\n }\n ).Razorpay;\n\n if (!RazorpayConstructor) {\n throw new Error('Payment gateway not ready');\n }\n\n const razorpay = new RazorpayConstructor(options);\n\n razorpay.on('payment.failed', (response: unknown) => {\n const errorResponse = response as {\n error?: {\n code?: string;\n description?: string;\n reason?: string;\n };\n };\n const errorMessage =\n errorResponse?.error?.description || errorResponse?.error?.reason || 'Payment failed';\n setError(errorMessage);\n setPaymentState('error');\n onError(errorMessage);\n });\n\n razorpay.open();\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Failed to open payment gateway';\n setError(errorMessage);\n setPaymentState('error');\n onError(errorMessage);\n }\n }, [\n isRazorpayLoaded,\n keyId,\n amount,\n currency,\n name,\n description,\n customerId,\n orderId,\n prefill,\n notes,\n onSuccess,\n onError,\n onCancel,\n ]);\n\n // Format amount for display (convert from paise to rupees)\n const displayAmount = (amount / 100).toLocaleString('en-IN', {\n style: 'currency',\n currency: 'INR',\n });\n\n // Render success state\n if (paymentState === 'success') {\n return (\n <div className=\"p-6 rounded-lg border border-border-subtle bg-status-success-bg-subtle\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-full bg-status-success-bg-subtle text-status-success-text\">\n <Check className=\"h-6 w-6\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-status-success-text\">Payment Successful!</h3>\n <p className=\"text-sm text-status-success-text\">\n Your payment has been processed successfully.\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Security badge */}\n <div className=\"flex items-center justify-center gap-1.5 text-xs text-muted-foreground\">\n <Lock className=\"h-3 w-3\" />\n Secured by Razorpay\n </div>\n\n {/* Error message */}\n {error && (\n <div className=\"flex items-center gap-2 p-3 rounded-lg bg-destructive/10 text-destructive text-sm\">\n <AlertCircle className=\"h-4 w-4 shrink-0\" />\n {error}\n </div>\n )}\n\n {/* Loading state */}\n {!isRazorpayLoaded && (\n <div className=\"flex items-center justify-center gap-2 py-4 text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n <span className=\"text-sm\">\n {tr('billing.checkout.loadingGateway', 'Loading payment gateway...')}\n </span>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex gap-3\">\n {onCancel && (\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={paymentState === 'processing' || disabled}\n className=\"flex-1 px-4 py-3 rounded-lg border border-border text-foreground font-medium\n hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Cancel\n </button>\n )}\n <button\n type=\"button\"\n onClick={handlePayment}\n disabled={!isRazorpayLoaded || paymentState === 'processing' || disabled}\n className=\"flex-1 px-4 py-3 rounded-lg bg-action-primary-bg text-action-primary-text font-medium\n hover:bg-action-primary-bg/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\n flex items-center justify-center gap-2\"\n >\n {paymentState === 'processing' ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n Processing...\n </>\n ) : (\n <>Pay {displayAmount}</>\n )}\n </button>\n </div>\n\n {/* Security note */}\n <p className=\"text-center text-xs text-muted-foreground\">\n Your payment is secured with 256-bit encryption. Powered by Razorpay.\n </p>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RazorpayPaymentButton.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/RazorpayPaymentButton.tsx"],"sourcesContent":["/**\n * {tr('billing.checkout.razorpayPayment', 'Razorpay Payment')} Button Component\n * Integrates with Razorpay checkout for INR payments\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport { Lock, Loader2, Check, AlertCircle } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type {\n RazorpaySuccessResponse,\n CheckoutRazorpayOptions,\n CheckoutRazorpayInstance,\n} from '../types';\nimport { getPaymentProviderThemeColor } from '../../../shared/utils';\n\ntype RazorpayCheckoutOptions = CheckoutRazorpayOptions & {\n customer_id?: string;\n remember_customer?: boolean;\n handler: (response: RazorpaySuccessResponse) => void;\n};\n\ninterface RazorpayPaymentButtonProps {\n orderId: string;\n keyId: string;\n amount: number; // In paise (smallest currency unit)\n currency: string;\n name?: string;\n description?: string;\n customerId?: string; // Razorpay customer ID for saved cards\n prefill?: {\n name?: string;\n email?: string;\n contact?: string;\n };\n notes?: Record<string, string>;\n onSuccess: (response: RazorpaySuccessResponse) => void;\n onError: (error: string) => void;\n onCancel?: () => void;\n disabled?: boolean;\n}\n\ntype PaymentState = 'idle' | 'loading' | 'processing' | 'success' | 'error';\n\nexport const RazorpayPaymentButton: FC<RazorpayPaymentButtonProps> = ({\n orderId,\n keyId,\n amount,\n currency,\n name = 'Burdenoff',\n description = 'Purchase',\n customerId,\n prefill,\n notes,\n onSuccess,\n onError,\n onCancel,\n disabled = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [paymentState, setPaymentState] = useState<PaymentState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isRazorpayLoaded, setIsRazorpayLoaded] = useState(false);\n\n // Load Razorpay SDK dynamically\n useEffect(() => {\n if (typeof window !== 'undefined' && !window.Razorpay) {\n const script = document.createElement('script');\n script.src = 'https://checkout.razorpay.com/v1/checkout.js';\n script.async = true;\n script.onload = () => {\n setIsRazorpayLoaded(true);\n };\n script.onerror = () => {\n setError('Failed to load payment gateway');\n };\n document.body.appendChild(script);\n } else if (window.Razorpay) {\n setIsRazorpayLoaded(true);\n }\n }, []);\n\n const handlePayment = useCallback(() => {\n if (!isRazorpayLoaded || !window.Razorpay) {\n setError('Payment gateway not ready');\n return;\n }\n\n setPaymentState('processing');\n setError(null);\n\n // Build Razorpay options - customer_id enables saved cards feature\n const options: RazorpayCheckoutOptions = {\n key: keyId,\n amount: amount, // Amount in paise\n currency: currency.toUpperCase(),\n name,\n description,\n order_id: orderId,\n prefill: prefill || {},\n notes: notes || {},\n handler: (response: RazorpaySuccessResponse) => {\n setPaymentState('success');\n onSuccess(response);\n },\n modal: {\n ondismiss: () => {\n setPaymentState('idle');\n onCancel?.();\n },\n escape: true,\n confirm_close: true,\n },\n };\n\n const providerThemeColor = getPaymentProviderThemeColor();\n if (providerThemeColor) {\n options.theme = { color: providerThemeColor };\n }\n\n // Add customer_id for saved cards feature\n if (customerId) {\n options.customer_id = customerId;\n options.remember_customer = true;\n }\n\n try {\n const RazorpayConstructor = (\n window as unknown as {\n Razorpay?: new (options: RazorpayCheckoutOptions) => CheckoutRazorpayInstance;\n }\n ).Razorpay;\n\n if (!RazorpayConstructor) {\n throw new Error('Payment gateway not ready');\n }\n\n const razorpay = new RazorpayConstructor(options);\n\n razorpay.on('payment.failed', (response: unknown) => {\n const errorResponse = response as {\n error?: {\n code?: string;\n description?: string;\n reason?: string;\n };\n };\n const errorMessage =\n errorResponse?.error?.description || errorResponse?.error?.reason || 'Payment failed';\n setError(errorMessage);\n setPaymentState('error');\n onError(errorMessage);\n });\n\n razorpay.open();\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Failed to open payment gateway';\n setError(errorMessage);\n setPaymentState('error');\n onError(errorMessage);\n }\n }, [\n isRazorpayLoaded,\n keyId,\n amount,\n currency,\n name,\n description,\n customerId,\n orderId,\n prefill,\n notes,\n onSuccess,\n onError,\n onCancel,\n ]);\n\n // Format amount for display (convert from paise to rupees)\n const displayAmount = (amount / 100).toLocaleString('en-IN', {\n style: 'currency',\n currency: 'INR',\n });\n\n // Render success state\n if (paymentState === 'success') {\n return (\n <div className=\"p-6 rounded-lg border border-border-subtle bg-status-success-bg-subtle\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-full bg-status-success-bg-subtle text-status-success-text\">\n <Check className=\"size-6\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-status-success-text\">Payment Successful!</h3>\n <p className=\"text-sm text-status-success-text\">\n Your payment has been processed successfully.\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Security badge */}\n <div className=\"flex items-center justify-center gap-1.5 text-xs text-muted-foreground\">\n <Lock className=\"size-3\" />\n Secured by Razorpay\n </div>\n\n {/* Error message */}\n {error && (\n <div className=\"flex items-center gap-2 p-3 rounded-lg bg-destructive/10 text-destructive text-sm\">\n <AlertCircle className=\"size-4 shrink-0\" />\n {error}\n </div>\n )}\n\n {/* Loading state */}\n {!isRazorpayLoaded && (\n <div className=\"flex items-center justify-center gap-2 py-4 text-muted-foreground\">\n <Loader2 className=\"size-4 animate-spin\" />\n <span className=\"text-sm\">\n {tr('billing.checkout.loadingGateway', 'Loading payment gateway...')}\n </span>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex gap-3\">\n {onCancel && (\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={paymentState === 'processing' || disabled}\n className=\"flex-1 px-4 py-3 rounded-lg border border-border text-foreground font-medium\n hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Cancel\n </button>\n )}\n <button\n type=\"button\"\n onClick={handlePayment}\n disabled={!isRazorpayLoaded || paymentState === 'processing' || disabled}\n className=\"flex-1 px-4 py-3 rounded-lg bg-action-primary-bg text-action-primary-text font-medium\n hover:bg-action-primary-bg/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\n flex items-center justify-center gap-2\"\n >\n {paymentState === 'processing' ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n <>Pay {displayAmount}</>\n )}\n </button>\n </div>\n\n {/* Security note */}\n <p className=\"text-center text-xs text-muted-foreground\">\n Your payment is secured with 256-bit encryption. Powered by Razorpay.\n </p>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -25,7 +25,7 @@ var o = ({ enabled: o, onToggle: s, hasPaymentMethodSelected: c, selectedPayment
|
|
|
25
25
|
${(!g || d) && "opacity-50 cursor-not-allowed"}
|
|
26
26
|
`,
|
|
27
27
|
children: /* @__PURE__ */ r("span", { className: `
|
|
28
|
-
pointer-events-none inline-block
|
|
28
|
+
pointer-events-none inline-block size-4 transform rounded-full bg-bg-surface shadow ring-0
|
|
29
29
|
transition duration-200 ease-in-out
|
|
30
30
|
${o ? "translate-x-4" : "translate-x-0"}
|
|
31
31
|
` })
|
|
@@ -34,7 +34,7 @@ var o = ({ enabled: o, onToggle: s, hasPaymentMethodSelected: c, selectedPayment
|
|
|
34
34
|
children: [/* @__PURE__ */ i("div", {
|
|
35
35
|
className: "flex items-center gap-1.5",
|
|
36
36
|
children: [
|
|
37
|
-
/* @__PURE__ */ r(n, { className: "
|
|
37
|
+
/* @__PURE__ */ r(n, { className: "size-3.5 text-muted-foreground shrink-0" }),
|
|
38
38
|
/* @__PURE__ */ r("span", {
|
|
39
39
|
className: "text-xs font-medium text-foreground",
|
|
40
40
|
children: p("billing.checkout.autoRenew", "Auto-renew subscription")
|
|
@@ -43,7 +43,7 @@ var o = ({ enabled: o, onToggle: s, hasPaymentMethodSelected: c, selectedPayment
|
|
|
43
43
|
type: "button",
|
|
44
44
|
onClick: () => h(!m),
|
|
45
45
|
className: "p-0.5 hover:bg-muted rounded transition-colors",
|
|
46
|
-
children: /* @__PURE__ */ r(t, { className: "
|
|
46
|
+
children: /* @__PURE__ */ r(t, { className: "size-3.5 text-muted-foreground" })
|
|
47
47
|
})
|
|
48
48
|
]
|
|
49
49
|
}), o && l && u ? /* @__PURE__ */ r("div", {
|
|
@@ -64,7 +64,7 @@ var o = ({ enabled: o, onToggle: s, hasPaymentMethodSelected: c, selectedPayment
|
|
|
64
64
|
children: g ? "Your subscription will automatically renew when it expires using the payment method selected on the left." : "Enable auto-renewal to automatically charge your payment method when the subscription expires. Select a payment method first."
|
|
65
65
|
}), !g && /* @__PURE__ */ i("div", {
|
|
66
66
|
className: "flex items-start gap-2 p-2 rounded bg-warning/10 mt-2",
|
|
67
|
-
children: [/* @__PURE__ */ r(t, { className: "
|
|
67
|
+
children: [/* @__PURE__ */ r(t, { className: "size-3.5 text-warning shrink-0 mt-0.5" }), /* @__PURE__ */ r("p", {
|
|
68
68
|
className: "text-xs text-warning",
|
|
69
69
|
children: "You can save a card during this purchase, then enable auto-renew for future subscriptions."
|
|
70
70
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SimpleAutoPayToggle.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/SimpleAutoPayToggle.tsx"],"sourcesContent":["/**\n * Simple Auto-Pay Toggle Component\n * Just enables/disables auto-renewal using the selected payment method\n */\n\nimport { type FC, useState } from 'react';\nimport { RefreshCw, Info } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface SimpleAutoPayToggleProps {\n enabled: boolean;\n onToggle: (enabled: boolean) => void;\n hasPaymentMethodSelected: boolean;\n selectedPaymentMethodDisplay?: string;\n isNewCard?: boolean; // Whether the selected payment method is a new card (not saved)\n disabled?: boolean;\n}\n\nexport const SimpleAutoPayToggle: FC<SimpleAutoPayToggleProps> = ({\n enabled,\n onToggle,\n hasPaymentMethodSelected,\n selectedPaymentMethodDisplay,\n isNewCard = false,\n disabled = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [showInfo, setShowInfo] = useState(false);\n const canEnable = hasPaymentMethodSelected;\n\n return (\n <div className=\"pt-3 border-t border-border space-y-2\">\n {/* Toggle Row */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n if (!canEnable) return;\n onToggle(!enabled);\n }}\n disabled={disabled || !canEnable}\n className={`\n relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent\n transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2\n ${enabled ? 'bg-primary' : 'bg-muted'}\n ${(!canEnable || disabled) && 'opacity-50 cursor-not-allowed'}\n `}\n >\n <span\n className={`\n pointer-events-none inline-block
|
|
1
|
+
{"version":3,"file":"SimpleAutoPayToggle.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/SimpleAutoPayToggle.tsx"],"sourcesContent":["/**\n * Simple Auto-Pay Toggle Component\n * Just enables/disables auto-renewal using the selected payment method\n */\n\nimport { type FC, useState } from 'react';\nimport { RefreshCw, Info } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface SimpleAutoPayToggleProps {\n enabled: boolean;\n onToggle: (enabled: boolean) => void;\n hasPaymentMethodSelected: boolean;\n selectedPaymentMethodDisplay?: string;\n isNewCard?: boolean; // Whether the selected payment method is a new card (not saved)\n disabled?: boolean;\n}\n\nexport const SimpleAutoPayToggle: FC<SimpleAutoPayToggleProps> = ({\n enabled,\n onToggle,\n hasPaymentMethodSelected,\n selectedPaymentMethodDisplay,\n isNewCard = false,\n disabled = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [showInfo, setShowInfo] = useState(false);\n const canEnable = hasPaymentMethodSelected;\n\n return (\n <div className=\"pt-3 border-t border-border space-y-2\">\n {/* Toggle Row */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n if (!canEnable) return;\n onToggle(!enabled);\n }}\n disabled={disabled || !canEnable}\n className={`\n relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent\n transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2\n ${enabled ? 'bg-primary' : 'bg-muted'}\n ${(!canEnable || disabled) && 'opacity-50 cursor-not-allowed'}\n `}\n >\n <span\n className={`\n pointer-events-none inline-block size-4 transform rounded-full bg-bg-surface shadow ring-0\n transition duration-200 ease-in-out\n ${enabled ? 'translate-x-4' : 'translate-x-0'}\n `}\n />\n </button>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <RefreshCw className=\"size-3.5 text-muted-foreground shrink-0\" />\n <span className=\"text-xs font-medium text-foreground\">\n {tr('billing.checkout.autoRenew', 'Auto-renew subscription')}\n </span>\n <button\n type=\"button\"\n onClick={() => setShowInfo(!showInfo)}\n className=\"p-0.5 hover:bg-muted rounded transition-colors\"\n >\n <Info className=\"size-3.5 text-muted-foreground\" />\n </button>\n </div>\n\n {/* Status Message */}\n {enabled && selectedPaymentMethodDisplay && isNewCard ? (\n <div className=\"text-xs text-accent-blue mt-0.5\">\n ℹ️ This payment method will be saved for future renewals\n </div>\n ) : enabled && selectedPaymentMethodDisplay ? (\n <div className=\"text-xs text-success mt-0.5\">\n ✓ Will renew using {selectedPaymentMethodDisplay}\n </div>\n ) : !canEnable ? (\n <div className=\"text-xs text-warning mt-0.5\">\n {tr('billing.checkout.selectPaymentFirst', 'Select a payment method first')}\n </div>\n ) : null}\n </div>\n </div>\n\n {/* Info Panel */}\n {showInfo && (\n <div className=\"p-3 rounded-lg bg-muted/50 border border-border\">\n <p className=\"text-xs text-muted-foreground\">\n {canEnable\n ? 'Your subscription will automatically renew when it expires using the payment method selected on the left.'\n : 'Enable auto-renewal to automatically charge your payment method when the subscription expires. Select a payment method first.'}\n </p>\n\n {!canEnable && (\n <div className=\"flex items-start gap-2 p-2 rounded bg-warning/10 mt-2\">\n <Info className=\"size-3.5 text-warning shrink-0 mt-0.5\" />\n <p className=\"text-xs text-warning\">\n You can save a card during this purchase, then enable auto-renew for future\n subscriptions.\n </p>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;AAkBA,IAAa,KAAqD,EAChE,YACA,aACA,6BACA,iCACA,eAAY,IACZ,cAAW,SACP;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,IAAY;AAElB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe;AACR,UACL,EAAS,CAAC,EAAQ;;IAEpB,UAAU,KAAY,CAAC;IACvB,WAAW;;;cAGP,IAAU,eAAe,WAAW;eACnC,CAAC,KAAa,MAAa,gCAAgC;;cAGhE,kBAAC,QAAD,EACE,WAAW;;;gBAGP,IAAU,kBAAkB,gBAAgB;eAEhD,CAAA;IACK,CAAA,EAET,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAW,WAAU,2CAA4C,CAAA;MACjE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,8BAA8B,0BAA0B;OACvD,CAAA;MACP,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAY,CAAC,EAAS;OACrC,WAAU;iBAEV,kBAAC,GAAD,EAAM,WAAU,kCAAmC,CAAA;OAC5C,CAAA;MACL;QAGL,KAAW,KAAgC,IAC1C,kBAAC,OAAD;KAAK,WAAU;eAAkC;KAE3C,CAAA,GACJ,KAAW,IACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CAA6C,uBACvB,EAChB;SACH,IAID,OAHF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAG,uCAAuC,gCAAgC;KACvE,CAAA,CAEJ;MACF;MAGL,KACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cACV,IACG,8GACA;IACF,CAAA,EAEH,CAAC,KACA,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAM,WAAU,yCAA0C,CAAA,EAC1D,kBAAC,KAAD;KAAG,WAAU;eAAuB;KAGhC,CAAA,CACA;MAEJ;KAEJ"}
|
|
@@ -119,7 +119,7 @@ var h = () => new Promise((e, t) => {
|
|
|
119
119
|
className: "flex items-center gap-3",
|
|
120
120
|
children: [/* @__PURE__ */ f("div", {
|
|
121
121
|
className: `p-2 rounded-full ${e.success.bg} ${e.success.icon}`,
|
|
122
|
-
children: /* @__PURE__ */ f(s, { className: "
|
|
122
|
+
children: /* @__PURE__ */ f(s, { className: "size-6" })
|
|
123
123
|
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
|
|
124
124
|
className: `font-semibold ${e.success.text}`,
|
|
125
125
|
children: "Payment Successful!"
|
|
@@ -133,17 +133,17 @@ var h = () => new Promise((e, t) => {
|
|
|
133
133
|
children: [
|
|
134
134
|
/* @__PURE__ */ p("div", {
|
|
135
135
|
className: "flex items-center gap-2",
|
|
136
|
-
children: [/* @__PURE__ */ f(u, { className: "
|
|
136
|
+
children: [/* @__PURE__ */ f(u, { className: "size-5 text-primary" }), /* @__PURE__ */ f("span", {
|
|
137
137
|
className: "font-medium text-foreground",
|
|
138
138
|
children: "Secure Payment Verification"
|
|
139
139
|
})]
|
|
140
140
|
}),
|
|
141
141
|
D === "processing" && /* @__PURE__ */ p("div", {
|
|
142
142
|
className: "p-6 rounded-lg border border-border bg-muted/30 flex flex-col items-center justify-center space-y-3",
|
|
143
|
-
children: [/* @__PURE__ */ f(l, { className: "
|
|
143
|
+
children: [/* @__PURE__ */ f(l, { className: "size-8 animate-spin text-primary" }), /* @__PURE__ */ p("p", {
|
|
144
144
|
className: "text-sm text-muted-foreground text-center",
|
|
145
145
|
children: [
|
|
146
|
-
"Confirming payment with your saved card
|
|
146
|
+
"Confirming payment with your saved card…",
|
|
147
147
|
/* @__PURE__ */ f("br", {}),
|
|
148
148
|
/* @__PURE__ */ f("span", {
|
|
149
149
|
className: "text-xs",
|
|
@@ -154,7 +154,7 @@ var h = () => new Promise((e, t) => {
|
|
|
154
154
|
}),
|
|
155
155
|
k && /* @__PURE__ */ p("div", {
|
|
156
156
|
className: "flex items-center gap-2 p-3 rounded-lg bg-destructive/10 text-destructive text-sm",
|
|
157
|
-
children: [/* @__PURE__ */ f(o, { className: "
|
|
157
|
+
children: [/* @__PURE__ */ f(o, { className: "size-4 shrink-0" }), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
|
|
158
158
|
className: "font-medium",
|
|
159
159
|
children: E("billing.checkout.paymentFailed", "Payment Failed")
|
|
160
160
|
}), /* @__PURE__ */ f("p", {
|
|
@@ -180,13 +180,13 @@ var h = () => new Promise((e, t) => {
|
|
|
180
180
|
className: "flex items-center justify-between",
|
|
181
181
|
children: [/* @__PURE__ */ p("div", {
|
|
182
182
|
className: "flex items-center gap-2",
|
|
183
|
-
children: [/* @__PURE__ */ f(c, { className: "
|
|
183
|
+
children: [/* @__PURE__ */ f(c, { className: "size-5 text-muted-foreground" }), /* @__PURE__ */ f("span", {
|
|
184
184
|
className: "font-medium text-foreground",
|
|
185
185
|
children: "Card Payment"
|
|
186
186
|
})]
|
|
187
187
|
}), /* @__PURE__ */ p("div", {
|
|
188
188
|
className: "flex items-center gap-1 text-xs text-muted-foreground",
|
|
189
|
-
children: [/* @__PURE__ */ f(u, { className: "
|
|
189
|
+
children: [/* @__PURE__ */ f(u, { className: "size-3" }), "Secured by Stripe"]
|
|
190
190
|
})]
|
|
191
191
|
}),
|
|
192
192
|
/* @__PURE__ */ p("div", {
|
|
@@ -201,7 +201,7 @@ var h = () => new Promise((e, t) => {
|
|
|
201
201
|
}),
|
|
202
202
|
k && /* @__PURE__ */ p("div", {
|
|
203
203
|
className: "flex items-center gap-2 p-3 rounded-lg bg-destructive/10 text-destructive text-sm",
|
|
204
|
-
children: [/* @__PURE__ */ f(o, { className: "
|
|
204
|
+
children: [/* @__PURE__ */ f(o, { className: "size-4 shrink-0" }), k]
|
|
205
205
|
}),
|
|
206
206
|
/* @__PURE__ */ p("div", {
|
|
207
207
|
className: "flex gap-3",
|
|
@@ -215,7 +215,7 @@ var h = () => new Promise((e, t) => {
|
|
|
215
215
|
type: "submit",
|
|
216
216
|
disabled: D === "processing" || C || !N,
|
|
217
217
|
className: "flex-1 px-4 py-3 rounded-lg bg-primary text-primary-foreground font-medium\n hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\n flex items-center justify-center gap-2",
|
|
218
|
-
children: D === "processing" ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(l, { className: "
|
|
218
|
+
children: D === "processing" ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(l, { className: "size-4 animate-spin" }), "Processing…"] }) : /* @__PURE__ */ p(d, { children: [
|
|
219
219
|
"Pay",
|
|
220
220
|
" ",
|
|
221
221
|
new Intl.NumberFormat("en-US", {
|
|
@@ -234,13 +234,13 @@ var h = () => new Promise((e, t) => {
|
|
|
234
234
|
className: "space-y-4",
|
|
235
235
|
children: [/* @__PURE__ */ p("div", {
|
|
236
236
|
className: "flex items-center gap-2",
|
|
237
|
-
children: [/* @__PURE__ */ f(c, { className: "
|
|
237
|
+
children: [/* @__PURE__ */ f(c, { className: "size-5 text-muted-foreground" }), /* @__PURE__ */ f("span", {
|
|
238
238
|
className: "font-medium text-foreground",
|
|
239
239
|
children: E("billing.checkout.cardPayment", "Card Payment")
|
|
240
240
|
})]
|
|
241
241
|
}), /* @__PURE__ */ p("div", {
|
|
242
242
|
className: "p-6 rounded-lg border border-border bg-muted/30 flex items-center justify-center",
|
|
243
|
-
children: [/* @__PURE__ */ f(l, { className: "
|
|
243
|
+
children: [/* @__PURE__ */ f(l, { className: "size-6 animate-spin text-muted-foreground" }), /* @__PURE__ */ f("span", {
|
|
244
244
|
className: "ml-2 text-muted-foreground",
|
|
245
245
|
children: E("billing.checkout.loadingPaymentForm", "Loading payment form...")
|
|
246
246
|
})]
|