@burdenoff/website-sdk 2026.1014.1 → 2026.1021.1
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/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +312 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -7,7 +7,7 @@ export { B as BlogListPage, a as BlogListPageProps, b as BlogPostPage, c as Blog
|
|
|
7
7
|
import * as react from 'react';
|
|
8
8
|
import { ComponentType, ReactNode } from 'react';
|
|
9
9
|
export { NewsletterForm, NewsletterFormProps, NewsletterPage, NewsletterPageProps } from './components/newsletter.mjs';
|
|
10
|
-
export { PlanCard, PricingPage, PricingProps, PricingSection } from './components/pricing.mjs';
|
|
10
|
+
export { PlanCard, PricingPage, PricingProductKeyError, PricingProps, PricingSection, availablePlanCurrencies, cleanPlanDisplayName, isUuidLike, planPriceIn, resolvePricingProductKey } from './components/pricing.mjs';
|
|
11
11
|
export { WaitlistForm, WaitlistFormProps, WaitlistPage, WaitlistPageProps } from './components/waitlist.mjs';
|
|
12
12
|
export { UnsubscribePage, UnsubscribePageProps } from './components/unsubscribe.mjs';
|
|
13
13
|
export { SocialLinks, SocialLinksProps, SocialPlatform } from './components/social-links.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { B as BlogListPage, a as BlogListPageProps, b as BlogPostPage, c as Blog
|
|
|
7
7
|
import * as react from 'react';
|
|
8
8
|
import { ComponentType, ReactNode } from 'react';
|
|
9
9
|
export { NewsletterForm, NewsletterFormProps, NewsletterPage, NewsletterPageProps } from './components/newsletter.js';
|
|
10
|
-
export { PlanCard, PricingPage, PricingProps, PricingSection } from './components/pricing.js';
|
|
10
|
+
export { PlanCard, PricingPage, PricingProductKeyError, PricingProps, PricingSection, availablePlanCurrencies, cleanPlanDisplayName, isUuidLike, planPriceIn, resolvePricingProductKey } from './components/pricing.js';
|
|
11
11
|
export { WaitlistForm, WaitlistFormProps, WaitlistPage, WaitlistPageProps } from './components/waitlist.js';
|
|
12
12
|
export { UnsubscribePage, UnsubscribePageProps } from './components/unsubscribe.js';
|
|
13
13
|
export { SocialLinks, SocialLinksProps, SocialPlatform } from './components/social-links.js';
|
package/dist/index.js
CHANGED
|
@@ -2983,6 +2983,168 @@ function NewsletterForm({
|
|
|
2983
2983
|
)
|
|
2984
2984
|
] });
|
|
2985
2985
|
}
|
|
2986
|
+
|
|
2987
|
+
// src/components/pricing-catalog.ts
|
|
2988
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2989
|
+
function isUuidLike(value) {
|
|
2990
|
+
return UUID_PATTERN.test(value.trim());
|
|
2991
|
+
}
|
|
2992
|
+
var PricingProductKeyError = class extends Error {
|
|
2993
|
+
constructor(productId) {
|
|
2994
|
+
super(
|
|
2995
|
+
`PricingPage was given the product id "${productId}", which matches no public product. publicPlans(productId) expects the product slug (for example "vibecontrols"); pass product.slug.`
|
|
2996
|
+
);
|
|
2997
|
+
this.name = "PricingProductKeyError";
|
|
2998
|
+
}
|
|
2999
|
+
};
|
|
3000
|
+
function resolvePricingProductKey(productId, candidates) {
|
|
3001
|
+
const trimmed = productId.trim();
|
|
3002
|
+
if (!isUuidLike(trimmed)) return trimmed;
|
|
3003
|
+
const wanted = trimmed.toLowerCase();
|
|
3004
|
+
const match = candidates.find(
|
|
3005
|
+
(c) => !!c && c.id.toLowerCase() === wanted && c.slug.trim().length > 0
|
|
3006
|
+
);
|
|
3007
|
+
return match ? match.slug.trim() : null;
|
|
3008
|
+
}
|
|
3009
|
+
var PERIOD_SUFFIX = /\s*[([]\s*(monthly|month|annual|annually|yearly|year)\s*[)\]]\s*$/i;
|
|
3010
|
+
var PRODUCT_PREFIX = /^\S.*?\s[-–—]\s+(?=\S)/;
|
|
3011
|
+
function cleanPlanDisplayName(name) {
|
|
3012
|
+
const withoutPeriod = name.replace(PERIOD_SUFFIX, "").trim();
|
|
3013
|
+
const withoutProduct = withoutPeriod.replace(PRODUCT_PREFIX, "").trim();
|
|
3014
|
+
if (withoutProduct.length > 0) return withoutProduct;
|
|
3015
|
+
if (withoutPeriod.length > 0) return withoutPeriod;
|
|
3016
|
+
return name.trim();
|
|
3017
|
+
}
|
|
3018
|
+
function isFiniteNumber(value) {
|
|
3019
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
3020
|
+
}
|
|
3021
|
+
function planPriceIn(plan, currency) {
|
|
3022
|
+
const code = currency.toUpperCase();
|
|
3023
|
+
if (plan.price === 0) return 0;
|
|
3024
|
+
if (plan.currency.toUpperCase() === code) return plan.price;
|
|
3025
|
+
const prices = plan.currencyPrices;
|
|
3026
|
+
if (!prices) return null;
|
|
3027
|
+
const direct = prices[code];
|
|
3028
|
+
if (isFiniteNumber(direct)) return direct;
|
|
3029
|
+
const key = Object.keys(prices).find((k) => k.toUpperCase() === code);
|
|
3030
|
+
const value = key === void 0 ? void 0 : prices[key];
|
|
3031
|
+
return isFiniteNumber(value) ? value : null;
|
|
3032
|
+
}
|
|
3033
|
+
function availablePlanCurrencies(plans) {
|
|
3034
|
+
if (plans.length === 0) return [];
|
|
3035
|
+
const all = /* @__PURE__ */ new Set();
|
|
3036
|
+
for (const plan of plans) {
|
|
3037
|
+
all.add(plan.currency.toUpperCase());
|
|
3038
|
+
for (const key of Object.keys(plan.currencyPrices ?? {})) {
|
|
3039
|
+
all.add(key.toUpperCase());
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
const usable = [...all].filter(
|
|
3043
|
+
(code) => plans.every((plan) => planPriceIn(plan, code) !== null)
|
|
3044
|
+
);
|
|
3045
|
+
const base = plans.find((p) => p.price > 0)?.currency.toUpperCase();
|
|
3046
|
+
return usable.sort((a, b) => {
|
|
3047
|
+
if (a === base) return -1;
|
|
3048
|
+
if (b === base) return 1;
|
|
3049
|
+
return a.localeCompare(b);
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
var EURO_REGIONS = [
|
|
3053
|
+
"AT",
|
|
3054
|
+
"BE",
|
|
3055
|
+
"CY",
|
|
3056
|
+
"DE",
|
|
3057
|
+
"EE",
|
|
3058
|
+
"ES",
|
|
3059
|
+
"FI",
|
|
3060
|
+
"FR",
|
|
3061
|
+
"GR",
|
|
3062
|
+
"HR",
|
|
3063
|
+
"IE",
|
|
3064
|
+
"IT",
|
|
3065
|
+
"LT",
|
|
3066
|
+
"LU",
|
|
3067
|
+
"LV",
|
|
3068
|
+
"MT",
|
|
3069
|
+
"NL",
|
|
3070
|
+
"PT",
|
|
3071
|
+
"SI",
|
|
3072
|
+
"SK"
|
|
3073
|
+
];
|
|
3074
|
+
var REGION_CURRENCY = Object.freeze({
|
|
3075
|
+
AU: "AUD",
|
|
3076
|
+
BR: "BRL",
|
|
3077
|
+
CA: "CAD",
|
|
3078
|
+
CH: "CHF",
|
|
3079
|
+
CN: "CNY",
|
|
3080
|
+
DK: "DKK",
|
|
3081
|
+
GB: "GBP",
|
|
3082
|
+
HK: "HKD",
|
|
3083
|
+
IN: "INR",
|
|
3084
|
+
JP: "JPY",
|
|
3085
|
+
KR: "KRW",
|
|
3086
|
+
MX: "MXN",
|
|
3087
|
+
MY: "MYR",
|
|
3088
|
+
NO: "NOK",
|
|
3089
|
+
NZ: "NZD",
|
|
3090
|
+
PL: "PLN",
|
|
3091
|
+
SE: "SEK",
|
|
3092
|
+
SG: "SGD",
|
|
3093
|
+
TH: "THB",
|
|
3094
|
+
US: "USD",
|
|
3095
|
+
...Object.fromEntries(EURO_REGIONS.map((r) => [r, "EUR"]))
|
|
3096
|
+
});
|
|
3097
|
+
var TIME_ZONE_REGION = Object.freeze({
|
|
3098
|
+
"Asia/Kolkata": "IN",
|
|
3099
|
+
"Asia/Calcutta": "IN"
|
|
3100
|
+
});
|
|
3101
|
+
function regionOfLanguage(tag) {
|
|
3102
|
+
const parts = tag.replace(/_/g, "-").split("-");
|
|
3103
|
+
for (const part of parts.slice(1)) {
|
|
3104
|
+
if (/^[A-Za-z]{2}$/.test(part)) return part.toUpperCase();
|
|
3105
|
+
}
|
|
3106
|
+
return null;
|
|
3107
|
+
}
|
|
3108
|
+
function detectRegionalCurrency(available, locale) {
|
|
3109
|
+
const sellable = new Set(available.map((c) => c.toUpperCase()));
|
|
3110
|
+
const regions = [];
|
|
3111
|
+
const tzRegion = locale.timeZone ? TIME_ZONE_REGION[locale.timeZone] : null;
|
|
3112
|
+
if (tzRegion) regions.push(tzRegion);
|
|
3113
|
+
for (const tag of locale.languages ?? []) {
|
|
3114
|
+
const region = regionOfLanguage(tag);
|
|
3115
|
+
if (region) regions.push(region);
|
|
3116
|
+
}
|
|
3117
|
+
for (const region of regions) {
|
|
3118
|
+
const currency = REGION_CURRENCY[region];
|
|
3119
|
+
if (currency && sellable.has(currency)) return currency;
|
|
3120
|
+
}
|
|
3121
|
+
return null;
|
|
3122
|
+
}
|
|
3123
|
+
function formatCatalogPrice(amount, currency) {
|
|
3124
|
+
const code = currency.toUpperCase();
|
|
3125
|
+
const whole = amount % 1 === 0;
|
|
3126
|
+
try {
|
|
3127
|
+
return new Intl.NumberFormat("en-US", {
|
|
3128
|
+
style: "currency",
|
|
3129
|
+
currency: code,
|
|
3130
|
+
minimumFractionDigits: whole ? 0 : 2,
|
|
3131
|
+
maximumFractionDigits: whole ? 0 : 2
|
|
3132
|
+
}).format(amount);
|
|
3133
|
+
} catch {
|
|
3134
|
+
return `${code} ${whole ? amount.toString() : amount.toFixed(2)}`;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
function plansForBillingCycle(plans, cycle) {
|
|
3138
|
+
if (cycle === "monthly") {
|
|
3139
|
+
return plans.filter((p) => p.duration === "monthly");
|
|
3140
|
+
}
|
|
3141
|
+
const yearly = plans.filter((p) => p.duration === "yearly");
|
|
3142
|
+
if (yearly.some((p) => p.priceRaw === 0)) return yearly;
|
|
3143
|
+
const free = plans.filter(
|
|
3144
|
+
(p) => p.duration === "monthly" && p.priceRaw === 0
|
|
3145
|
+
);
|
|
3146
|
+
return [...free, ...yearly];
|
|
3147
|
+
}
|
|
2986
3148
|
var GET_PLANS_QUERY = `
|
|
2987
3149
|
query GetPublicPlans($productId: String!) {
|
|
2988
3150
|
publicPlans(productId: $productId) {
|
|
@@ -3008,18 +3170,49 @@ var GET_PLANS_QUERY = `
|
|
|
3008
3170
|
}
|
|
3009
3171
|
}
|
|
3010
3172
|
`;
|
|
3173
|
+
var PUBLIC_PRODUCT_KEYS_QUERY = `
|
|
3174
|
+
query PricingProductKeys {
|
|
3175
|
+
publicProducts(limit: 200) {
|
|
3176
|
+
items {
|
|
3177
|
+
id
|
|
3178
|
+
slug
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
`;
|
|
3183
|
+
var CURRENCY_STORAGE_KEY = "burdenoff.pricing.currency";
|
|
3184
|
+
function readStoredCurrency() {
|
|
3185
|
+
try {
|
|
3186
|
+
if (typeof window === "undefined") return null;
|
|
3187
|
+
return window.localStorage.getItem(CURRENCY_STORAGE_KEY);
|
|
3188
|
+
} catch {
|
|
3189
|
+
return null;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
function storeCurrency(code) {
|
|
3193
|
+
try {
|
|
3194
|
+
if (typeof window === "undefined") return;
|
|
3195
|
+
window.localStorage.setItem(CURRENCY_STORAGE_KEY, code);
|
|
3196
|
+
} catch {
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
function readVisitorLocale() {
|
|
3200
|
+
let timeZone = null;
|
|
3201
|
+
try {
|
|
3202
|
+
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? null;
|
|
3203
|
+
} catch {
|
|
3204
|
+
timeZone = null;
|
|
3205
|
+
}
|
|
3206
|
+
const languages = typeof navigator === "undefined" ? [] : [...navigator.languages ?? [], navigator.language].filter(
|
|
3207
|
+
(l) => typeof l === "string" && l.length > 0
|
|
3208
|
+
);
|
|
3209
|
+
return { timeZone, languages };
|
|
3210
|
+
}
|
|
3011
3211
|
function formatPrice(price, currencyCode = "USD", currencies) {
|
|
3012
|
-
const
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
INR: { code: "INR", symbol: "\u20B9", position: "prefix" }
|
|
3017
|
-
};
|
|
3018
|
-
const currencyMap = currencies || defaultCurrencies;
|
|
3019
|
-
const currency = currencyMap[currencyCode] || {
|
|
3020
|
-
symbol: currencyCode,
|
|
3021
|
-
position: "prefix"
|
|
3022
|
-
};
|
|
3212
|
+
const currency = currencies?.[currencyCode];
|
|
3213
|
+
if (!currency) {
|
|
3214
|
+
return formatCatalogPrice(price, currencyCode);
|
|
3215
|
+
}
|
|
3023
3216
|
const formattedNumber = price % 1 === 0 ? price.toString() : price.toFixed(2);
|
|
3024
3217
|
return currency.position === "prefix" ? `${currency.symbol}${formattedNumber}` : `${formattedNumber}${currency.symbol}`;
|
|
3025
3218
|
}
|
|
@@ -3080,11 +3273,12 @@ function getFallbackFeaturesForTier(tier, fallbackFeatures) {
|
|
|
3080
3273
|
}
|
|
3081
3274
|
function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
3082
3275
|
const normalizedName = planName.toLowerCase();
|
|
3083
|
-
|
|
3084
|
-
|
|
3276
|
+
const cleanName = cleanPlanDisplayName(planName);
|
|
3277
|
+
const meta = planMetadata?.[normalizedName] ?? planMetadata?.[cleanName.toLowerCase()];
|
|
3278
|
+
if (meta) {
|
|
3085
3279
|
return {
|
|
3086
3280
|
tier: meta.tier || "basic",
|
|
3087
|
-
displayName: meta.displayName ||
|
|
3281
|
+
displayName: meta.displayName || cleanName,
|
|
3088
3282
|
defaultDescription: meta.defaultDescription || "Choose this plan",
|
|
3089
3283
|
popular: meta.popular ?? false,
|
|
3090
3284
|
ctaText: meta.ctaText || "Get Started",
|
|
@@ -3098,7 +3292,7 @@ function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
|
3098
3292
|
if (isPaidEnterpriseName) {
|
|
3099
3293
|
return {
|
|
3100
3294
|
tier: "professional",
|
|
3101
|
-
displayName:
|
|
3295
|
+
displayName: cleanName,
|
|
3102
3296
|
defaultDescription: "Choose this plan",
|
|
3103
3297
|
popular: false,
|
|
3104
3298
|
ctaText: "Get Started",
|
|
@@ -3107,7 +3301,7 @@ function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
|
3107
3301
|
}
|
|
3108
3302
|
return {
|
|
3109
3303
|
tier,
|
|
3110
|
-
displayName:
|
|
3304
|
+
displayName: cleanName,
|
|
3111
3305
|
defaultDescription: "Choose this plan",
|
|
3112
3306
|
popular: tier === "professional",
|
|
3113
3307
|
ctaText: tier === "enterprise" ? "Contact Sales" : "Get Started",
|
|
@@ -3116,7 +3310,7 @@ function getMetadataForPlan(planName, priceRaw, planMetadata) {
|
|
|
3116
3310
|
}
|
|
3117
3311
|
return {
|
|
3118
3312
|
tier: "basic",
|
|
3119
|
-
displayName:
|
|
3313
|
+
displayName: cleanName,
|
|
3120
3314
|
defaultDescription: "Choose this plan",
|
|
3121
3315
|
popular: false,
|
|
3122
3316
|
ctaText: "Get Started",
|
|
@@ -3173,17 +3367,14 @@ function extractDisplayFeatures(features, planName, fallbackFeatures) {
|
|
|
3173
3367
|
}
|
|
3174
3368
|
return [];
|
|
3175
3369
|
}
|
|
3176
|
-
function transformToDisplayCard(plan, props) {
|
|
3370
|
+
function transformToDisplayCard(plan, props, currency) {
|
|
3177
3371
|
const metadata = getMetadataForPlan(
|
|
3178
3372
|
plan.name,
|
|
3179
3373
|
plan.price,
|
|
3180
3374
|
props.planMetadata
|
|
3181
3375
|
);
|
|
3182
|
-
const
|
|
3183
|
-
|
|
3184
|
-
plan.currency,
|
|
3185
|
-
props.currencies
|
|
3186
|
-
);
|
|
3376
|
+
const selectedPrice = currency ? planPriceIn(plan, currency) : null;
|
|
3377
|
+
const formattedPrice = currency && selectedPrice !== null ? formatPrice(selectedPrice, currency.toUpperCase(), props.currencies) : formatPrice(plan.price, plan.currency, props.currencies);
|
|
3187
3378
|
const interval = formatInterval(plan.duration, props.billingIntervals);
|
|
3188
3379
|
const features = metadata.features && metadata.features.length > 0 ? metadata.features : extractDisplayFeatures(
|
|
3189
3380
|
plan.features,
|
|
@@ -3192,13 +3383,14 @@ function transformToDisplayCard(plan, props) {
|
|
|
3192
3383
|
);
|
|
3193
3384
|
const contactUrl = props.contactUrl || "/contact";
|
|
3194
3385
|
const ctaLink = metadata.ctaType === "contact" ? contactUrl : props.appLoginUrl;
|
|
3386
|
+
const isQuoteBased = metadata.ctaType === "contact" && plan.price === 0;
|
|
3195
3387
|
return {
|
|
3196
3388
|
id: plan.id,
|
|
3197
|
-
name:
|
|
3389
|
+
name: metadata.displayName,
|
|
3198
3390
|
description: metadata.defaultDescription,
|
|
3199
|
-
price: formattedPrice,
|
|
3391
|
+
price: isQuoteBased ? "Custom" : formattedPrice,
|
|
3200
3392
|
priceRaw: plan.price,
|
|
3201
|
-
interval,
|
|
3393
|
+
interval: isQuoteBased ? "" : interval,
|
|
3202
3394
|
duration: plan.duration,
|
|
3203
3395
|
features,
|
|
3204
3396
|
popular: metadata.popular,
|
|
@@ -3216,8 +3408,13 @@ function PricingSection(props) {
|
|
|
3216
3408
|
} = props;
|
|
3217
3409
|
const client = useWebSDK();
|
|
3218
3410
|
const config = useWebSDKConfig();
|
|
3219
|
-
const
|
|
3220
|
-
const
|
|
3411
|
+
const { product: contextProduct } = useProduct();
|
|
3412
|
+
const requestedProductId = (props.productId || config.productId || "").trim();
|
|
3413
|
+
const contextKey = resolvePricingProductKey(requestedProductId, [
|
|
3414
|
+
contextProduct
|
|
3415
|
+
]);
|
|
3416
|
+
const productKey = contextKey ?? requestedProductId;
|
|
3417
|
+
const [catalogPlans, setCatalogPlans] = React.useState([]);
|
|
3221
3418
|
const [loading, setLoading] = React.useState(true);
|
|
3222
3419
|
const [error, setError] = React.useState(null);
|
|
3223
3420
|
const [billingCycle, setBillingCycle] = React.useState(
|
|
@@ -3226,13 +3423,38 @@ function PricingSection(props) {
|
|
|
3226
3423
|
const [audience, setAudience] = React.useState(
|
|
3227
3424
|
"individual"
|
|
3228
3425
|
);
|
|
3426
|
+
const [selectedCurrency, setSelectedCurrency] = React.useState(
|
|
3427
|
+
props.defaultCurrency ? props.defaultCurrency.toUpperCase() : null
|
|
3428
|
+
);
|
|
3229
3429
|
React.useEffect(() => {
|
|
3230
3430
|
let cancelled = false;
|
|
3431
|
+
async function resolveKey() {
|
|
3432
|
+
if (!productKey) {
|
|
3433
|
+
throw new Error(
|
|
3434
|
+
"PricingPage needs a product slug: pass productId or set WebSDKConfig.productId."
|
|
3435
|
+
);
|
|
3436
|
+
}
|
|
3437
|
+
if (!isUuidLike(productKey)) return productKey;
|
|
3438
|
+
const response = await client.query(
|
|
3439
|
+
PUBLIC_PRODUCT_KEYS_QUERY
|
|
3440
|
+
);
|
|
3441
|
+
if (response.errors && response.errors.length > 0) {
|
|
3442
|
+
throw new Error(response.errors[0].message);
|
|
3443
|
+
}
|
|
3444
|
+
const resolved = resolvePricingProductKey(
|
|
3445
|
+
productKey,
|
|
3446
|
+
response.data?.publicProducts?.items ?? []
|
|
3447
|
+
);
|
|
3448
|
+
if (!resolved) throw new PricingProductKeyError(productKey);
|
|
3449
|
+
return resolved;
|
|
3450
|
+
}
|
|
3231
3451
|
async function loadPlans() {
|
|
3232
3452
|
try {
|
|
3233
3453
|
setLoading(true);
|
|
3454
|
+
const key = await resolveKey();
|
|
3455
|
+
if (cancelled) return;
|
|
3234
3456
|
const response = await client.query(GET_PLANS_QUERY, {
|
|
3235
|
-
productId:
|
|
3457
|
+
productId: key
|
|
3236
3458
|
});
|
|
3237
3459
|
if (cancelled) return;
|
|
3238
3460
|
if (response.errors && response.errors.length > 0) {
|
|
@@ -3241,12 +3463,20 @@ function PricingSection(props) {
|
|
|
3241
3463
|
if (!response.data?.publicPlans) {
|
|
3242
3464
|
throw new Error("No plans data received");
|
|
3243
3465
|
}
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3466
|
+
const active = response.data.publicPlans.filter(
|
|
3467
|
+
(plan) => plan.isActive
|
|
3468
|
+
);
|
|
3469
|
+
if (active.length === 0) {
|
|
3470
|
+
throw new Error(
|
|
3471
|
+
`publicPlans returned no active plans for product "${key}".`
|
|
3472
|
+
);
|
|
3473
|
+
}
|
|
3474
|
+
setCatalogPlans(active);
|
|
3246
3475
|
setError(null);
|
|
3247
3476
|
} catch (err) {
|
|
3248
3477
|
if (cancelled) return;
|
|
3249
3478
|
console.error("Failed to load plans:", err);
|
|
3479
|
+
setCatalogPlans([]);
|
|
3250
3480
|
setError("Unable to load pricing information. Please try again later.");
|
|
3251
3481
|
} finally {
|
|
3252
3482
|
if (!cancelled) {
|
|
@@ -3258,13 +3488,38 @@ function PricingSection(props) {
|
|
|
3258
3488
|
return () => {
|
|
3259
3489
|
cancelled = true;
|
|
3260
3490
|
};
|
|
3261
|
-
}, [client,
|
|
3491
|
+
}, [client, productKey]);
|
|
3492
|
+
const currencyOptions = React.useMemo(
|
|
3493
|
+
() => availablePlanCurrencies(catalogPlans),
|
|
3494
|
+
[catalogPlans]
|
|
3495
|
+
);
|
|
3496
|
+
React.useEffect(() => {
|
|
3497
|
+
if (selectedCurrency || currencyOptions.length < 2) return;
|
|
3498
|
+
const stored = readStoredCurrency()?.toUpperCase();
|
|
3499
|
+
if (stored && currencyOptions.includes(stored)) {
|
|
3500
|
+
setSelectedCurrency(stored);
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
const regional = detectRegionalCurrency(
|
|
3504
|
+
currencyOptions,
|
|
3505
|
+
readVisitorLocale()
|
|
3506
|
+
);
|
|
3507
|
+
if (regional) setSelectedCurrency(regional);
|
|
3508
|
+
}, [currencyOptions, selectedCurrency]);
|
|
3509
|
+
const activeCurrency = selectedCurrency && currencyOptions.includes(selectedCurrency) ? selectedCurrency : currencyOptions[0] ?? null;
|
|
3510
|
+
const allPlans = catalogPlans.map((plan) => transformToDisplayCard(plan, props, activeCurrency)).sort((a, b) => {
|
|
3511
|
+
const aCustom = a.price === "Custom" ? 1 : 0;
|
|
3512
|
+
const bCustom = b.price === "Custom" ? 1 : 0;
|
|
3513
|
+
return aCustom - bCustom || a.priceRaw - b.priceRaw;
|
|
3514
|
+
});
|
|
3515
|
+
const showCurrencySelector = props.showCurrencySelector !== false && currencyOptions.length > 1;
|
|
3262
3516
|
const {
|
|
3263
3517
|
individualPlans: individualAllPlans,
|
|
3264
3518
|
enterprisePlans: enterpriseApiPlans
|
|
3265
3519
|
} = splitPlansByAudience(allPlans, props.showAudienceTabs);
|
|
3266
|
-
const individualPlans =
|
|
3267
|
-
|
|
3520
|
+
const individualPlans = plansForBillingCycle(
|
|
3521
|
+
individualAllPlans,
|
|
3522
|
+
billingCycle
|
|
3268
3523
|
);
|
|
3269
3524
|
const enterpriseCards = enterpriseApiPlans.map((p) => {
|
|
3270
3525
|
if (p.priceRaw === 0 && p.name.toLowerCase().includes("enterprise")) {
|
|
@@ -3382,6 +3637,23 @@ function PricingSection(props) {
|
|
|
3382
3637
|
children: "Yearly"
|
|
3383
3638
|
}
|
|
3384
3639
|
)
|
|
3640
|
+
] }) }),
|
|
3641
|
+
showCurrencySelector && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "inline-flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
3642
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Currency" }),
|
|
3643
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3644
|
+
"select",
|
|
3645
|
+
{
|
|
3646
|
+
value: activeCurrency ?? "",
|
|
3647
|
+
onChange: (event) => {
|
|
3648
|
+
const code = event.target.value;
|
|
3649
|
+
setSelectedCurrency(code);
|
|
3650
|
+
storeCurrency(code);
|
|
3651
|
+
},
|
|
3652
|
+
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",
|
|
3653
|
+
"data-testid": "pricing-currency-select",
|
|
3654
|
+
children: currencyOptions.map((code) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: code, children: code }, code))
|
|
3655
|
+
}
|
|
3656
|
+
)
|
|
3385
3657
|
] }) })
|
|
3386
3658
|
] }),
|
|
3387
3659
|
props.showAudienceTabs && audience === "enterprise" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-3xl text-center mb-8", children: [
|
|
@@ -19123,6 +19395,7 @@ exports.PageHead = PageHead;
|
|
|
19123
19395
|
exports.PartnersPage = PartnersPage;
|
|
19124
19396
|
exports.PressKitPage = PressKitPage;
|
|
19125
19397
|
exports.PricingPage = PricingPage;
|
|
19398
|
+
exports.PricingProductKeyError = PricingProductKeyError;
|
|
19126
19399
|
exports.PricingSection = PricingSection;
|
|
19127
19400
|
exports.ProblemsSolvedSection = ProblemsSolvedSection;
|
|
19128
19401
|
exports.ProductCard = ProductCard;
|
|
@@ -19149,8 +19422,10 @@ exports.answerPlainText = answerPlainText;
|
|
|
19149
19422
|
exports.answerSourceHost = answerSourceHost;
|
|
19150
19423
|
exports.answerSpeechText = answerSpeechText;
|
|
19151
19424
|
exports.answerStrings = answerStrings;
|
|
19425
|
+
exports.availablePlanCurrencies = availablePlanCurrencies;
|
|
19152
19426
|
exports.buttonVariants = buttonVariants;
|
|
19153
19427
|
exports.classifyExploreError = classifyExploreError;
|
|
19428
|
+
exports.cleanPlanDisplayName = cleanPlanDisplayName;
|
|
19154
19429
|
exports.cn = cn;
|
|
19155
19430
|
exports.conceptPath = conceptPath;
|
|
19156
19431
|
exports.detectElectronPlatform = detectElectronPlatform;
|
|
@@ -19172,14 +19447,17 @@ exports.isInlineImageAllowed = isInlineImageAllowed;
|
|
|
19172
19447
|
exports.isInlineVideoAllowed = isInlineVideoAllowed;
|
|
19173
19448
|
exports.isPublishedSourceUrl = isPublishedSourceUrl;
|
|
19174
19449
|
exports.isSameProductSlug = isSameProductSlug;
|
|
19450
|
+
exports.isUuidLike = isUuidLike;
|
|
19175
19451
|
exports.isValidUrl = isValidUrl;
|
|
19176
19452
|
exports.legacyProductSlug = legacyProductSlug;
|
|
19177
19453
|
exports.loadRecaptchaV3 = loadRecaptchaV3;
|
|
19178
19454
|
exports.looksLikeDeliverableEmail = looksLikeDeliverableEmail;
|
|
19179
19455
|
exports.normalizeProductSlug = normalizeProductSlug;
|
|
19180
19456
|
exports.normalizeRoutePath = normalizeRoutePath;
|
|
19457
|
+
exports.planPriceIn = planPriceIn;
|
|
19181
19458
|
exports.productSlugVariants = productSlugVariants;
|
|
19182
19459
|
exports.productSlugsByOrigin = productSlugsByOrigin;
|
|
19460
|
+
exports.resolvePricingProductKey = resolvePricingProductKey;
|
|
19183
19461
|
exports.shouldRenderSiteFooter = shouldRenderSiteFooter;
|
|
19184
19462
|
exports.speechLocaleOf = speechLocaleOf;
|
|
19185
19463
|
exports.stopSpeaking = stopSpeaking;
|