@godgpt/waitlist-flow 0.1.0

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 ADDED
@@ -0,0 +1,433 @@
1
+ # godgpt-web-waitlist
2
+
3
+ A configurable React waitlist modal component with Mailchimp and Cloudflare Turnstile support.
4
+
5
+ ## Features
6
+
7
+ - 🎨 **Configurable Content** - Customize title, subtitle, and success messages
8
+ - 🔄 **Controlled & Uncontrolled Modes** - Use with or without external state management
9
+ - 📧 **Mailchimp Compatible** - Form fields match Mailchimp's expected schema
10
+ - 🛡️ **Cloudflare Turnstile** - Built-in spam protection with visible or managed modes
11
+ - 🍯 **Honeypot Protection** - Alternative spam protection using hidden form fields
12
+ - ❌ **Dismissible Modal** - Optional X button and click-outside-to-close
13
+ - 📱 **Mobile-First Design** - Optimized for touch interactions
14
+ - 🎯 **TypeScript** - Full type definitions included
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install godgpt-web-waitlist
20
+ ```
21
+
22
+ ### Peer Dependencies
23
+
24
+ This package requires React 18+ and Tailwind CSS:
25
+
26
+ ```bash
27
+ npm install react react-dom
28
+ ```
29
+
30
+ ### Tailwind CSS Setup
31
+
32
+ This component uses Tailwind CSS classes. Make sure Tailwind is configured in your project and add the package to your `content` paths:
33
+
34
+ ```js
35
+ // tailwind.config.js
36
+ module.exports = {
37
+ content: [
38
+ "./src/**/*.{js,ts,jsx,tsx}",
39
+ "./node_modules/godgpt-web-waitlist/**/*.{js,ts,jsx,tsx}",
40
+ ],
41
+ // ...
42
+ };
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```tsx
48
+ import { WaitlistCard } from "godgpt-web-waitlist";
49
+
50
+ function App() {
51
+ return (
52
+ <WaitlistCard
53
+ title="Join the Waitlist"
54
+ subtitle="Be the first to know when we launch."
55
+ onDone={() => console.log("Done!")}
56
+ />
57
+ );
58
+ }
59
+ ```
60
+
61
+ ## Usage Modes
62
+
63
+ ### Uncontrolled Mode (Simple)
64
+
65
+ Let the component manage its own state. Great for quick implementations:
66
+
67
+ ```tsx
68
+ <WaitlistCard
69
+ title="Join the Waitlist"
70
+ subtitle="Enter your details below."
71
+ actionUrl="https://your-api.com/subscribe"
72
+ onDone={() => console.log("Success!")}
73
+ />
74
+ ```
75
+
76
+ ### Controlled Mode (Full Control)
77
+
78
+ Manage state externally for custom submission logic:
79
+
80
+ ```tsx
81
+ import { WaitlistCard, WaitlistFormData } from "godgpt-web-waitlist";
82
+
83
+ function App() {
84
+ const [isSubmitting, setIsSubmitting] = useState(false);
85
+ const [isSuccess, setIsSuccess] = useState(false);
86
+ const [error, setError] = useState<string | null>(null);
87
+
88
+ const handleSubmit = async (data: WaitlistFormData) => {
89
+ setIsSubmitting(true);
90
+ setError(null);
91
+
92
+ try {
93
+ const response = await fetch("/api/subscribe", {
94
+ method: "POST",
95
+ headers: { "Content-Type": "application/json" },
96
+ body: JSON.stringify(data),
97
+ });
98
+
99
+ if (!response.ok) {
100
+ throw new Error("Subscription failed");
101
+ }
102
+
103
+ setIsSuccess(true);
104
+ } catch (err) {
105
+ setError("Something went wrong. Please try again.");
106
+ } finally {
107
+ setIsSubmitting(false);
108
+ }
109
+ };
110
+
111
+ return (
112
+ <WaitlistCard
113
+ title="Join the Waitlist"
114
+ onSubmit={handleSubmit}
115
+ isSubmitting={isSubmitting}
116
+ isSuccess={isSuccess}
117
+ error={error}
118
+ onDone={() => setIsSuccess(false)}
119
+ />
120
+ );
121
+ }
122
+ ```
123
+
124
+ ## Cloudflare Turnstile Integration
125
+
126
+ ### Visible Mode (Interactive Checkbox)
127
+
128
+ ```tsx
129
+ <WaitlistCard
130
+ title="Secure Waitlist"
131
+ turnstileSiteKey="your-site-key"
132
+ turnstileTheme="light"
133
+ onSubmit={(data) => {
134
+ console.log("Token:", data["cf-turnstile-response"]);
135
+ }}
136
+ isSubmitting={isSubmitting}
137
+ isSuccess={isSuccess}
138
+ />
139
+ ```
140
+
141
+ ### Managed Mode (Invisible)
142
+
143
+ For backend-managed Turnstile verification:
144
+
145
+ ```tsx
146
+ <WaitlistCard
147
+ title="Join the Waitlist"
148
+ turnstileSiteKey="your-site-key"
149
+ turnstileManaged={true} // Hides the widget
150
+ onSubmit={handleSubmit}
151
+ isSubmitting={isSubmitting}
152
+ isSuccess={isSuccess}
153
+ error={error}
154
+ />
155
+ ```
156
+
157
+ ### Backend Verification
158
+
159
+ When using Turnstile, verify the token on your backend:
160
+
161
+ ```typescript
162
+ // Example: Cloudflare Pages Function
163
+ export async function onRequestPost({ request, env }) {
164
+ const formData = await request.json();
165
+
166
+ // Get token and IP
167
+ const token = formData["cf-turnstile-response"];
168
+ const ip = request.headers.get("CF-Connecting-IP");
169
+
170
+ // Verify with Cloudflare
171
+ const verifyResponse = await fetch(
172
+ "https://challenges.cloudflare.com/turnstile/v0/siteverify",
173
+ {
174
+ method: "POST",
175
+ headers: { "Content-Type": "application/json" },
176
+ body: JSON.stringify({
177
+ secret: env.TURNSTILE_SECRET_KEY,
178
+ response: token,
179
+ remoteip: ip,
180
+ }),
181
+ }
182
+ );
183
+
184
+ const result = await verifyResponse.json();
185
+
186
+ if (!result.success) {
187
+ return new Response(JSON.stringify({ message: "Verification failed" }), {
188
+ status: 403,
189
+ });
190
+ }
191
+
192
+ // Continue with subscription...
193
+ }
194
+ ```
195
+
196
+ **Note:** Add the Turnstile script to your HTML:
197
+
198
+ ```html
199
+ <script
200
+ src="https://challenges.cloudflare.com/turnstile/v0/api.js"
201
+ async
202
+ defer
203
+ ></script>
204
+ ```
205
+
206
+ ## Honeypot Integration
207
+
208
+ Honeypot is a lightweight spam protection alternative to Turnstile. It uses a hidden form field that bots typically fill but humans won't see.
209
+
210
+ ### Uncontrolled Mode with Honeypot
211
+
212
+ ```tsx
213
+ <WaitlistCard
214
+ title="Join the Waitlist"
215
+ subtitle="Enter your details below."
216
+ honeypotFieldName="b_eecb4f508c0388d7720a99c82_0ad1109319" // Your honeypot field name
217
+ actionUrl="https://your-api.com/subscribe"
218
+ onDone={() => console.log("Success!")}
219
+ />
220
+ ```
221
+
222
+ ### Controlled Mode with Honeypot
223
+
224
+ ```tsx
225
+ import { WaitlistCard, WaitlistFormData } from "godgpt-web-waitlist";
226
+
227
+ function App() {
228
+ const [isSubmitting, setIsSubmitting] = useState(false);
229
+ const [isSuccess, setIsSuccess] = useState(false);
230
+ const [error, setError] = useState<string | null>(null);
231
+ const honeypotFieldName = "b_eecb4f508c0388d7720a99c82_0ad1109319";
232
+
233
+ const handleSubmit = async (data: WaitlistFormData) => {
234
+ // Check honeypot field
235
+ const honeypotValue = data[honeypotFieldName];
236
+
237
+ // If honeypot is filled, it's a bot - silently reject
238
+ if (honeypotValue && honeypotValue.trim()) {
239
+ console.log("Bot detected - honeypot field was filled");
240
+ return; // Don't process, don't show error
241
+ }
242
+
243
+ setIsSubmitting(true);
244
+ setError(null);
245
+
246
+ try {
247
+ const response = await fetch("/api/subscribe", {
248
+ method: "POST",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify({
251
+ EMAIL: data.EMAIL,
252
+ FNAME: data.FNAME,
253
+ LNAME: data.LNAME,
254
+ // Include honeypot value for backend verification
255
+ [honeypotFieldName]: honeypotValue || "",
256
+ }),
257
+ });
258
+
259
+ if (!response.ok) {
260
+ throw new Error("Subscription failed");
261
+ }
262
+
263
+ setIsSuccess(true);
264
+ } catch (err) {
265
+ setError("Something went wrong. Please try again.");
266
+ } finally {
267
+ setIsSubmitting(false);
268
+ }
269
+ };
270
+
271
+ return (
272
+ <WaitlistCard
273
+ title="Join the Waitlist"
274
+ honeypotFieldName={honeypotFieldName}
275
+ onSubmit={handleSubmit}
276
+ isSubmitting={isSubmitting}
277
+ isSuccess={isSuccess}
278
+ error={error}
279
+ onDone={() => setIsSuccess(false)}
280
+ />
281
+ );
282
+ }
283
+ ```
284
+
285
+ ### Backend Verification
286
+
287
+ Always verify the honeypot on your backend:
288
+
289
+ ```typescript
290
+ // Example: Cloudflare Pages Function
291
+ export async function onRequestPost({ request, env }) {
292
+ const formData = await request.json();
293
+
294
+ const honeypotFieldName = "b_eecb4f508c0388d7720a99c82_0ad1109319";
295
+ const honeypotValue = formData[honeypotFieldName];
296
+
297
+ // If honeypot is filled, it's a bot - reject silently
298
+ if (honeypotValue && honeypotValue.trim()) {
299
+ // Return success to bot (don't reveal it's a honeypot)
300
+ return new Response(JSON.stringify({ success: true }), {
301
+ status: 200,
302
+ headers: { "Content-Type": "application/json" },
303
+ });
304
+ }
305
+
306
+ // Process legitimate submission
307
+ const { EMAIL, FNAME, LNAME } = formData;
308
+
309
+ // Add to Mailchimp or your database
310
+ // ...
311
+
312
+ return new Response(JSON.stringify({ success: true }), {
313
+ status: 200,
314
+ });
315
+ }
316
+ ```
317
+
318
+ ### How Honeypot Works
319
+
320
+ 1. **Hidden Field**: The honeypot field is visually hidden using CSS (`position: absolute; left: -5000px`)
321
+ 2. **Bot Detection**: Bots typically fill all form fields, including hidden ones
322
+ 3. **Silent Rejection**: If the honeypot field is filled, the submission is rejected silently (no error shown)
323
+ 4. **Backend Check**: Always verify on your backend as well for additional security
324
+
325
+ **Note:** The honeypot field name should match your Mailchimp embed form or your custom backend field name.
326
+
327
+ ## Props
328
+
329
+ ### Content
330
+
331
+ | Prop | Type | Default | Description |
332
+ | ---------------- | -------- | ---------------------------- | --------------------- |
333
+ | `title` | `string` | `"Join the Waitlist"` | Modal title |
334
+ | `subtitle` | `string` | `"Be the first to know..."` | Description text |
335
+ | `successTitle` | `string` | `"You're on the list!"` | Success state title |
336
+ | `successMessage` | `string` | `"Thank you for joining..."` | Success state message |
337
+
338
+ ### Uncontrolled Mode
339
+
340
+ | Prop | Type | Description |
341
+ | ----------- | -------- | ------------------------ |
342
+ | `actionUrl` | `string` | URL to POST form data to |
343
+
344
+ ### Controlled Mode
345
+
346
+ | Prop | Type | Description |
347
+ | -------------- | ---------------------------------- | ------------------------------- |
348
+ | `onSubmit` | `(data: WaitlistFormData) => void` | Called with form data on submit |
349
+ | `isSubmitting` | `boolean` | Shows loading state |
350
+ | `isSuccess` | `boolean` | Shows success state |
351
+ | `error` | `string \| null` | Error message to display |
352
+
353
+ ### Mailchimp
354
+
355
+ | Prop | Type | Description |
356
+ | ------ | -------- | ------------------------------ |
357
+ | `tags` | `string` | Hidden tag value for Mailchimp |
358
+
359
+ ### Cloudflare Turnstile
360
+
361
+ | Prop | Type | Default | Description |
362
+ | ------------------ | ----------------------------- | ---------- | ------------------------------ |
363
+ | `turnstileSiteKey` | `string` | - | Enables Turnstile verification |
364
+ | `turnstileTheme` | `"light" \| "dark" \| "auto"` | `"auto"` | Widget theme |
365
+ | `turnstileSize` | `"normal" \| "compact"` | `"normal"` | Widget size |
366
+ | `turnstileAction` | `string` | - | Action name for analytics |
367
+ | `turnstileManaged` | `boolean` | `false` | Hide widget (invisible mode) |
368
+
369
+ ### Honeypot
370
+
371
+ | Prop | Type | Description |
372
+ | ------------------- | -------- | ------------------------------------------------------------------ |
373
+ | `honeypotFieldName` | `string` | Name attribute for the honeypot field (e.g., Mailchimp field name) |
374
+
375
+ ### Modal Behavior
376
+
377
+ | Prop | Type | Default | Description |
378
+ | ------------- | --------- | ------- | ---------------------------------- |
379
+ | `dismissible` | `boolean` | `false` | Show X button, allow click-outside |
380
+
381
+ ### Callbacks
382
+
383
+ | Prop | Type | Description |
384
+ | --------- | ------------ | ----------------------------------------- |
385
+ | `onDone` | `() => void` | Called when user clicks "Done" on success |
386
+ | `onClose` | `() => void` | Called when modal is dismissed |
387
+
388
+ ## Form Data Structure
389
+
390
+ ```typescript
391
+ interface WaitlistFormData {
392
+ EMAIL: string;
393
+ FNAME: string;
394
+ LNAME: string;
395
+ "cf-turnstile-response"?: string; // Present if Turnstile enabled
396
+ [key: string]: string | undefined; // Dynamic fields (e.g., honeypot field)
397
+ }
398
+ ```
399
+
400
+ **Note:** When using honeypot, the field name will be whatever you pass to `honeypotFieldName`. For example, if `honeypotFieldName="b_eecb4f508c0388d7720a99c82_0ad1109319"`, then `data["b_eecb4f508c0388d7720a99c82_0ad1109319"]` will contain the honeypot value.
401
+
402
+ ## Exports
403
+
404
+ ```typescript
405
+ // Components
406
+ export { WaitlistCard, WaitlistModal } from "godgpt-web-waitlist";
407
+
408
+ // Types
409
+ export type { WaitlistCardProps, WaitlistFormData } from "godgpt-web-waitlist";
410
+
411
+ // Sub-components (advanced usage)
412
+ export { Button, Input, Toast } from "godgpt-web-waitlist";
413
+ ```
414
+
415
+ ## Development
416
+
417
+ ```bash
418
+ # Install dependencies
419
+ npm install
420
+
421
+ # Start dev server
422
+ npm run dev
423
+
424
+ # Build library
425
+ npm run build:lib
426
+
427
+ # Build demo app
428
+ npm run build
429
+ ```
430
+
431
+ ## License
432
+
433
+ MIT
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
3
+ variant?: 'primary' | 'secondary' | 'text';
4
+ isLoading?: boolean;
5
+ children: React.ReactNode;
6
+ }
7
+ export declare const Button: React.FC<ButtonProps>;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
3
+ label?: string;
4
+ error?: string;
5
+ helperText?: string;
6
+ }
7
+ export declare const Input: React.FC<InputProps>;
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ interface ToastProps {
3
+ message: string;
4
+ type?: 'success' | 'error' | 'info';
5
+ duration?: number;
6
+ onClose: () => void;
7
+ }
8
+ export declare const Toast: React.FC<ToastProps>;
9
+ export {};
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ declare global {
3
+ interface Window {
4
+ turnstile?: {
5
+ render: (container: string | HTMLElement, options: {
6
+ sitekey: string;
7
+ callback?: (token: string) => void;
8
+ "error-callback"?: () => void;
9
+ "expired-callback"?: () => void;
10
+ theme?: "light" | "dark" | "auto";
11
+ size?: "normal" | "compact";
12
+ action?: string;
13
+ }) => string;
14
+ reset: (widgetId: string) => void;
15
+ remove: (widgetId: string) => void;
16
+ };
17
+ }
18
+ }
19
+ /**
20
+ * Form data structure matching Mailchimp field names
21
+ * - EMAIL: Email address
22
+ * - FNAME: First name
23
+ * - LNAME: Last name
24
+ * - cf-turnstile-response: Cloudflare Turnstile token (if enabled)
25
+ * - [honeypotFieldName]: Honeypot field value (if enabled)
26
+ */
27
+ export interface WaitlistFormData {
28
+ EMAIL: string;
29
+ FNAME: string;
30
+ LNAME: string;
31
+ "cf-turnstile-response"?: string;
32
+ [key: string]: string | undefined;
33
+ }
34
+ export interface WaitlistCardProps {
35
+ title?: string;
36
+ subtitle?: string;
37
+ successTitle?: string;
38
+ successMessage?: string;
39
+ actionUrl?: string;
40
+ onSubmit?: (data: WaitlistFormData) => void;
41
+ isSuccess?: boolean;
42
+ isSubmitting?: boolean;
43
+ error?: string | null;
44
+ tags?: string;
45
+ honeypotFieldName?: string;
46
+ turnstileSiteKey?: string;
47
+ turnstileTheme?: "light" | "dark" | "auto";
48
+ turnstileSize?: "normal" | "compact";
49
+ turnstileAction?: string;
50
+ turnstileManaged?: boolean;
51
+ dismissible?: boolean;
52
+ onDone?: () => void;
53
+ onClose?: () => void;
54
+ }
55
+ export declare const WaitlistCard: React.FC<WaitlistCardProps>;
56
+ export declare const WaitlistModal: React.FC<WaitlistCardProps>;