@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.js CHANGED
@@ -1,9 +1,11 @@
1
- import { useFunnelContext } from './chunk-E6KSJ5UI.js';
2
- export { FunnelProvider, registerIntegration } from './chunk-E6KSJ5UI.js';
3
- import { forwardRef, useState, useRef, useEffect, useCallback, useImperativeHandle, useMemo, useSyncExternalStore } from 'react';
1
+ import { useNavigation, useResponses } from './chunk-P4SLDMWY.js';
2
+ export { useNavigation, useResponse, useResponses } from './chunk-P4SLDMWY.js';
3
+ import { useFunnelContext } from './chunk-H3KHXZSI.js';
4
+ export { FunnelProvider, registerIntegration } from './chunk-H3KHXZSI.js';
5
+ import { forwardRef, useMemo, useRef, useEffect, useCallback, useImperativeHandle, useState, useSyncExternalStore } from 'react';
4
6
  import { loadStripe } from '@stripe/stripe-js';
5
7
  import { useStripe, useElements, PaymentElement, EmbeddedCheckoutProvider, EmbeddedCheckout, Elements } from '@stripe/react-stripe-js';
6
- import { jsxs, jsx } from 'react/jsx-runtime';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
9
 
8
10
  // src/config.ts
9
11
  function defineConfig(config) {
@@ -41,8 +43,104 @@ function useVariables() {
41
43
  );
42
44
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
43
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
44
142
  function useUser() {
45
- const { variableStore } = useFunnelContext();
143
+ const { variableStore, tracker } = useFunnelContext();
46
144
  const subscribe = useCallback(
47
145
  (cb) => variableStore.subscribe(cb, { prefix: "user." }),
48
146
  [variableStore]
@@ -59,6 +157,7 @@ function useUser() {
59
157
  stripeCustomerId: variables["user.stripeCustomerId"] || "",
60
158
  paddleCustomerId: variables["user.paddleCustomerId"] || "",
61
159
  dateOfBirth: variables["user.dateOfBirth"] || "",
160
+ marketingConsent: variables["user.marketingConsent"] === true,
62
161
  setEmail(email) {
63
162
  variableStore.set("user.email", email);
64
163
  },
@@ -66,10 +165,16 @@ function useUser() {
66
165
  variableStore.set("user.name", name);
67
166
  },
68
167
  setDateOfBirth(dateOfBirth) {
69
- 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);
70
175
  }
71
176
  }),
72
- [variables, variableStore]
177
+ [variables, variableStore, tracker]
73
178
  );
74
179
  }
75
180
  function useUserProperty(field) {
@@ -90,45 +195,24 @@ function useUserProperty(field) {
90
195
  );
91
196
  return [value, setValue];
92
197
  }
93
- function useResponse(key) {
198
+ function useDateOfBirth(format = "MM/DD/YYYY") {
94
199
  const { variableStore } = useFunnelContext();
95
- const prefixedKey = `answers.${key}`;
200
+ const key = "user.dateOfBirth";
96
201
  const subscribe = useCallback(
97
- (cb) => variableStore.subscribe(cb, { keys: [prefixedKey] }),
98
- [variableStore, prefixedKey]
202
+ (cb) => variableStore.subscribe(cb, { keys: [key] }),
203
+ [variableStore]
99
204
  );
100
205
  const getSnapshot = useCallback(
101
- () => variableStore.get(prefixedKey),
102
- [variableStore, prefixedKey]
206
+ () => variableStore.get(key) || "",
207
+ [variableStore]
103
208
  );
104
209
  const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
105
210
  const setValue = useCallback(
106
- (v) => variableStore.set(prefixedKey, v),
107
- [variableStore, prefixedKey]
211
+ (v) => variableStore.set(key, toISODateWithFormat(v, format)),
212
+ [variableStore, format]
108
213
  );
109
214
  return [value, setValue];
110
215
  }
111
- function useResponses() {
112
- const { variableStore } = useFunnelContext();
113
- const subscribe = useCallback(
114
- (cb) => variableStore.subscribe(cb, { prefix: "answers." }),
115
- [variableStore]
116
- );
117
- const getSnapshot = useCallback(
118
- () => variableStore.getState(),
119
- [variableStore]
120
- );
121
- const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
122
- return useMemo(() => {
123
- const result = {};
124
- for (const [key, value] of Object.entries(variables)) {
125
- if (key.startsWith("answers.")) {
126
- result[key.slice(8)] = value;
127
- }
128
- }
129
- return result;
130
- }, [variables]);
131
- }
132
216
  function useQueryParams() {
133
217
  const { variableStore } = useFunnelContext();
134
218
  const subscribe = useCallback(
@@ -239,64 +323,6 @@ function useTranslation() {
239
323
  const availableLocales = i18n.getAvailableLocales();
240
324
  return { t, locale, setLocale, availableLocales };
241
325
  }
242
- function useNavigation() {
243
- const { router, variableStore, tracker } = useFunnelContext();
244
- useSyncExternalStore(
245
- useCallback((cb) => router.subscribe(cb), [router]),
246
- useCallback(() => router.getSnapshot(), [router]),
247
- useCallback(() => router.getSnapshot(), [router])
248
- );
249
- const afterNavigate = useCallback((key) => {
250
- const page = router.getCurrentPage();
251
- if (page) {
252
- tracker.track("page.view", {
253
- pageId: page.key,
254
- pageKey: page.key,
255
- pageName: page.name
256
- });
257
- tracker.startPageTracking(page.key);
258
- }
259
- variableStore.setMany({
260
- "page.currentId": key,
261
- "page.currentIndex": router.getPageHistory().length,
262
- "page.current": router.getPageHistory().length + 1,
263
- "page.startedAt": Date.now()
264
- });
265
- }, [router, tracker, variableStore]);
266
- const goToNextPage = useCallback(() => {
267
- const previousPage = router.getCurrentPage();
268
- if (previousPage) {
269
- tracker.stopPageTracking();
270
- }
271
- const variables = variableStore.getState();
272
- const nextKey = router.goToNextPage(variables);
273
- if (nextKey) {
274
- afterNavigate(nextKey);
275
- }
276
- }, [router, tracker, variableStore, afterNavigate]);
277
- const goBack = useCallback(() => {
278
- tracker.stopPageTracking();
279
- const prevKey = router.goBack();
280
- if (prevKey) {
281
- afterNavigate(prevKey);
282
- }
283
- }, [router, tracker, afterNavigate]);
284
- const goToPage = useCallback((pageKey) => {
285
- tracker.stopPageTracking();
286
- const key = router.goToPage(pageKey);
287
- if (key) {
288
- afterNavigate(key);
289
- }
290
- }, [router, tracker, afterNavigate]);
291
- return {
292
- goToNextPage,
293
- goBack,
294
- goToPage,
295
- currentPage: router.getCurrentPage(),
296
- pageHistory: router.getPageHistory(),
297
- progress: router.getProgress()
298
- };
299
- }
300
326
  function useProducts() {
301
327
  const { products, variableStore, selectProduct: ctxSelect } = useFunnelContext();
302
328
  const subscribe = useCallback(
@@ -322,25 +348,12 @@ function useTracking() {
322
348
  },
323
349
  [tracker]
324
350
  );
325
- const identify = useCallback(
326
- (email) => {
327
- tracker.identify(email);
328
- },
329
- [tracker]
330
- );
331
- return { track, identify };
351
+ return { track };
332
352
  }
333
- var PAYMENT_KEYS = [
334
- "card.last4",
335
- "card.brand",
336
- "card.expMonth",
337
- "card.expYear",
338
- "payment.loading",
339
- "payment.error",
340
- "payment.customerId"
341
- ];
353
+ var API_BASE_URL = "https://api.appfunnel.net";
354
+ var PAYMENT_KEYS = ["payment.loading", "payment.error"];
342
355
  function usePayment() {
343
- const { variableStore } = useFunnelContext();
356
+ const { variableStore, products, campaignId, tracker } = useFunnelContext();
344
357
  const subscribe = useCallback(
345
358
  (cb) => variableStore.subscribe(cb, { keys: PAYMENT_KEYS }),
346
359
  [variableStore]
@@ -350,20 +363,258 @@ function usePayment() {
350
363
  [variableStore]
351
364
  );
352
365
  const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
353
- return useMemo(() => {
354
- const last4 = variables["card.last4"] || "";
355
- const brand = variables["card.brand"] || "";
356
- const expMonth = variables["card.expMonth"] || 0;
357
- const expYear = variables["card.expYear"] || 0;
358
- const customerId = variables["payment.customerId"] || null;
359
- return {
360
- customerId,
361
- isAuthorized: !!last4,
366
+ const purchase = 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 useMemo(
611
+ () => ({
362
612
  loading: !!variables["payment.loading"],
363
613
  error: variables["payment.error"] || null,
364
- cardDetails: last4 ? { last4, brand, expMonth, expYear } : null
365
- };
366
- }, [variables]);
614
+ purchase
615
+ }),
616
+ [variables, purchase]
617
+ );
367
618
  }
368
619
  var DEVICE_KEYS = [
369
620
  "os.name",
@@ -421,6 +672,95 @@ function useDeviceInfo() {
421
672
  }
422
673
  }), [variables]);
423
674
  }
675
+ function useSafeArea() {
676
+ const [insets, setInsets] = useState({ top: 0, right: 0, bottom: 0, left: 0 });
677
+ 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] = useState({ isOpen: false, height: 0 });
714
+ const timeoutRef = useRef();
715
+ 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
+ }
424
764
  var PAGE_KEYS = [
425
765
  "page.currentId",
426
766
  "page.currentIndex",
@@ -467,7 +807,456 @@ function useFunnel() {
467
807
  payment: usePayment()
468
808
  };
469
809
  }
470
- 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__ */ jsxs("div", { style: { display: "flex", gap: "4px", alignItems: "center" }, children: [
833
+ /* @__PURE__ */ 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__ */ 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__ */ 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__ */ jsxs("svg", { width: "20", height: "16", viewBox: "0 0 20 16", fill: "none", style: { opacity: 0.4 }, children: [
840
+ /* @__PURE__ */ jsx("rect", { x: "0.5", y: "0.5", width: "19", height: "15", rx: "2", stroke: "#888" }),
841
+ /* @__PURE__ */ jsx("rect", { x: "0", y: "4", width: "20", height: "3", fill: "#888" }),
842
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "10", width: "8", height: "2", rx: "1", fill: "#ccc" }),
843
+ /* @__PURE__ */ jsx("text", { x: "14", y: "12", fontSize: "6", fill: "#888", fontFamily: FONT, children: "123" })
844
+ ] });
845
+ }
846
+ function DemoElementsForm({ onReady }) {
847
+ const [cardNumber, setCardNumber] = useState("");
848
+ const [expiry, setExpiry] = useState("");
849
+ const [cvc, setCvc] = useState("");
850
+ const [country, setCountry] = useState("Denmark");
851
+ const [focusedField, setFocusedField] = useState(null);
852
+ const readyFired = useRef(false);
853
+ 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__ */ jsxs("div", { style: { fontFamily: FONT }, children: [
869
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
870
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Card number" }),
871
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
872
+ /* @__PURE__ */ 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__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CardBrandBadges, {}) })
885
+ ] })
886
+ ] }),
887
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px", marginBottom: "14px" }, children: [
888
+ /* @__PURE__ */ jsxs("div", { children: [
889
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Expiry date" }),
890
+ /* @__PURE__ */ 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__ */ jsxs("div", { children: [
904
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Security code" }),
905
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
906
+ /* @__PURE__ */ 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__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CvcIcon, {}) })
919
+ ] })
920
+ ] })
921
+ ] }),
922
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
923
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Country" }),
924
+ /* @__PURE__ */ 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__ */ jsx("option", { children: "Australia" }),
941
+ /* @__PURE__ */ jsx("option", { children: "Austria" }),
942
+ /* @__PURE__ */ jsx("option", { children: "Belgium" }),
943
+ /* @__PURE__ */ jsx("option", { children: "Brazil" }),
944
+ /* @__PURE__ */ jsx("option", { children: "Canada" }),
945
+ /* @__PURE__ */ jsx("option", { children: "Denmark" }),
946
+ /* @__PURE__ */ jsx("option", { children: "Finland" }),
947
+ /* @__PURE__ */ jsx("option", { children: "France" }),
948
+ /* @__PURE__ */ jsx("option", { children: "Germany" }),
949
+ /* @__PURE__ */ jsx("option", { children: "Ireland" }),
950
+ /* @__PURE__ */ jsx("option", { children: "Italy" }),
951
+ /* @__PURE__ */ jsx("option", { children: "Japan" }),
952
+ /* @__PURE__ */ jsx("option", { children: "Netherlands" }),
953
+ /* @__PURE__ */ jsx("option", { children: "New Zealand" }),
954
+ /* @__PURE__ */ jsx("option", { children: "Norway" }),
955
+ /* @__PURE__ */ jsx("option", { children: "Poland" }),
956
+ /* @__PURE__ */ jsx("option", { children: "Portugal" }),
957
+ /* @__PURE__ */ jsx("option", { children: "Spain" }),
958
+ /* @__PURE__ */ jsx("option", { children: "Sweden" }),
959
+ /* @__PURE__ */ jsx("option", { children: "Switzerland" }),
960
+ /* @__PURE__ */ jsx("option", { children: "United Kingdom" }),
961
+ /* @__PURE__ */ jsx("option", { children: "United States" })
962
+ ]
963
+ }
964
+ )
965
+ ] }),
966
+ /* @__PURE__ */ 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 = useRef(false);
971
+ const [email, setEmail] = useState("");
972
+ const [cardNumber, setCardNumber] = useState("");
973
+ const [expiry, setExpiry] = useState("");
974
+ const [cvc, setCvc] = useState("");
975
+ const [name, setName] = useState("");
976
+ const [country, setCountry] = useState("Denmark");
977
+ const [focusedField, setFocusedField] = useState(null);
978
+ 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__ */ jsxs("div", { style: { fontFamily: FONT, border: "1px solid #e0e0e0", borderRadius: "12px", overflow: "hidden", background: "#fff" }, children: [
1006
+ product && /* @__PURE__ */ jsxs("div", { style: { padding: "24px 20px", borderBottom: "1px solid #e0e0e0" }, children: [
1007
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
1008
+ "Pay ",
1009
+ product.displayName || product.name
1010
+ ] }),
1011
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "28px", fontWeight: 700, color: "#333", marginBottom: "4px" }, children: [
1012
+ product.currencyCode?.toUpperCase(),
1013
+ displayPrice
1014
+ ] }),
1015
+ product.hasTrial && /* @__PURE__ */ jsxs("div", { style: { fontSize: "13px", color: "#666" }, children: [
1016
+ "Then ",
1017
+ /* @__PURE__ */ jsxs("span", { style: { textDecoration: "underline" }, children: [
1018
+ product.currencyCode?.toUpperCase(),
1019
+ product.price
1020
+ ] }),
1021
+ " every ",
1022
+ product.period
1023
+ ] }),
1024
+ /* @__PURE__ */ 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__ */ jsxs("div", { style: { padding: "20px" }, children: [
1044
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
1045
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Email" }),
1046
+ /* @__PURE__ */ 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__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
1060
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Card information" }),
1061
+ /* @__PURE__ */ jsxs("div", { style: { border: "1px solid #e0e0e0", borderRadius: "6px", overflow: "hidden" }, children: [
1062
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1063
+ /* @__PURE__ */ 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__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CardBrandBadges, {}) })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr" }, children: [
1078
+ /* @__PURE__ */ 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__ */ jsxs("div", { style: { position: "relative" }, children: [
1091
+ /* @__PURE__ */ 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__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CvcIcon, {}) })
1104
+ ] })
1105
+ ] })
1106
+ ] })
1107
+ ] }),
1108
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
1109
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Cardholder name" }),
1110
+ /* @__PURE__ */ 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__ */ jsxs("div", { style: { marginBottom: "20px" }, children: [
1124
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Country or region" }),
1125
+ /* @__PURE__ */ 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__ */ jsx("option", { children: "Australia" }),
1142
+ /* @__PURE__ */ jsx("option", { children: "Austria" }),
1143
+ /* @__PURE__ */ jsx("option", { children: "Belgium" }),
1144
+ /* @__PURE__ */ jsx("option", { children: "Brazil" }),
1145
+ /* @__PURE__ */ jsx("option", { children: "Canada" }),
1146
+ /* @__PURE__ */ jsx("option", { children: "Denmark" }),
1147
+ /* @__PURE__ */ jsx("option", { children: "Finland" }),
1148
+ /* @__PURE__ */ jsx("option", { children: "France" }),
1149
+ /* @__PURE__ */ jsx("option", { children: "Germany" }),
1150
+ /* @__PURE__ */ jsx("option", { children: "Ireland" }),
1151
+ /* @__PURE__ */ jsx("option", { children: "Italy" }),
1152
+ /* @__PURE__ */ jsx("option", { children: "Japan" }),
1153
+ /* @__PURE__ */ jsx("option", { children: "Netherlands" }),
1154
+ /* @__PURE__ */ jsx("option", { children: "New Zealand" }),
1155
+ /* @__PURE__ */ jsx("option", { children: "Norway" }),
1156
+ /* @__PURE__ */ jsx("option", { children: "Poland" }),
1157
+ /* @__PURE__ */ jsx("option", { children: "Portugal" }),
1158
+ /* @__PURE__ */ jsx("option", { children: "Spain" }),
1159
+ /* @__PURE__ */ jsx("option", { children: "Sweden" }),
1160
+ /* @__PURE__ */ jsx("option", { children: "Switzerland" }),
1161
+ /* @__PURE__ */ jsx("option", { children: "United Kingdom" }),
1162
+ /* @__PURE__ */ jsx("option", { children: "United States" })
1163
+ ]
1164
+ }
1165
+ )
1166
+ ] }),
1167
+ /* @__PURE__ */ 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__ */ jsxs("div", { style: { textAlign: "center", marginTop: "12px", fontSize: "12px", color: "#aaa" }, children: [
1189
+ "Powered by ",
1190
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 700, letterSpacing: "-0.3px" }, children: "stripe" })
1191
+ ] })
1192
+ ] })
1193
+ ] });
1194
+ }
1195
+ var DemoStripePaymentForm = 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 } = useFunnelContext();
1206
+ const validateOnly = mode === "validate-only";
1207
+ const product = 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 = useRef(false);
1213
+ 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 = 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
+ useImperativeHandle(ref, () => ({ submit: handleSubmit }), [handleSubmit]);
1255
+ return /* @__PURE__ */ jsx("div", { className, children: variant === "embedded" ? /* @__PURE__ */ jsx(DemoEmbeddedForm, { product, onReady }) : /* @__PURE__ */ jsx(DemoElementsForm, { onReady }) });
1256
+ }
1257
+ );
1258
+ var API_BASE_URL2 = "https://api.appfunnel.net";
1259
+ var isDevMode = () => typeof globalThis !== "undefined" && !!globalThis.__APPFUNNEL_DEV__;
471
1260
  var InnerPaymentForm = forwardRef(
472
1261
  function InnerPaymentForm2({ paymentMode, validateOnly, onSuccess, onError, onReady, layout }, ref) {
473
1262
  const stripe = useStripe();
@@ -515,7 +1304,7 @@ var InnerPaymentForm = forwardRef(
515
1304
  return;
516
1305
  }
517
1306
  const response2 = await fetch(
518
- `${API_BASE_URL}/campaign/${campaignId}/stripe/validate-card`,
1307
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/validate-card`,
519
1308
  {
520
1309
  method: "POST",
521
1310
  headers: { "Content-Type": "application/json" },
@@ -556,7 +1345,7 @@ var InnerPaymentForm = forwardRef(
556
1345
  return;
557
1346
  }
558
1347
  const response = await fetch(
559
- `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
1348
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/purchase`,
560
1349
  {
561
1350
  method: "POST",
562
1351
  headers: { "Content-Type": "application/json" },
@@ -601,8 +1390,8 @@ var InnerPaymentForm = forwardRef(
601
1390
  ] });
602
1391
  }
603
1392
  );
604
- var StripePaymentForm = forwardRef(
605
- function StripePaymentForm2({
1393
+ var RealStripePaymentForm = forwardRef(
1394
+ function RealStripePaymentForm2({
606
1395
  productId,
607
1396
  mode = "checkout",
608
1397
  variant = "elements",
@@ -641,7 +1430,7 @@ var StripePaymentForm = forwardRef(
641
1430
  try {
642
1431
  if (variant === "embedded") {
643
1432
  const response = await fetch(
644
- `${API_BASE_URL}/campaign/${campaignId}/stripe/checkout-session`,
1433
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/checkout-session`,
645
1434
  {
646
1435
  method: "POST",
647
1436
  headers: { "Content-Type": "application/json" },
@@ -667,7 +1456,7 @@ var StripePaymentForm = forwardRef(
667
1456
  priceId: product.storePriceId
668
1457
  };
669
1458
  if (validateOnly) body.validateOnly = true;
670
- const response = await fetch(`${API_BASE_URL}${endpoint}`, {
1459
+ const response = await fetch(`${API_BASE_URL2}${endpoint}`, {
671
1460
  method: "POST",
672
1461
  headers: { "Content-Type": "application/json" },
673
1462
  body: JSON.stringify(body)
@@ -743,6 +1532,14 @@ var StripePaymentForm = forwardRef(
743
1532
  ) }) });
744
1533
  }
745
1534
  );
1535
+ var StripePaymentForm = forwardRef(
1536
+ function StripePaymentForm2(props, ref) {
1537
+ if (isDevMode()) {
1538
+ return /* @__PURE__ */ jsx(DemoStripePaymentForm, { ref, ...props });
1539
+ }
1540
+ return /* @__PURE__ */ jsx(RealStripePaymentForm, { ref, ...props });
1541
+ }
1542
+ );
746
1543
  function PaddleCheckout({
747
1544
  productId,
748
1545
  mode = "overlay",
@@ -807,6 +1604,6 @@ function PaddleCheckout({
807
1604
  return null;
808
1605
  }
809
1606
 
810
- export { PaddleCheckout, StripePaymentForm, defineConfig, definePage, useData, useDeviceInfo, useFunnel, useLocale, useNavigation, usePageData, usePayment, useProducts, useQueryParam, useQueryParams, useResponse, useResponses, useTracking, useTranslation, useUser, useUserProperty, useVariable, useVariables };
1607
+ export { PaddleCheckout, StripePaymentForm, defineConfig, definePage, useData, useDateOfBirth, useDeviceInfo, useFunnel, useKeyboard, useLocale, usePageData, usePayment, useProducts, useQueryParam, useQueryParams, useSafeArea, useTracking, useTranslation, useUser, useUserProperty, useVariable, useVariables };
811
1608
  //# sourceMappingURL=index.js.map
812
1609
  //# sourceMappingURL=index.js.map