@burdenoff/website-sdk 2026.1014.1 → 2026.1021.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/components/pricing.d.mts +71 -21
- package/dist/components/pricing.d.ts +71 -21
- package/dist/components/pricing.js +331 -34
- package/dist/components/pricing.js.map +1 -1
- package/dist/components/pricing.mjs +327 -36
- package/dist/components/pricing.mjs.map +1 -1
- package/dist/content/index.js +5 -2
- package/dist/content/index.js.map +1 -1
- package/dist/content/index.mjs +5 -2
- package/dist/content/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +317 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +312 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { createContext, useState, useEffect, useContext } from 'react';
|
|
2
|
+
import { createContext, useState, useEffect, useMemo, useContext } from 'react';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
import { Helmet } from 'react-helmet-async';
|
|
5
5
|
import { Slot } from '@radix-ui/react-slot';
|
|
@@ -11,6 +11,168 @@ var __defProp = Object.defineProperty;
|
|
|
11
11
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
12
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
13
13
|
|
|
14
|
+
// src/components/pricing-catalog.ts
|
|
15
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16
|
+
function isUuidLike(value) {
|
|
17
|
+
return UUID_PATTERN.test(value.trim());
|
|
18
|
+
}
|
|
19
|
+
var PricingProductKeyError = class extends Error {
|
|
20
|
+
constructor(productId) {
|
|
21
|
+
super(
|
|
22
|
+
`PricingPage was given the product id "${productId}", which matches no public product. publicPlans(productId) expects the product slug (for example "vibecontrols"); pass product.slug.`
|
|
23
|
+
);
|
|
24
|
+
this.name = "PricingProductKeyError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function resolvePricingProductKey(productId, candidates) {
|
|
28
|
+
const trimmed = productId.trim();
|
|
29
|
+
if (!isUuidLike(trimmed)) return trimmed;
|
|
30
|
+
const wanted = trimmed.toLowerCase();
|
|
31
|
+
const match = candidates.find(
|
|
32
|
+
(c) => !!c && c.id.toLowerCase() === wanted && c.slug.trim().length > 0
|
|
33
|
+
);
|
|
34
|
+
return match ? match.slug.trim() : null;
|
|
35
|
+
}
|
|
36
|
+
var PERIOD_SUFFIX = /\s*[([]\s*(monthly|month|annual|annually|yearly|year)\s*[)\]]\s*$/i;
|
|
37
|
+
var PRODUCT_PREFIX = /^\S.*?\s[-–—]\s+(?=\S)/;
|
|
38
|
+
function cleanPlanDisplayName(name) {
|
|
39
|
+
const withoutPeriod = name.replace(PERIOD_SUFFIX, "").trim();
|
|
40
|
+
const withoutProduct = withoutPeriod.replace(PRODUCT_PREFIX, "").trim();
|
|
41
|
+
if (withoutProduct.length > 0) return withoutProduct;
|
|
42
|
+
if (withoutPeriod.length > 0) return withoutPeriod;
|
|
43
|
+
return name.trim();
|
|
44
|
+
}
|
|
45
|
+
function isFiniteNumber(value) {
|
|
46
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
47
|
+
}
|
|
48
|
+
function planPriceIn(plan, currency) {
|
|
49
|
+
const code = currency.toUpperCase();
|
|
50
|
+
if (plan.price === 0) return 0;
|
|
51
|
+
if (plan.currency.toUpperCase() === code) return plan.price;
|
|
52
|
+
const prices = plan.currencyPrices;
|
|
53
|
+
if (!prices) return null;
|
|
54
|
+
const direct = prices[code];
|
|
55
|
+
if (isFiniteNumber(direct)) return direct;
|
|
56
|
+
const key = Object.keys(prices).find((k) => k.toUpperCase() === code);
|
|
57
|
+
const value = key === void 0 ? void 0 : prices[key];
|
|
58
|
+
return isFiniteNumber(value) ? value : null;
|
|
59
|
+
}
|
|
60
|
+
function availablePlanCurrencies(plans) {
|
|
61
|
+
if (plans.length === 0) return [];
|
|
62
|
+
const all = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const plan of plans) {
|
|
64
|
+
all.add(plan.currency.toUpperCase());
|
|
65
|
+
for (const key of Object.keys(plan.currencyPrices ?? {})) {
|
|
66
|
+
all.add(key.toUpperCase());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const usable = [...all].filter(
|
|
70
|
+
(code) => plans.every((plan) => planPriceIn(plan, code) !== null)
|
|
71
|
+
);
|
|
72
|
+
const base = plans.find((p) => p.price > 0)?.currency.toUpperCase();
|
|
73
|
+
return usable.sort((a, b) => {
|
|
74
|
+
if (a === base) return -1;
|
|
75
|
+
if (b === base) return 1;
|
|
76
|
+
return a.localeCompare(b);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
var EURO_REGIONS = [
|
|
80
|
+
"AT",
|
|
81
|
+
"BE",
|
|
82
|
+
"CY",
|
|
83
|
+
"DE",
|
|
84
|
+
"EE",
|
|
85
|
+
"ES",
|
|
86
|
+
"FI",
|
|
87
|
+
"FR",
|
|
88
|
+
"GR",
|
|
89
|
+
"HR",
|
|
90
|
+
"IE",
|
|
91
|
+
"IT",
|
|
92
|
+
"LT",
|
|
93
|
+
"LU",
|
|
94
|
+
"LV",
|
|
95
|
+
"MT",
|
|
96
|
+
"NL",
|
|
97
|
+
"PT",
|
|
98
|
+
"SI",
|
|
99
|
+
"SK"
|
|
100
|
+
];
|
|
101
|
+
var REGION_CURRENCY = Object.freeze({
|
|
102
|
+
AU: "AUD",
|
|
103
|
+
BR: "BRL",
|
|
104
|
+
CA: "CAD",
|
|
105
|
+
CH: "CHF",
|
|
106
|
+
CN: "CNY",
|
|
107
|
+
DK: "DKK",
|
|
108
|
+
GB: "GBP",
|
|
109
|
+
HK: "HKD",
|
|
110
|
+
IN: "INR",
|
|
111
|
+
JP: "JPY",
|
|
112
|
+
KR: "KRW",
|
|
113
|
+
MX: "MXN",
|
|
114
|
+
MY: "MYR",
|
|
115
|
+
NO: "NOK",
|
|
116
|
+
NZ: "NZD",
|
|
117
|
+
PL: "PLN",
|
|
118
|
+
SE: "SEK",
|
|
119
|
+
SG: "SGD",
|
|
120
|
+
TH: "THB",
|
|
121
|
+
US: "USD",
|
|
122
|
+
...Object.fromEntries(EURO_REGIONS.map((r) => [r, "EUR"]))
|
|
123
|
+
});
|
|
124
|
+
var TIME_ZONE_REGION = Object.freeze({
|
|
125
|
+
"Asia/Kolkata": "IN",
|
|
126
|
+
"Asia/Calcutta": "IN"
|
|
127
|
+
});
|
|
128
|
+
function regionOfLanguage(tag) {
|
|
129
|
+
const parts = tag.replace(/_/g, "-").split("-");
|
|
130
|
+
for (const part of parts.slice(1)) {
|
|
131
|
+
if (/^[A-Za-z]{2}$/.test(part)) return part.toUpperCase();
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function detectRegionalCurrency(available, locale) {
|
|
136
|
+
const sellable = new Set(available.map((c) => c.toUpperCase()));
|
|
137
|
+
const regions = [];
|
|
138
|
+
const tzRegion = locale.timeZone ? TIME_ZONE_REGION[locale.timeZone] : null;
|
|
139
|
+
if (tzRegion) regions.push(tzRegion);
|
|
140
|
+
for (const tag of locale.languages ?? []) {
|
|
141
|
+
const region = regionOfLanguage(tag);
|
|
142
|
+
if (region) regions.push(region);
|
|
143
|
+
}
|
|
144
|
+
for (const region of regions) {
|
|
145
|
+
const currency = REGION_CURRENCY[region];
|
|
146
|
+
if (currency && sellable.has(currency)) return currency;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function formatCatalogPrice(amount, currency) {
|
|
151
|
+
const code = currency.toUpperCase();
|
|
152
|
+
const whole = amount % 1 === 0;
|
|
153
|
+
try {
|
|
154
|
+
return new Intl.NumberFormat("en-US", {
|
|
155
|
+
style: "currency",
|
|
156
|
+
currency: code,
|
|
157
|
+
minimumFractionDigits: whole ? 0 : 2,
|
|
158
|
+
maximumFractionDigits: whole ? 0 : 2
|
|
159
|
+
}).format(amount);
|
|
160
|
+
} catch {
|
|
161
|
+
return `${code} ${whole ? amount.toString() : amount.toFixed(2)}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function plansForBillingCycle(plans, cycle) {
|
|
165
|
+
if (cycle === "monthly") {
|
|
166
|
+
return plans.filter((p) => p.duration === "monthly");
|
|
167
|
+
}
|
|
168
|
+
const yearly = plans.filter((p) => p.duration === "yearly");
|
|
169
|
+
if (yearly.some((p) => p.priceRaw === 0)) return yearly;
|
|
170
|
+
const free = plans.filter(
|
|
171
|
+
(p) => p.duration === "monthly" && p.priceRaw === 0
|
|
172
|
+
);
|
|
173
|
+
return [...free, ...yearly];
|
|
174
|
+
}
|
|
175
|
+
|
|
14
176
|
// src/client/security.ts
|
|
15
177
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
16
178
|
var RESERVED_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -234,6 +396,25 @@ function useWebSDKConfig() {
|
|
|
234
396
|
const client = useWebSDK();
|
|
235
397
|
return client.getConfig();
|
|
236
398
|
}
|
|
399
|
+
|
|
400
|
+
// src/utils/product-slug.ts
|
|
401
|
+
var PRODUCT_SLUG_RENAMES = Object.freeze(
|
|
402
|
+
[Object.freeze({ legacy: "botlit", current: "botmanor" })]
|
|
403
|
+
);
|
|
404
|
+
Object.freeze(
|
|
405
|
+
Object.fromEntries(PRODUCT_SLUG_RENAMES.map((r) => [r.legacy, r.current]))
|
|
406
|
+
);
|
|
407
|
+
var ProductContext = createContext({
|
|
408
|
+
product: null,
|
|
409
|
+
loading: true,
|
|
410
|
+
error: null,
|
|
411
|
+
hasProvider: false,
|
|
412
|
+
refetch: () => {
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
function useProduct() {
|
|
416
|
+
return useContext(ProductContext);
|
|
417
|
+
}
|
|
237
418
|
function canonicalFromLocation() {
|
|
238
419
|
if (typeof window === "undefined" || !window.location) return void 0;
|
|
239
420
|
const { origin, pathname } = window.location;
|
|
@@ -373,18 +554,49 @@ var GET_PLANS_QUERY = `
|
|
|
373
554
|
}
|
|
374
555
|
}
|
|
375
556
|
`;
|
|
557
|
+
var PUBLIC_PRODUCT_KEYS_QUERY = `
|
|
558
|
+
query PricingProductKeys {
|
|
559
|
+
publicProducts(limit: 200) {
|
|
560
|
+
items {
|
|
561
|
+
id
|
|
562
|
+
slug
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
`;
|
|
567
|
+
var CURRENCY_STORAGE_KEY = "burdenoff.pricing.currency";
|
|
568
|
+
function readStoredCurrency() {
|
|
569
|
+
try {
|
|
570
|
+
if (typeof window === "undefined") return null;
|
|
571
|
+
return window.localStorage.getItem(CURRENCY_STORAGE_KEY);
|
|
572
|
+
} catch {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function storeCurrency(code) {
|
|
577
|
+
try {
|
|
578
|
+
if (typeof window === "undefined") return;
|
|
579
|
+
window.localStorage.setItem(CURRENCY_STORAGE_KEY, code);
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function readVisitorLocale() {
|
|
584
|
+
let timeZone = null;
|
|
585
|
+
try {
|
|
586
|
+
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? null;
|
|
587
|
+
} catch {
|
|
588
|
+
timeZone = null;
|
|
589
|
+
}
|
|
590
|
+
const languages = typeof navigator === "undefined" ? [] : [...navigator.languages ?? [], navigator.language].filter(
|
|
591
|
+
(l) => typeof l === "string" && l.length > 0
|
|
592
|
+
);
|
|
593
|
+
return { timeZone, languages };
|
|
594
|
+
}
|
|
376
595
|
function formatPrice(price, currencyCode = "USD", currencies) {
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
INR: { code: "INR", symbol: "\u20B9", position: "prefix" }
|
|
382
|
-
};
|
|
383
|
-
const currencyMap = currencies || defaultCurrencies;
|
|
384
|
-
const currency = currencyMap[currencyCode] || {
|
|
385
|
-
symbol: currencyCode,
|
|
386
|
-
position: "prefix"
|
|
387
|
-
};
|
|
596
|
+
const currency = currencies?.[currencyCode];
|
|
597
|
+
if (!currency) {
|
|
598
|
+
return formatCatalogPrice(price, currencyCode);
|
|
599
|
+
}
|
|
388
600
|
const formattedNumber = price % 1 === 0 ? price.toString() : price.toFixed(2);
|
|
389
601
|
return currency.position === "prefix" ? `${currency.symbol}${formattedNumber}` : `${formattedNumber}${currency.symbol}`;
|
|
390
602
|
}
|
|
@@ -445,11 +657,12 @@ function getFallbackFeaturesForTier(tier, fallbackFeatures) {
|
|
|
445
657
|
}
|
|
446
658
|
function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
447
659
|
const normalizedName = planName.toLowerCase();
|
|
448
|
-
|
|
449
|
-
|
|
660
|
+
const cleanName = cleanPlanDisplayName(planName);
|
|
661
|
+
const meta = planMetadata?.[normalizedName] ?? planMetadata?.[cleanName.toLowerCase()];
|
|
662
|
+
if (meta) {
|
|
450
663
|
return {
|
|
451
664
|
tier: meta.tier || "basic",
|
|
452
|
-
displayName: meta.displayName ||
|
|
665
|
+
displayName: meta.displayName || cleanName,
|
|
453
666
|
defaultDescription: meta.defaultDescription || "Choose this plan",
|
|
454
667
|
popular: meta.popular ?? false,
|
|
455
668
|
ctaText: meta.ctaText || "Get Started",
|
|
@@ -463,7 +676,7 @@ function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
|
463
676
|
if (isPaidEnterpriseName) {
|
|
464
677
|
return {
|
|
465
678
|
tier: "professional",
|
|
466
|
-
displayName:
|
|
679
|
+
displayName: cleanName,
|
|
467
680
|
defaultDescription: "Choose this plan",
|
|
468
681
|
popular: false,
|
|
469
682
|
ctaText: "Get Started",
|
|
@@ -472,7 +685,7 @@ function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
|
472
685
|
}
|
|
473
686
|
return {
|
|
474
687
|
tier,
|
|
475
|
-
displayName:
|
|
688
|
+
displayName: cleanName,
|
|
476
689
|
defaultDescription: "Choose this plan",
|
|
477
690
|
popular: tier === "professional",
|
|
478
691
|
ctaText: tier === "enterprise" ? "Contact Sales" : "Get Started",
|
|
@@ -481,7 +694,7 @@ function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
|
481
694
|
}
|
|
482
695
|
return {
|
|
483
696
|
tier: "basic",
|
|
484
|
-
displayName:
|
|
697
|
+
displayName: cleanName,
|
|
485
698
|
defaultDescription: "Choose this plan",
|
|
486
699
|
popular: false,
|
|
487
700
|
ctaText: "Get Started",
|
|
@@ -538,17 +751,14 @@ function extractDisplayFeatures(features, planName, fallbackFeatures) {
|
|
|
538
751
|
}
|
|
539
752
|
return [];
|
|
540
753
|
}
|
|
541
|
-
function transformToDisplayCard(plan, props) {
|
|
754
|
+
function transformToDisplayCard(plan, props, currency) {
|
|
542
755
|
const metadata = getMetadataForPlan(
|
|
543
756
|
plan.name,
|
|
544
757
|
plan.price,
|
|
545
758
|
props.planMetadata
|
|
546
759
|
);
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
plan.currency,
|
|
550
|
-
props.currencies
|
|
551
|
-
);
|
|
760
|
+
const selectedPrice = currency ? planPriceIn(plan, currency) : null;
|
|
761
|
+
const formattedPrice = currency && selectedPrice !== null ? formatPrice(selectedPrice, currency.toUpperCase(), props.currencies) : formatPrice(plan.price, plan.currency, props.currencies);
|
|
552
762
|
const interval = formatInterval(plan.duration, props.billingIntervals);
|
|
553
763
|
const features = metadata.features && metadata.features.length > 0 ? metadata.features : extractDisplayFeatures(
|
|
554
764
|
plan.features,
|
|
@@ -557,13 +767,14 @@ function transformToDisplayCard(plan, props) {
|
|
|
557
767
|
);
|
|
558
768
|
const contactUrl = props.contactUrl || "/contact";
|
|
559
769
|
const ctaLink = metadata.ctaType === "contact" ? contactUrl : props.appLoginUrl;
|
|
770
|
+
const isQuoteBased = metadata.ctaType === "contact" && plan.price === 0;
|
|
560
771
|
return {
|
|
561
772
|
id: plan.id,
|
|
562
|
-
name:
|
|
773
|
+
name: metadata.displayName,
|
|
563
774
|
description: metadata.defaultDescription,
|
|
564
|
-
price: formattedPrice,
|
|
775
|
+
price: isQuoteBased ? "Custom" : formattedPrice,
|
|
565
776
|
priceRaw: plan.price,
|
|
566
|
-
interval,
|
|
777
|
+
interval: isQuoteBased ? "" : interval,
|
|
567
778
|
duration: plan.duration,
|
|
568
779
|
features,
|
|
569
780
|
popular: metadata.popular,
|
|
@@ -581,8 +792,13 @@ function PricingSection(props) {
|
|
|
581
792
|
} = props;
|
|
582
793
|
const client = useWebSDK();
|
|
583
794
|
const config = useWebSDKConfig();
|
|
584
|
-
const
|
|
585
|
-
const
|
|
795
|
+
const { product: contextProduct } = useProduct();
|
|
796
|
+
const requestedProductId = (props.productId || config.productId || "").trim();
|
|
797
|
+
const contextKey = resolvePricingProductKey(requestedProductId, [
|
|
798
|
+
contextProduct
|
|
799
|
+
]);
|
|
800
|
+
const productKey = contextKey ?? requestedProductId;
|
|
801
|
+
const [catalogPlans, setCatalogPlans] = useState([]);
|
|
586
802
|
const [loading, setLoading] = useState(true);
|
|
587
803
|
const [error, setError] = useState(null);
|
|
588
804
|
const [billingCycle, setBillingCycle] = useState(
|
|
@@ -591,13 +807,38 @@ function PricingSection(props) {
|
|
|
591
807
|
const [audience, setAudience] = useState(
|
|
592
808
|
"individual"
|
|
593
809
|
);
|
|
810
|
+
const [selectedCurrency, setSelectedCurrency] = useState(
|
|
811
|
+
props.defaultCurrency ? props.defaultCurrency.toUpperCase() : null
|
|
812
|
+
);
|
|
594
813
|
useEffect(() => {
|
|
595
814
|
let cancelled = false;
|
|
815
|
+
async function resolveKey() {
|
|
816
|
+
if (!productKey) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
"PricingPage needs a product slug: pass productId or set WebSDKConfig.productId."
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
if (!isUuidLike(productKey)) return productKey;
|
|
822
|
+
const response = await client.query(
|
|
823
|
+
PUBLIC_PRODUCT_KEYS_QUERY
|
|
824
|
+
);
|
|
825
|
+
if (response.errors && response.errors.length > 0) {
|
|
826
|
+
throw new Error(response.errors[0].message);
|
|
827
|
+
}
|
|
828
|
+
const resolved = resolvePricingProductKey(
|
|
829
|
+
productKey,
|
|
830
|
+
response.data?.publicProducts?.items ?? []
|
|
831
|
+
);
|
|
832
|
+
if (!resolved) throw new PricingProductKeyError(productKey);
|
|
833
|
+
return resolved;
|
|
834
|
+
}
|
|
596
835
|
async function loadPlans() {
|
|
597
836
|
try {
|
|
598
837
|
setLoading(true);
|
|
838
|
+
const key = await resolveKey();
|
|
839
|
+
if (cancelled) return;
|
|
599
840
|
const response = await client.query(GET_PLANS_QUERY, {
|
|
600
|
-
productId:
|
|
841
|
+
productId: key
|
|
601
842
|
});
|
|
602
843
|
if (cancelled) return;
|
|
603
844
|
if (response.errors && response.errors.length > 0) {
|
|
@@ -606,12 +847,20 @@ function PricingSection(props) {
|
|
|
606
847
|
if (!response.data?.publicPlans) {
|
|
607
848
|
throw new Error("No plans data received");
|
|
608
849
|
}
|
|
609
|
-
const
|
|
610
|
-
|
|
850
|
+
const active = response.data.publicPlans.filter(
|
|
851
|
+
(plan) => plan.isActive
|
|
852
|
+
);
|
|
853
|
+
if (active.length === 0) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
`publicPlans returned no active plans for product "${key}".`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
setCatalogPlans(active);
|
|
611
859
|
setError(null);
|
|
612
860
|
} catch (err) {
|
|
613
861
|
if (cancelled) return;
|
|
614
862
|
console.error("Failed to load plans:", err);
|
|
863
|
+
setCatalogPlans([]);
|
|
615
864
|
setError("Unable to load pricing information. Please try again later.");
|
|
616
865
|
} finally {
|
|
617
866
|
if (!cancelled) {
|
|
@@ -623,13 +872,38 @@ function PricingSection(props) {
|
|
|
623
872
|
return () => {
|
|
624
873
|
cancelled = true;
|
|
625
874
|
};
|
|
626
|
-
}, [client,
|
|
875
|
+
}, [client, productKey]);
|
|
876
|
+
const currencyOptions = useMemo(
|
|
877
|
+
() => availablePlanCurrencies(catalogPlans),
|
|
878
|
+
[catalogPlans]
|
|
879
|
+
);
|
|
880
|
+
useEffect(() => {
|
|
881
|
+
if (selectedCurrency || currencyOptions.length < 2) return;
|
|
882
|
+
const stored = readStoredCurrency()?.toUpperCase();
|
|
883
|
+
if (stored && currencyOptions.includes(stored)) {
|
|
884
|
+
setSelectedCurrency(stored);
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const regional = detectRegionalCurrency(
|
|
888
|
+
currencyOptions,
|
|
889
|
+
readVisitorLocale()
|
|
890
|
+
);
|
|
891
|
+
if (regional) setSelectedCurrency(regional);
|
|
892
|
+
}, [currencyOptions, selectedCurrency]);
|
|
893
|
+
const activeCurrency = selectedCurrency && currencyOptions.includes(selectedCurrency) ? selectedCurrency : currencyOptions[0] ?? null;
|
|
894
|
+
const allPlans = catalogPlans.map((plan) => transformToDisplayCard(plan, props, activeCurrency)).sort((a, b) => {
|
|
895
|
+
const aCustom = a.price === "Custom" ? 1 : 0;
|
|
896
|
+
const bCustom = b.price === "Custom" ? 1 : 0;
|
|
897
|
+
return aCustom - bCustom || a.priceRaw - b.priceRaw;
|
|
898
|
+
});
|
|
899
|
+
const showCurrencySelector = props.showCurrencySelector !== false && currencyOptions.length > 1;
|
|
627
900
|
const {
|
|
628
901
|
individualPlans: individualAllPlans,
|
|
629
902
|
enterprisePlans: enterpriseApiPlans
|
|
630
903
|
} = splitPlansByAudience(allPlans, props.showAudienceTabs);
|
|
631
|
-
const individualPlans =
|
|
632
|
-
|
|
904
|
+
const individualPlans = plansForBillingCycle(
|
|
905
|
+
individualAllPlans,
|
|
906
|
+
billingCycle
|
|
633
907
|
);
|
|
634
908
|
const enterpriseCards = enterpriseApiPlans.map((p) => {
|
|
635
909
|
if (p.priceRaw === 0 && p.name.toLowerCase().includes("enterprise")) {
|
|
@@ -747,6 +1021,23 @@ function PricingSection(props) {
|
|
|
747
1021
|
children: "Yearly"
|
|
748
1022
|
}
|
|
749
1023
|
)
|
|
1024
|
+
] }) }),
|
|
1025
|
+
showCurrencySelector && /* @__PURE__ */ jsx("div", { className: "flex justify-center mt-4", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1026
|
+
/* @__PURE__ */ jsx("span", { children: "Currency" }),
|
|
1027
|
+
/* @__PURE__ */ jsx(
|
|
1028
|
+
"select",
|
|
1029
|
+
{
|
|
1030
|
+
value: activeCurrency ?? "",
|
|
1031
|
+
onChange: (event) => {
|
|
1032
|
+
const code = event.target.value;
|
|
1033
|
+
setSelectedCurrency(code);
|
|
1034
|
+
storeCurrency(code);
|
|
1035
|
+
},
|
|
1036
|
+
className: "h-9 rounded-md border border-input bg-background px-3 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1037
|
+
"data-testid": "pricing-currency-select",
|
|
1038
|
+
children: currencyOptions.map((code) => /* @__PURE__ */ jsx("option", { value: code, children: code }, code))
|
|
1039
|
+
}
|
|
1040
|
+
)
|
|
750
1041
|
] }) })
|
|
751
1042
|
] }),
|
|
752
1043
|
props.showAudienceTabs && audience === "enterprise" && /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl text-center mb-8", children: [
|
|
@@ -931,6 +1222,6 @@ function PricingPage(props) {
|
|
|
931
1222
|
] });
|
|
932
1223
|
}
|
|
933
1224
|
|
|
934
|
-
export { PricingPage, PricingSection, resolveEnterprisePlans, splitPlansByAudience, transformFeatureToLabel };
|
|
1225
|
+
export { PricingPage, PricingProductKeyError, PricingSection, availablePlanCurrencies, cleanPlanDisplayName, isUuidLike, planPriceIn, resolveEnterprisePlans, resolvePricingProductKey, splitPlansByAudience, transformFeatureToLabel };
|
|
935
1226
|
//# sourceMappingURL=pricing.mjs.map
|
|
936
1227
|
//# sourceMappingURL=pricing.mjs.map
|