@burdenoff/microfe-billing 2026.913.1 → 2026.914.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.
Files changed (43) hide show
  1. package/dist/billing/BillingAdminRoutes.js +84 -71
  2. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  3. package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
  4. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +90 -87
  5. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  6. package/dist/billing/modules/billing/pages/RefundIssueLogsPage.js +97 -58
  7. package/dist/billing/modules/billing/pages/RefundIssueLogsPage.js.map +1 -1
  8. package/dist/billing/modules/checkout/components/OrderSummary.js +22 -19
  9. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  10. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  11. package/dist/billing/modules/checkout/hooks/useCheckout.js +174 -157
  12. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  13. package/dist/billing/modules/checkout/pages/CheckoutPage.js +189 -182
  14. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  15. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +293 -274
  16. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  17. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +140 -250
  18. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  19. package/dist/billing/modules/earnings/api.js +79 -42
  20. package/dist/billing/modules/earnings/api.js.map +1 -1
  21. package/dist/billing/modules/earnings/index.js +4 -3
  22. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +103 -310
  23. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
  24. package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +545 -0
  25. package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -0
  26. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +246 -196
  27. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  28. package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js +44 -28
  29. package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
  30. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +317 -270
  31. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  32. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +153 -150
  33. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  34. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +1 -1
  35. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  36. package/dist/billing/modules/usage/hooks/useUsage.js +74 -48
  37. package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
  38. package/dist/generated/global-operations.js +220 -183
  39. package/dist/generated/global-operations.js.map +1 -1
  40. package/dist/generated/global-types.js.map +1 -1
  41. package/dist/node_modules/sonner/dist/index.js +163 -0
  42. package/dist/node_modules/sonner/dist/index.js.map +1 -0
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentMethodsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/PaymentMethodsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Payment Methods Page\n * Displays and manages payment methods\n */\n\nimport { useCallback, useEffect, useState, type FC } from 'react';\nimport { useLocation } from 'react-router';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useDashboardMutations, useDefaultDashboardAccount } from '../../dashboard/hooks';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { AddPaymentMethodModal } from '../../dashboard/components/AddPaymentMethodModal';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport type { PaymentMethod, PaymentMethodType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nconst CARD_ICONS: Record<string, string> = {\n visa: '💳',\n mastercard: '💳',\n amex: '💳',\n default: '💳',\n};\n\nconst METHOD_TYPE_LABELS: Record<PaymentMethodType, string> = {\n CREDIT_CARD: 'Credit Card',\n DEBIT_CARD: 'Debit Card',\n WALLET: 'Digital Wallet',\n NETBANKING: 'Net Banking',\n BANK_TRANSFER: 'Bank Transfer',\n PAYPAL: 'PayPal',\n CRYPTO: 'Cryptocurrency',\n};\n\nexport const PaymentMethodsPage: FC = () => {\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 permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { navigate, basePath } = useBilling();\n const { pathname } = useLocation();\n const { billingAccount, paymentMethods, isLoading, refetch } = useDefaultDashboardAccount();\n const { setDefaultPaymentMethod, deletePaymentMethod } = useDashboardMutations();\n const [showAddModal, setShowAddModal] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // basePath-aware path join, mirroring OverviewPage's navigateTo helper.\n const toFullPath = useCallback(\n (path: string) => {\n if (basePath && basePath !== '/') {\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n return `${base}/${path}`;\n }\n return `/${path}`;\n },\n [basePath]\n );\n\n // Navigate (basePath-aware) to the dedicated add-payment-method route.\n const navigateToAdd = useCallback(() => {\n navigate(toFullPath('payment-methods/add'));\n }, [navigate, toFullPath]);\n\n const handleSetDefault = useCallback(\n async (paymentMethodId: string): Promise<void> => {\n const billingAccountId = billingAccount?.id;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n try {\n await setDefaultPaymentMethod(paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n emit('billing.payment_method.default_set', {\n route: '/billing/payment-methods',\n entityId: paymentMethodId,\n source: 'payment-methods',\n });\n } catch (error) {\n setActionError(\n error instanceof Error ? error.message : 'Failed to set default payment method'\n );\n void nativeNotify('error');\n }\n },\n [billingAccount?.id, emit, refetch, setDefaultPaymentMethod]\n );\n\n const handleDelete = useCallback(\n async (paymentMethodId: string): Promise<void> => {\n const billingAccountId = billingAccount?.id;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n try {\n await deletePaymentMethod(paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n emit('billing.payment_method.deleted', {\n route: '/billing/payment-methods',\n entityId: paymentMethodId,\n source: 'payment-methods',\n });\n } catch (error) {\n setActionError(error instanceof Error ? error.message : 'Failed to remove payment method');\n void nativeNotify('error');\n }\n },\n [billingAccount?.id, deletePaymentMethod, emit, refetch]\n );\n\n // The /payment-methods/add route renders this same page; open the add modal\n // automatically when landing there so the route is actually functional.\n useEffect(() => {\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n setShowAddModal(true);\n }\n }, [pathname]);\n\n if (!permissions.canViewPaymentMethods) {\n return (\n <AccessDenied\n message={tr(\n 'billing.paymentMethods.noViewPermission',\n \"You don't have permission to view payment methods.\"\n )}\n />\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-32 bg-bg-sunken animate-pulse rounded-lg\" />\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.paymentMethods.title', 'Payment Methods')}\n description={tr('billing.paymentMethods.subtitle', 'Manage your saved payment methods')}\n actions={\n permissions.canManagePaymentMethods && (\n <button\n type=\"button\"\n onClick={navigateToAdd}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr('billing.paymentMethods.add', 'Add payment method')}\n </button>\n )\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.paymentMethods.purpose',\n 'Save and manage the cards and payment options used to pay invoices, subscriptions and one-off purchases. Add a method, mark one as default for automatic billing, or remove ones you no longer use.'\n )}\n </PagePurpose>\n\n {actionError && (\n <div className=\"rounded-button border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm text-status-error-text\">{actionError}</p>\n </div>\n )}\n\n {/* Payment Methods List */}\n {paymentMethods.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr('billing.paymentMethods.noPaymentMethods', 'No payment methods')}\n description={tr(\n 'billing.paymentMethods.noPaymentMethodsDesc',\n 'Add a payment method to make purchases and manage subscriptions.'\n )}\n action={\n permissions.canManagePaymentMethods ? (\n <button\n type=\"button\"\n onClick={() => setShowAddModal(true)}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90\"\n >\n Add Payment Method\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {paymentMethods.map((method) => (\n <PaymentMethodCard\n key={method.id}\n method={method}\n canManage={permissions.canManagePaymentMethods}\n onSetDefault={handleSetDefault}\n onDelete={handleDelete}\n />\n ))}\n </div>\n )}\n\n {/* Add Payment Method Modal (Stripe + Razorpay) */}\n <AddPaymentMethodModal\n isOpen={showAddModal}\n onClose={() => {\n setShowAddModal(false);\n // If we arrived via the /add route, return to the list URL.\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n billingAccountId={billingAccount?.id ?? ''}\n currency={billingAccount?.currency ?? 'USD'}\n country={billingAccount?.billingAddresses?.[0]?.country ?? undefined}\n onSuccess={() => {\n emit('billing.payment_method.added', {\n route: '/billing/payment-methods',\n entityId: billingAccount?.id,\n source: 'payment-methods',\n });\n // Refetch billing account to get updated payment methods\n setActionError(null);\n void refetch();\n setShowAddModal(false);\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n />\n </div>\n );\n};\n\n// ============================================================================\n// Payment Method Card Component\n// ============================================================================\n\ninterface PaymentMethodCardProps {\n method: PaymentMethod;\n canManage: boolean;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n}\n\nconst PaymentMethodCard: FC<PaymentMethodCardProps> = ({\n method,\n canManage,\n onSetDefault,\n onDelete,\n}) => {\n const [isSettingDefault, setIsSettingDefault] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);\n const isExpired =\n method.expiryYear && method.expiryMonth\n ? new Date(method.expiryYear, method.expiryMonth - 1) < new Date()\n : false;\n\n const handleSetDefault = async () => {\n setIsSettingDefault(true);\n try {\n await onSetDefault(method.id);\n } finally {\n setIsSettingDefault(false);\n }\n };\n\n const handleDelete = async () => {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n } finally {\n setIsDeleting(false);\n setIsConfirmingDelete(false);\n }\n };\n\n const handleDeleteRequest = async () => {\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete payment method',\n message: 'Are you sure you want to remove this payment method?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n\n if (confirmed === true) {\n await handleDelete();\n } else if (confirmed === null) {\n setIsConfirmingDelete(true);\n }\n };\n\n return (\n <div\n className={`border rounded-lg bg-bg-surface p-4 ${\n method.isDefault ? 'border-primary' : 'border-border-subtle'\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-2xl\">{CARD_ICONS[method.brand?.toLowerCase() ?? 'default']}</span>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {method.brand ?? METHOD_TYPE_LABELS[method.type]} •••• {method.last4}\n </p>\n {method.isDefault && (\n <span className=\"px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded\">\n Default\n </span>\n )}\n </div>\n {method.expiryMonth && method.expiryYear && (\n <p\n className={`text-sm ${isExpired ? 'text-status-error-text' : 'text-text-secondary'}`}\n >\n {isExpired ? 'Expired' : 'Expires'} {method.expiryMonth}/{method.expiryYear}\n </p>\n )}\n </div>\n </div>\n </div>\n\n {canManage && (\n <div className=\"flex items-center gap-2 mt-4 pt-4 border-t border-border-subtle\">\n {!method.isDefault && (\n <button\n type=\"button\"\n onClick={handleSetDefault}\n disabled={isSettingDefault || isDeleting}\n className=\"text-sm text-primary hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isSettingDefault ? 'Setting default…' : 'Set as default'}\n </button>\n )}\n <button\n type=\"button\"\n onClick={handleDeleteRequest}\n disabled={isSettingDefault || isDeleting}\n className=\"text-sm text-status-error-text hover:underline ml-auto disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n )}\n\n {isConfirmingDelete && (\n <div className=\"mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm font-medium text-status-error-text\">\n Remove this payment method from your billing account?\n </p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingDelete(false)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PaymentMethodsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/PaymentMethodsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Payment Methods Page\n * Displays and manages payment methods\n */\n\nimport { useCallback, useEffect, useState, type FC } from 'react';\nimport { useLocation } from 'react-router';\nimport { toast } from 'sonner';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useDashboardMutations, useDefaultDashboardAccount } from '../../dashboard/hooks';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { AddPaymentMethodModal } from '../../dashboard/components/AddPaymentMethodModal';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport type { PaymentMethod, PaymentMethodType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nconst CARD_ICONS: Record<string, string> = {\n visa: '💳',\n mastercard: '💳',\n amex: '💳',\n default: '💳',\n};\n\nconst METHOD_TYPE_LABELS: Record<PaymentMethodType, string> = {\n CREDIT_CARD: 'Credit Card',\n DEBIT_CARD: 'Debit Card',\n WALLET: 'Digital Wallet',\n NETBANKING: 'Net Banking',\n BANK_TRANSFER: 'Bank Transfer',\n PAYPAL: 'PayPal',\n CRYPTO: 'Cryptocurrency',\n};\n\nexport const PaymentMethodsPage: FC = () => {\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 permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { navigate, basePath } = useBilling();\n const { pathname } = useLocation();\n const { billingAccount, paymentMethods, isLoading, refetch } = useDefaultDashboardAccount();\n const { setDefaultPaymentMethod, deletePaymentMethod } = useDashboardMutations();\n const [showAddModal, setShowAddModal] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // basePath-aware path join, mirroring OverviewPage's navigateTo helper.\n const toFullPath = useCallback(\n (path: string) => {\n if (basePath && basePath !== '/') {\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n return `${base}/${path}`;\n }\n return `/${path}`;\n },\n [basePath]\n );\n\n // Navigate (basePath-aware) to the dedicated add-payment-method route.\n const navigateToAdd = useCallback(() => {\n navigate(toFullPath('payment-methods/add'));\n }, [navigate, toFullPath]);\n\n const handleSetDefault = useCallback(\n async (paymentMethodId: string): Promise<void> => {\n const billingAccountId = billingAccount?.id;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n try {\n await setDefaultPaymentMethod(paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n emit('billing.payment_method.default_set', {\n route: '/billing/payment-methods',\n entityId: paymentMethodId,\n source: 'payment-methods',\n });\n } catch (error) {\n setActionError(\n error instanceof Error ? error.message : 'Failed to set default payment method'\n );\n void nativeNotify('error');\n }\n },\n [billingAccount?.id, emit, refetch, setDefaultPaymentMethod]\n );\n\n const handleDelete = useCallback(\n async (paymentMethodId: string): Promise<void> => {\n const billingAccountId = billingAccount?.id;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n try {\n await deletePaymentMethod(paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n emit('billing.payment_method.deleted', {\n route: '/billing/payment-methods',\n entityId: paymentMethodId,\n source: 'payment-methods',\n });\n } catch (error) {\n setActionError(error instanceof Error ? error.message : 'Failed to remove payment method');\n void nativeNotify('error');\n }\n },\n [billingAccount?.id, deletePaymentMethod, emit, refetch]\n );\n\n // The /payment-methods/add route renders this same page; open the add modal\n // automatically when landing there so the route is actually functional.\n useEffect(() => {\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n setShowAddModal(true);\n }\n }, [pathname]);\n\n if (!permissions.canViewPaymentMethods) {\n return (\n <AccessDenied\n message={tr(\n 'billing.paymentMethods.noViewPermission',\n \"You don't have permission to view payment methods.\"\n )}\n />\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-32 bg-bg-sunken animate-pulse rounded-lg\" />\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.paymentMethods.title', 'Payment Methods')}\n description={tr('billing.paymentMethods.subtitle', 'Manage your saved payment methods')}\n actions={\n permissions.canManagePaymentMethods && (\n <button\n type=\"button\"\n onClick={navigateToAdd}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr('billing.paymentMethods.add', 'Add payment method')}\n </button>\n )\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.paymentMethods.purpose',\n 'Save and manage the cards and payment options used to pay invoices, subscriptions and one-off purchases. Add a method, mark one as default for automatic billing, or remove ones you no longer use.'\n )}\n </PagePurpose>\n\n {actionError && (\n <div className=\"rounded-button border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm text-status-error-text\">{actionError}</p>\n </div>\n )}\n\n {/* Payment Methods List */}\n {paymentMethods.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr('billing.paymentMethods.noPaymentMethods', 'No payment methods')}\n description={tr(\n 'billing.paymentMethods.noPaymentMethodsDesc',\n 'Add a payment method to make purchases and manage subscriptions.'\n )}\n action={\n permissions.canManagePaymentMethods ? (\n <button\n type=\"button\"\n onClick={() => setShowAddModal(true)}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90\"\n >\n Add Payment Method\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {paymentMethods.map((method) => (\n <PaymentMethodCard\n key={method.id}\n method={method}\n canManage={permissions.canManagePaymentMethods}\n onSetDefault={handleSetDefault}\n onDelete={handleDelete}\n />\n ))}\n </div>\n )}\n\n {/* Add Payment Method Modal (Stripe + Razorpay) */}\n <AddPaymentMethodModal\n isOpen={showAddModal}\n onClose={() => {\n setShowAddModal(false);\n // If we arrived via the /add route, return to the list URL.\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n billingAccountId={billingAccount?.id ?? ''}\n currency={billingAccount?.currency ?? 'USD'}\n country={billingAccount?.billingAddresses?.[0]?.country ?? undefined}\n onSuccess={() => {\n emit('billing.payment_method.added', {\n route: '/billing/payment-methods',\n entityId: billingAccount?.id,\n source: 'payment-methods',\n });\n setActionError(null);\n void nativeNotify('success');\n toast.success('Payment method added successfully');\n setShowAddModal(false);\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n // The new PaymentMethod row is created by the provider's webhook\n // (e.g. Stripe's setup_intent.succeeded), which can land a beat\n // after the client-side confirm resolves — a single immediate\n // refetch can race it and show the stale list. Refetch again\n // shortly after as a safety net so the new card reliably appears\n // without the user needing to manually reload the page.\n void refetch();\n setTimeout(() => {\n void refetch();\n }, 2500);\n }}\n />\n </div>\n );\n};\n\n// ============================================================================\n// Payment Method Card Component\n// ============================================================================\n\ninterface PaymentMethodCardProps {\n method: PaymentMethod;\n canManage: boolean;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n}\n\nconst PaymentMethodCard: FC<PaymentMethodCardProps> = ({\n method,\n canManage,\n onSetDefault,\n onDelete,\n}) => {\n const [isSettingDefault, setIsSettingDefault] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);\n const isExpired =\n method.expiryYear && method.expiryMonth\n ? new Date(method.expiryYear, method.expiryMonth - 1) < new Date()\n : false;\n\n const handleSetDefault = async () => {\n setIsSettingDefault(true);\n try {\n await onSetDefault(method.id);\n } finally {\n setIsSettingDefault(false);\n }\n };\n\n const handleDelete = async () => {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n } finally {\n setIsDeleting(false);\n setIsConfirmingDelete(false);\n }\n };\n\n const handleDeleteRequest = async () => {\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete payment method',\n message: 'Are you sure you want to remove this payment method?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n\n if (confirmed === true) {\n await handleDelete();\n } else if (confirmed === null) {\n setIsConfirmingDelete(true);\n }\n };\n\n return (\n <div\n className={`border rounded-lg bg-bg-surface p-4 ${\n method.isDefault ? 'border-primary' : 'border-border-subtle'\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-2xl\">{CARD_ICONS[method.brand?.toLowerCase() ?? 'default']}</span>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {method.brand ?? METHOD_TYPE_LABELS[method.type]} •••• {method.last4}\n </p>\n {method.isDefault && (\n <span className=\"px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded\">\n Default\n </span>\n )}\n </div>\n {method.expiryMonth && method.expiryYear && (\n <p\n className={`text-sm ${isExpired ? 'text-status-error-text' : 'text-text-secondary'}`}\n >\n {isExpired ? 'Expired' : 'Expires'} {method.expiryMonth}/{method.expiryYear}\n </p>\n )}\n </div>\n </div>\n </div>\n\n {canManage && (\n <div className=\"flex items-center gap-2 mt-4 pt-4 border-t border-border-subtle\">\n {!method.isDefault && (\n <button\n type=\"button\"\n onClick={handleSetDefault}\n disabled={isSettingDefault || isDeleting}\n className=\"text-sm text-primary hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isSettingDefault ? 'Setting default…' : 'Set as default'}\n </button>\n )}\n <button\n type=\"button\"\n onClick={handleDeleteRequest}\n disabled={isSettingDefault || isDeleting}\n className=\"text-sm text-status-error-text hover:underline ml-auto disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n )}\n\n {isConfirmingDelete && (\n <div className=\"mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm font-medium text-status-error-text\">\n Remove this payment method from your billing account?\n </p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingDelete(false)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -9,38 +9,47 @@ import "../../../shared/utils/index.js";
9
9
  import { useBillingPermissions as s } from "../../../hooks/useBillingPermissions.js";
10
10
  import { useRefundRecords as c } from "../hooks/useRefunds.js";
11
11
  import "../hooks/index.js";
12
- import { AlertTriangle as l, ArrowLeft as u, RefreshCw as d } from "lucide-react";
13
- import { Link as f } from "react-router";
14
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
15
- import { IllustratedEmptyState as h, ResponsiveTable as g } from "@burdenoff/fe-libs/ui";
12
+ import { useEffect as l, useMemo as u, useState as d } from "react";
13
+ import { AlertTriangle as f, ArrowLeft as p, RefreshCw as m, Search as h, X as g } from "lucide-react";
14
+ import { Link as _ } from "react-router";
15
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
16
+ import { IllustratedEmptyState as b, ResponsiveTable as x } from "@burdenoff/fe-libs/ui";
16
17
  //#region src/billing/modules/billing/pages/RefundIssueLogsPage.tsx
17
- var _ = 25, v = { hasError: !0 }, y = () => {
18
- let y = s(), { records: b, pagination: x, isLoading: S, error: C, refetch: w, goToNextPage: T, goToPreviousPage: E } = c({
19
- pageSize: _,
20
- filters: v
18
+ var S = 25, C = 350, w = () => {
19
+ let w = s(), [T, E] = d(""), [D, O] = d("");
20
+ l(() => {
21
+ let e = setTimeout(() => O(T.trim()), C);
22
+ return () => clearTimeout(e);
23
+ }, [T]);
24
+ let { records: k, pagination: A, isLoading: j, error: M, refetch: N, goToNextPage: P, goToPreviousPage: F } = c({
25
+ pageSize: S,
26
+ filters: u(() => ({
27
+ hasError: !0,
28
+ search: D || void 0
29
+ }), [D])
21
30
  });
22
- if (!y.canViewPayments) return /* @__PURE__ */ p(e, { message: "You don't have permission to view refund logs." });
23
- if (C && o(C)) return /* @__PURE__ */ p("div", {
31
+ if (!w.canViewPayments) return /* @__PURE__ */ v(e, { message: "You don't have permission to view refund logs." });
32
+ if (M && o(M)) return /* @__PURE__ */ v("div", {
24
33
  className: "p-6",
25
- children: /* @__PURE__ */ p(r, {
34
+ children: /* @__PURE__ */ v(r, {
26
35
  title: "Server Unavailable",
27
36
  message: "Unable to load refund issue logs. The server might be down or experiencing issues.",
28
- onRetry: () => w(),
37
+ onRetry: () => N(),
29
38
  showRetry: !0
30
39
  })
31
40
  });
32
- let D = [
41
+ let I = [
33
42
  {
34
43
  key: "date",
35
44
  header: "Date",
36
45
  priority: "primary",
37
46
  cellClassName: "whitespace-nowrap",
38
- cell: (e) => /* @__PURE__ */ m("div", {
47
+ cell: (e) => /* @__PURE__ */ y("div", {
39
48
  className: "flex flex-col",
40
- children: [/* @__PURE__ */ p("span", {
49
+ children: [/* @__PURE__ */ v("span", {
41
50
  className: "text-sm text-text-primary",
42
51
  children: a(e.createdAt).split(" ")[0]
43
- }), /* @__PURE__ */ p("span", {
52
+ }), /* @__PURE__ */ v("span", {
44
53
  className: "text-xs text-text-muted",
45
54
  children: a(e.createdAt).split(" ")[1]
46
55
  })]
@@ -50,7 +59,7 @@ var _ = 25, v = { hasError: !0 }, y = () => {
50
59
  key: "status",
51
60
  header: "Status",
52
61
  cellClassName: "whitespace-nowrap",
53
- cell: (e) => /* @__PURE__ */ p("span", {
62
+ cell: (e) => /* @__PURE__ */ v("span", {
54
63
  className: `inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${n.success.bg} ${n.success.text}`,
55
64
  children: e.status
56
65
  })
@@ -60,16 +69,26 @@ var _ = 25, v = { hasError: !0 }, y = () => {
60
69
  header: "Provider",
61
70
  priority: "hidden-on-mobile",
62
71
  cellClassName: "whitespace-nowrap",
63
- cell: (e) => /* @__PURE__ */ p("span", {
72
+ cell: (e) => /* @__PURE__ */ v("span", {
64
73
  className: "text-sm text-text-primary capitalize",
65
74
  children: e.provider
66
75
  })
67
76
  },
77
+ {
78
+ key: "transactionId",
79
+ header: "Transaction ID",
80
+ priority: "hidden-on-mobile",
81
+ cellClassName: "whitespace-nowrap max-w-[140px] truncate",
82
+ cell: (e) => /* @__PURE__ */ v("span", {
83
+ className: "text-sm font-mono text-text-muted",
84
+ children: e.transactionId || "-"
85
+ })
86
+ },
68
87
  {
69
88
  key: "paymentId",
70
89
  header: "Payment ID",
71
90
  cellClassName: "whitespace-nowrap max-w-[140px] truncate",
72
- cell: (e) => /* @__PURE__ */ p("span", {
91
+ cell: (e) => /* @__PURE__ */ v("span", {
73
92
  className: "text-sm font-mono text-text-muted",
74
93
  children: e.gatewayPaymentId
75
94
  })
@@ -79,7 +98,7 @@ var _ = 25, v = { hasError: !0 }, y = () => {
79
98
  header: "Amount",
80
99
  headerClassName: "text-right",
81
100
  cellClassName: "text-right whitespace-nowrap",
82
- cell: (e) => /* @__PURE__ */ p("span", {
101
+ cell: (e) => /* @__PURE__ */ v("span", {
83
102
  className: "text-sm font-medium tabular-nums",
84
103
  children: i(e.amount, e.currency)
85
104
  })
@@ -89,7 +108,7 @@ var _ = 25, v = { hasError: !0 }, y = () => {
89
108
  header: "Type",
90
109
  priority: "hidden-on-mobile",
91
110
  cellClassName: "whitespace-nowrap",
92
- cell: (e) => /* @__PURE__ */ p("span", {
111
+ cell: (e) => /* @__PURE__ */ v("span", {
93
112
  className: "text-sm text-text-muted",
94
113
  children: e.purchaseType || "-"
95
114
  })
@@ -98,36 +117,56 @@ var _ = 25, v = { hasError: !0 }, y = () => {
98
117
  key: "issue",
99
118
  header: "Issue",
100
119
  cellClassName: "max-w-[360px]",
101
- cell: (e) => /* @__PURE__ */ m("div", {
120
+ cell: (e) => /* @__PURE__ */ y("div", {
102
121
  className: "flex items-start gap-1.5 py-1",
103
- children: [/* @__PURE__ */ p(l, { className: `size-3.5 mt-0.5 shrink-0 ${n.warning.icon}` }), /* @__PURE__ */ p("span", {
122
+ children: [/* @__PURE__ */ v(f, { className: `size-3.5 mt-0.5 shrink-0 ${n.warning.icon}` }), /* @__PURE__ */ v("span", {
104
123
  className: "text-sm text-status-warning-text",
105
124
  children: e.errorMessage
106
125
  })]
107
126
  })
108
127
  }
109
- ], O = Math.max(1, Math.ceil(x.totalCount / _));
110
- return /* @__PURE__ */ m("div", {
128
+ ], L = Math.max(1, Math.ceil(A.totalCount / S));
129
+ return /* @__PURE__ */ y("div", {
111
130
  className: "space-y-6 p-6",
112
131
  children: [
113
- /* @__PURE__ */ p(t, {
132
+ /* @__PURE__ */ v(t, {
114
133
  title: "Refund Issue Log",
115
134
  description: "Refunds that succeeded with the payment gateway but left an accounting or reconciliation issue behind — e.g. a Zoho credit note that couldn't be recorded. The customer was refunded; these still need manual follow-up.",
116
- actions: /* @__PURE__ */ m("div", {
135
+ actions: /* @__PURE__ */ y("div", {
117
136
  className: "flex items-center gap-2",
118
- children: [/* @__PURE__ */ m(f, {
137
+ children: [/* @__PURE__ */ y(_, {
119
138
  to: "../refunds",
120
139
  className: "inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50",
121
- children: [/* @__PURE__ */ p(u, { className: "size-4" }), "Back to Refunds"]
122
- }), /* @__PURE__ */ m("button", {
140
+ children: [/* @__PURE__ */ v(p, { className: "size-4" }), "Back to Refunds"]
141
+ }), /* @__PURE__ */ y("button", {
123
142
  type: "button",
124
- onClick: () => w(),
143
+ onClick: () => N(),
125
144
  className: "inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50",
126
- children: [/* @__PURE__ */ p(d, { className: "size-4" }), "Refresh"]
145
+ children: [/* @__PURE__ */ v(m, { className: "size-4" }), "Refresh"]
127
146
  })]
128
147
  })
129
148
  }),
130
- S && b.length === 0 ? /* @__PURE__ */ p("div", {
149
+ /* @__PURE__ */ y("div", {
150
+ className: "relative",
151
+ children: [
152
+ /* @__PURE__ */ v(h, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted" }),
153
+ /* @__PURE__ */ v("input", {
154
+ type: "text",
155
+ placeholder: "Search by transaction ID, refund ID, gateway payment ID, or gateway refund ID…",
156
+ value: T,
157
+ onChange: (e) => E(e.target.value),
158
+ className: "w-full pl-[3.25rem] pr-10 py-2 border border-border-subtle rounded-md bg-bg-surface"
159
+ }),
160
+ T && /* @__PURE__ */ v("button", {
161
+ type: "button",
162
+ onClick: () => E(""),
163
+ className: "absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-sunken/50",
164
+ "aria-label": "Clear search",
165
+ children: /* @__PURE__ */ v(g, { className: "size-4 text-text-muted" })
166
+ })
167
+ ]
168
+ }),
169
+ j && k.length === 0 ? /* @__PURE__ */ v("div", {
131
170
  className: "space-y-2",
132
171
  children: [
133
172
  1,
@@ -135,57 +174,57 @@ var _ = 25, v = { hasError: !0 }, y = () => {
135
174
  3,
136
175
  4,
137
176
  5
138
- ].map((e) => /* @__PURE__ */ p("div", { className: "h-16 bg-bg-sunken animate-pulse rounded" }, e))
139
- }) : b.length === 0 ? /* @__PURE__ */ p(h, {
140
- illustration: "empty-data",
141
- title: "No open refund issues",
142
- description: "Every completed refund reconciled cleanly — nothing here needs follow-up right now."
143
- }) : /* @__PURE__ */ p("div", {
177
+ ].map((e) => /* @__PURE__ */ v("div", { className: "h-16 bg-bg-sunken animate-pulse rounded" }, e))
178
+ }) : k.length === 0 ? /* @__PURE__ */ v(b, {
179
+ illustration: D ? "empty-search" : "empty-data",
180
+ title: D ? "No matching refund issues" : "No open refund issues",
181
+ description: D ? `Nothing matched "${D}". Try the transaction ID, refund ID, or a payment/refund ID from the gateway.` : "Every completed refund reconciled cleanly — nothing here needs follow-up right now."
182
+ }) : /* @__PURE__ */ v("div", {
144
183
  className: "border border-border-subtle rounded-lg overflow-hidden",
145
- children: /* @__PURE__ */ p(g, {
146
- columns: D,
147
- data: b,
184
+ children: /* @__PURE__ */ v(x, {
185
+ columns: I,
186
+ data: k,
148
187
  rowKey: (e) => e.id
149
188
  })
150
189
  }),
151
- x.totalCount > 0 && /* @__PURE__ */ m("div", {
190
+ A.totalCount > 0 && /* @__PURE__ */ y("div", {
152
191
  className: "flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4",
153
- children: [/* @__PURE__ */ m("p", {
192
+ children: [/* @__PURE__ */ y("p", {
154
193
  className: "text-sm text-text-muted text-center sm:text-left",
155
194
  children: [
156
195
  "Showing ",
157
- x.page * _ + 1,
196
+ A.page * S + 1,
158
197
  " to",
159
198
  " ",
160
- Math.min((x.page + 1) * _, x.totalCount),
199
+ Math.min((A.page + 1) * S, A.totalCount),
161
200
  " of",
162
201
  " ",
163
- x.totalCount,
202
+ A.totalCount,
164
203
  " refunds with an open issue"
165
204
  ]
166
- }), /* @__PURE__ */ m("div", {
205
+ }), /* @__PURE__ */ y("div", {
167
206
  className: "flex items-center gap-2",
168
207
  children: [
169
- /* @__PURE__ */ p("button", {
208
+ /* @__PURE__ */ v("button", {
170
209
  type: "button",
171
- onClick: E,
172
- disabled: !x.hasPrevious,
210
+ onClick: F,
211
+ disabled: !A.hasPrevious,
173
212
  className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
174
213
  children: "Previous"
175
214
  }),
176
- /* @__PURE__ */ m("span", {
215
+ /* @__PURE__ */ y("span", {
177
216
  className: "text-sm text-text-muted px-2",
178
217
  children: [
179
218
  "Page ",
180
- x.page + 1,
219
+ A.page + 1,
181
220
  " of ",
182
- O
221
+ L
183
222
  ]
184
223
  }),
185
- /* @__PURE__ */ p("button", {
224
+ /* @__PURE__ */ v("button", {
186
225
  type: "button",
187
- onClick: T,
188
- disabled: !x.hasMore,
226
+ onClick: P,
227
+ disabled: !A.hasMore,
189
228
  className: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
190
229
  children: "Next"
191
230
  })
@@ -196,6 +235,6 @@ var _ = 25, v = { hasError: !0 }, y = () => {
196
235
  });
197
236
  };
198
237
  //#endregion
199
- export { y as RefundIssueLogsPage };
238
+ export { w as RefundIssueLogsPage };
200
239
 
201
240
  //# sourceMappingURL=RefundIssueLogsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RefundIssueLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/RefundIssueLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Refund Issue Logs Page\n * Admin visibility into refunds that completed at the payment gateway but\n * carry an unresolved accounting/reconciliation issue (RefundRecord.errorMessage)\n * — e.g. a Zoho credit-note refund that couldn't be recorded because no bank\n * account is configured. These refunds are otherwise invisible: the customer\n * got their money back, so they don't show up as Failed, and the warning is\n * normally only visible by opening one refund's Details modal at a time.\n * This page surfaces every one of them in a single scannable list.\n */\n\nimport { type FC } from 'react';\nimport { AlertTriangle, ArrowLeft, RefreshCw } from 'lucide-react';\nimport { Link } from 'react-router';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useRefundRecords, type RefundRecord, type RefundRecordFilters } from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 25;\n\n// Module-level constant, not an inline object literal: useRefundRecords has a\n// useEffect keyed on options.filters, so a new object every render would\n// trigger it unconditionally on every render — an infinite update loop (the\n// same failure mode RefundsPage's tabFilters memoization already guards\n// against). This filter never changes, so a stable reference is sufficient.\nconst REFUND_ISSUE_FILTERS: RefundRecordFilters = { hasError: true };\n\nexport const RefundIssueLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n\n const { records, pagination, isLoading, error, refetch, goToNextPage, goToPreviousPage } =\n useRefundRecords({\n pageSize: PAGE_SIZE,\n filters: REFUND_ISSUE_FILTERS,\n });\n\n if (!permissions.canViewPayments) {\n return <AccessDenied message=\"You don't have permission to view refund logs.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load refund issue logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const columns: ResponsiveTableColumn<RefundRecord>[] = [\n {\n key: 'date',\n header: 'Date',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(record.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(record.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n {record.status}\n </span>\n ),\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-primary capitalize\">{record.provider}</span>\n ),\n },\n {\n key: 'paymentId',\n header: 'Payment ID',\n cellClassName: 'whitespace-nowrap max-w-[140px] truncate',\n cell: (record) => (\n <span className=\"text-sm font-mono text-text-muted\">{record.gatewayPaymentId}</span>\n ),\n },\n {\n key: 'amount',\n header: 'Amount',\n headerClassName: 'text-right',\n cellClassName: 'text-right whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm font-medium tabular-nums\">\n {formatCurrency(record.amount, record.currency)}\n </span>\n ),\n },\n {\n key: 'purchaseType',\n header: 'Type',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-muted\">{record.purchaseType || '-'}</span>\n ),\n },\n {\n key: 'issue',\n header: 'Issue',\n cellClassName: 'max-w-[360px]',\n cell: (record) => (\n <div className=\"flex items-start gap-1.5 py-1\">\n <AlertTriangle className={`size-3.5 mt-0.5 shrink-0 ${statusTokens.warning.icon}`} />\n <span className=\"text-sm text-status-warning-text\">{record.errorMessage}</span>\n </div>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(pagination.totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Refund Issue Log\"\n description=\"Refunds that succeeded with the payment gateway but left an accounting or reconciliation issue behind — e.g. a Zoho credit note that couldn't be recorded. The customer was refunded; these still need manual follow-up.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <Link\n to=\"../refunds\"\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Refunds\n </Link>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n }\n />\n\n {isLoading && records.length === 0 ? (\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n ) : records.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No open refund issues\"\n description=\"Every completed refund reconciled cleanly — nothing here needs follow-up right now.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<RefundRecord>\n columns={columns}\n data={records}\n rowKey={(record) => record.id}\n />\n </div>\n )}\n\n {pagination.totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {pagination.page * PAGE_SIZE + 1} to{' '}\n {Math.min((pagination.page + 1) * PAGE_SIZE, pagination.totalCount)} of{' '}\n {pagination.totalCount} refunds with an open issue\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goToPreviousPage}\n disabled={!pagination.hasPrevious}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Previous\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n Page {pagination.page + 1} of {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!pagination.hasMore}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"RefundIssueLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/RefundIssueLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Refund Issue Logs Page\n * Admin visibility into refunds that completed at the payment gateway but\n * carry an unresolved accounting/reconciliation issue (RefundRecord.errorMessage)\n * — e.g. a Zoho credit-note refund that couldn't be recorded because no bank\n * account is configured. These refunds are otherwise invisible: the customer\n * got their money back, so they don't show up as Failed, and the warning is\n * normally only visible by opening one refund's Details modal at a time.\n * This page surfaces every one of them in a single scannable list.\n */\n\nimport { useEffect, useMemo, useState, type FC } from 'react';\nimport { AlertTriangle, ArrowLeft, RefreshCw, Search, X } from 'lucide-react';\nimport { Link } from 'react-router';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useRefundRecords, type RefundRecord, type RefundRecordFilters } from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 25;\nconst SEARCH_DEBOUNCE_MS = 350;\n\nexport const RefundIssueLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n\n // Debounced so every keystroke doesn't fire a new server query.\n const [searchInput, setSearchInput] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(searchInput.trim()), SEARCH_DEBOUNCE_MS);\n return () => clearTimeout(timer);\n }, [searchInput]);\n\n // Memoized so useRefundRecords' internal useEffect (keyed on options.filters\n // by reference) only re-fires when the debounced search actually changes,\n // not on every render — the same infinite-loop failure mode documented on\n // RefundsPage's tabFilters memo.\n const filters = useMemo<RefundRecordFilters>(\n () => ({ hasError: true, search: debouncedSearch || undefined }),\n [debouncedSearch]\n );\n\n const { records, pagination, isLoading, error, refetch, goToNextPage, goToPreviousPage } =\n useRefundRecords({\n pageSize: PAGE_SIZE,\n filters,\n });\n\n if (!permissions.canViewPayments) {\n return <AccessDenied message=\"You don't have permission to view refund logs.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load refund issue logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const columns: ResponsiveTableColumn<RefundRecord>[] = [\n {\n key: 'date',\n header: 'Date',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(record.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(record.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n {record.status}\n </span>\n ),\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-primary capitalize\">{record.provider}</span>\n ),\n },\n {\n key: 'transactionId',\n header: 'Transaction ID',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap max-w-[140px] truncate',\n cell: (record) => (\n <span className=\"text-sm font-mono text-text-muted\">{record.transactionId || '-'}</span>\n ),\n },\n {\n key: 'paymentId',\n header: 'Payment ID',\n cellClassName: 'whitespace-nowrap max-w-[140px] truncate',\n cell: (record) => (\n <span className=\"text-sm font-mono text-text-muted\">{record.gatewayPaymentId}</span>\n ),\n },\n {\n key: 'amount',\n header: 'Amount',\n headerClassName: 'text-right',\n cellClassName: 'text-right whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm font-medium tabular-nums\">\n {formatCurrency(record.amount, record.currency)}\n </span>\n ),\n },\n {\n key: 'purchaseType',\n header: 'Type',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-muted\">{record.purchaseType || '-'}</span>\n ),\n },\n {\n key: 'issue',\n header: 'Issue',\n cellClassName: 'max-w-[360px]',\n cell: (record) => (\n <div className=\"flex items-start gap-1.5 py-1\">\n <AlertTriangle className={`size-3.5 mt-0.5 shrink-0 ${statusTokens.warning.icon}`} />\n <span className=\"text-sm text-status-warning-text\">{record.errorMessage}</span>\n </div>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(pagination.totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Refund Issue Log\"\n description=\"Refunds that succeeded with the payment gateway but left an accounting or reconciliation issue behind — e.g. a Zoho credit note that couldn't be recorded. The customer was refunded; these still need manual follow-up.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <Link\n to=\"../refunds\"\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Refunds\n </Link>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n }\n />\n\n {/* Search */}\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search by transaction ID, refund ID, gateway payment ID, or gateway refund ID…\"\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-10 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n {searchInput && (\n <button\n type=\"button\"\n onClick={() => setSearchInput('')}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded hover:bg-bg-sunken/50\"\n aria-label=\"Clear search\"\n >\n <X className=\"size-4 text-text-muted\" />\n </button>\n )}\n </div>\n\n {isLoading && records.length === 0 ? (\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n ) : records.length === 0 ? (\n <IllustratedEmptyState\n illustration={debouncedSearch ? 'empty-search' : 'empty-data'}\n title={debouncedSearch ? 'No matching refund issues' : 'No open refund issues'}\n description={\n debouncedSearch\n ? `Nothing matched \"${debouncedSearch}\". Try the transaction ID, refund ID, or a payment/refund ID from the gateway.`\n : 'Every completed refund reconciled cleanly — nothing here needs follow-up right now.'\n }\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<RefundRecord>\n columns={columns}\n data={records}\n rowKey={(record) => record.id}\n />\n </div>\n )}\n\n {pagination.totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {pagination.page * PAGE_SIZE + 1} to{' '}\n {Math.min((pagination.page + 1) * PAGE_SIZE, pagination.totalCount)} of{' '}\n {pagination.totalCount} refunds with an open issue\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goToPreviousPage}\n disabled={!pagination.hasPrevious}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Previous\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n Page {pagination.page + 1} of {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!pagination.hasMore}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
6
6
  import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
7
  import { Accordion as _, AccordionContent as v, AccordionItem as y, AccordionTrigger as b } from "@burdenoff/fe-libs/ui";
8
8
  //#region src/billing/modules/checkout/components/OrderSummary.tsx
9
- var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxRate: E, taxType: D, total: O, currency: k, showInCredits: A = !1, isCalculatingTax: j = !1, onUpdateQuantity: M, onRemoveItem: N, isEditable: P = !0 }) => {
9
+ var x = ({ items: x, subtotal: S, discountAmount: C, taxAmount: w, taxRate: T, taxType: E, total: D, currency: O, showInCredits: k = !1, creditsRequired: A = 0, isCalculatingTax: j = !1, onUpdateQuantity: M, onRemoveItem: N, isEditable: P = !0 }) => {
10
10
  let { t: F } = g(), I = (e, t) => {
11
11
  let n = F(e);
12
12
  return n === e ? t : n;
@@ -35,10 +35,10 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
35
35
  label: "Store Order",
36
36
  className: "bg-status-success-bg-subtle text-status-success-text"
37
37
  }
38
- })[t], z = (e) => e * x, B = r(() => S.reduce((e, t) => e + (t.priceUSD ?? t.price) * t.quantity, 0), [S]), V = (e, t) => A ? `${z(t?.priceUSD ?? e).toLocaleString(void 0, {
38
+ })[t], z = r(() => x.reduce((e, t) => e + t.price * t.quantity, 0), [x]), B = (e) => z > 0 ? e / z * A : 0, V = (e) => k ? `${B(e).toLocaleString(void 0, {
39
39
  minimumFractionDigits: 0,
40
40
  maximumFractionDigits: 0
41
- })} Credits` : n(e, k), H = (e, t) => typeof t == "boolean" ? t ? "Included" : "Not included" : typeof t == "number" ? t === -1 || t === Infinity ? "Unlimited" : t.toLocaleString() : String(t), U = (e) => {
41
+ })} Credits` : n(e, O), H = (e, t) => typeof t == "boolean" ? t ? "Included" : "Not included" : typeof t == "number" ? t === -1 || t === Infinity ? "Unlimited" : t.toLocaleString() : String(t), U = (e) => {
42
42
  let t = /* @__PURE__ */ new Map();
43
43
  for (let n of e) {
44
44
  let e = t.get(n.name);
@@ -89,13 +89,13 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
89
89
  }),
90
90
  /* @__PURE__ */ m("div", {
91
91
  className: "divide-y divide-border-subtle",
92
- children: S.length === 0 ? /* @__PURE__ */ h("div", {
92
+ children: x.length === 0 ? /* @__PURE__ */ h("div", {
93
93
  className: "p-6 text-center",
94
94
  children: [/* @__PURE__ */ m(c, { className: "mx-auto size-8 text-text-secondary" }), /* @__PURE__ */ m("p", {
95
95
  className: "mt-2 text-sm text-text-secondary",
96
96
  children: "No items in checkout"
97
97
  })]
98
- }) : S.map((e) => {
98
+ }) : x.map((e) => {
99
99
  let t = R(e.type), n = e.features && e.features.length > 0;
100
100
  return /* @__PURE__ */ m("div", {
101
101
  className: "p-4",
@@ -153,7 +153,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
153
153
  className: "flex items-center gap-3",
154
154
  children: [/* @__PURE__ */ m("span", {
155
155
  className: "font-medium text-text-primary",
156
- children: V(e.price * e.quantity, e)
156
+ children: V(e.price * e.quantity)
157
157
  }), P && N && /* @__PURE__ */ m("button", {
158
158
  type: "button",
159
159
  onClick: () => N(e.id),
@@ -198,7 +198,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
198
198
  }, e.id);
199
199
  })
200
200
  }),
201
- S.length > 0 && /* @__PURE__ */ m("div", {
201
+ x.length > 0 && /* @__PURE__ */ m("div", {
202
202
  className: "border-t border-border-subtle bg-bg-sunken",
203
203
  children: /* @__PURE__ */ h("div", {
204
204
  className: "p-4 space-y-2",
@@ -210,17 +210,20 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
210
210
  children: I("billing.checkout.subtotal", "Subtotal")
211
211
  }), /* @__PURE__ */ m("span", {
212
212
  className: "text-text-primary",
213
- children: V(A ? B : C)
213
+ children: k ? `${A.toLocaleString(void 0, {
214
+ minimumFractionDigits: 0,
215
+ maximumFractionDigits: 0
216
+ })} Credits` : V(S)
214
217
  })]
215
218
  }),
216
- w > 0 && !A && /* @__PURE__ */ h("div", {
219
+ C > 0 && !k && /* @__PURE__ */ h("div", {
217
220
  className: "flex items-center justify-between text-sm",
218
221
  children: [/* @__PURE__ */ m("span", {
219
222
  className: t.success.text,
220
223
  children: "Discount"
221
224
  }), /* @__PURE__ */ h("span", {
222
225
  className: t.success.text,
223
- children: ["-", V(w)]
226
+ children: ["-", V(C)]
224
227
  })]
225
228
  }),
226
229
  j ? /* @__PURE__ */ h("div", {
@@ -232,23 +235,23 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
232
235
  className: "text-text-secondary animate-pulse",
233
236
  children: "Calculating…"
234
237
  })]
235
- }) : T > 0 && E > 0 ? /* @__PURE__ */ h("div", {
238
+ }) : w > 0 && T > 0 ? /* @__PURE__ */ h("div", {
236
239
  className: "flex items-center justify-between text-sm",
237
240
  children: [/* @__PURE__ */ h("span", {
238
241
  className: "text-text-secondary",
239
242
  children: [
240
- D || "Tax",
243
+ E || "Tax",
241
244
  " (",
242
- (E * 100).toFixed(0),
245
+ (T * 100).toFixed(0),
243
246
  "%)"
244
247
  ]
245
248
  }), /* @__PURE__ */ m("span", {
246
249
  className: "text-text-primary",
247
- children: V(T)
250
+ children: V(w)
248
251
  })]
249
252
  }) : null,
250
253
  /* @__PURE__ */ m("div", { className: "border-t border-border-subtle my-2" }),
251
- A ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ h("div", {
254
+ k ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ h("div", {
252
255
  className: "flex items-center justify-between",
253
256
  children: [/* @__PURE__ */ m("span", {
254
257
  className: "font-semibold text-text-primary",
@@ -256,7 +259,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
256
259
  }), /* @__PURE__ */ h("span", {
257
260
  className: "text-xl font-bold text-text-primary",
258
261
  children: [
259
- z(B).toLocaleString(void 0, {
262
+ A.toLocaleString(void 0, {
260
263
  minimumFractionDigits: 0,
261
264
  maximumFractionDigits: 0
262
265
  }),
@@ -271,10 +274,10 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
271
274
  className: "flex items-center justify-between",
272
275
  children: [/* @__PURE__ */ h("span", {
273
276
  className: "font-semibold text-text-primary",
274
- children: ["Total in ", k]
277
+ children: ["Total in ", O]
275
278
  }), /* @__PURE__ */ m("span", {
276
279
  className: "text-xl font-bold text-text-primary",
277
- children: n(O, k)
280
+ children: n(D, O)
278
281
  })]
279
282
  }) })
280
283
  ]
@@ -284,6 +287,6 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
284
287
  });
285
288
  };
286
289
  //#endregion
287
- export { S as OrderSummary };
290
+ export { x as OrderSummary };
288
291
 
289
292
  //# sourceMappingURL=OrderSummary.js.map