@burdenoff/microfe-billing 2026.614.1 → 2026.623.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.
- package/dist/billing/BillingAdminRoutes.js +7 -6
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingRoot.js +1 -1
- package/dist/billing/BillingRoot.js.map +1 -1
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js +22 -22
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +31 -31
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +39 -39
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +29 -29
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +91 -91
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +40 -40
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +115 -115
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +75 -72
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +126 -126
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +45 -45
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +25 -25
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +31 -35
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js +9 -9
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +31 -31
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/PromoCodeInput.js +8 -8
- package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +9 -9
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
- package/dist/billing/modules/checkout/components/StripePaymentForm.js +21 -21
- package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +11 -11
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +31 -31
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +23 -23
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +91 -91
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +62 -62
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +72 -60
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +66 -66
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +46 -46
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +19 -19
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +37 -37
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +90 -90
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +41 -41
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +31 -31
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +35 -35
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +24 -24
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +99 -99
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +23 -23
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +22 -22
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +58 -58
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +126 -126
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +83 -79
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +16 -16
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +40 -40
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +35 -35
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +57 -57
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +35 -35
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +57 -57
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +52 -52
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +40 -39
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +22 -19
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +34 -34
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +47 -47
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +39 -39
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +52 -52
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +23 -23
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +259 -246
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +88 -85
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +51 -51
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +206 -196
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/components/ServerError.js +4 -4
- package/dist/billing/shared/components/ServerError.js.map +1 -1
- package/dist/billing/shared/components/StatCard.js +11 -33
- package/dist/billing/shared/components/StatCard.js.map +1 -1
- package/dist/billing/shared/ui/Card.js +15 -0
- package/dist/billing/shared/ui/Card.js.map +1 -0
- package/dist/billing/shared/ui/MetricCard.js +44 -0
- package/dist/billing/shared/ui/MetricCard.js.map +1 -0
- package/dist/billing/shared/ui/ProgressBar.js +30 -0
- package/dist/billing/shared/ui/ProgressBar.js.map +1 -0
- package/dist/billing/shared/ui/SectionHeader.js +2 -0
- package/dist/billing/shared/ui/StatusPill.js +28 -0
- package/dist/billing/shared/ui/StatusPill.js.map +1 -0
- package/dist/billing/shared/ui/index.js +5 -0
- package/dist/billing/shared/utils/tokens.js +13 -30
- package/dist/billing/shared/utils/tokens.js.map +1 -1
- package/package.json +1 -1
|
@@ -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, getStatusBadgeClasses } from '../../../shared/utils/status';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\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-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground max-w-sm\">\n You don'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-muted animate-pulse rounded\" />\n <div className=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-muted animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-48 bg-muted 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-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\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-foreground\">Failed to load subscriptions</h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-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 font-bold text-foreground\">\n {tr('billing.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\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-primary text-primary-foreground rounded-md hover:bg-primary/90 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=\"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 rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\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-foreground\">\n {selectedBillingAccount?.name || 'Select Account'}\n </p>\n <p className=\"text-xs text-muted-foreground\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`size-4 text-muted-foreground 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-popover border border-border rounded-lg shadow-lg 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-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"size-4 text-muted-foreground\"\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-foreground truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"size-4 text-primary 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 */}\n {selectedBillingAccount && (\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Account</p>\n <p className=\"text-lg font-semibold text-foreground mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Active Subscriptions</p>\n <p className=\"text-lg font-semibold text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div className=\"border border-border rounded-lg bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">Credit Balance</p>\n <p className=\"text-lg font-semibold text-foreground mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\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\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50 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-md transition-colors capitalize ${\n filterStatus === status\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\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-muted-foreground\"\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 rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\"\n />\n </div>\n\n {/* Total Count */}\n <div className=\"text-sm text-muted-foreground\">\n {totalCount} subscription{totalCount !== 1 ? 's' : ''} total\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"size-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-muted-foreground\"\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 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {filterStatus !== 'all' || debouncedSearch\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"}\n </p>\n {permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n </button>\n )}\n </div>\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 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted 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-muted-foreground\">\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-md transition-colors ${\n page === currentPage\n ? 'bg-primary text-primary-foreground'\n : 'border border-border hover:bg-muted'\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 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted 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 rounded-lg bg-card hover:shadow-md transition-shadow cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-6\">\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-primary/10 flex items-center justify-center shrink-0\">\n <svg\n className=\"size-6 text-primary\"\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-foreground\">{plan?.name || 'Unknown Plan'}</h3>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-muted-foreground\">\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-foreground\">{daysRemaining} days</p>\n <p className=\"text-xs text-muted-foreground\">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 text-foreground rounded-md hover:bg-muted transition-colors\"\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\">\n <p className=\"text-sm text-muted-foreground\">\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\">\n <p className=\"text-sm text-muted-foreground\">\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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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'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 </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-subtle 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-subtle 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 */}\n {selectedBillingAccount && (\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div className=\"border border-border-subtle rounded-card bg-bg-surface shadow-elevation-1 p-5\">\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 className=\"border border-border-subtle rounded-card bg-bg-surface shadow-elevation-1 p-5\">\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 className=\"border border-border-subtle rounded-card bg-bg-surface shadow-elevation-1 p-5\">\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 )}\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 <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border-subtle rounded-card\">\n <div className=\"size-12 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {filterStatus !== 'all' || debouncedSearch\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"}\n </p>\n {permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"mt-4 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 )}\n </div>\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-subtle rounded-card bg-bg-surface shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2 hover:-translate-y-0.5 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>\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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"}
|
|
@@ -41,7 +41,7 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
41
41
|
}, A = ({ features: e, isExpanded: t, onToggle: n, variant: r = "available" }) => {
|
|
42
42
|
let i = u(() => k(e), [e]), a = i.length;
|
|
43
43
|
return a === 0 ? /* @__PURE__ */ w("p", {
|
|
44
|
-
className: "text-xs text-
|
|
44
|
+
className: "text-xs text-text-secondary italic",
|
|
45
45
|
children: "No features defined"
|
|
46
46
|
}) : /* @__PURE__ */ T("div", {
|
|
47
47
|
className: "mt-2",
|
|
@@ -50,7 +50,7 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
50
50
|
onClick: (e) => {
|
|
51
51
|
e.stopPropagation(), n();
|
|
52
52
|
},
|
|
53
|
-
className: `flex items-center gap-1.5 text-xs font-medium transition-colors ${r === "current" ? "text-
|
|
53
|
+
className: `flex items-center gap-1.5 text-xs font-medium transition-colors ${r === "current" ? "text-text-secondary hover:text-text-primary" : "text-text-link hover:text-text-link"}`,
|
|
54
54
|
children: [
|
|
55
55
|
/* @__PURE__ */ w(x, { className: "size-3" }),
|
|
56
56
|
/* @__PURE__ */ T("span", { children: [
|
|
@@ -67,21 +67,21 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
67
67
|
if (!t) return null;
|
|
68
68
|
let n = t.limits || {}, i = Object.entries(n);
|
|
69
69
|
return /* @__PURE__ */ T("div", {
|
|
70
|
-
className: `rounded-
|
|
70
|
+
className: `rounded-button p-2 ${r === "current" ? "bg-bg-sunken" : "bg-[var(--color-accent-soft)]"}`,
|
|
71
71
|
children: [
|
|
72
72
|
/* @__PURE__ */ w("p", {
|
|
73
|
-
className: "text-xs font-medium text-
|
|
73
|
+
className: "text-xs font-medium text-text-primary",
|
|
74
74
|
children: t.name
|
|
75
75
|
}),
|
|
76
76
|
i.length > 0 && /* @__PURE__ */ w("div", {
|
|
77
77
|
className: "mt-1 flex flex-wrap gap-x-3 gap-y-1",
|
|
78
78
|
children: i.map(([e, t]) => /* @__PURE__ */ T("div", {
|
|
79
|
-
className: "flex items-center gap-1 text-xs text-
|
|
79
|
+
className: "flex items-center gap-1 text-xs text-text-secondary",
|
|
80
80
|
children: [/* @__PURE__ */ T("span", {
|
|
81
81
|
className: "opacity-70",
|
|
82
82
|
children: [O(e), ":"]
|
|
83
83
|
}), /* @__PURE__ */ w("span", {
|
|
84
|
-
className: "font-medium text-
|
|
84
|
+
className: "font-medium text-text-primary flex items-center gap-0.5",
|
|
85
85
|
children: t === -1 || t === Infinity ? /* @__PURE__ */ T(C, { children: [/* @__PURE__ */ w(v, { className: "size-3" }), /* @__PURE__ */ w("span", {
|
|
86
86
|
className: "sr-only",
|
|
87
87
|
children: "Unlimited"
|
|
@@ -133,15 +133,15 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
133
133
|
className: "text-center space-y-2",
|
|
134
134
|
children: [
|
|
135
135
|
/* @__PURE__ */ w("div", {
|
|
136
|
-
className: "size-12 mx-auto rounded-full bg-
|
|
137
|
-
children: /* @__PURE__ */ w(f, { className: "size-6 text-
|
|
136
|
+
className: "size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center",
|
|
137
|
+
children: /* @__PURE__ */ w(f, { className: "size-6 text-text-secondary" })
|
|
138
138
|
}),
|
|
139
139
|
/* @__PURE__ */ w("h2", {
|
|
140
|
-
className: "text-lg font-semibold text-
|
|
140
|
+
className: "text-lg font-semibold text-text-primary",
|
|
141
141
|
children: "Access Denied"
|
|
142
142
|
}),
|
|
143
143
|
/* @__PURE__ */ w("p", {
|
|
144
|
-
className: "text-sm text-
|
|
144
|
+
className: "text-sm text-text-secondary max-w-sm",
|
|
145
145
|
children: "You don't have permission to change subscriptions."
|
|
146
146
|
})
|
|
147
147
|
]
|
|
@@ -151,8 +151,8 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
151
151
|
className: "flex items-center justify-center min-h-[400px]",
|
|
152
152
|
children: /* @__PURE__ */ T("div", {
|
|
153
153
|
className: "text-center space-y-4",
|
|
154
|
-
children: [/* @__PURE__ */ w(y, { className: "size-8 animate-spin mx-auto text-
|
|
155
|
-
className: "text-
|
|
154
|
+
children: [/* @__PURE__ */ w(y, { className: "size-8 animate-spin mx-auto text-text-link" }), /* @__PURE__ */ w("p", {
|
|
155
|
+
className: "text-text-secondary",
|
|
156
156
|
children: "Loading plans…"
|
|
157
157
|
})]
|
|
158
158
|
})
|
|
@@ -162,15 +162,15 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
162
162
|
children: /* @__PURE__ */ T("div", {
|
|
163
163
|
className: "text-center space-y-4",
|
|
164
164
|
children: [
|
|
165
|
-
/* @__PURE__ */ w(f, { className: "size-12 mx-auto text-
|
|
165
|
+
/* @__PURE__ */ w(f, { className: "size-12 mx-auto text-status-error-text" }),
|
|
166
166
|
/* @__PURE__ */ w("h2", {
|
|
167
|
-
className: "text-lg font-semibold text-
|
|
167
|
+
className: "text-lg font-semibold text-text-primary",
|
|
168
168
|
children: "Subscription not found"
|
|
169
169
|
}),
|
|
170
170
|
/* @__PURE__ */ w("button", {
|
|
171
171
|
type: "button",
|
|
172
172
|
onClick: () => x(c("/subscriptions", M)),
|
|
173
|
-
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-
|
|
173
|
+
className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button",
|
|
174
174
|
children: _("billing.subscriptions.backToSubscriptions", "Back to Subscriptions")
|
|
175
175
|
})
|
|
176
176
|
]
|
|
@@ -181,24 +181,24 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
181
181
|
children: [/* @__PURE__ */ T("button", {
|
|
182
182
|
type: "button",
|
|
183
183
|
onClick: () => x(c(`/subscriptions/${v}`, M)),
|
|
184
|
-
className: "flex items-center gap-2 text-sm text-
|
|
184
|
+
className: "flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary",
|
|
185
185
|
children: [/* @__PURE__ */ w(p, { className: "size-4" }), _("billing.subscriptions.backToSubscription", "Back to Subscription")]
|
|
186
186
|
}), /* @__PURE__ */ T("div", {
|
|
187
187
|
className: "text-center space-y-4 py-12",
|
|
188
188
|
children: [
|
|
189
189
|
/* @__PURE__ */ w(f, { className: "size-12 mx-auto text-status-warning-text" }),
|
|
190
190
|
/* @__PURE__ */ w("h2", {
|
|
191
|
-
className: "text-lg font-semibold text-
|
|
191
|
+
className: "text-lg font-semibold text-text-primary",
|
|
192
192
|
children: _("billing.subscriptions.cannotChangePlan", "Cannot Change Plan")
|
|
193
193
|
}),
|
|
194
194
|
/* @__PURE__ */ w("p", {
|
|
195
|
-
className: "text-sm text-
|
|
195
|
+
className: "text-sm text-text-secondary max-w-md mx-auto",
|
|
196
196
|
children: _("billing.subscriptions.notActiveMessage", "This subscription is no longer active. You can only change plans for active subscriptions.")
|
|
197
197
|
}),
|
|
198
198
|
/* @__PURE__ */ w("button", {
|
|
199
199
|
type: "button",
|
|
200
200
|
onClick: () => x(c("/subscriptions", M)),
|
|
201
|
-
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-
|
|
201
|
+
className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button",
|
|
202
202
|
children: _("billing.subscriptions.viewAll", "View All Subscriptions")
|
|
203
203
|
})
|
|
204
204
|
]
|
|
@@ -209,18 +209,18 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
209
209
|
children: [/* @__PURE__ */ T("button", {
|
|
210
210
|
type: "button",
|
|
211
211
|
onClick: () => x(c(`/subscriptions/${v}`, M)),
|
|
212
|
-
className: "flex items-center gap-2 text-sm text-
|
|
212
|
+
className: "flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary",
|
|
213
213
|
children: [/* @__PURE__ */ w(p, { className: "size-4" }), _("billing.subscriptions.backToSubscription", "Back to Subscription")]
|
|
214
214
|
}), /* @__PURE__ */ T("div", {
|
|
215
215
|
className: "text-center space-y-4 py-12",
|
|
216
216
|
children: [
|
|
217
|
-
/* @__PURE__ */ w(b, { className: "size-12 mx-auto text-
|
|
217
|
+
/* @__PURE__ */ w(b, { className: "size-12 mx-auto text-text-secondary" }),
|
|
218
218
|
/* @__PURE__ */ w("h2", {
|
|
219
|
-
className: "text-lg font-semibold text-
|
|
219
|
+
className: "text-lg font-semibold text-text-primary",
|
|
220
220
|
children: _("billing.subscriptions.noPlansAvailable", "No Other Plans Available")
|
|
221
221
|
}),
|
|
222
222
|
/* @__PURE__ */ w("p", {
|
|
223
|
-
className: "text-sm text-
|
|
223
|
+
className: "text-sm text-text-secondary max-w-md mx-auto",
|
|
224
224
|
children: _("billing.subscriptions.noPlansMessage", "There are no other active plans available for this product.")
|
|
225
225
|
})
|
|
226
226
|
]
|
|
@@ -234,15 +234,15 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
234
234
|
/* @__PURE__ */ T("button", {
|
|
235
235
|
type: "button",
|
|
236
236
|
onClick: () => x(c(`/subscriptions/${v}`, M)),
|
|
237
|
-
className: "flex items-center gap-2 text-sm text-
|
|
237
|
+
className: "flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary mb-4",
|
|
238
238
|
children: [/* @__PURE__ */ w(p, { className: "size-4" }), _("billing.subscriptions.backToSubscription", "Back to Subscription")]
|
|
239
239
|
}),
|
|
240
240
|
/* @__PURE__ */ w("h1", {
|
|
241
|
-
className: "text-2xl font-
|
|
241
|
+
className: "text-2xl md:text-3xl font-semibold tracking-tight text-text-primary",
|
|
242
242
|
children: _("billing.subscriptions.changeYourPlan", "Change Your Plan")
|
|
243
243
|
}),
|
|
244
244
|
/* @__PURE__ */ T("p", {
|
|
245
|
-
className: "text-
|
|
245
|
+
className: "text-text-secondary mt-1",
|
|
246
246
|
children: [
|
|
247
247
|
"Switch from ",
|
|
248
248
|
/* @__PURE__ */ w("span", {
|
|
@@ -254,18 +254,18 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
254
254
|
})
|
|
255
255
|
] }),
|
|
256
256
|
/* @__PURE__ */ w("div", {
|
|
257
|
-
className: "bg-
|
|
257
|
+
className: "bg-bg-sunken border border-border-subtle rounded-lg p-4",
|
|
258
258
|
children: /* @__PURE__ */ T("div", {
|
|
259
259
|
className: "flex items-start justify-between",
|
|
260
260
|
children: [/* @__PURE__ */ T("div", {
|
|
261
261
|
className: "flex-1",
|
|
262
262
|
children: [
|
|
263
263
|
/* @__PURE__ */ w("p", {
|
|
264
|
-
className: "text-sm text-
|
|
264
|
+
className: "text-sm text-text-secondary",
|
|
265
265
|
children: "Current Plan"
|
|
266
266
|
}),
|
|
267
267
|
/* @__PURE__ */ w("p", {
|
|
268
|
-
className: "font-semibold text-
|
|
268
|
+
className: "font-semibold text-text-primary",
|
|
269
269
|
children: N.plan?.name
|
|
270
270
|
}),
|
|
271
271
|
/* @__PURE__ */ w(A, {
|
|
@@ -278,12 +278,12 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
278
278
|
}), /* @__PURE__ */ T("div", {
|
|
279
279
|
className: "text-right shrink-0 ml-4",
|
|
280
280
|
children: [/* @__PURE__ */ w("p", {
|
|
281
|
-
className: "text-sm text-
|
|
281
|
+
className: "text-sm text-text-secondary",
|
|
282
282
|
children: "Price"
|
|
283
283
|
}), /* @__PURE__ */ T("p", {
|
|
284
|
-
className: "font-semibold text-
|
|
284
|
+
className: "font-semibold text-text-primary",
|
|
285
285
|
children: [n(N.plan?.price || 0, N.plan?.currency || "USD"), /* @__PURE__ */ T("span", {
|
|
286
|
-
className: "text-sm font-normal text-
|
|
286
|
+
className: "text-sm font-normal text-text-secondary",
|
|
287
287
|
children: [
|
|
288
288
|
" ",
|
|
289
289
|
"/ ",
|
|
@@ -297,14 +297,14 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
297
297
|
/* @__PURE__ */ T("div", {
|
|
298
298
|
className: "space-y-3",
|
|
299
299
|
children: [/* @__PURE__ */ w("h2", {
|
|
300
|
-
className: "text-lg font-semibold text-
|
|
300
|
+
className: "text-lg font-semibold text-text-primary",
|
|
301
301
|
children: "Select New Plan"
|
|
302
302
|
}), /* @__PURE__ */ w("div", {
|
|
303
303
|
className: "grid gap-3",
|
|
304
304
|
children: U.map((e) => {
|
|
305
305
|
let t = e.price - (N.plan?.price || 0), i = L === e.id, a = e.features || [], o = z[e.id] || !1;
|
|
306
306
|
return /* @__PURE__ */ T("div", {
|
|
307
|
-
className: `rounded-lg border transition-all ${i ? "border-
|
|
307
|
+
className: `rounded-lg border transition-all ${i ? "border-border-strong bg-[var(--color-accent-soft)] ring-2 ring-[var(--color-focus-ring)]" : "border-border-subtle bg-bg-surface hover:border-border-strong"}`,
|
|
308
308
|
children: [/* @__PURE__ */ w("button", {
|
|
309
309
|
type: "button",
|
|
310
310
|
onClick: () => R(e.id),
|
|
@@ -314,12 +314,12 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
314
314
|
children: [/* @__PURE__ */ T("div", {
|
|
315
315
|
className: "flex items-start gap-3",
|
|
316
316
|
children: [/* @__PURE__ */ w("div", {
|
|
317
|
-
className: `size-5 mt-0.5 rounded-full border-2 flex items-center justify-center shrink-0 ${i ? "border-
|
|
318
|
-
children: i && /* @__PURE__ */ w(h, { className: "size-3 text-primary-
|
|
317
|
+
className: `size-5 mt-0.5 rounded-full border-2 flex items-center justify-center shrink-0 ${i ? "border-border-strong bg-action-primary-bg" : "border-border-default"}`,
|
|
318
|
+
children: i && /* @__PURE__ */ w(h, { className: "size-3 text-action-primary-text" })
|
|
319
319
|
}), /* @__PURE__ */ T("div", {
|
|
320
320
|
className: "flex-1",
|
|
321
321
|
children: [/* @__PURE__ */ w("p", {
|
|
322
|
-
className: "font-semibold text-
|
|
322
|
+
className: "font-semibold text-text-primary",
|
|
323
323
|
children: e.name
|
|
324
324
|
}), t !== 0 && /* @__PURE__ */ T("p", {
|
|
325
325
|
className: `text-sm ${t > 0 ? "text-status-warning-text" : "text-status-success-text"}`,
|
|
@@ -335,10 +335,10 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
335
335
|
}), /* @__PURE__ */ T("div", {
|
|
336
336
|
className: "text-right shrink-0 ml-4",
|
|
337
337
|
children: [/* @__PURE__ */ w("p", {
|
|
338
|
-
className: "font-bold text-
|
|
338
|
+
className: "font-bold text-text-primary",
|
|
339
339
|
children: n(e.price, e.currency)
|
|
340
340
|
}), /* @__PURE__ */ T("p", {
|
|
341
|
-
className: "text-sm text-
|
|
341
|
+
className: "text-sm text-text-secondary",
|
|
342
342
|
children: ["/ ", r(e.duration)]
|
|
343
343
|
})]
|
|
344
344
|
})]
|
|
@@ -359,7 +359,7 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
359
359
|
W && (() => {
|
|
360
360
|
let e = k(J).length, t = k(W.features || []).length;
|
|
361
361
|
return /* @__PURE__ */ T("div", {
|
|
362
|
-
className: "bg-
|
|
362
|
+
className: "bg-bg-surface border border-border-subtle rounded-lg p-4 space-y-4",
|
|
363
363
|
children: [
|
|
364
364
|
/* @__PURE__ */ T("div", {
|
|
365
365
|
className: "flex items-center justify-center gap-4 py-2",
|
|
@@ -368,15 +368,15 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
368
368
|
className: "text-center",
|
|
369
369
|
children: [
|
|
370
370
|
/* @__PURE__ */ w("p", {
|
|
371
|
-
className: "text-xs text-
|
|
371
|
+
className: "text-xs text-text-secondary",
|
|
372
372
|
children: "From"
|
|
373
373
|
}),
|
|
374
374
|
/* @__PURE__ */ w("p", {
|
|
375
|
-
className: "font-medium text-
|
|
375
|
+
className: "font-medium text-text-primary",
|
|
376
376
|
children: N.plan?.name
|
|
377
377
|
}),
|
|
378
378
|
/* @__PURE__ */ T("p", {
|
|
379
|
-
className: "text-xs text-
|
|
379
|
+
className: "text-xs text-text-secondary",
|
|
380
380
|
children: [
|
|
381
381
|
e,
|
|
382
382
|
" feature",
|
|
@@ -385,20 +385,20 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
385
385
|
})
|
|
386
386
|
]
|
|
387
387
|
}),
|
|
388
|
-
/* @__PURE__ */ w(m, { className: "size-5 text-
|
|
388
|
+
/* @__PURE__ */ w(m, { className: "size-5 text-text-secondary" }),
|
|
389
389
|
/* @__PURE__ */ T("div", {
|
|
390
390
|
className: "text-center",
|
|
391
391
|
children: [
|
|
392
392
|
/* @__PURE__ */ w("p", {
|
|
393
|
-
className: "text-xs text-
|
|
393
|
+
className: "text-xs text-text-secondary",
|
|
394
394
|
children: "To"
|
|
395
395
|
}),
|
|
396
396
|
/* @__PURE__ */ w("p", {
|
|
397
|
-
className: "font-medium text-
|
|
397
|
+
className: "font-medium text-text-link",
|
|
398
398
|
children: W.name
|
|
399
399
|
}),
|
|
400
400
|
/* @__PURE__ */ T("p", {
|
|
401
|
-
className: "text-xs text-
|
|
401
|
+
className: "text-xs text-text-secondary",
|
|
402
402
|
children: [
|
|
403
403
|
t,
|
|
404
404
|
" feature",
|
|
@@ -410,20 +410,20 @@ var D = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
410
410
|
]
|
|
411
411
|
}),
|
|
412
412
|
/* @__PURE__ */ w("p", {
|
|
413
|
-
className: "text-sm text-
|
|
413
|
+
className: "text-sm text-text-secondary text-center",
|
|
414
414
|
children: "You'll be taken to checkout to select payment method and complete the change."
|
|
415
415
|
}),
|
|
416
416
|
/* @__PURE__ */ T("div", {
|
|
417
|
-
className: "flex items-center justify-end gap-3 pt-2 border-t border-border",
|
|
417
|
+
className: "flex items-center justify-end gap-3 pt-2 border-t border-border-subtle",
|
|
418
418
|
children: [/* @__PURE__ */ w("button", {
|
|
419
419
|
type: "button",
|
|
420
420
|
onClick: () => x(c(`/subscriptions/${v}`, M)),
|
|
421
|
-
className: "px-4 py-2 text-sm font-medium text-
|
|
421
|
+
className: "px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary",
|
|
422
422
|
children: "Cancel"
|
|
423
423
|
}), /* @__PURE__ */ w("button", {
|
|
424
424
|
type: "button",
|
|
425
425
|
onClick: q,
|
|
426
|
-
className: "px-6 py-2 text-sm font-medium bg-primary text-primary-
|
|
426
|
+
className: "px-6 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors",
|
|
427
427
|
children: G ? "Downgrade Plan" : "Upgrade Plan"
|
|
428
428
|
})]
|
|
429
429
|
})
|