@aforoai/storefront-widgets 1.0.3 → 1.0.5
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.
- package/CHANGELOG.md +22 -0
- package/dist/index.cjs +80 -33
- package/dist/index.d.cts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.mjs +80 -33
- package/dist/loader.js +1 -1
- package/dist/loader.mjs +1 -1
- package/dist/sri.json +11 -11
- package/dist/vanilla/index.cjs +80 -33
- package/dist/vanilla/index.mjs +80 -33
- package/dist/vue/index.cjs +80 -33
- package/dist/vue/index.mjs +80 -33
- package/dist/widgets/checkout-flow.js +5 -5
- package/dist/widgets/invoice-list.js +6 -6
- package/dist/widgets/payment-method.js +5 -5
- package/dist/widgets/pricing-card.js +8 -8
- package/dist/widgets/subscribe-button.js +8 -8
- package/dist/widgets/subscription-manager.js +5 -5
- package/dist/widgets/upgrade-cancel.js +6 -6
- package/dist/widgets/usage-meter.js +5 -5
- package/loader.sri.txt +1 -1
- package/package.json +1 -1
package/dist/vue/index.cjs
CHANGED
|
@@ -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.
|
|
31
|
+
var VERSION = "1.0.5";
|
|
32
32
|
|
|
33
33
|
// src/core/AforoEmbed.ts
|
|
34
34
|
var mountedElements = /* @__PURE__ */ new WeakMap();
|
|
@@ -548,7 +548,8 @@ var _BridgeClient = class _BridgeClient {
|
|
|
548
548
|
throw await this.parseError(resp);
|
|
549
549
|
}
|
|
550
550
|
const body = await resp.json();
|
|
551
|
-
|
|
551
|
+
const data = body?.data ?? body;
|
|
552
|
+
if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
|
|
552
553
|
throw new BridgeClientError(
|
|
553
554
|
"Malformed bridge exchange response",
|
|
554
555
|
500,
|
|
@@ -557,9 +558,9 @@ var _BridgeClient = class _BridgeClient {
|
|
|
557
558
|
);
|
|
558
559
|
}
|
|
559
560
|
return {
|
|
560
|
-
sessionJwt:
|
|
561
|
-
expiresAt:
|
|
562
|
-
customerId:
|
|
561
|
+
sessionJwt: data.sessionJwt,
|
|
562
|
+
expiresAt: data.expiresAt,
|
|
563
|
+
customerId: data.customerId ?? null
|
|
563
564
|
};
|
|
564
565
|
}
|
|
565
566
|
/**
|
|
@@ -600,10 +601,23 @@ var _BridgeClient = class _BridgeClient {
|
|
|
600
601
|
/**
|
|
601
602
|
* Headless config — the PricingCard widget's primary read path.
|
|
602
603
|
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
604
|
+
* Hits `GET /api/v1/portal/embed/tenant-config/{slug}` — the SAME
|
|
605
|
+
* embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
|
|
606
|
+
* extended server-side with `offerings` + `embeddableWidget`. This was
|
|
607
|
+
* previously wired to `GET /api/v1/portal/headless/config`, an unrelated
|
|
608
|
+
* T6 Headless-tier endpoint that requires `X-Storefront-Key`/
|
|
609
|
+
* `X-Tenant-Id` auth — headers the embed key flow never sends. Every
|
|
610
|
+
* embed-tier fetch against that URL failed 400 "Missing tenant
|
|
611
|
+
* identification" by construction; this endpoint is scoped to the same
|
|
612
|
+
* `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
|
|
613
|
+
* `Authorization: Bearer <embed_key>` header `buildHeaders()` already
|
|
614
|
+
* sends is sufficient.
|
|
615
|
+
*
|
|
616
|
+
* The backend response is still a flat record (`tenantSlug,
|
|
617
|
+
* primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
|
|
618
|
+
* embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
|
|
619
|
+
* consumers (the ThemeReader cascade) are unaffected. This method nests
|
|
620
|
+
* the branding fields into the shape `HeadlessConfigResponse` expects.
|
|
607
621
|
*
|
|
608
622
|
* Pattern #18 fail-soft — returns `null` on transport failure, 404, or
|
|
609
623
|
* malformed JSON. Widgets fall through to their empty / error state and
|
|
@@ -618,7 +632,7 @@ var _BridgeClient = class _BridgeClient {
|
|
|
618
632
|
}
|
|
619
633
|
const target = slug || this.tenantSlug;
|
|
620
634
|
if (!target) return null;
|
|
621
|
-
const url = `${this.baseUrl}/api/v1/portal/
|
|
635
|
+
const url = `${this.baseUrl}/api/v1/portal/embed/tenant-config/${encodeURIComponent(target)}`;
|
|
622
636
|
let resp;
|
|
623
637
|
try {
|
|
624
638
|
resp = await this.send(url, {
|
|
@@ -631,7 +645,19 @@ var _BridgeClient = class _BridgeClient {
|
|
|
631
645
|
if (!resp.ok) return null;
|
|
632
646
|
try {
|
|
633
647
|
const body = await resp.json();
|
|
634
|
-
|
|
648
|
+
if (!body || typeof body !== "object") return null;
|
|
649
|
+
const data = body.data ?? body;
|
|
650
|
+
return {
|
|
651
|
+
tenantSlug: data.tenantSlug,
|
|
652
|
+
branding: {
|
|
653
|
+
primaryColor: data.primaryColor ?? null,
|
|
654
|
+
secondaryColor: data.secondaryColor ?? null,
|
|
655
|
+
logoUrl: data.logoUrl ?? null,
|
|
656
|
+
fontFamily: data.fontFamily ?? null
|
|
657
|
+
},
|
|
658
|
+
offerings: Array.isArray(data.offerings) ? data.offerings : [],
|
|
659
|
+
embeddableWidget: data.embeddableWidget ?? null
|
|
660
|
+
};
|
|
635
661
|
} catch {
|
|
636
662
|
return null;
|
|
637
663
|
}
|
|
@@ -775,7 +801,8 @@ var _BridgeClient = class _BridgeClient {
|
|
|
775
801
|
false
|
|
776
802
|
);
|
|
777
803
|
}
|
|
778
|
-
|
|
804
|
+
const data = body?.data ?? body ?? {};
|
|
805
|
+
if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
|
|
779
806
|
throw new BridgeClientError(
|
|
780
807
|
"Malformed checkout-initiate response (missing required fields)",
|
|
781
808
|
500,
|
|
@@ -784,9 +811,9 @@ var _BridgeClient = class _BridgeClient {
|
|
|
784
811
|
);
|
|
785
812
|
}
|
|
786
813
|
return {
|
|
787
|
-
checkoutUrl:
|
|
788
|
-
checkoutSessionId:
|
|
789
|
-
expiresAt:
|
|
814
|
+
checkoutUrl: data.checkoutUrl,
|
|
815
|
+
checkoutSessionId: data.checkoutSessionId,
|
|
816
|
+
expiresAt: data.expiresAt
|
|
790
817
|
};
|
|
791
818
|
}
|
|
792
819
|
/* ────────────────────────────────────────────────────────────────────
|
|
@@ -1594,13 +1621,16 @@ var _BridgeClient = class _BridgeClient {
|
|
|
1594
1621
|
if (!resp.ok) return emptyPage;
|
|
1595
1622
|
try {
|
|
1596
1623
|
const body = await resp.json();
|
|
1597
|
-
if (!body || typeof body !== "object")
|
|
1624
|
+
if (!body || typeof body !== "object") {
|
|
1625
|
+
return emptyPage;
|
|
1626
|
+
}
|
|
1627
|
+
const data = body.data ?? body;
|
|
1598
1628
|
return {
|
|
1599
|
-
content: Array.isArray(
|
|
1600
|
-
totalElements: typeof
|
|
1601
|
-
totalPages: typeof
|
|
1602
|
-
page: typeof
|
|
1603
|
-
size: typeof
|
|
1629
|
+
content: Array.isArray(data.content) ? data.content : [],
|
|
1630
|
+
totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
|
|
1631
|
+
totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
|
|
1632
|
+
page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
|
|
1633
|
+
size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
|
|
1604
1634
|
};
|
|
1605
1635
|
} catch {
|
|
1606
1636
|
return emptyPage;
|
|
@@ -3104,6 +3134,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
|
|
|
3104
3134
|
return `${currency} ${amount.toFixed(2)}`;
|
|
3105
3135
|
}
|
|
3106
3136
|
}
|
|
3137
|
+
function resolveDisplayPrice(offering, locale, intlOverride) {
|
|
3138
|
+
const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
|
|
3139
|
+
if (flat !== "\u2014") return { text: flat, unitSuffix: null };
|
|
3140
|
+
const perUnitPlan = offering.ratePlans?.find(
|
|
3141
|
+
(rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
|
|
3142
|
+
);
|
|
3143
|
+
if (perUnitPlan) {
|
|
3144
|
+
const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
|
|
3145
|
+
if (unitPrice !== "\u2014") {
|
|
3146
|
+
return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
return { text: "\u2014", unitSuffix: null };
|
|
3150
|
+
}
|
|
3107
3151
|
function localizedName(o) {
|
|
3108
3152
|
return o.localizedDisplayName || o.name || "(unnamed plan)";
|
|
3109
3153
|
}
|
|
@@ -3507,12 +3551,7 @@ function PlanCard({
|
|
|
3507
3551
|
onCtaClick,
|
|
3508
3552
|
intlOverride
|
|
3509
3553
|
}) {
|
|
3510
|
-
const price =
|
|
3511
|
-
offering.priceCents,
|
|
3512
|
-
offering.currency,
|
|
3513
|
-
locale,
|
|
3514
|
-
intlOverride
|
|
3515
|
-
);
|
|
3554
|
+
const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
|
|
3516
3555
|
const isContactSales = price === "\u2014";
|
|
3517
3556
|
const planName = localizedName(offering);
|
|
3518
3557
|
const description = localizedDescription(offering);
|
|
@@ -3533,7 +3572,7 @@ function PlanCard({
|
|
|
3533
3572
|
"div",
|
|
3534
3573
|
{
|
|
3535
3574
|
role: "group",
|
|
3536
|
-
"aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
|
|
3575
|
+
"aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
|
|
3537
3576
|
className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
|
|
3538
3577
|
style: cardStyle2,
|
|
3539
3578
|
children: [
|
|
@@ -3598,14 +3637,14 @@ function PlanCard({
|
|
|
3598
3637
|
children: price
|
|
3599
3638
|
}
|
|
3600
3639
|
),
|
|
3601
|
-
!isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3640
|
+
!isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3602
3641
|
"span",
|
|
3603
3642
|
{
|
|
3604
3643
|
className: "aforo-w-pc-cycle",
|
|
3605
3644
|
style: { fontSize: 13, color: tokens.textMuted },
|
|
3606
3645
|
children: [
|
|
3607
3646
|
"/ ",
|
|
3608
|
-
humanCycle(offering.billingCycle)
|
|
3647
|
+
unitSuffix ?? humanCycle(offering.billingCycle)
|
|
3609
3648
|
]
|
|
3610
3649
|
}
|
|
3611
3650
|
) : null
|
|
@@ -3763,6 +3802,7 @@ function TableLayout({
|
|
|
3763
3802
|
/* @__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" }) }),
|
|
3764
3803
|
offerings.map((o) => {
|
|
3765
3804
|
const featured = offeringIsFeatured(o, featuredOverride);
|
|
3805
|
+
const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
|
|
3766
3806
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3767
3807
|
"th",
|
|
3768
3808
|
{
|
|
@@ -3792,13 +3832,20 @@ function TableLayout({
|
|
|
3792
3832
|
children: "Most popular"
|
|
3793
3833
|
}
|
|
3794
3834
|
) : null,
|
|
3795
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3835
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
|
|
3836
|
+
tablePrice,
|
|
3837
|
+
tableUnitSuffix ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
|
|
3838
|
+
" ",
|
|
3839
|
+
"/ ",
|
|
3840
|
+
tableUnitSuffix
|
|
3841
|
+
] }) : null
|
|
3842
|
+
] }),
|
|
3796
3843
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3797
3844
|
"button",
|
|
3798
3845
|
{
|
|
3799
3846
|
type: "button",
|
|
3800
3847
|
onClick: () => onCtaClick(o),
|
|
3801
|
-
"aria-label": `${ctaLabel(o, ctaTextProp,
|
|
3848
|
+
"aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
|
|
3802
3849
|
style: {
|
|
3803
3850
|
inlineSize: "100%",
|
|
3804
3851
|
padding: "8px 12px",
|
|
@@ -3811,7 +3858,7 @@ function TableLayout({
|
|
|
3811
3858
|
cursor: "pointer",
|
|
3812
3859
|
fontFamily: tokens.fontFamily
|
|
3813
3860
|
},
|
|
3814
|
-
children: ctaLabel(o, ctaTextProp,
|
|
3861
|
+
children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
|
|
3815
3862
|
}
|
|
3816
3863
|
)
|
|
3817
3864
|
] })
|
package/dist/vue/index.mjs
CHANGED
|
@@ -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.
|
|
9
|
+
var VERSION = "1.0.5";
|
|
10
10
|
|
|
11
11
|
// src/core/AforoEmbed.ts
|
|
12
12
|
var mountedElements = /* @__PURE__ */ new WeakMap();
|
|
@@ -526,7 +526,8 @@ var _BridgeClient = class _BridgeClient {
|
|
|
526
526
|
throw await this.parseError(resp);
|
|
527
527
|
}
|
|
528
528
|
const body = await resp.json();
|
|
529
|
-
|
|
529
|
+
const data = body?.data ?? body;
|
|
530
|
+
if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
|
|
530
531
|
throw new BridgeClientError(
|
|
531
532
|
"Malformed bridge exchange response",
|
|
532
533
|
500,
|
|
@@ -535,9 +536,9 @@ var _BridgeClient = class _BridgeClient {
|
|
|
535
536
|
);
|
|
536
537
|
}
|
|
537
538
|
return {
|
|
538
|
-
sessionJwt:
|
|
539
|
-
expiresAt:
|
|
540
|
-
customerId:
|
|
539
|
+
sessionJwt: data.sessionJwt,
|
|
540
|
+
expiresAt: data.expiresAt,
|
|
541
|
+
customerId: data.customerId ?? null
|
|
541
542
|
};
|
|
542
543
|
}
|
|
543
544
|
/**
|
|
@@ -578,10 +579,23 @@ var _BridgeClient = class _BridgeClient {
|
|
|
578
579
|
/**
|
|
579
580
|
* Headless config — the PricingCard widget's primary read path.
|
|
580
581
|
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
582
|
+
* Hits `GET /api/v1/portal/embed/tenant-config/{slug}` — the SAME
|
|
583
|
+
* embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
|
|
584
|
+
* extended server-side with `offerings` + `embeddableWidget`. This was
|
|
585
|
+
* previously wired to `GET /api/v1/portal/headless/config`, an unrelated
|
|
586
|
+
* T6 Headless-tier endpoint that requires `X-Storefront-Key`/
|
|
587
|
+
* `X-Tenant-Id` auth — headers the embed key flow never sends. Every
|
|
588
|
+
* embed-tier fetch against that URL failed 400 "Missing tenant
|
|
589
|
+
* identification" by construction; this endpoint is scoped to the same
|
|
590
|
+
* `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
|
|
591
|
+
* `Authorization: Bearer <embed_key>` header `buildHeaders()` already
|
|
592
|
+
* sends is sufficient.
|
|
593
|
+
*
|
|
594
|
+
* The backend response is still a flat record (`tenantSlug,
|
|
595
|
+
* primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
|
|
596
|
+
* embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
|
|
597
|
+
* consumers (the ThemeReader cascade) are unaffected. This method nests
|
|
598
|
+
* the branding fields into the shape `HeadlessConfigResponse` expects.
|
|
585
599
|
*
|
|
586
600
|
* Pattern #18 fail-soft — returns `null` on transport failure, 404, or
|
|
587
601
|
* malformed JSON. Widgets fall through to their empty / error state and
|
|
@@ -596,7 +610,7 @@ var _BridgeClient = class _BridgeClient {
|
|
|
596
610
|
}
|
|
597
611
|
const target = slug || this.tenantSlug;
|
|
598
612
|
if (!target) return null;
|
|
599
|
-
const url = `${this.baseUrl}/api/v1/portal/
|
|
613
|
+
const url = `${this.baseUrl}/api/v1/portal/embed/tenant-config/${encodeURIComponent(target)}`;
|
|
600
614
|
let resp;
|
|
601
615
|
try {
|
|
602
616
|
resp = await this.send(url, {
|
|
@@ -609,7 +623,19 @@ var _BridgeClient = class _BridgeClient {
|
|
|
609
623
|
if (!resp.ok) return null;
|
|
610
624
|
try {
|
|
611
625
|
const body = await resp.json();
|
|
612
|
-
|
|
626
|
+
if (!body || typeof body !== "object") return null;
|
|
627
|
+
const data = body.data ?? body;
|
|
628
|
+
return {
|
|
629
|
+
tenantSlug: data.tenantSlug,
|
|
630
|
+
branding: {
|
|
631
|
+
primaryColor: data.primaryColor ?? null,
|
|
632
|
+
secondaryColor: data.secondaryColor ?? null,
|
|
633
|
+
logoUrl: data.logoUrl ?? null,
|
|
634
|
+
fontFamily: data.fontFamily ?? null
|
|
635
|
+
},
|
|
636
|
+
offerings: Array.isArray(data.offerings) ? data.offerings : [],
|
|
637
|
+
embeddableWidget: data.embeddableWidget ?? null
|
|
638
|
+
};
|
|
613
639
|
} catch {
|
|
614
640
|
return null;
|
|
615
641
|
}
|
|
@@ -753,7 +779,8 @@ var _BridgeClient = class _BridgeClient {
|
|
|
753
779
|
false
|
|
754
780
|
);
|
|
755
781
|
}
|
|
756
|
-
|
|
782
|
+
const data = body?.data ?? body ?? {};
|
|
783
|
+
if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
|
|
757
784
|
throw new BridgeClientError(
|
|
758
785
|
"Malformed checkout-initiate response (missing required fields)",
|
|
759
786
|
500,
|
|
@@ -762,9 +789,9 @@ var _BridgeClient = class _BridgeClient {
|
|
|
762
789
|
);
|
|
763
790
|
}
|
|
764
791
|
return {
|
|
765
|
-
checkoutUrl:
|
|
766
|
-
checkoutSessionId:
|
|
767
|
-
expiresAt:
|
|
792
|
+
checkoutUrl: data.checkoutUrl,
|
|
793
|
+
checkoutSessionId: data.checkoutSessionId,
|
|
794
|
+
expiresAt: data.expiresAt
|
|
768
795
|
};
|
|
769
796
|
}
|
|
770
797
|
/* ────────────────────────────────────────────────────────────────────
|
|
@@ -1572,13 +1599,16 @@ var _BridgeClient = class _BridgeClient {
|
|
|
1572
1599
|
if (!resp.ok) return emptyPage;
|
|
1573
1600
|
try {
|
|
1574
1601
|
const body = await resp.json();
|
|
1575
|
-
if (!body || typeof body !== "object")
|
|
1602
|
+
if (!body || typeof body !== "object") {
|
|
1603
|
+
return emptyPage;
|
|
1604
|
+
}
|
|
1605
|
+
const data = body.data ?? body;
|
|
1576
1606
|
return {
|
|
1577
|
-
content: Array.isArray(
|
|
1578
|
-
totalElements: typeof
|
|
1579
|
-
totalPages: typeof
|
|
1580
|
-
page: typeof
|
|
1581
|
-
size: typeof
|
|
1607
|
+
content: Array.isArray(data.content) ? data.content : [],
|
|
1608
|
+
totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
|
|
1609
|
+
totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
|
|
1610
|
+
page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
|
|
1611
|
+
size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
|
|
1582
1612
|
};
|
|
1583
1613
|
} catch {
|
|
1584
1614
|
return emptyPage;
|
|
@@ -3082,6 +3112,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
|
|
|
3082
3112
|
return `${currency} ${amount.toFixed(2)}`;
|
|
3083
3113
|
}
|
|
3084
3114
|
}
|
|
3115
|
+
function resolveDisplayPrice(offering, locale, intlOverride) {
|
|
3116
|
+
const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
|
|
3117
|
+
if (flat !== "\u2014") return { text: flat, unitSuffix: null };
|
|
3118
|
+
const perUnitPlan = offering.ratePlans?.find(
|
|
3119
|
+
(rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
|
|
3120
|
+
);
|
|
3121
|
+
if (perUnitPlan) {
|
|
3122
|
+
const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
|
|
3123
|
+
if (unitPrice !== "\u2014") {
|
|
3124
|
+
return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return { text: "\u2014", unitSuffix: null };
|
|
3128
|
+
}
|
|
3085
3129
|
function localizedName(o) {
|
|
3086
3130
|
return o.localizedDisplayName || o.name || "(unnamed plan)";
|
|
3087
3131
|
}
|
|
@@ -3485,12 +3529,7 @@ function PlanCard({
|
|
|
3485
3529
|
onCtaClick,
|
|
3486
3530
|
intlOverride
|
|
3487
3531
|
}) {
|
|
3488
|
-
const price =
|
|
3489
|
-
offering.priceCents,
|
|
3490
|
-
offering.currency,
|
|
3491
|
-
locale,
|
|
3492
|
-
intlOverride
|
|
3493
|
-
);
|
|
3532
|
+
const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
|
|
3494
3533
|
const isContactSales = price === "\u2014";
|
|
3495
3534
|
const planName = localizedName(offering);
|
|
3496
3535
|
const description = localizedDescription(offering);
|
|
@@ -3511,7 +3550,7 @@ function PlanCard({
|
|
|
3511
3550
|
"div",
|
|
3512
3551
|
{
|
|
3513
3552
|
role: "group",
|
|
3514
|
-
"aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
|
|
3553
|
+
"aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
|
|
3515
3554
|
className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
|
|
3516
3555
|
style: cardStyle2,
|
|
3517
3556
|
children: [
|
|
@@ -3576,14 +3615,14 @@ function PlanCard({
|
|
|
3576
3615
|
children: price
|
|
3577
3616
|
}
|
|
3578
3617
|
),
|
|
3579
|
-
!isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxs(
|
|
3618
|
+
!isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxs(
|
|
3580
3619
|
"span",
|
|
3581
3620
|
{
|
|
3582
3621
|
className: "aforo-w-pc-cycle",
|
|
3583
3622
|
style: { fontSize: 13, color: tokens.textMuted },
|
|
3584
3623
|
children: [
|
|
3585
3624
|
"/ ",
|
|
3586
|
-
humanCycle(offering.billingCycle)
|
|
3625
|
+
unitSuffix ?? humanCycle(offering.billingCycle)
|
|
3587
3626
|
]
|
|
3588
3627
|
}
|
|
3589
3628
|
) : null
|
|
@@ -3741,6 +3780,7 @@ function TableLayout({
|
|
|
3741
3780
|
/* @__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" }) }),
|
|
3742
3781
|
offerings.map((o) => {
|
|
3743
3782
|
const featured = offeringIsFeatured(o, featuredOverride);
|
|
3783
|
+
const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
|
|
3744
3784
|
return /* @__PURE__ */ jsx(
|
|
3745
3785
|
"th",
|
|
3746
3786
|
{
|
|
@@ -3770,13 +3810,20 @@ function TableLayout({
|
|
|
3770
3810
|
children: "Most popular"
|
|
3771
3811
|
}
|
|
3772
3812
|
) : null,
|
|
3773
|
-
/* @__PURE__ */
|
|
3813
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
|
|
3814
|
+
tablePrice,
|
|
3815
|
+
tableUnitSuffix ? /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
|
|
3816
|
+
" ",
|
|
3817
|
+
"/ ",
|
|
3818
|
+
tableUnitSuffix
|
|
3819
|
+
] }) : null
|
|
3820
|
+
] }),
|
|
3774
3821
|
/* @__PURE__ */ jsx(
|
|
3775
3822
|
"button",
|
|
3776
3823
|
{
|
|
3777
3824
|
type: "button",
|
|
3778
3825
|
onClick: () => onCtaClick(o),
|
|
3779
|
-
"aria-label": `${ctaLabel(o, ctaTextProp,
|
|
3826
|
+
"aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
|
|
3780
3827
|
style: {
|
|
3781
3828
|
inlineSize: "100%",
|
|
3782
3829
|
padding: "8px 12px",
|
|
@@ -3789,7 +3836,7 @@ function TableLayout({
|
|
|
3789
3836
|
cursor: "pointer",
|
|
3790
3837
|
fontFamily: tokens.fontFamily
|
|
3791
3838
|
},
|
|
3792
|
-
children: ctaLabel(o, ctaTextProp,
|
|
3839
|
+
children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
|
|
3793
3840
|
}
|
|
3794
3841
|
)
|
|
3795
3842
|
] })
|