@burdenoff/microfe-billing 2026.625.4 → 2026.625.5

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 (96) hide show
  1. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +192 -187
  2. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonCreatePage.js +322 -317
  4. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  5. package/dist/billing/modules/addons/pages/AddonDetailPage.js +190 -185
  6. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  7. package/dist/billing/modules/addons/pages/AddonEditPage.js +297 -292
  8. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  9. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +60 -75
  10. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonsListPage.js +86 -101
  12. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  13. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +124 -121
  14. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  15. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +103 -111
  16. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  17. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +196 -160
  18. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  19. package/dist/billing/modules/billing/pages/InvoicesListPage.js +58 -73
  20. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  21. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +65 -75
  22. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/pages/RefundsPage.js +399 -399
  24. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  25. package/dist/billing/modules/billing/pages/TransactionsPage.js +49 -48
  26. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  27. package/dist/billing/modules/billing/pages/UserRefundsPage.js +144 -151
  28. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  29. package/dist/billing/modules/checkout/pages/CheckoutPage.js +430 -425
  30. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  31. package/dist/billing/modules/coupons/pages/CouponDetailPage.js +121 -93
  32. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  33. package/dist/billing/modules/coupons/pages/CouponsListPage.js +154 -152
  34. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  35. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +467 -462
  36. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  37. package/dist/billing/modules/coupons/pages/EditCouponPage.js +294 -286
  38. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  39. package/dist/billing/modules/credits/pages/AdminCreditsPage.js +123 -117
  40. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  41. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +43 -42
  42. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  43. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +192 -207
  44. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  45. package/dist/billing/modules/credits/pages/CreditsPage.js +40 -56
  46. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  47. package/dist/billing/modules/dashboard/pages/OverviewPage.js +128 -121
  48. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  49. package/dist/billing/modules/discounts/pages/DiscountsListPage.js +165 -180
  50. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  51. package/dist/billing/modules/earnings/pages/EarningsPage.js +103 -100
  52. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  53. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +175 -175
  54. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  55. package/dist/billing/modules/earnings/pages/PayoutsPage.js +96 -96
  56. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  57. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +42 -40
  58. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  59. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +45 -38
  60. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  61. package/dist/billing/modules/plans/pages/PlanCreatePage.js +339 -334
  62. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  63. package/dist/billing/modules/plans/pages/PlanDetailPage.js +80 -74
  64. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  65. package/dist/billing/modules/plans/pages/PlanEditPage.js +342 -337
  66. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  67. package/dist/billing/modules/plans/pages/PlansBrowsePage.js +38 -53
  68. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  69. package/dist/billing/modules/plans/pages/PlansListPage.js +60 -75
  70. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  71. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +242 -237
  72. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  73. package/dist/billing/modules/promotions/pages/PromotionsListPage.js +29 -39
  74. package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
  75. package/dist/billing/modules/quota/pages/QuotaPage.js +1 -0
  76. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +299 -294
  77. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  78. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +191 -186
  79. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  80. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +308 -303
  81. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  82. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +57 -78
  83. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  84. package/dist/billing/modules/settings/pages/SettingsPage.js +79 -78
  85. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  86. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +15 -13
  87. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  88. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +338 -299
  89. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  90. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +82 -103
  91. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  92. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +59 -54
  93. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  94. package/dist/billing/modules/usage/pages/UsagePage.js +172 -215
  95. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  96. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"AddonEditPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonEditPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Edit Page\n * Form to edit an existing addon\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/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 { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdateAddonInput,\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 AddonEditPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { addon, isLoading: isAddonLoading, error: addonError, refetch } = useAddon(addonId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updateAddon, isUpdating } = useAddonMutations();\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 addon data loads\n useEffect(() => {\n if (addon) {\n setName(addon.name ?? '');\n setPrice(String(addon.price ?? ''));\n setCurrency(addon.currency ?? 'USD');\n setDuration(addon.duration ?? PlanDuration.MONTHLY);\n setIsActive(addon.isActive ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const existing = addon.currencyPrices as Record<string, number>;\n setCurrencyPriceOverrides(\n Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }))\n );\n }\n // Extract quota allocations from features\n if (addon.features && addon.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of addon.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n const value = getPlanFeatureQuantity(feature);\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 }, [addon]);\n\n // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr('billing.addons.noEditPermission', \"You don't have permission to edit addons.\")}\n />\n );\n }\n\n // Loading state\n if (isAddonLoading || isQuotasLoading) {\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=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (addonError && isServerError(addonError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableLoadAddon',\n 'Unable to load addon. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!addon) {\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-text-primary\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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\"\n >\n Back to Addons\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('Addon 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: UpdateAddonInput = {\n id: addonId!,\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 updateAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/addons/${addonId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Addon: ${addon.name}`}\n description={tr(\n 'billing.addons.editAddonDesc',\n 'Update the addon 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.addons.updateSuccess', 'Addon 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-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.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-text-primary mb-1\">\n {tr('billing.addons.addonNameLabel', 'Addon 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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.addons.addonNamePlaceholder', 'e.g., Extra Storage')}\n required\n />\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"9.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\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-text-primary mb-1\"\n >\n {tr('billing.addons.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\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-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-text-primary\">\n {tr('billing.addons.addonIsActive', 'Addon is active')}\n </label>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.quotaAllocations', 'Quota Allocations')}\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] 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-text-muted\">\n {tr(\n 'billing.addons.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 limitType =\n (selectedQuota?.limits as { type?: string } | null)?.type ?? 'unknown';\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border-subtle rounded-lg bg-bg-sunken/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-subtle rounded-input bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n allocation.quotaId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.addons.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 <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 px-3 py-1.5 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-center\"\n />\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-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isUpdating\n ? tr('billing.addons.saving', 'Saving...')\n : tr('billing.addons.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addonId}`)}\n className=\"px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken 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":"AddonEditPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonEditPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Edit Page\n * Form to edit an existing addon\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { isServerError } from '../../../shared/utils';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdateAddonInput,\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 AddonEditPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { addon, isLoading: isAddonLoading, error: addonError, refetch } = useAddon(addonId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updateAddon, isUpdating } = useAddonMutations();\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 addon data loads\n useEffect(() => {\n if (addon) {\n setName(addon.name ?? '');\n setPrice(String(addon.price ?? ''));\n setCurrency(addon.currency ?? 'USD');\n setDuration(addon.duration ?? PlanDuration.MONTHLY);\n setIsActive(addon.isActive ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const existing = addon.currencyPrices as Record<string, number>;\n setCurrencyPriceOverrides(\n Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }))\n );\n }\n // Extract quota allocations from features\n if (addon.features && addon.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of addon.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n const value = getPlanFeatureQuantity(feature);\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 }, [addon]);\n\n // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr('billing.addons.noEditPermission', \"You don't have permission to edit addons.\")}\n />\n );\n }\n\n // Loading state\n if (isAddonLoading || isQuotasLoading) {\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=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (addonError && isServerError(addonError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableLoadAddon',\n 'Unable to load addon. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!addon) {\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-text-primary\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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\"\n >\n Back to Addons\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('Addon 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: UpdateAddonInput = {\n id: addonId!,\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 updateAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/addons/${addonId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Addon: ${addon.name}`}\n description={tr(\n 'billing.addons.editAddonDesc',\n 'Update the addon details and quota allocations'\n )}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.editPurpose',\n \"Update this add-on's price, currency overrides and the quotas it grants. Changes affect new purchases; existing add-on subscriptions keep what they already bought.\"\n )}\n </PagePurpose>\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.addons.updateSuccess', 'Addon 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-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.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-text-primary mb-1\">\n {tr('billing.addons.addonNameLabel', 'Addon 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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.addons.addonNamePlaceholder', 'e.g., Extra Storage')}\n required\n />\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"9.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\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-text-primary mb-1\"\n >\n {tr('billing.addons.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\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-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-text-primary\">\n {tr('billing.addons.addonIsActive', 'Addon is active')}\n </label>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.quotaAllocations', 'Quota Allocations')}\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] 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-text-muted\">\n {tr(\n 'billing.addons.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 limitType =\n (selectedQuota?.limits as { type?: string } | null)?.type ?? 'unknown';\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border-subtle rounded-lg bg-bg-sunken/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-subtle rounded-input bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n allocation.quotaId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.addons.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 <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 px-3 py-1.5 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-center\"\n />\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-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isUpdating\n ? tr('billing.addons.saving', 'Saving...')\n : tr('billing.addons.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addonId}`)}\n className=\"px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -10,8 +10,9 @@ import { useMemo as l, useState as u } from "react";
10
10
  import { Minus as d, Package as f, Plus as p, ShoppingCart as m, Trash2 as h, X as g } from "lucide-react";
11
11
  import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
12
12
  import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
+ import { IllustratedEmptyState as x, PagePurpose as S } from "@burdenoff/fe-libs/ui";
13
14
  //#region src/billing/modules/addons/pages/AddonsBrowsePage.tsx
14
- function x(e) {
15
+ function C(e) {
15
16
  let t = /* @__PURE__ */ new Map();
16
17
  for (let n of e) {
17
18
  let e = n.name.indexOf(" — "), r = e === -1 ? n.name : n.name.slice(0, e).trim(), i = t.get(r) ?? [];
@@ -28,7 +29,7 @@ function x(e) {
28
29
  });
29
30
  return n;
30
31
  }
31
- function S(e) {
32
+ function w(e) {
32
33
  let t = /* @__PURE__ */ new Map();
33
34
  for (let n of e) {
34
35
  if (!n.quota) continue;
@@ -41,7 +42,7 @@ function S(e) {
41
42
  }
42
43
  return Array.from(t.values());
43
44
  }
44
- function C(e, t = "USD") {
45
+ function T(e, t = "USD") {
45
46
  if (e.currencyPrices && typeof e.currencyPrices == "object") {
46
47
  let n = e.currencyPrices;
47
48
  if (n[t] !== void 0 && n[t] !== null) return {
@@ -54,14 +55,14 @@ function C(e, t = "USD") {
54
55
  currency: e.currency || "USD"
55
56
  };
56
57
  }
57
- var w = () => {
58
+ var E = () => {
58
59
  let { t } = b(), n = (e, n) => {
59
60
  let r = t(e);
60
61
  return r === e ? n : r;
61
62
  }, r = o(), { productId: i } = e(), { addons: d, isLoading: f, error: p, refetch: m } = s({
62
63
  includeFree: !1,
63
64
  productId: i ?? void 0
64
- }), { addToCart: h, removeFromCart: g, updateQuantity: _, isInCart: S, getCartItem: C } = a(), [w, k] = u("grid"), [A, j] = u("all"), M = c(), N = d.filter((e) => !(!e.isActive || A !== "all" && e.duration !== A)), P = l(() => x(N), [N]);
65
+ }), { addToCart: h, removeFromCart: g, updateQuantity: _, isInCart: w, getCartItem: T } = a(), [E, j] = u("grid"), [M, N] = u("all"), P = c(), F = d.filter((e) => !(!e.isActive || M !== "all" && e.duration !== M)), I = l(() => C(F), [F]);
65
66
  return f && d.length === 0 ? /* @__PURE__ */ y("div", {
66
67
  className: "space-y-6 p-6",
67
68
  children: [/* @__PURE__ */ v("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ v("div", {
@@ -110,13 +111,20 @@ var w = () => {
110
111
  }) : /* @__PURE__ */ y("div", {
111
112
  className: "space-y-6 p-6",
112
113
  children: [
113
- /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
114
- className: "text-2xl font-bold text-text-primary",
115
- children: n("billing.addons.title", "Addons")
116
- }), /* @__PURE__ */ v("p", {
117
- className: "text-sm text-text-secondary mt-1",
118
- children: n("billing.addons.subtitle", "Enhance your subscription with additional features")
119
- })] }),
114
+ /* @__PURE__ */ y("div", { children: [
115
+ /* @__PURE__ */ v("h1", {
116
+ className: "text-2xl font-bold text-text-primary",
117
+ children: n("billing.addons.title", "Addons")
118
+ }),
119
+ /* @__PURE__ */ v("p", {
120
+ className: "text-sm text-text-secondary mt-1",
121
+ children: n("billing.addons.subtitle", "Enhance your subscription with additional features")
122
+ }),
123
+ /* @__PURE__ */ v(S, {
124
+ className: "mt-3",
125
+ children: n("billing.addons.browsePurpose", "Add extra capacity or capabilities on top of your current plan — like more seats, storage or usage credits — without upgrading your whole subscription. Add the add-ons you want to your cart and check out when ready.")
126
+ })
127
+ ] }),
120
128
  /* @__PURE__ */ y("div", {
121
129
  className: "flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle",
122
130
  children: [/* @__PURE__ */ v("div", {
@@ -127,16 +135,16 @@ var w = () => {
127
135
  "yearly"
128
136
  ].map((e) => /* @__PURE__ */ v("button", {
129
137
  type: "button",
130
- onClick: () => j(e),
131
- className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${A === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
138
+ onClick: () => N(e),
139
+ className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${M === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
132
140
  children: e === "all" ? "All" : e === "monthly" ? "Monthly" : "Yearly"
133
141
  }, e))
134
142
  }), /* @__PURE__ */ y("div", {
135
143
  className: "ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50",
136
144
  children: [/* @__PURE__ */ v("button", {
137
145
  type: "button",
138
- onClick: () => k("grid"),
139
- className: `p-1.5 rounded-md transition-colors ${w === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
146
+ onClick: () => j("grid"),
147
+ className: `p-1.5 rounded-md transition-colors ${E === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
140
148
  title: "Grid view",
141
149
  children: /* @__PURE__ */ v("svg", {
142
150
  className: "size-4",
@@ -152,8 +160,8 @@ var w = () => {
152
160
  })
153
161
  }), /* @__PURE__ */ v("button", {
154
162
  type: "button",
155
- onClick: () => k("list"),
156
- className: `p-1.5 rounded-md transition-colors ${w === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
163
+ onClick: () => j("list"),
164
+ className: `p-1.5 rounded-md transition-colors ${E === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
157
165
  title: "List view",
158
166
  children: /* @__PURE__ */ v("svg", {
159
167
  className: "size-4",
@@ -170,65 +178,42 @@ var w = () => {
170
178
  })]
171
179
  })]
172
180
  }),
173
- N.length === 0 ? /* @__PURE__ */ y("div", {
174
- className: "flex flex-col items-center justify-center py-12 border border-dashed border-border-subtle rounded-lg",
175
- children: [
176
- /* @__PURE__ */ v("div", {
177
- className: "size-12 rounded-full bg-bg-sunken flex items-center justify-center mb-4",
178
- children: /* @__PURE__ */ v("svg", {
179
- className: "size-6 text-text-secondary",
180
- fill: "none",
181
- viewBox: "0 0 24 24",
182
- stroke: "currentColor",
183
- children: /* @__PURE__ */ v("path", {
184
- strokeLinecap: "round",
185
- strokeLinejoin: "round",
186
- strokeWidth: 2,
187
- d: "M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"
188
- })
189
- })
190
- }),
191
- /* @__PURE__ */ v("h3", {
192
- className: "text-lg font-medium text-text-primary",
193
- children: n("billing.addons.noAddonsAvailable", "No addons available")
194
- }),
195
- /* @__PURE__ */ v("p", {
196
- className: "text-sm text-text-secondary mt-1",
197
- children: n("billing.addons.checkBackLater", "Check back later for new addons.")
198
- })
199
- ]
181
+ F.length === 0 ? /* @__PURE__ */ v(x, {
182
+ illustration: "empty-data",
183
+ title: n("billing.addons.noAddonsAvailable", "No addons available"),
184
+ description: n("billing.addons.checkBackLater", "Check back later for new addons.")
200
185
  }) : /* @__PURE__ */ v("div", {
201
186
  className: "space-y-8",
202
- children: P.map((e) => {
203
- if (e.type === "group") return /* @__PURE__ */ v(T, {
187
+ children: I.map((e) => {
188
+ if (e.type === "group") return /* @__PURE__ */ v(D, {
204
189
  groupName: e.name,
205
190
  addons: e.addons,
206
- viewMode: w,
207
- getCartItem: C,
208
- isInCart: S,
191
+ viewMode: E,
192
+ getCartItem: T,
193
+ isInCart: w,
209
194
  addToCart: h,
210
195
  updateQuantity: _,
211
196
  removeFromCart: g,
212
- displayCurrency: M,
197
+ displayCurrency: P,
213
198
  navigateTo: r
214
199
  }, e.name);
215
- let t = e.addon, n = C(t.id);
216
- return v(w === "grid" ? E : D, {
200
+ let t = e.addon, n = T(t.id);
201
+ return v(E === "grid" ? O : k, {
217
202
  addon: t,
218
203
  onView: () => r(`/addons/${t.id}`),
219
- isInCart: S(t.id),
204
+ isInCart: w(t.id),
220
205
  cartQuantity: n?.quantity || 0,
221
206
  onAddToCart: h,
222
207
  onUpdateQuantity: _,
223
208
  onRemoveFromCart: g,
224
- displayCurrency: M
209
+ displayCurrency: P
225
210
  }, t.id);
226
211
  })
227
212
  }),
228
- /* @__PURE__ */ v(O, { displayCurrency: M })
213
+ /* @__PURE__ */ v(A, { displayCurrency: P })
229
214
  ]
230
215
  });
231
- }, T = ({ groupName: e, addons: i, viewMode: a, getCartItem: o, isInCart: s, addToCart: c, updateQuantity: l, removeFromCart: u, displayCurrency: g, navigateTo: _ }) => {
216
+ }, D = ({ groupName: e, addons: i, viewMode: a, getCartItem: o, isInCart: s, addToCart: c, updateQuantity: l, removeFromCart: u, displayCurrency: g, navigateTo: _ }) => {
232
217
  let b = i.filter((e) => s(e.id)).length;
233
218
  return /* @__PURE__ */ y("div", {
234
219
  className: "border border-border-subtle rounded-xl overflow-hidden",
@@ -415,8 +400,8 @@ var w = () => {
415
400
  })
416
401
  })]
417
402
  });
418
- }, E = ({ addon: e, onView: i, isInCart: a, cartQuantity: o, onAddToCart: s, onUpdateQuantity: c, onRemoveFromCart: u, displayCurrency: f }) => {
419
- let g = l(() => S(e.features || []), [e.features]);
403
+ }, O = ({ addon: e, onView: i, isInCart: a, cartQuantity: o, onAddToCart: s, onUpdateQuantity: c, onRemoveFromCart: u, displayCurrency: f }) => {
404
+ let g = l(() => w(e.features || []), [e.features]);
420
405
  return /* @__PURE__ */ y("div", {
421
406
  className: `relative border rounded-lg p-6 transition-all hover:shadow-md flex flex-col h-full ${a ? "border-primary bg-action-primary-bg/5" : "border-border-subtle bg-bg-surface"}`,
422
407
  children: [
@@ -458,7 +443,7 @@ var w = () => {
458
443
  children: [/* @__PURE__ */ v("span", {
459
444
  className: "text-2xl font-bold text-text-primary",
460
445
  children: (() => {
461
- let { price: n, currency: r } = C(e, f);
446
+ let { price: n, currency: r } = T(e, f);
462
447
  return t(n, r);
463
448
  })()
464
449
  }), /* @__PURE__ */ y("span", {
@@ -545,8 +530,8 @@ var w = () => {
545
530
  })
546
531
  ]
547
532
  });
548
- }, D = ({ addon: e, onView: r, isInCart: i, cartQuantity: a, onAddToCart: o, onUpdateQuantity: s, onRemoveFromCart: c, displayCurrency: u }) => {
549
- let f = l(() => S(e.features || []), [e.features]);
533
+ }, k = ({ addon: e, onView: r, isInCart: i, cartQuantity: a, onAddToCart: o, onUpdateQuantity: s, onRemoveFromCart: c, displayCurrency: u }) => {
534
+ let f = l(() => w(e.features || []), [e.features]);
550
535
  return /* @__PURE__ */ y("div", {
551
536
  className: `flex items-center gap-4 p-4 border rounded-lg transition-all hover:shadow-sm ${i ? "border-primary bg-action-primary-bg/5" : "border-border-subtle bg-bg-surface"}`,
552
537
  children: [
@@ -594,7 +579,7 @@ var w = () => {
594
579
  children: [/* @__PURE__ */ v("div", {
595
580
  className: "font-semibold text-text-primary",
596
581
  children: (() => {
597
- let { price: n, currency: r } = C(e, u);
582
+ let { price: n, currency: r } = T(e, u);
598
583
  return t(n, r);
599
584
  })()
600
585
  }), /* @__PURE__ */ y("div", {
@@ -643,11 +628,11 @@ var w = () => {
643
628
  })
644
629
  ]
645
630
  });
646
- }, O = ({ displayCurrency: e }) => {
631
+ }, A = ({ displayCurrency: e }) => {
647
632
  let { t: r } = b(), i = (e, t) => {
648
633
  let n = r(e);
649
634
  return n === e ? t : n;
650
- }, s = o(), { items: c, isCartOpen: l, toggleCart: u, closeCart: f, removeFromCart: x, updateQuantity: S, clearCart: w } = a(), T = c.reduce((e, t) => e + t.quantity, 0);
635
+ }, s = o(), { items: c, isCartOpen: l, toggleCart: u, closeCart: f, removeFromCart: x, updateQuantity: S, clearCart: C } = a(), w = c.reduce((e, t) => e + t.quantity, 0);
651
636
  return c.length === 0 && !l ? null : /* @__PURE__ */ y(_, { children: [!l && c.length > 0 && /* @__PURE__ */ y("button", {
652
637
  type: "button",
653
638
  onClick: u,
@@ -657,15 +642,15 @@ var w = () => {
657
642
  /* @__PURE__ */ y("span", {
658
643
  className: "font-medium",
659
644
  children: [
660
- T,
645
+ w,
661
646
  " item",
662
- T === 1 ? "" : "s"
647
+ w === 1 ? "" : "s"
663
648
  ]
664
649
  }),
665
650
  /* @__PURE__ */ v("span", {
666
651
  className: "px-2 py-0.5 bg-primary-foreground/20 rounded-full text-sm",
667
652
  children: t(c.reduce((t, { addon: n, quantity: r }) => {
668
- let { price: i } = C(n, e);
653
+ let { price: i } = T(n, e);
669
654
  return t + i * r;
670
655
  }, 0), e)
671
656
  })
@@ -691,9 +676,9 @@ var w = () => {
691
676
  /* @__PURE__ */ y("span", {
692
677
  className: "px-2 py-0.5 text-xs bg-action-primary-bg text-action-primary-text rounded-full",
693
678
  children: [
694
- T,
679
+ w,
695
680
  " item",
696
- T === 1 ? "" : "s"
681
+ w === 1 ? "" : "s"
697
682
  ]
698
683
  })
699
684
  ]
@@ -718,7 +703,7 @@ var w = () => {
718
703
  className: "text-sm text-text-secondary",
719
704
  children: [
720
705
  (() => {
721
- let { price: n, currency: i } = C(r, e);
706
+ let { price: n, currency: i } = T(r, e);
722
707
  return t(n, i);
723
708
  })(),
724
709
  " ",
@@ -751,7 +736,7 @@ var w = () => {
751
736
  /* @__PURE__ */ v("p", {
752
737
  className: "font-medium text-text-primary",
753
738
  children: (() => {
754
- let { price: n, currency: a } = C(r, e);
739
+ let { price: n, currency: a } = T(r, e);
755
740
  return t(n * i, a);
756
741
  })()
757
742
  }),
@@ -774,7 +759,7 @@ var w = () => {
774
759
  }), /* @__PURE__ */ v("span", {
775
760
  className: "text-text-primary",
776
761
  children: t(c.reduce((t, { addon: n, quantity: r }) => {
777
- let { price: i } = C(n, e);
762
+ let { price: i } = T(n, e);
778
763
  return t + i * r;
779
764
  }, 0), e)
780
765
  })]
@@ -782,7 +767,7 @@ var w = () => {
782
767
  className: "flex gap-2",
783
768
  children: [/* @__PURE__ */ v("button", {
784
769
  type: "button",
785
- onClick: w,
770
+ onClick: C,
786
771
  className: "flex-1 px-4 py-2.5 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors",
787
772
  children: "Clear Cart"
788
773
  }), /* @__PURE__ */ v("button", {
@@ -800,6 +785,6 @@ var w = () => {
800
785
  })] });
801
786
  };
802
787
  //#endregion
803
- export { w as AddonsBrowsePage };
788
+ export { E as AddonsBrowsePage };
804
789
 
805
790
  //# sourceMappingURL=AddonsBrowsePage.js.map