@aforoai/storefront-widgets 1.0.4 → 1.0.6

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.
@@ -27,7 +27,7 @@ var React7__namespace = /*#__PURE__*/_interopNamespace(React7);
27
27
  // src/vanilla/index.ts
28
28
 
29
29
  // src/core/version.ts
30
- var VERSION = "1.0.4";
30
+ var VERSION = "1.0.6";
31
31
 
32
32
  // src/core/AforoEmbed.ts
33
33
  var mountedElements = /* @__PURE__ */ new WeakMap();
@@ -182,6 +182,7 @@ function pricingCardConfigToProps(config) {
182
182
  const theme = readEnum(extra.theme, ["light", "dark", "auto"], "auto");
183
183
  const locale = typeof extra.locale === "string" ? extra.locale : void 0;
184
184
  const maxPlans = typeof extra.maxPlans === "number" && Number.isFinite(extra.maxPlans) ? extra.maxPlans : void 0;
185
+ const borderRadius = typeof extra.borderRadius === "number" && Number.isFinite(extra.borderRadius) ? extra.borderRadius : void 0;
185
186
  const ctaText = typeof extra.ctaText === "string" ? extra.ctaText : void 0;
186
187
  const ctaUrl = typeof extra.ctaUrl === "string" ? extra.ctaUrl : void 0;
187
188
  const showFeatures = typeof extra.showFeatures === "boolean" ? extra.showFeatures : void 0;
@@ -195,6 +196,7 @@ function pricingCardConfigToProps(config) {
195
196
  theme,
196
197
  locale,
197
198
  maxPlans,
199
+ borderRadius,
198
200
  showFeatures,
199
201
  ctaText,
200
202
  ctaUrl,
@@ -547,7 +549,8 @@ var _BridgeClient = class _BridgeClient {
547
549
  throw await this.parseError(resp);
548
550
  }
549
551
  const body = await resp.json();
550
- if (!body.sessionJwt || typeof body.expiresAt !== "number") {
552
+ const data = body?.data ?? body;
553
+ if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
551
554
  throw new BridgeClientError(
552
555
  "Malformed bridge exchange response",
553
556
  500,
@@ -556,9 +559,9 @@ var _BridgeClient = class _BridgeClient {
556
559
  );
557
560
  }
558
561
  return {
559
- sessionJwt: body.sessionJwt,
560
- expiresAt: body.expiresAt,
561
- customerId: body.customerId ?? null
562
+ sessionJwt: data.sessionJwt,
563
+ expiresAt: data.expiresAt,
564
+ customerId: data.customerId ?? null
562
565
  };
563
566
  }
564
567
  /**
@@ -644,16 +647,17 @@ var _BridgeClient = class _BridgeClient {
644
647
  try {
645
648
  const body = await resp.json();
646
649
  if (!body || typeof body !== "object") return null;
650
+ const data = body.data ?? body;
647
651
  return {
648
- tenantSlug: body.tenantSlug,
652
+ tenantSlug: data.tenantSlug,
649
653
  branding: {
650
- primaryColor: body.primaryColor ?? null,
651
- secondaryColor: body.secondaryColor ?? null,
652
- logoUrl: body.logoUrl ?? null,
653
- fontFamily: body.fontFamily ?? null
654
+ primaryColor: data.primaryColor ?? null,
655
+ secondaryColor: data.secondaryColor ?? null,
656
+ logoUrl: data.logoUrl ?? null,
657
+ fontFamily: data.fontFamily ?? null
654
658
  },
655
- offerings: Array.isArray(body.offerings) ? body.offerings : [],
656
- embeddableWidget: body.embeddableWidget ?? null
659
+ offerings: Array.isArray(data.offerings) ? data.offerings : [],
660
+ embeddableWidget: data.embeddableWidget ?? null
657
661
  };
658
662
  } catch {
659
663
  return null;
@@ -798,7 +802,8 @@ var _BridgeClient = class _BridgeClient {
798
802
  false
799
803
  );
800
804
  }
801
- if (!body.checkoutUrl || !body.checkoutSessionId || !body.expiresAt) {
805
+ const data = body?.data ?? body ?? {};
806
+ if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
802
807
  throw new BridgeClientError(
803
808
  "Malformed checkout-initiate response (missing required fields)",
804
809
  500,
@@ -807,9 +812,9 @@ var _BridgeClient = class _BridgeClient {
807
812
  );
808
813
  }
809
814
  return {
810
- checkoutUrl: body.checkoutUrl,
811
- checkoutSessionId: body.checkoutSessionId,
812
- expiresAt: body.expiresAt
815
+ checkoutUrl: data.checkoutUrl,
816
+ checkoutSessionId: data.checkoutSessionId,
817
+ expiresAt: data.expiresAt
813
818
  };
814
819
  }
815
820
  /* ────────────────────────────────────────────────────────────────────
@@ -1617,13 +1622,16 @@ var _BridgeClient = class _BridgeClient {
1617
1622
  if (!resp.ok) return emptyPage;
1618
1623
  try {
1619
1624
  const body = await resp.json();
1620
- if (!body || typeof body !== "object") return emptyPage;
1625
+ if (!body || typeof body !== "object") {
1626
+ return emptyPage;
1627
+ }
1628
+ const data = body.data ?? body;
1621
1629
  return {
1622
- content: Array.isArray(body.content) ? body.content : [],
1623
- totalElements: typeof body.totalElements === "number" && Number.isFinite(body.totalElements) ? body.totalElements : 0,
1624
- totalPages: typeof body.totalPages === "number" && Number.isFinite(body.totalPages) ? body.totalPages : 0,
1625
- page: typeof body.page === "number" && Number.isFinite(body.page) ? body.page : filter.page ?? 0,
1626
- size: typeof body.size === "number" && Number.isFinite(body.size) ? body.size : filter.size ?? 10
1630
+ content: Array.isArray(data.content) ? data.content : [],
1631
+ totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
1632
+ totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
1633
+ page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
1634
+ size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
1627
1635
  };
1628
1636
  } catch {
1629
1637
  return emptyPage;
@@ -3042,6 +3050,7 @@ function WidgetShell(props) {
3042
3050
  var DEFAULT_LAYOUT = "horizontal";
3043
3051
  var DEFAULT_MAX_PLANS = 4;
3044
3052
  var DEFAULT_CTA_TEXT = "Get Started";
3053
+ var DEFAULT_CARD_RADIUS = 12;
3045
3054
  function clampLayout(raw) {
3046
3055
  return raw === "horizontal" || raw === "vertical" || raw === "table" ? raw : void 0;
3047
3056
  }
@@ -3052,7 +3061,7 @@ function setTenantDefaultsIdempotent(setter, next) {
3052
3061
  setter((prev) => {
3053
3062
  if (prev === next) return prev;
3054
3063
  if (!prev && !next) return prev;
3055
- if (prev && next && prev.layout === next.layout && prev.theme === next.theme && prev.maxPlans === next.maxPlans && prev.showFeatures === next.showFeatures && prev.ctaText === next.ctaText) {
3064
+ if (prev && next && prev.layout === next.layout && prev.theme === next.theme && prev.maxPlans === next.maxPlans && prev.showFeatures === next.showFeatures && prev.ctaText === next.ctaText && prev.borderRadius === next.borderRadius) {
3056
3065
  return prev;
3057
3066
  }
3058
3067
  return next;
@@ -3067,7 +3076,8 @@ function extractTenantDefaults(config) {
3067
3076
  theme: typeof raw.theme === "string" ? raw.theme : void 0,
3068
3077
  maxPlans: typeof raw.maxPlans === "number" && Number.isFinite(raw.maxPlans) ? raw.maxPlans : void 0,
3069
3078
  showFeatures: typeof raw.showFeatures === "boolean" ? raw.showFeatures : void 0,
3070
- ctaText: typeof raw.ctaText === "string" && raw.ctaText.length > 0 ? raw.ctaText : void 0
3079
+ ctaText: typeof raw.ctaText === "string" && raw.ctaText.length > 0 ? raw.ctaText : void 0,
3080
+ borderRadius: typeof raw.borderRadius === "number" && Number.isFinite(raw.borderRadius) ? raw.borderRadius : void 0
3071
3081
  };
3072
3082
  }
3073
3083
  var DARK_TOKEN_OVERLAY = {
@@ -3127,6 +3137,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
3127
3137
  return `${currency} ${amount.toFixed(2)}`;
3128
3138
  }
3129
3139
  }
3140
+ function resolveDisplayPrice(offering, locale, intlOverride) {
3141
+ const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
3142
+ if (flat !== "\u2014") return { text: flat, unitSuffix: null };
3143
+ const perUnitPlan = offering.ratePlans?.find(
3144
+ (rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
3145
+ );
3146
+ if (perUnitPlan) {
3147
+ const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
3148
+ if (unitPrice !== "\u2014") {
3149
+ return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
3150
+ }
3151
+ }
3152
+ return { text: "\u2014", unitSuffix: null };
3153
+ }
3130
3154
  function localizedName(o) {
3131
3155
  return o.localizedDisplayName || o.name || "(unnamed plan)";
3132
3156
  }
@@ -3175,6 +3199,7 @@ function PricingCardBody(props) {
3175
3199
  theme: themeProp,
3176
3200
  locale: localeProp,
3177
3201
  maxPlans: maxPlansProp,
3202
+ borderRadius: borderRadiusProp,
3178
3203
  showFeatures: showFeaturesProp,
3179
3204
  ctaText: ctaTextProp,
3180
3205
  ctaUrl: ctaUrlProp,
@@ -3188,6 +3213,7 @@ function PricingCardBody(props) {
3188
3213
  const maxPlans = typeof maxPlansProp === "number" && Number.isFinite(maxPlansProp) ? maxPlansProp : typeof tenantDefaults?.maxPlans === "number" && Number.isFinite(tenantDefaults.maxPlans) ? tenantDefaults.maxPlans : DEFAULT_MAX_PLANS;
3189
3214
  const showFeatures = typeof showFeaturesProp === "boolean" ? showFeaturesProp : typeof tenantDefaults?.showFeatures === "boolean" ? tenantDefaults.showFeatures : true;
3190
3215
  const ctaTextEffective = ctaTextProp ?? tenantDefaults?.ctaText ?? void 0;
3216
+ const cardRadius = typeof borderRadiusProp === "number" && Number.isFinite(borderRadiusProp) ? borderRadiusProp : typeof tenantDefaults?.borderRadius === "number" && Number.isFinite(tenantDefaults.borderRadius) ? tenantDefaults.borderRadius : DEFAULT_CARD_RADIUS;
3191
3217
  const shell = useWidgetShell();
3192
3218
  const { tokens: shellTokens, bridgeClient, telemetry, bus, tenantSlug } = shell;
3193
3219
  const isDark = React7__namespace.useMemo(() => {
@@ -3405,7 +3431,8 @@ function PricingCardBody(props) {
3405
3431
  {
3406
3432
  layout,
3407
3433
  tokens,
3408
- prefersReducedMotion
3434
+ prefersReducedMotion,
3435
+ cardRadius
3409
3436
  }
3410
3437
  )
3411
3438
  }
@@ -3477,7 +3504,8 @@ function PricingCardBody(props) {
3477
3504
  ctaTextProp: ctaTextEffective,
3478
3505
  featuredOverride: featuredOfferingId,
3479
3506
  onCtaClick: handleCtaClick,
3480
- intlOverride
3507
+ intlOverride,
3508
+ cardRadius
3481
3509
  }
3482
3510
  )
3483
3511
  }
@@ -3492,7 +3520,8 @@ function PlanGrid({
3492
3520
  ctaTextProp,
3493
3521
  featuredOverride,
3494
3522
  onCtaClick,
3495
- intlOverride
3523
+ intlOverride,
3524
+ cardRadius
3496
3525
  }) {
3497
3526
  const isHorizontal = layout === "horizontal";
3498
3527
  const gridStyle2 = isHorizontal ? {
@@ -3515,7 +3544,8 @@ function PlanGrid({
3515
3544
  ctaTextProp,
3516
3545
  featured: offeringIsFeatured(o, featuredOverride),
3517
3546
  onCtaClick,
3518
- intlOverride
3547
+ intlOverride,
3548
+ cardRadius
3519
3549
  },
3520
3550
  o.offeringId
3521
3551
  )) });
@@ -3528,14 +3558,10 @@ function PlanCard({
3528
3558
  ctaTextProp,
3529
3559
  featured,
3530
3560
  onCtaClick,
3531
- intlOverride
3561
+ intlOverride,
3562
+ cardRadius
3532
3563
  }) {
3533
- const price = safeFormatCurrency(
3534
- offering.priceCents,
3535
- offering.currency,
3536
- locale,
3537
- intlOverride
3538
- );
3564
+ const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
3539
3565
  const isContactSales = price === "\u2014";
3540
3566
  const planName = localizedName(offering);
3541
3567
  const description = localizedDescription(offering);
@@ -3544,7 +3570,7 @@ function PlanCard({
3544
3570
  display: "flex",
3545
3571
  flexDirection: "column",
3546
3572
  padding: 24,
3547
- borderRadius: 12,
3573
+ borderRadius: cardRadius,
3548
3574
  border: featured ? `2px solid ${tokens.primary}` : `1px solid ${tokens.border}`,
3549
3575
  background: tokens.bg,
3550
3576
  color: tokens.text,
@@ -3556,7 +3582,7 @@ function PlanCard({
3556
3582
  "div",
3557
3583
  {
3558
3584
  role: "group",
3559
- "aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
3585
+ "aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
3560
3586
  className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
3561
3587
  style: cardStyle2,
3562
3588
  children: [
@@ -3621,14 +3647,14 @@ function PlanCard({
3621
3647
  children: price
3622
3648
  }
3623
3649
  ),
3624
- !isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxRuntime.jsxs(
3650
+ !isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxRuntime.jsxs(
3625
3651
  "span",
3626
3652
  {
3627
3653
  className: "aforo-w-pc-cycle",
3628
3654
  style: { fontSize: 13, color: tokens.textMuted },
3629
3655
  children: [
3630
3656
  "/ ",
3631
- humanCycle(offering.billingCycle)
3657
+ unitSuffix ?? humanCycle(offering.billingCycle)
3632
3658
  ]
3633
3659
  }
3634
3660
  ) : null
@@ -3786,6 +3812,7 @@ function TableLayout({
3786
3812
  /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", style: { ...headerCellStyle, color: tokens.textMuted, fontSize: 12 }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aforo-w-pc-sr-only", style: srOnlyStyle, children: "Feature" }) }),
3787
3813
  offerings.map((o) => {
3788
3814
  const featured = offeringIsFeatured(o, featuredOverride);
3815
+ const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
3789
3816
  return /* @__PURE__ */ jsxRuntime.jsx(
3790
3817
  "th",
3791
3818
  {
@@ -3815,13 +3842,20 @@ function TableLayout({
3815
3842
  children: "Most popular"
3816
3843
  }
3817
3844
  ) : null,
3818
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) }),
3845
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
3846
+ tablePrice,
3847
+ tableUnitSuffix ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
3848
+ " ",
3849
+ "/ ",
3850
+ tableUnitSuffix
3851
+ ] }) : null
3852
+ ] }),
3819
3853
  /* @__PURE__ */ jsxRuntime.jsx(
3820
3854
  "button",
3821
3855
  {
3822
3856
  type: "button",
3823
3857
  onClick: () => onCtaClick(o),
3824
- "aria-label": `${ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")} for ${localizedName(o)}`,
3858
+ "aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
3825
3859
  style: {
3826
3860
  inlineSize: "100%",
3827
3861
  padding: "8px 12px",
@@ -3834,7 +3868,7 @@ function TableLayout({
3834
3868
  cursor: "pointer",
3835
3869
  fontFamily: tokens.fontFamily
3836
3870
  },
3837
- children: ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")
3871
+ children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
3838
3872
  }
3839
3873
  )
3840
3874
  ] })
@@ -3886,7 +3920,8 @@ function cellAria(label, feature) {
3886
3920
  function LoadingSkeleton({
3887
3921
  layout,
3888
3922
  tokens,
3889
- prefersReducedMotion
3923
+ prefersReducedMotion,
3924
+ cardRadius
3890
3925
  }) {
3891
3926
  const shimmerStyle = prefersReducedMotion ? { background: tokens.border, opacity: 0.6 } : {
3892
3927
  background: `linear-gradient(90deg, ${tokens.border} 0%, ${tokens.bg} 50%, ${tokens.border} 100%)`,
@@ -3919,7 +3954,7 @@ function LoadingSkeleton({
3919
3954
  "aria-hidden": "true",
3920
3955
  style: {
3921
3956
  padding: 24,
3922
- borderRadius: 12,
3957
+ borderRadius: cardRadius,
3923
3958
  border: `1px solid ${tokens.border}`,
3924
3959
  background: tokens.bg,
3925
3960
  minHeight: 280,
@@ -5,7 +5,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  // src/vanilla/index.ts
6
6
 
7
7
  // src/core/version.ts
8
- var VERSION = "1.0.4";
8
+ var VERSION = "1.0.6";
9
9
 
10
10
  // src/core/AforoEmbed.ts
11
11
  var mountedElements = /* @__PURE__ */ new WeakMap();
@@ -160,6 +160,7 @@ function pricingCardConfigToProps(config) {
160
160
  const theme = readEnum(extra.theme, ["light", "dark", "auto"], "auto");
161
161
  const locale = typeof extra.locale === "string" ? extra.locale : void 0;
162
162
  const maxPlans = typeof extra.maxPlans === "number" && Number.isFinite(extra.maxPlans) ? extra.maxPlans : void 0;
163
+ const borderRadius = typeof extra.borderRadius === "number" && Number.isFinite(extra.borderRadius) ? extra.borderRadius : void 0;
163
164
  const ctaText = typeof extra.ctaText === "string" ? extra.ctaText : void 0;
164
165
  const ctaUrl = typeof extra.ctaUrl === "string" ? extra.ctaUrl : void 0;
165
166
  const showFeatures = typeof extra.showFeatures === "boolean" ? extra.showFeatures : void 0;
@@ -173,6 +174,7 @@ function pricingCardConfigToProps(config) {
173
174
  theme,
174
175
  locale,
175
176
  maxPlans,
177
+ borderRadius,
176
178
  showFeatures,
177
179
  ctaText,
178
180
  ctaUrl,
@@ -525,7 +527,8 @@ var _BridgeClient = class _BridgeClient {
525
527
  throw await this.parseError(resp);
526
528
  }
527
529
  const body = await resp.json();
528
- if (!body.sessionJwt || typeof body.expiresAt !== "number") {
530
+ const data = body?.data ?? body;
531
+ if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
529
532
  throw new BridgeClientError(
530
533
  "Malformed bridge exchange response",
531
534
  500,
@@ -534,9 +537,9 @@ var _BridgeClient = class _BridgeClient {
534
537
  );
535
538
  }
536
539
  return {
537
- sessionJwt: body.sessionJwt,
538
- expiresAt: body.expiresAt,
539
- customerId: body.customerId ?? null
540
+ sessionJwt: data.sessionJwt,
541
+ expiresAt: data.expiresAt,
542
+ customerId: data.customerId ?? null
540
543
  };
541
544
  }
542
545
  /**
@@ -622,16 +625,17 @@ var _BridgeClient = class _BridgeClient {
622
625
  try {
623
626
  const body = await resp.json();
624
627
  if (!body || typeof body !== "object") return null;
628
+ const data = body.data ?? body;
625
629
  return {
626
- tenantSlug: body.tenantSlug,
630
+ tenantSlug: data.tenantSlug,
627
631
  branding: {
628
- primaryColor: body.primaryColor ?? null,
629
- secondaryColor: body.secondaryColor ?? null,
630
- logoUrl: body.logoUrl ?? null,
631
- fontFamily: body.fontFamily ?? null
632
+ primaryColor: data.primaryColor ?? null,
633
+ secondaryColor: data.secondaryColor ?? null,
634
+ logoUrl: data.logoUrl ?? null,
635
+ fontFamily: data.fontFamily ?? null
632
636
  },
633
- offerings: Array.isArray(body.offerings) ? body.offerings : [],
634
- embeddableWidget: body.embeddableWidget ?? null
637
+ offerings: Array.isArray(data.offerings) ? data.offerings : [],
638
+ embeddableWidget: data.embeddableWidget ?? null
635
639
  };
636
640
  } catch {
637
641
  return null;
@@ -776,7 +780,8 @@ var _BridgeClient = class _BridgeClient {
776
780
  false
777
781
  );
778
782
  }
779
- if (!body.checkoutUrl || !body.checkoutSessionId || !body.expiresAt) {
783
+ const data = body?.data ?? body ?? {};
784
+ if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
780
785
  throw new BridgeClientError(
781
786
  "Malformed checkout-initiate response (missing required fields)",
782
787
  500,
@@ -785,9 +790,9 @@ var _BridgeClient = class _BridgeClient {
785
790
  );
786
791
  }
787
792
  return {
788
- checkoutUrl: body.checkoutUrl,
789
- checkoutSessionId: body.checkoutSessionId,
790
- expiresAt: body.expiresAt
793
+ checkoutUrl: data.checkoutUrl,
794
+ checkoutSessionId: data.checkoutSessionId,
795
+ expiresAt: data.expiresAt
791
796
  };
792
797
  }
793
798
  /* ────────────────────────────────────────────────────────────────────
@@ -1595,13 +1600,16 @@ var _BridgeClient = class _BridgeClient {
1595
1600
  if (!resp.ok) return emptyPage;
1596
1601
  try {
1597
1602
  const body = await resp.json();
1598
- if (!body || typeof body !== "object") return emptyPage;
1603
+ if (!body || typeof body !== "object") {
1604
+ return emptyPage;
1605
+ }
1606
+ const data = body.data ?? body;
1599
1607
  return {
1600
- content: Array.isArray(body.content) ? body.content : [],
1601
- totalElements: typeof body.totalElements === "number" && Number.isFinite(body.totalElements) ? body.totalElements : 0,
1602
- totalPages: typeof body.totalPages === "number" && Number.isFinite(body.totalPages) ? body.totalPages : 0,
1603
- page: typeof body.page === "number" && Number.isFinite(body.page) ? body.page : filter.page ?? 0,
1604
- size: typeof body.size === "number" && Number.isFinite(body.size) ? body.size : filter.size ?? 10
1608
+ content: Array.isArray(data.content) ? data.content : [],
1609
+ totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
1610
+ totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
1611
+ page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
1612
+ size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
1605
1613
  };
1606
1614
  } catch {
1607
1615
  return emptyPage;
@@ -3020,6 +3028,7 @@ function WidgetShell(props) {
3020
3028
  var DEFAULT_LAYOUT = "horizontal";
3021
3029
  var DEFAULT_MAX_PLANS = 4;
3022
3030
  var DEFAULT_CTA_TEXT = "Get Started";
3031
+ var DEFAULT_CARD_RADIUS = 12;
3023
3032
  function clampLayout(raw) {
3024
3033
  return raw === "horizontal" || raw === "vertical" || raw === "table" ? raw : void 0;
3025
3034
  }
@@ -3030,7 +3039,7 @@ function setTenantDefaultsIdempotent(setter, next) {
3030
3039
  setter((prev) => {
3031
3040
  if (prev === next) return prev;
3032
3041
  if (!prev && !next) return prev;
3033
- if (prev && next && prev.layout === next.layout && prev.theme === next.theme && prev.maxPlans === next.maxPlans && prev.showFeatures === next.showFeatures && prev.ctaText === next.ctaText) {
3042
+ if (prev && next && prev.layout === next.layout && prev.theme === next.theme && prev.maxPlans === next.maxPlans && prev.showFeatures === next.showFeatures && prev.ctaText === next.ctaText && prev.borderRadius === next.borderRadius) {
3034
3043
  return prev;
3035
3044
  }
3036
3045
  return next;
@@ -3045,7 +3054,8 @@ function extractTenantDefaults(config) {
3045
3054
  theme: typeof raw.theme === "string" ? raw.theme : void 0,
3046
3055
  maxPlans: typeof raw.maxPlans === "number" && Number.isFinite(raw.maxPlans) ? raw.maxPlans : void 0,
3047
3056
  showFeatures: typeof raw.showFeatures === "boolean" ? raw.showFeatures : void 0,
3048
- ctaText: typeof raw.ctaText === "string" && raw.ctaText.length > 0 ? raw.ctaText : void 0
3057
+ ctaText: typeof raw.ctaText === "string" && raw.ctaText.length > 0 ? raw.ctaText : void 0,
3058
+ borderRadius: typeof raw.borderRadius === "number" && Number.isFinite(raw.borderRadius) ? raw.borderRadius : void 0
3049
3059
  };
3050
3060
  }
3051
3061
  var DARK_TOKEN_OVERLAY = {
@@ -3105,6 +3115,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
3105
3115
  return `${currency} ${amount.toFixed(2)}`;
3106
3116
  }
3107
3117
  }
3118
+ function resolveDisplayPrice(offering, locale, intlOverride) {
3119
+ const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
3120
+ if (flat !== "\u2014") return { text: flat, unitSuffix: null };
3121
+ const perUnitPlan = offering.ratePlans?.find(
3122
+ (rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
3123
+ );
3124
+ if (perUnitPlan) {
3125
+ const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
3126
+ if (unitPrice !== "\u2014") {
3127
+ return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
3128
+ }
3129
+ }
3130
+ return { text: "\u2014", unitSuffix: null };
3131
+ }
3108
3132
  function localizedName(o) {
3109
3133
  return o.localizedDisplayName || o.name || "(unnamed plan)";
3110
3134
  }
@@ -3153,6 +3177,7 @@ function PricingCardBody(props) {
3153
3177
  theme: themeProp,
3154
3178
  locale: localeProp,
3155
3179
  maxPlans: maxPlansProp,
3180
+ borderRadius: borderRadiusProp,
3156
3181
  showFeatures: showFeaturesProp,
3157
3182
  ctaText: ctaTextProp,
3158
3183
  ctaUrl: ctaUrlProp,
@@ -3166,6 +3191,7 @@ function PricingCardBody(props) {
3166
3191
  const maxPlans = typeof maxPlansProp === "number" && Number.isFinite(maxPlansProp) ? maxPlansProp : typeof tenantDefaults?.maxPlans === "number" && Number.isFinite(tenantDefaults.maxPlans) ? tenantDefaults.maxPlans : DEFAULT_MAX_PLANS;
3167
3192
  const showFeatures = typeof showFeaturesProp === "boolean" ? showFeaturesProp : typeof tenantDefaults?.showFeatures === "boolean" ? tenantDefaults.showFeatures : true;
3168
3193
  const ctaTextEffective = ctaTextProp ?? tenantDefaults?.ctaText ?? void 0;
3194
+ const cardRadius = typeof borderRadiusProp === "number" && Number.isFinite(borderRadiusProp) ? borderRadiusProp : typeof tenantDefaults?.borderRadius === "number" && Number.isFinite(tenantDefaults.borderRadius) ? tenantDefaults.borderRadius : DEFAULT_CARD_RADIUS;
3169
3195
  const shell = useWidgetShell();
3170
3196
  const { tokens: shellTokens, bridgeClient, telemetry, bus, tenantSlug } = shell;
3171
3197
  const isDark = React7.useMemo(() => {
@@ -3383,7 +3409,8 @@ function PricingCardBody(props) {
3383
3409
  {
3384
3410
  layout,
3385
3411
  tokens,
3386
- prefersReducedMotion
3412
+ prefersReducedMotion,
3413
+ cardRadius
3387
3414
  }
3388
3415
  )
3389
3416
  }
@@ -3455,7 +3482,8 @@ function PricingCardBody(props) {
3455
3482
  ctaTextProp: ctaTextEffective,
3456
3483
  featuredOverride: featuredOfferingId,
3457
3484
  onCtaClick: handleCtaClick,
3458
- intlOverride
3485
+ intlOverride,
3486
+ cardRadius
3459
3487
  }
3460
3488
  )
3461
3489
  }
@@ -3470,7 +3498,8 @@ function PlanGrid({
3470
3498
  ctaTextProp,
3471
3499
  featuredOverride,
3472
3500
  onCtaClick,
3473
- intlOverride
3501
+ intlOverride,
3502
+ cardRadius
3474
3503
  }) {
3475
3504
  const isHorizontal = layout === "horizontal";
3476
3505
  const gridStyle2 = isHorizontal ? {
@@ -3493,7 +3522,8 @@ function PlanGrid({
3493
3522
  ctaTextProp,
3494
3523
  featured: offeringIsFeatured(o, featuredOverride),
3495
3524
  onCtaClick,
3496
- intlOverride
3525
+ intlOverride,
3526
+ cardRadius
3497
3527
  },
3498
3528
  o.offeringId
3499
3529
  )) });
@@ -3506,14 +3536,10 @@ function PlanCard({
3506
3536
  ctaTextProp,
3507
3537
  featured,
3508
3538
  onCtaClick,
3509
- intlOverride
3539
+ intlOverride,
3540
+ cardRadius
3510
3541
  }) {
3511
- const price = safeFormatCurrency(
3512
- offering.priceCents,
3513
- offering.currency,
3514
- locale,
3515
- intlOverride
3516
- );
3542
+ const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
3517
3543
  const isContactSales = price === "\u2014";
3518
3544
  const planName = localizedName(offering);
3519
3545
  const description = localizedDescription(offering);
@@ -3522,7 +3548,7 @@ function PlanCard({
3522
3548
  display: "flex",
3523
3549
  flexDirection: "column",
3524
3550
  padding: 24,
3525
- borderRadius: 12,
3551
+ borderRadius: cardRadius,
3526
3552
  border: featured ? `2px solid ${tokens.primary}` : `1px solid ${tokens.border}`,
3527
3553
  background: tokens.bg,
3528
3554
  color: tokens.text,
@@ -3534,7 +3560,7 @@ function PlanCard({
3534
3560
  "div",
3535
3561
  {
3536
3562
  role: "group",
3537
- "aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
3563
+ "aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
3538
3564
  className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
3539
3565
  style: cardStyle2,
3540
3566
  children: [
@@ -3599,14 +3625,14 @@ function PlanCard({
3599
3625
  children: price
3600
3626
  }
3601
3627
  ),
3602
- !isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxs(
3628
+ !isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxs(
3603
3629
  "span",
3604
3630
  {
3605
3631
  className: "aforo-w-pc-cycle",
3606
3632
  style: { fontSize: 13, color: tokens.textMuted },
3607
3633
  children: [
3608
3634
  "/ ",
3609
- humanCycle(offering.billingCycle)
3635
+ unitSuffix ?? humanCycle(offering.billingCycle)
3610
3636
  ]
3611
3637
  }
3612
3638
  ) : null
@@ -3764,6 +3790,7 @@ function TableLayout({
3764
3790
  /* @__PURE__ */ jsx("th", { scope: "col", style: { ...headerCellStyle, color: tokens.textMuted, fontSize: 12 }, children: /* @__PURE__ */ jsx("span", { className: "aforo-w-pc-sr-only", style: srOnlyStyle, children: "Feature" }) }),
3765
3791
  offerings.map((o) => {
3766
3792
  const featured = offeringIsFeatured(o, featuredOverride);
3793
+ const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
3767
3794
  return /* @__PURE__ */ jsx(
3768
3795
  "th",
3769
3796
  {
@@ -3793,13 +3820,20 @@ function TableLayout({
3793
3820
  children: "Most popular"
3794
3821
  }
3795
3822
  ) : null,
3796
- /* @__PURE__ */ jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) }),
3823
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
3824
+ tablePrice,
3825
+ tableUnitSuffix ? /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
3826
+ " ",
3827
+ "/ ",
3828
+ tableUnitSuffix
3829
+ ] }) : null
3830
+ ] }),
3797
3831
  /* @__PURE__ */ jsx(
3798
3832
  "button",
3799
3833
  {
3800
3834
  type: "button",
3801
3835
  onClick: () => onCtaClick(o),
3802
- "aria-label": `${ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")} for ${localizedName(o)}`,
3836
+ "aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
3803
3837
  style: {
3804
3838
  inlineSize: "100%",
3805
3839
  padding: "8px 12px",
@@ -3812,7 +3846,7 @@ function TableLayout({
3812
3846
  cursor: "pointer",
3813
3847
  fontFamily: tokens.fontFamily
3814
3848
  },
3815
- children: ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")
3849
+ children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
3816
3850
  }
3817
3851
  )
3818
3852
  ] })
@@ -3864,7 +3898,8 @@ function cellAria(label, feature) {
3864
3898
  function LoadingSkeleton({
3865
3899
  layout,
3866
3900
  tokens,
3867
- prefersReducedMotion
3901
+ prefersReducedMotion,
3902
+ cardRadius
3868
3903
  }) {
3869
3904
  const shimmerStyle = prefersReducedMotion ? { background: tokens.border, opacity: 0.6 } : {
3870
3905
  background: `linear-gradient(90deg, ${tokens.border} 0%, ${tokens.bg} 50%, ${tokens.border} 100%)`,
@@ -3897,7 +3932,7 @@ function LoadingSkeleton({
3897
3932
  "aria-hidden": "true",
3898
3933
  style: {
3899
3934
  padding: 24,
3900
- borderRadius: 12,
3935
+ borderRadius: cardRadius,
3901
3936
  border: `1px solid ${tokens.border}`,
3902
3937
  background: tokens.bg,
3903
3938
  minHeight: 280,