@burdenoff/microfe-billing 2026.830.1 → 2026.830.2

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":"SubscriptionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscriptions List Page\n * Displays paginated subscriptions for the selected billing account\n */\n\nimport { useState, useEffect, useRef, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingAccounts, usePaginatedSubscriptions } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { BillingSubscription, SubscriptionStatus, BillingAccount } from '../../../shared/types';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor } from '../../../shared/utils/status';\nimport { StatusPill } from '../../../shared/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, EmphasisPanel, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterStatus = 'all' | 'active' | 'canceled' | 'inactive';\n\n// Statuses that count as \"inactive\" (historical/past subscriptions)\nconst INACTIVE_STATUSES = ['expired', 'upgraded'] as const;\n\nconst PAGE_SIZE = 10;\n\nexport const SubscriptionsListPage: 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 navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { orgId } = useBilling();\n\n // Fetch all billing accounts for the selector\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccounts();\n\n // State\n const {\n selectedAccountId: selectedBillingAccountId,\n setSelectedAccountId: setSelectedBillingAccountId,\n } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setCurrentPage(1);\n }, 400);\n return () => clearTimeout(debounceTimer.current);\n }, [searchQuery]);\n\n // Fetch paginated subscriptions for selected billing account\n const {\n subscriptions,\n activeSubscriptions,\n totalCount,\n totalPages,\n isLoading: isLoadingSubscriptions,\n error,\n refetch,\n } = usePaginatedSubscriptions(\n selectedBillingAccountId ?? undefined,\n currentPage,\n PAGE_SIZE,\n debouncedSearch || undefined\n );\n\n // Get selected billing account info from the list\n const selectedBillingAccount = billingAccounts.find((a) => a.id === selectedBillingAccountId);\n\n // Handle account selection\n const handleSelectAccount = (account: BillingAccount) => {\n setSelectedBillingAccountId(account.id);\n setIsAccountSelectorOpen(false);\n setCurrentPage(1); // Reset to first page when switching accounts\n };\n\n // Handle page change\n const handlePageChange = (newPage: number) => {\n setCurrentPage(newPage);\n };\n\n // Client-side status filter only; search is handled by the backend\n const filteredSubscriptions = subscriptions.filter((subscription) => {\n if (filterStatus === 'all') return true;\n if (filterStatus === 'inactive') {\n return INACTIVE_STATUSES.includes(subscription.status as (typeof INACTIVE_STATUSES)[number]);\n }\n return subscription.status === filterStatus;\n });\n\n const isLoading = isLoadingAccounts || isLoadingSubscriptions;\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\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\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n You don&apos;t have permission to view subscriptions.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !selectedBillingAccount) {\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-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 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\">Failed to load subscriptions</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 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.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.subscriptions.purpose',\n 'See every plan a billing account is subscribed to — active, canceled and expired — in one place. Use it to review what you are paying for, when each plan renews or ends, upgrade or cancel a subscription, and start a new one by browsing plans.'\n )}\n </PagePurpose>\n </div>\n\n {permissions.canCreateSubscription && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"inline-flex items-center gap-2 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 duration-200\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n New Subscription\n </button>\n )}\n </div>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-4 text-text-link\"\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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedBillingAccount?.name || 'Select Account'}\n </p>\n <p className=\"text-xs text-text-secondary\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`size-4 text-text-secondary transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\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=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"size-4 text-text-link flex-shrink-0\"\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=\"M5 13l4 4L19 7\"\n />\n </svg>\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Account Summary — primary emphasis zone */}\n {selectedBillingAccount && (\n <EmphasisPanel className=\"p-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">Account</p>\n <p className=\"text-lg font-semibold text-text-primary mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Active Subscriptions</p>\n <p className=\"text-lg font-semibold tabular-nums text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Credit Balance</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3 sm: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', 'active', 'canceled', 'inactive'] as FilterStatus[]).map((status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${\n filterStatus === status\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {status === 'inactive' ? 'Inactive' : status}\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.subscriptions.searchPlaceholder', 'Search subscriptions...')}\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\n {/* Total Count */}\n <div className=\"text-sm text-text-secondary\">\n {totalCount} subscription{totalCount !== 1 ? 's' : ''} total\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterStatus !== 'all' || debouncedSearch ? 'empty-search' : 'empty-data'}\n title={tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n description={\n filterStatus !== 'all' || debouncedSearch\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"\n }\n action={\n permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch ? (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\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 duration-200\"\n >\n Browse Plans\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {filteredSubscriptions.map((subscription) => (\n <SubscriptionCard\n key={subscription.id}\n subscription={subscription}\n onView={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}`,\n subscription.billingAccountId\n )\n )\n }\n canCancel={permissions.canCancelSubscription}\n />\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={currentPage === 1}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {\n // Show first, last, current, and adjacent pages\n const showPage =\n page === 1 || page === totalPages || Math.abs(page - currentPage) <= 1;\n\n // Show ellipsis\n const showEllipsisBefore = page === currentPage - 2 && currentPage > 3;\n const showEllipsisAfter = page === currentPage + 2 && currentPage < totalPages - 2;\n\n if (showEllipsisBefore || showEllipsisAfter) {\n return (\n <span key={page} className=\"px-2 text-text-secondary\">\n ...\n </span>\n );\n }\n\n if (!showPage) return null;\n\n return (\n <button\n type=\"button\"\n key={page}\n onClick={() => handlePageChange(page)}\n className={`px-3 py-1.5 text-sm rounded-button transition-colors ${\n page === currentPage\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n {page}\n </button>\n );\n })}\n </div>\n\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={currentPage === totalPages}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Subscription Card Component\n// ============================================================================\n\ninterface SubscriptionCardProps {\n subscription: BillingSubscription;\n onView: () => void;\n canCancel: boolean;\n}\n\nconst SubscriptionCard: FC<SubscriptionCardProps> = ({\n subscription,\n onView,\n canCancel: _canCancel,\n}) => {\n const plan = subscription.plan;\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const now = new Date();\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-5\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-4\">\n {/* Plan Icon */}\n <div className=\"size-12 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-6 text-text-link\"\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=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n\n {/* Subscription Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary\">{plan?.name || 'Unknown Plan'}</h3>\n <StatusPill status={statusColor} dot pulse={isActive}>\n {formatSubscriptionStatus(subscription.status)}\n </StatusPill>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-text-secondary\">\n <span>\n {plan ? formatCurrency(plan.price, plan.currency) : '—'} /{' '}\n {plan ? formatPlanDuration(plan.duration) : '—'}\n </span>\n <span className=\"hidden sm:inline\">•</span>\n <span className=\"hidden sm:inline\">\n Started {formatDate(subscription.startDate, 'short')}\n </span>\n </div>\n </div>\n\n {/* Right Side Info */}\n <div className=\"flex items-center gap-4\">\n {isActive && (\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{daysRemaining} days</p>\n <p className=\"text-xs text-text-secondary\">remaining</p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onView();\n }}\n className=\"px-3 py-1.5 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 View\n </button>\n </div>\n </div>\n\n {/* Additional Info */}\n {subscription.addonSubscriptions && subscription.addonSubscriptions.length > 0 && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n {subscription.addonSubscriptions.length} addon\n {subscription.addonSubscriptions.length !== 1 ? 's' : ''} attached\n </p>\n </div>\n )}\n\n {/* Cancellation Info */}\n {subscription.canceledAt && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Canceled on {formatDate(subscription.canceledAt)}\n {subscription.cancellationReason && <span>: {subscription.cancellationReason}</span>}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"SubscriptionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscriptions List Page\n * Displays paginated subscriptions for the selected billing account\n */\n\nimport { useState, useEffect, useRef, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingAccounts, usePaginatedSubscriptions } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { BillingSubscription, SubscriptionStatus, BillingAccount } from '../../../shared/types';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor } from '../../../shared/utils/status';\nimport { StatusPill } from '../../../shared/ui';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { PagePurpose, EmphasisPanel, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterStatus = 'all' | 'active' | 'canceled' | 'inactive';\n\n// Statuses that count as \"inactive\" (historical/past subscriptions)\nconst INACTIVE_STATUSES = ['expired', 'upgraded'] as const;\n\nconst PAGE_SIZE = 10;\n\nexport const SubscriptionsListPage: FC = () => {\n const tr = useTr();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { orgId } = useBilling();\n\n // Fetch all billing accounts for the selector\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccounts();\n\n // State\n const {\n selectedAccountId: selectedBillingAccountId,\n setSelectedAccountId: setSelectedBillingAccountId,\n } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setCurrentPage(1);\n }, 400);\n return () => clearTimeout(debounceTimer.current);\n }, [searchQuery]);\n\n // Fetch paginated subscriptions for selected billing account\n const {\n subscriptions,\n activeSubscriptions,\n totalCount,\n totalPages,\n isLoading: isLoadingSubscriptions,\n error,\n refetch,\n } = usePaginatedSubscriptions(\n selectedBillingAccountId ?? undefined,\n currentPage,\n PAGE_SIZE,\n debouncedSearch || undefined\n );\n\n // Get selected billing account info from the list\n const selectedBillingAccount = billingAccounts.find((a) => a.id === selectedBillingAccountId);\n\n // Handle account selection\n const handleSelectAccount = (account: BillingAccount) => {\n setSelectedBillingAccountId(account.id);\n setIsAccountSelectorOpen(false);\n setCurrentPage(1); // Reset to first page when switching accounts\n };\n\n // Handle page change\n const handlePageChange = (newPage: number) => {\n setCurrentPage(newPage);\n };\n\n // Client-side status filter only; search is handled by the backend\n const filteredSubscriptions = subscriptions.filter((subscription) => {\n if (filterStatus === 'all') return true;\n if (filterStatus === 'inactive') {\n return INACTIVE_STATUSES.includes(subscription.status as (typeof INACTIVE_STATUSES)[number]);\n }\n return subscription.status === filterStatus;\n });\n\n const isLoading = isLoadingAccounts || isLoadingSubscriptions;\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\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(\n 'billing.subscriptions.noViewPermission',\n \"You don't have permission to view subscriptions.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !selectedBillingAccount) {\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-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 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.subscriptions.failedToLoad', 'Failed to load subscriptions')}\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.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.subscriptions.purpose',\n 'See every plan a billing account is subscribed to — active, canceled and expired — in one place. Use it to review what you are paying for, when each plan renews or ends, upgrade or cancel a subscription, and start a new one by browsing plans.'\n )}\n </PagePurpose>\n </div>\n\n {permissions.canCreateSubscription && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"inline-flex items-center gap-2 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 duration-200\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n {tr('billing.subscriptions.newSubscription', 'New Subscription')}\n </button>\n )}\n </div>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-4 text-text-link\"\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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedBillingAccount?.name ||\n tr('billing.subscriptions.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`size-4 text-text-secondary transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\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=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"size-4 text-text-link flex-shrink-0\"\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=\"M5 13l4 4L19 7\"\n />\n </svg>\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Account Summary — primary emphasis zone */}\n {selectedBillingAccount && (\n <EmphasisPanel className=\"p-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.subscriptions.account', 'Account')}\n </p>\n <p className=\"text-lg font-semibold text-text-primary mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.subscriptions.activeSubscriptions', 'Active Subscriptions')}\n </p>\n <p className=\"text-lg font-semibold tabular-nums text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.subscriptions.creditBalance', 'Credit Balance')}\n </p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()}{' '}\n {tr('billing.common.credits', 'Credits')}\n </p>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3 sm: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', 'active', 'canceled', 'inactive'] as FilterStatus[]).map((status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${\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'\n ? tr('billing.subscriptions.statusAll', 'All')\n : status === 'active'\n ? tr('billing.subscriptions.statusActive', 'Active')\n : status === 'canceled'\n ? tr('billing.subscriptions.statusCanceled', 'Canceled')\n : tr('billing.subscriptions.statusInactive', 'Inactive')}\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.subscriptions.searchPlaceholder', 'Search subscriptions...')}\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\n {/* Total Count */}\n <div className=\"text-sm text-text-secondary\">\n {tr('billing.subscriptions.totalCount', '{{count}} subscription{{plural}} total', {\n count: totalCount,\n plural: totalCount !== 1 ? 's' : '',\n })}\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterStatus !== 'all' || debouncedSearch ? 'empty-search' : 'empty-data'}\n title={tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n description={\n filterStatus !== 'all' || debouncedSearch\n ? tr(\n 'billing.subscriptions.noSubscriptionsMatchFilters',\n 'No subscriptions match your filters.'\n )\n : tr(\n 'billing.subscriptions.noSubscriptionsYet',\n \"You don't have any subscriptions yet.\"\n )\n }\n action={\n permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch ? (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\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 duration-200\"\n >\n {tr('billing.subscriptions.browsePlans', 'Browse Plans')}\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {filteredSubscriptions.map((subscription) => (\n <SubscriptionCard\n key={subscription.id}\n subscription={subscription}\n onView={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}`,\n subscription.billingAccountId\n )\n )\n }\n canCancel={permissions.canCancelSubscription}\n />\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={currentPage === 1}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {\n // Show first, last, current, and adjacent pages\n const showPage =\n page === 1 || page === totalPages || Math.abs(page - currentPage) <= 1;\n\n // Show ellipsis\n const showEllipsisBefore = page === currentPage - 2 && currentPage > 3;\n const showEllipsisAfter = page === currentPage + 2 && currentPage < totalPages - 2;\n\n if (showEllipsisBefore || showEllipsisAfter) {\n return (\n <span key={page} className=\"px-2 text-text-secondary\">\n ...\n </span>\n );\n }\n\n if (!showPage) return null;\n\n return (\n <button\n type=\"button\"\n key={page}\n onClick={() => handlePageChange(page)}\n className={`px-3 py-1.5 text-sm rounded-button transition-colors ${\n page === currentPage\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n {page}\n </button>\n );\n })}\n </div>\n\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={currentPage === totalPages}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Subscription Card Component\n// ============================================================================\n\ninterface SubscriptionCardProps {\n subscription: BillingSubscription;\n onView: () => void;\n canCancel: boolean;\n}\n\nconst SubscriptionCard: FC<SubscriptionCardProps> = ({\n subscription,\n onView,\n canCancel: _canCancel,\n}) => {\n const tr = useTr();\n const plan = subscription.plan;\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const now = new Date();\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-5\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-4\">\n {/* Plan Icon */}\n <div className=\"size-12 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-6 text-text-link\"\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=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n\n {/* Subscription Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary\">\n {plan?.name || tr('billing.common.unknownPlan', 'Unknown Plan')}\n </h3>\n <StatusPill status={statusColor} dot pulse={isActive}>\n {formatSubscriptionStatus(subscription.status)}\n </StatusPill>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-text-secondary\">\n <span>\n {plan ? formatCurrency(plan.price, plan.currency) : '—'} /{' '}\n {plan ? formatPlanDuration(plan.duration) : '—'}\n </span>\n <span className=\"hidden sm:inline\">•</span>\n <span className=\"hidden sm:inline\">\n {tr('billing.subscriptions.started', 'Started {{date}}', {\n date: formatDate(subscription.startDate, 'short'),\n })}\n </span>\n </div>\n </div>\n\n {/* Right Side Info */}\n <div className=\"flex items-center gap-4\">\n {isActive && (\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.subscriptions.daysRemaining', '{{count}} days', {\n count: daysRemaining,\n })}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr('billing.subscriptions.remaining', 'remaining')}\n </p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onView();\n }}\n className=\"px-3 py-1.5 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 {tr('billing.common.view', 'View')}\n </button>\n </div>\n </div>\n\n {/* Additional Info */}\n {subscription.addonSubscriptions && subscription.addonSubscriptions.length > 0 && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.subscriptions.addonsAttached', '{{count}} addon{{plural}} attached', {\n count: subscription.addonSubscriptions.length,\n plural: subscription.addonSubscriptions.length !== 1 ? 's' : '',\n })}\n </p>\n </div>\n )}\n\n {/* Cancellation Info */}\n {subscription.canceledAt && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.subscriptions.canceledOn', 'Canceled on {{date}}', {\n date: formatDate(subscription.canceledAt),\n })}\n {subscription.cancellationReason && <span>: {subscription.cancellationReason}</span>}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,24 +1,21 @@
1
- import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
- import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
3
  //#region src/billing/shared/components/AccessDenied.tsx
4
4
  var r = ({ message: r }) => {
5
- let { t: i } = n(), a = (e, t) => {
6
- let n = i(e);
7
- return n === e ? t : n;
8
- }, o = r ?? a("billing.accessDenied.defaultMessage", "You don't have permission to access this page.");
9
- return /* @__PURE__ */ e("div", {
5
+ let i = e(), a = r ?? i("billing.accessDenied.defaultMessage", "You don't have permission to access this page.");
6
+ return /* @__PURE__ */ t("div", {
10
7
  className: "flex items-center justify-center h-full min-h-[400px]",
11
- children: /* @__PURE__ */ t("div", {
8
+ children: /* @__PURE__ */ n("div", {
12
9
  className: "text-center space-y-2",
13
10
  children: [
14
- /* @__PURE__ */ e("div", {
11
+ /* @__PURE__ */ t("div", {
15
12
  className: "size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center",
16
- children: /* @__PURE__ */ e("svg", {
13
+ children: /* @__PURE__ */ t("svg", {
17
14
  className: "size-6 text-text-secondary",
18
15
  fill: "none",
19
16
  viewBox: "0 0 24 24",
20
17
  stroke: "currentColor",
21
- children: /* @__PURE__ */ e("path", {
18
+ children: /* @__PURE__ */ t("path", {
22
19
  strokeLinecap: "round",
23
20
  strokeLinejoin: "round",
24
21
  strokeWidth: 2,
@@ -26,13 +23,13 @@ var r = ({ message: r }) => {
26
23
  })
27
24
  })
28
25
  }),
29
- /* @__PURE__ */ e("h2", {
26
+ /* @__PURE__ */ t("h2", {
30
27
  className: "text-lg font-semibold text-text-primary",
31
- children: a("billing.accessDenied.title", "Access Denied")
28
+ children: i("billing.accessDenied.title", "Access Denied")
32
29
  }),
33
- /* @__PURE__ */ e("p", {
30
+ /* @__PURE__ */ t("p", {
34
31
  className: "text-sm text-text-secondary max-w-sm",
35
- children: o
32
+ children: a
36
33
  })
37
34
  ]
38
35
  })
@@ -1 +1 @@
1
- {"version":3,"file":"AccessDenied.js","names":[],"sources":["../../../../src/billing/shared/components/AccessDenied.tsx"],"sourcesContent":["/**\n * Shared Access Denied Component\n * Displays when user lacks permission to view a page\n */\n\nimport type { FC } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface AccessDeniedProps {\n message?: string;\n}\n\nexport const AccessDenied: FC<AccessDeniedProps> = ({ message }) => {\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\n const displayMessage =\n message ??\n tr('billing.accessDenied.defaultMessage', \"You don't have permission to access this page.\");\n\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\">{displayMessage}</p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;AAYA,IAAa,KAAuC,EAAE,iBAAc;CAClE,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GAEM,IACJ,KACA,EAAG,uCAAuC,gDAAgD;CAE5F,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;IACL,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,eAAe;IAC/C,CAAA;IACJ,kBAAC,KAAD;KAAG,WAAU;eAAwC;IAAkB,CAAA;GACpE;;CACF,CAAA;AAET"}
1
+ {"version":3,"file":"AccessDenied.js","names":[],"sources":["../../../../src/billing/shared/components/AccessDenied.tsx"],"sourcesContent":["/**\n * Shared Access Denied Component\n * Displays when user lacks permission to view a page\n */\n\nimport type { FC } from 'react';\nimport { useTr } from '../hooks/useTr';\n\ninterface AccessDeniedProps {\n message?: string;\n}\n\nexport const AccessDenied: FC<AccessDeniedProps> = ({ message }) => {\n const tr = useTr();\n\n const displayMessage =\n message ??\n tr('billing.accessDenied.defaultMessage', \"You don't have permission to access this page.\");\n\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\">{displayMessage}</p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;AAYA,IAAa,KAAuC,EAAE,iBAAc;CAClE,IAAM,IAAK,EAAM,GAEX,IACJ,KACA,EAAG,uCAAuC,gDAAgD;CAE5F,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;IACL,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,eAAe;IAC/C,CAAA;IACJ,kBAAC,KAAD;KAAG,WAAU;eAAwC;IAAkB,CAAA;GACpE;;CACF,CAAA;AAET"}
@@ -1,60 +1,61 @@
1
- import { useState as e } from "react";
2
- import { Check as t, Copy as n, User2 as r } from "lucide-react";
3
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { useState as t } from "react";
3
+ import { Check as n, Copy as r, User2 as i } from "lucide-react";
4
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
4
5
  //#region src/billing/shared/components/ActorIdentity.tsx
5
- function o(e) {
6
+ function s(e) {
6
7
  let t = e.trim();
7
8
  if (!t) return "?";
8
9
  let n = t.split(/\s+/).filter(Boolean);
9
10
  return n.length === 1 ? n[0].charAt(0).toUpperCase() : (n[0].charAt(0) + n[n.length - 1].charAt(0)).toUpperCase();
10
11
  }
11
- var s = ({ profile: s, variant: c = "cell" }) => {
12
- let [l, u] = e(!1), d = () => {
13
- typeof navigator > "u" || !navigator.clipboard || navigator.clipboard.writeText(s.actorId).then(() => {
14
- u(!0), setTimeout(() => u(!1), 1500);
12
+ var c = ({ profile: c, variant: l = "cell" }) => {
13
+ let u = e(), [d, f] = t(!1), p = () => {
14
+ typeof navigator > "u" || !navigator.clipboard || navigator.clipboard.writeText(c.actorId).then(() => {
15
+ f(!0), setTimeout(() => f(!1), 1500);
15
16
  });
16
- }, f = c === "chip", p = c === "inline";
17
- return /* @__PURE__ */ a("div", {
18
- className: f ? "inline-flex items-center gap-2 rounded-full border border-border-subtle bg-bg-sunken/40 px-2 py-1" : p ? "inline-flex items-center gap-2" : "flex items-center gap-3",
17
+ }, m = l === "chip", h = l === "inline";
18
+ return /* @__PURE__ */ o("div", {
19
+ className: m ? "inline-flex items-center gap-2 rounded-full border border-border-subtle bg-bg-sunken/40 px-2 py-1" : h ? "inline-flex items-center gap-2" : "flex items-center gap-3",
19
20
  children: [
20
- /* @__PURE__ */ i("div", {
21
- className: f ? "flex size-6 items-center justify-center rounded-full bg-action-primary-bg/10 text-[10px] font-semibold text-primary" : "flex size-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary",
21
+ /* @__PURE__ */ a("div", {
22
+ className: m ? "flex size-6 items-center justify-center rounded-full bg-action-primary-bg/10 text-[10px] font-semibold text-primary" : "flex size-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary",
22
23
  "aria-hidden": "true",
23
- children: s.avatarUrl ? /* @__PURE__ */ i("img", {
24
- src: s.avatarUrl,
24
+ children: c.avatarUrl ? /* @__PURE__ */ a("img", {
25
+ src: c.avatarUrl,
25
26
  alt: "",
26
27
  className: "size-full rounded-full object-cover"
27
- }) : s.resolved ? o(s.displayName) : /* @__PURE__ */ i(r, {
28
+ }) : c.resolved ? s(c.displayName) : /* @__PURE__ */ a(i, {
28
29
  className: "size-4",
29
30
  "aria-hidden": "true"
30
31
  })
31
32
  }),
32
- /* @__PURE__ */ a("div", {
33
+ /* @__PURE__ */ o("div", {
33
34
  className: "min-w-0 flex-1",
34
- children: [/* @__PURE__ */ i("p", {
35
- className: f ? "text-xs font-medium text-text-primary" : "truncate text-sm font-medium text-text-primary",
36
- children: s.displayName
37
- }), f ? null : /* @__PURE__ */ i("p", {
35
+ children: [/* @__PURE__ */ a("p", {
36
+ className: m ? "text-xs font-medium text-text-primary" : "truncate text-sm font-medium text-text-primary",
37
+ children: c.displayName
38
+ }), m ? null : /* @__PURE__ */ a("p", {
38
39
  className: "truncate text-xs text-text-secondary",
39
- children: s.email ?? s.secondaryLabel
40
+ children: c.email ?? c.secondaryLabel
40
41
  })]
41
42
  }),
42
- !f && !p ? /* @__PURE__ */ a("details", {
43
+ !m && !h ? /* @__PURE__ */ o("details", {
43
44
  className: "group",
44
- children: [/* @__PURE__ */ i("summary", {
45
+ children: [/* @__PURE__ */ a("summary", {
45
46
  className: "cursor-pointer text-[10px] uppercase tracking-wider text-text-secondary hover:text-text-primary",
46
- children: "ID"
47
- }), /* @__PURE__ */ a("div", {
47
+ children: u("billing.common.id", "ID")
48
+ }), /* @__PURE__ */ o("div", {
48
49
  className: "mt-1 flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-2 py-1",
49
- children: [/* @__PURE__ */ i("span", {
50
+ children: [/* @__PURE__ */ a("span", {
50
51
  className: "font-mono text-[11px] text-text-secondary",
51
- children: s.actorId
52
- }), /* @__PURE__ */ i("button", {
52
+ children: c.actorId
53
+ }), /* @__PURE__ */ a("button", {
53
54
  type: "button",
54
- onClick: d,
55
+ onClick: p,
55
56
  className: "text-text-secondary transition hover:text-text-primary",
56
- "aria-label": "Copy actor ID",
57
- children: i(l ? t : n, { className: "size-3.5" })
57
+ "aria-label": u("billing.common.copyActorId", "Copy actor ID"),
58
+ children: a(d ? n : r, { className: "size-3.5" })
58
59
  })]
59
60
  })]
60
61
  }) : null
@@ -62,6 +63,6 @@ var s = ({ profile: s, variant: c = "cell" }) => {
62
63
  });
63
64
  };
64
65
  //#endregion
65
- export { s as ActorIdentity };
66
+ export { c as ActorIdentity };
66
67
 
67
68
  //# sourceMappingURL=ActorIdentity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActorIdentity.js","names":[],"sources":["../../../../src/billing/shared/components/ActorIdentity.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\nimport { Check, Copy, User2 } from 'lucide-react';\nimport type { ActorProfile } from '../hooks/useActorProfiles';\n\ninterface ActorIdentityProps {\n profile: ActorProfile;\n variant?: 'cell' | 'inline' | 'chip';\n}\n\nfunction initials(displayName: string): string {\n const cleaned = displayName.trim();\n if (!cleaned) return '?';\n const parts = cleaned.split(/\\s+/).filter(Boolean);\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase();\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();\n}\n\nexport const ActorIdentity: FC<ActorIdentityProps> = ({ profile, variant = 'cell' }) => {\n const [copied, setCopied] = useState(false);\n\n const onCopy = () => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) return;\n void navigator.clipboard.writeText(profile.actorId).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n };\n\n const isChip = variant === 'chip';\n const isInline = variant === 'inline';\n\n return (\n <div\n className={\n isChip\n ? 'inline-flex items-center gap-2 rounded-full border border-border-subtle bg-bg-sunken/40 px-2 py-1'\n : isInline\n ? 'inline-flex items-center gap-2'\n : 'flex items-center gap-3'\n }\n >\n <div\n className={\n isChip\n ? 'flex size-6 items-center justify-center rounded-full bg-action-primary-bg/10 text-[10px] font-semibold text-primary'\n : 'flex size-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary'\n }\n aria-hidden=\"true\"\n >\n {profile.avatarUrl ? (\n <img src={profile.avatarUrl} alt=\"\" className=\"size-full rounded-full object-cover\" />\n ) : profile.resolved ? (\n initials(profile.displayName)\n ) : (\n <User2 className=\"size-4\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p\n className={\n isChip\n ? 'text-xs font-medium text-text-primary'\n : 'truncate text-sm font-medium text-text-primary'\n }\n >\n {profile.displayName}\n </p>\n {!isChip ? (\n <p className=\"truncate text-xs text-text-secondary\">\n {profile.email ?? profile.secondaryLabel}\n </p>\n ) : null}\n </div>\n {!isChip && !isInline ? (\n <details className=\"group\">\n <summary className=\"cursor-pointer text-[10px] uppercase tracking-wider text-text-secondary hover:text-text-primary\">\n ID\n </summary>\n <div className=\"mt-1 flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-2 py-1\">\n <span className=\"font-mono text-[11px] text-text-secondary\">{profile.actorId}</span>\n <button\n type=\"button\"\n onClick={onCopy}\n className=\"text-text-secondary transition hover:text-text-primary\"\n aria-label=\"Copy actor ID\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n </div>\n </details>\n ) : null}\n </div>\n );\n};\n"],"mappings":";;;;AASA,SAAS,EAAS,GAA6B;CAC7C,IAAM,IAAU,EAAY,KAAK;CACjC,IAAI,CAAC,GAAS,OAAO;CACrB,IAAM,IAAQ,EAAQ,MAAM,KAAK,EAAE,OAAO,OAAO;CAEjD,OADI,EAAM,WAAW,IAAU,EAAM,GAAG,OAAO,CAAC,EAAE,YAAY,KACtD,EAAM,GAAG,OAAO,CAAC,IAAI,EAAM,EAAM,SAAS,GAAG,OAAO,CAAC,GAAG,YAAY;AAC9E;AAEA,IAAa,KAAyC,EAAE,YAAS,aAAU,aAAa;CACtF,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAK,GAEpC,UAAe;EACf,OAAO,YAAc,OAAe,CAAC,UAAU,aACnD,UAAe,UAAU,UAAU,EAAQ,OAAO,EAAE,WAAW;GAE7D,AADA,EAAU,EAAI,GACd,iBAAiB,EAAU,EAAK,GAAG,IAAI;EACzC,CAAC;CACH,GAEM,IAAS,MAAY,QACrB,IAAW,MAAY;CAE7B,OACE,kBAAC,OAAD;EACE,WACE,IACI,sGACA,IACE,mCACA;YANV;GASE,kBAAC,OAAD;IACE,WACE,IACI,wHACA;IAEN,eAAY;cAEX,EAAQ,YACP,kBAAC,OAAD;KAAK,KAAK,EAAQ;KAAW,KAAI;KAAG,WAAU;IAAuC,CAAA,IACnF,EAAQ,WACV,EAAS,EAAQ,WAAW,IAE5B,kBAAC,GAAD;KAAO,WAAU;KAAS,eAAY;IAAQ,CAAA;GAE7C,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KACE,WACE,IACI,0CACA;eAGL,EAAQ;IACR,CAAA,GACD,IAIE,OAHF,kBAAC,KAAD;KAAG,WAAU;eACV,EAAQ,SAAS,EAAQ;IACzB,CAAA,CAEF;;GACJ,CAAC,KAAU,CAAC,IACX,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,WAAD;KAAS,WAAU;eAAkG;IAE5G,CAAA,GACT,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAA6C,EAAQ;KAAc,CAAA,GACnF,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAW;gBAED,EAAT,IAAU,IAAiC,GAAlC,EAAO,WAAU,WAAY,CAAgC;KACjE,CAAA,CACL;MACE;QACP;EACD;;AAET"}
1
+ {"version":3,"file":"ActorIdentity.js","names":[],"sources":["../../../../src/billing/shared/components/ActorIdentity.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\nimport { Check, Copy, User2 } from 'lucide-react';\nimport { useTr } from '../hooks/useTr';\nimport type { ActorProfile } from '../hooks/useActorProfiles';\n\ninterface ActorIdentityProps {\n profile: ActorProfile;\n variant?: 'cell' | 'inline' | 'chip';\n}\n\nfunction initials(displayName: string): string {\n const cleaned = displayName.trim();\n if (!cleaned) return '?';\n const parts = cleaned.split(/\\s+/).filter(Boolean);\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase();\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();\n}\n\nexport const ActorIdentity: FC<ActorIdentityProps> = ({ profile, variant = 'cell' }) => {\n const tr = useTr();\n const [copied, setCopied] = useState(false);\n\n const onCopy = () => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) return;\n void navigator.clipboard.writeText(profile.actorId).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n };\n\n const isChip = variant === 'chip';\n const isInline = variant === 'inline';\n\n return (\n <div\n className={\n isChip\n ? 'inline-flex items-center gap-2 rounded-full border border-border-subtle bg-bg-sunken/40 px-2 py-1'\n : isInline\n ? 'inline-flex items-center gap-2'\n : 'flex items-center gap-3'\n }\n >\n <div\n className={\n isChip\n ? 'flex size-6 items-center justify-center rounded-full bg-action-primary-bg/10 text-[10px] font-semibold text-primary'\n : 'flex size-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary'\n }\n aria-hidden=\"true\"\n >\n {profile.avatarUrl ? (\n <img src={profile.avatarUrl} alt=\"\" className=\"size-full rounded-full object-cover\" />\n ) : profile.resolved ? (\n initials(profile.displayName)\n ) : (\n <User2 className=\"size-4\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p\n className={\n isChip\n ? 'text-xs font-medium text-text-primary'\n : 'truncate text-sm font-medium text-text-primary'\n }\n >\n {profile.displayName}\n </p>\n {!isChip ? (\n <p className=\"truncate text-xs text-text-secondary\">\n {profile.email ?? profile.secondaryLabel}\n </p>\n ) : null}\n </div>\n {!isChip && !isInline ? (\n <details className=\"group\">\n <summary className=\"cursor-pointer text-[10px] uppercase tracking-wider text-text-secondary hover:text-text-primary\">\n {tr('billing.common.id', 'ID')}\n </summary>\n <div className=\"mt-1 flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-2 py-1\">\n <span className=\"font-mono text-[11px] text-text-secondary\">{profile.actorId}</span>\n <button\n type=\"button\"\n onClick={onCopy}\n className=\"text-text-secondary transition hover:text-text-primary\"\n aria-label={tr('billing.common.copyActorId', 'Copy actor ID')}\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n </div>\n </details>\n ) : null}\n </div>\n );\n};\n"],"mappings":";;;;;AAUA,SAAS,EAAS,GAA6B;CAC7C,IAAM,IAAU,EAAY,KAAK;CACjC,IAAI,CAAC,GAAS,OAAO;CACrB,IAAM,IAAQ,EAAQ,MAAM,KAAK,EAAE,OAAO,OAAO;CAEjD,OADI,EAAM,WAAW,IAAU,EAAM,GAAG,OAAO,CAAC,EAAE,YAAY,KACtD,EAAM,GAAG,OAAO,CAAC,IAAI,EAAM,EAAM,SAAS,GAAG,OAAO,CAAC,GAAG,YAAY;AAC9E;AAEA,IAAa,KAAyC,EAAE,YAAS,aAAU,aAAa;CACtF,IAAM,IAAK,EAAM,GACX,CAAC,GAAQ,KAAa,EAAS,EAAK,GAEpC,UAAe;EACf,OAAO,YAAc,OAAe,CAAC,UAAU,aACnD,UAAe,UAAU,UAAU,EAAQ,OAAO,EAAE,WAAW;GAE7D,AADA,EAAU,EAAI,GACd,iBAAiB,EAAU,EAAK,GAAG,IAAI;EACzC,CAAC;CACH,GAEM,IAAS,MAAY,QACrB,IAAW,MAAY;CAE7B,OACE,kBAAC,OAAD;EACE,WACE,IACI,sGACA,IACE,mCACA;YANV;GASE,kBAAC,OAAD;IACE,WACE,IACI,wHACA;IAEN,eAAY;cAEX,EAAQ,YACP,kBAAC,OAAD;KAAK,KAAK,EAAQ;KAAW,KAAI;KAAG,WAAU;IAAuC,CAAA,IACnF,EAAQ,WACV,EAAS,EAAQ,WAAW,IAE5B,kBAAC,GAAD;KAAO,WAAU;KAAS,eAAY;IAAQ,CAAA;GAE7C,CAAA;GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KACE,WACE,IACI,0CACA;eAGL,EAAQ;IACR,CAAA,GACD,IAIE,OAHF,kBAAC,KAAD;KAAG,WAAU;eACV,EAAQ,SAAS,EAAQ;IACzB,CAAA,CAEF;;GACJ,CAAC,KAAU,CAAC,IACX,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,WAAD;KAAS,WAAU;eAChB,EAAG,qBAAqB,IAAI;IACtB,CAAA,GACT,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAA6C,EAAQ;KAAc,CAAA,GACnF,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,8BAA8B,eAAe;gBAElD,EAAT,IAAU,IAAiC,GAAlC,EAAO,WAAU,WAAY,CAAgC;KACjE,CAAA,CACL;MACE;QACP;EACD;;AAET"}
@@ -1,70 +1,67 @@
1
- import { statusTokens as e } from "../utils/tokens.js";
2
- import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
- import { useI18n as r } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { statusTokens as t } from "../utils/tokens.js";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  //#region src/billing/shared/components/ServerError.tsx
5
5
  var i = ({ title: i, message: a, onRetry: o, showRetry: s = !0 }) => {
6
- let { t: c } = r(), l = (e, t) => {
7
- let n = c(e);
8
- return n === e ? t : n;
9
- }, u = i ?? l("billing.serverError.title", "Server Unavailable"), d = a ?? l("billing.serverError.message", "We're having trouble connecting to the server. Please try again later."), f = e.error;
10
- return /* @__PURE__ */ t("div", {
6
+ let c = e(), l = i ?? c("billing.serverError.title", "Server Unavailable"), u = a ?? c("billing.serverError.message", "We're having trouble connecting to the server. Please try again later."), d = t.error;
7
+ return /* @__PURE__ */ n("div", {
11
8
  className: "flex items-center justify-center h-full min-h-[400px]",
12
- children: /* @__PURE__ */ n("div", {
9
+ children: /* @__PURE__ */ r("div", {
13
10
  className: "text-center space-y-4 max-w-md",
14
11
  children: [
15
- /* @__PURE__ */ t("div", {
16
- className: `size-16 mx-auto rounded-full ${f.bg} flex items-center justify-center`,
17
- children: /* @__PURE__ */ t("svg", {
18
- className: `size-8 ${f.icon}`,
12
+ /* @__PURE__ */ n("div", {
13
+ className: `size-16 mx-auto rounded-full ${d.bg} flex items-center justify-center`,
14
+ children: /* @__PURE__ */ n("svg", {
15
+ className: `size-8 ${d.icon}`,
19
16
  fill: "none",
20
17
  viewBox: "0 0 24 24",
21
18
  stroke: "currentColor",
22
19
  strokeWidth: 1.5,
23
- children: /* @__PURE__ */ t("path", {
20
+ children: /* @__PURE__ */ n("path", {
24
21
  strokeLinecap: "round",
25
22
  strokeLinejoin: "round",
26
23
  d: "M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
27
24
  })
28
25
  })
29
26
  }),
30
- /* @__PURE__ */ t("h2", {
27
+ /* @__PURE__ */ n("h2", {
31
28
  className: "text-xl font-semibold text-text-primary",
32
- children: u
29
+ children: l
33
30
  }),
34
- /* @__PURE__ */ t("p", {
31
+ /* @__PURE__ */ n("p", {
35
32
  className: "text-sm text-text-secondary",
36
- children: d
33
+ children: u
37
34
  }),
38
- /* @__PURE__ */ n("div", {
39
- className: `inline-flex items-center gap-2 px-3 py-1.5 ${f.bg} rounded-full`,
40
- children: [/* @__PURE__ */ t("span", { className: `size-2 rounded-full ${f.dot} animate-pulse` }), /* @__PURE__ */ t("span", {
41
- className: `text-xs font-medium ${f.text}`,
42
- children: l("billing.serverError.statusOffline", "Server Status: Offline")
35
+ /* @__PURE__ */ r("div", {
36
+ className: `inline-flex items-center gap-2 px-3 py-1.5 ${d.bg} rounded-full`,
37
+ children: [/* @__PURE__ */ n("span", { className: `size-2 rounded-full ${d.dot} animate-pulse` }), /* @__PURE__ */ n("span", {
38
+ className: `text-xs font-medium ${d.text}`,
39
+ children: c("billing.serverError.statusOffline", "Server Status: Offline")
43
40
  })]
44
41
  }),
45
- s && o && /* @__PURE__ */ t("div", {
42
+ s && o && /* @__PURE__ */ n("div", {
46
43
  className: "pt-2",
47
- children: /* @__PURE__ */ n("button", {
44
+ children: /* @__PURE__ */ r("button", {
48
45
  type: "button",
49
46
  onClick: o,
50
47
  className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-button transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2",
51
- children: [/* @__PURE__ */ t("svg", {
48
+ children: [/* @__PURE__ */ n("svg", {
52
49
  className: "size-4",
53
50
  fill: "none",
54
51
  viewBox: "0 0 24 24",
55
52
  stroke: "currentColor",
56
53
  strokeWidth: 2,
57
- children: /* @__PURE__ */ t("path", {
54
+ children: /* @__PURE__ */ n("path", {
58
55
  strokeLinecap: "round",
59
56
  strokeLinejoin: "round",
60
57
  d: "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
61
58
  })
62
- }), l("billing.serverError.tryAgain", "Try Again")]
59
+ }), c("billing.serverError.tryAgain", "Try Again")]
63
60
  })
64
61
  }),
65
- /* @__PURE__ */ t("p", {
62
+ /* @__PURE__ */ n("p", {
66
63
  className: "text-xs text-text-muted pt-4",
67
- children: l("billing.serverError.persistHelp", "If this issue persists, please contact support or check the system status page.")
64
+ children: c("billing.serverError.persistHelp", "If this issue persists, please contact support or check the system status page.")
68
65
  })
69
66
  ]
70
67
  })
@@ -1 +1 @@
1
- {"version":3,"file":"ServerError.js","names":[],"sources":["../../../../src/billing/shared/components/ServerError.tsx"],"sourcesContent":["/**\n * Shared Server Error Component\n * Displays when backend server is unavailable or returns 500 errors\n */\n\nimport type { FC } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { statusTokens } from '../utils/tokens';\n\ninterface ServerErrorProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n showRetry?: boolean;\n}\n\nexport const ServerError: FC<ServerErrorProps> = ({\n title,\n message,\n onRetry,\n showRetry = true,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const displayTitle = title ?? tr('billing.serverError.title', 'Server Unavailable');\n const displayMessage =\n message ??\n tr(\n 'billing.serverError.message',\n \"We're having trouble connecting to the server. Please try again later.\"\n );\n const errorTokens = statusTokens.error;\n\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4 max-w-md\">\n {/* Server Icon */}\n <div\n className={`size-16 mx-auto rounded-full ${errorTokens.bg} flex items-center justify-center`}\n >\n <svg\n className={`size-8 ${errorTokens.icon}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z\"\n />\n </svg>\n </div>\n\n {/* Error Title */}\n <h2 className=\"text-xl font-semibold text-text-primary\">{displayTitle}</h2>\n\n {/* Error Message */}\n <p className=\"text-sm text-text-secondary\">{displayMessage}</p>\n\n {/* Status Indicator */}\n <div\n className={`inline-flex items-center gap-2 px-3 py-1.5 ${errorTokens.bg} rounded-full`}\n >\n <span className={`size-2 rounded-full ${errorTokens.dot} animate-pulse`} />\n <span className={`text-xs font-medium ${errorTokens.text}`}>\n {tr('billing.serverError.statusOffline', 'Server Status: Offline')}\n </span>\n </div>\n\n {/* Retry Button */}\n {showRetry && onRetry && (\n <div className=\"pt-2\">\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-button transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99\"\n />\n </svg>\n {tr('billing.serverError.tryAgain', 'Try Again')}\n </button>\n </div>\n )}\n\n {/* Additional Help */}\n <p className=\"text-xs text-text-muted pt-4\">\n {tr(\n 'billing.serverError.persistHelp',\n 'If this issue persists, please contact support or check the system status page.'\n )}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;AAgBA,IAAa,KAAqC,EAChD,UACA,YACA,YACA,eAAY,SACR;CACJ,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GAEM,IAAe,KAAS,EAAG,6BAA6B,oBAAoB,GAC5E,IACJ,KACA,EACE,+BACA,wEACF,GACI,IAAc,EAAa;CAEjC,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KACE,WAAW,gCAAgC,EAAY,GAAG;eAE1D,kBAAC,OAAD;MACE,WAAW,UAAU,EAAY;MACjC,MAAK;MACL,SAAQ;MACR,QAAO;MACP,aAAa;gBAEb,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;IAGL,kBAAC,MAAD;KAAI,WAAU;eAA2C;IAAiB,CAAA;IAG1E,kBAAC,KAAD;KAAG,WAAU;eAA+B;IAAkB,CAAA;IAG9D,kBAAC,OAAD;KACE,WAAW,8CAA8C,EAAY,GAAG;eAD1E,CAGE,kBAAC,QAAD,EAAM,WAAW,uBAAuB,EAAY,IAAI,gBAAkB,CAAA,GAC1E,kBAAC,QAAD;MAAM,WAAW,uBAAuB,EAAY;gBACjD,EAAG,qCAAqC,wBAAwB;KAC7D,CAAA,CACH;;IAGJ,KAAa,KACZ,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBAHZ,CAKE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;OACP,aAAa;iBAEb,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,GAAE;OACH,CAAA;MACE,CAAA,GACJ,EAAG,gCAAgC,WAAW,CACzC;;IACL,CAAA;IAIP,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,mCACA,iFACF;IACC,CAAA;GACA;;CACF,CAAA;AAET"}
1
+ {"version":3,"file":"ServerError.js","names":[],"sources":["../../../../src/billing/shared/components/ServerError.tsx"],"sourcesContent":["/**\n * Shared Server Error Component\n * Displays when backend server is unavailable or returns 500 errors\n */\n\nimport type { FC } from 'react';\nimport { useTr } from '../hooks/useTr';\nimport { statusTokens } from '../utils/tokens';\n\ninterface ServerErrorProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n showRetry?: boolean;\n}\n\nexport const ServerError: FC<ServerErrorProps> = ({\n title,\n message,\n onRetry,\n showRetry = true,\n}) => {\n const tr = useTr();\n\n const displayTitle = title ?? tr('billing.serverError.title', 'Server Unavailable');\n const displayMessage =\n message ??\n tr(\n 'billing.serverError.message',\n \"We're having trouble connecting to the server. Please try again later.\"\n );\n const errorTokens = statusTokens.error;\n\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4 max-w-md\">\n {/* Server Icon */}\n <div\n className={`size-16 mx-auto rounded-full ${errorTokens.bg} flex items-center justify-center`}\n >\n <svg\n className={`size-8 ${errorTokens.icon}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z\"\n />\n </svg>\n </div>\n\n {/* Error Title */}\n <h2 className=\"text-xl font-semibold text-text-primary\">{displayTitle}</h2>\n\n {/* Error Message */}\n <p className=\"text-sm text-text-secondary\">{displayMessage}</p>\n\n {/* Status Indicator */}\n <div\n className={`inline-flex items-center gap-2 px-3 py-1.5 ${errorTokens.bg} rounded-full`}\n >\n <span className={`size-2 rounded-full ${errorTokens.dot} animate-pulse`} />\n <span className={`text-xs font-medium ${errorTokens.text}`}>\n {tr('billing.serverError.statusOffline', 'Server Status: Offline')}\n </span>\n </div>\n\n {/* Retry Button */}\n {showRetry && onRetry && (\n <div className=\"pt-2\">\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-button transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2\"\n >\n <svg\n className=\"size-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99\"\n />\n </svg>\n {tr('billing.serverError.tryAgain', 'Try Again')}\n </button>\n </div>\n )}\n\n {/* Additional Help */}\n <p className=\"text-xs text-text-muted pt-4\">\n {tr(\n 'billing.serverError.persistHelp',\n 'If this issue persists, please contact support or check the system status page.'\n )}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;AAgBA,IAAa,KAAqC,EAChD,UACA,YACA,YACA,eAAY,SACR;CACJ,IAAM,IAAK,EAAM,GAEX,IAAe,KAAS,EAAG,6BAA6B,oBAAoB,GAC5E,IACJ,KACA,EACE,+BACA,wEACF,GACI,IAAc,EAAa;CAEjC,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KACE,WAAW,gCAAgC,EAAY,GAAG;eAE1D,kBAAC,OAAD;MACE,WAAW,UAAU,EAAY;MACjC,MAAK;MACL,SAAQ;MACR,QAAO;MACP,aAAa;gBAEb,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,GAAE;MACH,CAAA;KACE,CAAA;IACF,CAAA;IAGL,kBAAC,MAAD;KAAI,WAAU;eAA2C;IAAiB,CAAA;IAG1E,kBAAC,KAAD;KAAG,WAAU;eAA+B;IAAkB,CAAA;IAG9D,kBAAC,OAAD;KACE,WAAW,8CAA8C,EAAY,GAAG;eAD1E,CAGE,kBAAC,QAAD,EAAM,WAAW,uBAAuB,EAAY,IAAI,gBAAkB,CAAA,GAC1E,kBAAC,QAAD;MAAM,WAAW,uBAAuB,EAAY;gBACjD,EAAG,qCAAqC,wBAAwB;KAC7D,CAAA,CACH;;IAGJ,KAAa,KACZ,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBAHZ,CAKE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;OACP,aAAa;iBAEb,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,GAAE;OACH,CAAA;MACE,CAAA,GACJ,EAAG,gCAAgC,WAAW,CACzC;;IACL,CAAA;IAIP,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,mCACA,iFACF;IACC,CAAA;GACA;;CACF,CAAA;AAET"}
@@ -1 +1,2 @@
1
+ import "./useTr.js";
1
2
  import "./useActorProfiles.js";
@@ -0,0 +1,14 @@
1
+ import { useCallback as e } from "react";
2
+ import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ //#region src/billing/shared/hooks/useTr.ts
4
+ function n() {
5
+ let { t: n } = t();
6
+ return e((e, t, r) => {
7
+ let i = n(e), a = i === e ? t : i;
8
+ return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
9
+ }, [n]);
10
+ }
11
+ //#endregion
12
+ export { n as useTr };
13
+
14
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../../../src/billing/shared/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('billing.dashboard.title', 'Dashboard')}</h1>;\n * return <p>{tr('billing.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;\n */\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,EAAQ;CACtB,OAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,CAAG,GAClB,IAAM,MAAe,IAAM,IAAW;EAE5C,OADK,IACE,OAAO,QAAQ,CAAM,EAAE,QAC3B,GAAK,CAAC,GAAG,OAAO,EAAI,QAAY,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,OAAO,CAAC,CAAC,GAC3E,CACF,IAJoB;CAKtB,GACA,CAAC,CAAC,CACJ;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.830.1",
3
+ "version": "2026.830.2",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [