@burdenoff/microfe-billing 2026.1023.1 → 2026.1023.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,11 +42,11 @@ var O = () => {
42
42
  let { t: r } = C(), i = (e, t) => {
43
43
  let n = r(e);
44
44
  return n === e ? t : n;
45
- }, a = c(), { productId: s } = e(), l = g(s), { plans: u, isLoading: f, error: p, refetch: m } = d({
45
+ }, a = c(), { productId: s, plansDocsUrl: l } = e(), u = g(s), { plans: f, isLoading: p, error: m, refetch: h } = d({
46
46
  includeFree: !1,
47
47
  productId: s ?? void 0
48
- }), [h, v] = _("grid"), [y, b] = _(n.MONTHLY), E = u.filter((e) => e.isActive && e.isPurchasable !== !1 && e.duration === y), D = E.length >= 2 ? 1 : -1;
49
- return f && u.length === 0 ? /* @__PURE__ */ S("div", {
48
+ }), [v, y] = _("grid"), [b, E] = _(n.MONTHLY), D = f.filter((e) => e.isActive && e.isPurchasable !== !1 && e.duration === b), O = D.length >= 2 ? 1 : -1;
49
+ return p && f.length === 0 ? /* @__PURE__ */ S("div", {
50
50
  className: "space-y-6 px-6 lg:px-8 py-6",
51
51
  children: [/* @__PURE__ */ x("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded-card" }), /* @__PURE__ */ x("div", {
52
52
  className: "grid grid-cols-1 md:grid-cols-3 gap-6",
@@ -70,12 +70,12 @@ var O = () => {
70
70
  ]
71
71
  }, e))
72
72
  })]
73
- }) : p && o(p) ? /* @__PURE__ */ x("div", {
73
+ }) : m && o(m) ? /* @__PURE__ */ x("div", {
74
74
  className: "p-6",
75
75
  children: /* @__PURE__ */ x(t, {
76
76
  title: i("billing.plans.serverUnavailable", "Server Unavailable"),
77
77
  message: i("billing.plans.unableToLoadPlans", "Unable to load plans."),
78
- onRetry: () => m(),
78
+ onRetry: () => h(),
79
79
  showRetry: !0
80
80
  })
81
81
  }) : /* @__PURE__ */ S("div", {
@@ -92,6 +92,8 @@ var O = () => {
92
92
  }),
93
93
  /* @__PURE__ */ x(T, {
94
94
  className: "mt-3",
95
+ learnMoreHref: l,
96
+ learnMoreLabel: i("billing.plans.learnMore", "Learn more"),
95
97
  children: i("billing.plans.browsePurpose", "Compare what each plan includes — quotas, features and price — and subscribe to the one that fits your team. Switch between monthly and yearly billing to see the difference, or contact us for a custom Enterprise contract.")
96
98
  })
97
99
  ] }),
@@ -101,16 +103,16 @@ var O = () => {
101
103
  className: "inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken",
102
104
  children: [n.MONTHLY, n.YEARLY].map((e) => /* @__PURE__ */ x("button", {
103
105
  type: "button",
104
- onClick: () => b(e),
105
- className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${y === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
106
+ onClick: () => E(e),
107
+ className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${b === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
106
108
  children: e === n.MONTHLY ? "Monthly" : "Yearly"
107
109
  }, e))
108
110
  }), /* @__PURE__ */ S("div", {
109
111
  className: "ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken",
110
112
  children: [/* @__PURE__ */ x("button", {
111
113
  type: "button",
112
- onClick: () => v("grid"),
113
- className: `p-1.5 rounded-button transition-colors ${h === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
114
+ onClick: () => y("grid"),
115
+ className: `p-1.5 rounded-button transition-colors ${v === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
114
116
  title: "Grid view",
115
117
  children: /* @__PURE__ */ x("svg", {
116
118
  className: "size-4",
@@ -126,8 +128,8 @@ var O = () => {
126
128
  })
127
129
  }), /* @__PURE__ */ x("button", {
128
130
  type: "button",
129
- onClick: () => v("list"),
130
- className: `p-1.5 rounded-button transition-colors ${h === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
131
+ onClick: () => y("list"),
132
+ className: `p-1.5 rounded-button transition-colors ${v === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
131
133
  title: "List view",
132
134
  children: /* @__PURE__ */ x("svg", {
133
135
  className: "size-4",
@@ -144,25 +146,25 @@ var O = () => {
144
146
  })]
145
147
  })]
146
148
  }),
147
- E.length === 0 ? /* @__PURE__ */ x(w, {
149
+ D.length === 0 ? /* @__PURE__ */ x(w, {
148
150
  illustration: "empty-data",
149
151
  title: i("billing.plans.noPlansAvailable", "No plans available"),
150
152
  description: i("billing.plans.checkBackLater", "Check back later for new plans.")
151
- }) : h === "grid" ? /* @__PURE__ */ S("div", {
153
+ }) : v === "grid" ? /* @__PURE__ */ S("div", {
152
154
  className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch",
153
- children: [E.map((e, t) => /* @__PURE__ */ x(j, {
155
+ children: [D.map((e, t) => /* @__PURE__ */ x(j, {
154
156
  plan: e,
155
- highlighted: t === D,
157
+ highlighted: t === O,
156
158
  onView: () => a(`/plans/${e.id}`),
157
159
  onSubscribe: () => a(`/checkout/${e.id}`)
158
- }, e.id)), /* @__PURE__ */ x(k, { offer: l })]
160
+ }, e.id)), /* @__PURE__ */ x(k, { offer: u })]
159
161
  }) : /* @__PURE__ */ S("div", {
160
162
  className: "space-y-3",
161
- children: [E.map((e) => /* @__PURE__ */ x(M, {
163
+ children: [D.map((e) => /* @__PURE__ */ x(M, {
162
164
  plan: e,
163
165
  onView: () => a(`/plans/${e.id}`),
164
166
  onSubscribe: () => a(`/checkout/${e.id}`)
165
- }, e.id)), /* @__PURE__ */ x(A, { offer: l })]
167
+ }, e.id)), /* @__PURE__ */ x(A, { offer: u })]
166
168
  })
167
169
  ]
168
170
  });
@@ -1 +1 @@
1
- {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { Check, ArrowRight, Sparkles } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport { PlanDuration } from '../../../shared/types';\nimport type { Plan, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\nimport {\n FEATURED_QUOTA_NAMES,\n FEATURED_QUOTA_LABELS,\n formatFeaturedQuotaValue,\n} from '../constants/featuredQuotas';\nimport {\n enterpriseMailto,\n getEnterpriseOffer,\n type EnterpriseOffer,\n} from '../constants/enterprise';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\n/** Returns only the featured quotas, in the canonical display order. */\nfunction getFeaturedQuotas(features: PlanFeature[]): CombinedQuota[] {\n const all = combineQuotas(features);\n const byName = new Map(all.map((q) => [q.name, q]));\n return FEATURED_QUOTA_NAMES.flatMap((name) => {\n const q = byName.get(name);\n return q ? [q] : [];\n });\n}\n\nexport const PlansBrowsePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const { productId } = useBilling();\n const enterpriseOffer = getEnterpriseOffer(productId);\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration>(PlanDuration.MONTHLY);\n\n // Only show active, publicly purchasable plans to users\n const filteredPlans = plans.filter(\n (plan) => plan.isActive && plan.isPurchasable !== false && plan.duration === billingInterval\n );\n\n // Spotlight a recommended tier (the second plan when there are 2+, the common\n // \"mid / Pro\" sweet-spot) so the eye lands on a clear primary choice.\n const recommendedIndex = filteredPlans.length >= 2 ? 1 : -1;\n\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded-card\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4\"\n >\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.browsePurpose',\n 'Compare what each plan includes — quotas, features and price — and subscribe to the one that fits your team. Switch between monthly and yearly billing to see the difference, or contact us for a custom Enterprise contract.'\n )}\n </PagePurpose>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {([PlanDuration.MONTHLY, PlanDuration.YEARLY] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n billingInterval === interval\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {interval === PlanDuration.MONTHLY ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.plans.noPlansAvailable', 'No plans available')}\n description={tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n />\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch\">\n {filteredPlans.map((plan, idx) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n highlighted={idx === recommendedIndex}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseCard offer={enterpriseOffer} />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseRow offer={enterpriseOffer} />\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Static Enterprise \"Contact Us\" card — no backend plan record\n// ============================================================================\n\nconst EnterpriseCard: FC<{ offer: EnterpriseOffer }> = ({ offer }) => (\n <div className=\"border border-border-strong bg-[var(--color-accent-soft)] rounded-card shadow-elevation-1 p-5 flex flex-col h-full\">\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link\">\n Contact Us\n </span>\n </div>\n <span className=\"text-sm text-text-secondary\">Custom contract</span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-semibold text-text-primary\">Custom pricing</span>\n </div>\n\n <ul className=\"space-y-2 mb-4 flex-1\">\n {offer.highlights.map((item) => (\n <li key={item} className=\"flex items-center gap-2 text-sm\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n <span className=\"text-text-primary\">{item}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <a\n href={enterpriseMailto(offer)}\n className=\"block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Contact Us\n </a>\n </div>\n </div>\n);\n\nconst EnterpriseRow: FC<{ offer: EnterpriseOffer }> = ({ offer }) => (\n <div className=\"flex items-center gap-4 p-5 border border-border-strong rounded-card bg-[var(--color-accent-soft)] shadow-elevation-1 transition-all duration-200 hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center shrink-0 shadow-elevation-1\">\n <svg className=\"size-5 text-text-link\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link shrink-0\">\n Contact Us\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">{offer.summary}</p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">Custom pricing</div>\n <div className=\"text-sm text-text-secondary\">contract-based</div>\n </div>\n\n <a\n href={enterpriseMailto(offer)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 shrink-0\"\n >\n Contact Us\n </a>\n </div>\n);\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{\n plan: Plan;\n highlighted?: boolean;\n onView: () => void;\n onSubscribe: () => void;\n}> = ({ plan, highlighted = false, onView, onSubscribe }) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const allQuotas = combineQuotas(plan.features || []);\n const extraCount = allQuotas.length - featuredQuotas.length;\n const displayCurrency = useDefaultBillingCurrency();\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n\n return (\n <div\n className={`relative flex flex-col h-full rounded-card bg-bg-surface p-6 transition-[border-color,box-shadow] duration-200 ${\n highlighted\n ? 'border-2 border-action-primary-bg shadow-[var(--shadow-glow)]'\n : 'border border-border-subtle shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2'\n }`}\n >\n {highlighted && (\n <span className=\"absolute -top-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1 rounded-full bg-action-primary-bg px-3 py-1 text-xs font-semibold text-action-primary-text shadow-elevation-2\">\n <Sparkles className=\"size-3.5\" aria-hidden=\"true\" />\n Most popular\n </span>\n )}\n\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{plan.name}</h3>\n <span className=\"text-sm text-text-secondary\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1.5 mb-5\">\n <span className=\"text-4xl font-bold tabular-nums tracking-tight text-text-primary\">\n {formatCurrency(price, currency)}\n </span>\n <span className=\"text-text-secondary text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {featuredQuotas.length > 0 && (\n <ul className=\"space-y-2.5 mb-5 flex-1\">\n {featuredQuotas.map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"flex items-center gap-2 min-w-0\">\n <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Check className=\"size-3 text-action-primary-bg\" aria-hidden=\"true\" />\n </span>\n <span className=\"truncate text-text-primary\">\n {FEATURED_QUOTA_LABELS[quota.name] ?? quota.name}\n </span>\n </span>\n <span className=\"font-semibold text-text-primary tabular-nums shrink-0\">\n {formatFeaturedQuotaValue(quota.name, quota.totalValue)}\n </span>\n </li>\n ))}\n {extraCount > 0 && (\n <li className=\"text-sm text-text-secondary pl-7\">+{extraCount} more — see details</li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"group inline-flex w-full items-center justify-center gap-2 rounded-button bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n Subscribe\n <ArrowRight className=\"size-4 transition-transform duration-200\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-secondary rounded-button hover:bg-bg-sunken hover:text-text-primary hover:border-border-strong transition-colors duration-200\"\n >\n View details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-5 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 transition-all duration-200 hover:border-border-strong hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate\">{plan.name}</h3>\n <p className=\"text-sm text-text-secondary\">\n {featuredQuotas.length} key feature{featuredQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-secondary\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n View\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { Check, ArrowRight, Sparkles } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport { PlanDuration } from '../../../shared/types';\nimport type { Plan, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\nimport {\n FEATURED_QUOTA_NAMES,\n FEATURED_QUOTA_LABELS,\n formatFeaturedQuotaValue,\n} from '../constants/featuredQuotas';\nimport {\n enterpriseMailto,\n getEnterpriseOffer,\n type EnterpriseOffer,\n} from '../constants/enterprise';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\n/** Returns only the featured quotas, in the canonical display order. */\nfunction getFeaturedQuotas(features: PlanFeature[]): CombinedQuota[] {\n const all = combineQuotas(features);\n const byName = new Map(all.map((q) => [q.name, q]));\n return FEATURED_QUOTA_NAMES.flatMap((name) => {\n const q = byName.get(name);\n return q ? [q] : [];\n });\n}\n\nexport const PlansBrowsePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const { productId, plansDocsUrl } = useBilling();\n const enterpriseOffer = getEnterpriseOffer(productId);\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration>(PlanDuration.MONTHLY);\n\n // Only show active, publicly purchasable plans to users\n const filteredPlans = plans.filter(\n (plan) => plan.isActive && plan.isPurchasable !== false && plan.duration === billingInterval\n );\n\n // Spotlight a recommended tier (the second plan when there are 2+, the common\n // \"mid / Pro\" sweet-spot) so the eye lands on a clear primary choice.\n const recommendedIndex = filteredPlans.length >= 2 ? 1 : -1;\n\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded-card\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4\"\n >\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n <PagePurpose\n className=\"mt-3\"\n learnMoreHref={plansDocsUrl}\n learnMoreLabel={tr('billing.plans.learnMore', 'Learn more')}\n >\n {tr(\n 'billing.plans.browsePurpose',\n 'Compare what each plan includes — quotas, features and price — and subscribe to the one that fits your team. Switch between monthly and yearly billing to see the difference, or contact us for a custom Enterprise contract.'\n )}\n </PagePurpose>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {([PlanDuration.MONTHLY, PlanDuration.YEARLY] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n billingInterval === interval\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {interval === PlanDuration.MONTHLY ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.plans.noPlansAvailable', 'No plans available')}\n description={tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n />\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch\">\n {filteredPlans.map((plan, idx) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n highlighted={idx === recommendedIndex}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseCard offer={enterpriseOffer} />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseRow offer={enterpriseOffer} />\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Static Enterprise \"Contact Us\" card — no backend plan record\n// ============================================================================\n\nconst EnterpriseCard: FC<{ offer: EnterpriseOffer }> = ({ offer }) => (\n <div className=\"border border-border-strong bg-[var(--color-accent-soft)] rounded-card shadow-elevation-1 p-5 flex flex-col h-full\">\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link\">\n Contact Us\n </span>\n </div>\n <span className=\"text-sm text-text-secondary\">Custom contract</span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-semibold text-text-primary\">Custom pricing</span>\n </div>\n\n <ul className=\"space-y-2 mb-4 flex-1\">\n {offer.highlights.map((item) => (\n <li key={item} className=\"flex items-center gap-2 text-sm\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n <span className=\"text-text-primary\">{item}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <a\n href={enterpriseMailto(offer)}\n className=\"block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Contact Us\n </a>\n </div>\n </div>\n);\n\nconst EnterpriseRow: FC<{ offer: EnterpriseOffer }> = ({ offer }) => (\n <div className=\"flex items-center gap-4 p-5 border border-border-strong rounded-card bg-[var(--color-accent-soft)] shadow-elevation-1 transition-all duration-200 hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center shrink-0 shadow-elevation-1\">\n <svg className=\"size-5 text-text-link\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link shrink-0\">\n Contact Us\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">{offer.summary}</p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">Custom pricing</div>\n <div className=\"text-sm text-text-secondary\">contract-based</div>\n </div>\n\n <a\n href={enterpriseMailto(offer)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 shrink-0\"\n >\n Contact Us\n </a>\n </div>\n);\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{\n plan: Plan;\n highlighted?: boolean;\n onView: () => void;\n onSubscribe: () => void;\n}> = ({ plan, highlighted = false, onView, onSubscribe }) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const allQuotas = combineQuotas(plan.features || []);\n const extraCount = allQuotas.length - featuredQuotas.length;\n const displayCurrency = useDefaultBillingCurrency();\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n\n return (\n <div\n className={`relative flex flex-col h-full rounded-card bg-bg-surface p-6 transition-[border-color,box-shadow] duration-200 ${\n highlighted\n ? 'border-2 border-action-primary-bg shadow-[var(--shadow-glow)]'\n : 'border border-border-subtle shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2'\n }`}\n >\n {highlighted && (\n <span className=\"absolute -top-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1 rounded-full bg-action-primary-bg px-3 py-1 text-xs font-semibold text-action-primary-text shadow-elevation-2\">\n <Sparkles className=\"size-3.5\" aria-hidden=\"true\" />\n Most popular\n </span>\n )}\n\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{plan.name}</h3>\n <span className=\"text-sm text-text-secondary\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1.5 mb-5\">\n <span className=\"text-4xl font-bold tabular-nums tracking-tight text-text-primary\">\n {formatCurrency(price, currency)}\n </span>\n <span className=\"text-text-secondary text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {featuredQuotas.length > 0 && (\n <ul className=\"space-y-2.5 mb-5 flex-1\">\n {featuredQuotas.map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"flex items-center gap-2 min-w-0\">\n <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Check className=\"size-3 text-action-primary-bg\" aria-hidden=\"true\" />\n </span>\n <span className=\"truncate text-text-primary\">\n {FEATURED_QUOTA_LABELS[quota.name] ?? quota.name}\n </span>\n </span>\n <span className=\"font-semibold text-text-primary tabular-nums shrink-0\">\n {formatFeaturedQuotaValue(quota.name, quota.totalValue)}\n </span>\n </li>\n ))}\n {extraCount > 0 && (\n <li className=\"text-sm text-text-secondary pl-7\">+{extraCount} more — see details</li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"group inline-flex w-full items-center justify-center gap-2 rounded-button bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n Subscribe\n <ArrowRight className=\"size-4 transition-transform duration-200\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-secondary rounded-button hover:bg-bg-sunken hover:text-text-primary hover:border-border-strong transition-colors duration-200\"\n >\n View details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-5 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 transition-all duration-200 hover:border-border-strong hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate\">{plan.name}</h3>\n <p className=\"text-sm text-text-secondary\">\n {featuredQuotas.length} key feature{featuredQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-secondary\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n View\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -7,32 +7,33 @@ var a = e(null), o = () => {
7
7
  let e = t(a);
8
8
  if (!e) throw Error("useBilling must be used within BillingProvider");
9
9
  return e;
10
- }, s = ({ children: e, basePath: t, navigate: o, currentUser: s, workspaceId: c, orgId: l, productId: u, apiGatewayUrl: d, authToken: f, stripePublishableKey: p, razorpayKey: m, onSubscriptionCreate: h, onSubscriptionUpdate: g, onPaymentProcess: _, registerNavItems: v, registerFooterItems: y }) => {
11
- let b = {
10
+ }, s = ({ children: e, basePath: t, navigate: o, currentUser: s, workspaceId: c, orgId: l, productId: u, plansDocsUrl: d, apiGatewayUrl: f, authToken: p, stripePublishableKey: m, razorpayKey: h, onSubscriptionCreate: g, onSubscriptionUpdate: _, onPaymentProcess: v, registerNavItems: y, registerFooterItems: b }) => {
11
+ let x = {
12
12
  basePath: t,
13
13
  navigate: o,
14
14
  currentUser: s,
15
15
  workspaceId: c,
16
16
  orgId: l,
17
17
  productId: u,
18
- apiGatewayUrl: d,
19
- authToken: f,
20
- stripePublishableKey: p,
21
- razorpayKey: m,
22
- onSubscriptionCreate: h,
23
- onSubscriptionUpdate: g,
24
- onPaymentProcess: _,
25
- registerNavItems: v,
26
- registerFooterItems: y
27
- }, x = /* @__PURE__ */ i(a.Provider, {
28
- value: b,
18
+ plansDocsUrl: d,
19
+ apiGatewayUrl: f,
20
+ authToken: p,
21
+ stripePublishableKey: m,
22
+ razorpayKey: h,
23
+ onSubscriptionCreate: g,
24
+ onSubscriptionUpdate: _,
25
+ onPaymentProcess: v,
26
+ registerNavItems: y,
27
+ registerFooterItems: b
28
+ }, S = /* @__PURE__ */ i(a.Provider, {
29
+ value: x,
29
30
  children: /* @__PURE__ */ i(r, {
30
31
  scopeId: l,
31
32
  gateway: "global",
32
33
  children: e
33
34
  })
34
35
  });
35
- return t ? x : /* @__PURE__ */ i(n, { children: x });
36
+ return t ? S : /* @__PURE__ */ i(n, { children: S });
36
37
  };
37
38
  //#endregion
38
39
  export { s as BillingProvider, o as useBilling };
@@ -1 +1 @@
1
- {"version":3,"file":"BillingProvider.js","names":[],"sources":["../../../src/billing/providers/BillingProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { BillingRootProps } from '../types';\n\n/**\n * Context value for Billing microfrontend\n */\ntype BillingContextValue = BillingRootProps;\n\nconst BillingContext = createContext<BillingContextValue | null>(null);\n\n/**\n * Hook to access Billing context\n */\nexport const useBilling = () => {\n const context = useContext(BillingContext);\n if (!context) {\n throw new Error('useBilling must be used within BillingProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Billing microfrontend\n * Sets up routing and context\n *\n * When integrated into a shell (basePath provided), the shell already has a router\n * so we don't create another one. When standalone (no basePath), use MemoryRouter.\n */\nexport const BillingProvider: FC<PropsWithChildren<BillingRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n}) => {\n const contextValue: BillingContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n };\n\n const content = (\n <BillingContext.Provider value={contextValue}>\n <PermissionProvider scopeId={orgId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </BillingContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAiB,EAA0C,IAAI,GAKxD,UAAmB;CAC9B,IAAM,IAAU,EAAW,CAAc;CACzC,IAAI,CAAC,GACH,MAAU,MAAM,gDAAgD;CAElE,OAAO;AACT,GASa,KAA4D,EACvE,aACA,aACA,aACA,gBACA,gBACA,UACA,cACA,kBACA,cACA,yBACA,gBACA,yBACA,yBACA,qBACA,qBACA,6BACI;CACJ,IAAM,IAAoC;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GAEM,IACJ,kBAAC,EAAe,UAAhB;EAAyB,OAAO;YAC9B,kBAAC,GAAD;GAAoB,SAAS;GAAO,SAAQ;GACzC;EACiB,CAAA;CACG,CAAA;CAU3B,OALI,IACK,IAIF,kBAAC,GAAD,EAAA,UAAe,EAAsB,CAAA;AAC9C"}
1
+ {"version":3,"file":"BillingProvider.js","names":[],"sources":["../../../src/billing/providers/BillingProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { BillingRootProps } from '../types';\n\n/**\n * Context value for Billing microfrontend\n */\ntype BillingContextValue = BillingRootProps;\n\nconst BillingContext = createContext<BillingContextValue | null>(null);\n\n/**\n * Hook to access Billing context\n */\nexport const useBilling = () => {\n const context = useContext(BillingContext);\n if (!context) {\n throw new Error('useBilling must be used within BillingProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Billing microfrontend\n * Sets up routing and context\n *\n * When integrated into a shell (basePath provided), the shell already has a router\n * so we don't create another one. When standalone (no basePath), use MemoryRouter.\n */\nexport const BillingProvider: FC<PropsWithChildren<BillingRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n plansDocsUrl,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n}) => {\n const contextValue: BillingContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n plansDocsUrl,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n };\n\n const content = (\n <BillingContext.Provider value={contextValue}>\n <PermissionProvider scopeId={orgId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </BillingContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAiB,EAA0C,IAAI,GAKxD,UAAmB;CAC9B,IAAM,IAAU,EAAW,CAAc;CACzC,IAAI,CAAC,GACH,MAAU,MAAM,gDAAgD;CAElE,OAAO;AACT,GASa,KAA4D,EACvE,aACA,aACA,aACA,gBACA,gBACA,UACA,cACA,iBACA,kBACA,cACA,yBACA,gBACA,yBACA,yBACA,qBACA,qBACA,6BACI;CACJ,IAAM,IAAoC;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GAEM,IACJ,kBAAC,EAAe,UAAhB;EAAyB,OAAO;YAC9B,kBAAC,GAAD;GAAoB,SAAS;GAAO,SAAQ;GACzC;EACiB,CAAA;CACG,CAAA;CAU3B,OALI,IACK,IAIF,kBAAC,GAAD,EAAA,UAAe,EAAsB,CAAA;AAC9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.1023.1",
3
+ "version": "2026.1023.2",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [