@burdenoff/website-sdk 0.0.0-dev

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.
Files changed (119) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +357 -0
  3. package/dist/analytics/index.d.mts +27 -0
  4. package/dist/analytics/index.d.ts +27 -0
  5. package/dist/analytics/index.js +36 -0
  6. package/dist/analytics/index.js.map +1 -0
  7. package/dist/analytics/index.mjs +34 -0
  8. package/dist/analytics/index.mjs.map +1 -0
  9. package/dist/client/index.d.mts +29 -0
  10. package/dist/client/index.d.ts +29 -0
  11. package/dist/client/index.js +261 -0
  12. package/dist/client/index.js.map +1 -0
  13. package/dist/client/index.mjs +255 -0
  14. package/dist/client/index.mjs.map +1 -0
  15. package/dist/components/contact.d.mts +144 -0
  16. package/dist/components/contact.d.ts +144 -0
  17. package/dist/components/contact.js +806 -0
  18. package/dist/components/contact.js.map +1 -0
  19. package/dist/components/contact.mjs +780 -0
  20. package/dist/components/contact.mjs.map +1 -0
  21. package/dist/components/electron-download.d.mts +32 -0
  22. package/dist/components/electron-download.d.ts +32 -0
  23. package/dist/components/electron-download.js +131 -0
  24. package/dist/components/electron-download.js.map +1 -0
  25. package/dist/components/electron-download.mjs +108 -0
  26. package/dist/components/electron-download.mjs.map +1 -0
  27. package/dist/components/launch-countdown.d.mts +15 -0
  28. package/dist/components/launch-countdown.d.ts +15 -0
  29. package/dist/components/launch-countdown.js +89 -0
  30. package/dist/components/launch-countdown.js.map +1 -0
  31. package/dist/components/launch-countdown.mjs +87 -0
  32. package/dist/components/launch-countdown.mjs.map +1 -0
  33. package/dist/components/newsletter.d.mts +155 -0
  34. package/dist/components/newsletter.d.ts +155 -0
  35. package/dist/components/newsletter.js +880 -0
  36. package/dist/components/newsletter.js.map +1 -0
  37. package/dist/components/newsletter.mjs +854 -0
  38. package/dist/components/newsletter.mjs.map +1 -0
  39. package/dist/components/partners.d.mts +88 -0
  40. package/dist/components/partners.d.ts +88 -0
  41. package/dist/components/partners.js +1169 -0
  42. package/dist/components/partners.js.map +1 -0
  43. package/dist/components/partners.mjs +1143 -0
  44. package/dist/components/partners.mjs.map +1 -0
  45. package/dist/components/pricing.d.mts +194 -0
  46. package/dist/components/pricing.d.ts +194 -0
  47. package/dist/components/pricing.js +813 -0
  48. package/dist/components/pricing.js.map +1 -0
  49. package/dist/components/pricing.mjs +791 -0
  50. package/dist/components/pricing.mjs.map +1 -0
  51. package/dist/components/problems-solved.d.mts +37 -0
  52. package/dist/components/problems-solved.d.ts +37 -0
  53. package/dist/components/problems-solved.js +157 -0
  54. package/dist/components/problems-solved.js.map +1 -0
  55. package/dist/components/problems-solved.mjs +135 -0
  56. package/dist/components/problems-solved.mjs.map +1 -0
  57. package/dist/components/resource-links.d.mts +28 -0
  58. package/dist/components/resource-links.d.ts +28 -0
  59. package/dist/components/resource-links.js +229 -0
  60. package/dist/components/resource-links.js.map +1 -0
  61. package/dist/components/resource-links.mjs +227 -0
  62. package/dist/components/resource-links.mjs.map +1 -0
  63. package/dist/components/social-links.d.mts +37 -0
  64. package/dist/components/social-links.d.ts +37 -0
  65. package/dist/components/social-links.js +268 -0
  66. package/dist/components/social-links.js.map +1 -0
  67. package/dist/components/social-links.mjs +266 -0
  68. package/dist/components/social-links.mjs.map +1 -0
  69. package/dist/components/unsubscribe.d.mts +14 -0
  70. package/dist/components/unsubscribe.d.ts +14 -0
  71. package/dist/components/unsubscribe.js +538 -0
  72. package/dist/components/unsubscribe.js.map +1 -0
  73. package/dist/components/unsubscribe.mjs +517 -0
  74. package/dist/components/unsubscribe.mjs.map +1 -0
  75. package/dist/components/waitlist.d.mts +70 -0
  76. package/dist/components/waitlist.d.ts +70 -0
  77. package/dist/components/waitlist.js +864 -0
  78. package/dist/components/waitlist.js.map +1 -0
  79. package/dist/components/waitlist.mjs +841 -0
  80. package/dist/components/waitlist.mjs.map +1 -0
  81. package/dist/components/website-switcher.d.mts +27 -0
  82. package/dist/components/website-switcher.d.ts +27 -0
  83. package/dist/components/website-switcher.js +168 -0
  84. package/dist/components/website-switcher.js.map +1 -0
  85. package/dist/components/website-switcher.mjs +146 -0
  86. package/dist/components/website-switcher.mjs.map +1 -0
  87. package/dist/content/index.d.mts +247 -0
  88. package/dist/content/index.d.ts +247 -0
  89. package/dist/content/index.js +2458 -0
  90. package/dist/content/index.js.map +1 -0
  91. package/dist/content/index.mjs +2428 -0
  92. package/dist/content/index.mjs.map +1 -0
  93. package/dist/index.d.mts +72 -0
  94. package/dist/index.d.ts +72 -0
  95. package/dist/index.js +6273 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/index.mjs +6204 -0
  98. package/dist/index.mjs.map +1 -0
  99. package/dist/product/index.d.mts +94 -0
  100. package/dist/product/index.d.ts +94 -0
  101. package/dist/product/index.js +282 -0
  102. package/dist/product/index.js.map +1 -0
  103. package/dist/product/index.mjs +278 -0
  104. package/dist/product/index.mjs.map +1 -0
  105. package/dist/provider-D7Rij9UM.d.mts +86 -0
  106. package/dist/provider-D7Rij9UM.d.ts +86 -0
  107. package/dist/seo/index.d.mts +64 -0
  108. package/dist/seo/index.d.ts +64 -0
  109. package/dist/seo/index.js +61 -0
  110. package/dist/seo/index.js.map +1 -0
  111. package/dist/seo/index.mjs +59 -0
  112. package/dist/seo/index.mjs.map +1 -0
  113. package/dist/utils/index.d.mts +21 -0
  114. package/dist/utils/index.d.ts +21 -0
  115. package/dist/utils/index.js +12 -0
  116. package/dist/utils/index.js.map +1 -0
  117. package/dist/utils/index.mjs +10 -0
  118. package/dist/utils/index.mjs.map +1 -0
  119. package/package.json +168 -0
@@ -0,0 +1,813 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var reactHelmetAsync = require('react-helmet-async');
6
+ var reactSlot = require('@radix-ui/react-slot');
7
+ var classVarianceAuthority = require('class-variance-authority');
8
+ var clsx = require('clsx');
9
+ var tailwindMerge = require('tailwind-merge');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
30
+
31
+ var __defProp = Object.defineProperty;
32
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
33
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
34
+
35
+ // src/client/security.ts
36
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
37
+ var RESERVED_HEADERS = /* @__PURE__ */ new Set([
38
+ "authorization",
39
+ "cookie",
40
+ "x-actor-id",
41
+ "x-actor-type",
42
+ "x-consent-granted-by",
43
+ "x-consent-scope",
44
+ "x-consent-granted-at",
45
+ "x-consent-expires-at"
46
+ ]);
47
+ function validateGatewayUrl(value) {
48
+ let url;
49
+ try {
50
+ url = new URL(value);
51
+ } catch {
52
+ throw new Error("Gateway URL must be a valid absolute URL");
53
+ }
54
+ if (url.protocol === "https:") {
55
+ return url.toString();
56
+ }
57
+ if (url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase())) {
58
+ return url.toString();
59
+ }
60
+ throw new Error("Gateway URL must use HTTPS unless it targets localhost");
61
+ }
62
+ function sanitizeHeaders(headers) {
63
+ if (!headers) return void 0;
64
+ return Object.fromEntries(
65
+ Object.entries(headers).filter(
66
+ ([key]) => !RESERVED_HEADERS.has(key.toLowerCase())
67
+ )
68
+ );
69
+ }
70
+
71
+ // src/client/graphql-client.ts
72
+ var _WebSDKClient = class _WebSDKClient {
73
+ constructor(config) {
74
+ __publicField(this, "config");
75
+ __publicField(this, "queryCache", /* @__PURE__ */ new Map());
76
+ __publicField(this, "inflight", /* @__PURE__ */ new Map());
77
+ this.config = {
78
+ ...config,
79
+ gatewayUrl: validateGatewayUrl(config.gatewayUrl),
80
+ headers: sanitizeHeaders(config.headers)
81
+ };
82
+ }
83
+ getConfig() {
84
+ return this.config;
85
+ }
86
+ /**
87
+ * Execute a GraphQL query (with deduplication and caching)
88
+ */
89
+ async query(query, variables) {
90
+ const cacheKey = JSON.stringify({ query, variables });
91
+ const cached = this.queryCache.get(cacheKey);
92
+ if (cached) {
93
+ if (Date.now() - cached.ts < _WebSDKClient.CACHE_TTL) {
94
+ return cached.data;
95
+ }
96
+ this.queryCache.delete(cacheKey);
97
+ }
98
+ const existing = this.inflight.get(cacheKey);
99
+ if (existing) {
100
+ return existing;
101
+ }
102
+ const promise = this.request(query, variables).then((result) => {
103
+ if (result.data && !result.errors?.length) {
104
+ if (this.queryCache.size >= _WebSDKClient.MAX_CACHE_ENTRIES) {
105
+ const oldestEntry = this.queryCache.keys().next().value;
106
+ if (oldestEntry) {
107
+ this.queryCache.delete(oldestEntry);
108
+ }
109
+ }
110
+ this.queryCache.set(cacheKey, { data: result, ts: Date.now() });
111
+ }
112
+ return result;
113
+ }).finally(() => {
114
+ this.inflight.delete(cacheKey);
115
+ });
116
+ this.inflight.set(cacheKey, promise);
117
+ return promise;
118
+ }
119
+ /**
120
+ * Execute a GraphQL mutation
121
+ */
122
+ async mutate(mutation, variables) {
123
+ return this.request(mutation, variables);
124
+ }
125
+ async request(query, variables) {
126
+ try {
127
+ const response = await fetch(this.config.gatewayUrl, {
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "application/json",
131
+ "x-product-id": this.config.productId,
132
+ ...this.config.headers
133
+ },
134
+ body: JSON.stringify({ query, variables })
135
+ });
136
+ if (!response.ok) {
137
+ console.error(
138
+ "[WebSDK] GraphQL request failed",
139
+ response.status,
140
+ response.statusText
141
+ );
142
+ return {
143
+ errors: [
144
+ {
145
+ message: `HTTP ${response.status}: ${response.statusText}`,
146
+ extensions: { code: `HTTP_${response.status}` }
147
+ }
148
+ ]
149
+ };
150
+ }
151
+ return await response.json();
152
+ } catch (error) {
153
+ console.error(
154
+ "[WebSDK] Network error",
155
+ error instanceof Error ? error.message : "unknown error"
156
+ );
157
+ return {
158
+ errors: [
159
+ {
160
+ message: error instanceof Error ? error.message : "Network error",
161
+ extensions: { code: "NETWORK_ERROR" }
162
+ }
163
+ ]
164
+ };
165
+ }
166
+ }
167
+ };
168
+ __publicField(_WebSDKClient, "CACHE_TTL", 5 * 60 * 1e3);
169
+ // 5 minutes
170
+ __publicField(_WebSDKClient, "MAX_CACHE_ENTRIES", 100);
171
+ var WebSDKContext = React.createContext(null);
172
+ function useWebSDK() {
173
+ const client = React.useContext(WebSDKContext);
174
+ if (!client) {
175
+ throw new Error("useWebSDK must be used within a WebSDKProvider");
176
+ }
177
+ return client;
178
+ }
179
+ function useWebSDKConfig() {
180
+ const client = useWebSDK();
181
+ return client.getConfig();
182
+ }
183
+ function PageHead({
184
+ title,
185
+ description,
186
+ productName,
187
+ keywords,
188
+ image,
189
+ url,
190
+ favicon,
191
+ robots = "index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1",
192
+ locale = "en_US",
193
+ twitterHandle,
194
+ themeColor = "#000000",
195
+ structuredData,
196
+ additionalMeta
197
+ }) {
198
+ const fullTitle = productName && !title.includes(productName) ? `${title} | ${productName}` : title;
199
+ const fullImageUrl = image && !image.startsWith("http") && url ? `${url.replace(/\/$/, "")}${image}` : image;
200
+ const structuredDataItems = structuredData ? Array.isArray(structuredData) ? structuredData : [structuredData] : [];
201
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactHelmetAsync.Helmet, { children: [
202
+ /* @__PURE__ */ jsxRuntime.jsx("title", { children: fullTitle }),
203
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "title", content: fullTitle }),
204
+ description && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "description", content: description }),
205
+ keywords && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "keywords", content: keywords }),
206
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "author", content: "Burdenoff Consultancy Services Pvt Ltd" }),
207
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "robots", content: robots }),
208
+ url && /* @__PURE__ */ jsxRuntime.jsx("link", { rel: "canonical", href: url }),
209
+ favicon && /* @__PURE__ */ jsxRuntime.jsx("link", { rel: "icon", href: favicon }),
210
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:type", content: "website" }),
211
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:title", content: fullTitle }),
212
+ description && /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:description", content: description }),
213
+ fullImageUrl && /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:image", content: fullImageUrl }),
214
+ url && /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:url", content: url }),
215
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:locale", content: locale }),
216
+ productName && /* @__PURE__ */ jsxRuntime.jsx("meta", { property: "og:site_name", content: productName }),
217
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "twitter:card", content: "summary_large_image" }),
218
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "twitter:title", content: fullTitle }),
219
+ description && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "twitter:description", content: description }),
220
+ fullImageUrl && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "twitter:image", content: fullImageUrl }),
221
+ twitterHandle && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "twitter:creator", content: twitterHandle }),
222
+ /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "theme-color", content: themeColor }),
223
+ structuredDataItems.map((data, index) => /* @__PURE__ */ jsxRuntime.jsx("script", { type: "application/ld+json", children: JSON.stringify(data) }, `ld-${index}`)),
224
+ additionalMeta?.map((meta, index) => /* @__PURE__ */ jsxRuntime.jsx(
225
+ "meta",
226
+ {
227
+ ...meta.name ? { name: meta.name } : {},
228
+ ...meta.property ? { property: meta.property } : {},
229
+ content: meta.content
230
+ },
231
+ `meta-${index}`
232
+ ))
233
+ ] });
234
+ }
235
+ function cn(...inputs) {
236
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
237
+ }
238
+ var buttonVariants = classVarianceAuthority.cva(
239
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
240
+ {
241
+ variants: {
242
+ variant: {
243
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
244
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
245
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
246
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
247
+ ghost: "hover:bg-accent hover:text-accent-foreground",
248
+ link: "text-primary underline-offset-4 hover:underline"
249
+ },
250
+ size: {
251
+ default: "h-9 px-4 py-2",
252
+ sm: "h-8 rounded-md px-3 text-xs",
253
+ lg: "h-10 rounded-md px-8",
254
+ icon: "h-9 w-9"
255
+ }
256
+ },
257
+ defaultVariants: {
258
+ variant: "default",
259
+ size: "default"
260
+ }
261
+ }
262
+ );
263
+ var Button = React__namespace.forwardRef(
264
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
265
+ const Comp = asChild ? reactSlot.Slot : "button";
266
+ return /* @__PURE__ */ jsxRuntime.jsx(
267
+ Comp,
268
+ {
269
+ className: cn(buttonVariants({ variant, size, className })),
270
+ ref,
271
+ ...props
272
+ }
273
+ );
274
+ }
275
+ );
276
+ Button.displayName = "Button";
277
+ var GET_PLANS_QUERY = `
278
+ query GetPublicPlans($productId: String!) {
279
+ publicPlans(productId: $productId) {
280
+ id
281
+ name
282
+ price
283
+ currency
284
+ currencyPrices
285
+ duration
286
+ features {
287
+ id
288
+ quotaId
289
+ quota {
290
+ id
291
+ name
292
+ limits
293
+ }
294
+ }
295
+ isActive
296
+ productID
297
+ createdAt
298
+ }
299
+ }
300
+ `;
301
+ function formatPrice(price, currencyCode = "USD", currencies) {
302
+ const defaultCurrencies = {
303
+ USD: { code: "USD", symbol: "$", position: "prefix" },
304
+ EUR: { code: "EUR", symbol: "\u20AC", position: "prefix" },
305
+ GBP: { code: "GBP", symbol: "\xA3", position: "prefix" },
306
+ INR: { code: "INR", symbol: "\u20B9", position: "prefix" }
307
+ };
308
+ const currencyMap = currencies || defaultCurrencies;
309
+ const currency = currencyMap[currencyCode] || {
310
+ symbol: currencyCode,
311
+ position: "prefix"
312
+ };
313
+ const formattedNumber = price % 1 === 0 ? price.toString() : price.toFixed(2);
314
+ return currency.position === "prefix" ? `${currency.symbol}${formattedNumber}` : `${formattedNumber}${currency.symbol}`;
315
+ }
316
+ function formatInterval(duration, billingIntervals) {
317
+ const defaultIntervals = {
318
+ monthly: "/month",
319
+ yearly: "/year"
320
+ };
321
+ const intervals = billingIntervals || defaultIntervals;
322
+ return intervals[duration] || `/${duration}`;
323
+ }
324
+ function isEnterprisePlan(planName) {
325
+ const name = planName.toLowerCase();
326
+ return name.includes("team") || name.includes("business") || name.includes("enterprise");
327
+ }
328
+ function detectPlanTier(planName) {
329
+ const name = planName.toLowerCase();
330
+ if (name.includes("free") || name.includes("basic") || name.includes("starter") || name.includes("hobby")) {
331
+ return "basic";
332
+ } else if (name.includes("pro") || name.includes("professional") || name.includes("premium") || name.includes("team")) {
333
+ return "professional";
334
+ } else if (name.includes("enterprise") || name.includes("business")) {
335
+ return "enterprise";
336
+ }
337
+ return null;
338
+ }
339
+ function getFallbackFeaturesForTier(tier, fallbackFeatures) {
340
+ return [...fallbackFeatures?.[tier] || []];
341
+ }
342
+ function getMetadataForPlan(planName, planMetadata) {
343
+ const normalizedName = planName.toLowerCase();
344
+ if (planMetadata?.[normalizedName]) {
345
+ const meta = planMetadata[normalizedName];
346
+ return {
347
+ tier: meta.tier || "basic",
348
+ displayName: meta.displayName || planName,
349
+ defaultDescription: meta.defaultDescription || "Choose this plan",
350
+ popular: meta.popular ?? false,
351
+ ctaText: meta.ctaText || "Get Started",
352
+ ctaType: meta.ctaType || "signup",
353
+ features: meta.features
354
+ };
355
+ }
356
+ const tier = detectPlanTier(planName);
357
+ if (tier) {
358
+ return {
359
+ tier,
360
+ displayName: planName,
361
+ defaultDescription: "Choose this plan",
362
+ popular: tier === "professional",
363
+ ctaText: tier === "enterprise" ? "Contact Sales" : "Get Started",
364
+ ctaType: tier === "enterprise" ? "contact" : "signup"
365
+ };
366
+ }
367
+ return {
368
+ tier: "basic",
369
+ displayName: planName,
370
+ defaultDescription: "Choose this plan",
371
+ popular: false,
372
+ ctaText: "Get Started",
373
+ ctaType: "signup"
374
+ };
375
+ }
376
+ function humanizeQuotaName(name) {
377
+ const parts = name.split(".");
378
+ const resource = parts.length >= 3 ? parts[parts.length - 2] : parts[parts.length - 1];
379
+ return resource.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
380
+ }
381
+ function transformQuotaToFeature(quota) {
382
+ const { name, limits } = quota;
383
+ if (!limits || typeof limits !== "object") {
384
+ return humanizeQuotaName(name);
385
+ }
386
+ const limitsObj = limits;
387
+ const value = limitsObj.value;
388
+ const unit = limitsObj.unit;
389
+ const type = limitsObj.type;
390
+ const description = limitsObj.description;
391
+ if (typeof description === "string" && description) {
392
+ if (type === "number" && typeof value === "number") {
393
+ return `${value.toLocaleString()} ${description}`;
394
+ }
395
+ return description;
396
+ }
397
+ if (typeof unit === "string" && unit) {
398
+ if (typeof value === "number") {
399
+ return `${value.toLocaleString()} ${unit}`;
400
+ }
401
+ return `${unit}`;
402
+ }
403
+ const label = humanizeQuotaName(name);
404
+ if (type === "boolean") {
405
+ return label;
406
+ }
407
+ if (typeof value === "number") {
408
+ return `${value.toLocaleString()} ${label}`;
409
+ }
410
+ return label;
411
+ }
412
+ function extractDisplayFeatures(features, planName, fallbackFeatures) {
413
+ const tier = detectPlanTier(planName);
414
+ if (tier) {
415
+ const tierFeatures = getFallbackFeaturesForTier(tier, fallbackFeatures);
416
+ if (tierFeatures.length > 0) {
417
+ return tierFeatures;
418
+ }
419
+ }
420
+ const quotaFeatures = features.filter((f) => f.quota).map((f) => transformQuotaToFeature(f.quota));
421
+ if (quotaFeatures.length > 0) {
422
+ return quotaFeatures;
423
+ }
424
+ return [];
425
+ }
426
+ function transformToDisplayCard(plan, props) {
427
+ const metadata = getMetadataForPlan(plan.name, props.planMetadata);
428
+ const formattedPrice = formatPrice(
429
+ plan.price,
430
+ plan.currency,
431
+ props.currencies
432
+ );
433
+ const interval = formatInterval(plan.duration, props.billingIntervals);
434
+ const features = metadata.features && metadata.features.length > 0 ? metadata.features : extractDisplayFeatures(
435
+ plan.features,
436
+ plan.name,
437
+ props.fallbackFeatures
438
+ );
439
+ const contactUrl = props.contactUrl || "/contact";
440
+ const ctaLink = metadata.ctaType === "contact" ? contactUrl : props.appLoginUrl;
441
+ return {
442
+ id: plan.id,
443
+ name: plan.name,
444
+ description: metadata.defaultDescription,
445
+ price: formattedPrice,
446
+ priceRaw: plan.price,
447
+ interval,
448
+ duration: plan.duration,
449
+ features,
450
+ popular: metadata.popular,
451
+ ctaText: metadata.ctaText,
452
+ ctaLink,
453
+ tier: metadata.tier
454
+ };
455
+ }
456
+ function PricingSection(props) {
457
+ const {
458
+ sectionHeading = "Pricing Plans",
459
+ sectionSubtitle = "Choose the perfect plan for your needs",
460
+ id,
461
+ className = ""
462
+ } = props;
463
+ const client = useWebSDK();
464
+ const config = useWebSDKConfig();
465
+ const resolvedProductId = props.productId || config.productId;
466
+ const [allPlans, setAllPlans] = React.useState([]);
467
+ const [loading, setLoading] = React.useState(true);
468
+ const [error, setError] = React.useState(null);
469
+ const [billingCycle, setBillingCycle] = React.useState(
470
+ "monthly"
471
+ );
472
+ const [audience, setAudience] = React.useState(
473
+ "individual"
474
+ );
475
+ React.useEffect(() => {
476
+ let cancelled = false;
477
+ async function loadPlans() {
478
+ try {
479
+ setLoading(true);
480
+ const response = await client.query(GET_PLANS_QUERY, {
481
+ productId: resolvedProductId
482
+ });
483
+ if (cancelled) return;
484
+ if (response.errors && response.errors.length > 0) {
485
+ throw new Error(response.errors[0].message);
486
+ }
487
+ if (!response.data?.publicPlans) {
488
+ throw new Error("No plans data received");
489
+ }
490
+ const cards = response.data.publicPlans.filter((plan) => plan.isActive).map((plan) => transformToDisplayCard(plan, props)).sort((a, b) => a.priceRaw - b.priceRaw);
491
+ setAllPlans(cards);
492
+ setError(null);
493
+ } catch (err) {
494
+ if (cancelled) return;
495
+ console.error("Failed to load plans:", err);
496
+ setError("Unable to load pricing information. Please try again later.");
497
+ } finally {
498
+ if (!cancelled) {
499
+ setLoading(false);
500
+ }
501
+ }
502
+ }
503
+ loadPlans();
504
+ return () => {
505
+ cancelled = true;
506
+ };
507
+ }, [client, resolvedProductId]);
508
+ const individualAllPlans = allPlans.filter((p) => !isEnterprisePlan(p.name));
509
+ const enterpriseApiPlans = allPlans.filter((p) => isEnterprisePlan(p.name));
510
+ const individualPlans = individualAllPlans.filter(
511
+ (plan) => billingCycle === "monthly" ? plan.duration === "monthly" : plan.duration === "yearly"
512
+ );
513
+ const enterpriseCards = enterpriseApiPlans.map((p) => {
514
+ if (p.priceRaw === 0 && p.name.toLowerCase().includes("enterprise")) {
515
+ return {
516
+ ...p,
517
+ price: "Custom",
518
+ interval: "",
519
+ ctaText: "Contact Sales",
520
+ ctaLink: props.contactUrl || "/contact"
521
+ };
522
+ }
523
+ return p;
524
+ });
525
+ const hasApiEnterprisePlans = enterpriseCards.length > 0;
526
+ const defaultEnterpriseCard = {
527
+ name: "Enterprise",
528
+ subtitle: "For organizations that need more",
529
+ description: "For organizations that need more",
530
+ price: "Custom",
531
+ period: "",
532
+ interval: "",
533
+ button: "Contact Sales",
534
+ buttonLink: props.contactUrl || "/contact",
535
+ ctaText: "Contact Sales",
536
+ ctaLink: props.contactUrl || "/contact",
537
+ popular: false,
538
+ features: props.fallbackFeatures?.enterprise || [
539
+ "Unlimited everything",
540
+ "SSO & SAML",
541
+ "Audit logs",
542
+ "Dedicated support",
543
+ "Self-hosted option"
544
+ ]
545
+ };
546
+ const resolvedEnterprisePlans = hasApiEnterprisePlans ? enterpriseCards : props.enterprisePlans ? props.enterprisePlans : [defaultEnterpriseCard];
547
+ const plans = props.showAudienceTabs && audience === "enterprise" ? [] : individualPlans;
548
+ if (loading) {
549
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { id, className: `py-12 sm:py-16 md:py-20 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "container mx-auto px-4 sm:px-6", children: [
550
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-8 sm:mb-12", children: [
551
+ sectionHeading ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-3 sm:mb-4", children: sectionHeading }) : (
552
+ // Visually hidden but DOM-present heading so axe's heading-order
553
+ // rule still sees an h2 between the consumer's hero h1 and the
554
+ // h3 plan-card titles. See `getMetadataForPlan` for why a falsy
555
+ // sectionHeading is the canonical "no visible heading" signal.
556
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sr-only", children: "Pricing" })
557
+ ),
558
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm sm:text-base md:text-lg", children: "Loading pricing information..." })
559
+ ] }),
560
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid md:grid-cols-3 gap-6 sm:gap-8", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsxs(
561
+ "div",
562
+ {
563
+ className: "bg-card border rounded-lg p-6 animate-pulse",
564
+ children: [
565
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6 bg-muted rounded w-1/2 mb-4" }),
566
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 bg-muted rounded w-3/4 mb-6" }),
567
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 bg-muted rounded w-1/3 mb-6" }),
568
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3 mb-6", children: [1, 2, 3, 4].map((j) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 bg-muted rounded" }, j)) }),
569
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 bg-muted rounded" })
570
+ ]
571
+ },
572
+ i
573
+ )) })
574
+ ] }) });
575
+ }
576
+ if (error && plans.length === 0) {
577
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { id, className: `py-12 sm:py-16 md:py-20 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "container mx-auto px-4 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
578
+ sectionHeading ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-4", children: sectionHeading }) : /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sr-only", children: "Pricing" }),
579
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mb-8", children: error }),
580
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: () => window.location.reload(), children: "Retry" })
581
+ ] }) }) });
582
+ }
583
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { id, className: `py-12 sm:py-16 md:py-20 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "container mx-auto px-4 sm:px-6", children: [
584
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-8 sm:mb-12", children: [
585
+ sectionHeading ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-2xl sm:text-3xl md:text-4xl font-bold mb-3 sm:mb-4", children: sectionHeading }) : /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sr-only", children: "Pricing" }),
586
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm sm:text-base md:text-lg mb-6", children: sectionSubtitle }),
587
+ props.showAudienceTabs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center bg-muted p-1.5 rounded-xl", children: [
588
+ /* @__PURE__ */ jsxRuntime.jsx(
589
+ "button",
590
+ {
591
+ type: "button",
592
+ onClick: () => setAudience("individual"),
593
+ className: `px-6 sm:px-8 py-2.5 rounded-lg text-sm font-semibold transition-all ${audience === "individual" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
594
+ children: "Individual"
595
+ }
596
+ ),
597
+ /* @__PURE__ */ jsxRuntime.jsx(
598
+ "button",
599
+ {
600
+ type: "button",
601
+ onClick: () => setAudience("enterprise"),
602
+ className: `px-6 sm:px-8 py-2.5 rounded-lg text-sm font-semibold transition-all ${audience === "enterprise" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
603
+ children: "Enterprise"
604
+ }
605
+ )
606
+ ] }) }),
607
+ (audience === "individual" || !props.showAudienceTabs) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center bg-muted/60 p-1.5 rounded-xl", children: [
608
+ /* @__PURE__ */ jsxRuntime.jsx(
609
+ "button",
610
+ {
611
+ type: "button",
612
+ onClick: () => setBillingCycle("monthly"),
613
+ className: `px-5 sm:px-6 py-2 rounded-lg text-sm font-medium transition-all ${billingCycle === "monthly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
614
+ children: "Monthly"
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsxRuntime.jsx(
618
+ "button",
619
+ {
620
+ type: "button",
621
+ onClick: () => setBillingCycle("yearly"),
622
+ className: `px-5 sm:px-6 py-2 rounded-lg text-sm font-medium transition-all ${billingCycle === "yearly" ? "bg-background shadow-sm text-foreground" : "text-muted-foreground hover:text-foreground"}`,
623
+ children: "Yearly"
624
+ }
625
+ )
626
+ ] }) })
627
+ ] }),
628
+ props.showAudienceTabs && audience === "enterprise" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-3xl text-center mb-8", children: [
629
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl sm:text-2xl font-bold text-foreground", children: props.enterpriseHeading || "Enterprise Solutions" }),
630
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-muted-foreground", children: props.enterpriseSubtitle || "Built for teams, agencies, and organizations that need advanced collaboration, security, support, and scale." })
631
+ ] }),
632
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid md:grid-cols-3 gap-6 sm:gap-8 max-w-6xl mx-auto items-stretch", children: props.showAudienceTabs && audience === "enterprise" ? (
633
+ /* Enterprise cards — from API, static prop, or built-in default */
634
+ resolvedEnterprisePlans.map((ep) => {
635
+ const subtitle = ep.subtitle || ep.description || "";
636
+ const btnText = ep.button || ep.ctaText || "Get Started";
637
+ const btnLink = ep.buttonLink || ep.ctaLink || props.appLoginUrl;
638
+ const period = ep.period || ep.interval || "";
639
+ const note = ep.note || "";
640
+ const badge = ep.badge || "";
641
+ const isPopular = ep.popular;
642
+ const features = ep.features || [];
643
+ const price = String(ep.price);
644
+ return /* @__PURE__ */ jsxRuntime.jsxs(
645
+ "div",
646
+ {
647
+ className: `bg-card rounded-xl p-6 sm:p-8 transition-all duration-300 flex flex-col ${isPopular ? "border-2 border-primary shadow-xl ring-1 ring-primary/20" : "border hover:shadow-lg hover:border-primary/50"}`,
648
+ children: [
649
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-6", children: [
650
+ isPopular && badge && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block bg-primary text-primary-foreground px-4 py-1 rounded-full text-xs font-semibold mb-3", children: badge }),
651
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl sm:text-2xl font-bold mb-2", children: ep.name }),
652
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: subtitle })
653
+ ] }),
654
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-2", children: [
655
+ price === "Custom" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl sm:text-4xl font-bold", children: "Custom" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-center gap-1", children: [
656
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-4xl sm:text-5xl font-extrabold", children: price }),
657
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-lg", children: period })
658
+ ] }),
659
+ note && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm font-medium text-primary", children: note })
660
+ ] }),
661
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-3 mb-8 mt-6 flex-1", children: features.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs(
662
+ "li",
663
+ {
664
+ className: "text-sm flex items-start gap-2",
665
+ children: [
666
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary mt-0.5 flex-shrink-0", children: "\u2713" }),
667
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: feature })
668
+ ]
669
+ },
670
+ feature
671
+ )) }),
672
+ btnLink.startsWith("/") ? /* @__PURE__ */ jsxRuntime.jsx(
673
+ Button,
674
+ {
675
+ className: "w-full h-11",
676
+ variant: isPopular ? "default" : "outline",
677
+ size: "lg",
678
+ asChild: true,
679
+ children: /* @__PURE__ */ jsxRuntime.jsx(
680
+ "a",
681
+ {
682
+ href: btnLink,
683
+ onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }),
684
+ children: btnText
685
+ }
686
+ )
687
+ }
688
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
689
+ Button,
690
+ {
691
+ className: "w-full h-11",
692
+ variant: isPopular ? "default" : "outline",
693
+ size: "lg",
694
+ asChild: true,
695
+ children: /* @__PURE__ */ jsxRuntime.jsx(
696
+ "a",
697
+ {
698
+ href: btnLink,
699
+ target: "_blank",
700
+ rel: "noopener noreferrer",
701
+ children: btnText
702
+ }
703
+ )
704
+ }
705
+ )
706
+ ]
707
+ },
708
+ ep.name
709
+ );
710
+ })
711
+ ) : (
712
+ /* Individual API-fetched cards */
713
+ plans.map((plan) => /* @__PURE__ */ jsxRuntime.jsxs(
714
+ "div",
715
+ {
716
+ className: `bg-card rounded-xl p-6 sm:p-8 transition-all duration-300 flex flex-col ${plan.popular ? "border-2 border-primary shadow-xl ring-1 ring-primary/20" : "border hover:shadow-lg hover:border-primary/50"}`,
717
+ children: [
718
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-6", children: [
719
+ plan.popular && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block bg-primary text-primary-foreground px-4 py-1 rounded-full text-xs font-semibold mb-3", children: "Most Popular" }),
720
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl sm:text-2xl font-bold mb-2", children: plan.name }),
721
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: plan.description })
722
+ ] }),
723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center mb-8", children: plan.price === "Custom" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl sm:text-4xl font-bold", children: "Custom" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-center gap-1", children: [
724
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-4xl sm:text-5xl font-bold", children: plan.price }),
725
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-lg", children: plan.interval })
726
+ ] }) }),
727
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-3 mb-8 flex-1", children: plan.features.map((feature, index) => /* @__PURE__ */ jsxRuntime.jsxs(
728
+ "li",
729
+ {
730
+ className: "text-sm flex items-start gap-2",
731
+ children: [
732
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary mt-0.5 flex-shrink-0", children: "\u2713" }),
733
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: feature })
734
+ ]
735
+ },
736
+ index
737
+ )) }),
738
+ plan.ctaLink.startsWith("/") ? /* @__PURE__ */ jsxRuntime.jsx(
739
+ Button,
740
+ {
741
+ className: "w-full h-11",
742
+ variant: plan.popular ? "default" : "outline",
743
+ size: "lg",
744
+ asChild: true,
745
+ children: /* @__PURE__ */ jsxRuntime.jsx(
746
+ "a",
747
+ {
748
+ href: plan.ctaLink,
749
+ onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }),
750
+ children: plan.ctaText
751
+ }
752
+ )
753
+ }
754
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
755
+ Button,
756
+ {
757
+ className: "w-full h-11",
758
+ variant: plan.popular ? "default" : "outline",
759
+ size: "lg",
760
+ asChild: true,
761
+ children: /* @__PURE__ */ jsxRuntime.jsx(
762
+ "a",
763
+ {
764
+ href: plan.ctaLink,
765
+ target: "_blank",
766
+ rel: "noopener noreferrer",
767
+ children: plan.ctaText
768
+ }
769
+ )
770
+ }
771
+ )
772
+ ]
773
+ },
774
+ plan.id
775
+ ))
776
+ ) })
777
+ ] }) });
778
+ }
779
+ function PricingPage(props) {
780
+ const {
781
+ productName,
782
+ heroHeading = "Simple, Transparent Pricing",
783
+ heroSubtitle = "Choose the plan that's right for you. No hidden fees, no surprises.",
784
+ seoTitle,
785
+ seoDescription,
786
+ seoKeywords,
787
+ seoImage,
788
+ seoUrl
789
+ } = props;
790
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
791
+ seoTitle && /* @__PURE__ */ jsxRuntime.jsx(
792
+ PageHead,
793
+ {
794
+ title: seoTitle,
795
+ description: seoDescription || heroSubtitle,
796
+ productName,
797
+ keywords: seoKeywords,
798
+ image: seoImage,
799
+ url: seoUrl
800
+ }
801
+ ),
802
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "bg-gradient-to-b from-background to-muted/20 py-12 sm:py-16 md:py-20 lg:py-24", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-3xl mx-auto", children: [
803
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-bold mb-4 sm:mb-6", children: heroHeading }),
804
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg sm:text-xl text-muted-foreground leading-relaxed", children: heroSubtitle })
805
+ ] }) }) }),
806
+ /* @__PURE__ */ jsxRuntime.jsx(PricingSection, { ...props })
807
+ ] });
808
+ }
809
+
810
+ exports.PricingPage = PricingPage;
811
+ exports.PricingSection = PricingSection;
812
+ //# sourceMappingURL=pricing.js.map
813
+ //# sourceMappingURL=pricing.js.map