@burdenoff/microfe-billing 2026.526.1 → 2026.528.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/billing/BillingAdminRoutes.js +1 -1
  2. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  3. package/dist/billing/BillingUserRoutes.js +1 -1
  4. package/dist/billing/BillingUserRoutes.js.map +1 -1
  5. package/dist/billing/modules/addons/components/FloatingCartDrawer.js +15 -8
  6. package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
  7. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +13 -6
  8. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  9. package/dist/billing/modules/addons/pages/AddonCreatePage.js +1 -1
  10. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonDetailPage.js +8 -4
  12. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -2
  14. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  15. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +44 -23
  16. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  17. package/dist/billing/modules/addons/pages/AddonsListPage.js +60 -31
  18. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  19. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +9 -9
  20. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  21. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +8 -8
  22. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +16 -10
  24. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  25. package/dist/billing/modules/billing/pages/InvoicesListPage.js +24 -16
  26. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  27. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +5 -1
  28. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  29. package/dist/billing/modules/billing/pages/RefundsPage.js +46 -29
  30. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  31. package/dist/billing/modules/billing/pages/TransactionsPage.js +34 -22
  32. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  33. package/dist/billing/modules/checkout/components/BillingAccountSelector.js +10 -10
  34. package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
  35. package/dist/billing/modules/checkout/components/CreditsAmountInput.js +3 -3
  36. package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
  37. package/dist/billing/modules/checkout/components/NativeCheckoutView.js +7 -0
  38. package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
  39. package/dist/billing/modules/checkout/components/OrderSummary.js +13 -13
  40. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  41. package/dist/billing/modules/checkout/components/PromoCodeInput.js +5 -5
  42. package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
  43. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
  44. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  45. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +4 -4
  46. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
  47. package/dist/billing/modules/checkout/components/StripePaymentForm.js +11 -11
  48. package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
  49. package/dist/billing/modules/checkout/components/SubscriptionSelector.js +3 -3
  50. package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
  51. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +8 -8
  52. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
  53. package/dist/billing/modules/checkout/pages/CheckoutPage.js +61 -52
  54. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  55. package/dist/billing/modules/coupons/pages/CouponDetailPage.js +22 -15
  56. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  57. package/dist/billing/modules/coupons/pages/CouponsListPage.js +30 -21
  58. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  59. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +9 -8
  60. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  61. package/dist/billing/modules/coupons/pages/EditCouponPage.js +11 -8
  62. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  63. package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
  64. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  65. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +22 -17
  66. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  67. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +15 -8
  68. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  69. package/dist/billing/modules/credits/pages/CreditsPage.js +17 -11
  70. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  71. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +15 -11
  72. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
  73. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +21 -17
  74. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  75. package/dist/billing/modules/dashboard/components/BillingAccountCard.js +14 -11
  76. package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
  77. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +32 -26
  78. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  79. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +14 -10
  80. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
  81. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +18 -11
  82. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  83. package/dist/billing/modules/dashboard/pages/OverviewPage.js +44 -32
  84. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  85. package/dist/billing/modules/discounts/pages/DiscountsListPage.js +35 -26
  86. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  87. package/dist/billing/modules/earnings/pages/EarningsPage.js +15 -15
  88. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  89. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +11 -11
  90. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  91. package/dist/billing/modules/earnings/pages/PayoutsPage.js +5 -5
  92. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  93. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +9 -4
  94. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  95. package/dist/billing/modules/plans/pages/PlanCreatePage.js +1 -1
  96. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  97. package/dist/billing/modules/plans/pages/PlanDetailPage.js +13 -8
  98. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  99. package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -2
  100. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  101. package/dist/billing/modules/plans/pages/PlansBrowsePage.js +14 -7
  102. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  103. package/dist/billing/modules/plans/pages/PlansListPage.js +23 -9
  104. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  105. package/dist/billing/modules/quota/pages/QuotaPage.js +11 -7
  106. package/dist/billing/modules/quota/pages/QuotaPage.js.map +1 -1
  107. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
  108. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  109. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +6 -2
  110. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  111. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +5 -4
  112. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  113. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +26 -12
  114. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  115. package/dist/billing/modules/settings/pages/SettingsPage.js +356 -349
  116. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  117. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +157 -123
  118. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  119. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +13 -8
  120. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  121. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +15 -7
  122. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  123. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +30 -20
  124. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  125. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +25 -16
  126. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  127. package/dist/billing/modules/usage/pages/UsagePage.js +50 -34
  128. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  129. package/dist/billing/shared/components/AccessDenied.js +2 -2
  130. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  131. package/dist/billing/shared/components/ActorIdentity.js +67 -0
  132. package/dist/billing/shared/components/ActorIdentity.js.map +1 -0
  133. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +6 -2
  134. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  135. package/dist/billing/shared/components/EmptyState.js +7 -21
  136. package/dist/billing/shared/components/EmptyState.js.map +1 -1
  137. package/dist/billing/shared/components/PageHeader.js +7 -19
  138. package/dist/billing/shared/components/PageHeader.js.map +1 -1
  139. package/dist/billing/shared/components/ServerError.js +5 -4
  140. package/dist/billing/shared/components/ServerError.js.map +1 -1
  141. package/dist/billing/shared/components/index.js +1 -0
  142. package/dist/billing/shared/hooks/index.js +1 -0
  143. package/dist/billing/shared/hooks/useActorProfiles.js +113 -0
  144. package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -0
  145. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"PlanEditPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanEditPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Edit Page\n * Form to edit an existing plan\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { isServerError } from '../../../shared/utils';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdatePlanInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const PlanEditPage: 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 { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { plan, isLoading: isPlanLoading, error: planError, refetch } = usePlan(planId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updatePlan, isUpdating } = usePlanMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [isActive, setIsActive] = useState(true);\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Populate form when plan data loads\n useEffect(() => {\n if (plan) {\n setName(plan.name ?? '');\n setPrice(String(plan.price ?? ''));\n setCurrency(plan.currency ?? 'USD');\n setDuration(plan.duration ?? PlanDuration.MONTHLY);\n setIsActive(plan.isActive ?? true);\n // Extract quota allocations from features\n if (plan.features && plan.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of plan.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const value = limits?.value ?? 1;\n quotaMap.set(quotaId, (quotaMap.get(quotaId) ?? 0) + value);\n }\n }\n\n for (const [quotaId, quantity] of quotaMap) {\n allocations.push({ quotaId, quantity });\n }\n\n setQuotaAllocations(allocations);\n }\n }\n }, [plan]);\n\n // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr('billing.plans.noEditPermission', \"You don't have permission to edit plans.\")}\n />\n );\n }\n\n // Loading state\n if (isPlanLoading || isQuotasLoading) {\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=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (planError && isServerError(planError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableLoadPlan',\n 'Unable to load plan. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!plan) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <button\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Plan name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: UpdatePlanInput = {\n id: planId!,\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n isActive,\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await updatePlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/plans/${planId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Plan: ${plan.name}`}\n description={tr(\n 'billing.plans.editPlanDesc',\n 'Update the plan details and quota allocations'\n )}\n />\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.plans.updateSuccess', 'Plan updated successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.planNameLabel', 'Plan Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder={tr('billing.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"price\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"49.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"w-4 h-4 rounded border-border text-primary focus:ring-primary\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-foreground\">\n {tr('billing.plans.planIsActive', 'Plan is active')}\n </label>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.quotaAllocations', 'Quota Allocations')}\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n className=\"px-3 py-1 text-sm font-medium text-primary hover:bg-primary/10 rounded-md transition-colors\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.plans.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n const totalValue = limitValue * allocation.quantity;\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border rounded-lg bg-muted/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-primary ${\n allocation.quotaId === '' ? 'text-muted-foreground' : 'text-foreground'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.plans.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"w-5 h-5\"\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 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"text-muted-foreground\">\n <span className=\"capitalize\">Type: {limitType}</span>\n <span className=\"mx-2\">•</span>\n <span>\n Value per unit:{' '}\n <span className=\"font-medium text-foreground\">\n {limitValue.toLocaleString()}\n </span>\n </span>\n </div>\n </div>\n\n {/* Quantity & Total */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.quantity', 'Quantity')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) =>\n handleQuotaChange(index, 'quantity', e.target.value)\n }\n className=\"w-24 px-3 py-1.5 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary text-center\"\n />\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">=</span>\n <span className=\"font-semibold text-status-success-text\">\n {totalValue.toLocaleString()} total\n </span>\n </div>\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isUpdating}\n className=\"px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isUpdating\n ? tr('billing.plans.saving', 'Saving...')\n : tr('billing.plans.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/plans/${planId}`)}\n className=\"px-6 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlanEditPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanEditPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Edit Page\n * Form to edit an existing plan\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { isServerError } from '../../../shared/utils';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdatePlanInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const PlanEditPage: 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 { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { plan, isLoading: isPlanLoading, error: planError, refetch } = usePlan(planId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updatePlan, isUpdating } = usePlanMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [isActive, setIsActive] = useState(true);\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Populate form when plan data loads\n useEffect(() => {\n if (plan) {\n setName(plan.name ?? '');\n setPrice(String(plan.price ?? ''));\n setCurrency(plan.currency ?? 'USD');\n setDuration(plan.duration ?? PlanDuration.MONTHLY);\n setIsActive(plan.isActive ?? true);\n // Extract quota allocations from features\n if (plan.features && plan.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of plan.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const value = limits?.value ?? 1;\n quotaMap.set(quotaId, (quotaMap.get(quotaId) ?? 0) + value);\n }\n }\n\n for (const [quotaId, quantity] of quotaMap) {\n allocations.push({ quotaId, quantity });\n }\n\n setQuotaAllocations(allocations);\n }\n }\n }, [plan]);\n\n // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr('billing.plans.noEditPermission', \"You don't have permission to edit plans.\")}\n />\n );\n }\n\n // Loading state\n if (isPlanLoading || isQuotasLoading) {\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=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (planError && isServerError(planError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableLoadPlan',\n 'Unable to load plan. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!plan) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Plan name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: UpdatePlanInput = {\n id: planId!,\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n isActive,\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await updatePlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/plans/${planId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Plan: ${plan.name}`}\n description={tr(\n 'billing.plans.editPlanDesc',\n 'Update the plan details and quota allocations'\n )}\n />\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.plans.updateSuccess', 'Plan updated successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.planNameLabel', 'Plan Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder={tr('billing.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"price\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"49.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border text-primary focus:ring-primary\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-foreground\">\n {tr('billing.plans.planIsActive', 'Plan is active')}\n </label>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.quotaAllocations', 'Quota Allocations')}\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n className=\"px-3 py-1 text-sm font-medium text-primary hover:bg-primary/10 rounded-md transition-colors\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.plans.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n const totalValue = limitValue * allocation.quantity;\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border rounded-lg bg-muted/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-primary ${\n allocation.quotaId === '' ? 'text-muted-foreground' : 'text-foreground'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.plans.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\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 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"text-muted-foreground\">\n <span className=\"capitalize\">Type: {limitType}</span>\n <span className=\"mx-2\">•</span>\n <span>\n Value per unit:{' '}\n <span className=\"font-medium text-foreground\">\n {limitValue.toLocaleString()}\n </span>\n </span>\n </div>\n </div>\n\n {/* Quantity & Total */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.quantity', 'Quantity')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) =>\n handleQuotaChange(index, 'quantity', e.target.value)\n }\n className=\"w-24 px-3 py-1.5 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary text-center\"\n />\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">=</span>\n <span className=\"font-semibold text-status-success-text\">\n {totalValue.toLocaleString()} total\n </span>\n </div>\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isUpdating}\n className=\"px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isUpdating\n ? tr('billing.plans.saving', 'Saving...')\n : tr('billing.plans.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/plans/${planId}`)}\n className=\"px-6 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -84,6 +84,7 @@ var h = () => {
84
84
  "monthly",
85
85
  "yearly"
86
86
  ].map((e) => /* @__PURE__ */ d("button", {
87
+ type: "button",
87
88
  onClick: () => S(e),
88
89
  className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${x === e ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
89
90
  children: e === "all" ? "All" : e === "monthly" ? "Monthly" : "Yearly"
@@ -91,11 +92,12 @@ var h = () => {
91
92
  }), /* @__PURE__ */ f("div", {
92
93
  className: "ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50",
93
94
  children: [/* @__PURE__ */ d("button", {
95
+ type: "button",
94
96
  onClick: () => b("grid"),
95
97
  className: `p-1.5 rounded-md transition-colors ${y === "grid" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
96
98
  title: "Grid view",
97
99
  children: /* @__PURE__ */ d("svg", {
98
- className: "w-4 h-4",
100
+ className: "size-4",
99
101
  fill: "none",
100
102
  viewBox: "0 0 24 24",
101
103
  stroke: "currentColor",
@@ -107,11 +109,12 @@ var h = () => {
107
109
  })
108
110
  })
109
111
  }), /* @__PURE__ */ d("button", {
112
+ type: "button",
110
113
  onClick: () => b("list"),
111
114
  className: `p-1.5 rounded-md transition-colors ${y === "list" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
112
115
  title: "List view",
113
116
  children: /* @__PURE__ */ d("svg", {
114
- className: "w-4 h-4",
117
+ className: "size-4",
115
118
  fill: "none",
116
119
  viewBox: "0 0 24 24",
117
120
  stroke: "currentColor",
@@ -129,9 +132,9 @@ var h = () => {
129
132
  className: "flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg",
130
133
  children: [
131
134
  /* @__PURE__ */ d("div", {
132
- className: "w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4",
135
+ className: "size-12 rounded-full bg-muted flex items-center justify-center mb-4",
133
136
  children: /* @__PURE__ */ d("svg", {
134
- className: "w-6 h-6 text-muted-foreground",
137
+ className: "size-6 text-muted-foreground",
135
138
  fill: "none",
136
139
  viewBox: "0 0 24 24",
137
140
  stroke: "currentColor",
@@ -204,7 +207,7 @@ var h = () => {
204
207
  children: [/* @__PURE__ */ f("div", {
205
208
  className: "flex items-center gap-2",
206
209
  children: [/* @__PURE__ */ d("svg", {
207
- className: "w-4 h-4 text-status-success-text shrink-0",
210
+ className: "size-4 text-status-success-text shrink-0",
208
211
  fill: "none",
209
212
  viewBox: "0 0 24 24",
210
213
  stroke: "currentColor",
@@ -234,10 +237,12 @@ var h = () => {
234
237
  /* @__PURE__ */ f("div", {
235
238
  className: "space-y-2 pt-4 border-t border-border mt-auto",
236
239
  children: [/* @__PURE__ */ d("button", {
240
+ type: "button",
237
241
  onClick: a,
238
242
  className: "w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
239
243
  children: "Subscribe"
240
244
  }), /* @__PURE__ */ d("button", {
245
+ type: "button",
241
246
  onClick: t,
242
247
  className: "w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
243
248
  children: "View Details"
@@ -251,9 +256,9 @@ var h = () => {
251
256
  className: "flex items-center gap-4 p-4 border border-border rounded-lg bg-card transition-all hover:shadow-sm",
252
257
  children: [
253
258
  /* @__PURE__ */ d("div", {
254
- className: "w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0",
259
+ className: "size-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0",
255
260
  children: /* @__PURE__ */ d("svg", {
256
- className: "w-5 h-5 text-primary",
261
+ className: "size-5 text-primary",
257
262
  fill: "none",
258
263
  viewBox: "0 0 24 24",
259
264
  stroke: "currentColor",
@@ -296,10 +301,12 @@ var h = () => {
296
301
  /* @__PURE__ */ f("div", {
297
302
  className: "flex items-center gap-2",
298
303
  children: [/* @__PURE__ */ d("button", {
304
+ type: "button",
299
305
  onClick: i,
300
306
  className: "px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
301
307
  children: "Subscribe"
302
308
  }), /* @__PURE__ */ d("button", {
309
+ type: "button",
303
310
  onClick: t,
304
311
  className: "px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
305
312
  children: "View"
@@ -1 +1 @@
1
- {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { Plan, PlanDuration, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const PlansBrowsePage: 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 { productId } = useBilling();\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n\n // Only show active plans to users\n const filteredPlans = plans.filter((plan) => {\n if (!plan.isActive) return false;\n if (billingInterval !== 'all' && plan.duration !== billingInterval) return false;\n return true;\n });\n\n if (isLoading && plans.length === 0) {\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=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n {(['all', 'monthly', 'yearly'] as const).map((interval) => (\n <button\n key={interval}\n onClick={() => setBillingInterval(interval as PlanDuration | 'all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === interval\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {interval === 'all' ? 'All' : interval === 'monthly' ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"List view\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"w-6 h-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.plans.noPlansAvailable', 'No plans available')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n </p>\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredPlans.map((plan) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const combinedQuotas = combineQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"border border-border rounded-lg bg-card p-6 transition-all hover:shadow-md flex flex-col h-full\">\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-foreground\">{plan.name}</h3>\n <span className=\"text-sm text-muted-foreground\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-3xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-muted-foreground text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4\">\n {combinedQuotas.slice(0, 5).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"w-4 h-4 text-status-success-text 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 <span className=\"text-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 5 && (\n <li className=\"text-sm text-muted-foreground pl-6\">\n +{combinedQuotas.length - 5} more\n </li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n <button\n onClick={onSubscribe}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View Details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const combinedQuotas = combineQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-4 border border-border rounded-lg bg-card transition-all hover:shadow-sm\">\n <div className=\"w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg className=\"w-5 h-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\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\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-foreground truncate\">{plan.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-muted-foreground\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n onClick={onView}\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};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { Plan, PlanDuration, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const PlansBrowsePage: 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 { productId } = useBilling();\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n\n // Only show active plans to users\n const filteredPlans = plans.filter((plan) => {\n if (!plan.isActive) return false;\n if (billingInterval !== 'all' && plan.duration !== billingInterval) return false;\n return true;\n });\n\n if (isLoading && plans.length === 0) {\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=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n {(['all', 'monthly', 'yearly'] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval as PlanDuration | 'all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === interval\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {interval === 'all' ? 'All' : interval === 'monthly' ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"Grid view\"\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=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"List view\"\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=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.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.plans.noPlansAvailable', 'No plans available')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n </p>\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredPlans.map((plan) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const combinedQuotas = combineQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"border border-border rounded-lg bg-card p-6 transition-all hover:shadow-md flex flex-col h-full\">\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-foreground\">{plan.name}</h3>\n <span className=\"text-sm text-muted-foreground\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-3xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-muted-foreground text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4\">\n {combinedQuotas.slice(0, 5).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text 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 <span className=\"text-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 5 && (\n <li className=\"text-sm text-muted-foreground pl-6\">\n +{combinedQuotas.length - 5} more\n </li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View Details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const combinedQuotas = combineQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-4 border border-border rounded-lg bg-card transition-all hover:shadow-sm\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\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\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-foreground truncate\">{plan.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-muted-foreground\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\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};\n"],"mappings":"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"}
@@ -83,9 +83,9 @@ var y = () => {
83
83
  className: "text-center space-y-4",
84
84
  children: [
85
85
  /* @__PURE__ */ h("div", {
86
- className: "w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center",
86
+ className: "size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center",
87
87
  children: /* @__PURE__ */ h("svg", {
88
- className: "w-6 h-6 text-destructive",
88
+ className: "size-6 text-destructive",
89
89
  fill: "none",
90
90
  viewBox: "0 0 24 24",
91
91
  stroke: "currentColor",
@@ -106,6 +106,7 @@ var y = () => {
106
106
  children: p.message
107
107
  }),
108
108
  /* @__PURE__ */ h("button", {
109
+ type: "button",
109
110
  onClick: () => v(),
110
111
  className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
111
112
  children: "Try Again"
@@ -124,10 +125,11 @@ var y = () => {
124
125
  className: "text-sm text-muted-foreground mt-1",
125
126
  children: r("billing.plans.subtitle", "Choose the plan that best fits your needs")
126
127
  })] }), a.canManagePlans && /* @__PURE__ */ g("button", {
128
+ type: "button",
127
129
  onClick: () => i("/plans/new"),
128
130
  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",
129
131
  children: [/* @__PURE__ */ h("svg", {
130
- className: "w-4 h-4",
132
+ className: "size-4",
131
133
  fill: "none",
132
134
  viewBox: "0 0 24 24",
133
135
  stroke: "currentColor",
@@ -147,16 +149,19 @@ var y = () => {
147
149
  className: "inline-flex rounded-lg border border-border p-1 bg-muted/50",
148
150
  children: [
149
151
  /* @__PURE__ */ h("button", {
152
+ type: "button",
150
153
  onClick: () => O("all"),
151
154
  className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${D === "all" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
152
155
  children: "All"
153
156
  }),
154
157
  /* @__PURE__ */ h("button", {
158
+ type: "button",
155
159
  onClick: () => O("monthly"),
156
160
  className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${D === "monthly" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
157
161
  children: "Monthly"
158
162
  }),
159
163
  /* @__PURE__ */ h("button", {
164
+ type: "button",
160
165
  onClick: () => O("yearly"),
161
166
  className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${D === "yearly" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
162
167
  children: "Yearly"
@@ -169,7 +174,7 @@ var y = () => {
169
174
  type: "checkbox",
170
175
  checked: T,
171
176
  onChange: (e) => E(e.target.checked),
172
- className: "w-4 h-4 rounded border-border text-primary focus:ring-primary"
177
+ className: "size-4 rounded border-border text-primary focus:ring-primary"
173
178
  }), /* @__PURE__ */ h("span", {
174
179
  className: "text-muted-foreground",
175
180
  children: r("billing.plans.showInactive", "Show inactive plans")
@@ -178,11 +183,12 @@ var y = () => {
178
183
  /* @__PURE__ */ g("div", {
179
184
  className: "ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50",
180
185
  children: [/* @__PURE__ */ h("button", {
186
+ type: "button",
181
187
  onClick: () => w("grid"),
182
188
  className: `p-1.5 rounded-md transition-colors ${C === "grid" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
183
189
  title: "Grid view",
184
190
  children: /* @__PURE__ */ h("svg", {
185
- className: "w-4 h-4",
191
+ className: "size-4",
186
192
  fill: "none",
187
193
  viewBox: "0 0 24 24",
188
194
  stroke: "currentColor",
@@ -194,11 +200,12 @@ var y = () => {
194
200
  })
195
201
  })
196
202
  }), /* @__PURE__ */ h("button", {
203
+ type: "button",
197
204
  onClick: () => w("list"),
198
205
  className: `p-1.5 rounded-md transition-colors ${C === "list" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
199
206
  title: "List view",
200
207
  children: /* @__PURE__ */ h("svg", {
201
- className: "w-4 h-4",
208
+ className: "size-4",
202
209
  fill: "none",
203
210
  viewBox: "0 0 24 24",
204
211
  stroke: "currentColor",
@@ -217,9 +224,9 @@ var y = () => {
217
224
  className: "flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg",
218
225
  children: [
219
226
  /* @__PURE__ */ h("div", {
220
- className: "w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4",
227
+ className: "size-12 rounded-full bg-muted flex items-center justify-center mb-4",
221
228
  children: /* @__PURE__ */ h("svg", {
222
- className: "w-6 h-6 text-muted-foreground",
229
+ className: "size-6 text-muted-foreground",
223
230
  fill: "none",
224
231
  viewBox: "0 0 24 24",
225
232
  stroke: "currentColor",
@@ -240,6 +247,7 @@ var y = () => {
240
247
  children: T ? r("billing.plans.noPlansFiltered", "No plans match your current filters.") : r("billing.plans.noActivePlans", "There are no active plans available.")
241
248
  }),
242
249
  a.canManagePlans && /* @__PURE__ */ h("button", {
250
+ type: "button",
243
251
  onClick: () => i("/plans/new"),
244
252
  className: "mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
245
253
  children: r("billing.plans.createFirstPlan", "Create your first plan")
@@ -320,7 +328,7 @@ var y = () => {
320
328
  children: [/* @__PURE__ */ g("div", {
321
329
  className: "flex items-center gap-2",
322
330
  children: [/* @__PURE__ */ h("svg", {
323
- className: "w-5 h-5 text-status-success-text shrink-0",
331
+ className: "size-5 text-status-success-text shrink-0",
324
332
  fill: "none",
325
333
  viewBox: "0 0 24 24",
326
334
  stroke: "currentColor",
@@ -350,16 +358,19 @@ var y = () => {
350
358
  /* @__PURE__ */ g("div", {
351
359
  className: "space-y-2 pt-4 border-t border-border mt-auto",
352
360
  children: [c && e.isActive && /* @__PURE__ */ h("button", {
361
+ type: "button",
353
362
  onClick: () => t(e),
354
363
  className: "w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
355
364
  children: "Select Plan"
356
365
  }), /* @__PURE__ */ g("div", {
357
366
  className: "flex gap-2",
358
367
  children: [/* @__PURE__ */ h("button", {
368
+ type: "button",
359
369
  onClick: () => o(e),
360
370
  className: "flex-1 px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
361
371
  children: "View Details"
362
372
  }), d && /* @__PURE__ */ h("button", {
373
+ type: "button",
363
374
  onClick: () => s(e),
364
375
  disabled: f,
365
376
  className: `px-4 py-2 text-sm font-medium border border-border rounded-md transition-colors ${e.isActive ? "text-destructive hover:bg-destructive/10" : "text-status-success-text hover:bg-status-success-bg-subtle"}`,
@@ -412,16 +423,19 @@ var y = () => {
412
423
  className: "flex items-center gap-2",
413
424
  children: [
414
425
  o && e.isActive && /* @__PURE__ */ h("button", {
426
+ type: "button",
415
427
  onClick: () => t(e),
416
428
  className: "px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
417
429
  children: "Select"
418
430
  }),
419
431
  /* @__PURE__ */ h("button", {
432
+ type: "button",
420
433
  onClick: () => n(e),
421
434
  className: "px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
422
435
  children: "View"
423
436
  }),
424
437
  s && /* @__PURE__ */ h("button", {
438
+ type: "button",
425
439
  onClick: () => a(e),
426
440
  disabled: c,
427
441
  className: `px-3 py-1.5 text-sm font-medium border border-border rounded-md transition-colors ${e.isActive ? "text-destructive hover:bg-destructive/10" : "text-status-success-text hover:bg-status-success-bg-subtle"}`,