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