@burdenoff/microfe-billing 2026.1026.2 → 2026.1026.3

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.
@@ -8,10 +8,11 @@ import { isServerError as a } from "../../../shared/utils/error.js";
8
8
  import { getPlanFeatureQuantity as o } from "../../../shared/utils/planFeatureQuantity.js";
9
9
  import "../../../shared/utils/index.js";
10
10
  import { useBillingPermissions as s } from "../../../hooks/useBillingPermissions.js";
11
- import { useBillingNavigate as c } from "../../../hooks/useBillingNavigate.js";
12
- import { usePlan as l, usePlanMutations as u } from "../hooks/usePlans.js";
13
- import { useQuotas as d } from "../hooks/useQuotas.js";
11
+ import { useBillingNavigate as ee } from "../../../hooks/useBillingNavigate.js";
12
+ import { usePlan as c, usePlanMutations as l } from "../hooks/usePlans.js";
13
+ import { useQuotas as u } from "../hooks/useQuotas.js";
14
14
  import "../hooks/index.js";
15
+ import { getChangedCurrencyPriceOverrides as d } from "../utils/currencyPriceOverrides.js";
15
16
  import { useEffect as f, useState as p } from "react";
16
17
  import { useParams as m } from "react-router";
17
18
  import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
@@ -22,15 +23,15 @@ var b = () => {
22
23
  let { t: b } = v(), x = (e, t) => {
23
24
  let n = b(e);
24
25
  return n === e ? t : n;
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
+ }, { planId: S } = m(), C = ee(), w = s(), { plan: T, isLoading: te, error: E, refetch: D } = c(S), { quotas: O, isLoading: k } = u({ onlyActive: !0 }), { updatePlan: A, isUpdating: j } = l(), [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([]), [X, Z] = p(null), [ne, Q] = p(!1);
26
27
  if (f(() => {
27
28
  if (T) {
28
29
  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
- q(Object.entries(e).filter(([, e]) => typeof e == "number").map(([e, t]) => ({
30
+ let e = T.currencyPrices, t = Object.entries(e).filter(([, e]) => typeof e == "number").map(([e, t]) => ({
31
31
  currency: e,
32
32
  amount: t
33
- })));
33
+ }));
34
+ q(t), Y(t);
34
35
  }
35
36
  if (T.features && T.features.length > 0) {
36
37
  let e = [], t = /* @__PURE__ */ new Map();
@@ -46,7 +47,7 @@ var b = () => {
46
47
  }
47
48
  }
48
49
  }, [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
+ if (te || k) return /* @__PURE__ */ _("div", {
50
51
  className: "space-y-6 p-6",
51
52
  children: [/* @__PURE__ */ g("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ g("div", {
52
53
  className: "max-w-2xl space-y-4",
@@ -83,12 +84,12 @@ var b = () => {
83
84
  })]
84
85
  })
85
86
  });
86
- let Q = () => {
87
+ let re = () => {
87
88
  G([{
88
89
  quotaId: "",
89
90
  quantity: 1
90
91
  }, ...W]);
91
- }, te = (e) => {
92
+ }, ie = (e) => {
92
93
  G(W.filter((t, n) => n !== e));
93
94
  }, $ = (e, t, n) => {
94
95
  let r = [...W];
@@ -104,20 +105,20 @@ var b = () => {
104
105
  /* @__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
106
  /* @__PURE__ */ _("form", {
106
107
  onSubmit: async (e) => {
107
- if (e.preventDefault(), Y(null), Z(!1), !M.trim()) {
108
- Y("Plan name is required");
108
+ if (e.preventDefault(), Z(null), Q(!1), !M.trim()) {
109
+ Z("Plan name is required");
109
110
  return;
110
111
  }
111
112
  if (!P || isNaN(Number(P)) || Number(P) < 0) {
112
- Y("Valid price is required");
113
+ Z("Valid price is required");
113
114
  return;
114
115
  }
115
116
  if (W.length === 0) {
116
- Y("At least one quota is required");
117
+ Z("At least one quota is required");
117
118
  return;
118
119
  }
119
120
  if (W.filter((e) => e.quotaId === "").length > 0) {
120
- Y("Please select a quota for all allocations");
121
+ Z("Please select a quota for all allocations");
121
122
  return;
122
123
  }
123
124
  let t = {
@@ -132,24 +133,24 @@ var b = () => {
132
133
  quotaId: e.quotaId,
133
134
  quantity: e.quantity
134
135
  })),
135
- ...K.length > 0 ? { currencyPriceOverrides: K } : {}
136
+ ...d(J, K).length > 0 ? { currencyPriceOverrides: d(J, K) } : {}
136
137
  };
137
138
  try {
138
- await A(t), Z(!0), setTimeout(() => C(`/plans/${S}`), 1500);
139
+ await A(t), Q(!0), setTimeout(() => C(`/plans/${S}`), 1500);
139
140
  } catch (e) {
140
- Y(e instanceof Error ? e.message : "Failed to update plan");
141
+ Z(e instanceof Error ? e.message : "Failed to update plan");
141
142
  }
142
143
  },
143
144
  className: "space-y-6",
144
145
  children: [
145
- J && /* @__PURE__ */ g("div", {
146
+ X && /* @__PURE__ */ g("div", {
146
147
  className: "p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg",
147
148
  children: /* @__PURE__ */ g("p", {
148
149
  className: "text-sm text-status-error-text",
149
- children: J
150
+ children: X
150
151
  })
151
152
  }),
152
- X && /* @__PURE__ */ g("div", {
153
+ ne && /* @__PURE__ */ g("div", {
153
154
  className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
154
155
  children: /* @__PURE__ */ g("p", {
155
156
  className: "text-sm text-status-success-text",
@@ -302,7 +303,7 @@ var b = () => {
302
303
  children: x("billing.plans.quotaAllocations", "Quota Allocations")
303
304
  }), /* @__PURE__ */ g("button", {
304
305
  type: "button",
305
- onClick: Q,
306
+ onClick: re,
306
307
  className: "px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors",
307
308
  children: "+ Add Quota"
308
309
  })]
@@ -331,7 +332,7 @@ var b = () => {
331
332
  }, e.id))]
332
333
  }), /* @__PURE__ */ g("button", {
333
334
  type: "button",
334
- onClick: () => te(t),
335
+ onClick: () => ie(t),
335
336
  className: "p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors",
336
337
  title: "Remove quota",
337
338
  children: /* @__PURE__ */ g("svg", {
@@ -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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary 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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"}
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';\nimport { getChangedCurrencyPriceOverrides } from '../utils/currencyPriceOverrides';\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 [initialCurrencyPriceOverrides, setInitialCurrencyPriceOverrides] = 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 const overrides = Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }));\n setCurrencyPriceOverrides(overrides);\n setInitialCurrencyPriceOverrides(overrides);\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 ...(getChangedCurrencyPriceOverrides(initialCurrencyPriceOverrides, currencyPriceOverrides)\n .length > 0\n ? {\n currencyPriceOverrides: getChangedCurrencyPriceOverrides(\n initialCurrencyPriceOverrides,\n currencyPriceOverrides\n ),\n }\n : {}),\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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary 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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"}
@@ -0,0 +1,9 @@
1
+ //#region src/billing/modules/plans/utils/currencyPriceOverrides.ts
2
+ function e(e, t) {
3
+ let n = new Map(e.map((e) => [e.currency, e.amount]));
4
+ return t.filter((e) => n.get(e.currency) !== e.amount);
5
+ }
6
+ //#endregion
7
+ export { e as getChangedCurrencyPriceOverrides };
8
+
9
+ //# sourceMappingURL=currencyPriceOverrides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"currencyPriceOverrides.js","names":[],"sources":["../../../../../src/billing/modules/plans/utils/currencyPriceOverrides.ts"],"sourcesContent":["import type { CurrencyPriceOverrideInput } from '../../../shared/types';\n\nexport function getChangedCurrencyPriceOverrides(\n initial: CurrencyPriceOverrideInput[],\n current: CurrencyPriceOverrideInput[]\n): CurrencyPriceOverrideInput[] {\n const initialByCurrency = new Map(\n initial.map((override) => [override.currency, override.amount])\n );\n\n return current.filter((override) => initialByCurrency.get(override.currency) !== override.amount);\n}\n"],"mappings":";AAEA,SAAgB,EACd,GACA,GAC8B;CAC9B,IAAM,IAAoB,IAAI,IAC5B,EAAQ,KAAK,MAAa,CAAC,EAAS,UAAU,EAAS,MAAM,CAAC,CAChE;CAEA,OAAO,EAAQ,QAAQ,MAAa,EAAkB,IAAI,EAAS,QAAQ,MAAM,EAAS,MAAM;AAClG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.1026.2",
3
+ "version": "2026.1026.3",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [