@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.
- package/dist/billing/BillingAdminRoutes.js +84 -71
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +90 -87
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundIssueLogsPage.js +97 -58
- package/dist/billing/modules/billing/pages/RefundIssueLogsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +22 -19
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useCheckout.js +174 -157
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +189 -182
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +293 -274
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +140 -250
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/earnings/api.js +79 -42
- package/dist/billing/modules/earnings/api.js.map +1 -1
- package/dist/billing/modules/earnings/index.js +4 -3
- package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +103 -310
- package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +545 -0
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -0
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +246 -196
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js +44 -28
- package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +317 -270
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +153 -150
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/hooks/useUsage.js +74 -48
- package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
- package/dist/generated/global-operations.js +220 -183
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/dist/node_modules/sonner/dist/index.js +163 -0
- package/dist/node_modules/sonner/dist/index.js.map +1 -0
- 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 {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
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 (!
|
|
23
|
-
if (
|
|
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__ */
|
|
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: () =>
|
|
37
|
+
onRetry: () => N(),
|
|
29
38
|
showRetry: !0
|
|
30
39
|
})
|
|
31
40
|
});
|
|
32
|
-
let
|
|
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__ */
|
|
47
|
+
cell: (e) => /* @__PURE__ */ y("div", {
|
|
39
48
|
className: "flex flex-col",
|
|
40
|
-
children: [/* @__PURE__ */
|
|
49
|
+
children: [/* @__PURE__ */ v("span", {
|
|
41
50
|
className: "text-sm text-text-primary",
|
|
42
51
|
children: a(e.createdAt).split(" ")[0]
|
|
43
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
120
|
+
cell: (e) => /* @__PURE__ */ y("div", {
|
|
102
121
|
className: "flex items-start gap-1.5 py-1",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
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
|
-
],
|
|
110
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
135
|
+
actions: /* @__PURE__ */ y("div", {
|
|
117
136
|
className: "flex items-center gap-2",
|
|
118
|
-
children: [/* @__PURE__ */
|
|
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
|
|
122
|
-
}), /* @__PURE__ */
|
|
140
|
+
children: [/* @__PURE__ */ v(p, { className: "size-4" }), "Back to Refunds"]
|
|
141
|
+
}), /* @__PURE__ */ y("button", {
|
|
123
142
|
type: "button",
|
|
124
|
-
onClick: () =>
|
|
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__ */
|
|
145
|
+
children: [/* @__PURE__ */ v(m, { className: "size-4" }), "Refresh"]
|
|
127
146
|
})]
|
|
128
147
|
})
|
|
129
148
|
}),
|
|
130
|
-
|
|
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__ */
|
|
139
|
-
}) :
|
|
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__ */
|
|
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__ */
|
|
146
|
-
columns:
|
|
147
|
-
data:
|
|
184
|
+
children: /* @__PURE__ */ v(x, {
|
|
185
|
+
columns: I,
|
|
186
|
+
data: k,
|
|
148
187
|
rowKey: (e) => e.id
|
|
149
188
|
})
|
|
150
189
|
}),
|
|
151
|
-
|
|
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__ */
|
|
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
|
-
|
|
196
|
+
A.page * S + 1,
|
|
158
197
|
" to",
|
|
159
198
|
" ",
|
|
160
|
-
Math.min((
|
|
199
|
+
Math.min((A.page + 1) * S, A.totalCount),
|
|
161
200
|
" of",
|
|
162
201
|
" ",
|
|
163
|
-
|
|
202
|
+
A.totalCount,
|
|
164
203
|
" refunds with an open issue"
|
|
165
204
|
]
|
|
166
|
-
}), /* @__PURE__ */
|
|
205
|
+
}), /* @__PURE__ */ y("div", {
|
|
167
206
|
className: "flex items-center gap-2",
|
|
168
207
|
children: [
|
|
169
|
-
/* @__PURE__ */
|
|
208
|
+
/* @__PURE__ */ v("button", {
|
|
170
209
|
type: "button",
|
|
171
|
-
onClick:
|
|
172
|
-
disabled: !
|
|
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__ */
|
|
215
|
+
/* @__PURE__ */ y("span", {
|
|
177
216
|
className: "text-sm text-text-muted px-2",
|
|
178
217
|
children: [
|
|
179
218
|
"Page ",
|
|
180
|
-
|
|
219
|
+
A.page + 1,
|
|
181
220
|
" of ",
|
|
182
|
-
|
|
221
|
+
L
|
|
183
222
|
]
|
|
184
223
|
}),
|
|
185
|
-
/* @__PURE__ */
|
|
224
|
+
/* @__PURE__ */ v("button", {
|
|
186
225
|
type: "button",
|
|
187
|
-
onClick:
|
|
188
|
-
disabled: !
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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,
|
|
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:
|
|
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
|
-
}) :
|
|
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
|
|
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
|
-
|
|
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:
|
|
213
|
+
children: k ? `${A.toLocaleString(void 0, {
|
|
214
|
+
minimumFractionDigits: 0,
|
|
215
|
+
maximumFractionDigits: 0
|
|
216
|
+
})} Credits` : V(S)
|
|
214
217
|
})]
|
|
215
218
|
}),
|
|
216
|
-
|
|
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(
|
|
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
|
-
}) :
|
|
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
|
-
|
|
243
|
+
E || "Tax",
|
|
241
244
|
" (",
|
|
242
|
-
(
|
|
245
|
+
(T * 100).toFixed(0),
|
|
243
246
|
"%)"
|
|
244
247
|
]
|
|
245
248
|
}), /* @__PURE__ */ m("span", {
|
|
246
249
|
className: "text-text-primary",
|
|
247
|
-
children: V(
|
|
250
|
+
children: V(w)
|
|
248
251
|
})]
|
|
249
252
|
}) : null,
|
|
250
253
|
/* @__PURE__ */ m("div", { className: "border-t border-border-subtle my-2" }),
|
|
251
|
-
|
|
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
|
-
|
|
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 ",
|
|
277
|
+
children: ["Total in ", O]
|
|
275
278
|
}), /* @__PURE__ */ m("span", {
|
|
276
279
|
className: "text-xl font-bold text-text-primary",
|
|
277
|
-
children: n(
|
|
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 {
|
|
290
|
+
export { x as OrderSummary };
|
|
288
291
|
|
|
289
292
|
//# sourceMappingURL=OrderSummary.js.map
|