@burdenoff/microfe-billing 2026.1013.2 → 2026.1013.4
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/modules/addons/pages/AddonCreatePage.js +4 -4
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -3
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js +3 -3
- package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js +2 -2
- package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +2 -2
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/StoreShippingSection.js +1 -1
- package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +2 -2
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +2 -2
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +2 -2
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +2 -2
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +2 -2
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +3 -3
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +3 -3
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -3
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +2 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BillingWebhookLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/BillingWebhookLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Billing Webhook Logs Page\n * Admin visibility into every Stripe/Razorpay webhook processing attempt —\n * not just the payment-succeeded subset eligible for automatic refund\n * creation (see RefundsPage's \"Import Failed Webhooks\" action). Lets whoever\n * owns billing ops see and investigate ANY webhook processing failure\n * without needing direct DB access.\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n X,\n DollarSign,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useBillingWebhookLogs,\n useRefundMutations,\n type BillingWebhookLogFilters,\n type WebhookLog,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { 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 PROVIDER_OPTIONS = ['stripe', 'razorpay'];\nconst REFUND_ELIGIBLE_EVENTS = new Set([\n 'payment_intent.succeeded',\n 'charge.succeeded',\n 'payment.captured',\n 'order.paid',\n]);\n\nconst getBadge = (\n log: WebhookLog\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (!log.processed) {\n return {\n label: log.error ? 'Failed' : 'Pending',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n return {\n label: 'Processed',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n};\n\nconst WebhookLogDetailModal: FC<{\n log: WebhookLog | null;\n onClose: () => void;\n onCreateRefund: (id: string) => void;\n isCreatingRefund: boolean;\n canCreateRefund: boolean;\n}> = ({ log, onClose, onCreateRefund, isCreatingRefund, canCreateRefund }) => {\n if (!log) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Webhook log details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Provider</p>\n <p className=\"text-text-primary capitalize\">{log.provider}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Event</p>\n <p className=\"text-text-primary font-mono text-xs\">{log.event}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Received</p>\n <p className=\"text-text-primary\">{formatDateTime(log.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Processed</p>\n <p className=\"text-text-primary\">{log.processed ? 'Yes' : 'No'}</p>\n </div>\n </div>\n {log.error && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {log.error}\n </pre>\n </div>\n )}\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Payload</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-bg-sunken/60 text-text-secondary max-h-64 overflow-y-auto\">\n {JSON.stringify(log.payload, null, 2)}\n </pre>\n </div>\n {!log.processed && canCreateRefund && (\n <button\n type=\"button\"\n onClick={() => onCreateRefund(log.id)}\n disabled={isCreatingRefund}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isCreatingRefund ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <DollarSign className=\"size-4\" />\n )}\n Create refund from this webhook\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const BillingWebhookLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<BillingWebhookLogFilters>({});\n const [selectedLog, setSelectedLog] = useState<WebhookLog | null>(null);\n\n const { logs, totalCount, isLoading, error, refetch } = useBillingWebhookLogs(\n filters,\n page,\n PAGE_SIZE\n );\n const { createRefundRecordFromWebhook, isCreatingFromWebhook } = useRefundMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view billing webhook 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 webhook logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof BillingWebhookLogFilters>(\n key: K,\n value: BillingWebhookLogFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleCreateRefund = async (webhookLogId: string) => {\n await createRefundRecordFromWebhook(webhookLogId);\n setSelectedLog(null);\n };\n\n const columns: ResponsiveTableColumn<WebhookLog>[] = [\n {\n key: 'date',\n header: 'Received',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(log.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(log.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => {\n const badge = getBadge(log);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => <span className=\"text-sm text-text-primary capitalize\">{log.provider}</span>,\n },\n {\n key: 'event',\n header: 'Event',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (log) => <span className=\"text-sm font-mono text-text-muted\">{log.event}</span>,\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (log) => <span className=\"text-sm text-status-error-text\">{log.error || '-'}</span>,\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Billing Webhook Logs\"\n description=\"Every Stripe/Razorpay webhook delivery — investigate processing failures directly.\"\n actions={\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 }\n />\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Processed</label>\n <select\n value={filters.processed === undefined ? '' : String(filters.processed)}\n onChange={(e) =>\n handleFilterChange(\n 'processed',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"false\">Unprocessed / Failed</option>\n <option value=\"true\">Processed</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Provider</label>\n <select\n value={filters.provider || ''}\n onChange={(e) => handleFilterChange('provider', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Providers</option>\n {PROVIDER_OPTIONS.map((p) => (\n <option key={p} value={p}>\n {p.charAt(0).toUpperCase() + p.slice(1)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && logs.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 ) : logs.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No webhook logs found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next payment.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<WebhookLog>\n columns={columns}\n data={logs}\n rowKey={(log) => log.id}\n rowActions={(log) => (\n <button\n type=\"button\"\n onClick={() => setSelectedLog(log)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {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 {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} logs\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\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 <ChevronLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\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 <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <WebhookLogDetailModal\n log={selectedLog}\n onClose={() => setSelectedLog(null)}\n onCreateRefund={handleCreateRefund}\n isCreatingRefund={isCreatingFromWebhook}\n canCreateRefund={\n selectedLog\n ? REFUND_ELIGIBLE_EVENTS.has(selectedLog.event) && Boolean(selectedLog.error)\n : false\n }\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAqCA,IAAM,IAAY,IACZ,IAAmB,CAAC,UAAU,UAAU,GACxC,IAAyB,IAAI,IAAI;CACrC;CACA;CACA;CACA;AACF,CAAC,GAEK,KACJ,MAEK,EAAI,YAOF;CACL,OAAO;CACP,WAAW,GAAG,EAAa,QAAQ,GAAG,GAAG,EAAa,QAAQ;CAC9D,MAAM;AACR,IAVS;CACL,OAAO,EAAI,QAAQ,WAAW;CAC9B,WAAW,GAAG,EAAa,MAAM,GAAG,GAAG,EAAa,MAAM;CAC1D,MAAM;AACR,GASE,KAMA,EAAE,QAAK,YAAS,mBAAgB,qBAAkB,yBACjD,IAGH,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cAA4C;GAAuB,CAAA,GACjF,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAEV,kBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;GACjB,CAAA,CACL;MACL,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkB;MAAW,CAAA,GAC1C,kBAAC,KAAD;OAAG,WAAU;iBAAgC,EAAI;MAAY,CAAA,CAC1D,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkB;MAAQ,CAAA,GACvC,kBAAC,KAAD;OAAG,WAAU;iBAAuC,EAAI;MAAS,CAAA,CAC9D,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkB;MAAW,CAAA,GAC1C,kBAAC,KAAD;OAAG,WAAU;iBAAqB,EAAe,EAAI,SAAS;MAAK,CAAA,CAChE,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkB;MAAY,CAAA,GAC3C,kBAAC,KAAD;OAAG,WAAU;iBAAqB,EAAI,YAAY,QAAQ;MAAQ,CAAA,CAC/D,EAAA,CAAA;KACF;;IACJ,EAAI,SACH,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAA+B;IAAQ,CAAA,GACpD,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAI;IACF,CAAA,CACF,EAAA,CAAA;IAEP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAA+B;IAAU,CAAA,GACtD,kBAAC,OAAD;KAAK,WAAU;eACZ,KAAK,UAAU,EAAI,SAAS,MAAM,CAAC;IACjC,CAAA,CACF,EAAA,CAAA;IACJ,CAAC,EAAI,aAAa,KACjB,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAe,EAAI,EAAE;KACpC,UAAU;KACV,WAAU;eAJZ,CAMG,IACC,kBAAC,GAAD,EAAS,WAAU,sBAAuB,CAAA,IAE1C,kBAAC,GAAD,EAAY,WAAU,SAAU,CAAA,GAChC,iCAEI;;GAEP;IACF;;AACF,CAAA,IAjEU,MAqEN,UAAmC;CAC9C,IAAM,IAAc,EAAsB,GACpC,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,CAAC,GAAS,KAAc,EAAmC,CAAC,CAAC,GAC7D,CAAC,GAAa,KAAkB,EAA4B,IAAI,GAEhE,EAAE,SAAM,eAAY,cAAW,UAAO,eAAY,EACtD,GACA,GACA,CACF,GACM,EAAE,kCAA+B,6BAA0B,EAAmB;CAEpF,IAAI,CAAC,EAAY,gBACf,OAAO,kBAAC,GAAD,EAAc,SAAQ,0DAA2D,CAAA;CAG1F,IAAI,KAAS,EAAc,CAAK,GAC9B,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAQ;GACvB,WAAA;EACD,CAAA;CACE,CAAA;CAIT,IAAM,KACJ,GACA,MACG;EAEH,AADA,GAAY,OAAU;GAAE,GAAG;IAAO,IAAM;EAAM,EAAE,GAChD,EAAQ,CAAC;CACX,GAEM,IAAqB,OAAO,MAAyB;EAEzD,AADA,MAAM,EAA8B,CAAY,GAChD,EAAe,IAAI;CACrB,GAEM,IAA+C;EACnD;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,eAAe;GACf,OAAO,MACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAe,EAAI,SAAS,EAAE,MAAM,GAAG,EAAE;IACtC,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;eACb,EAAe,EAAI,SAAS,EAAE,MAAM,GAAG,EAAE;IACtC,CAAA,CACH;;EAET;EACA;GACE,KAAK;GACL,QAAQ;GACR,eAAe;GACf,OAAO,MAAQ;IACb,IAAM,IAAQ,EAAS,CAAG,GACpB,IAAY,EAAM;IACxB,OACE,kBAAC,QAAD;KACE,WAAW,0EAA0E,EAAM;eAD7F,CAGE,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAC9B,EAAM,KACH;;GAEV;EACF;EACA;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,eAAe;GACf,OAAO,MAAQ,kBAAC,QAAD;IAAM,WAAU;cAAwC,EAAI;GAAe,CAAA;EAC5F;EACA;GACE,KAAK;GACL,QAAQ;GACR,eAAe;GACf,OAAO,MAAQ,kBAAC,QAAD;IAAM,WAAU;cAAqC,EAAI;GAAY,CAAA;EACtF;EACA;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,eAAe;GACf,OAAO,MAAQ,kBAAC,QAAD;IAAM,WAAU;cAAkC,EAAI,SAAS;GAAU,CAAA;EAC1F;CACF,GAEM,IAAa,KAAK,IAAI,GAAG,KAAK,KAAK,IAAa,CAAS,CAAC;CAEhE,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,SACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAQ;KACvB,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA,GAAC,SAE1B;;GAEX,CAAA;GAGD,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAiC;KAAgB,CAAA,GAClE,kBAAC,UAAD;MACE,OAAO,EAAQ,cAAc,KAAA,IAAY,KAAK,OAAO,EAAQ,SAAS;MACtE,WAAW,MACT,EACE,aACA,EAAE,OAAO,UAAU,KAAK,KAAA,IAAY,EAAE,OAAO,UAAU,MACzD;MAEF,WAAU;gBARZ;OAUE,kBAAC,UAAD;QAAQ,OAAM;kBAAG;OAAW,CAAA;OAC5B,kBAAC,UAAD;QAAQ,OAAM;kBAAQ;OAA4B,CAAA;OAClD,kBAAC,UAAD;QAAQ,OAAM;kBAAO;OAAiB,CAAA;MAChC;OACL,EAAA,CAAA;KACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAiC;KAAe,CAAA,GACjE,kBAAC,UAAD;MACE,OAAO,EAAQ,YAAY;MAC3B,WAAW,MAAM,EAAmB,YAAY,EAAE,OAAO,SAAS,KAAA,CAAS;MAC3E,WAAU;gBAHZ,CAKE,kBAAC,UAAD;OAAQ,OAAM;iBAAG;MAAqB,CAAA,GACrC,EAAiB,KAAK,MACrB,kBAAC,UAAD;OAAgB,OAAO;iBACpB,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC;MAChC,GAFK,CAEL,CACT,CACK;OACL,EAAA,CAAA;KACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAiC;KAAgB,CAAA,GAClE,kBAAC,UAAD;MACE,OAAO,EAAQ,aAAa,KAAA,IAAY,KAAK,OAAO,EAAQ,QAAQ;MACpE,WAAW,MACT,EACE,YACA,EAAE,OAAO,UAAU,KAAK,KAAA,IAAY,EAAE,OAAO,UAAU,MACzD;MAEF,WAAU;gBARZ;OAUE,kBAAC,UAAD;QAAQ,OAAM;kBAAG;OAAW,CAAA;OAC5B,kBAAC,UAAD;QAAQ,OAAM;kBAAO;OAAiB,CAAA;OACtC,kBAAC,UAAD;QAAQ,OAAM;kBAAQ;OAAgB,CAAA;MAChC;OACL,EAAA,CAAA;IACF;;GAGJ,KAAa,EAAK,WAAW,IAC5B,kBAAC,OAAD;IAAK,WAAU;cACZ;KAAC;KAAG;KAAG;KAAG;KAAG;IAAC,EAAE,KAAK,MACpB,kBAAC,OAAD,EAAa,WAAU,0CAA2C,GAAxD,CAAwD,CACnE;GACE,CAAA,IACH,EAAK,WAAW,IAClB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;GACb,CAAA,IAED,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACW;KACT,MAAM;KACN,SAAS,MAAQ,EAAI;KACrB,aAAa,MACX,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAe,CAAG;MACjC,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAK,WAAU,SAAU,CAAA,GACzB,kBAAC,QAAD;OAAM,WAAU;iBAAmB;MAAU,CAAA,CACvC;;IAEX,CAAA;GACE,CAAA;GAIN,IAAa,KACZ,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MAAgE;MACrD,IAAO,IAAY;MAAE;MAAK,KAAK,KAAK,IAAO,KAAK,GAAW,CAAU;MAAE;MAAI;MACnF;MAAW;KACX;QACH,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;OAChD,UAAU,MAAS;OACnB,WAAU;iBAJZ,CAME,kBAAC,GAAD,EAAa,WAAU,SAAU,CAAA,GACjC,kBAAC,QAAD;QAAM,WAAU;kBAAmB;OAAc,CAAA,CAC3C;;MACR,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,QAAD;SAAM,WAAU;mBAAmB;QAAW,CAAA;QAC7C,IAAO;QAAE;QAAC,kBAAC,QAAD;SAAM,WAAU;mBAAmB;QAAQ,CAAA;QACtD,kBAAC,QAAD;SAAM,WAAU;mBAAY;QAAO,CAAA;QAAC;QAAE;OAClC;;MACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAO,IAAI,IAAI,IAAa,IAAI,IAAI,CAAE;OAC9D,UAAU,IAAO,KAAK;OACtB,WAAU;iBAJZ,CAME,kBAAC,QAAD;QAAM,WAAU;kBAAmB;OAAU,CAAA,GAC7C,kBAAC,GAAD,EAAc,WAAU,SAAU,CAAA,CAC5B;;KACL;MACF;;GAGP,kBAAC,GAAD;IACE,KAAK;IACL,eAAe,EAAe,IAAI;IAClC,gBAAgB;IAChB,kBAAkB;IAClB,iBACE,IACI,EAAuB,IAAI,EAAY,KAAK,KAAK,EAAQ,EAAY,QACrE;GAEP,CAAA;EACE;;AAET"}
|
|
1
|
+
{"version":3,"file":"BillingWebhookLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/BillingWebhookLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Billing Webhook Logs Page\n * Admin visibility into every Stripe/Razorpay webhook processing attempt —\n * not just the payment-succeeded subset eligible for automatic refund\n * creation (see RefundsPage's \"Import Failed Webhooks\" action). Lets whoever\n * owns billing ops see and investigate ANY webhook processing failure\n * without needing direct DB access.\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n X,\n DollarSign,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useBillingWebhookLogs,\n useRefundMutations,\n type BillingWebhookLogFilters,\n type WebhookLog,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { 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 PROVIDER_OPTIONS = ['stripe', 'razorpay'];\nconst REFUND_ELIGIBLE_EVENTS = new Set([\n 'payment_intent.succeeded',\n 'charge.succeeded',\n 'payment.captured',\n 'order.paid',\n]);\n\nconst getBadge = (\n log: WebhookLog\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (!log.processed) {\n return {\n label: log.error ? 'Failed' : 'Pending',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n return {\n label: 'Processed',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n};\n\nconst WebhookLogDetailModal: FC<{\n log: WebhookLog | null;\n onClose: () => void;\n onCreateRefund: (id: string) => void;\n isCreatingRefund: boolean;\n canCreateRefund: boolean;\n}> = ({ log, onClose, onCreateRefund, isCreatingRefund, canCreateRefund }) => {\n if (!log) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Webhook log details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Provider</p>\n <p className=\"text-text-primary capitalize\">{log.provider}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Event</p>\n <p className=\"text-text-primary font-mono text-xs\">{log.event}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Received</p>\n <p className=\"text-text-primary\">{formatDateTime(log.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Processed</p>\n <p className=\"text-text-primary\">{log.processed ? 'Yes' : 'No'}</p>\n </div>\n </div>\n {log.error && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {log.error}\n </pre>\n </div>\n )}\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Payload</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-bg-sunken/60 text-text-secondary max-h-64 overflow-y-auto\">\n {JSON.stringify(log.payload, null, 2)}\n </pre>\n </div>\n {!log.processed && canCreateRefund && (\n <button\n type=\"button\"\n onClick={() => onCreateRefund(log.id)}\n disabled={isCreatingRefund}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isCreatingRefund ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <DollarSign className=\"size-4\" />\n )}\n Create refund from this webhook\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const BillingWebhookLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<BillingWebhookLogFilters>({});\n const [selectedLog, setSelectedLog] = useState<WebhookLog | null>(null);\n\n const { logs, totalCount, isLoading, error, refetch } = useBillingWebhookLogs(\n filters,\n page,\n PAGE_SIZE\n );\n const { createRefundRecordFromWebhook, isCreatingFromWebhook } = useRefundMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view billing webhook 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 webhook logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof BillingWebhookLogFilters>(\n key: K,\n value: BillingWebhookLogFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleCreateRefund = async (webhookLogId: string) => {\n await createRefundRecordFromWebhook(webhookLogId);\n setSelectedLog(null);\n };\n\n const columns: ResponsiveTableColumn<WebhookLog>[] = [\n {\n key: 'date',\n header: 'Received',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(log.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(log.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => {\n const badge = getBadge(log);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (log) => <span className=\"text-sm text-text-primary capitalize\">{log.provider}</span>,\n },\n {\n key: 'event',\n header: 'Event',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (log) => <span className=\"text-sm font-mono text-text-muted\">{log.event}</span>,\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (log) => <span className=\"text-sm text-status-error-text\">{log.error || '-'}</span>,\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Billing Webhook Logs\"\n description=\"Every Stripe/Razorpay webhook delivery — investigate processing failures directly.\"\n actions={\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 }\n />\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Processed</label>\n <select\n value={filters.processed === undefined ? '' : String(filters.processed)}\n onChange={(e) =>\n handleFilterChange(\n 'processed',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All</option>\n <option value=\"false\">Unprocessed / Failed</option>\n <option value=\"true\">Processed</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Provider</label>\n <select\n value={filters.provider || ''}\n onChange={(e) => handleFilterChange('provider', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All Providers</option>\n {PROVIDER_OPTIONS.map((p) => (\n <option key={p} value={p}>\n {p.charAt(0).toUpperCase() + p.slice(1)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && logs.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 ) : logs.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No webhook logs found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next payment.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<WebhookLog>\n columns={columns}\n data={logs}\n rowKey={(log) => log.id}\n rowActions={(log) => (\n <button\n type=\"button\"\n onClick={() => setSelectedLog(log)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {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 {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} logs\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\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 <ChevronLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\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 <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <WebhookLogDetailModal\n log={selectedLog}\n onClose={() => setSelectedLog(null)}\n onCreateRefund={handleCreateRefund}\n isCreatingRefund={isCreatingFromWebhook}\n canCreateRefund={\n selectedLog\n ? REFUND_ELIGIBLE_EVENTS.has(selectedLog.event) && Boolean(selectedLog.error)\n : false\n }\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -165,7 +165,7 @@ var O = () => {
|
|
|
165
165
|
}), /* @__PURE__ */ T("select", {
|
|
166
166
|
value: R ?? "",
|
|
167
167
|
onChange: (e) => I(e.target.value || null),
|
|
168
|
-
className: "text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
168
|
+
className: "text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
169
169
|
children: [/* @__PURE__ */ w("option", {
|
|
170
170
|
value: "",
|
|
171
171
|
children: t("billing.invoices.allAccounts", "All Accounts")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoicesListPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/InvoicesListPage.tsx"],"sourcesContent":["/**\n * Billing Module - Invoices List Page\n * Displays all invoices for the current tenant\n */\n\nimport { useEffect, useState, useMemo, useRef, useCallback, type FC } from 'react';\nimport { RotateCcw } from 'lucide-react';\nimport { useSearchParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useInvoiceMutations, usePaginatedInvoices } from '../hooks/useInvoices';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useGetBillingAccountsByOrgQuery,\n type GetBillingAccountsByOrgQuery,\n} from '../../../../generated/global-operations';\n\ntype BillingAccountItem = GetBillingAccountsByOrgQuery['getBillingAccountsByOrg'][number];\nimport {\n formatCurrency,\n formatDate,\n formatInvoiceStatus,\n formatInvoiceNumber,\n} from '../../../shared/utils/format';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport { getInvoiceStatusColor } from '../../../shared/utils/status';\nimport { StatusPill } from '../../../shared/ui';\nimport type { Invoice, InvoiceStatus } from '../../../shared/types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterStatus = 'all' | InvoiceStatus;\ntype InvoiceAction = { invoiceId: string; action: 'pay' | 'void' } | null;\n\nexport const InvoicesListPage: FC = () => {\n const tr = useTr();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [searchParams] = useSearchParams();\n const urlBillingAccountId = searchParams.get('billingAccountId') ?? undefined;\n\n const { data: accountsData } = useGetBillingAccountsByOrgQuery({ fetchPolicy: 'cache-first' });\n const billingAccounts = useMemo(\n () => accountsData?.getBillingAccountsByOrg ?? [],\n [accountsData]\n );\n const { payInvoice, voidInvoice } = useInvoiceMutations();\n const [actionError, setActionError] = useState<string | null>(null);\n const [activeAction, setActiveAction] = useState<InvoiceAction>(null);\n\n // undefined = user hasn't explicitly chosen yet → auto-pick default\n // null = user explicitly chose \"All Accounts\"\n // string = user explicitly chose a specific account\n const [explicitAccountId, setExplicitAccountId] = useState<string | null | undefined>(\n urlBillingAccountId\n );\n\n useEffect(() => {\n setExplicitAccountId(urlBillingAccountId);\n }, [urlBillingAccountId]);\n\n const defaultAccountId = useMemo(\n () =>\n (billingAccounts.find((a: BillingAccountItem) => a.isDefault) ?? billingAccounts[0])?.id ??\n undefined,\n [billingAccounts]\n );\n\n // Resolved selection: explicit choice wins; before any choice, use the default\n const selectedAccountId: string | null =\n explicitAccountId !== undefined ? explicitAccountId : (defaultAccountId ?? null);\n\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [page, setPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setPage(1);\n }, 400);\n return () => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n };\n }, [searchQuery]);\n\n const { invoices, totalPages, isLoading, error, refetch } = usePaginatedInvoices({\n billingAccountId: selectedAccountId ?? undefined,\n status: filterStatus !== 'all' ? (filterStatus as InvoiceStatus) : undefined,\n search: debouncedSearch || undefined,\n page,\n pageSize: 20,\n });\n\n const handlePayInvoice = useCallback(\n async (invoice: Invoice): Promise<void> => {\n const billingAccountId = invoice.billingAccountId;\n const paymentMethodId = billingAccountId\n ? billingAccounts.find((account: BillingAccountItem) => account.id === billingAccountId)\n ?.defaultPaymentMethod\n : undefined;\n\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n if (!paymentMethodId) {\n setActionError('Add and set a default payment method before paying this invoice.');\n return;\n }\n\n setActionError(null);\n setActiveAction({ invoiceId: invoice.id, action: 'pay' });\n try {\n await payInvoice(invoice.id, paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n } catch (actionErrorValue) {\n setActionError(\n actionErrorValue instanceof Error ? actionErrorValue.message : 'Failed to pay invoice'\n );\n void nativeNotify('error');\n } finally {\n setActiveAction(null);\n }\n },\n [billingAccounts, payInvoice, refetch]\n );\n\n const handleVoidInvoice = useCallback(\n async (invoice: Invoice): Promise<void> => {\n const billingAccountId = invoice.billingAccountId;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n setActiveAction({ invoiceId: invoice.id, action: 'void' });\n try {\n await voidInvoice(invoice.id, billingAccountId);\n await refetch();\n void nativeNotify('success');\n } catch (actionErrorValue) {\n setActionError(\n actionErrorValue instanceof Error ? actionErrorValue.message : 'Failed to void invoice'\n );\n void nativeNotify('error');\n } finally {\n setActiveAction(null);\n }\n },\n [refetch, voidInvoice]\n );\n\n // Permission check\n if (!permissions.canViewInvoices) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n {tr('billing.invoices.noViewPermission', \"You don't have permission to view invoices.\")}\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && invoices.length === 0) {\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=\"space-y-3\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-4 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-3 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.invoices.failedToLoad', 'Failed to load invoices')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.common.tryAgain', 'Try Again')}\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.invoices.title', 'Invoices')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.invoices.subtitle', 'View and manage your billing invoices')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.invoices.purpose',\n 'A record of everything you have been billed for — subscriptions, add-ons and one-off charges. Open an invoice to see line items, download the PDF, pay an outstanding amount, or request a refund.'\n )}\n </PagePurpose>\n </div>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/refunds')}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('billing.invoices.myRefunds', 'My Refunds')}\n </button>\n {billingAccounts.length > 1 && (\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-secondary whitespace-nowrap\">\n {tr('billing.invoices.billingAccountLabel', 'Billing Account')}\n </label>\n <select\n value={selectedAccountId ?? ''}\n onChange={(e) => setExplicitAccountId(e.target.value || null)}\n className=\"text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{tr('billing.invoices.allAccounts', 'All Accounts')}</option>\n {billingAccounts.map((account: BillingAccountItem) => (\n <option key={account.id ?? ''} value={account.id ?? ''}>\n {account.name ?? account.email ?? account.id}\n {account.isDefault ? tr('billing.invoices.defaultSuffix', ' (Default)') : ''}\n </option>\n ))}\n </select>\n </div>\n )}\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken flex-wrap\">\n {(['all', 'OPEN', 'PAID', 'DRAFT', 'VOID'] as FilterStatus[]).map((status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => {\n setFilterStatus(status);\n setPage(1);\n }}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n filterStatus === status\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {status === 'all' ? 'All' : formatInvoiceStatus(status as InvoiceStatus)}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder={tr('billing.invoices.searchPlaceholder', 'Search by invoice number...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent\"\n />\n </div>\n </div>\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 {/* Invoices Table */}\n {!isLoading && invoices.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterStatus !== 'all' || debouncedSearch ? 'empty-search' : 'empty-files'}\n title={tr('billing.invoices.noInvoicesFound', 'No invoices found')}\n description={\n filterStatus !== 'all' || debouncedSearch\n ? tr('billing.invoices.noInvoicesFiltered', 'No invoices match your filters.')\n : tr('billing.invoices.noInvoicesYet', \"You don't have any invoices yet.\")\n }\n />\n ) : (\n <>\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <table className=\"w-full\">\n <thead className=\"bg-bg-sunken\">\n <tr>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.invoices.invoice', 'Invoice')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.common.status', 'Status')}\n </th>\n {!selectedAccountId && billingAccounts.length > 1 && (\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted hidden lg:table-cell\">\n {tr('billing.common.account', 'Account')}\n </th>\n )}\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted hidden sm:table-cell\">\n {tr('billing.common.date', 'Date')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted hidden md:table-cell\">\n {tr('billing.common.dueDate', 'Due Date')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.common.amount', 'Amount')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {invoices.map((invoice) => (\n <InvoiceRow\n key={invoice.id}\n invoice={invoice}\n onView={() =>\n navigateTo(\n withBillingAccountId(`/invoices/${invoice.id}`, invoice.billingAccountId)\n )\n }\n canPay={permissions.canPayInvoice}\n canVoid={permissions.canVoidInvoice}\n onPay={handlePayInvoice}\n onVoid={handleVoidInvoice}\n isProcessing={activeAction?.invoiceId === invoice.id}\n processingAction={\n activeAction?.invoiceId === invoice.id ? activeAction.action : undefined\n }\n showAccount={!selectedAccountId && billingAccounts.length > 1}\n accountName={\n billingAccounts.find(\n (a: BillingAccountItem) => a.id === invoice.billingAccountId\n )?.name ?? null\n }\n />\n ))}\n </tbody>\n </table>\n </div>\n\n {totalPages > 1 && (\n <div className=\"flex items-center justify-between pt-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.common.pageOf', 'Page {{page}} of {{totalPages}}', {\n page,\n totalPages,\n })}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n disabled={page <= 1}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-button bg-bg-surface text-text-primary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n {tr('billing.common.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.min(totalPages, p + 1))}\n disabled={page >= totalPages}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-button bg-bg-surface text-text-primary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n {tr('billing.common.next', 'Next')}\n </button>\n </div>\n </div>\n )}\n </>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Invoice Row Component\n// ============================================================================\n\ninterface InvoiceRowProps {\n invoice: Invoice;\n onView: () => void;\n onPay: (invoice: Invoice) => Promise<void>;\n onVoid: (invoice: Invoice) => Promise<void>;\n canPay: boolean;\n canVoid: boolean;\n isProcessing: boolean;\n processingAction?: 'pay' | 'void';\n showAccount?: boolean;\n accountName?: string | null;\n}\n\nconst InvoiceRow: FC<InvoiceRowProps> = ({\n invoice,\n onView,\n onPay,\n onVoid,\n canPay,\n canVoid,\n isProcessing,\n processingAction,\n showAccount = false,\n accountName,\n}) => {\n const tr = useTr();\n const [isConfirmingVoid, setIsConfirmingVoid] = useState(false);\n const statusColor = getInvoiceStatusColor(invoice.status);\n const isOpen = invoice.status === ('OPEN' as InvoiceStatus);\n const isDraft = invoice.status === ('DRAFT' as InvoiceStatus);\n\n const handleVoidRequest = async () => {\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Void invoice',\n message: 'Are you sure you want to void this invoice?',\n okButtonTitle: 'Void',\n cancelButtonTitle: 'Cancel',\n });\n if (confirmed === true) {\n await onVoid(invoice);\n } else if (confirmed === null) {\n setIsConfirmingVoid(true);\n }\n };\n\n const handleVoidConfirmed = async () => {\n await onVoid(invoice);\n setIsConfirmingVoid(false);\n };\n\n const invoicePdfBackupUrl = (invoice.metadata as Record<string, unknown> | undefined)\n ?.invoicePdfBackupUrl as string | undefined;\n\n return (\n <tr className=\"hover:bg-bg-sunken transition-colors\">\n <td className=\"p-4\">\n {invoice.invoiceUrl ? (\n <a\n href={invoice.invoiceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-text-primary hover:text-text-link transition-colors\"\n >\n {formatInvoiceNumber(invoice.invoiceNumber)}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={onView}\n className=\"font-medium text-text-primary hover:text-text-link transition-colors\"\n >\n {formatInvoiceNumber(invoice.invoiceNumber)}\n </button>\n )}\n {(invoice.itemDescription ?? invoice.subscription?.plan?.name) && (\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {invoice.itemDescription ?? invoice.subscription?.plan?.name}\n </p>\n )}\n </td>\n <td className=\"p-4\">\n <StatusPill status={statusColor} dot>\n {formatInvoiceStatus(invoice.status)}\n </StatusPill>\n </td>\n {showAccount && (\n <td className=\"p-4 hidden lg:table-cell\">\n {accountName ? (\n <span className=\"inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-bg-sunken text-text-secondary border border-border-subtle\">\n {accountName}\n </span>\n ) : (\n <span className=\"text-sm text-text-secondary\">, </span>\n )}\n </td>\n )}\n <td className=\"p-4 text-sm text-text-secondary hidden sm:table-cell\">\n {formatDate(invoice.createdAt, 'short')}\n </td>\n <td className=\"p-4 text-sm text-text-secondary hidden md:table-cell\">\n {invoice.dueDate ? formatDate(invoice.dueDate, 'short') : '—'}\n </td>\n <td className=\"p-4 text-right font-medium text-text-primary\">\n {formatCurrency(invoice.total, invoice.currency)}\n </td>\n <td className=\"p-4 text-right\">\n <div className=\"relative flex items-center justify-end gap-2\">\n {invoice.invoiceUrl ? (\n <a\n href={invoice.invoiceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.view', 'View')}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.view', 'View')}\n </button>\n )}\n {invoice.pdfUrl && (\n <a\n href={invoice.pdfUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"px-3 py-1.5 text-sm font-medium text-text-link hover:bg-[var(--color-accent-softHover)] rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.pdf', 'PDF')}\n </a>\n )}\n {/* Our own durable backup copy, shown alongside the provider's —\n not a replacement, so this still works if the provider link\n ever breaks. */}\n {invoicePdfBackupUrl && (\n <a\n href={invoicePdfBackupUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"px-3 py-1.5 text-sm font-medium text-text-link hover:bg-[var(--color-accent-softHover)] rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.pdfBackup', 'PDF (backup)')}\n </a>\n )}\n {isOpen && canPay && (\n <button\n type=\"button\"\n onClick={() => void onPay(invoice)}\n disabled={isProcessing}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessing && processingAction === 'pay'\n ? tr('billing.common.paying', 'Paying…')\n : tr('billing.common.pay', 'Pay')}\n </button>\n )}\n {(isOpen || isDraft) && canVoid && (\n <button\n type=\"button\"\n onClick={() => void handleVoidRequest()}\n disabled={isProcessing}\n className=\"px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle rounded-button transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessing && processingAction === 'void'\n ? tr('billing.common.voiding', 'Voiding…')\n : tr('billing.common.void', 'Void')}\n </button>\n )}\n {isConfirmingVoid && (\n <div className=\"absolute right-4 mt-12 z-10 w-64 rounded-card border border-status-error-border bg-bg-elevated p-3 text-left shadow-elevation-2\">\n <p className=\"text-sm font-medium text-text-primary\">Void this invoice?</p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingVoid(false)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-2.5 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleVoidConfirmed()}\n disabled={isProcessing}\n className=\"rounded-button bg-action-danger-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessing ? 'Voiding…' : 'Confirm'}\n </button>\n </div>\n </div>\n )}\n </div>\n </td>\n </tr>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InvoicesListPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/InvoicesListPage.tsx"],"sourcesContent":["/**\n * Billing Module - Invoices List Page\n * Displays all invoices for the current tenant\n */\n\nimport { useEffect, useState, useMemo, useRef, useCallback, type FC } from 'react';\nimport { RotateCcw } from 'lucide-react';\nimport { useSearchParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useInvoiceMutations, usePaginatedInvoices } from '../hooks/useInvoices';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useGetBillingAccountsByOrgQuery,\n type GetBillingAccountsByOrgQuery,\n} from '../../../../generated/global-operations';\n\ntype BillingAccountItem = GetBillingAccountsByOrgQuery['getBillingAccountsByOrg'][number];\nimport {\n formatCurrency,\n formatDate,\n formatInvoiceStatus,\n formatInvoiceNumber,\n} from '../../../shared/utils/format';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport { getInvoiceStatusColor } from '../../../shared/utils/status';\nimport { StatusPill } from '../../../shared/ui';\nimport type { Invoice, InvoiceStatus } from '../../../shared/types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterStatus = 'all' | InvoiceStatus;\ntype InvoiceAction = { invoiceId: string; action: 'pay' | 'void' } | null;\n\nexport const InvoicesListPage: FC = () => {\n const tr = useTr();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [searchParams] = useSearchParams();\n const urlBillingAccountId = searchParams.get('billingAccountId') ?? undefined;\n\n const { data: accountsData } = useGetBillingAccountsByOrgQuery({ fetchPolicy: 'cache-first' });\n const billingAccounts = useMemo(\n () => accountsData?.getBillingAccountsByOrg ?? [],\n [accountsData]\n );\n const { payInvoice, voidInvoice } = useInvoiceMutations();\n const [actionError, setActionError] = useState<string | null>(null);\n const [activeAction, setActiveAction] = useState<InvoiceAction>(null);\n\n // undefined = user hasn't explicitly chosen yet → auto-pick default\n // null = user explicitly chose \"All Accounts\"\n // string = user explicitly chose a specific account\n const [explicitAccountId, setExplicitAccountId] = useState<string | null | undefined>(\n urlBillingAccountId\n );\n\n useEffect(() => {\n setExplicitAccountId(urlBillingAccountId);\n }, [urlBillingAccountId]);\n\n const defaultAccountId = useMemo(\n () =>\n (billingAccounts.find((a: BillingAccountItem) => a.isDefault) ?? billingAccounts[0])?.id ??\n undefined,\n [billingAccounts]\n );\n\n // Resolved selection: explicit choice wins; before any choice, use the default\n const selectedAccountId: string | null =\n explicitAccountId !== undefined ? explicitAccountId : (defaultAccountId ?? null);\n\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [page, setPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setPage(1);\n }, 400);\n return () => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n };\n }, [searchQuery]);\n\n const { invoices, totalPages, isLoading, error, refetch } = usePaginatedInvoices({\n billingAccountId: selectedAccountId ?? undefined,\n status: filterStatus !== 'all' ? (filterStatus as InvoiceStatus) : undefined,\n search: debouncedSearch || undefined,\n page,\n pageSize: 20,\n });\n\n const handlePayInvoice = useCallback(\n async (invoice: Invoice): Promise<void> => {\n const billingAccountId = invoice.billingAccountId;\n const paymentMethodId = billingAccountId\n ? billingAccounts.find((account: BillingAccountItem) => account.id === billingAccountId)\n ?.defaultPaymentMethod\n : undefined;\n\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n if (!paymentMethodId) {\n setActionError('Add and set a default payment method before paying this invoice.');\n return;\n }\n\n setActionError(null);\n setActiveAction({ invoiceId: invoice.id, action: 'pay' });\n try {\n await payInvoice(invoice.id, paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n } catch (actionErrorValue) {\n setActionError(\n actionErrorValue instanceof Error ? actionErrorValue.message : 'Failed to pay invoice'\n );\n void nativeNotify('error');\n } finally {\n setActiveAction(null);\n }\n },\n [billingAccounts, payInvoice, refetch]\n );\n\n const handleVoidInvoice = useCallback(\n async (invoice: Invoice): Promise<void> => {\n const billingAccountId = invoice.billingAccountId;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n setActiveAction({ invoiceId: invoice.id, action: 'void' });\n try {\n await voidInvoice(invoice.id, billingAccountId);\n await refetch();\n void nativeNotify('success');\n } catch (actionErrorValue) {\n setActionError(\n actionErrorValue instanceof Error ? actionErrorValue.message : 'Failed to void invoice'\n );\n void nativeNotify('error');\n } finally {\n setActiveAction(null);\n }\n },\n [refetch, voidInvoice]\n );\n\n // Permission check\n if (!permissions.canViewInvoices) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n {tr('billing.invoices.noViewPermission', \"You don't have permission to view invoices.\")}\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && invoices.length === 0) {\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=\"space-y-3\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-4 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-3 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.invoices.failedToLoad', 'Failed to load invoices')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.common.tryAgain', 'Try Again')}\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.invoices.title', 'Invoices')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.invoices.subtitle', 'View and manage your billing invoices')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.invoices.purpose',\n 'A record of everything you have been billed for — subscriptions, add-ons and one-off charges. Open an invoice to see line items, download the PDF, pay an outstanding amount, or request a refund.'\n )}\n </PagePurpose>\n </div>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/refunds')}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <RotateCcw className=\"size-4\" />\n {tr('billing.invoices.myRefunds', 'My Refunds')}\n </button>\n {billingAccounts.length > 1 && (\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-secondary whitespace-nowrap\">\n {tr('billing.invoices.billingAccountLabel', 'Billing Account')}\n </label>\n <select\n value={selectedAccountId ?? ''}\n onChange={(e) => setExplicitAccountId(e.target.value || null)}\n className=\"text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">{tr('billing.invoices.allAccounts', 'All Accounts')}</option>\n {billingAccounts.map((account: BillingAccountItem) => (\n <option key={account.id ?? ''} value={account.id ?? ''}>\n {account.name ?? account.email ?? account.id}\n {account.isDefault ? tr('billing.invoices.defaultSuffix', ' (Default)') : ''}\n </option>\n ))}\n </select>\n </div>\n )}\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken flex-wrap\">\n {(['all', 'OPEN', 'PAID', 'DRAFT', 'VOID'] as FilterStatus[]).map((status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => {\n setFilterStatus(status);\n setPage(1);\n }}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n filterStatus === status\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {status === 'all' ? 'All' : formatInvoiceStatus(status as InvoiceStatus)}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder={tr('billing.invoices.searchPlaceholder', 'Search by invoice number...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent\"\n />\n </div>\n </div>\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 {/* Invoices Table */}\n {!isLoading && invoices.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterStatus !== 'all' || debouncedSearch ? 'empty-search' : 'empty-files'}\n title={tr('billing.invoices.noInvoicesFound', 'No invoices found')}\n description={\n filterStatus !== 'all' || debouncedSearch\n ? tr('billing.invoices.noInvoicesFiltered', 'No invoices match your filters.')\n : tr('billing.invoices.noInvoicesYet', \"You don't have any invoices yet.\")\n }\n />\n ) : (\n <>\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <table className=\"w-full\">\n <thead className=\"bg-bg-sunken\">\n <tr>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.invoices.invoice', 'Invoice')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.common.status', 'Status')}\n </th>\n {!selectedAccountId && billingAccounts.length > 1 && (\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted hidden lg:table-cell\">\n {tr('billing.common.account', 'Account')}\n </th>\n )}\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted hidden sm:table-cell\">\n {tr('billing.common.date', 'Date')}\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted hidden md:table-cell\">\n {tr('billing.common.dueDate', 'Due Date')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.common.amount', 'Amount')}\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('billing.common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {invoices.map((invoice) => (\n <InvoiceRow\n key={invoice.id}\n invoice={invoice}\n onView={() =>\n navigateTo(\n withBillingAccountId(`/invoices/${invoice.id}`, invoice.billingAccountId)\n )\n }\n canPay={permissions.canPayInvoice}\n canVoid={permissions.canVoidInvoice}\n onPay={handlePayInvoice}\n onVoid={handleVoidInvoice}\n isProcessing={activeAction?.invoiceId === invoice.id}\n processingAction={\n activeAction?.invoiceId === invoice.id ? activeAction.action : undefined\n }\n showAccount={!selectedAccountId && billingAccounts.length > 1}\n accountName={\n billingAccounts.find(\n (a: BillingAccountItem) => a.id === invoice.billingAccountId\n )?.name ?? null\n }\n />\n ))}\n </tbody>\n </table>\n </div>\n\n {totalPages > 1 && (\n <div className=\"flex items-center justify-between pt-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.common.pageOf', 'Page {{page}} of {{totalPages}}', {\n page,\n totalPages,\n })}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n disabled={page <= 1}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-button bg-bg-surface text-text-primary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n {tr('billing.common.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.min(totalPages, p + 1))}\n disabled={page >= totalPages}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-button bg-bg-surface text-text-primary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n {tr('billing.common.next', 'Next')}\n </button>\n </div>\n </div>\n )}\n </>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Invoice Row Component\n// ============================================================================\n\ninterface InvoiceRowProps {\n invoice: Invoice;\n onView: () => void;\n onPay: (invoice: Invoice) => Promise<void>;\n onVoid: (invoice: Invoice) => Promise<void>;\n canPay: boolean;\n canVoid: boolean;\n isProcessing: boolean;\n processingAction?: 'pay' | 'void';\n showAccount?: boolean;\n accountName?: string | null;\n}\n\nconst InvoiceRow: FC<InvoiceRowProps> = ({\n invoice,\n onView,\n onPay,\n onVoid,\n canPay,\n canVoid,\n isProcessing,\n processingAction,\n showAccount = false,\n accountName,\n}) => {\n const tr = useTr();\n const [isConfirmingVoid, setIsConfirmingVoid] = useState(false);\n const statusColor = getInvoiceStatusColor(invoice.status);\n const isOpen = invoice.status === ('OPEN' as InvoiceStatus);\n const isDraft = invoice.status === ('DRAFT' as InvoiceStatus);\n\n const handleVoidRequest = async () => {\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Void invoice',\n message: 'Are you sure you want to void this invoice?',\n okButtonTitle: 'Void',\n cancelButtonTitle: 'Cancel',\n });\n if (confirmed === true) {\n await onVoid(invoice);\n } else if (confirmed === null) {\n setIsConfirmingVoid(true);\n }\n };\n\n const handleVoidConfirmed = async () => {\n await onVoid(invoice);\n setIsConfirmingVoid(false);\n };\n\n const invoicePdfBackupUrl = (invoice.metadata as Record<string, unknown> | undefined)\n ?.invoicePdfBackupUrl as string | undefined;\n\n return (\n <tr className=\"hover:bg-bg-sunken transition-colors\">\n <td className=\"p-4\">\n {invoice.invoiceUrl ? (\n <a\n href={invoice.invoiceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-text-primary hover:text-text-link transition-colors\"\n >\n {formatInvoiceNumber(invoice.invoiceNumber)}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={onView}\n className=\"font-medium text-text-primary hover:text-text-link transition-colors\"\n >\n {formatInvoiceNumber(invoice.invoiceNumber)}\n </button>\n )}\n {(invoice.itemDescription ?? invoice.subscription?.plan?.name) && (\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {invoice.itemDescription ?? invoice.subscription?.plan?.name}\n </p>\n )}\n </td>\n <td className=\"p-4\">\n <StatusPill status={statusColor} dot>\n {formatInvoiceStatus(invoice.status)}\n </StatusPill>\n </td>\n {showAccount && (\n <td className=\"p-4 hidden lg:table-cell\">\n {accountName ? (\n <span className=\"inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-bg-sunken text-text-secondary border border-border-subtle\">\n {accountName}\n </span>\n ) : (\n <span className=\"text-sm text-text-secondary\">, </span>\n )}\n </td>\n )}\n <td className=\"p-4 text-sm text-text-secondary hidden sm:table-cell\">\n {formatDate(invoice.createdAt, 'short')}\n </td>\n <td className=\"p-4 text-sm text-text-secondary hidden md:table-cell\">\n {invoice.dueDate ? formatDate(invoice.dueDate, 'short') : '—'}\n </td>\n <td className=\"p-4 text-right font-medium text-text-primary\">\n {formatCurrency(invoice.total, invoice.currency)}\n </td>\n <td className=\"p-4 text-right\">\n <div className=\"relative flex items-center justify-end gap-2\">\n {invoice.invoiceUrl ? (\n <a\n href={invoice.invoiceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.view', 'View')}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.view', 'View')}\n </button>\n )}\n {invoice.pdfUrl && (\n <a\n href={invoice.pdfUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"px-3 py-1.5 text-sm font-medium text-text-link hover:bg-[var(--color-accent-softHover)] rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.pdf', 'PDF')}\n </a>\n )}\n {/* Our own durable backup copy, shown alongside the provider's —\n not a replacement, so this still works if the provider link\n ever breaks. */}\n {invoicePdfBackupUrl && (\n <a\n href={invoicePdfBackupUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"px-3 py-1.5 text-sm font-medium text-text-link hover:bg-[var(--color-accent-softHover)] rounded-button transition-colors duration-200\"\n >\n {tr('billing.common.pdfBackup', 'PDF (backup)')}\n </a>\n )}\n {isOpen && canPay && (\n <button\n type=\"button\"\n onClick={() => void onPay(invoice)}\n disabled={isProcessing}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessing && processingAction === 'pay'\n ? tr('billing.common.paying', 'Paying…')\n : tr('billing.common.pay', 'Pay')}\n </button>\n )}\n {(isOpen || isDraft) && canVoid && (\n <button\n type=\"button\"\n onClick={() => void handleVoidRequest()}\n disabled={isProcessing}\n className=\"px-3 py-1.5 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle rounded-button transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessing && processingAction === 'void'\n ? tr('billing.common.voiding', 'Voiding…')\n : tr('billing.common.void', 'Void')}\n </button>\n )}\n {isConfirmingVoid && (\n <div className=\"absolute right-4 mt-12 z-10 w-64 rounded-card border border-status-error-border bg-bg-elevated p-3 text-left shadow-elevation-2\">\n <p className=\"text-sm font-medium text-text-primary\">Void this invoice?</p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingVoid(false)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-2.5 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleVoidConfirmed()}\n disabled={isProcessing}\n className=\"rounded-button bg-action-danger-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessing ? 'Voiding…' : 'Confirm'}\n </button>\n </div>\n </div>\n )}\n </div>\n </td>\n </tr>\n );\n};\n"],"mappings":"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"}
|
|
@@ -278,7 +278,7 @@ var w = 25, T = [
|
|
|
278
278
|
}), /* @__PURE__ */ x("select", {
|
|
279
279
|
value: m.callbackStatus || "",
|
|
280
280
|
onChange: (e) => L("callbackStatus", e.target.value || void 0),
|
|
281
|
-
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
281
|
+
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
282
282
|
children: [/* @__PURE__ */ b("option", {
|
|
283
283
|
value: "",
|
|
284
284
|
children: "All"
|
|
@@ -303,7 +303,7 @@ var w = 25, T = [
|
|
|
303
303
|
}), /* @__PURE__ */ x("select", {
|
|
304
304
|
value: m.hasError === void 0 ? "" : String(m.hasError),
|
|
305
305
|
onChange: (e) => L("hasError", e.target.value === "" ? void 0 : e.target.value === "true"),
|
|
306
|
-
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
306
|
+
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
307
307
|
children: [
|
|
308
308
|
/* @__PURE__ */ b("option", {
|
|
309
309
|
value: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderCallbackLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/OrderCallbackLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Order Callback Logs Page\n * Admin visibility into the billing→external-service (store, etc.) order\n * completion callback — every BillingOrder's callback delivery attempt, not\n * just the ones currently FAILED. The outbound counterpart to\n * BillingWebhookLogsPage's inbound Stripe/Razorpay webhook view: lets\n * whoever owns billing ops see and retry a stuck billing→store handoff\n * without needing direct DB access (BOFF-7192).\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n Clock,\n X,\n RotateCw,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useOrderCallbacks,\n useOrderCallbackMutations,\n type OrderCallbackFilters,\n type OrderCallback,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { 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 CALLBACK_STATUS_OPTIONS = ['PENDING', 'SUCCESS', 'FAILED'];\n\nconst getBadge = (\n order: OrderCallback\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (order.callbackStatus === 'FAILED') {\n return {\n label: 'Failed',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n if (order.callbackStatus === 'SUCCESS') {\n return {\n label: 'Delivered',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n }\n return {\n label: order.callbackStatus || 'Pending',\n className: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n icon: Clock,\n };\n};\n\nconst OrderCallbackDetailModal: FC<{\n order: OrderCallback | null;\n onClose: () => void;\n onRetry: (order: OrderCallback) => void;\n isRetrying: boolean;\n}> = ({ order, onClose, onRetry, isRetrying }) => {\n if (!order) return null;\n\n const canRetry = order.callbackStatus === 'FAILED' && Boolean(order.billingAccountId);\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Order callback details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Order ID</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">{order.id}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Service</p>\n <p className=\"text-text-primary\">{order.externalService}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Order status</p>\n <p className=\"text-text-primary\">{order.status}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Amount</p>\n <p className=\"text-text-primary\">\n {order.subtotal} {order.currency}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Billing account</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.billingAccountId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Payment / Transaction</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.paymentId || '—'} / {order.transactionId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Created</p>\n <p className=\"text-text-primary\">{formatDateTime(order.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Last callback attempt</p>\n <p className=\"text-text-primary\">\n {order.callbackLastAttemptAt ? formatDateTime(order.callbackLastAttemptAt) : '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback attempts</p>\n <p className=\"text-text-primary\">{order.callbackAttempts ?? 0}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback status</p>\n <p className=\"text-text-primary\">{order.callbackStatus || 'PENDING'}</p>\n </div>\n </div>\n {order.callbackLastError && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Last error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {order.callbackLastError}\n </pre>\n </div>\n )}\n {order.callbackStatus === 'FAILED' && !order.billingAccountId && (\n <p className=\"text-xs text-text-muted\">\n No billing account was resolved on this order, so it can't be retried from here —\n the callback was never able to identify who paid.\n </p>\n )}\n {order.callbackStatus === 'FAILED' && (\n <button\n type=\"button\"\n onClick={() => onRetry(order)}\n disabled={!canRetry || isRetrying}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isRetrying ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCw className=\"size-4\" />\n )}\n Retry callback\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const OrderCallbackLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<OrderCallbackFilters>({});\n const [selectedOrder, setSelectedOrder] = useState<OrderCallback | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n\n const { orders, totalCount, isLoading, error, refetch } = useOrderCallbacks(\n filters,\n page,\n PAGE_SIZE\n );\n const { retryOrderCallback, isRetrying } = useOrderCallbackMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view order callback 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 order callback logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof OrderCallbackFilters>(\n key: K,\n value: OrderCallbackFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleRetry = async (order: OrderCallback) => {\n if (!order.billingAccountId) return;\n setRetryError(null);\n try {\n await retryOrderCallback(order.id, order.billingAccountId);\n await refetch();\n setSelectedOrder(null);\n } catch (err) {\n setRetryError(err instanceof Error ? err.message : 'Failed to retry callback');\n }\n };\n\n const columns: ResponsiveTableColumn<OrderCallback>[] = [\n {\n key: 'date',\n header: 'Created',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(order.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(order.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'callbackStatus',\n header: 'Callback',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => {\n const badge = getBadge(order);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'service',\n header: 'Service',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => <span className=\"text-sm text-text-primary\">{order.externalService}</span>,\n },\n {\n key: 'orderId',\n header: 'Order ID',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (order) => (\n <span className=\"text-sm font-mono text-text-muted\" title={order.id}>\n {order.id}\n </span>\n ),\n },\n {\n key: 'attempts',\n header: 'Attempts',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <span className=\"text-sm text-text-secondary\">{order.callbackAttempts ?? 0}</span>\n ),\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (order) => (\n <span className=\"text-sm text-status-error-text\">{order.callbackLastError || '-'}</span>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Order Callback Logs\"\n description=\"Every billing→store (and other external service) order-completion callback — investigate a stuck handoff directly.\"\n actions={\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 }\n />\n\n {retryError && (\n <div className=\"p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border text-sm\">\n {retryError}\n </div>\n )}\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Callback status</label>\n <select\n value={filters.callbackStatus || ''}\n onChange={(e) => handleFilterChange('callbackStatus', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n {CALLBACK_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Service</label>\n <input\n type=\"text\"\n value={filters.externalService || ''}\n onChange={(e) => handleFilterChange('externalService', e.target.value || undefined)}\n placeholder=\"e.g. STORE\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && orders.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 ) : orders.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No order callbacks found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next store purchase.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<OrderCallback>\n columns={columns}\n data={orders}\n rowKey={(order) => order.id}\n rowActions={(order) => (\n <button\n type=\"button\"\n onClick={() => setSelectedOrder(order)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {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 {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} orders\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\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 <ChevronLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\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 <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <OrderCallbackDetailModal\n order={selectedOrder}\n onClose={() => setSelectedOrder(null)}\n onRetry={handleRetry}\n isRetrying={isRetrying}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrderCallbackLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/OrderCallbackLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Order Callback Logs Page\n * Admin visibility into the billing→external-service (store, etc.) order\n * completion callback — every BillingOrder's callback delivery attempt, not\n * just the ones currently FAILED. The outbound counterpart to\n * BillingWebhookLogsPage's inbound Stripe/Razorpay webhook view: lets\n * whoever owns billing ops see and retry a stuck billing→store handoff\n * without needing direct DB access (BOFF-7192).\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n Clock,\n X,\n RotateCw,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useOrderCallbacks,\n useOrderCallbackMutations,\n type OrderCallbackFilters,\n type OrderCallback,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { 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 CALLBACK_STATUS_OPTIONS = ['PENDING', 'SUCCESS', 'FAILED'];\n\nconst getBadge = (\n order: OrderCallback\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (order.callbackStatus === 'FAILED') {\n return {\n label: 'Failed',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n if (order.callbackStatus === 'SUCCESS') {\n return {\n label: 'Delivered',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n }\n return {\n label: order.callbackStatus || 'Pending',\n className: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n icon: Clock,\n };\n};\n\nconst OrderCallbackDetailModal: FC<{\n order: OrderCallback | null;\n onClose: () => void;\n onRetry: (order: OrderCallback) => void;\n isRetrying: boolean;\n}> = ({ order, onClose, onRetry, isRetrying }) => {\n if (!order) return null;\n\n const canRetry = order.callbackStatus === 'FAILED' && Boolean(order.billingAccountId);\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Order callback details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Order ID</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">{order.id}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Service</p>\n <p className=\"text-text-primary\">{order.externalService}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Order status</p>\n <p className=\"text-text-primary\">{order.status}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Amount</p>\n <p className=\"text-text-primary\">\n {order.subtotal} {order.currency}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Billing account</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.billingAccountId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Payment / Transaction</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.paymentId || '—'} / {order.transactionId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Created</p>\n <p className=\"text-text-primary\">{formatDateTime(order.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Last callback attempt</p>\n <p className=\"text-text-primary\">\n {order.callbackLastAttemptAt ? formatDateTime(order.callbackLastAttemptAt) : '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback attempts</p>\n <p className=\"text-text-primary\">{order.callbackAttempts ?? 0}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback status</p>\n <p className=\"text-text-primary\">{order.callbackStatus || 'PENDING'}</p>\n </div>\n </div>\n {order.callbackLastError && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Last error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {order.callbackLastError}\n </pre>\n </div>\n )}\n {order.callbackStatus === 'FAILED' && !order.billingAccountId && (\n <p className=\"text-xs text-text-muted\">\n No billing account was resolved on this order, so it can't be retried from here —\n the callback was never able to identify who paid.\n </p>\n )}\n {order.callbackStatus === 'FAILED' && (\n <button\n type=\"button\"\n onClick={() => onRetry(order)}\n disabled={!canRetry || isRetrying}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isRetrying ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCw className=\"size-4\" />\n )}\n Retry callback\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const OrderCallbackLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<OrderCallbackFilters>({});\n const [selectedOrder, setSelectedOrder] = useState<OrderCallback | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n\n const { orders, totalCount, isLoading, error, refetch } = useOrderCallbacks(\n filters,\n page,\n PAGE_SIZE\n );\n const { retryOrderCallback, isRetrying } = useOrderCallbackMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view order callback 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 order callback logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof OrderCallbackFilters>(\n key: K,\n value: OrderCallbackFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleRetry = async (order: OrderCallback) => {\n if (!order.billingAccountId) return;\n setRetryError(null);\n try {\n await retryOrderCallback(order.id, order.billingAccountId);\n await refetch();\n setSelectedOrder(null);\n } catch (err) {\n setRetryError(err instanceof Error ? err.message : 'Failed to retry callback');\n }\n };\n\n const columns: ResponsiveTableColumn<OrderCallback>[] = [\n {\n key: 'date',\n header: 'Created',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(order.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(order.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'callbackStatus',\n header: 'Callback',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => {\n const badge = getBadge(order);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'service',\n header: 'Service',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => <span className=\"text-sm text-text-primary\">{order.externalService}</span>,\n },\n {\n key: 'orderId',\n header: 'Order ID',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (order) => (\n <span className=\"text-sm font-mono text-text-muted\" title={order.id}>\n {order.id}\n </span>\n ),\n },\n {\n key: 'attempts',\n header: 'Attempts',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <span className=\"text-sm text-text-secondary\">{order.callbackAttempts ?? 0}</span>\n ),\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (order) => (\n <span className=\"text-sm text-status-error-text\">{order.callbackLastError || '-'}</span>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Order Callback Logs\"\n description=\"Every billing→store (and other external service) order-completion callback — investigate a stuck handoff directly.\"\n actions={\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 }\n />\n\n {retryError && (\n <div className=\"p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border text-sm\">\n {retryError}\n </div>\n )}\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Callback status</label>\n <select\n value={filters.callbackStatus || ''}\n onChange={(e) => handleFilterChange('callbackStatus', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All</option>\n {CALLBACK_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Service</label>\n <input\n type=\"text\"\n value={filters.externalService || ''}\n onChange={(e) => handleFilterChange('externalService', e.target.value || undefined)}\n placeholder=\"e.g. STORE\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && orders.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 ) : orders.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No order callbacks found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next store purchase.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<OrderCallback>\n columns={columns}\n data={orders}\n rowKey={(order) => order.id}\n rowActions={(order) => (\n <button\n type=\"button\"\n onClick={() => setSelectedOrder(order)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {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 {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} orders\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\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 <ChevronLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\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 <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <OrderCallbackDetailModal\n order={selectedOrder}\n onClose={() => setSelectedOrder(null)}\n onRetry={handleRetry}\n isRetrying={isRetrying}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -926,7 +926,7 @@ var I = {
|
|
|
926
926
|
}), /* @__PURE__ */ P("select", {
|
|
927
927
|
value: b.source || "",
|
|
928
928
|
onChange: (e) => Q("source", e.target.value),
|
|
929
|
-
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
929
|
+
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
930
930
|
children: [/* @__PURE__ */ N("option", {
|
|
931
931
|
value: "",
|
|
932
932
|
children: "All Sources"
|
|
@@ -941,7 +941,7 @@ var I = {
|
|
|
941
941
|
}), /* @__PURE__ */ P("select", {
|
|
942
942
|
value: b.provider || "",
|
|
943
943
|
onChange: (e) => Q("provider", e.target.value),
|
|
944
|
-
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
|
|
944
|
+
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
945
945
|
children: [/* @__PURE__ */ N("option", {
|
|
946
946
|
value: "",
|
|
947
947
|
children: "All Providers"
|