@appfunnel-dev/sdk 0.6.0 → 0.8.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/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkBUF5FDKC_cjs = require('./chunk-BUF5FDKC.cjs');
3
+ var chunkXP44I2MU_cjs = require('./chunk-XP44I2MU.cjs');
4
+ var chunkEVUYCLVY_cjs = require('./chunk-EVUYCLVY.cjs');
4
5
  var react = require('react');
5
6
  var stripeJs = require('@stripe/stripe-js');
6
7
  var reactStripeJs = require('@stripe/react-stripe-js');
@@ -14,7 +15,7 @@ function definePage(definition) {
14
15
  return definition;
15
16
  }
16
17
  function useVariable(id) {
17
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
18
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
18
19
  const subscribe = react.useCallback(
19
20
  (callback) => variableStore.subscribe(callback, { keys: [id] }),
20
21
  [variableStore, id]
@@ -31,7 +32,7 @@ function useVariable(id) {
31
32
  return [value, setValue];
32
33
  }
33
34
  function useVariables() {
34
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
35
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
35
36
  const subscribe = react.useCallback(
36
37
  (callback) => variableStore.subscribe(callback),
37
38
  [variableStore]
@@ -42,8 +43,104 @@ function useVariables() {
42
43
  );
43
44
  return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
44
45
  }
46
+
47
+ // src/utils/date.ts
48
+ function toISODate(input) {
49
+ if (!input || !input.trim()) return "";
50
+ const s = input.trim();
51
+ if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s;
52
+ if (/^\d{4}-\d{2}-\d{2}T/.test(s)) return s.slice(0, 10);
53
+ const sepMatch = s.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
54
+ if (sepMatch) {
55
+ const a = parseInt(sepMatch[1], 10);
56
+ const b = parseInt(sepMatch[2], 10);
57
+ const year = sepMatch[3];
58
+ let month;
59
+ let day;
60
+ if (a > 12 && b <= 12) {
61
+ day = a;
62
+ month = b;
63
+ } else if (b > 12 && a <= 12) {
64
+ month = a;
65
+ day = b;
66
+ } else {
67
+ month = a;
68
+ day = b;
69
+ }
70
+ if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
71
+ return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
72
+ }
73
+ }
74
+ const ymdSlash = s.match(/^(\d{4})[/\-.](\d{1,2})[/\-.](\d{1,2})$/);
75
+ if (ymdSlash) {
76
+ const year = ymdSlash[1];
77
+ const month = parseInt(ymdSlash[2], 10);
78
+ const day = parseInt(ymdSlash[3], 10);
79
+ if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
80
+ return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
81
+ }
82
+ }
83
+ if (/^\d{8}$/.test(s)) {
84
+ const a = parseInt(s.slice(0, 2), 10);
85
+ const b = parseInt(s.slice(2, 4), 10);
86
+ const year = s.slice(4, 8);
87
+ let month;
88
+ let day;
89
+ if (a > 12 && b <= 12) {
90
+ day = a;
91
+ month = b;
92
+ } else {
93
+ month = a;
94
+ day = b;
95
+ }
96
+ if (month >= 1 && month <= 12 && day >= 1 && day <= 31) {
97
+ return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
98
+ }
99
+ }
100
+ throw new Error(
101
+ `[AppFunnel] Invalid date format: "${input}". Expected a date string like MM/DD/YYYY, DD/MM/YYYY, YYYY-MM-DD, or MMDDYYYY.`
102
+ );
103
+ }
104
+ function toISODateWithFormat(input, format) {
105
+ if (!input || !input.trim()) return "";
106
+ const s = input.trim();
107
+ if (/^\d{4}-\d{2}-\d{2}$/.test(s) || /^\d{4}-\d{2}-\d{2}T/.test(s)) {
108
+ return s.slice(0, 10);
109
+ }
110
+ const digits = s.replace(/[^\d]/g, "");
111
+ if (format === "YYYY-MM-DD") {
112
+ const m = s.match(/^(\d{4})[/\-.](\d{1,2})[/\-.](\d{1,2})$/);
113
+ if (m) return `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}`;
114
+ if (digits.length === 8) {
115
+ return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`;
116
+ }
117
+ }
118
+ if (format === "MM/DD/YYYY") {
119
+ const m = s.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
120
+ if (m) return `${m[3]}-${m[1].padStart(2, "0")}-${m[2].padStart(2, "0")}`;
121
+ if (digits.length === 8) {
122
+ return `${digits.slice(4, 8)}-${digits.slice(0, 2)}-${digits.slice(2, 4)}`;
123
+ }
124
+ }
125
+ if (format === "DD/MM/YYYY") {
126
+ const m = s.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
127
+ if (m) return `${m[3]}-${m[2].padStart(2, "0")}-${m[1].padStart(2, "0")}`;
128
+ if (digits.length === 8) {
129
+ return `${digits.slice(4, 8)}-${digits.slice(2, 4)}-${digits.slice(0, 2)}`;
130
+ }
131
+ }
132
+ throw new Error(
133
+ `[AppFunnel] Invalid date format: "${input}". Expected format ${format} (e.g. ${{
134
+ "MM/DD/YYYY": "03/15/1990 or 03151990",
135
+ "DD/MM/YYYY": "15/03/1990 or 15031990",
136
+ "YYYY-MM-DD": "1990-03-15 or 19900315"
137
+ }[format]}).`
138
+ );
139
+ }
140
+
141
+ // src/hooks/useUser.ts
45
142
  function useUser() {
46
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
143
+ const { variableStore, tracker } = chunkEVUYCLVY_cjs.useFunnelContext();
47
144
  const subscribe = react.useCallback(
48
145
  (cb) => variableStore.subscribe(cb, { prefix: "user." }),
49
146
  [variableStore]
@@ -60,6 +157,7 @@ function useUser() {
60
157
  stripeCustomerId: variables["user.stripeCustomerId"] || "",
61
158
  paddleCustomerId: variables["user.paddleCustomerId"] || "",
62
159
  dateOfBirth: variables["user.dateOfBirth"] || "",
160
+ marketingConsent: variables["user.marketingConsent"] === true,
63
161
  setEmail(email) {
64
162
  variableStore.set("user.email", email);
65
163
  },
@@ -67,14 +165,20 @@ function useUser() {
67
165
  variableStore.set("user.name", name);
68
166
  },
69
167
  setDateOfBirth(dateOfBirth) {
70
- variableStore.set("user.dateOfBirth", dateOfBirth);
168
+ variableStore.set("user.dateOfBirth", toISODate(dateOfBirth));
169
+ },
170
+ setMarketingConsent(consent) {
171
+ variableStore.set("user.marketingConsent", consent);
172
+ },
173
+ identify(email) {
174
+ tracker.identify(email);
71
175
  }
72
176
  }),
73
- [variables, variableStore]
177
+ [variables, variableStore, tracker]
74
178
  );
75
179
  }
76
180
  function useUserProperty(field) {
77
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
181
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
78
182
  const key = `user.${field}`;
79
183
  const subscribe = react.useCallback(
80
184
  (cb) => variableStore.subscribe(cb, { keys: [key] }),
@@ -91,47 +195,26 @@ function useUserProperty(field) {
91
195
  );
92
196
  return [value, setValue];
93
197
  }
94
- function useResponse(key) {
95
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
96
- const prefixedKey = `answers.${key}`;
198
+ function useDateOfBirth(format = "MM/DD/YYYY") {
199
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
200
+ const key = "user.dateOfBirth";
97
201
  const subscribe = react.useCallback(
98
- (cb) => variableStore.subscribe(cb, { keys: [prefixedKey] }),
99
- [variableStore, prefixedKey]
202
+ (cb) => variableStore.subscribe(cb, { keys: [key] }),
203
+ [variableStore]
100
204
  );
101
205
  const getSnapshot = react.useCallback(
102
- () => variableStore.get(prefixedKey),
103
- [variableStore, prefixedKey]
206
+ () => variableStore.get(key) || "",
207
+ [variableStore]
104
208
  );
105
209
  const value = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
106
210
  const setValue = react.useCallback(
107
- (v) => variableStore.set(prefixedKey, v),
108
- [variableStore, prefixedKey]
211
+ (v) => variableStore.set(key, toISODateWithFormat(v, format)),
212
+ [variableStore, format]
109
213
  );
110
214
  return [value, setValue];
111
215
  }
112
- function useResponses() {
113
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
114
- const subscribe = react.useCallback(
115
- (cb) => variableStore.subscribe(cb, { prefix: "answers." }),
116
- [variableStore]
117
- );
118
- const getSnapshot = react.useCallback(
119
- () => variableStore.getState(),
120
- [variableStore]
121
- );
122
- const variables = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
123
- return react.useMemo(() => {
124
- const result = {};
125
- for (const [key, value] of Object.entries(variables)) {
126
- if (key.startsWith("answers.")) {
127
- result[key.slice(8)] = value;
128
- }
129
- }
130
- return result;
131
- }, [variables]);
132
- }
133
216
  function useQueryParams() {
134
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
217
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
135
218
  const subscribe = react.useCallback(
136
219
  (cb) => variableStore.subscribe(cb, { prefix: "query." }),
137
220
  [variableStore]
@@ -152,7 +235,7 @@ function useQueryParams() {
152
235
  }, [variables]);
153
236
  }
154
237
  function useQueryParam(key) {
155
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
238
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
156
239
  const prefixedKey = `query.${key}`;
157
240
  const subscribe = react.useCallback(
158
241
  (cb) => variableStore.subscribe(cb, { keys: [prefixedKey] }),
@@ -165,7 +248,7 @@ function useQueryParam(key) {
165
248
  return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
166
249
  }
167
250
  function useData(key) {
168
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
251
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
169
252
  const prefixedKey = `data.${key}`;
170
253
  const subscribe = react.useCallback(
171
254
  (cb) => variableStore.subscribe(cb, { keys: [prefixedKey] }),
@@ -218,7 +301,7 @@ function detect24Hour(locale) {
218
301
  }
219
302
  }
220
303
  function useTranslation() {
221
- const { i18n } = chunkBUF5FDKC_cjs.useFunnelContext();
304
+ const { i18n } = chunkEVUYCLVY_cjs.useFunnelContext();
222
305
  const subscribe = react.useCallback(
223
306
  (cb) => i18n.subscribe(cb),
224
307
  [i18n]
@@ -240,66 +323,8 @@ function useTranslation() {
240
323
  const availableLocales = i18n.getAvailableLocales();
241
324
  return { t, locale, setLocale, availableLocales };
242
325
  }
243
- function useNavigation() {
244
- const { router, variableStore, tracker } = chunkBUF5FDKC_cjs.useFunnelContext();
245
- react.useSyncExternalStore(
246
- react.useCallback((cb) => router.subscribe(cb), [router]),
247
- react.useCallback(() => router.getSnapshot(), [router]),
248
- react.useCallback(() => router.getSnapshot(), [router])
249
- );
250
- const afterNavigate = react.useCallback((key) => {
251
- const page = router.getCurrentPage();
252
- if (page) {
253
- tracker.track("page.view", {
254
- pageId: page.key,
255
- pageKey: page.key,
256
- pageName: page.name
257
- });
258
- tracker.startPageTracking(page.key);
259
- }
260
- variableStore.setMany({
261
- "page.currentId": key,
262
- "page.currentIndex": router.getPageHistory().length,
263
- "page.current": router.getPageHistory().length + 1,
264
- "page.startedAt": Date.now()
265
- });
266
- }, [router, tracker, variableStore]);
267
- const goToNextPage = react.useCallback(() => {
268
- const previousPage = router.getCurrentPage();
269
- if (previousPage) {
270
- tracker.stopPageTracking();
271
- }
272
- const variables = variableStore.getState();
273
- const nextKey = router.goToNextPage(variables);
274
- if (nextKey) {
275
- afterNavigate(nextKey);
276
- }
277
- }, [router, tracker, variableStore, afterNavigate]);
278
- const goBack = react.useCallback(() => {
279
- tracker.stopPageTracking();
280
- const prevKey = router.goBack();
281
- if (prevKey) {
282
- afterNavigate(prevKey);
283
- }
284
- }, [router, tracker, afterNavigate]);
285
- const goToPage = react.useCallback((pageKey) => {
286
- tracker.stopPageTracking();
287
- const key = router.goToPage(pageKey);
288
- if (key) {
289
- afterNavigate(key);
290
- }
291
- }, [router, tracker, afterNavigate]);
292
- return {
293
- goToNextPage,
294
- goBack,
295
- goToPage,
296
- currentPage: router.getCurrentPage(),
297
- pageHistory: router.getPageHistory(),
298
- progress: router.getProgress()
299
- };
300
- }
301
326
  function useProducts() {
302
- const { products, variableStore, selectProduct: ctxSelect } = chunkBUF5FDKC_cjs.useFunnelContext();
327
+ const { products, variableStore, selectProduct: ctxSelect } = chunkEVUYCLVY_cjs.useFunnelContext();
303
328
  const subscribe = react.useCallback(
304
329
  (cb) => variableStore.subscribe(cb, { keys: ["products.selectedProductId"] }),
305
330
  [variableStore]
@@ -316,32 +341,19 @@ function useProducts() {
316
341
  return { products, selected, select };
317
342
  }
318
343
  function useTracking() {
319
- const { tracker } = chunkBUF5FDKC_cjs.useFunnelContext();
344
+ const { tracker } = chunkEVUYCLVY_cjs.useFunnelContext();
320
345
  const track = react.useCallback(
321
346
  (eventName, data) => {
322
347
  tracker.track(eventName, data);
323
348
  },
324
349
  [tracker]
325
350
  );
326
- const identify = react.useCallback(
327
- (email) => {
328
- tracker.identify(email);
329
- },
330
- [tracker]
331
- );
332
- return { track, identify };
351
+ return { track };
333
352
  }
334
- var PAYMENT_KEYS = [
335
- "card.last4",
336
- "card.brand",
337
- "card.expMonth",
338
- "card.expYear",
339
- "payment.loading",
340
- "payment.error",
341
- "payment.customerId"
342
- ];
353
+ var API_BASE_URL = "https://api.appfunnel.net";
354
+ var PAYMENT_KEYS = ["payment.loading", "payment.error"];
343
355
  function usePayment() {
344
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
356
+ const { variableStore, products, campaignId, tracker } = chunkEVUYCLVY_cjs.useFunnelContext();
345
357
  const subscribe = react.useCallback(
346
358
  (cb) => variableStore.subscribe(cb, { keys: PAYMENT_KEYS }),
347
359
  [variableStore]
@@ -351,20 +363,258 @@ function usePayment() {
351
363
  [variableStore]
352
364
  );
353
365
  const variables = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
354
- return react.useMemo(() => {
355
- const last4 = variables["card.last4"] || "";
356
- const brand = variables["card.brand"] || "";
357
- const expMonth = variables["card.expMonth"] || 0;
358
- const expYear = variables["card.expYear"] || 0;
359
- const customerId = variables["payment.customerId"] || null;
360
- return {
361
- customerId,
362
- isAuthorized: !!last4,
366
+ const purchase = react.useCallback(
367
+ async (productId, options) => {
368
+ console.log("[Purchase] Starting purchase for product:", productId);
369
+ if (globalThis.__APPFUNNEL_DEV__) {
370
+ console.log(
371
+ "[Purchase] Dev mode \u2014 simulating success (500ms delay)"
372
+ );
373
+ variableStore.set("payment.loading", true);
374
+ await new Promise((r) => setTimeout(r, 500));
375
+ variableStore.set("payment.error", "");
376
+ console.log("[Purchase] Dev mode \u2014 success");
377
+ options?.onSuccess?.();
378
+ return true;
379
+ }
380
+ const customerId = variableStore.get(
381
+ "user.stripeCustomerId"
382
+ );
383
+ console.log("[Purchase] Customer ID:", customerId || "(none)");
384
+ if (!customerId) {
385
+ const msg = "Please complete payment authorization first";
386
+ console.error("[Purchase] Failed:", msg);
387
+ variableStore.set("payment.error", msg);
388
+ options?.onError?.(msg);
389
+ return false;
390
+ }
391
+ const product = products.find((p) => p.id === productId);
392
+ console.log(
393
+ "[Purchase] Product found:",
394
+ product ? {
395
+ id: product.id,
396
+ name: product.name,
397
+ stripePriceId: product.stripePriceId
398
+ } : "(not found)"
399
+ );
400
+ if (!product?.stripePriceId) {
401
+ const msg = "Product not found or missing Stripe price";
402
+ console.error("[Purchase] Failed:", msg);
403
+ variableStore.set("payment.error", msg);
404
+ options?.onError?.(msg);
405
+ return false;
406
+ }
407
+ const trialPeriodDays = product.hasTrial ? product.trialDays : void 0;
408
+ const trialChargePriceId = product.paidTrial && product.trialStorePriceId ? product.trialStorePriceId : void 0;
409
+ console.log("[Purchase] Trial config:", {
410
+ trialPeriodDays,
411
+ trialChargePriceId,
412
+ hasTrial: product.hasTrial,
413
+ paidTrial: product.paidTrial
414
+ });
415
+ variableStore.set("payment.loading", true);
416
+ variableStore.set("payment.error", "");
417
+ const requestBody = {
418
+ campaignId,
419
+ sessionId: tracker.getSessionId(),
420
+ stripePriceId: product.stripePriceId,
421
+ trialPeriodDays,
422
+ trialChargePriceId
423
+ };
424
+ console.log(
425
+ "[Purchase] Sending request:",
426
+ `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
427
+ requestBody
428
+ );
429
+ try {
430
+ const response = await fetch(
431
+ `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
432
+ {
433
+ method: "POST",
434
+ headers: { "Content-Type": "application/json" },
435
+ body: JSON.stringify(requestBody)
436
+ }
437
+ );
438
+ console.log("[Purchase] Response status:", response.status);
439
+ let result = await response.json();
440
+ console.log("[Purchase] Response body:", result);
441
+ if (!result.success && result.requiresAction) {
442
+ console.log(
443
+ "[Purchase] 3DS authentication required, loading Stripe..."
444
+ );
445
+ const { loadStripe: loadStripe2 } = await import('@stripe/stripe-js');
446
+ const stripeInstance = await loadStripe2(
447
+ result.publishableKey
448
+ );
449
+ if (!stripeInstance) {
450
+ const msg = "Failed to load payment processor";
451
+ console.error("[Purchase] Failed:", msg);
452
+ variableStore.set("payment.error", msg);
453
+ options?.onError?.(msg);
454
+ return false;
455
+ }
456
+ console.log(
457
+ "[Purchase] Confirming card payment with 3DS..."
458
+ );
459
+ const { error: confirmError, paymentIntent: confirmedPi } = await stripeInstance.confirmCardPayment(
460
+ result.clientSecret,
461
+ {
462
+ payment_method: result.paymentMethodId
463
+ }
464
+ );
465
+ if (confirmError || !confirmedPi || confirmedPi.status !== "succeeded") {
466
+ const msg = confirmError?.message || "Payment authentication failed";
467
+ console.error("[Purchase] 3DS failed:", msg);
468
+ variableStore.set("payment.error", msg);
469
+ options?.onError?.(msg);
470
+ return false;
471
+ }
472
+ console.log(
473
+ "[Purchase] 3DS succeeded, retrying purchase with confirmed PI:",
474
+ confirmedPi.id
475
+ );
476
+ const retryResponse = await fetch(
477
+ `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
478
+ {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/json" },
481
+ body: JSON.stringify({
482
+ campaignId,
483
+ sessionId: tracker.getSessionId(),
484
+ stripePriceId: product.stripePriceId,
485
+ trialPeriodDays,
486
+ trialChargePriceId,
487
+ onSessionPiId: confirmedPi.id
488
+ })
489
+ }
490
+ );
491
+ result = await retryResponse.json();
492
+ console.log("[Purchase] Retry response:", result);
493
+ }
494
+ if (result.success) {
495
+ console.log("[Purchase] Success! Type:", result.type);
496
+ variableStore.set("payment.error", "");
497
+ if (result.type === "validate_only") {
498
+ console.log(
499
+ "[Purchase] Validate-only \u2014 setting card variables"
500
+ );
501
+ if (result.card) {
502
+ variableStore.setMany({
503
+ "card.last4": result.card.last4,
504
+ "card.brand": result.card.brand,
505
+ "card.expMonth": result.card.expMonth,
506
+ "card.expYear": result.card.expYear,
507
+ "card.funding": result.card.funding
508
+ });
509
+ }
510
+ if (result.eventId) {
511
+ console.log(
512
+ "[Purchase] Tracking purchase.complete (validate_only), eventId:",
513
+ result.eventId
514
+ );
515
+ tracker.track("purchase.complete", {
516
+ eventId: result.eventId,
517
+ amount: product.rawPrice ? product.rawPrice / 100 : 0,
518
+ currency: product.currencyCode || "USD"
519
+ });
520
+ }
521
+ } else if (result.type === "one_time") {
522
+ console.log(
523
+ "[Purchase] One-time charge \u2014 paymentIntentId:",
524
+ result.paymentIntentId
525
+ );
526
+ variableStore.set(
527
+ "stripe.paymentIntentId",
528
+ result.paymentIntentId
529
+ );
530
+ variableStore.set("payment.status", result.status);
531
+ if (result.eventId) {
532
+ console.log(
533
+ "[Purchase] Tracking purchase.complete (one_time), eventId:",
534
+ result.eventId
535
+ );
536
+ tracker.track("purchase.complete", {
537
+ eventId: result.eventId,
538
+ amount: product.rawPrice ? product.rawPrice / 100 : 0,
539
+ currency: product.currencyCode || "USD"
540
+ });
541
+ }
542
+ } else {
543
+ console.log(
544
+ "[Purchase] Subscription \u2014 subscriptionId:",
545
+ result.subscriptionId,
546
+ "status:",
547
+ result.status
548
+ );
549
+ variableStore.set(
550
+ "stripe.subscriptionId",
551
+ result.subscriptionId
552
+ );
553
+ variableStore.set("subscription.status", result.status);
554
+ if (result.trialCharge) {
555
+ console.log(
556
+ "[Purchase] Tracking trial charge purchase.complete"
557
+ );
558
+ tracker.track("purchase.complete", {
559
+ eventId: result.eventIds?.trialCharge,
560
+ amount: result.trialCharge.amount / 100,
561
+ currency: "USD"
562
+ });
563
+ }
564
+ if (result.eventIds?.subscription) {
565
+ console.log(
566
+ "[Purchase] Tracking subscription.created, eventId:",
567
+ result.eventIds.subscription
568
+ );
569
+ tracker.track("subscription.created", {
570
+ eventId: result.eventIds.subscription,
571
+ subscriptionId: result.subscriptionId,
572
+ status: result.status
573
+ });
574
+ }
575
+ if (result.eventIds?.firstPeriod) {
576
+ console.log(
577
+ "[Purchase] Tracking first-period purchase.complete, eventId:",
578
+ result.eventIds.firstPeriod
579
+ );
580
+ tracker.track("purchase.complete", {
581
+ eventId: result.eventIds.firstPeriod,
582
+ amount: product.rawPrice ? product.rawPrice / 100 : 0,
583
+ currency: product.currencyCode || "USD"
584
+ });
585
+ }
586
+ }
587
+ console.log("[Purchase] Calling onSuccess callback");
588
+ options?.onSuccess?.();
589
+ return true;
590
+ } else {
591
+ const msg = result.error || "Purchase failed";
592
+ console.error("[Purchase] Failed:", msg);
593
+ variableStore.set("payment.error", msg);
594
+ options?.onError?.(msg);
595
+ return false;
596
+ }
597
+ } catch (err) {
598
+ const msg = err instanceof Error ? err.message : "Purchase failed";
599
+ console.error("[Purchase] Exception:", err);
600
+ variableStore.set("payment.error", msg);
601
+ options?.onError?.(msg);
602
+ return false;
603
+ } finally {
604
+ console.log("[Purchase] Done \u2014 setting loading to false");
605
+ variableStore.set("payment.loading", false);
606
+ }
607
+ },
608
+ [variableStore, products, campaignId, tracker]
609
+ );
610
+ return react.useMemo(
611
+ () => ({
363
612
  loading: !!variables["payment.loading"],
364
613
  error: variables["payment.error"] || null,
365
- cardDetails: last4 ? { last4, brand, expMonth, expYear } : null
366
- };
367
- }, [variables]);
614
+ purchase
615
+ }),
616
+ [variables, purchase]
617
+ );
368
618
  }
369
619
  var DEVICE_KEYS = [
370
620
  "os.name",
@@ -386,7 +636,7 @@ var DEVICE_KEYS = [
386
636
  "browser.language"
387
637
  ];
388
638
  function useDeviceInfo() {
389
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
639
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
390
640
  const subscribe = react.useCallback(
391
641
  (cb) => variableStore.subscribe(cb, { keys: DEVICE_KEYS }),
392
642
  [variableStore]
@@ -422,6 +672,95 @@ function useDeviceInfo() {
422
672
  }
423
673
  }), [variables]);
424
674
  }
675
+ function useSafeArea() {
676
+ const [insets, setInsets] = react.useState({ top: 0, right: 0, bottom: 0, left: 0 });
677
+ react.useEffect(() => {
678
+ if (typeof window === "undefined") return;
679
+ const el = document.createElement("div");
680
+ el.style.cssText = [
681
+ "position:fixed",
682
+ "top:env(safe-area-inset-top,0px)",
683
+ "right:env(safe-area-inset-right,0px)",
684
+ "bottom:env(safe-area-inset-bottom,0px)",
685
+ "left:env(safe-area-inset-left,0px)",
686
+ "pointer-events:none",
687
+ "visibility:hidden",
688
+ "z-index:-1"
689
+ ].join(";");
690
+ document.body.appendChild(el);
691
+ function read() {
692
+ const style = getComputedStyle(el);
693
+ setInsets({
694
+ top: parseFloat(style.top) || 0,
695
+ right: parseFloat(style.right) || 0,
696
+ bottom: parseFloat(style.bottom) || 0,
697
+ left: parseFloat(style.left) || 0
698
+ });
699
+ }
700
+ read();
701
+ const observer = new ResizeObserver(read);
702
+ observer.observe(el);
703
+ window.addEventListener("resize", read);
704
+ return () => {
705
+ observer.disconnect();
706
+ window.removeEventListener("resize", read);
707
+ el.remove();
708
+ };
709
+ }, []);
710
+ return insets;
711
+ }
712
+ function useKeyboard() {
713
+ const [state, setState] = react.useState({ isOpen: false, height: 0 });
714
+ const timeoutRef = react.useRef();
715
+ react.useEffect(() => {
716
+ if (typeof window === "undefined") return;
717
+ if ("virtualKeyboard" in navigator) {
718
+ const vk = navigator.virtualKeyboard;
719
+ vk.overlaysContent = true;
720
+ const handler = () => {
721
+ const h = vk.boundingRect.height;
722
+ setState((prev) => {
723
+ if (prev.height === h && prev.isOpen === h > 0) return prev;
724
+ return { isOpen: h > 0, height: h };
725
+ });
726
+ };
727
+ vk.addEventListener("geometrychange", handler);
728
+ handler();
729
+ return () => vk.removeEventListener("geometrychange", handler);
730
+ }
731
+ const vv = window.visualViewport;
732
+ if (!vv) return;
733
+ let layoutHeight = window.innerHeight;
734
+ function compute() {
735
+ const kbHeight = Math.max(0, layoutHeight - vv.height);
736
+ const h = kbHeight > 40 ? Math.round(kbHeight) : 0;
737
+ setState((prev) => {
738
+ if (prev.height === h && prev.isOpen === h > 0) return prev;
739
+ return { isOpen: h > 0, height: h };
740
+ });
741
+ }
742
+ function onViewportResize() {
743
+ compute();
744
+ clearTimeout(timeoutRef.current);
745
+ timeoutRef.current = setTimeout(compute, 1e3);
746
+ }
747
+ function onWindowResize() {
748
+ layoutHeight = window.innerHeight;
749
+ compute();
750
+ }
751
+ vv.addEventListener("resize", onViewportResize);
752
+ window.addEventListener("resize", onWindowResize);
753
+ window.addEventListener("orientationchange", onWindowResize);
754
+ compute();
755
+ return () => {
756
+ clearTimeout(timeoutRef.current);
757
+ vv.removeEventListener("resize", onViewportResize);
758
+ window.removeEventListener("resize", onWindowResize);
759
+ window.removeEventListener("orientationchange", onWindowResize);
760
+ };
761
+ }, []);
762
+ return state;
763
+ }
425
764
  var PAGE_KEYS = [
426
765
  "page.currentId",
427
766
  "page.currentIndex",
@@ -431,7 +770,7 @@ var PAGE_KEYS = [
431
770
  "page.startedAt"
432
771
  ];
433
772
  function usePageData() {
434
- const { variableStore } = chunkBUF5FDKC_cjs.useFunnelContext();
773
+ const { variableStore } = chunkEVUYCLVY_cjs.useFunnelContext();
435
774
  const subscribe = react.useCallback(
436
775
  (cb) => variableStore.subscribe(cb, { keys: PAGE_KEYS }),
437
776
  [variableStore]
@@ -453,28 +792,477 @@ function usePageData() {
453
792
 
454
793
  // src/hooks/useFunnel.ts
455
794
  function useFunnel() {
456
- const { funnelId, campaignId, tracker } = chunkBUF5FDKC_cjs.useFunnelContext();
795
+ const { funnelId, campaignId, tracker } = chunkEVUYCLVY_cjs.useFunnelContext();
457
796
  return {
458
797
  funnelId,
459
798
  campaignId,
460
799
  sessionId: tracker.getSessionId(),
461
800
  variables: useVariables(),
462
801
  user: useUser(),
463
- responses: useResponses(),
802
+ responses: chunkXP44I2MU_cjs.useResponses(),
464
803
  queryParams: useQueryParams(),
465
- navigation: useNavigation(),
804
+ navigation: chunkXP44I2MU_cjs.useNavigation(),
466
805
  products: useProducts(),
467
806
  tracking: useTracking(),
468
807
  payment: usePayment()
469
808
  };
470
809
  }
471
- var API_BASE_URL = "https://api.appfunnel.net";
810
+ var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
811
+ var inputStyle = (focused) => ({
812
+ width: "100%",
813
+ padding: "12px",
814
+ border: `1px solid ${focused ? "#666" : "#e0e0e0"}`,
815
+ borderRadius: "6px",
816
+ fontSize: "14px",
817
+ fontFamily: FONT,
818
+ outline: "none",
819
+ backgroundColor: "#fff",
820
+ boxSizing: "border-box",
821
+ transition: "border-color 0.15s"
822
+ });
823
+ var labelStyle = {
824
+ display: "block",
825
+ fontSize: "14px",
826
+ fontWeight: 500,
827
+ color: "#333",
828
+ marginBottom: "6px",
829
+ fontFamily: FONT
830
+ };
831
+ function CardBrandBadges() {
832
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "4px", alignItems: "center" }, children: [
833
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#1a1f71", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "VISA" }),
834
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#eb001b", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "MC" }),
835
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#ff6000", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "DISC" })
836
+ ] });
837
+ }
838
+ function CvcIcon() {
839
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "16", viewBox: "0 0 20 16", fill: "none", style: { opacity: 0.4 }, children: [
840
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0.5", y: "0.5", width: "19", height: "15", rx: "2", stroke: "#888" }),
841
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0", y: "4", width: "20", height: "3", fill: "#888" }),
842
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "10", width: "8", height: "2", rx: "1", fill: "#ccc" }),
843
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: "14", y: "12", fontSize: "6", fill: "#888", fontFamily: FONT, children: "123" })
844
+ ] });
845
+ }
846
+ function DemoElementsForm({ onReady }) {
847
+ const [cardNumber, setCardNumber] = react.useState("");
848
+ const [expiry, setExpiry] = react.useState("");
849
+ const [cvc, setCvc] = react.useState("");
850
+ const [country, setCountry] = react.useState("Denmark");
851
+ const [focusedField, setFocusedField] = react.useState(null);
852
+ const readyFired = react.useRef(false);
853
+ react.useEffect(() => {
854
+ if (!readyFired.current) {
855
+ readyFired.current = true;
856
+ onReady?.();
857
+ }
858
+ }, [onReady]);
859
+ const formatCardNumber = (val) => {
860
+ const digits = val.replace(/\D/g, "").slice(0, 16);
861
+ return digits.replace(/(.{4})/g, "$1 ").trim();
862
+ };
863
+ const formatExpiry = (val) => {
864
+ const digits = val.replace(/\D/g, "").slice(0, 4);
865
+ if (digits.length > 2) return digits.slice(0, 2) + " / " + digits.slice(2);
866
+ return digits;
867
+ };
868
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontFamily: FONT }, children: [
869
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "14px" }, children: [
870
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: labelStyle, children: "Card number" }),
871
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
872
+ /* @__PURE__ */ jsxRuntime.jsx(
873
+ "input",
874
+ {
875
+ type: "text",
876
+ value: cardNumber,
877
+ onChange: (e) => setCardNumber(formatCardNumber(e.target.value)),
878
+ onFocus: () => setFocusedField("card"),
879
+ onBlur: () => setFocusedField(null),
880
+ placeholder: "1234 1234 1234 1234",
881
+ style: inputStyle(focusedField === "card")
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsxRuntime.jsx(CardBrandBadges, {}) })
885
+ ] })
886
+ ] }),
887
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px", marginBottom: "14px" }, children: [
888
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
889
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: labelStyle, children: "Expiry date" }),
890
+ /* @__PURE__ */ jsxRuntime.jsx(
891
+ "input",
892
+ {
893
+ type: "text",
894
+ value: expiry,
895
+ onChange: (e) => setExpiry(formatExpiry(e.target.value)),
896
+ onFocus: () => setFocusedField("expiry"),
897
+ onBlur: () => setFocusedField(null),
898
+ placeholder: "MM / YY",
899
+ style: inputStyle(focusedField === "expiry")
900
+ }
901
+ )
902
+ ] }),
903
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
904
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: labelStyle, children: "Security code" }),
905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
906
+ /* @__PURE__ */ jsxRuntime.jsx(
907
+ "input",
908
+ {
909
+ type: "text",
910
+ value: cvc,
911
+ onChange: (e) => setCvc(e.target.value.replace(/\D/g, "").slice(0, 4)),
912
+ onFocus: () => setFocusedField("cvc"),
913
+ onBlur: () => setFocusedField(null),
914
+ placeholder: "CVC",
915
+ style: inputStyle(focusedField === "cvc")
916
+ }
917
+ ),
918
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsxRuntime.jsx(CvcIcon, {}) })
919
+ ] })
920
+ ] })
921
+ ] }),
922
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "14px" }, children: [
923
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: labelStyle, children: "Country" }),
924
+ /* @__PURE__ */ jsxRuntime.jsxs(
925
+ "select",
926
+ {
927
+ value: country,
928
+ onChange: (e) => setCountry(e.target.value),
929
+ onFocus: () => setFocusedField("country"),
930
+ onBlur: () => setFocusedField(null),
931
+ style: {
932
+ ...inputStyle(focusedField === "country"),
933
+ appearance: "none",
934
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E")`,
935
+ backgroundRepeat: "no-repeat",
936
+ backgroundPosition: "right 12px center",
937
+ paddingRight: "32px"
938
+ },
939
+ children: [
940
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Australia" }),
941
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Austria" }),
942
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Belgium" }),
943
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Brazil" }),
944
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Canada" }),
945
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Denmark" }),
946
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Finland" }),
947
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "France" }),
948
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Germany" }),
949
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Ireland" }),
950
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Italy" }),
951
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Japan" }),
952
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Netherlands" }),
953
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "New Zealand" }),
954
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Norway" }),
955
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Poland" }),
956
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Portugal" }),
957
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Spain" }),
958
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Sweden" }),
959
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Switzerland" }),
960
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "United Kingdom" }),
961
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "United States" })
962
+ ]
963
+ }
964
+ )
965
+ ] }),
966
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "12px", color: "#6b7280", lineHeight: 1.5, margin: 0, fontFamily: FONT }, children: "By providing your card information, you allow the merchant to charge your card for future payments in accordance with their terms." })
967
+ ] });
968
+ }
969
+ function DemoEmbeddedForm({ product, onReady }) {
970
+ const readyFired = react.useRef(false);
971
+ const [email, setEmail] = react.useState("");
972
+ const [cardNumber, setCardNumber] = react.useState("");
973
+ const [expiry, setExpiry] = react.useState("");
974
+ const [cvc, setCvc] = react.useState("");
975
+ const [name, setName] = react.useState("");
976
+ const [country, setCountry] = react.useState("Denmark");
977
+ const [focusedField, setFocusedField] = react.useState(null);
978
+ react.useEffect(() => {
979
+ if (!readyFired.current) {
980
+ readyFired.current = true;
981
+ onReady?.();
982
+ }
983
+ }, [onReady]);
984
+ const formatCardNumber = (val) => {
985
+ const digits = val.replace(/\D/g, "").slice(0, 16);
986
+ return digits.replace(/(.{4})/g, "$1 ").trim();
987
+ };
988
+ const formatExpiry = (val) => {
989
+ const digits = val.replace(/\D/g, "").slice(0, 4);
990
+ if (digits.length > 2) return digits.slice(0, 2) + " / " + digits.slice(2);
991
+ return digits;
992
+ };
993
+ const embeddedInputStyle = (focused) => ({
994
+ width: "100%",
995
+ padding: "10px 12px",
996
+ border: "none",
997
+ borderBottom: `1px solid ${focused ? "#666" : "#e0e0e0"}`,
998
+ fontSize: "14px",
999
+ fontFamily: FONT,
1000
+ outline: "none",
1001
+ backgroundColor: "#fff",
1002
+ boxSizing: "border-box"
1003
+ });
1004
+ const displayPrice = product?.hasTrial ? product.trialPrice : product?.price;
1005
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontFamily: FONT, border: "1px solid #e0e0e0", borderRadius: "12px", overflow: "hidden", background: "#fff" }, children: [
1006
+ product && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "24px 20px", borderBottom: "1px solid #e0e0e0" }, children: [
1007
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
1008
+ "Pay ",
1009
+ product.displayName || product.name
1010
+ ] }),
1011
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "28px", fontWeight: 700, color: "#333", marginBottom: "4px" }, children: [
1012
+ product.currencyCode?.toUpperCase(),
1013
+ displayPrice
1014
+ ] }),
1015
+ product.hasTrial && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "13px", color: "#666" }, children: [
1016
+ "Then ",
1017
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { textDecoration: "underline" }, children: [
1018
+ product.currencyCode?.toUpperCase(),
1019
+ product.price
1020
+ ] }),
1021
+ " every ",
1022
+ product.period
1023
+ ] }),
1024
+ /* @__PURE__ */ jsxRuntime.jsx(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ style: {
1029
+ marginTop: "12px",
1030
+ padding: "6px 14px",
1031
+ borderRadius: "6px",
1032
+ border: "1px solid #e0e0e0",
1033
+ backgroundColor: "#fff",
1034
+ color: "#333",
1035
+ fontSize: "13px",
1036
+ cursor: "default",
1037
+ fontFamily: FONT
1038
+ },
1039
+ children: "View details \u25BE"
1040
+ }
1041
+ )
1042
+ ] }),
1043
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "20px" }, children: [
1044
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "16px" }, children: [
1045
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Email" }),
1046
+ /* @__PURE__ */ jsxRuntime.jsx(
1047
+ "input",
1048
+ {
1049
+ type: "email",
1050
+ value: email,
1051
+ onChange: (e) => setEmail(e.target.value),
1052
+ onFocus: () => setFocusedField("email"),
1053
+ onBlur: () => setFocusedField(null),
1054
+ placeholder: "you@example.com",
1055
+ style: embeddedInputStyle(focusedField === "email")
1056
+ }
1057
+ )
1058
+ ] }),
1059
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "16px" }, children: [
1060
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Card information" }),
1061
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid #e0e0e0", borderRadius: "6px", overflow: "hidden" }, children: [
1062
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1063
+ /* @__PURE__ */ jsxRuntime.jsx(
1064
+ "input",
1065
+ {
1066
+ type: "text",
1067
+ value: cardNumber,
1068
+ onChange: (e) => setCardNumber(formatCardNumber(e.target.value)),
1069
+ onFocus: () => setFocusedField("card"),
1070
+ onBlur: () => setFocusedField(null),
1071
+ placeholder: "1234 1234 1234 1234",
1072
+ style: { ...embeddedInputStyle(focusedField === "card"), padding: "12px" }
1073
+ }
1074
+ ),
1075
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsxRuntime.jsx(CardBrandBadges, {}) })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr" }, children: [
1078
+ /* @__PURE__ */ jsxRuntime.jsx(
1079
+ "input",
1080
+ {
1081
+ type: "text",
1082
+ value: expiry,
1083
+ onChange: (e) => setExpiry(formatExpiry(e.target.value)),
1084
+ onFocus: () => setFocusedField("expiry"),
1085
+ onBlur: () => setFocusedField(null),
1086
+ placeholder: "MM / YY",
1087
+ style: { ...embeddedInputStyle(focusedField === "expiry"), borderRight: "1px solid #e0e0e0", padding: "12px" }
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1091
+ /* @__PURE__ */ jsxRuntime.jsx(
1092
+ "input",
1093
+ {
1094
+ type: "text",
1095
+ value: cvc,
1096
+ onChange: (e) => setCvc(e.target.value.replace(/\D/g, "").slice(0, 4)),
1097
+ onFocus: () => setFocusedField("cvc"),
1098
+ onBlur: () => setFocusedField(null),
1099
+ placeholder: "CVC",
1100
+ style: { ...embeddedInputStyle(focusedField === "cvc"), padding: "12px" }
1101
+ }
1102
+ ),
1103
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsxRuntime.jsx(CvcIcon, {}) })
1104
+ ] })
1105
+ ] })
1106
+ ] })
1107
+ ] }),
1108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "16px" }, children: [
1109
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Cardholder name" }),
1110
+ /* @__PURE__ */ jsxRuntime.jsx(
1111
+ "input",
1112
+ {
1113
+ type: "text",
1114
+ value: name,
1115
+ onChange: (e) => setName(e.target.value),
1116
+ onFocus: () => setFocusedField("name"),
1117
+ onBlur: () => setFocusedField(null),
1118
+ placeholder: "Full name on card",
1119
+ style: inputStyle(focusedField === "name")
1120
+ }
1121
+ )
1122
+ ] }),
1123
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "20px" }, children: [
1124
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Country or region" }),
1125
+ /* @__PURE__ */ jsxRuntime.jsxs(
1126
+ "select",
1127
+ {
1128
+ value: country,
1129
+ onChange: (e) => setCountry(e.target.value),
1130
+ onFocus: () => setFocusedField("country"),
1131
+ onBlur: () => setFocusedField(null),
1132
+ style: {
1133
+ ...inputStyle(focusedField === "country"),
1134
+ appearance: "none",
1135
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E")`,
1136
+ backgroundRepeat: "no-repeat",
1137
+ backgroundPosition: "right 12px center",
1138
+ paddingRight: "32px"
1139
+ },
1140
+ children: [
1141
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Australia" }),
1142
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Austria" }),
1143
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Belgium" }),
1144
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Brazil" }),
1145
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Canada" }),
1146
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Denmark" }),
1147
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Finland" }),
1148
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "France" }),
1149
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Germany" }),
1150
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Ireland" }),
1151
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Italy" }),
1152
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Japan" }),
1153
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Netherlands" }),
1154
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "New Zealand" }),
1155
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Norway" }),
1156
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Poland" }),
1157
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Portugal" }),
1158
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Spain" }),
1159
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Sweden" }),
1160
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Switzerland" }),
1161
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "United Kingdom" }),
1162
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "United States" })
1163
+ ]
1164
+ }
1165
+ )
1166
+ ] }),
1167
+ /* @__PURE__ */ jsxRuntime.jsx(
1168
+ "button",
1169
+ {
1170
+ type: "button",
1171
+ disabled: true,
1172
+ style: {
1173
+ width: "100%",
1174
+ padding: "14px",
1175
+ borderRadius: "6px",
1176
+ border: "none",
1177
+ backgroundColor: "#0570de",
1178
+ color: "#fff",
1179
+ fontSize: "16px",
1180
+ fontWeight: 600,
1181
+ cursor: "default",
1182
+ fontFamily: FONT,
1183
+ opacity: 0.7
1184
+ },
1185
+ children: "Subscribe"
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", marginTop: "12px", fontSize: "12px", color: "#aaa" }, children: [
1189
+ "Powered by ",
1190
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, letterSpacing: "-0.3px" }, children: "stripe" })
1191
+ ] })
1192
+ ] })
1193
+ ] });
1194
+ }
1195
+ var DemoStripePaymentForm = react.forwardRef(
1196
+ function DemoStripePaymentForm2({
1197
+ productId,
1198
+ mode = "checkout",
1199
+ variant = "elements",
1200
+ onSuccess,
1201
+ onError,
1202
+ onReady,
1203
+ className
1204
+ }, ref) {
1205
+ const { variableStore, tracker, products } = chunkEVUYCLVY_cjs.useFunnelContext();
1206
+ const validateOnly = mode === "validate-only";
1207
+ const product = react.useMemo(() => {
1208
+ if (productId) return products.find((p) => p.id === productId) || null;
1209
+ const selectedId = variableStore.get("products.selectedProductId");
1210
+ return products.find((p) => p.id === selectedId) || null;
1211
+ }, [productId, products, variableStore]);
1212
+ const hasFiredStart = react.useRef(false);
1213
+ react.useEffect(() => {
1214
+ if (hasFiredStart.current) return;
1215
+ hasFiredStart.current = true;
1216
+ variableStore.set("payment.loading", false);
1217
+ tracker.track("checkout.start", {
1218
+ productId: product?.id,
1219
+ priceId: product?.stripePriceId || product?.storePriceId,
1220
+ productName: product?.displayName
1221
+ });
1222
+ }, [product, tracker, variableStore]);
1223
+ const handleSubmit = react.useCallback(async () => {
1224
+ variableStore.set("payment.loading", true);
1225
+ await new Promise((r) => setTimeout(r, 800));
1226
+ try {
1227
+ tracker.track("checkout.payment_added");
1228
+ if (validateOnly) {
1229
+ variableStore.setMany({
1230
+ "card.last4": "4242",
1231
+ "card.brand": "visa",
1232
+ "card.expMonth": 12,
1233
+ "card.expYear": 2030,
1234
+ "card.funding": "credit",
1235
+ "payment.error": ""
1236
+ });
1237
+ onSuccess?.();
1238
+ return;
1239
+ }
1240
+ variableStore.set("payment.error", "");
1241
+ tracker.track("purchase.complete", {
1242
+ amount: product?.rawPrice ? product.rawPrice / 100 : 0,
1243
+ currency: product?.currencyCode || "USD"
1244
+ });
1245
+ onSuccess?.();
1246
+ } catch (err) {
1247
+ const msg = err instanceof Error ? err.message : "An error occurred";
1248
+ variableStore.set("payment.error", msg);
1249
+ onError?.(msg);
1250
+ } finally {
1251
+ variableStore.set("payment.loading", false);
1252
+ }
1253
+ }, [validateOnly, variableStore, tracker, product, onSuccess, onError]);
1254
+ react.useImperativeHandle(ref, () => ({ submit: handleSubmit }), [handleSubmit]);
1255
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: variant === "embedded" ? /* @__PURE__ */ jsxRuntime.jsx(DemoEmbeddedForm, { product, onReady }) : /* @__PURE__ */ jsxRuntime.jsx(DemoElementsForm, { onReady }) });
1256
+ }
1257
+ );
1258
+ var API_BASE_URL2 = "https://api.appfunnel.net";
1259
+ var isDevMode = () => typeof globalThis !== "undefined" && !!globalThis.__APPFUNNEL_DEV__;
472
1260
  var InnerPaymentForm = react.forwardRef(
473
1261
  function InnerPaymentForm2({ paymentMode, validateOnly, onSuccess, onError, onReady, layout }, ref) {
474
1262
  const stripe = reactStripeJs.useStripe();
475
1263
  const elements = reactStripeJs.useElements();
476
1264
  const [error, setError] = react.useState(null);
477
- const { variableStore, campaignId, tracker, products } = chunkBUF5FDKC_cjs.useFunnelContext();
1265
+ const { variableStore, campaignId, tracker, products } = chunkEVUYCLVY_cjs.useFunnelContext();
478
1266
  const readyFired = react.useRef(false);
479
1267
  react.useEffect(() => {
480
1268
  if (stripe && elements && !readyFired.current) {
@@ -516,7 +1304,7 @@ var InnerPaymentForm = react.forwardRef(
516
1304
  return;
517
1305
  }
518
1306
  const response2 = await fetch(
519
- `${API_BASE_URL}/campaign/${campaignId}/stripe/validate-card`,
1307
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/validate-card`,
520
1308
  {
521
1309
  method: "POST",
522
1310
  headers: { "Content-Type": "application/json" },
@@ -557,7 +1345,7 @@ var InnerPaymentForm = react.forwardRef(
557
1345
  return;
558
1346
  }
559
1347
  const response = await fetch(
560
- `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
1348
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/purchase`,
561
1349
  {
562
1350
  method: "POST",
563
1351
  headers: { "Content-Type": "application/json" },
@@ -602,8 +1390,8 @@ var InnerPaymentForm = react.forwardRef(
602
1390
  ] });
603
1391
  }
604
1392
  );
605
- var StripePaymentForm = react.forwardRef(
606
- function StripePaymentForm2({
1393
+ var RealStripePaymentForm = react.forwardRef(
1394
+ function RealStripePaymentForm2({
607
1395
  productId,
608
1396
  mode = "checkout",
609
1397
  variant = "elements",
@@ -614,7 +1402,7 @@ var StripePaymentForm = react.forwardRef(
614
1402
  appearance,
615
1403
  layout
616
1404
  }, ref) {
617
- const { campaignId, tracker, variableStore, products } = chunkBUF5FDKC_cjs.useFunnelContext();
1405
+ const { campaignId, tracker, variableStore, products } = chunkEVUYCLVY_cjs.useFunnelContext();
618
1406
  const [email] = useVariable("user.email");
619
1407
  const validateOnly = mode === "validate-only";
620
1408
  const product = react.useMemo(() => {
@@ -642,7 +1430,7 @@ var StripePaymentForm = react.forwardRef(
642
1430
  try {
643
1431
  if (variant === "embedded") {
644
1432
  const response = await fetch(
645
- `${API_BASE_URL}/campaign/${campaignId}/stripe/checkout-session`,
1433
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/checkout-session`,
646
1434
  {
647
1435
  method: "POST",
648
1436
  headers: { "Content-Type": "application/json" },
@@ -668,7 +1456,7 @@ var StripePaymentForm = react.forwardRef(
668
1456
  priceId: product.storePriceId
669
1457
  };
670
1458
  if (validateOnly) body.validateOnly = true;
671
- const response = await fetch(`${API_BASE_URL}${endpoint}`, {
1459
+ const response = await fetch(`${API_BASE_URL2}${endpoint}`, {
672
1460
  method: "POST",
673
1461
  headers: { "Content-Type": "application/json" },
674
1462
  body: JSON.stringify(body)
@@ -744,6 +1532,14 @@ var StripePaymentForm = react.forwardRef(
744
1532
  ) }) });
745
1533
  }
746
1534
  );
1535
+ var StripePaymentForm = react.forwardRef(
1536
+ function StripePaymentForm2(props, ref) {
1537
+ if (isDevMode()) {
1538
+ return /* @__PURE__ */ jsxRuntime.jsx(DemoStripePaymentForm, { ref, ...props });
1539
+ }
1540
+ return /* @__PURE__ */ jsxRuntime.jsx(RealStripePaymentForm, { ref, ...props });
1541
+ }
1542
+ );
747
1543
  function PaddleCheckout({
748
1544
  productId,
749
1545
  mode = "overlay",
@@ -751,7 +1547,7 @@ function PaddleCheckout({
751
1547
  onError,
752
1548
  className
753
1549
  }) {
754
- const { variableStore, tracker, products } = chunkBUF5FDKC_cjs.useFunnelContext();
1550
+ const { variableStore, tracker, products } = chunkEVUYCLVY_cjs.useFunnelContext();
755
1551
  const containerRef = react.useRef(null);
756
1552
  const initializedRef = react.useRef(false);
757
1553
  const product = productId ? products.find((p) => p.id === productId) : products.find((p) => p.id === variableStore.get("products.selectedProductId"));
@@ -808,30 +1604,42 @@ function PaddleCheckout({
808
1604
  return null;
809
1605
  }
810
1606
 
1607
+ Object.defineProperty(exports, "useNavigation", {
1608
+ enumerable: true,
1609
+ get: function () { return chunkXP44I2MU_cjs.useNavigation; }
1610
+ });
1611
+ Object.defineProperty(exports, "useResponse", {
1612
+ enumerable: true,
1613
+ get: function () { return chunkXP44I2MU_cjs.useResponse; }
1614
+ });
1615
+ Object.defineProperty(exports, "useResponses", {
1616
+ enumerable: true,
1617
+ get: function () { return chunkXP44I2MU_cjs.useResponses; }
1618
+ });
811
1619
  Object.defineProperty(exports, "FunnelProvider", {
812
1620
  enumerable: true,
813
- get: function () { return chunkBUF5FDKC_cjs.FunnelProvider; }
1621
+ get: function () { return chunkEVUYCLVY_cjs.FunnelProvider; }
814
1622
  });
815
1623
  Object.defineProperty(exports, "registerIntegration", {
816
1624
  enumerable: true,
817
- get: function () { return chunkBUF5FDKC_cjs.registerIntegration; }
1625
+ get: function () { return chunkEVUYCLVY_cjs.registerIntegration; }
818
1626
  });
819
1627
  exports.PaddleCheckout = PaddleCheckout;
820
1628
  exports.StripePaymentForm = StripePaymentForm;
821
1629
  exports.defineConfig = defineConfig;
822
1630
  exports.definePage = definePage;
823
1631
  exports.useData = useData;
1632
+ exports.useDateOfBirth = useDateOfBirth;
824
1633
  exports.useDeviceInfo = useDeviceInfo;
825
1634
  exports.useFunnel = useFunnel;
1635
+ exports.useKeyboard = useKeyboard;
826
1636
  exports.useLocale = useLocale;
827
- exports.useNavigation = useNavigation;
828
1637
  exports.usePageData = usePageData;
829
1638
  exports.usePayment = usePayment;
830
1639
  exports.useProducts = useProducts;
831
1640
  exports.useQueryParam = useQueryParam;
832
1641
  exports.useQueryParams = useQueryParams;
833
- exports.useResponse = useResponse;
834
- exports.useResponses = useResponses;
1642
+ exports.useSafeArea = useSafeArea;
835
1643
  exports.useTracking = useTracking;
836
1644
  exports.useTranslation = useTranslation;
837
1645
  exports.useUser = useUser;