@burdenoff/microfe-billing 2026.907.2 → 2026.909.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, type FC } from 'react';\nimport { RotateCcw } from 'lucide-react';\nimport { useSearchParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { 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 { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterStatus = 'all' | InvoiceStatus;\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\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 // 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 {/* 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 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 canPay: boolean;\n canVoid: boolean;\n showAccount?: boolean;\n accountName?: string | null;\n}\n\nconst InvoiceRow: FC<InvoiceRowProps> = ({\n invoice,\n onView,\n canPay,\n canVoid,\n showAccount = false,\n accountName,\n}) => {\n const tr = useTr();\n const statusColor = getInvoiceStatusColor(invoice.status);\n const isOpen = invoice.status === ('OPEN' as InvoiceStatus);\n const isDraft = invoice.status === ('DRAFT' as InvoiceStatus);\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=\"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 {isOpen && canPay && (\n <button\n type=\"button\"\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\"\n >\n {tr('billing.common.pay', 'Pay')}\n </button>\n )}\n {(isOpen || isDraft) && canVoid && (\n <button\n type=\"button\"\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\"\n >\n {tr('billing.common.void', 'Void')}\n </button>\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)]\"\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 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 {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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"}
@@ -4,21 +4,22 @@ import { PageHeader as n } from "../../../shared/components/PageHeader.js";
4
4
  import "../../../shared/components/index.js";
5
5
  import { useBillingEventEmitter as r } from "../../../hooks/useBillingEventEmitter.js";
6
6
  import { useBillingPermissions as i } from "../../../hooks/useBillingPermissions.js";
7
- import { useDefaultDashboardAccount as a } from "../../dashboard/hooks/useDashboard.js";
7
+ import { useDashboardMutations as a, useDefaultDashboardAccount as o } from "../../dashboard/hooks/useDashboard.js";
8
8
  import "../../dashboard/hooks/index.js";
9
- import { AddPaymentMethodModal as o } from "../../dashboard/components/AddPaymentMethodModal.js";
10
- import { useCallback as s, useEffect as c, useState as l } from "react";
11
- import { useLocation as u } from "react-router";
12
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
13
- import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
- import { IllustratedEmptyState as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
9
+ import { nativeConfirm as s, nativeImpact as c, nativeNotify as l } from "../../../../utils/nativeBridge.js";
10
+ import { AddPaymentMethodModal as u } from "../../dashboard/components/AddPaymentMethodModal.js";
11
+ import { useCallback as d, useEffect as f, useState as p } from "react";
12
+ import { useLocation as m } from "react-router";
13
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
14
+ import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
15
+ import { IllustratedEmptyState as v, PagePurpose as y } from "@burdenoff/fe-libs/ui";
15
16
  //#region src/billing/modules/billing/pages/PaymentMethodsPage.tsx
16
- var g = {
17
+ var b = {
17
18
  visa: "💳",
18
19
  mastercard: "💳",
19
20
  amex: "💳",
20
21
  default: "💳"
21
- }, _ = {
22
+ }, x = {
22
23
  CREDIT_CARD: "Credit Card",
23
24
  DEBIT_CARD: "Debit Card",
24
25
  WALLET: "Digital Wallet",
@@ -26,124 +27,226 @@ var g = {
26
27
  BANK_TRANSFER: "Bank Transfer",
27
28
  PAYPAL: "PayPal",
28
29
  CRYPTO: "Cryptocurrency"
29
- }, v = () => {
30
- let { t: g } = p(), _ = (e, t) => {
31
- let n = g(e);
30
+ }, S = () => {
31
+ let { t: s } = _(), c = (e, t) => {
32
+ let n = s(e);
32
33
  return n === e ? t : n;
33
- }, v = i(), { emit: b } = r(), { navigate: x, basePath: S } = e(), { pathname: C } = u(), { billingAccount: w, paymentMethods: T, isLoading: E } = a(), [D, O] = l(!1), k = s((e) => S && S !== "/" ? `${S.endsWith("/") ? S.slice(0, -1) : S}/${e}` : `/${e}`, [S]), A = s(() => {
34
- x(k("payment-methods/add"));
35
- }, [x, k]);
36
- return c(() => {
37
- C.replace(/\/+$/, "").endsWith("/payment-methods/add") && O(!0);
38
- }, [C]), v.canViewPaymentMethods ? E ? /* @__PURE__ */ f("div", {
34
+ }, b = i(), { emit: x } = r(), { navigate: S, basePath: w } = e(), { pathname: T } = m(), { billingAccount: E, paymentMethods: D, isLoading: O, refetch: k } = o(), { setDefaultPaymentMethod: A, deletePaymentMethod: j } = a(), [M, N] = p(!1), [P, F] = p(null), I = d((e) => w && w !== "/" ? `${w.endsWith("/") ? w.slice(0, -1) : w}/${e}` : `/${e}`, [w]), L = d(() => {
35
+ S(I("payment-methods/add"));
36
+ }, [S, I]), R = d(async (e) => {
37
+ let t = E?.id;
38
+ if (!t) {
39
+ F("No billing account context");
40
+ return;
41
+ }
42
+ F(null);
43
+ try {
44
+ await A(e, t), await k(), l("success"), x("billing.payment_method.default_set", {
45
+ route: "/billing/payment-methods",
46
+ entityId: e,
47
+ source: "payment-methods"
48
+ });
49
+ } catch (e) {
50
+ F(e instanceof Error ? e.message : "Failed to set default payment method"), l("error");
51
+ }
52
+ }, [
53
+ E?.id,
54
+ x,
55
+ k,
56
+ A
57
+ ]), z = d(async (e) => {
58
+ let t = E?.id;
59
+ if (!t) {
60
+ F("No billing account context");
61
+ return;
62
+ }
63
+ F(null);
64
+ try {
65
+ await j(e, t), await k(), l("success"), x("billing.payment_method.deleted", {
66
+ route: "/billing/payment-methods",
67
+ entityId: e,
68
+ source: "payment-methods"
69
+ });
70
+ } catch (e) {
71
+ F(e instanceof Error ? e.message : "Failed to remove payment method"), l("error");
72
+ }
73
+ }, [
74
+ E?.id,
75
+ j,
76
+ x,
77
+ k
78
+ ]);
79
+ return f(() => {
80
+ T.replace(/\/+$/, "").endsWith("/payment-methods/add") && N(!0);
81
+ }, [T]), b.canViewPaymentMethods ? O ? /* @__PURE__ */ g("div", {
39
82
  className: "space-y-6 p-6",
40
- children: [/* @__PURE__ */ d("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ d("div", {
83
+ children: [/* @__PURE__ */ h("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ h("div", {
41
84
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
42
85
  children: [
43
86
  1,
44
87
  2,
45
88
  3
46
- ].map((e) => /* @__PURE__ */ d("div", { className: "h-32 bg-bg-sunken animate-pulse rounded-lg" }, e))
89
+ ].map((e) => /* @__PURE__ */ h("div", { className: "h-32 bg-bg-sunken animate-pulse rounded-lg" }, e))
47
90
  })]
48
- }) : /* @__PURE__ */ f("div", {
91
+ }) : /* @__PURE__ */ g("div", {
49
92
  className: "space-y-6 p-6",
50
93
  children: [
51
- /* @__PURE__ */ d(n, {
52
- title: _("billing.paymentMethods.title", "Payment Methods"),
53
- description: _("billing.paymentMethods.subtitle", "Manage your saved payment methods"),
54
- actions: v.canManagePaymentMethods && /* @__PURE__ */ d("button", {
94
+ /* @__PURE__ */ h(n, {
95
+ title: c("billing.paymentMethods.title", "Payment Methods"),
96
+ description: c("billing.paymentMethods.subtitle", "Manage your saved payment methods"),
97
+ actions: b.canManagePaymentMethods && /* @__PURE__ */ h("button", {
55
98
  type: "button",
56
- onClick: A,
99
+ onClick: L,
57
100
  className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors",
58
- children: _("billing.paymentMethods.add", "Add payment method")
101
+ children: c("billing.paymentMethods.add", "Add payment method")
59
102
  })
60
103
  }),
61
- /* @__PURE__ */ d(h, { children: _("billing.paymentMethods.purpose", "Save and manage the cards and payment options used to pay invoices, subscriptions and one-off purchases. Add a method, mark one as default for automatic billing, or remove ones you no longer use.") }),
62
- T.length === 0 ? /* @__PURE__ */ d(m, {
104
+ /* @__PURE__ */ h(y, { children: c("billing.paymentMethods.purpose", "Save and manage the cards and payment options used to pay invoices, subscriptions and one-off purchases. Add a method, mark one as default for automatic billing, or remove ones you no longer use.") }),
105
+ P && /* @__PURE__ */ h("div", {
106
+ className: "rounded-button border border-status-error-border bg-status-error-bg-subtle p-3",
107
+ children: /* @__PURE__ */ h("p", {
108
+ className: "text-sm text-status-error-text",
109
+ children: P
110
+ })
111
+ }),
112
+ D.length === 0 ? /* @__PURE__ */ h(v, {
63
113
  illustration: "empty-generic",
64
- title: _("billing.paymentMethods.noPaymentMethods", "No payment methods"),
65
- description: _("billing.paymentMethods.noPaymentMethodsDesc", "Add a payment method to make purchases and manage subscriptions."),
66
- action: v.canManagePaymentMethods ? /* @__PURE__ */ d("button", {
114
+ title: c("billing.paymentMethods.noPaymentMethods", "No payment methods"),
115
+ description: c("billing.paymentMethods.noPaymentMethodsDesc", "Add a payment method to make purchases and manage subscriptions."),
116
+ action: b.canManagePaymentMethods ? /* @__PURE__ */ h("button", {
67
117
  type: "button",
68
- onClick: () => O(!0),
118
+ onClick: () => N(!0),
69
119
  className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90",
70
120
  children: "Add Payment Method"
71
121
  }) : void 0
72
- }) : /* @__PURE__ */ d("div", {
122
+ }) : /* @__PURE__ */ h("div", {
73
123
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
74
- children: T.map((e) => /* @__PURE__ */ d(y, {
124
+ children: D.map((e) => /* @__PURE__ */ h(C, {
75
125
  method: e,
76
- canManage: v.canManagePaymentMethods
126
+ canManage: b.canManagePaymentMethods,
127
+ onSetDefault: R,
128
+ onDelete: z
77
129
  }, e.id))
78
130
  }),
79
- /* @__PURE__ */ d(o, {
80
- isOpen: D,
131
+ /* @__PURE__ */ h(u, {
132
+ isOpen: M,
81
133
  onClose: () => {
82
- O(!1), C.replace(/\/+$/, "").endsWith("/payment-methods/add") && x(k("payment-methods"));
134
+ N(!1), T.replace(/\/+$/, "").endsWith("/payment-methods/add") && S(I("payment-methods"));
83
135
  },
84
- billingAccountId: w?.id ?? "",
85
- currency: w?.currency ?? "USD",
86
- country: w?.billingAddresses?.[0]?.country ?? void 0,
136
+ billingAccountId: E?.id ?? "",
137
+ currency: E?.currency ?? "USD",
138
+ country: E?.billingAddresses?.[0]?.country ?? void 0,
87
139
  onSuccess: () => {
88
- b("billing.payment_method.added", {
140
+ x("billing.payment_method.added", {
89
141
  route: "/billing/payment-methods",
90
- entityId: w?.id,
142
+ entityId: E?.id,
91
143
  source: "payment-methods"
92
- }), O(!1), C.replace(/\/+$/, "").endsWith("/payment-methods/add") && x(k("payment-methods"));
144
+ }), F(null), k(), N(!1), T.replace(/\/+$/, "").endsWith("/payment-methods/add") && S(I("payment-methods"));
93
145
  }
94
146
  })
95
147
  ]
96
- }) : /* @__PURE__ */ d(t, { message: _("billing.paymentMethods.noViewPermission", "You don't have permission to view payment methods.") });
97
- }, y = ({ method: e, canManage: t }) => {
98
- let n = e.expiryYear && e.expiryMonth ? new Date(e.expiryYear, e.expiryMonth - 1) < /* @__PURE__ */ new Date() : !1;
99
- return /* @__PURE__ */ f("div", {
148
+ }) : /* @__PURE__ */ h(t, { message: c("billing.paymentMethods.noViewPermission", "You don't have permission to view payment methods.") });
149
+ }, C = ({ method: e, canManage: t, onSetDefault: n, onDelete: r }) => {
150
+ let [i, a] = p(!1), [o, l] = p(!1), [u, d] = p(!1), f = e.expiryYear && e.expiryMonth ? new Date(e.expiryYear, e.expiryMonth - 1) < /* @__PURE__ */ new Date() : !1, m = async () => {
151
+ a(!0);
152
+ try {
153
+ await n(e.id);
154
+ } finally {
155
+ a(!1);
156
+ }
157
+ }, _ = async () => {
158
+ l(!0);
159
+ try {
160
+ await r(e.id);
161
+ } finally {
162
+ l(!1), d(!1);
163
+ }
164
+ };
165
+ return /* @__PURE__ */ g("div", {
100
166
  className: `border rounded-lg bg-bg-surface p-4 ${e.isDefault ? "border-primary" : "border-border-subtle"}`,
101
- children: [/* @__PURE__ */ d("div", {
102
- className: "flex items-start justify-between",
103
- children: /* @__PURE__ */ f("div", {
104
- className: "flex items-center gap-3",
105
- children: [/* @__PURE__ */ d("span", {
106
- className: "text-2xl",
107
- children: g[e.brand?.toLowerCase() ?? "default"]
108
- }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ f("div", {
109
- className: "flex items-center gap-2",
110
- children: [/* @__PURE__ */ f("p", {
111
- className: "font-medium text-text-primary",
167
+ children: [
168
+ /* @__PURE__ */ h("div", {
169
+ className: "flex items-start justify-between",
170
+ children: /* @__PURE__ */ g("div", {
171
+ className: "flex items-center gap-3",
172
+ children: [/* @__PURE__ */ h("span", {
173
+ className: "text-2xl",
174
+ children: b[e.brand?.toLowerCase() ?? "default"]
175
+ }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("div", {
176
+ className: "flex items-center gap-2",
177
+ children: [/* @__PURE__ */ g("p", {
178
+ className: "font-medium text-text-primary",
179
+ children: [
180
+ e.brand ?? x[e.type],
181
+ " •••• ",
182
+ e.last4
183
+ ]
184
+ }), e.isDefault && /* @__PURE__ */ h("span", {
185
+ className: "px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded",
186
+ children: "Default"
187
+ })]
188
+ }), e.expiryMonth && e.expiryYear && /* @__PURE__ */ g("p", {
189
+ className: `text-sm ${f ? "text-status-error-text" : "text-text-secondary"}`,
112
190
  children: [
113
- e.brand ?? _[e.type],
114
- " •••• ",
115
- e.last4
191
+ f ? "Expired" : "Expires",
192
+ " ",
193
+ e.expiryMonth,
194
+ "/",
195
+ e.expiryYear
116
196
  ]
117
- }), e.isDefault && /* @__PURE__ */ d("span", {
118
- className: "px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded",
119
- children: "Default"
197
+ })] })]
198
+ })
199
+ }),
200
+ t && /* @__PURE__ */ g("div", {
201
+ className: "flex items-center gap-2 mt-4 pt-4 border-t border-border-subtle",
202
+ children: [!e.isDefault && /* @__PURE__ */ h("button", {
203
+ type: "button",
204
+ onClick: m,
205
+ disabled: i || o,
206
+ className: "text-sm text-primary hover:underline disabled:cursor-not-allowed disabled:opacity-50",
207
+ children: i ? "Setting default…" : "Set as default"
208
+ }), /* @__PURE__ */ h("button", {
209
+ type: "button",
210
+ onClick: async () => {
211
+ c("medium");
212
+ let e = await s({
213
+ title: "Delete payment method",
214
+ message: "Are you sure you want to remove this payment method?",
215
+ okButtonTitle: "Delete",
216
+ cancelButtonTitle: "Cancel"
217
+ });
218
+ e === !0 ? await _() : e === null && d(!0);
219
+ },
220
+ disabled: i || o,
221
+ className: "text-sm text-status-error-text hover:underline ml-auto disabled:cursor-not-allowed disabled:opacity-50",
222
+ children: o ? "Removing…" : "Remove"
223
+ })]
224
+ }),
225
+ u && /* @__PURE__ */ g("div", {
226
+ className: "mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3",
227
+ children: [/* @__PURE__ */ h("p", {
228
+ className: "text-sm font-medium text-status-error-text",
229
+ children: "Remove this payment method from your billing account?"
230
+ }), /* @__PURE__ */ g("div", {
231
+ className: "mt-3 flex justify-end gap-2",
232
+ children: [/* @__PURE__ */ h("button", {
233
+ type: "button",
234
+ onClick: () => d(!1),
235
+ className: "rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
236
+ children: "Cancel"
237
+ }), /* @__PURE__ */ h("button", {
238
+ type: "button",
239
+ onClick: _,
240
+ disabled: o,
241
+ className: "rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50",
242
+ children: o ? "Removing…" : "Remove"
120
243
  })]
121
- }), e.expiryMonth && e.expiryYear && /* @__PURE__ */ f("p", {
122
- className: `text-sm ${n ? "text-status-error-text" : "text-text-secondary"}`,
123
- children: [
124
- n ? "Expired" : "Expires",
125
- " ",
126
- e.expiryMonth,
127
- "/",
128
- e.expiryYear
129
- ]
130
- })] })]
244
+ })]
131
245
  })
132
- }), t && /* @__PURE__ */ f("div", {
133
- className: "flex items-center gap-2 mt-4 pt-4 border-t border-border-subtle",
134
- children: [!e.isDefault && /* @__PURE__ */ d("button", {
135
- type: "button",
136
- className: "text-sm text-primary hover:underline",
137
- children: "Set as default"
138
- }), /* @__PURE__ */ d("button", {
139
- type: "button",
140
- className: "text-sm text-status-error-text hover:underline ml-auto",
141
- children: "Remove"
142
- })]
143
- })]
246
+ ]
144
247
  });
145
248
  };
146
249
  //#endregion
147
- export { v as PaymentMethodsPage };
250
+ export { S as PaymentMethodsPage };
148
251
 
149
252
  //# sourceMappingURL=PaymentMethodsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentMethodsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/PaymentMethodsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Payment Methods Page\n * Displays and manages payment methods\n */\n\nimport { useCallback, useEffect, useState, type FC } from 'react';\nimport { useLocation } from 'react-router';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useDefaultDashboardAccount } from '../../dashboard/hooks';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { AddPaymentMethodModal } from '../../dashboard/components/AddPaymentMethodModal';\nimport type { PaymentMethod, PaymentMethodType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nconst CARD_ICONS: Record<string, string> = {\n visa: '💳',\n mastercard: '💳',\n amex: '💳',\n default: '💳',\n};\n\nconst METHOD_TYPE_LABELS: Record<PaymentMethodType, string> = {\n CREDIT_CARD: 'Credit Card',\n DEBIT_CARD: 'Debit Card',\n WALLET: 'Digital Wallet',\n NETBANKING: 'Net Banking',\n BANK_TRANSFER: 'Bank Transfer',\n PAYPAL: 'PayPal',\n CRYPTO: 'Cryptocurrency',\n};\n\nexport const PaymentMethodsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { navigate, basePath } = useBilling();\n const { pathname } = useLocation();\n const { billingAccount, paymentMethods, isLoading } = useDefaultDashboardAccount();\n const [showAddModal, setShowAddModal] = useState(false);\n\n // basePath-aware path join, mirroring OverviewPage's navigateTo helper.\n const toFullPath = useCallback(\n (path: string) => {\n if (basePath && basePath !== '/') {\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n return `${base}/${path}`;\n }\n return `/${path}`;\n },\n [basePath]\n );\n\n // Navigate (basePath-aware) to the dedicated add-payment-method route.\n const navigateToAdd = useCallback(() => {\n navigate(toFullPath('payment-methods/add'));\n }, [navigate, toFullPath]);\n\n // The /payment-methods/add route renders this same page; open the add modal\n // automatically when landing there so the route is actually functional.\n useEffect(() => {\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n setShowAddModal(true);\n }\n }, [pathname]);\n\n if (!permissions.canViewPaymentMethods) {\n return (\n <AccessDenied\n message={tr(\n 'billing.paymentMethods.noViewPermission',\n \"You don't have permission to view payment methods.\"\n )}\n />\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-32 bg-bg-sunken animate-pulse rounded-lg\" />\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.paymentMethods.title', 'Payment Methods')}\n description={tr('billing.paymentMethods.subtitle', 'Manage your saved payment methods')}\n actions={\n permissions.canManagePaymentMethods && (\n <button\n type=\"button\"\n onClick={navigateToAdd}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr('billing.paymentMethods.add', 'Add payment method')}\n </button>\n )\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.paymentMethods.purpose',\n 'Save and manage the cards and payment options used to pay invoices, subscriptions and one-off purchases. Add a method, mark one as default for automatic billing, or remove ones you no longer use.'\n )}\n </PagePurpose>\n\n {/* Payment Methods List */}\n {paymentMethods.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr('billing.paymentMethods.noPaymentMethods', 'No payment methods')}\n description={tr(\n 'billing.paymentMethods.noPaymentMethodsDesc',\n 'Add a payment method to make purchases and manage subscriptions.'\n )}\n action={\n permissions.canManagePaymentMethods ? (\n <button\n type=\"button\"\n onClick={() => setShowAddModal(true)}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90\"\n >\n Add Payment Method\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {paymentMethods.map((method) => (\n <PaymentMethodCard\n key={method.id}\n method={method}\n canManage={permissions.canManagePaymentMethods}\n />\n ))}\n </div>\n )}\n\n {/* Add Payment Method Modal (Stripe + Razorpay) */}\n <AddPaymentMethodModal\n isOpen={showAddModal}\n onClose={() => {\n setShowAddModal(false);\n // If we arrived via the /add route, return to the list URL.\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n billingAccountId={billingAccount?.id ?? ''}\n currency={billingAccount?.currency ?? 'USD'}\n country={billingAccount?.billingAddresses?.[0]?.country ?? undefined}\n onSuccess={() => {\n emit('billing.payment_method.added', {\n route: '/billing/payment-methods',\n entityId: billingAccount?.id,\n source: 'payment-methods',\n });\n // Refetch billing account to get updated payment methods\n setShowAddModal(false);\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n />\n </div>\n );\n};\n\n// ============================================================================\n// Payment Method Card Component\n// ============================================================================\n\ninterface PaymentMethodCardProps {\n method: PaymentMethod;\n canManage: boolean;\n}\n\nconst PaymentMethodCard: FC<PaymentMethodCardProps> = ({ method, canManage }) => {\n const isExpired =\n method.expiryYear && method.expiryMonth\n ? new Date(method.expiryYear, method.expiryMonth - 1) < new Date()\n : false;\n\n return (\n <div\n className={`border rounded-lg bg-bg-surface p-4 ${\n method.isDefault ? 'border-primary' : 'border-border-subtle'\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-2xl\">{CARD_ICONS[method.brand?.toLowerCase() ?? 'default']}</span>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {method.brand ?? METHOD_TYPE_LABELS[method.type]} •••• {method.last4}\n </p>\n {method.isDefault && (\n <span className=\"px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded\">\n Default\n </span>\n )}\n </div>\n {method.expiryMonth && method.expiryYear && (\n <p\n className={`text-sm ${isExpired ? 'text-status-error-text' : 'text-text-secondary'}`}\n >\n {isExpired ? 'Expired' : 'Expires'} {method.expiryMonth}/{method.expiryYear}\n </p>\n )}\n </div>\n </div>\n </div>\n\n {canManage && (\n <div className=\"flex items-center gap-2 mt-4 pt-4 border-t border-border-subtle\">\n {!method.isDefault && (\n <button type=\"button\" className=\"text-sm text-primary hover:underline\">\n Set as default\n </button>\n )}\n <button type=\"button\" className=\"text-sm text-status-error-text hover:underline ml-auto\">\n Remove\n </button>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PaymentMethodsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/PaymentMethodsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Payment Methods Page\n * Displays and manages payment methods\n */\n\nimport { useCallback, useEffect, useState, type FC } from 'react';\nimport { useLocation } from 'react-router';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useDashboardMutations, useDefaultDashboardAccount } from '../../dashboard/hooks';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { AddPaymentMethodModal } from '../../dashboard/components/AddPaymentMethodModal';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport type { PaymentMethod, PaymentMethodType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nconst CARD_ICONS: Record<string, string> = {\n visa: '💳',\n mastercard: '💳',\n amex: '💳',\n default: '💳',\n};\n\nconst METHOD_TYPE_LABELS: Record<PaymentMethodType, string> = {\n CREDIT_CARD: 'Credit Card',\n DEBIT_CARD: 'Debit Card',\n WALLET: 'Digital Wallet',\n NETBANKING: 'Net Banking',\n BANK_TRANSFER: 'Bank Transfer',\n PAYPAL: 'PayPal',\n CRYPTO: 'Cryptocurrency',\n};\n\nexport const PaymentMethodsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { navigate, basePath } = useBilling();\n const { pathname } = useLocation();\n const { billingAccount, paymentMethods, isLoading, refetch } = useDefaultDashboardAccount();\n const { setDefaultPaymentMethod, deletePaymentMethod } = useDashboardMutations();\n const [showAddModal, setShowAddModal] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // basePath-aware path join, mirroring OverviewPage's navigateTo helper.\n const toFullPath = useCallback(\n (path: string) => {\n if (basePath && basePath !== '/') {\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n return `${base}/${path}`;\n }\n return `/${path}`;\n },\n [basePath]\n );\n\n // Navigate (basePath-aware) to the dedicated add-payment-method route.\n const navigateToAdd = useCallback(() => {\n navigate(toFullPath('payment-methods/add'));\n }, [navigate, toFullPath]);\n\n const handleSetDefault = useCallback(\n async (paymentMethodId: string): Promise<void> => {\n const billingAccountId = billingAccount?.id;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n try {\n await setDefaultPaymentMethod(paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n emit('billing.payment_method.default_set', {\n route: '/billing/payment-methods',\n entityId: paymentMethodId,\n source: 'payment-methods',\n });\n } catch (error) {\n setActionError(\n error instanceof Error ? error.message : 'Failed to set default payment method'\n );\n void nativeNotify('error');\n }\n },\n [billingAccount?.id, emit, refetch, setDefaultPaymentMethod]\n );\n\n const handleDelete = useCallback(\n async (paymentMethodId: string): Promise<void> => {\n const billingAccountId = billingAccount?.id;\n if (!billingAccountId) {\n setActionError('No billing account context');\n return;\n }\n\n setActionError(null);\n try {\n await deletePaymentMethod(paymentMethodId, billingAccountId);\n await refetch();\n void nativeNotify('success');\n emit('billing.payment_method.deleted', {\n route: '/billing/payment-methods',\n entityId: paymentMethodId,\n source: 'payment-methods',\n });\n } catch (error) {\n setActionError(error instanceof Error ? error.message : 'Failed to remove payment method');\n void nativeNotify('error');\n }\n },\n [billingAccount?.id, deletePaymentMethod, emit, refetch]\n );\n\n // The /payment-methods/add route renders this same page; open the add modal\n // automatically when landing there so the route is actually functional.\n useEffect(() => {\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n setShowAddModal(true);\n }\n }, [pathname]);\n\n if (!permissions.canViewPaymentMethods) {\n return (\n <AccessDenied\n message={tr(\n 'billing.paymentMethods.noViewPermission',\n \"You don't have permission to view payment methods.\"\n )}\n />\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-32 bg-bg-sunken animate-pulse rounded-lg\" />\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.paymentMethods.title', 'Payment Methods')}\n description={tr('billing.paymentMethods.subtitle', 'Manage your saved payment methods')}\n actions={\n permissions.canManagePaymentMethods && (\n <button\n type=\"button\"\n onClick={navigateToAdd}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr('billing.paymentMethods.add', 'Add payment method')}\n </button>\n )\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.paymentMethods.purpose',\n 'Save and manage the cards and payment options used to pay invoices, subscriptions and one-off purchases. Add a method, mark one as default for automatic billing, or remove ones you no longer use.'\n )}\n </PagePurpose>\n\n {actionError && (\n <div className=\"rounded-button border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm text-status-error-text\">{actionError}</p>\n </div>\n )}\n\n {/* Payment Methods List */}\n {paymentMethods.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tr('billing.paymentMethods.noPaymentMethods', 'No payment methods')}\n description={tr(\n 'billing.paymentMethods.noPaymentMethodsDesc',\n 'Add a payment method to make purchases and manage subscriptions.'\n )}\n action={\n permissions.canManagePaymentMethods ? (\n <button\n type=\"button\"\n onClick={() => setShowAddModal(true)}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90\"\n >\n Add Payment Method\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {paymentMethods.map((method) => (\n <PaymentMethodCard\n key={method.id}\n method={method}\n canManage={permissions.canManagePaymentMethods}\n onSetDefault={handleSetDefault}\n onDelete={handleDelete}\n />\n ))}\n </div>\n )}\n\n {/* Add Payment Method Modal (Stripe + Razorpay) */}\n <AddPaymentMethodModal\n isOpen={showAddModal}\n onClose={() => {\n setShowAddModal(false);\n // If we arrived via the /add route, return to the list URL.\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n billingAccountId={billingAccount?.id ?? ''}\n currency={billingAccount?.currency ?? 'USD'}\n country={billingAccount?.billingAddresses?.[0]?.country ?? undefined}\n onSuccess={() => {\n emit('billing.payment_method.added', {\n route: '/billing/payment-methods',\n entityId: billingAccount?.id,\n source: 'payment-methods',\n });\n // Refetch billing account to get updated payment methods\n setActionError(null);\n void refetch();\n setShowAddModal(false);\n if (pathname.replace(/\\/+$/, '').endsWith('/payment-methods/add')) {\n navigate(toFullPath('payment-methods'));\n }\n }}\n />\n </div>\n );\n};\n\n// ============================================================================\n// Payment Method Card Component\n// ============================================================================\n\ninterface PaymentMethodCardProps {\n method: PaymentMethod;\n canManage: boolean;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n}\n\nconst PaymentMethodCard: FC<PaymentMethodCardProps> = ({\n method,\n canManage,\n onSetDefault,\n onDelete,\n}) => {\n const [isSettingDefault, setIsSettingDefault] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);\n const isExpired =\n method.expiryYear && method.expiryMonth\n ? new Date(method.expiryYear, method.expiryMonth - 1) < new Date()\n : false;\n\n const handleSetDefault = async () => {\n setIsSettingDefault(true);\n try {\n await onSetDefault(method.id);\n } finally {\n setIsSettingDefault(false);\n }\n };\n\n const handleDelete = async () => {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n } finally {\n setIsDeleting(false);\n setIsConfirmingDelete(false);\n }\n };\n\n const handleDeleteRequest = async () => {\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete payment method',\n message: 'Are you sure you want to remove this payment method?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n\n if (confirmed === true) {\n await handleDelete();\n } else if (confirmed === null) {\n setIsConfirmingDelete(true);\n }\n };\n\n return (\n <div\n className={`border rounded-lg bg-bg-surface p-4 ${\n method.isDefault ? 'border-primary' : 'border-border-subtle'\n }`}\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-2xl\">{CARD_ICONS[method.brand?.toLowerCase() ?? 'default']}</span>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {method.brand ?? METHOD_TYPE_LABELS[method.type]} •••• {method.last4}\n </p>\n {method.isDefault && (\n <span className=\"px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded\">\n Default\n </span>\n )}\n </div>\n {method.expiryMonth && method.expiryYear && (\n <p\n className={`text-sm ${isExpired ? 'text-status-error-text' : 'text-text-secondary'}`}\n >\n {isExpired ? 'Expired' : 'Expires'} {method.expiryMonth}/{method.expiryYear}\n </p>\n )}\n </div>\n </div>\n </div>\n\n {canManage && (\n <div className=\"flex items-center gap-2 mt-4 pt-4 border-t border-border-subtle\">\n {!method.isDefault && (\n <button\n type=\"button\"\n onClick={handleSetDefault}\n disabled={isSettingDefault || isDeleting}\n className=\"text-sm text-primary hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isSettingDefault ? 'Setting default…' : 'Set as default'}\n </button>\n )}\n <button\n type=\"button\"\n onClick={handleDeleteRequest}\n disabled={isSettingDefault || isDeleting}\n className=\"text-sm text-status-error-text hover:underline ml-auto disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n )}\n\n {isConfirmingDelete && (\n <div className=\"mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm font-medium text-status-error-text\">\n Remove this payment method from your billing account?\n </p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingDelete(false)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}