@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
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: [
|
|
@@ -15075,8 +15347,8 @@ function AnimationStyles() {
|
|
|
15075
15347
|
return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: ANIMATION_STYLES } });
|
|
15076
15348
|
}
|
|
15077
15349
|
var CASE_STUDIES_QUERY = `
|
|
15078
|
-
query CaseStudies($productId: String!, $limit: Int, $offset: Int) {
|
|
15079
|
-
publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
|
|
15350
|
+
query CaseStudies($productId: String!, $tag: String, $limit: Int, $offset: Int) {
|
|
15351
|
+
publicContentPages(productId: $productId, tag: $tag, limit: $limit, offset: $offset) {
|
|
15080
15352
|
items {
|
|
15081
15353
|
id
|
|
15082
15354
|
title
|
|
@@ -15476,6 +15748,9 @@ function CaseStudiesListPage(props) {
|
|
|
15476
15748
|
try {
|
|
15477
15749
|
const result = await client.query(CASE_STUDIES_QUERY, {
|
|
15478
15750
|
productId: resolvedProductId,
|
|
15751
|
+
// Filter server-side: an unfiltered page of `limit` rows is mostly blog/legal pages, so
|
|
15752
|
+
// real case studies past it were never listed and the page fell back to the empty state.
|
|
15753
|
+
tag: CASE_STUDY_TAG,
|
|
15479
15754
|
limit,
|
|
15480
15755
|
offset
|
|
15481
15756
|
});
|
|
@@ -19123,6 +19398,7 @@ exports.PageHead = PageHead;
|
|
|
19123
19398
|
exports.PartnersPage = PartnersPage;
|
|
19124
19399
|
exports.PressKitPage = PressKitPage;
|
|
19125
19400
|
exports.PricingPage = PricingPage;
|
|
19401
|
+
exports.PricingProductKeyError = PricingProductKeyError;
|
|
19126
19402
|
exports.PricingSection = PricingSection;
|
|
19127
19403
|
exports.ProblemsSolvedSection = ProblemsSolvedSection;
|
|
19128
19404
|
exports.ProductCard = ProductCard;
|
|
@@ -19149,8 +19425,10 @@ exports.answerPlainText = answerPlainText;
|
|
|
19149
19425
|
exports.answerSourceHost = answerSourceHost;
|
|
19150
19426
|
exports.answerSpeechText = answerSpeechText;
|
|
19151
19427
|
exports.answerStrings = answerStrings;
|
|
19428
|
+
exports.availablePlanCurrencies = availablePlanCurrencies;
|
|
19152
19429
|
exports.buttonVariants = buttonVariants;
|
|
19153
19430
|
exports.classifyExploreError = classifyExploreError;
|
|
19431
|
+
exports.cleanPlanDisplayName = cleanPlanDisplayName;
|
|
19154
19432
|
exports.cn = cn;
|
|
19155
19433
|
exports.conceptPath = conceptPath;
|
|
19156
19434
|
exports.detectElectronPlatform = detectElectronPlatform;
|
|
@@ -19172,14 +19450,17 @@ exports.isInlineImageAllowed = isInlineImageAllowed;
|
|
|
19172
19450
|
exports.isInlineVideoAllowed = isInlineVideoAllowed;
|
|
19173
19451
|
exports.isPublishedSourceUrl = isPublishedSourceUrl;
|
|
19174
19452
|
exports.isSameProductSlug = isSameProductSlug;
|
|
19453
|
+
exports.isUuidLike = isUuidLike;
|
|
19175
19454
|
exports.isValidUrl = isValidUrl;
|
|
19176
19455
|
exports.legacyProductSlug = legacyProductSlug;
|
|
19177
19456
|
exports.loadRecaptchaV3 = loadRecaptchaV3;
|
|
19178
19457
|
exports.looksLikeDeliverableEmail = looksLikeDeliverableEmail;
|
|
19179
19458
|
exports.normalizeProductSlug = normalizeProductSlug;
|
|
19180
19459
|
exports.normalizeRoutePath = normalizeRoutePath;
|
|
19460
|
+
exports.planPriceIn = planPriceIn;
|
|
19181
19461
|
exports.productSlugVariants = productSlugVariants;
|
|
19182
19462
|
exports.productSlugsByOrigin = productSlugsByOrigin;
|
|
19463
|
+
exports.resolvePricingProductKey = resolvePricingProductKey;
|
|
19183
19464
|
exports.shouldRenderSiteFooter = shouldRenderSiteFooter;
|
|
19184
19465
|
exports.speechLocaleOf = speechLocaleOf;
|
|
19185
19466
|
exports.stopSpeaking = stopSpeaking;
|