@burdenoff/website-sdk 2026.728.1 → 2026.728.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.
@@ -35,6 +35,7 @@ interface PlanCard {
35
35
  features: Array<{
36
36
  id: string;
37
37
  quotaId?: string | null;
38
+ context?: unknown;
38
39
  quota?: {
39
40
  id: string;
40
41
  name: string;
@@ -239,6 +240,33 @@ declare function resolveEnterprisePlans<T extends {
239
240
  }): Array<T | {
240
241
  name: string;
241
242
  }>;
243
+ /**
244
+ * Transform a plan feature (quota link) into a human-readable string.
245
+ *
246
+ * BOFF-4206: the per-PLAN quantity lives on the SubscriptionFeature's own
247
+ * `context` (e.g. `{ quantity: 250, unit: 'profiles' }`) — `quota.limits` is
248
+ * the SHARED Quota row's value, set once at quota-creation time and
249
+ * IDENTICAL for every plan that links that quota. Reading `quota.limits`
250
+ * here made every tier's card show the same numbers: live on TimeCampus's
251
+ * /pricing, Starter/Growth/Business all rendered Free's "1 Projects" and "2
252
+ * Admin Seats" instead of their real, higher, per-tier limits. Prefer the
253
+ * per-feature `context.quantity`; fall back to `quota.limits.value` only
254
+ * when a feature genuinely carries no context (legacy rows seeded before
255
+ * this convention, or a quota linked without a context payload).
256
+ *
257
+ * Exported (pure, no React/hooks) so this exact precedence is unit-testable
258
+ * without a DOM/render harness, matching `splitPlansByAudience` /
259
+ * `resolveEnterprisePlans` above.
260
+ */
261
+ declare function transformFeatureToLabel(feature: {
262
+ quotaId?: string | null;
263
+ context?: unknown;
264
+ quota?: {
265
+ id: string;
266
+ name: string;
267
+ limits?: unknown;
268
+ } | null;
269
+ }): string;
242
270
  /**
243
271
  * Pricing Section Component - Embeddable
244
272
  */
@@ -248,4 +276,4 @@ declare function PricingSection(props: PricingProps): react_jsx_runtime.JSX.Elem
248
276
  */
249
277
  declare function PricingPage(props: PricingProps): react_jsx_runtime.JSX.Element;
250
278
 
251
- export { type PlanCard, PricingPage, type PricingProps, PricingSection, resolveEnterprisePlans, splitPlansByAudience };
279
+ export { type PlanCard, PricingPage, type PricingProps, PricingSection, resolveEnterprisePlans, splitPlansByAudience, transformFeatureToLabel };
@@ -35,6 +35,7 @@ interface PlanCard {
35
35
  features: Array<{
36
36
  id: string;
37
37
  quotaId?: string | null;
38
+ context?: unknown;
38
39
  quota?: {
39
40
  id: string;
40
41
  name: string;
@@ -239,6 +240,33 @@ declare function resolveEnterprisePlans<T extends {
239
240
  }): Array<T | {
240
241
  name: string;
241
242
  }>;
243
+ /**
244
+ * Transform a plan feature (quota link) into a human-readable string.
245
+ *
246
+ * BOFF-4206: the per-PLAN quantity lives on the SubscriptionFeature's own
247
+ * `context` (e.g. `{ quantity: 250, unit: 'profiles' }`) — `quota.limits` is
248
+ * the SHARED Quota row's value, set once at quota-creation time and
249
+ * IDENTICAL for every plan that links that quota. Reading `quota.limits`
250
+ * here made every tier's card show the same numbers: live on TimeCampus's
251
+ * /pricing, Starter/Growth/Business all rendered Free's "1 Projects" and "2
252
+ * Admin Seats" instead of their real, higher, per-tier limits. Prefer the
253
+ * per-feature `context.quantity`; fall back to `quota.limits.value` only
254
+ * when a feature genuinely carries no context (legacy rows seeded before
255
+ * this convention, or a quota linked without a context payload).
256
+ *
257
+ * Exported (pure, no React/hooks) so this exact precedence is unit-testable
258
+ * without a DOM/render harness, matching `splitPlansByAudience` /
259
+ * `resolveEnterprisePlans` above.
260
+ */
261
+ declare function transformFeatureToLabel(feature: {
262
+ quotaId?: string | null;
263
+ context?: unknown;
264
+ quota?: {
265
+ id: string;
266
+ name: string;
267
+ limits?: unknown;
268
+ } | null;
269
+ }): string;
242
270
  /**
243
271
  * Pricing Section Component - Embeddable
244
272
  */
@@ -248,4 +276,4 @@ declare function PricingSection(props: PricingProps): react_jsx_runtime.JSX.Elem
248
276
  */
249
277
  declare function PricingPage(props: PricingProps): react_jsx_runtime.JSX.Element;
250
278
 
251
- export { type PlanCard, PricingPage, type PricingProps, PricingSection, resolveEnterprisePlans, splitPlansByAudience };
279
+ export { type PlanCard, PricingPage, type PricingProps, PricingSection, resolveEnterprisePlans, splitPlansByAudience, transformFeatureToLabel };
@@ -334,6 +334,7 @@ var GET_PLANS_QUERY = `
334
334
  features {
335
335
  id
336
336
  quotaId
337
+ context
337
338
  quota {
338
339
  id
339
340
  name
@@ -446,27 +447,27 @@ function humanizeQuotaName(name) {
446
447
  const resource = parts.length >= 3 ? parts[parts.length - 2] : parts[parts.length - 1];
447
448
  return resource.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
448
449
  }
449
- function transformQuotaToFeature(quota) {
450
+ function transformFeatureToLabel(feature) {
451
+ const quota = feature.quota;
452
+ if (!quota) return "";
450
453
  const { name, limits } = quota;
451
- if (!limits || typeof limits !== "object") {
452
- return humanizeQuotaName(name);
453
- }
454
- const limitsObj = limits;
455
- const value = limitsObj.value;
456
- const unit = limitsObj.unit;
457
- const type = limitsObj.type;
458
- const description = limitsObj.description;
459
- if (typeof description === "string" && description) {
454
+ const featureContext = feature.context && typeof feature.context === "object" ? feature.context : null;
455
+ const limitsObj = limits && typeof limits === "object" ? limits : null;
456
+ const value = typeof featureContext?.quantity === "number" ? featureContext.quantity : typeof limitsObj?.value === "number" ? limitsObj.value : void 0;
457
+ const unit = typeof featureContext?.unit === "string" && featureContext.unit || typeof limitsObj?.unit === "string" && limitsObj.unit || void 0;
458
+ const description = typeof featureContext?.description === "string" && featureContext.description || typeof limitsObj?.description === "string" && limitsObj.description || void 0;
459
+ const type = limitsObj?.type;
460
+ if (description) {
460
461
  if (type === "number" && typeof value === "number") {
461
462
  return `${value.toLocaleString()} ${description}`;
462
463
  }
463
464
  return description;
464
465
  }
465
- if (typeof unit === "string" && unit) {
466
+ if (unit) {
466
467
  if (typeof value === "number") {
467
468
  return `${value.toLocaleString()} ${unit}`;
468
469
  }
469
- return `${unit}`;
470
+ return unit;
470
471
  }
471
472
  const label = humanizeQuotaName(name);
472
473
  if (type === "boolean") {
@@ -485,7 +486,7 @@ function extractDisplayFeatures(features, planName, fallbackFeatures) {
485
486
  return tierFeatures;
486
487
  }
487
488
  }
488
- const quotaFeatures = features.filter((f) => f.quota).map((f) => transformQuotaToFeature(f.quota));
489
+ const quotaFeatures = features.filter((f) => f.quota).map((f) => transformFeatureToLabel(f));
489
490
  if (quotaFeatures.length > 0) {
490
491
  return quotaFeatures;
491
492
  }
@@ -884,5 +885,6 @@ exports.PricingPage = PricingPage;
884
885
  exports.PricingSection = PricingSection;
885
886
  exports.resolveEnterprisePlans = resolveEnterprisePlans;
886
887
  exports.splitPlansByAudience = splitPlansByAudience;
888
+ exports.transformFeatureToLabel = transformFeatureToLabel;
887
889
  //# sourceMappingURL=pricing.js.map
888
890
  //# sourceMappingURL=pricing.js.map