@tagadapay/plugin-sdk 2.8.7 → 2.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/react/config/environment.d.ts +1 -22
  2. package/dist/react/config/environment.js +1 -132
  3. package/dist/react/utils/deviceInfo.d.ts +1 -39
  4. package/dist/react/utils/deviceInfo.js +1 -163
  5. package/dist/react/utils/jwtDecoder.d.ts +1 -14
  6. package/dist/react/utils/jwtDecoder.js +1 -86
  7. package/dist/react/utils/tokenStorage.d.ts +1 -16
  8. package/dist/react/utils/tokenStorage.js +1 -53
  9. package/dist/v2/core/client.d.ts +92 -0
  10. package/dist/v2/core/client.js +386 -0
  11. package/dist/v2/core/config/environment.d.ts +22 -0
  12. package/dist/v2/core/config/environment.js +140 -0
  13. package/dist/v2/core/pathRemapping.js +61 -3
  14. package/dist/v2/core/resources/apiClient.d.ts +8 -0
  15. package/dist/v2/core/resources/apiClient.js +30 -9
  16. package/dist/v2/core/resources/funnel.d.ts +253 -16
  17. package/dist/v2/core/resources/payments.d.ts +23 -0
  18. package/dist/v2/core/types.d.ts +271 -0
  19. package/dist/v2/core/types.js +4 -0
  20. package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
  21. package/dist/v2/core/utils/deviceInfo.js +162 -0
  22. package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
  23. package/dist/v2/core/utils/eventDispatcher.js +24 -0
  24. package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
  25. package/dist/v2/core/utils/jwtDecoder.js +85 -0
  26. package/dist/v2/core/utils/pluginConfig.js +6 -0
  27. package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
  28. package/dist/v2/core/utils/tokenStorage.js +52 -0
  29. package/dist/v2/index.d.ts +2 -1
  30. package/dist/v2/index.js +1 -1
  31. package/dist/v2/react/components/DebugDrawer.js +90 -1
  32. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
  33. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
  34. package/dist/v2/react/hooks/useFunnel.d.ts +2 -2
  35. package/dist/v2/react/hooks/useFunnel.js +209 -32
  36. package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
  37. package/dist/v2/react/hooks/useISOData.js +4 -2
  38. package/dist/v2/react/hooks/useOffersQuery.d.ts +24 -29
  39. package/dist/v2/react/hooks/useOffersQuery.js +164 -204
  40. package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
  41. package/dist/v2/react/providers/TagadaProvider.d.ts +8 -21
  42. package/dist/v2/react/providers/TagadaProvider.js +79 -673
  43. package/package.json +1 -1
@@ -23,9 +23,10 @@ export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './co
23
23
  export type { StoreConfig } from './core/resources/storeConfig';
24
24
  export { FunnelActionType } from './core/resources/funnel';
25
25
  export type { BackNavigationActionData, CartUpdatedActionData, DirectNavigationActionData, FormSubmitActionData, FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelAction as FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, NextAction, OfferAcceptedActionData, OfferDeclinedActionData, PaymentFailedActionData, PaymentSuccessActionData, SimpleFunnelContext } from './core/resources/funnel';
26
- export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
26
+ export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
27
27
  export type { TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './react/hooks/useTranslation';
28
28
  export type { ClubOffer, ClubOfferItem, ClubOfferLineItem, ClubOfferSummary, UseClubOffersOptions, UseClubOffersResult } from './react/hooks/useClubOffers';
29
+ export type { UseCreditsOptions, UseCreditsResult } from './react/hooks/useCredits';
29
30
  export type { UseLoginOptions, UseLoginResult } from './react/hooks/useLogin';
30
31
  export type { CustomerAddress, CustomerInfos, CustomerOrderSummary, OrderWithRelations, Subscription, SubscriptionsResponse } from './core/resources/customer';
31
32
  export type { UseCustomerResult } from './react/hooks/useCustomer';
package/dist/v2/index.js CHANGED
@@ -15,4 +15,4 @@ export * from './core/utils/products';
15
15
  export * from './core/pathRemapping';
16
16
  export { FunnelActionType } from './core/resources/funnel';
17
17
  // React exports (hooks and components only, types are exported above)
18
- export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
18
+ export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
@@ -582,7 +582,96 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
582
582
  color: isCurrent ? '#d1fae5' : '#9ca3af',
583
583
  marginLeft: '8px',
584
584
  }, children: ["\u2022 ", cond.type, " \u2192 ", cond.nextStepId] }, condIdx)))] }))] }, step.id));
585
- }) })] })), _jsxs("div", { style: { marginTop: '12px' }, children: [_jsx("h4", { style: { margin: '0 0 8px 0', color: '#60a5fa', fontSize: '13px' }, children: "\uD83D\uDD0D Raw Data" }), _jsx("div", { style: { fontSize: '11px' }, children: _jsx(TreeView, { data: context.debugFunnel.data, name: "funnelData" }) })] })] })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No funnel hook active" }))] })), activeTab === 'checkout' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Checkout Debug" }), context.debugCheckout.isActive ? (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#60a5fa' }, children: "Status" }), _jsxs("div", { style: { display: 'grid', gap: '8px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Hook Active:" }), _jsx("span", { style: { color: '#10b981' }, children: "\u2705 Yes" })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Loading:" }), _jsx("span", { style: { color: context.debugCheckout.isLoading ? '#f59e0b' : '#10b981' }, children: context.debugCheckout.isLoading ? '⏳ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Has Error:" }), _jsx("span", { style: { color: context.debugCheckout.error ? '#ef4444' : '#10b981' }, children: context.debugCheckout.error ? '❌ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Last Updated:" }), _jsx("span", { style: { color: '#9ca3af', fontSize: '12px' }, children: context.debugCheckout.lastUpdated?.toLocaleTimeString() || 'Never' })] })] })] }), context.debugCheckout.error && (_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#ef4444' }, children: "Error Details" }), _jsxs("div", { style: {
585
+ }) })] })), context.debugFunnel.data?.session && (_jsxs("div", { style: { marginBottom: '16px' }, children: [_jsx("h4", { style: { margin: '0 0 8px 0', color: '#60a5fa', fontSize: '13px' }, children: "\uD83D\uDCE6 Resources & Context" }), context.debugFunnel.data.session.resources &&
586
+ Object.keys(context.debugFunnel.data.session.resources).length > 0 && (_jsxs("div", { style: { marginBottom: '12px' }, children: [_jsxs("div", { style: {
587
+ fontSize: '11px',
588
+ color: '#10b981',
589
+ fontWeight: 'bold',
590
+ marginBottom: '6px',
591
+ }, children: ["\uD83C\uDFAF Available Resources (", Object.keys(context.debugFunnel.data.session.resources).length, ")"] }), _jsx("div", { style: { display: 'grid', gap: '8px' }, children: Object.entries(context.debugFunnel.data.session.resources).map(([key, value]) => {
592
+ // Skip undefined/null resources
593
+ if (value === undefined || value === null)
594
+ return null;
595
+ // Check if it's an array or single resource
596
+ const isArray = Array.isArray(value);
597
+ const resourceCount = isArray ? value.length : 1;
598
+ return (_jsxs("div", { style: {
599
+ border: '1px solid #374151',
600
+ borderRadius: '6px',
601
+ padding: '10px',
602
+ backgroundColor: '#111827',
603
+ }, children: [_jsxs("div", { style: {
604
+ display: 'flex',
605
+ justifyContent: 'space-between',
606
+ alignItems: 'center',
607
+ marginBottom: '8px',
608
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '6px' }, children: [_jsx("span", { style: { color: '#10b981', fontSize: '14px' }, children: key === 'order' || key === 'mainOrder'
609
+ ? '🛒'
610
+ : key === 'customer'
611
+ ? '👤'
612
+ : key === 'checkout'
613
+ ? '💳'
614
+ : key === 'offer'
615
+ ? '🎁'
616
+ : key === 'subscription'
617
+ ? '🔄'
618
+ : '📄' }), _jsx("span", { style: {
619
+ color: '#f9fafb',
620
+ fontWeight: 'bold',
621
+ fontSize: '12px',
622
+ }, children: key }), isArray && (_jsxs("span", { style: {
623
+ fontSize: '10px',
624
+ backgroundColor: '#3b82f6',
625
+ color: '#fff',
626
+ padding: '2px 6px',
627
+ borderRadius: '3px',
628
+ }, children: [resourceCount, " items"] }))] }), !isArray && value && typeof value === 'object' && (_jsx("div", { style: { fontSize: '10px', color: '#9ca3af' }, children: value.id && (_jsx("span", { style: { fontFamily: 'monospace' }, children: value.id })) }))] }), !isArray && value && typeof value === 'object' && (_jsxs("div", { style: {
629
+ display: 'grid',
630
+ gridTemplateColumns: '1fr 1fr',
631
+ gap: '6px',
632
+ fontSize: '11px',
633
+ marginBottom: '8px',
634
+ }, children: [value.amount !== undefined && (_jsxs("div", { children: [_jsx("span", { style: { color: '#6b7280' }, children: "Amount: " }), _jsx("span", { style: { color: '#10b981', fontWeight: 'bold' }, children: (() => {
635
+ try {
636
+ return context.money.formatMoney(Number(value.amount) || 0, String(value.currency) || 'USD', context.locale.locale);
637
+ }
638
+ catch {
639
+ return `${value.amount} ${value.currency || ''}`;
640
+ }
641
+ })() })] })), value.email !== undefined && (_jsxs("div", { children: [_jsx("span", { style: { color: '#6b7280' }, children: "Email: " }), _jsx("span", { style: { color: '#60a5fa' }, children: value.email })] })), value.status !== undefined && (_jsxs("div", { children: [_jsx("span", { style: { color: '#6b7280' }, children: "Status: " }), _jsx("span", { style: { color: '#f59e0b' }, children: value.status })] })), value.currency !== undefined && (_jsxs("div", { children: [_jsx("span", { style: { color: '#6b7280' }, children: "Currency: " }), _jsx("span", { style: { color: '#8b5cf6' }, children: value.currency })] }))] })), _jsxs("details", { style: { marginTop: '6px' }, children: [_jsx("summary", { style: {
642
+ cursor: 'pointer',
643
+ fontSize: '10px',
644
+ color: '#60a5fa',
645
+ fontWeight: 'bold',
646
+ }, children: "Show Full Data" }), _jsx("div", { style: { marginTop: '6px', fontSize: '10px' }, children: _jsx(TreeView, { data: value, name: key, maxLevel: 3 }) })] })] }, key));
647
+ }) })] })), context.debugFunnel.data.session.metadata &&
648
+ Object.keys(context.debugFunnel.data.session.metadata).length > 0 && (_jsxs("div", { style: { marginBottom: '12px' }, children: [_jsxs("div", { style: {
649
+ fontSize: '11px',
650
+ color: '#8b5cf6',
651
+ fontWeight: 'bold',
652
+ marginBottom: '6px',
653
+ }, children: ["\uD83D\uDCDD Session Metadata (", Object.keys(context.debugFunnel.data.session.metadata).length, ")"] }), _jsx("div", { style: {
654
+ border: '1px solid #374151',
655
+ borderRadius: '6px',
656
+ padding: '10px',
657
+ backgroundColor: '#111827',
658
+ }, children: _jsx(TreeView, { data: context.debugFunnel.data.session.metadata, name: "metadata", maxLevel: 2 }) })] })), _jsxs("div", { style: { marginBottom: '12px' }, children: [_jsx("div", { style: {
659
+ fontSize: '11px',
660
+ color: '#f59e0b',
661
+ fontWeight: 'bold',
662
+ marginBottom: '6px',
663
+ }, children: "\u2139\uFE0F Session Context" }), _jsx("div", { style: {
664
+ border: '1px solid #374151',
665
+ borderRadius: '6px',
666
+ padding: '10px',
667
+ backgroundColor: '#111827',
668
+ }, children: _jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '11px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Customer ID:" }), _jsx("span", { style: { color: '#60a5fa', fontFamily: 'monospace', fontSize: '10px' }, children: context.debugFunnel.data.session.customerId })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Store ID:" }), _jsx("span", { style: { color: '#60a5fa', fontFamily: 'monospace', fontSize: '10px' }, children: context.debugFunnel.data.session.storeId })] }), context.debugFunnel.data.session.environment && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Environment:" }), _jsx("span", { style: { color: '#10b981' }, children: context.debugFunnel.data.session.environment })] })), context.debugFunnel.data.session.startedAt && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Started At:" }), _jsx("span", { style: { color: '#9ca3af', fontSize: '10px' }, children: new Date(context.debugFunnel.data.session.startedAt).toLocaleString() })] })), context.debugFunnel.data.session.lastActivityAt && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Last Activity:" }), _jsx("span", { style: { color: '#9ca3af', fontSize: '10px' }, children: new Date(context.debugFunnel.data.session.lastActivityAt).toLocaleString() })] }))] }) })] })] })), _jsxs("details", { style: { marginTop: '12px' }, children: [_jsx("summary", { style: {
669
+ cursor: 'pointer',
670
+ fontWeight: 'bold',
671
+ fontSize: '13px',
672
+ color: '#60a5fa',
673
+ marginBottom: '8px',
674
+ }, children: "\uD83D\uDD0D Raw Funnel Data (Debug)" }), _jsx("div", { style: { fontSize: '11px', marginTop: '8px' }, children: _jsx(TreeView, { data: context.debugFunnel.data, name: "funnelData", maxLevel: 4 }) })] })] })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No funnel hook active" }))] })), activeTab === 'checkout' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Checkout Debug" }), context.debugCheckout.isActive ? (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#60a5fa' }, children: "Status" }), _jsxs("div", { style: { display: 'grid', gap: '8px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Hook Active:" }), _jsx("span", { style: { color: '#10b981' }, children: "\u2705 Yes" })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Loading:" }), _jsx("span", { style: { color: context.debugCheckout.isLoading ? '#f59e0b' : '#10b981' }, children: context.debugCheckout.isLoading ? '⏳ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Has Error:" }), _jsx("span", { style: { color: context.debugCheckout.error ? '#ef4444' : '#10b981' }, children: context.debugCheckout.error ? '❌ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Last Updated:" }), _jsx("span", { style: { color: '#9ca3af', fontSize: '12px' }, children: context.debugCheckout.lastUpdated?.toLocaleTimeString() || 'Never' })] })] })] }), context.debugCheckout.error && (_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#ef4444' }, children: "Error Details" }), _jsxs("div", { style: {
586
675
  backgroundColor: '#372c2c',
587
676
  padding: '12px',
588
677
  borderRadius: '4px',
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Example: Accessing Funnel Context & Resources
3
+ *
4
+ * This example demonstrates how to access data from previous steps
5
+ * using the funnel context, including order, customer, and other resources.
6
+ */
7
+ export declare function FunnelContextExample(): import("react/jsx-runtime").JSX.Element;
8
+ /**
9
+ * Simple example for documentation
10
+ * This can now be called with or without options
11
+ */
12
+ export declare function SimpleContextExample(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Example: Accessing Funnel Context & Resources
4
+ *
5
+ * This example demonstrates how to access data from previous steps
6
+ * using the funnel context, including order, customer, and other resources.
7
+ */
8
+ import { useFunnel } from '../useFunnel';
9
+ export function FunnelContextExample() {
10
+ const { context, isLoading, isInitialized } = useFunnel({
11
+ autoInitialize: true,
12
+ });
13
+ if (isLoading) {
14
+ return _jsx("div", { children: "Loading funnel session..." });
15
+ }
16
+ if (!isInitialized || !context) {
17
+ return _jsx("div", { children: "Funnel not initialized" });
18
+ }
19
+ // Access resources from context
20
+ const order = context.resources?.order;
21
+ const customer = context.resources?.customer;
22
+ const checkout = context.resources?.checkout;
23
+ const orders = context.resources?.orders || [];
24
+ return (_jsxs("div", { style: { padding: '20px', fontFamily: 'sans-serif' }, children: [_jsx("h1", { children: "Funnel Context Example" }), _jsxs("section", { style: { marginBottom: '30px' }, children: [_jsx("h2", { children: "Session Info" }), _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Session ID:" }), " ", context.sessionId] }), _jsxs("li", { children: [_jsx("strong", { children: "Funnel ID:" }), " ", context.funnelId] }), _jsxs("li", { children: [_jsx("strong", { children: "Current Step:" }), " ", context.currentStepId] }), _jsxs("li", { children: [_jsx("strong", { children: "Previous Step:" }), " ", context.previousStepId || 'None'] }), _jsxs("li", { children: [_jsx("strong", { children: "Furthest Step:" }), " ", context.furthestStepId || 'None'] }), _jsxs("li", { children: [_jsx("strong", { children: "Environment:" }), " ", context.environment || 'Not set'] })] })] }), _jsxs("section", { style: { marginBottom: '30px' }, children: [_jsx("h2", { children: "Resources" }), order && (_jsxs("div", { style: { marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '5px' }, children: [_jsx("h3", { children: "Order (Hot Context)" }), _jsx("pre", { style: { background: '#f5f5f5', padding: '10px', borderRadius: '3px', overflow: 'auto' }, children: JSON.stringify(order, null, 2) })] })), customer && (_jsxs("div", { style: { marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '5px' }, children: [_jsx("h3", { children: "Customer" }), _jsx("pre", { style: { background: '#f5f5f5', padding: '10px', borderRadius: '3px', overflow: 'auto' }, children: JSON.stringify(customer, null, 2) })] })), checkout && (_jsxs("div", { style: { marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '5px' }, children: [_jsx("h3", { children: "Checkout" }), _jsx("pre", { style: { background: '#f5f5f5', padding: '10px', borderRadius: '3px', overflow: 'auto' }, children: JSON.stringify(checkout, null, 2) })] })), orders.length > 0 && (_jsxs("div", { style: { marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '5px' }, children: [_jsxs("h3", { children: ["Orders Collection (", orders.length, " orders)"] }), orders.map((o, index) => (_jsxs("div", { style: { marginBottom: '10px' }, children: [_jsxs("strong", { children: ["Order ", index + 1, ":"] }), _jsx("pre", { style: {
25
+ background: '#f5f5f5',
26
+ padding: '10px',
27
+ borderRadius: '3px',
28
+ marginTop: '5px',
29
+ overflow: 'auto',
30
+ }, children: JSON.stringify(o, null, 2) })] }, o.id || index)))] })), _jsxs("details", { style: { marginTop: '20px' }, children: [_jsx("summary", { style: { cursor: 'pointer', fontWeight: 'bold' }, children: "Show All Resources (Raw)" }), _jsx("pre", { style: {
31
+ background: '#f5f5f5',
32
+ padding: '10px',
33
+ borderRadius: '3px',
34
+ marginTop: '10px',
35
+ overflow: 'auto',
36
+ }, children: JSON.stringify(context.resources, null, 2) })] })] }), context.metadata && Object.keys(context.metadata).length > 0 && (_jsxs("section", { style: { marginBottom: '30px' }, children: [_jsx("h2", { children: "Metadata" }), _jsx("pre", { style: { background: '#f5f5f5', padding: '10px', borderRadius: '3px', overflow: 'auto' }, children: JSON.stringify(context.metadata, null, 2) })] })), _jsxs("details", { children: [_jsx("summary", { style: { cursor: 'pointer', fontWeight: 'bold' }, children: "Show Full Context (Debug)" }), _jsx("pre", { style: {
37
+ background: '#f5f5f5',
38
+ padding: '10px',
39
+ borderRadius: '3px',
40
+ marginTop: '10px',
41
+ overflow: 'auto',
42
+ }, children: JSON.stringify(context, null, 2) })] })] }));
43
+ }
44
+ /**
45
+ * Simple example for documentation
46
+ * This can now be called with or without options
47
+ */
48
+ export function SimpleContextExample() {
49
+ const funnel = useFunnel(); // Works without params!
50
+ // Access data from previous steps
51
+ const order = funnel.context?.resources?.order;
52
+ const customer = funnel.context?.resources?.customer;
53
+ return (_jsxs("div", { children: [_jsxs("h1", { children: ["Order ", order?.id] }), _jsxs("p", { children: ["Customer: ", customer?.email] }), _jsxs("p", { children: ["Amount: $", ((order?.amount || 0) / 100).toFixed(2)] })] }));
54
+ }
@@ -37,7 +37,7 @@ export interface UseFunnelResult {
37
37
  * Modern funnel navigation using TanStack Query for state management
38
38
  * and the v2 ApiClient architecture.
39
39
  */
40
- export declare function useFunnel(options: UseFunnelOptions): UseFunnelResult;
40
+ export declare function useFunnel(options?: UseFunnelOptions): UseFunnelResult;
41
41
  /**
42
42
  * Simplified funnel hook for basic step tracking (v2)
43
43
  */
@@ -46,6 +46,6 @@ export declare function useSimpleFunnel(funnelId: string, initialStepId?: string
46
46
  next: (event: FunnelAction) => Promise<any>;
47
47
  goToStep: (stepId: string) => Promise<any>;
48
48
  isLoading: boolean;
49
- context: SimpleFunnelContext | null;
49
+ context: SimpleFunnelContext<{}> | null;
50
50
  };
51
51
  export type { FunnelAction as FunnelEvent, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from '../../core/resources/funnel';
@@ -5,7 +5,7 @@
5
5
  * and the v2 ApiClient for API calls.
6
6
  */
7
7
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
8
- import { useCallback, useEffect, useMemo, useState } from 'react';
8
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
9
9
  import { FunnelActionType, FunnelResource } from '../../core/resources/funnel';
10
10
  import { useTagadaContext } from '../providers/TagadaProvider';
11
11
  import { getGlobalApiClient } from './useApiQuery';
@@ -20,7 +20,7 @@ const funnelQueryKeys = {
20
20
  * Modern funnel navigation using TanStack Query for state management
21
21
  * and the v2 ApiClient architecture.
22
22
  */
23
- export function useFunnel(options) {
23
+ export function useFunnel(options = {}) {
24
24
  const { auth, store, debugMode, updateFunnelDebugData } = useTagadaContext();
25
25
  const queryClient = useQueryClient();
26
26
  const apiClient = getGlobalApiClient();
@@ -29,10 +29,53 @@ export function useFunnel(options) {
29
29
  const [context, setContext] = useState(null);
30
30
  const [initializationAttempted, setInitializationAttempted] = useState(false);
31
31
  const [initializationError, setInitializationError] = useState(null);
32
+ // Track the last processed URL funnelId to avoid re-processing on context changes
33
+ const lastProcessedUrlFunnelIdRef = useRef(undefined);
34
+ // Check if we have an existing session in cookies (to avoid unnecessary re-initialization)
35
+ const hasExistingSessionCookie = useMemo(() => {
36
+ if (typeof document === 'undefined')
37
+ return false;
38
+ const funnelSessionCookie = document.cookie
39
+ .split('; ')
40
+ .find(row => row.startsWith('tgd-funnel-session-id='));
41
+ return !!funnelSessionCookie;
42
+ }, []); // Only check once on mount
32
43
  const currentStepId = options.currentStepId || context?.currentStepId;
33
- // Check for URL parameter overrides
34
- const urlParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams();
35
- const urlFunnelId = urlParams.get('funnelId') || undefined;
44
+ // Check for URL parameter overrides - recalculate on every render to detect URL changes
45
+ // This is cheap and ensures we always have the latest URL params
46
+ const [currentUrl, setCurrentUrl] = useState(() => typeof window !== 'undefined' ? window.location.href : '');
47
+ // Listen for URL changes (navigation)
48
+ useEffect(() => {
49
+ if (typeof window === 'undefined')
50
+ return;
51
+ const updateUrl = () => {
52
+ setCurrentUrl(window.location.href);
53
+ };
54
+ // Listen for popstate (back/forward buttons) and pushState/replaceState
55
+ window.addEventListener('popstate', updateUrl);
56
+ // Also update on any navigation
57
+ const originalPushState = window.history.pushState;
58
+ const originalReplaceState = window.history.replaceState;
59
+ window.history.pushState = function (...args) {
60
+ originalPushState.apply(window.history, args);
61
+ updateUrl();
62
+ };
63
+ window.history.replaceState = function (...args) {
64
+ originalReplaceState.apply(window.history, args);
65
+ updateUrl();
66
+ };
67
+ return () => {
68
+ window.removeEventListener('popstate', updateUrl);
69
+ window.history.pushState = originalPushState;
70
+ window.history.replaceState = originalReplaceState;
71
+ };
72
+ }, []);
73
+ const urlFunnelId = useMemo(() => {
74
+ if (typeof window === 'undefined')
75
+ return undefined;
76
+ const params = new URLSearchParams(window.location.search);
77
+ return params.get('funnelId') || undefined;
78
+ }, [currentUrl]); // Re-compute when URL changes
36
79
  const effectiveFunnelId = urlFunnelId || options.funnelId;
37
80
  /**
38
81
  * Fetch debug data for the funnel debugger
@@ -66,13 +109,8 @@ export function useFunnel(options) {
66
109
  console.warn('🍪 Funnel: No session ID available for query');
67
110
  return null;
68
111
  }
69
- // ✅ Automatically include currentUrl for session sync on page load/refresh
70
- const currentUrl = typeof window !== 'undefined' ? window.location.href : undefined;
71
112
  console.log(`🍪 Funnel: Fetching session data for ID: ${context.sessionId}`);
72
- if (currentUrl) {
73
- console.log(`🍪 Funnel: Including current URL for sync: ${currentUrl}`);
74
- }
75
- const response = await funnelResource.getSession(context.sessionId, currentUrl);
113
+ const response = await funnelResource.getSession(context.sessionId);
76
114
  console.log(`🍪 Funnel: Session fetch response:`, response);
77
115
  if (response.success && response.context) {
78
116
  return response.context;
@@ -90,8 +128,14 @@ export function useFunnel(options) {
90
128
  if (!auth.session?.customerId || !store?.id) {
91
129
  throw new Error('Authentication required for funnel session');
92
130
  }
131
+ // Get CURRENT URL params (not cached) to ensure we have latest values
132
+ const currentUrlParams = typeof window !== 'undefined'
133
+ ? new URLSearchParams(window.location.search)
134
+ : new URLSearchParams();
135
+ const currentUrlFunnelId = currentUrlParams.get('funnelId') || undefined;
136
+ const currentEffectiveFunnelId = currentUrlFunnelId || options.funnelId;
93
137
  // Check for existing session ID in URL parameters
94
- let existingSessionId = urlParams.get('funnelSessionId') || undefined;
138
+ let existingSessionId = currentUrlParams.get('funnelSessionId') || undefined;
95
139
  if (existingSessionId) {
96
140
  console.log(`🍪 Funnel: Found session ID in URL params: ${existingSessionId}`);
97
141
  }
@@ -102,11 +146,23 @@ export function useFunnel(options) {
102
146
  .find(row => row.startsWith('tgd-funnel-session-id='));
103
147
  existingSessionId = funnelSessionCookie ? funnelSessionCookie.split('=')[1] : undefined;
104
148
  if (existingSessionId) {
105
- console.log(`🍪 Funnel: Found session in cookie: ${existingSessionId}`);
149
+ console.log(`🍪 Funnel: Found existing session in cookie: ${existingSessionId}`);
106
150
  }
107
151
  }
108
- if (!existingSessionId) {
109
- console.log(`🍪 Funnel: No existing session found in URL params or cookie`);
152
+ // Log initialization strategy
153
+ console.log(`🍪 Funnel: Initialize request:`);
154
+ console.log(` - Existing session ID: ${existingSessionId || 'none'}`);
155
+ console.log(` - URL funnelId: ${currentUrlFunnelId || 'none'}`);
156
+ console.log(` - Hook funnelId: ${options.funnelId || 'none'}`);
157
+ console.log(` - Effective funnelId to send: ${currentEffectiveFunnelId || 'none (will use existing or backend default)'}`);
158
+ if (existingSessionId && !currentEffectiveFunnelId) {
159
+ console.log(` → Strategy: REUSE existing session from cookie`);
160
+ }
161
+ else if (existingSessionId && currentEffectiveFunnelId) {
162
+ console.log(` → Strategy: VALIDATE existing session matches funnelId, or create new`);
163
+ }
164
+ else {
165
+ console.log(` → Strategy: CREATE new session`);
110
166
  }
111
167
  // Send minimal CMS session data
112
168
  const cmsSessionData = {
@@ -115,17 +171,35 @@ export function useFunnel(options) {
115
171
  sessionId: auth.session.sessionId,
116
172
  accountId: auth.session.accountId
117
173
  };
174
+ // Get current URL for session synchronization
175
+ const currentUrl = typeof window !== 'undefined' ? window.location.href : undefined;
118
176
  // Call API to initialize session - backend will restore existing or create new
119
177
  const requestBody = {
120
178
  cmsSession: cmsSessionData,
121
179
  entryStepId, // Optional override
122
- existingSessionId // Pass existing session ID from URL or cookie
180
+ existingSessionId, // Pass existing session ID from URL or cookie
181
+ currentUrl // Include current URL for step tracking
123
182
  };
124
183
  // Only include funnelId if it's provided (for backend fallback)
125
- if (effectiveFunnelId) {
126
- requestBody.funnelId = effectiveFunnelId;
184
+ if (currentEffectiveFunnelId) {
185
+ requestBody.funnelId = currentEffectiveFunnelId;
127
186
  }
187
+ console.log(`📤 Funnel: Sending initialize request to backend:`);
188
+ console.log(` Request body:`, JSON.stringify({
189
+ ...requestBody,
190
+ cmsSession: { ...cmsSessionData, sessionId: `${cmsSessionData.sessionId.substring(0, 20)}...` }
191
+ }, null, 2));
128
192
  const response = await funnelResource.initialize(requestBody);
193
+ console.log(`📥 Funnel: Received response from backend:`);
194
+ console.log(` Success: ${response.success}`);
195
+ if (response.context) {
196
+ console.log(` Returned session ID: ${response.context.sessionId}`);
197
+ console.log(` Returned funnel ID: ${response.context.funnelId}`);
198
+ console.log(` Is same session? ${response.context.sessionId === existingSessionId ? '✅ YES (reused)' : '❌ NO (new session created)'}`);
199
+ }
200
+ if (response.error) {
201
+ console.log(` Error: ${response.error}`);
202
+ }
129
203
  if (response.success && response.context) {
130
204
  return response.context;
131
205
  }
@@ -138,7 +212,10 @@ export function useFunnel(options) {
138
212
  setInitializationError(null);
139
213
  // Set session cookie for persistence across page reloads
140
214
  setSessionCookie(newContext.sessionId);
141
- console.log(`🍪 Funnel: Initialized session for funnel ${effectiveFunnelId || 'default'}`, newContext);
215
+ console.log(`✅ Funnel: Session initialized/loaded successfully`);
216
+ console.log(` - Session ID: ${newContext.sessionId}`);
217
+ console.log(` - Funnel ID: ${newContext.funnelId}`);
218
+ console.log(` - Current Step: ${newContext.currentStepId}`);
142
219
  // Fetch debug data if in debug mode
143
220
  if (debugMode) {
144
221
  void fetchFunnelDebugData(newContext.sessionId);
@@ -317,6 +394,8 @@ export function useFunnel(options) {
317
394
  return;
318
395
  console.log(`🍪 Funnel: Ended session ${context.sessionId}`);
319
396
  setContext(null);
397
+ // Reset the processed URL funnelId ref to allow re-initialization
398
+ lastProcessedUrlFunnelIdRef.current = undefined;
320
399
  // Clear queries
321
400
  queryClient.removeQueries({
322
401
  queryKey: funnelQueryKeys.session(context.sessionId)
@@ -421,29 +500,127 @@ export function useFunnel(options) {
421
500
  setInitializationError(null);
422
501
  await initializeSession();
423
502
  }, [initializeSession]);
424
- // Auto-initialize if requested and not already initialized
503
+ /**
504
+ * Auto-initialization effect
505
+ *
506
+ * Handles funnel session initialization with the following logic:
507
+ * 1. If no session exists => Initialize with URL funnelId or hook funnelId or backend default
508
+ * 2. If session exists + explicit funnelId provided that differs => Reset and create new
509
+ * 3. If session exists + no funnelId provided => Keep existing session
510
+ *
511
+ * IMPORTANT: Only resets session if an EXPLICIT funnelId is provided that differs
512
+ * from the current session. If no funnelId is provided, keeps the existing session.
513
+ *
514
+ * Priority: URL funnelId > Hook funnelId > Existing session funnelId > Backend default
515
+ */
425
516
  useEffect(() => {
426
- if (options.autoInitialize &&
427
- !context &&
428
- !initializationAttempted &&
429
- !initializationError &&
430
- auth.session?.customerId &&
431
- store?.id &&
432
- !initializeMutation.isPending) {
433
- console.log('🍪 Funnel: Auto-initializing session...');
517
+ console.log('🔍 Funnel: Auto-init effect triggered');
518
+ console.log(` - autoInitialize: ${options.autoInitialize ?? true}`);
519
+ console.log(` - hasAuth: ${!!auth.session?.customerId}`);
520
+ console.log(` - hasStore: ${!!store?.id}`);
521
+ console.log(` - isPending: ${initializeMutation.isPending}`);
522
+ console.log(` - hasContext: ${!!context}`);
523
+ console.log(` - context.sessionId: ${context?.sessionId || 'none'}`);
524
+ console.log(` - context.funnelId: ${context?.funnelId || 'none'}`);
525
+ console.log(` - urlFunnelId: ${urlFunnelId || 'none'}`);
526
+ console.log(` - options.funnelId: ${options.funnelId || 'none'}`);
527
+ console.log(` - initializationAttempted: ${initializationAttempted}`);
528
+ console.log(` - hasExistingSessionCookie: ${hasExistingSessionCookie}`);
529
+ // Skip if auto-initialize is disabled
530
+ const autoInit = options.autoInitialize ?? true; // Default to true
531
+ if (!autoInit) {
532
+ console.log('⏭️ Funnel: Skipping - auto-initialize disabled');
533
+ return;
534
+ }
535
+ // Skip if required dependencies are not available
536
+ if (!auth.session?.customerId || !store?.id) {
537
+ console.log('⏭️ Funnel: Skipping - missing auth or store');
538
+ return;
539
+ }
540
+ // Skip if already initializing
541
+ if (initializeMutation.isPending) {
542
+ console.log('⏭️ Funnel: Skipping - already initializing');
543
+ return;
544
+ }
545
+ // Determine if we have an explicit funnelId request (URL has priority)
546
+ const explicitFunnelId = urlFunnelId || options.funnelId;
547
+ console.log(` - explicitFunnelId: ${explicitFunnelId || 'none'}`);
548
+ // Case 1: No session exists yet - need to initialize
549
+ if (!context) {
550
+ console.log('📍 Funnel: Case 1 - No context exists');
551
+ // Check if we've already attempted initialization
552
+ if (!initializationAttempted) {
553
+ if (hasExistingSessionCookie) {
554
+ console.log('🍪 Funnel: Loading existing session from cookie...');
555
+ }
556
+ else {
557
+ console.log('🍪 Funnel: No session found - creating new session...');
558
+ }
559
+ if (explicitFunnelId) {
560
+ console.log(` with funnelId: ${explicitFunnelId}`);
561
+ }
562
+ setInitializationAttempted(true);
563
+ initializeSession().catch(error => {
564
+ console.error('❌ Funnel: Auto-initialization failed:', error);
565
+ });
566
+ }
567
+ else {
568
+ console.log('⏭️ Funnel: Skipping - already attempted initialization');
569
+ }
570
+ return;
571
+ }
572
+ console.log('📍 Funnel: Case 2 - Context exists, checking for reset needs');
573
+ // Case 2: Session exists - check if we need to reset it
574
+ // ONLY reset if an explicit funnelId is provided AND it differs from current session
575
+ if (explicitFunnelId && context.funnelId && explicitFunnelId !== context.funnelId) {
576
+ console.log(`🔍 Funnel: Mismatch check - explicitFunnelId: ${explicitFunnelId}, context.funnelId: ${context.funnelId}`);
577
+ // Check if we've already processed this funnelId to prevent loops
578
+ if (lastProcessedUrlFunnelIdRef.current === explicitFunnelId) {
579
+ console.log('⏭️ Funnel: Skipping - already processed this funnelId');
580
+ return;
581
+ }
582
+ console.log(`🔄 Funnel: Explicit funnelId mismatch detected!`);
583
+ console.log(` Current session funnelId: ${context.funnelId}`);
584
+ console.log(` Requested funnelId: ${explicitFunnelId}`);
585
+ console.log(` Resetting session...`);
586
+ // Mark this funnelId as processed
587
+ lastProcessedUrlFunnelIdRef.current = explicitFunnelId;
588
+ // Clear existing session
589
+ setContext(null);
590
+ setInitializationAttempted(false);
591
+ setInitializationError(null);
592
+ // Clear session cookie
593
+ document.cookie = 'tgd-funnel-session-id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
594
+ // Clear queries for old session
595
+ queryClient.removeQueries({
596
+ queryKey: funnelQueryKeys.session(context.sessionId)
597
+ });
598
+ // Initialize new session with correct funnelId
599
+ console.log(`🍪 Funnel: Creating new session with funnelId: ${explicitFunnelId}`);
434
600
  initializeSession().catch(error => {
435
- console.error('Auto-initialization failed - will not retry:', error);
601
+ console.error(' Funnel: Failed to reset session:', error);
436
602
  });
437
603
  }
604
+ else {
605
+ // Case 3: Session exists and no conflicting funnelId - keep using it
606
+ console.log('✅ Funnel: Case 3 - Keeping existing session (no reset needed)');
607
+ console.log(` - explicitFunnelId: ${explicitFunnelId || 'none (will keep existing)'}`);
608
+ console.log(` - context.funnelId: ${context.funnelId}`);
609
+ console.log(` - Match or no explicit request: keeping session ${context.sessionId}`);
610
+ }
438
611
  }, [
439
612
  options.autoInitialize,
440
- context,
613
+ options.funnelId,
614
+ urlFunnelId,
615
+ context?.funnelId,
616
+ context?.sessionId,
441
617
  initializationAttempted,
442
- initializationError,
443
618
  auth.session?.customerId,
444
619
  store?.id,
445
620
  initializeMutation.isPending,
446
- initializeSession
621
+ initializeSession,
622
+ hasExistingSessionCookie,
623
+ queryClient
447
624
  ]);
448
625
  // Sync session data from query to local state
449
626
  useEffect(() => {