@burdenoff/microfe-billing 2026.828.4 → 2026.830.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.
@@ -265,7 +265,7 @@ var h = () => {
265
265
  className: "text-sm text-text-muted",
266
266
  children: g("billing.addons.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
267
267
  }) : F.map((e, t) => {
268
- let n = y.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
268
+ let n = y.find((t) => t.id === e.quotaId), r = n?.limits?.type ?? "unknown";
269
269
  return /* @__PURE__ */ f("div", {
270
270
  className: "p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3",
271
271
  children: [/* @__PURE__ */ f("div", {
@@ -302,50 +302,21 @@ var h = () => {
302
302
  })]
303
303
  }), n && /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d("div", {
304
304
  className: "flex items-center justify-between text-sm",
305
- children: /* @__PURE__ */ f("div", {
306
- className: "text-text-muted",
307
- children: [
308
- /* @__PURE__ */ f("span", {
309
- className: "capitalize",
310
- children: ["Type: ", a]
311
- }),
312
- /* @__PURE__ */ d("span", {
313
- className: "mx-2",
314
- children: "•"
315
- }),
316
- /* @__PURE__ */ f("span", { children: [
317
- "Value per unit:",
318
- " ",
319
- /* @__PURE__ */ d("span", {
320
- className: "font-medium text-text-primary",
321
- children: i.toLocaleString()
322
- })
323
- ] })
324
- ]
305
+ children: /* @__PURE__ */ f("span", {
306
+ className: "capitalize text-text-muted",
307
+ children: ["Type: ", r]
325
308
  })
326
309
  }), /* @__PURE__ */ f("div", {
327
- className: "flex items-center gap-4",
328
- children: [/* @__PURE__ */ f("div", {
329
- className: "flex items-center gap-2",
330
- children: [/* @__PURE__ */ f("label", {
331
- className: "text-sm text-text-muted",
332
- children: [g("billing.addons.quantity", "Quantity"), ":"]
333
- }), /* @__PURE__ */ d("input", {
334
- type: "number",
335
- min: "1",
336
- value: e.quantity,
337
- onChange: (e) => G(t, "quantity", e.target.value),
338
- className: "w-24 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"
339
- })]
340
- }), /* @__PURE__ */ f("div", {
341
- className: "flex items-center gap-2 text-sm",
342
- children: [/* @__PURE__ */ d("span", {
343
- className: "text-text-muted",
344
- children: "="
345
- }), /* @__PURE__ */ f("span", {
346
- className: "font-semibold text-status-success-text",
347
- children: [o.toLocaleString(), " total"]
348
- })]
310
+ className: "flex items-center gap-2",
311
+ children: [/* @__PURE__ */ f("label", {
312
+ className: "text-sm text-text-muted",
313
+ children: [g("billing.addons.limit", "Limit"), ":"]
314
+ }), /* @__PURE__ */ d("input", {
315
+ type: "number",
316
+ min: "1",
317
+ value: e.quantity,
318
+ onChange: (e) => G(t, "quantity", e.target.value),
319
+ 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"
349
320
  })]
350
321
  })] })]
351
322
  }, t);
@@ -1 +1 @@
1
- {"version":3,"file":"AddonCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonCreatePage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Create Page\n * Form to create a new addon\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useProducts } from '../../../hooks/useProducts';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n PlanDuration,\n type CreateAddonInput,\n type CurrencyPriceOverrideInput,\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 AddonCreatePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { products, isLoading: isProductsLoading } = useProducts();\n const { createAddon, isCreating } = 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 [productId, setProductId] = useState('');\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 // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noCreatePermission',\n \"You don't have permission to create addons.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isQuotasLoading || isProductsLoading) {\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 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 (!productId.trim()) {\n setError('Product ID 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: CreateAddonInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\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 createAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/addons'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.addons.createNewAddon', 'Create New Addon')}\n description={tr('billing.addons.createNewAddonDesc', 'Set up a new addon with quotas')}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.createPurpose',\n 'Create an add-on that customers can buy on top of a subscription — extra seats, storage, usage credits and so on. Set its price (with optional per-currency overrides) and the quotas it grants, then make it available to purchase.'\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.createSuccess', 'Addon created 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 {/* Product */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.product', 'Product')} *\n </label>\n <select\n id=\"productId\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n disabled={products.length === 0}\n className={`w-full 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)] disabled:opacity-50 ${\n productId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n required\n >\n <option value=\"\" disabled>\n {tr('billing.addons.selectProduct', 'Select a product...')}\n </option>\n {products.map((product) => (\n <option key={product.id} value={product.id}>\n {product.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-muted mt-1\">\n {products.length === 0\n ? tr('billing.addons.noProductsAvailable', 'No active products found.')\n : tr('billing.addons.productIdDesc', 'The product this addon belongs to')}\n </p>\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 </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 disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\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 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-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 <div className=\"text-text-muted\">\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-text-primary\">\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-text-muted\">\n {tr('billing.addons.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-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 <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-text-muted\">=</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={isCreating || quotas.length === 0}\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 {isCreating\n ? tr('billing.addons.creating', 'Creating...')\n : tr('billing.addons.createAddonBtn', 'Create Addon')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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":"AddonCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonCreatePage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Create Page\n * Form to create a new addon\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useProducts } from '../../../hooks/useProducts';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n PlanDuration,\n type CreateAddonInput,\n type CurrencyPriceOverrideInput,\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 AddonCreatePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { products, isLoading: isProductsLoading } = useProducts();\n const { createAddon, isCreating } = 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 [productId, setProductId] = useState('');\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 // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noCreatePermission',\n \"You don't have permission to create addons.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isQuotasLoading || isProductsLoading) {\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 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 (!productId.trim()) {\n setError('Product ID 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: CreateAddonInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\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 createAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/addons'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.addons.createNewAddon', 'Create New Addon')}\n description={tr('billing.addons.createNewAddonDesc', 'Set up a new addon with quotas')}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.createPurpose',\n 'Create an add-on that customers can buy on top of a subscription — extra seats, storage, usage credits and so on. Set its price (with optional per-currency overrides) and the quotas it grants, then make it available to purchase.'\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.createSuccess', 'Addon created 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 {/* Product */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.product', 'Product')} *\n </label>\n <select\n id=\"productId\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n disabled={products.length === 0}\n className={`w-full 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)] disabled:opacity-50 ${\n productId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n required\n >\n <option value=\"\" disabled>\n {tr('billing.addons.selectProduct', 'Select a product...')}\n </option>\n {products.map((product) => (\n <option key={product.id} value={product.id}>\n {product.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-muted mt-1\">\n {products.length === 0\n ? tr('billing.addons.noProductsAvailable', 'No active products found.')\n : tr('billing.addons.productIdDesc', 'The product this addon belongs to')}\n </p>\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 </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 disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\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 limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitType = limits?.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={isCreating || quotas.length === 0}\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 {isCreating\n ? tr('billing.addons.creating', 'Creating...')\n : tr('billing.addons.createAddonBtn', 'Create Addon')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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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"}
@@ -274,7 +274,7 @@ var m = () => {
274
274
  className: "text-sm text-text-muted",
275
275
  children: h("billing.plans.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
276
276
  }) : P.map((e, t) => {
277
- let n = v.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
277
+ let n = v.find((t) => t.id === e.quotaId), r = n?.limits?.type ?? "unknown";
278
278
  return /* @__PURE__ */ d("div", {
279
279
  className: "p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3",
280
280
  children: [/* @__PURE__ */ d("div", {
@@ -311,50 +311,21 @@ var m = () => {
311
311
  })]
312
312
  }), n && /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ u("div", {
313
313
  className: "flex items-center justify-between text-sm",
314
- children: /* @__PURE__ */ d("div", {
315
- className: "text-text-muted",
316
- children: [
317
- /* @__PURE__ */ d("span", {
318
- className: "capitalize",
319
- children: ["Type: ", a]
320
- }),
321
- /* @__PURE__ */ u("span", {
322
- className: "mx-2",
323
- children: "•"
324
- }),
325
- /* @__PURE__ */ d("span", { children: [
326
- "Value per unit:",
327
- " ",
328
- /* @__PURE__ */ u("span", {
329
- className: "font-medium text-text-primary",
330
- children: i.toLocaleString()
331
- })
332
- ] })
333
- ]
314
+ children: /* @__PURE__ */ d("span", {
315
+ className: "capitalize text-text-muted",
316
+ children: ["Type: ", r]
334
317
  })
335
318
  }), /* @__PURE__ */ d("div", {
336
- className: "flex items-center gap-4",
337
- children: [/* @__PURE__ */ d("div", {
338
- className: "flex items-center gap-2",
339
- children: [/* @__PURE__ */ d("label", {
340
- className: "text-sm text-text-muted",
341
- children: [h("billing.plans.quantity", "Quantity"), ":"]
342
- }), /* @__PURE__ */ u("input", {
343
- type: "number",
344
- min: "1",
345
- value: e.quantity,
346
- onChange: (e) => W(t, "quantity", e.target.value),
347
- className: "w-24 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"
348
- })]
349
- }), /* @__PURE__ */ d("div", {
350
- className: "flex items-center gap-2 text-sm",
351
- children: [/* @__PURE__ */ u("span", {
352
- className: "text-text-muted",
353
- children: "="
354
- }), /* @__PURE__ */ d("span", {
355
- className: "font-semibold text-status-success-text",
356
- children: [o.toLocaleString(), " total"]
357
- })]
319
+ className: "flex items-center gap-2",
320
+ children: [/* @__PURE__ */ d("label", {
321
+ className: "text-sm text-text-muted",
322
+ children: [h("billing.plans.limit", "Limit"), ":"]
323
+ }), /* @__PURE__ */ u("input", {
324
+ type: "number",
325
+ min: "1",
326
+ value: e.quantity,
327
+ onChange: (e) => W(t, "quantity", e.target.value),
328
+ 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"
358
329
  })]
359
330
  })] })]
360
331
  }, t);
@@ -1 +1 @@
1
- {"version":3,"file":"PlanCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanCreatePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Create Page\n * Form to create a new plan\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n PlanDuration,\n type CreatePlanInput,\n type CurrencyPriceOverrideInput,\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 PlanCreatePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { createPlan, isCreating } = 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 [productId, setProductId] = useState('');\n const [isPurchasable, setIsPurchasable] = 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 // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noCreatePermission',\n \"You don't have permission to create plans.\"\n )}\n />\n );\n }\n\n // Loading state\n if (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 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 (!productId.trim()) {\n setError('Product ID 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: CreatePlanInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\n isPurchasable,\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 createPlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/plans'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.plans.createNewPlan', 'Create New Plan')}\n description={tr('billing.plans.createNewPlanDesc', 'Set up a new pricing plan with quotas')}\n />\n\n <PagePurpose>\n {tr(\n 'billing.plans.createPurpose',\n 'Define a new subscription tier customers can buy — its price and billing period, optional per-currency and per-seat pricing, and the quotas it includes. Once active, it appears on the Plans page for purchase.'\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.plans.createSuccess', 'Plan created 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.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-text-primary 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-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.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Product ID */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.productId', 'Product ID')} *\n </label>\n <input\n id=\"productId\"\n type=\"text\"\n value={productId}\n onChange={(e) => setProductId(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.plans.productIdPlaceholder', 'e.g., prod_abc123')}\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr('billing.plans.productIdDesc', 'The product this plan belongs to')}\n </p>\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.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"49.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.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-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.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-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 {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-text-muted mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n isPurchasable ? 'bg-action-primary-bg' : 'bg-text-muted/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\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.plans.quotaAllocations', 'Quota Allocations')} *\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.plans.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\n ) : quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\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-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.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-text-muted\">\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-text-primary\">\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-text-muted\">\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-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 <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-text-muted\">=</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={isCreating || quotas.length === 0}\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 {isCreating\n ? tr('billing.plans.creating', 'Creating...')\n : tr('billing.plans.createPlanBtn', 'Create Plan')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\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":"PlanCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanCreatePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Create Page\n * Form to create a new plan\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n PlanDuration,\n type CreatePlanInput,\n type CurrencyPriceOverrideInput,\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 PlanCreatePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { createPlan, isCreating } = 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 [productId, setProductId] = useState('');\n const [isPurchasable, setIsPurchasable] = 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 // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noCreatePermission',\n \"You don't have permission to create plans.\"\n )}\n />\n );\n }\n\n // Loading state\n if (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 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 (!productId.trim()) {\n setError('Product ID 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: CreatePlanInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\n isPurchasable,\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 createPlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/plans'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.plans.createNewPlan', 'Create New Plan')}\n description={tr('billing.plans.createNewPlanDesc', 'Set up a new pricing plan with quotas')}\n />\n\n <PagePurpose>\n {tr(\n 'billing.plans.createPurpose',\n 'Define a new subscription tier customers can buy — its price and billing period, optional per-currency and per-seat pricing, and the quotas it includes. Once active, it appears on the Plans page for purchase.'\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.plans.createSuccess', 'Plan created 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.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-text-primary 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-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.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Product ID */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.productId', 'Product ID')} *\n </label>\n <input\n id=\"productId\"\n type=\"text\"\n value={productId}\n onChange={(e) => setProductId(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.plans.productIdPlaceholder', 'e.g., prod_abc123')}\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr('billing.plans.productIdDesc', 'The product this plan belongs to')}\n </p>\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.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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"49.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.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-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.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-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 {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-text-muted mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n isPurchasable ? 'bg-action-primary-bg' : 'bg-text-muted/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\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.plans.quotaAllocations', 'Quota Allocations')} *\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.plans.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\n ) : quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\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 limitType = limits?.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.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 <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.plans.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={isCreating || quotas.length === 0}\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 {isCreating\n ? tr('billing.plans.creating', 'Creating...')\n : tr('billing.plans.createPlanBtn', 'Create Plan')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\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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"}
@@ -9,32 +9,32 @@ import { getPlanFeatureQuantity as o } from "../../../shared/utils/planFeatureQu
9
9
  import "../../../shared/utils/index.js";
10
10
  import { useBillingPermissions as s } from "../../../hooks/useBillingPermissions.js";
11
11
  import { useBillingNavigate as c } from "../../../hooks/useBillingNavigate.js";
12
- import { usePlan as l, usePlanMutations as ee } from "../hooks/usePlans.js";
13
- import { useQuotas as u } from "../hooks/useQuotas.js";
12
+ import { usePlan as l, usePlanMutations as u } from "../hooks/usePlans.js";
13
+ import { useQuotas as d } from "../hooks/useQuotas.js";
14
14
  import "../hooks/index.js";
15
- import { useEffect as d, useState as f } from "react";
16
- import { useParams as p } from "react-router";
17
- import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
18
- import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
19
- import { PagePurpose as v } from "@burdenoff/fe-libs/ui";
15
+ import { useEffect as f, useState as p } from "react";
16
+ import { useParams as m } from "react-router";
17
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
18
+ import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
19
+ import { PagePurpose as y } from "@burdenoff/fe-libs/ui";
20
20
  //#region src/billing/modules/plans/pages/PlanEditPage.tsx
21
- var y = () => {
22
- let { t: y } = _(), b = (e, t) => {
23
- let n = y(e);
21
+ var b = () => {
22
+ let { t: b } = v(), x = (e, t) => {
23
+ let n = b(e);
24
24
  return n === e ? t : n;
25
- }, { planId: x } = p(), S = c(), C = s(), { plan: w, isLoading: T, error: E, refetch: D } = l(x), { quotas: O, isLoading: k } = u({ onlyActive: !0 }), { updatePlan: A, isUpdating: j } = ee(), [M, N] = f(""), [P, F] = f(""), [I, L] = f("USD"), [R, z] = f(i.MONTHLY), [B, V] = f(!0), [H, U] = f(!0), [W, G] = f([]), [K, q] = f([]), [J, Y] = f(null), [X, Z] = f(!1);
26
- if (d(() => {
27
- if (w) {
28
- if (N(w.name ?? ""), F(String(w.price ?? "")), L(w.currency ?? "USD"), z(w.duration ?? i.MONTHLY), V(w.isActive ?? !0), U(w.isPurchasable ?? !0), w.currencyPrices && typeof w.currencyPrices == "object") {
29
- let e = w.currencyPrices;
25
+ }, { planId: S } = m(), C = c(), w = s(), { plan: T, isLoading: ee, error: E, refetch: D } = l(S), { quotas: O, isLoading: k } = d({ onlyActive: !0 }), { updatePlan: A, isUpdating: j } = u(), [M, N] = p(""), [P, F] = p(""), [I, L] = p("USD"), [R, z] = p(i.MONTHLY), [B, V] = p(!0), [H, U] = p(!0), [W, G] = p([]), [K, q] = p([]), [J, Y] = p(null), [X, Z] = p(!1);
26
+ if (f(() => {
27
+ if (T) {
28
+ if (N(T.name ?? ""), F(String(T.price ?? "")), L(T.currency ?? "USD"), z(T.duration ?? i.MONTHLY), V(T.isActive ?? !0), U(T.isPurchasable ?? !0), T.currencyPrices && typeof T.currencyPrices == "object") {
29
+ let e = T.currencyPrices;
30
30
  q(Object.entries(e).filter(([, e]) => typeof e == "number").map(([e, t]) => ({
31
31
  currency: e,
32
32
  amount: t
33
33
  })));
34
34
  }
35
- if (w.features && w.features.length > 0) {
35
+ if (T.features && T.features.length > 0) {
36
36
  let e = [], t = /* @__PURE__ */ new Map();
37
- for (let e of w.features) if (e.quota?.id) {
37
+ for (let e of T.features) if (e.quota?.id) {
38
38
  let n = e.quota.id, r = o(e) || 1;
39
39
  t.set(n, r);
40
40
  }
@@ -45,10 +45,10 @@ var y = () => {
45
45
  G(e);
46
46
  }
47
47
  }
48
- }, [w]), !C.canManagePlans) return /* @__PURE__ */ h(e, { message: b("billing.plans.noEditPermission", "You don't have permission to edit plans.") });
49
- if (T || k) return /* @__PURE__ */ g("div", {
48
+ }, [T]), !w.canManagePlans) return /* @__PURE__ */ g(e, { message: x("billing.plans.noEditPermission", "You don't have permission to edit plans.") });
49
+ if (ee || k) return /* @__PURE__ */ _("div", {
50
50
  className: "space-y-6 p-6",
51
- children: [/* @__PURE__ */ h("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ h("div", {
51
+ children: [/* @__PURE__ */ g("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ g("div", {
52
52
  className: "max-w-2xl space-y-4",
53
53
  children: [
54
54
  1,
@@ -56,28 +56,28 @@ var y = () => {
56
56
  3,
57
57
  4,
58
58
  5
59
- ].map((e) => /* @__PURE__ */ h("div", { className: "h-16 bg-bg-sunken animate-pulse rounded" }, e))
59
+ ].map((e) => /* @__PURE__ */ g("div", { className: "h-16 bg-bg-sunken animate-pulse rounded" }, e))
60
60
  })]
61
61
  });
62
- if (E && a(E)) return /* @__PURE__ */ h("div", {
62
+ if (E && a(E)) return /* @__PURE__ */ g("div", {
63
63
  className: "p-6",
64
- children: /* @__PURE__ */ h(n, {
65
- title: b("billing.plans.serverUnavailable", "Server Unavailable"),
66
- message: b("billing.plans.serverUnavailableLoadPlan", "Unable to load plan. The server might be down or experiencing issues."),
64
+ children: /* @__PURE__ */ g(n, {
65
+ title: x("billing.plans.serverUnavailable", "Server Unavailable"),
66
+ message: x("billing.plans.serverUnavailableLoadPlan", "Unable to load plan. The server might be down or experiencing issues."),
67
67
  onRetry: () => D(),
68
68
  showRetry: !0
69
69
  })
70
70
  });
71
- if (!w) return /* @__PURE__ */ h("div", {
71
+ if (!T) return /* @__PURE__ */ g("div", {
72
72
  className: "flex items-center justify-center h-full min-h-[400px]",
73
- children: /* @__PURE__ */ g("div", {
73
+ children: /* @__PURE__ */ _("div", {
74
74
  className: "text-center space-y-4",
75
- children: [/* @__PURE__ */ h("h2", {
75
+ children: [/* @__PURE__ */ g("h2", {
76
76
  className: "text-lg font-semibold text-text-primary",
77
- children: b("billing.plans.planNotFound", "Plan not found")
78
- }), /* @__PURE__ */ h("button", {
77
+ children: x("billing.plans.planNotFound", "Plan not found")
78
+ }), /* @__PURE__ */ g("button", {
79
79
  type: "button",
80
- onClick: () => S("/plans"),
80
+ onClick: () => C("/plans"),
81
81
  className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover",
82
82
  children: "Back to Plans"
83
83
  })]
@@ -94,15 +94,15 @@ var y = () => {
94
94
  let r = [...W];
95
95
  t === "quotaId" ? r[e].quotaId = n : r[e].quantity = Number(n), G(r);
96
96
  };
97
- return /* @__PURE__ */ g("div", {
97
+ return /* @__PURE__ */ _("div", {
98
98
  className: "space-y-6 p-6",
99
99
  children: [
100
- /* @__PURE__ */ h(t, {
101
- title: `Edit Plan: ${w.name}`,
102
- description: b("billing.plans.editPlanDesc", "Update the plan details and quota allocations")
100
+ /* @__PURE__ */ g(t, {
101
+ title: `Edit Plan: ${T.name}`,
102
+ description: x("billing.plans.editPlanDesc", "Update the plan details and quota allocations")
103
103
  }),
104
- /* @__PURE__ */ h(v, { children: b("billing.plans.editPurpose", "Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.") }),
105
- /* @__PURE__ */ g("form", {
104
+ /* @__PURE__ */ g(y, { children: x("billing.plans.editPurpose", "Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.") }),
105
+ /* @__PURE__ */ _("form", {
106
106
  onSubmit: async (e) => {
107
107
  if (e.preventDefault(), Y(null), Z(!1), !M.trim()) {
108
108
  Y("Plan name is required");
@@ -121,7 +121,7 @@ var y = () => {
121
121
  return;
122
122
  }
123
123
  let t = {
124
- id: x,
124
+ id: S,
125
125
  name: M.trim(),
126
126
  price: Number(P),
127
127
  currency: I,
@@ -135,60 +135,60 @@ var y = () => {
135
135
  ...K.length > 0 ? { currencyPriceOverrides: K } : {}
136
136
  };
137
137
  try {
138
- await A(t), Z(!0), setTimeout(() => S(`/plans/${x}`), 1500);
138
+ await A(t), Z(!0), setTimeout(() => C(`/plans/${S}`), 1500);
139
139
  } catch (e) {
140
140
  Y(e instanceof Error ? e.message : "Failed to update plan");
141
141
  }
142
142
  },
143
143
  className: "space-y-6",
144
144
  children: [
145
- J && /* @__PURE__ */ h("div", {
145
+ J && /* @__PURE__ */ g("div", {
146
146
  className: "p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg",
147
- children: /* @__PURE__ */ h("p", {
147
+ children: /* @__PURE__ */ g("p", {
148
148
  className: "text-sm text-status-error-text",
149
149
  children: J
150
150
  })
151
151
  }),
152
- X && /* @__PURE__ */ h("div", {
152
+ X && /* @__PURE__ */ g("div", {
153
153
  className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
154
- children: /* @__PURE__ */ h("p", {
154
+ children: /* @__PURE__ */ g("p", {
155
155
  className: "text-sm text-status-success-text",
156
- children: b("billing.plans.updateSuccess", "Plan updated successfully! Redirecting...")
156
+ children: x("billing.plans.updateSuccess", "Plan updated successfully! Redirecting...")
157
157
  })
158
158
  }),
159
- /* @__PURE__ */ g("div", {
159
+ /* @__PURE__ */ _("div", {
160
160
  className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
161
- children: [/* @__PURE__ */ g("div", {
161
+ children: [/* @__PURE__ */ _("div", {
162
162
  className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit",
163
- children: [/* @__PURE__ */ h("div", {
163
+ children: [/* @__PURE__ */ g("div", {
164
164
  className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
165
- children: /* @__PURE__ */ h("h2", {
165
+ children: /* @__PURE__ */ g("h2", {
166
166
  className: "font-semibold text-text-primary",
167
- children: b("billing.plans.basicInfo", "Basic Information")
167
+ children: x("billing.plans.basicInfo", "Basic Information")
168
168
  })
169
- }), /* @__PURE__ */ g("div", {
169
+ }), /* @__PURE__ */ _("div", {
170
170
  className: "p-4 space-y-4",
171
171
  children: [
172
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
172
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
173
173
  htmlFor: "name",
174
174
  className: "block text-sm font-medium text-text-primary mb-1",
175
- children: [b("billing.plans.planNameLabel", "Plan Name"), " *"]
176
- }), /* @__PURE__ */ h("input", {
175
+ children: [x("billing.plans.planNameLabel", "Plan Name"), " *"]
176
+ }), /* @__PURE__ */ g("input", {
177
177
  id: "name",
178
178
  type: "text",
179
179
  value: M,
180
180
  onChange: (e) => N(e.target.value),
181
181
  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)]",
182
- placeholder: b("billing.plans.planNamePlaceholder", "e.g., Pro Plan"),
182
+ placeholder: x("billing.plans.planNamePlaceholder", "e.g., Pro Plan"),
183
183
  required: !0
184
184
  })] }),
185
- /* @__PURE__ */ g("div", {
185
+ /* @__PURE__ */ _("div", {
186
186
  className: "grid grid-cols-2 gap-4",
187
- children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
187
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
188
188
  htmlFor: "price",
189
189
  className: "block text-sm font-medium text-text-primary mb-1",
190
- children: [b("billing.plans.priceLabel", "Price"), " *"]
191
- }), /* @__PURE__ */ h("input", {
190
+ children: [x("billing.plans.priceLabel", "Price"), " *"]
191
+ }), /* @__PURE__ */ g("input", {
192
192
  id: "price",
193
193
  type: "number",
194
194
  step: "0.01",
@@ -198,148 +198,148 @@ var y = () => {
198
198
  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)]",
199
199
  placeholder: "49.99",
200
200
  required: !0
201
- })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
201
+ })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
202
202
  htmlFor: "currency",
203
203
  className: "block text-sm font-medium text-text-primary mb-1",
204
- children: [b("billing.plans.currencyLabel", "Currency"), " *"]
205
- }), /* @__PURE__ */ g("select", {
204
+ children: [x("billing.plans.currencyLabel", "Currency"), " *"]
205
+ }), /* @__PURE__ */ _("select", {
206
206
  id: "currency",
207
207
  value: I,
208
208
  onChange: (e) => L(e.target.value),
209
209
  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)]",
210
210
  children: [
211
- /* @__PURE__ */ h("option", {
211
+ /* @__PURE__ */ g("option", {
212
212
  value: "USD",
213
213
  children: "USD ($)"
214
214
  }),
215
- /* @__PURE__ */ h("option", {
215
+ /* @__PURE__ */ g("option", {
216
216
  value: "EUR",
217
217
  children: "EUR"
218
218
  }),
219
- /* @__PURE__ */ h("option", {
219
+ /* @__PURE__ */ g("option", {
220
220
  value: "GBP",
221
221
  children: "GBP"
222
222
  }),
223
- /* @__PURE__ */ h("option", {
223
+ /* @__PURE__ */ g("option", {
224
224
  value: "INR",
225
225
  children: "INR"
226
226
  }),
227
- /* @__PURE__ */ h("option", {
227
+ /* @__PURE__ */ g("option", {
228
228
  value: "AUD",
229
229
  children: "AUD"
230
230
  }),
231
- /* @__PURE__ */ h("option", {
231
+ /* @__PURE__ */ g("option", {
232
232
  value: "CAD",
233
233
  children: "CAD"
234
234
  }),
235
- /* @__PURE__ */ h("option", {
235
+ /* @__PURE__ */ g("option", {
236
236
  value: "JPY",
237
237
  children: "JPY"
238
238
  }),
239
- /* @__PURE__ */ h("option", {
239
+ /* @__PURE__ */ g("option", {
240
240
  value: "SGD",
241
241
  children: "SGD"
242
242
  })
243
243
  ]
244
244
  })] })]
245
245
  }),
246
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
246
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
247
247
  htmlFor: "duration",
248
248
  className: "block text-sm font-medium text-text-primary mb-1",
249
- children: [b("billing.plans.billingPeriodLabel", "Billing Period"), " *"]
250
- }), /* @__PURE__ */ g("select", {
249
+ children: [x("billing.plans.billingPeriodLabel", "Billing Period"), " *"]
250
+ }), /* @__PURE__ */ _("select", {
251
251
  id: "duration",
252
252
  value: R,
253
253
  onChange: (e) => z(e.target.value),
254
254
  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)]",
255
- children: [/* @__PURE__ */ h("option", {
255
+ children: [/* @__PURE__ */ g("option", {
256
256
  value: i.MONTHLY,
257
257
  children: "Monthly"
258
- }), /* @__PURE__ */ h("option", {
258
+ }), /* @__PURE__ */ g("option", {
259
259
  value: i.YEARLY,
260
260
  children: "Yearly"
261
261
  })]
262
262
  })] }),
263
- /* @__PURE__ */ g("div", {
263
+ /* @__PURE__ */ _("div", {
264
264
  className: "flex items-center gap-3",
265
- children: [/* @__PURE__ */ h("input", {
265
+ children: [/* @__PURE__ */ g("input", {
266
266
  id: "isActive",
267
267
  type: "checkbox",
268
268
  checked: B,
269
269
  onChange: (e) => V(e.target.checked),
270
270
  className: "size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]"
271
- }), /* @__PURE__ */ h("label", {
271
+ }), /* @__PURE__ */ g("label", {
272
272
  htmlFor: "isActive",
273
273
  className: "text-sm font-medium text-text-primary",
274
- children: b("billing.plans.planIsActive", "Plan is active")
274
+ children: x("billing.plans.planIsActive", "Plan is active")
275
275
  })]
276
276
  }),
277
- /* @__PURE__ */ g("div", {
277
+ /* @__PURE__ */ _("div", {
278
278
  className: "flex items-start justify-between gap-4 pt-1",
279
- children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
279
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
280
280
  className: "text-sm font-medium text-text-primary",
281
- children: b("billing.plans.isPurchasableLabel", "Publicly purchasable")
282
- }), /* @__PURE__ */ h("p", {
281
+ children: x("billing.plans.isPurchasableLabel", "Publicly purchasable")
282
+ }), /* @__PURE__ */ g("p", {
283
283
  className: "text-xs text-text-muted mt-0.5",
284
- children: H ? b("billing.plans.isPurchasableOn", "Visible on pricing page — users can subscribe directly.") : b("billing.plans.isPurchasableOff", "Internal / custom plan — hidden from pricing page, assign via API only.")
285
- })] }), /* @__PURE__ */ h("button", {
284
+ children: H ? x("billing.plans.isPurchasableOn", "Visible on pricing page — users can subscribe directly.") : x("billing.plans.isPurchasableOff", "Internal / custom plan — hidden from pricing page, assign via API only.")
285
+ })] }), /* @__PURE__ */ g("button", {
286
286
  type: "button",
287
287
  role: "switch",
288
288
  "aria-checked": H,
289
289
  onClick: () => U((e) => !e),
290
290
  className: `relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${H ? "bg-action-primary-bg" : "bg-text-muted/30"}`,
291
- children: /* @__PURE__ */ h("span", { className: `inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${H ? "translate-x-5" : "translate-x-0"}` })
291
+ children: /* @__PURE__ */ g("span", { className: `inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${H ? "translate-x-5" : "translate-x-0"}` })
292
292
  })]
293
293
  })
294
294
  ]
295
295
  })]
296
- }), /* @__PURE__ */ g("div", {
296
+ }), /* @__PURE__ */ _("div", {
297
297
  className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit",
298
- children: [/* @__PURE__ */ g("div", {
298
+ children: [/* @__PURE__ */ _("div", {
299
299
  className: "p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between",
300
- children: [/* @__PURE__ */ h("h2", {
300
+ children: [/* @__PURE__ */ g("h2", {
301
301
  className: "font-semibold text-text-primary",
302
- children: b("billing.plans.quotaAllocations", "Quota Allocations")
303
- }), /* @__PURE__ */ h("button", {
302
+ children: x("billing.plans.quotaAllocations", "Quota Allocations")
303
+ }), /* @__PURE__ */ g("button", {
304
304
  type: "button",
305
305
  onClick: Q,
306
306
  className: "px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors",
307
307
  children: "+ Add Quota"
308
308
  })]
309
- }), /* @__PURE__ */ h("div", {
309
+ }), /* @__PURE__ */ g("div", {
310
310
  className: "p-4 space-y-3",
311
- children: W.length === 0 ? /* @__PURE__ */ h("p", {
311
+ children: W.length === 0 ? /* @__PURE__ */ g("p", {
312
312
  className: "text-sm text-text-muted",
313
- children: b("billing.plans.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
313
+ children: x("billing.plans.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
314
314
  }) : W.map((e, t) => {
315
- let n = O.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
316
- return /* @__PURE__ */ g("div", {
315
+ let n = O.find((t) => t.id === e.quotaId), r = n?.limits?.type ?? "unknown";
316
+ return /* @__PURE__ */ _("div", {
317
317
  className: "p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3",
318
- children: [/* @__PURE__ */ g("div", {
318
+ children: [/* @__PURE__ */ _("div", {
319
319
  className: "flex items-center gap-3",
320
- children: [/* @__PURE__ */ g("select", {
320
+ children: [/* @__PURE__ */ _("select", {
321
321
  value: e.quotaId,
322
322
  onChange: (e) => $(t, "quotaId", e.target.value),
323
323
  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)] ${e.quotaId === "" ? "text-text-muted" : "text-text-primary"}`,
324
- children: [/* @__PURE__ */ h("option", {
324
+ children: [/* @__PURE__ */ g("option", {
325
325
  value: "",
326
326
  disabled: !0,
327
- children: b("billing.plans.selectQuota", "Select a quota...")
328
- }), O.map((e) => /* @__PURE__ */ h("option", {
327
+ children: x("billing.plans.selectQuota", "Select a quota...")
328
+ }), O.map((e) => /* @__PURE__ */ g("option", {
329
329
  value: e.id,
330
330
  children: e.name
331
331
  }, e.id))]
332
- }), /* @__PURE__ */ h("button", {
332
+ }), /* @__PURE__ */ g("button", {
333
333
  type: "button",
334
334
  onClick: () => te(t),
335
335
  className: "p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors",
336
336
  title: "Remove quota",
337
- children: /* @__PURE__ */ h("svg", {
337
+ children: /* @__PURE__ */ g("svg", {
338
338
  className: "size-5",
339
339
  fill: "none",
340
340
  viewBox: "0 0 24 24",
341
341
  stroke: "currentColor",
342
- children: /* @__PURE__ */ h("path", {
342
+ children: /* @__PURE__ */ g("path", {
343
343
  strokeLinecap: "round",
344
344
  strokeLinejoin: "round",
345
345
  strokeWidth: 2,
@@ -347,52 +347,23 @@ var y = () => {
347
347
  })
348
348
  })
349
349
  })]
350
- }), n && /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
350
+ }), n && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("div", {
351
351
  className: "flex items-center justify-between text-sm",
352
- children: /* @__PURE__ */ g("div", {
353
- className: "text-text-muted",
354
- children: [
355
- /* @__PURE__ */ g("span", {
356
- className: "capitalize",
357
- children: ["Type: ", a]
358
- }),
359
- /* @__PURE__ */ h("span", {
360
- className: "mx-2",
361
- children: "•"
362
- }),
363
- /* @__PURE__ */ g("span", { children: [
364
- "Value per unit:",
365
- " ",
366
- /* @__PURE__ */ h("span", {
367
- className: "font-medium text-text-primary",
368
- children: i.toLocaleString()
369
- })
370
- ] })
371
- ]
352
+ children: /* @__PURE__ */ _("span", {
353
+ className: "capitalize text-text-muted",
354
+ children: ["Type: ", r]
372
355
  })
373
- }), /* @__PURE__ */ g("div", {
374
- className: "flex items-center gap-4",
375
- children: [/* @__PURE__ */ g("div", {
376
- className: "flex items-center gap-2",
377
- children: [/* @__PURE__ */ g("label", {
378
- className: "text-sm text-text-muted",
379
- children: [b("billing.plans.quantity", "Quantity"), ":"]
380
- }), /* @__PURE__ */ h("input", {
381
- type: "number",
382
- min: "1",
383
- value: e.quantity,
384
- onChange: (e) => $(t, "quantity", e.target.value),
385
- className: "w-24 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"
386
- })]
387
- }), /* @__PURE__ */ g("div", {
388
- className: "flex items-center gap-2 text-sm",
389
- children: [/* @__PURE__ */ h("span", {
390
- className: "text-text-muted",
391
- children: "="
392
- }), /* @__PURE__ */ g("span", {
393
- className: "font-semibold text-status-success-text",
394
- children: [o.toLocaleString(), " total"]
395
- })]
356
+ }), /* @__PURE__ */ _("div", {
357
+ className: "flex items-center gap-2",
358
+ children: [/* @__PURE__ */ _("label", {
359
+ className: "text-sm text-text-muted",
360
+ children: [x("billing.plans.limit", "Limit"), ":"]
361
+ }), /* @__PURE__ */ g("input", {
362
+ type: "number",
363
+ min: "1",
364
+ value: e.quantity,
365
+ onChange: (e) => $(t, "quantity", e.target.value),
366
+ 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"
396
367
  })]
397
368
  })] })]
398
369
  }, t);
@@ -400,20 +371,20 @@ var y = () => {
400
371
  })]
401
372
  })]
402
373
  }),
403
- /* @__PURE__ */ h(r, {
374
+ /* @__PURE__ */ g(r, {
404
375
  overrides: K,
405
376
  onChange: q
406
377
  }),
407
- /* @__PURE__ */ g("div", {
378
+ /* @__PURE__ */ _("div", {
408
379
  className: "flex items-center gap-3 pt-4",
409
- children: [/* @__PURE__ */ h("button", {
380
+ children: [/* @__PURE__ */ g("button", {
410
381
  type: "submit",
411
382
  disabled: j,
412
383
  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",
413
- children: j ? b("billing.plans.saving", "Saving...") : b("billing.plans.saveChanges", "Save Changes")
414
- }), /* @__PURE__ */ h("button", {
384
+ children: j ? x("billing.plans.saving", "Saving...") : x("billing.plans.saveChanges", "Save Changes")
385
+ }), /* @__PURE__ */ g("button", {
415
386
  type: "button",
416
- onClick: () => S(`/plans/${x}`),
387
+ onClick: () => C(`/plans/${S}`),
417
388
  className: "px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors",
418
389
  children: "Cancel"
419
390
  })]
@@ -424,6 +395,6 @@ var y = () => {
424
395
  });
425
396
  };
426
397
  //#endregion
427
- export { y as PlanEditPage };
398
+ export { b as PlanEditPage };
428
399
 
429
400
  //# sourceMappingURL=PlanEditPage.js.map
@@ -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';\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 { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { getPlanFeatureQuantity, 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 [isPurchasable, setIsPurchasable] = 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 setIsPurchasable(plan.isPurchasable ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (plan.currencyPrices && typeof plan.currencyPrices === 'object') {\n const existing = plan.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 (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 // SubscriptionFeature now stores per-plan override in context.quantity.\n // Fall back to quota.limits.value for legacy autoseeded plans.\n const value = getPlanFeatureQuantity(feature) || 1;\n quotaMap.set(quotaId, 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-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 (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-text-primary\">\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-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover\"\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 isPurchasable,\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 <PagePurpose>\n {tr(\n 'billing.plans.editPurpose',\n \"Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.\"\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.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-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.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-text-primary 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-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.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\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"49.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.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-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.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-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.plans.planIsActive', 'Plan is active')}\n </label>\n </div>\n\n {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-text-muted mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n isPurchasable ? 'bg-action-primary-bg' : 'bg-text-muted/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\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.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-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.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-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.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-text-muted\">\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-text-primary\">\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-text-muted\">\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-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 <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-text-muted\">=</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-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.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-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":"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';\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 { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { getPlanFeatureQuantity, 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 [isPurchasable, setIsPurchasable] = 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 setIsPurchasable(plan.isPurchasable ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (plan.currencyPrices && typeof plan.currencyPrices === 'object') {\n const existing = plan.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 (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 // SubscriptionFeature now stores per-plan override in context.quantity.\n // Fall back to quota.limits.value for legacy autoseeded plans.\n const value = getPlanFeatureQuantity(feature) || 1;\n quotaMap.set(quotaId, 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-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 (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-text-primary\">\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-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover\"\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 isPurchasable,\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 <PagePurpose>\n {tr(\n 'billing.plans.editPurpose',\n \"Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.\"\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.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-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.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-text-primary 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-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.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\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\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-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"49.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.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-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.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-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.plans.planIsActive', 'Plan is active')}\n </label>\n </div>\n\n {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-text-muted mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n isPurchasable ? 'bg-action-primary-bg' : 'bg-text-muted/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\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.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-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.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 limitType = limits?.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.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 <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.plans.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.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-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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.828.4",
3
+ "version": "2026.830.1",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [