@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,841 @@
1
+ import * as React from 'react';
2
+ import { createContext, useState, useContext, useRef } from 'react';
3
+ import { zodResolver } from '@hookform/resolvers/zod';
4
+ import { useForm } from 'react-hook-form';
5
+ import { toast } from 'sonner';
6
+ import { z } from 'zod';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { Helmet } from 'react-helmet-async';
9
+ import { Slot } from '@radix-ui/react-slot';
10
+ import { cva } from 'class-variance-authority';
11
+ import { clsx } from 'clsx';
12
+ import { twMerge } from 'tailwind-merge';
13
+ import * as LabelPrimitive from '@radix-ui/react-label';
14
+
15
+ var __defProp = Object.defineProperty;
16
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
17
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
18
+
19
+ // src/client/security.ts
20
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
21
+ var RESERVED_HEADERS = /* @__PURE__ */ new Set([
22
+ "authorization",
23
+ "cookie",
24
+ "x-actor-id",
25
+ "x-actor-type",
26
+ "x-consent-granted-by",
27
+ "x-consent-scope",
28
+ "x-consent-granted-at",
29
+ "x-consent-expires-at"
30
+ ]);
31
+ function validateGatewayUrl(value) {
32
+ let url;
33
+ try {
34
+ url = new URL(value);
35
+ } catch {
36
+ throw new Error("Gateway URL must be a valid absolute URL");
37
+ }
38
+ if (url.protocol === "https:") {
39
+ return url.toString();
40
+ }
41
+ if (url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase())) {
42
+ return url.toString();
43
+ }
44
+ throw new Error("Gateway URL must use HTTPS unless it targets localhost");
45
+ }
46
+ function sanitizeHeaders(headers) {
47
+ if (!headers) return void 0;
48
+ return Object.fromEntries(
49
+ Object.entries(headers).filter(
50
+ ([key]) => !RESERVED_HEADERS.has(key.toLowerCase())
51
+ )
52
+ );
53
+ }
54
+
55
+ // src/client/graphql-client.ts
56
+ var _WebSDKClient = class _WebSDKClient {
57
+ constructor(config) {
58
+ __publicField(this, "config");
59
+ __publicField(this, "queryCache", /* @__PURE__ */ new Map());
60
+ __publicField(this, "inflight", /* @__PURE__ */ new Map());
61
+ this.config = {
62
+ ...config,
63
+ gatewayUrl: validateGatewayUrl(config.gatewayUrl),
64
+ headers: sanitizeHeaders(config.headers)
65
+ };
66
+ }
67
+ getConfig() {
68
+ return this.config;
69
+ }
70
+ /**
71
+ * Execute a GraphQL query (with deduplication and caching)
72
+ */
73
+ async query(query, variables) {
74
+ const cacheKey = JSON.stringify({ query, variables });
75
+ const cached = this.queryCache.get(cacheKey);
76
+ if (cached) {
77
+ if (Date.now() - cached.ts < _WebSDKClient.CACHE_TTL) {
78
+ return cached.data;
79
+ }
80
+ this.queryCache.delete(cacheKey);
81
+ }
82
+ const existing = this.inflight.get(cacheKey);
83
+ if (existing) {
84
+ return existing;
85
+ }
86
+ const promise = this.request(query, variables).then((result) => {
87
+ if (result.data && !result.errors?.length) {
88
+ if (this.queryCache.size >= _WebSDKClient.MAX_CACHE_ENTRIES) {
89
+ const oldestEntry = this.queryCache.keys().next().value;
90
+ if (oldestEntry) {
91
+ this.queryCache.delete(oldestEntry);
92
+ }
93
+ }
94
+ this.queryCache.set(cacheKey, { data: result, ts: Date.now() });
95
+ }
96
+ return result;
97
+ }).finally(() => {
98
+ this.inflight.delete(cacheKey);
99
+ });
100
+ this.inflight.set(cacheKey, promise);
101
+ return promise;
102
+ }
103
+ /**
104
+ * Execute a GraphQL mutation
105
+ */
106
+ async mutate(mutation, variables) {
107
+ return this.request(mutation, variables);
108
+ }
109
+ async request(query, variables) {
110
+ try {
111
+ const response = await fetch(this.config.gatewayUrl, {
112
+ method: "POST",
113
+ headers: {
114
+ "Content-Type": "application/json",
115
+ "x-product-id": this.config.productId,
116
+ ...this.config.headers
117
+ },
118
+ body: JSON.stringify({ query, variables })
119
+ });
120
+ if (!response.ok) {
121
+ console.error(
122
+ "[WebSDK] GraphQL request failed",
123
+ response.status,
124
+ response.statusText
125
+ );
126
+ return {
127
+ errors: [
128
+ {
129
+ message: `HTTP ${response.status}: ${response.statusText}`,
130
+ extensions: { code: `HTTP_${response.status}` }
131
+ }
132
+ ]
133
+ };
134
+ }
135
+ return await response.json();
136
+ } catch (error) {
137
+ console.error(
138
+ "[WebSDK] Network error",
139
+ error instanceof Error ? error.message : "unknown error"
140
+ );
141
+ return {
142
+ errors: [
143
+ {
144
+ message: error instanceof Error ? error.message : "Network error",
145
+ extensions: { code: "NETWORK_ERROR" }
146
+ }
147
+ ]
148
+ };
149
+ }
150
+ }
151
+ };
152
+ __publicField(_WebSDKClient, "CACHE_TTL", 5 * 60 * 1e3);
153
+ // 5 minutes
154
+ __publicField(_WebSDKClient, "MAX_CACHE_ENTRIES", 100);
155
+ var WebSDKContext = createContext(null);
156
+ function useWebSDK() {
157
+ const client = useContext(WebSDKContext);
158
+ if (!client) {
159
+ throw new Error("useWebSDK must be used within a WebSDKProvider");
160
+ }
161
+ return client;
162
+ }
163
+ function useWebSDKConfig() {
164
+ const client = useWebSDK();
165
+ return client.getConfig();
166
+ }
167
+ var ProductContext = createContext({
168
+ product: null,
169
+ loading: true,
170
+ error: null,
171
+ hasProvider: false,
172
+ refetch: () => {
173
+ }
174
+ });
175
+ function useProduct() {
176
+ return useContext(ProductContext);
177
+ }
178
+ function PageHead({
179
+ title,
180
+ description,
181
+ productName,
182
+ keywords,
183
+ image,
184
+ url,
185
+ favicon,
186
+ robots = "index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1",
187
+ locale = "en_US",
188
+ twitterHandle,
189
+ themeColor = "#000000",
190
+ structuredData,
191
+ additionalMeta
192
+ }) {
193
+ const fullTitle = productName && !title.includes(productName) ? `${title} | ${productName}` : title;
194
+ const fullImageUrl = image && !image.startsWith("http") && url ? `${url.replace(/\/$/, "")}${image}` : image;
195
+ const structuredDataItems = structuredData ? Array.isArray(structuredData) ? structuredData : [structuredData] : [];
196
+ return /* @__PURE__ */ jsxs(Helmet, { children: [
197
+ /* @__PURE__ */ jsx("title", { children: fullTitle }),
198
+ /* @__PURE__ */ jsx("meta", { name: "title", content: fullTitle }),
199
+ description && /* @__PURE__ */ jsx("meta", { name: "description", content: description }),
200
+ keywords && /* @__PURE__ */ jsx("meta", { name: "keywords", content: keywords }),
201
+ /* @__PURE__ */ jsx("meta", { name: "author", content: "Burdenoff Consultancy Services Pvt Ltd" }),
202
+ /* @__PURE__ */ jsx("meta", { name: "robots", content: robots }),
203
+ url && /* @__PURE__ */ jsx("link", { rel: "canonical", href: url }),
204
+ favicon && /* @__PURE__ */ jsx("link", { rel: "icon", href: favicon }),
205
+ /* @__PURE__ */ jsx("meta", { property: "og:type", content: "website" }),
206
+ /* @__PURE__ */ jsx("meta", { property: "og:title", content: fullTitle }),
207
+ description && /* @__PURE__ */ jsx("meta", { property: "og:description", content: description }),
208
+ fullImageUrl && /* @__PURE__ */ jsx("meta", { property: "og:image", content: fullImageUrl }),
209
+ url && /* @__PURE__ */ jsx("meta", { property: "og:url", content: url }),
210
+ /* @__PURE__ */ jsx("meta", { property: "og:locale", content: locale }),
211
+ productName && /* @__PURE__ */ jsx("meta", { property: "og:site_name", content: productName }),
212
+ /* @__PURE__ */ jsx("meta", { name: "twitter:card", content: "summary_large_image" }),
213
+ /* @__PURE__ */ jsx("meta", { name: "twitter:title", content: fullTitle }),
214
+ description && /* @__PURE__ */ jsx("meta", { name: "twitter:description", content: description }),
215
+ fullImageUrl && /* @__PURE__ */ jsx("meta", { name: "twitter:image", content: fullImageUrl }),
216
+ twitterHandle && /* @__PURE__ */ jsx("meta", { name: "twitter:creator", content: twitterHandle }),
217
+ /* @__PURE__ */ jsx("meta", { name: "theme-color", content: themeColor }),
218
+ structuredDataItems.map((data, index) => /* @__PURE__ */ jsx("script", { type: "application/ld+json", children: JSON.stringify(data) }, `ld-${index}`)),
219
+ additionalMeta?.map((meta, index) => /* @__PURE__ */ jsx(
220
+ "meta",
221
+ {
222
+ ...meta.name ? { name: meta.name } : {},
223
+ ...meta.property ? { property: meta.property } : {},
224
+ content: meta.content
225
+ },
226
+ `meta-${index}`
227
+ ))
228
+ ] });
229
+ }
230
+ function cn(...inputs) {
231
+ return twMerge(clsx(inputs));
232
+ }
233
+ var buttonVariants = cva(
234
+ "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",
235
+ {
236
+ variants: {
237
+ variant: {
238
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
239
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
240
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
241
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
242
+ ghost: "hover:bg-accent hover:text-accent-foreground",
243
+ link: "text-primary underline-offset-4 hover:underline"
244
+ },
245
+ size: {
246
+ default: "h-9 px-4 py-2",
247
+ sm: "h-8 rounded-md px-3 text-xs",
248
+ lg: "h-10 rounded-md px-8",
249
+ icon: "h-9 w-9"
250
+ }
251
+ },
252
+ defaultVariants: {
253
+ variant: "default",
254
+ size: "default"
255
+ }
256
+ }
257
+ );
258
+ var Button = React.forwardRef(
259
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
260
+ const Comp = asChild ? Slot : "button";
261
+ return /* @__PURE__ */ jsx(
262
+ Comp,
263
+ {
264
+ className: cn(buttonVariants({ variant, size, className })),
265
+ ref,
266
+ ...props
267
+ }
268
+ );
269
+ }
270
+ );
271
+ Button.displayName = "Button";
272
+ var Input = React.forwardRef(
273
+ ({ className, type, ...props }, ref) => {
274
+ return /* @__PURE__ */ jsx(
275
+ "input",
276
+ {
277
+ type,
278
+ className: cn(
279
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
280
+ className
281
+ ),
282
+ ref,
283
+ ...props
284
+ }
285
+ );
286
+ }
287
+ );
288
+ Input.displayName = "Input";
289
+ var labelVariants = cva(
290
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
291
+ );
292
+ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
293
+ LabelPrimitive.Root,
294
+ {
295
+ ref,
296
+ className: cn(labelVariants(), className),
297
+ ...props
298
+ }
299
+ ));
300
+ Label.displayName = LabelPrimitive.Root.displayName;
301
+ var Textarea = React.forwardRef(({ className, ...props }, ref) => {
302
+ return /* @__PURE__ */ jsx(
303
+ "textarea",
304
+ {
305
+ className: cn(
306
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
307
+ className
308
+ ),
309
+ ref,
310
+ ...props
311
+ }
312
+ );
313
+ });
314
+ Textarea.displayName = "Textarea";
315
+ var waitlistPageSchema = z.object({
316
+ name: z.string().min(1, "Name is required"),
317
+ email: z.string().email("Please enter a valid email address"),
318
+ company: z.string().optional(),
319
+ reason: z.string().optional()
320
+ });
321
+ var JOIN_WAITLIST_MUTATION = (
322
+ /* GraphQL */
323
+ `
324
+ mutation JoinWaitlist($input: JoinWaitlistInput!) {
325
+ joinWaitlist(input: $input) {
326
+ id
327
+ email
328
+ name
329
+ status
330
+ createdAt
331
+ }
332
+ }
333
+ `
334
+ );
335
+ async function joinWaitlist(client, data, productId) {
336
+ try {
337
+ const result = await client.mutate(JOIN_WAITLIST_MUTATION, {
338
+ input: {
339
+ email: data.email.trim().toLowerCase(),
340
+ platformProductId: productId,
341
+ name: data.name?.trim() || void 0,
342
+ company: data.company?.trim() || void 0,
343
+ reason: data.reason?.trim() || void 0,
344
+ context: {
345
+ source: "website",
346
+ referrer: typeof window !== "undefined" ? window.location.href : "",
347
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
348
+ }
349
+ }
350
+ });
351
+ if (result.errors?.length) {
352
+ const errorMsg = result.errors[0]?.message ?? "Failed to join waitlist";
353
+ if (errorMsg.toLowerCase().includes("already")) {
354
+ return {
355
+ success: false,
356
+ message: "You are already on the waitlist!"
357
+ };
358
+ }
359
+ return { success: false, message: errorMsg };
360
+ }
361
+ return { success: true, message: "You have been added to the waitlist!" };
362
+ } catch (error) {
363
+ console.error("Waitlist signup error:", error);
364
+ return { success: false, message: "Network error. Please try again." };
365
+ }
366
+ }
367
+ function useRateLimit(maxAttempts, windowMinutes) {
368
+ const rateLimitRef = useRef(null);
369
+ const [isLimited, setIsLimited] = useState(false);
370
+ const [resetTime, setResetTime] = useState(null);
371
+ function recordAttempt() {
372
+ const now = Date.now();
373
+ const state = rateLimitRef.current;
374
+ if (state && now > state.resetTime) {
375
+ rateLimitRef.current = null;
376
+ }
377
+ const current = rateLimitRef.current;
378
+ if (!current) {
379
+ rateLimitRef.current = {
380
+ attempts: 1,
381
+ resetTime: now + windowMinutes * 60 * 1e3
382
+ };
383
+ return false;
384
+ }
385
+ current.attempts += 1;
386
+ if (current.attempts >= maxAttempts) {
387
+ setIsLimited(true);
388
+ setResetTime(new Date(current.resetTime));
389
+ return true;
390
+ }
391
+ return false;
392
+ }
393
+ function checkLimit() {
394
+ const state = rateLimitRef.current;
395
+ if (!state) return false;
396
+ if (Date.now() > state.resetTime) {
397
+ rateLimitRef.current = null;
398
+ setIsLimited(false);
399
+ setResetTime(null);
400
+ return false;
401
+ }
402
+ return state.attempts >= maxAttempts;
403
+ }
404
+ return { isLimited, resetTime, recordAttempt, checkLimit };
405
+ }
406
+ function WaitlistPage({
407
+ productName,
408
+ heroTitle = "Join the Waitlist",
409
+ heroSubtitle = "Be among the first to experience what's coming next",
410
+ heroDescription,
411
+ successMessage = "You have been added to the waitlist!",
412
+ errorMessage = "Failed to join waitlist. Please try again.",
413
+ className,
414
+ seo
415
+ }) {
416
+ const client = useWebSDK();
417
+ const config = useWebSDKConfig();
418
+ const {
419
+ product,
420
+ loading: productLoading,
421
+ error: productError,
422
+ hasProvider
423
+ } = useProduct();
424
+ const displayName = productName ?? product?.name ?? config.productId;
425
+ const productStatus = product?.status;
426
+ const canCheckStatus = Boolean(config.productSlug);
427
+ const [isSubmitting, setIsSubmitting] = useState(false);
428
+ const { isLimited, resetTime, recordAttempt, checkLimit } = useRateLimit(
429
+ 3,
430
+ 15
431
+ );
432
+ const {
433
+ register,
434
+ handleSubmit,
435
+ formState: { errors },
436
+ reset
437
+ } = useForm({
438
+ resolver: zodResolver(waitlistPageSchema)
439
+ });
440
+ if (canCheckStatus && !hasProvider) {
441
+ return /* @__PURE__ */ jsxs("div", { className, children: [
442
+ seo && /* @__PURE__ */ jsx(
443
+ PageHead,
444
+ {
445
+ title: seo.title ?? "",
446
+ description: seo.description ?? "",
447
+ keywords: seo.keywords,
448
+ image: seo.image,
449
+ url: seo.url
450
+ }
451
+ ),
452
+ /* @__PURE__ */ jsx("section", { className: "relative overflow-hidden py-24 lg:py-32", children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-6", children: [
453
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight sm:text-5xl text-foreground", children: "Waitlist Unavailable" }),
454
+ /* @__PURE__ */ jsxs("p", { className: "text-lg text-muted-foreground", children: [
455
+ "Product status checks require ",
456
+ /* @__PURE__ */ jsx("code", { children: "ProductProvider" }),
457
+ " to be mounted. Please update your integration configuration."
458
+ ] })
459
+ ] }) })
460
+ ] });
461
+ }
462
+ if (canCheckStatus && productLoading) {
463
+ return /* @__PURE__ */ jsxs("div", { className, children: [
464
+ seo && /* @__PURE__ */ jsx(
465
+ PageHead,
466
+ {
467
+ title: seo.title ?? "",
468
+ description: seo.description ?? "",
469
+ keywords: seo.keywords,
470
+ image: seo.image,
471
+ url: seo.url
472
+ }
473
+ ),
474
+ /* @__PURE__ */ jsx("section", { className: "relative overflow-hidden py-24 lg:py-32", children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-6", children: [
475
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight sm:text-5xl text-foreground", children: "Loading\u2026" }),
476
+ /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: "Fetching product status." })
477
+ ] }) })
478
+ ] });
479
+ }
480
+ if (canCheckStatus && (productError || !productStatus)) {
481
+ return /* @__PURE__ */ jsxs("div", { className, children: [
482
+ seo && /* @__PURE__ */ jsx(
483
+ PageHead,
484
+ {
485
+ title: seo.title ?? "",
486
+ description: seo.description ?? "",
487
+ keywords: seo.keywords,
488
+ image: seo.image,
489
+ url: seo.url
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx("section", { className: "relative overflow-hidden py-24 lg:py-32", children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-6", children: [
493
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight sm:text-5xl text-foreground", children: "Waitlist Unavailable" }),
494
+ /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: "We can\u2019t confirm waitlist availability right now. Please try again later." })
495
+ ] }) })
496
+ ] });
497
+ }
498
+ if (canCheckStatus && (productStatus === "ACTIVE" || productStatus === "RELEASED")) {
499
+ const appUrl = product?.appUrl || product?.website || "#";
500
+ return /* @__PURE__ */ jsxs("div", { className, children: [
501
+ seo && /* @__PURE__ */ jsx(
502
+ PageHead,
503
+ {
504
+ title: seo.title ?? "",
505
+ description: seo.description ?? "",
506
+ keywords: seo.keywords,
507
+ image: seo.image,
508
+ url: seo.url
509
+ }
510
+ ),
511
+ /* @__PURE__ */ jsx("section", { className: "relative overflow-hidden py-24 lg:py-32", children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-8", children: [
512
+ /* @__PURE__ */ jsxs("h1", { className: "text-4xl font-bold tracking-tight sm:text-5xl text-foreground", children: [
513
+ displayName,
514
+ " is now available!"
515
+ ] }),
516
+ /* @__PURE__ */ jsxs("p", { className: "text-lg text-muted-foreground", children: [
517
+ "Great news \u2014 ",
518
+ displayName,
519
+ " is open to everyone. Sign up or log in to get started."
520
+ ] }),
521
+ /* @__PURE__ */ jsx(Button, { size: "lg", asChild: true, children: /* @__PURE__ */ jsx("a", { href: appUrl, target: "_blank", rel: "noopener noreferrer", children: "Get Started" }) })
522
+ ] }) })
523
+ ] });
524
+ }
525
+ if (canCheckStatus && (productStatus === "DRAFT" || productStatus === "COMING_SOON")) {
526
+ return /* @__PURE__ */ jsxs("div", { className, children: [
527
+ seo && /* @__PURE__ */ jsx(
528
+ PageHead,
529
+ {
530
+ title: seo.title ?? "",
531
+ description: seo.description ?? "",
532
+ keywords: seo.keywords,
533
+ image: seo.image,
534
+ url: seo.url
535
+ }
536
+ ),
537
+ /* @__PURE__ */ jsx("section", { className: "relative overflow-hidden py-24 lg:py-32", children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-8", children: [
538
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight sm:text-5xl text-foreground", children: "Coming Soon" }),
539
+ /* @__PURE__ */ jsxs("p", { className: "text-lg text-muted-foreground", children: [
540
+ displayName,
541
+ " is not yet accepting waitlist signups. Check back soon!"
542
+ ] })
543
+ ] }) })
544
+ ] });
545
+ }
546
+ if (canCheckStatus && productStatus !== "WAITLIST") {
547
+ return /* @__PURE__ */ jsxs("div", { className, children: [
548
+ seo && /* @__PURE__ */ jsx(
549
+ PageHead,
550
+ {
551
+ title: seo.title ?? "",
552
+ description: seo.description ?? "",
553
+ keywords: seo.keywords,
554
+ image: seo.image,
555
+ url: seo.url
556
+ }
557
+ ),
558
+ /* @__PURE__ */ jsx("section", { className: "relative overflow-hidden py-24 lg:py-32", children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center space-y-6", children: [
559
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight sm:text-5xl text-foreground", children: "Waitlist Unavailable" }),
560
+ /* @__PURE__ */ jsxs("p", { className: "text-lg text-muted-foreground", children: [
561
+ displayName,
562
+ " is not accepting waitlist signups right now."
563
+ ] })
564
+ ] }) })
565
+ ] });
566
+ }
567
+ const onSubmit = async (data) => {
568
+ if (canCheckStatus && productStatus !== "WAITLIST") {
569
+ toast.error("This product is not accepting waitlist signups right now.");
570
+ return;
571
+ }
572
+ if (checkLimit()) {
573
+ toast.error(
574
+ `Too many attempts. Please try again after ${resetTime?.toLocaleTimeString()}`
575
+ );
576
+ return;
577
+ }
578
+ setIsSubmitting(true);
579
+ try {
580
+ const result = await joinWaitlist(
581
+ client,
582
+ data,
583
+ product?.id || config.productId
584
+ );
585
+ if (result.success) {
586
+ toast.success(result.message ?? successMessage);
587
+ reset();
588
+ } else {
589
+ toast.error(result.message ?? errorMessage);
590
+ }
591
+ recordAttempt();
592
+ } catch (_error) {
593
+ toast.error(errorMessage);
594
+ } finally {
595
+ setIsSubmitting(false);
596
+ }
597
+ };
598
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
599
+ seo && /* @__PURE__ */ jsx(
600
+ PageHead,
601
+ {
602
+ title: seo.title ?? `Join the Waitlist - ${displayName}`,
603
+ description: seo.description ?? heroDescription ?? heroSubtitle,
604
+ productName: displayName,
605
+ keywords: seo.keywords,
606
+ image: seo.image,
607
+ url: seo.url
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center px-4 py-12", children: /* @__PURE__ */ jsxs("div", { className: "max-w-lg w-full space-y-8", children: [
611
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
612
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight", children: heroTitle }),
613
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-lg text-muted-foreground", children: heroSubtitle }),
614
+ heroDescription && /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: heroDescription })
615
+ ] }),
616
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-5", children: [
617
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
618
+ /* @__PURE__ */ jsx(Label, { htmlFor: "waitlist-name", children: "Name *" }),
619
+ /* @__PURE__ */ jsx(
620
+ Input,
621
+ {
622
+ id: "waitlist-name",
623
+ type: "text",
624
+ placeholder: "Your name",
625
+ disabled: isSubmitting || isLimited,
626
+ ...register("name")
627
+ }
628
+ ),
629
+ errors.name && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errors.name.message })
630
+ ] }),
631
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
632
+ /* @__PURE__ */ jsx(Label, { htmlFor: "waitlist-email", children: "Email *" }),
633
+ /* @__PURE__ */ jsx(
634
+ Input,
635
+ {
636
+ id: "waitlist-email",
637
+ type: "email",
638
+ placeholder: "you@example.com",
639
+ disabled: isSubmitting || isLimited,
640
+ ...register("email")
641
+ }
642
+ ),
643
+ errors.email && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: errors.email.message })
644
+ ] }),
645
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
646
+ /* @__PURE__ */ jsx(Label, { htmlFor: "waitlist-company", children: "Company" }),
647
+ /* @__PURE__ */ jsx(
648
+ Input,
649
+ {
650
+ id: "waitlist-company",
651
+ type: "text",
652
+ placeholder: "Your company (optional)",
653
+ disabled: isSubmitting || isLimited,
654
+ ...register("company")
655
+ }
656
+ )
657
+ ] }),
658
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
659
+ /* @__PURE__ */ jsx(Label, { htmlFor: "waitlist-reason", children: "Why are you interested?" }),
660
+ /* @__PURE__ */ jsx(
661
+ Textarea,
662
+ {
663
+ id: "waitlist-reason",
664
+ placeholder: "Tell us what excites you about the product (optional)",
665
+ rows: 3,
666
+ disabled: isSubmitting || isLimited,
667
+ className: "resize-y",
668
+ ...register("reason")
669
+ }
670
+ )
671
+ ] }),
672
+ /* @__PURE__ */ jsx(
673
+ Button,
674
+ {
675
+ type: "submit",
676
+ className: "w-full",
677
+ disabled: isSubmitting || isLimited,
678
+ children: isSubmitting ? "Joining..." : "Join Waitlist"
679
+ }
680
+ ),
681
+ isLimited && resetTime && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground text-center", children: [
682
+ "Rate limit reached. Try again after",
683
+ " ",
684
+ resetTime.toLocaleTimeString()
685
+ ] })
686
+ ] }),
687
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground text-center", children: [
688
+ "By joining the waitlist, you agree to receive updates from",
689
+ " ",
690
+ displayName,
691
+ ". We respect your privacy and will never share your information."
692
+ ] })
693
+ ] }) }) })
694
+ ] });
695
+ }
696
+ function WaitlistForm({
697
+ variant = "inline",
698
+ showLabel = false,
699
+ showDescription = false,
700
+ showNameField = false,
701
+ description = "Join our waitlist to get early access",
702
+ buttonText = "Join Waitlist",
703
+ className = ""
704
+ }) {
705
+ const client = useWebSDK();
706
+ const config = useWebSDKConfig();
707
+ const {
708
+ product,
709
+ loading: productLoading,
710
+ error: productError,
711
+ hasProvider
712
+ } = useProduct();
713
+ const productStatus = product?.status;
714
+ const canCheckStatus = Boolean(config.productSlug);
715
+ const [email, setEmail] = useState("");
716
+ const [name, setName] = useState("");
717
+ const [isLoading, setIsLoading] = useState(false);
718
+ const handleSubmit = async (e) => {
719
+ e.preventDefault();
720
+ if (canCheckStatus && productStatus !== "WAITLIST") {
721
+ toast.error("This product is not accepting waitlist signups right now.");
722
+ return;
723
+ }
724
+ if (!email || !email.includes("@")) {
725
+ toast.error("Please enter a valid email address");
726
+ return;
727
+ }
728
+ setIsLoading(true);
729
+ try {
730
+ const result = await joinWaitlist(
731
+ client,
732
+ { email, name: name || void 0 },
733
+ product?.id || config.productId
734
+ );
735
+ if (result.success) {
736
+ toast.success(result.message ?? "You have been added to the waitlist!");
737
+ setEmail("");
738
+ setName("");
739
+ } else {
740
+ toast.error(result.message ?? "Failed to join waitlist");
741
+ }
742
+ } catch (_error) {
743
+ toast.error("An error occurred. Please try again.");
744
+ } finally {
745
+ setIsLoading(false);
746
+ }
747
+ };
748
+ const isInline = variant === "inline";
749
+ if (canCheckStatus && !hasProvider) {
750
+ return /* @__PURE__ */ jsxs("div", { className, children: [
751
+ showDescription && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: description }),
752
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
753
+ "Product status checks require ",
754
+ /* @__PURE__ */ jsx("code", { children: "ProductProvider" }),
755
+ " to be mounted."
756
+ ] })
757
+ ] });
758
+ }
759
+ if (canCheckStatus && productLoading) {
760
+ return /* @__PURE__ */ jsxs("div", { className, children: [
761
+ showDescription && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: description }),
762
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading waitlist\u2026" })
763
+ ] });
764
+ }
765
+ if (canCheckStatus && (productError || productStatus !== "WAITLIST")) {
766
+ return /* @__PURE__ */ jsxs("div", { className, children: [
767
+ showDescription && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: description }),
768
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Waitlist signups are not available right now." })
769
+ ] });
770
+ }
771
+ return /* @__PURE__ */ jsxs("div", { className, children: [
772
+ showDescription && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: description }),
773
+ /* @__PURE__ */ jsxs(
774
+ "form",
775
+ {
776
+ onSubmit: handleSubmit,
777
+ className: isInline ? "flex gap-2" : "flex flex-col gap-2",
778
+ children: [
779
+ showNameField && /* @__PURE__ */ jsxs("div", { className: isInline ? "flex-1" : "w-full", children: [
780
+ showLabel && /* @__PURE__ */ jsx(
781
+ Label,
782
+ {
783
+ htmlFor: "waitlist-inline-name",
784
+ className: "text-sm mb-1 block",
785
+ children: "Name"
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsx(
789
+ Input,
790
+ {
791
+ id: "waitlist-inline-name",
792
+ type: "text",
793
+ placeholder: "Your name",
794
+ value: name,
795
+ onChange: (e) => setName(e.target.value),
796
+ disabled: isLoading,
797
+ className: "w-full"
798
+ }
799
+ )
800
+ ] }),
801
+ /* @__PURE__ */ jsxs("div", { className: isInline ? "flex-1" : "w-full", children: [
802
+ showLabel && /* @__PURE__ */ jsx(
803
+ Label,
804
+ {
805
+ htmlFor: "waitlist-inline-email",
806
+ className: "text-sm mb-1 block",
807
+ children: "Email"
808
+ }
809
+ ),
810
+ /* @__PURE__ */ jsx(
811
+ Input,
812
+ {
813
+ id: "waitlist-inline-email",
814
+ type: "email",
815
+ placeholder: "Enter your email",
816
+ value: email,
817
+ onChange: (e) => setEmail(e.target.value),
818
+ disabled: isLoading,
819
+ required: true,
820
+ className: "w-full"
821
+ }
822
+ )
823
+ ] }),
824
+ /* @__PURE__ */ jsx(
825
+ Button,
826
+ {
827
+ type: "submit",
828
+ disabled: isLoading,
829
+ className: isInline ? "whitespace-nowrap" : "w-full",
830
+ children: isLoading ? "Joining..." : buttonText
831
+ }
832
+ )
833
+ ]
834
+ }
835
+ )
836
+ ] });
837
+ }
838
+
839
+ export { WaitlistForm, WaitlistPage };
840
+ //# sourceMappingURL=waitlist.mjs.map
841
+ //# sourceMappingURL=waitlist.mjs.map