@tagadapay/plugin-sdk 2.8.8 → 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.
- package/dist/react/config/environment.d.ts +1 -22
- package/dist/react/config/environment.js +1 -132
- package/dist/react/utils/deviceInfo.d.ts +1 -39
- package/dist/react/utils/deviceInfo.js +1 -163
- package/dist/react/utils/jwtDecoder.d.ts +1 -14
- package/dist/react/utils/jwtDecoder.js +1 -86
- package/dist/react/utils/tokenStorage.d.ts +1 -16
- package/dist/react/utils/tokenStorage.js +1 -53
- package/dist/v2/core/client.d.ts +92 -0
- package/dist/v2/core/client.js +386 -0
- package/dist/v2/core/config/environment.d.ts +22 -0
- package/dist/v2/core/config/environment.js +140 -0
- package/dist/v2/core/pathRemapping.js +61 -3
- package/dist/v2/core/resources/apiClient.d.ts +8 -0
- package/dist/v2/core/resources/apiClient.js +30 -9
- package/dist/v2/core/resources/funnel.d.ts +14 -0
- package/dist/v2/core/resources/payments.d.ts +23 -0
- package/dist/v2/core/types.d.ts +271 -0
- package/dist/v2/core/types.js +4 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
- package/dist/v2/core/utils/deviceInfo.js +162 -0
- package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
- package/dist/v2/core/utils/eventDispatcher.js +24 -0
- package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
- package/dist/v2/core/utils/jwtDecoder.js +85 -0
- package/dist/v2/core/utils/pluginConfig.js +6 -0
- package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
- package/dist/v2/core/utils/tokenStorage.js +52 -0
- package/dist/v2/react/components/DebugDrawer.js +90 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +1 -1
- package/dist/v2/react/hooks/useFunnel.js +209 -32
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
- package/dist/v2/react/hooks/useISOData.js +4 -2
- package/dist/v2/react/hooks/useOffersQuery.d.ts +24 -29
- package/dist/v2/react/hooks/useOffersQuery.js +164 -204
- package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
- package/dist/v2/react/providers/TagadaProvider.d.ts +8 -21
- package/dist/v2/react/providers/TagadaProvider.js +79 -673
- package/package.json +1 -1
|
@@ -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
|
-
|
|
35
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
109
|
-
|
|
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 (
|
|
126
|
-
requestBody.funnelId =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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('
|
|
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
|
-
|
|
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(() => {
|
|
@@ -15,6 +15,7 @@ export function useGoogleAutocomplete(options) {
|
|
|
15
15
|
const autocompleteServiceRef = useRef(null);
|
|
16
16
|
const placesServiceRef = useRef(null);
|
|
17
17
|
const scriptLoadedRef = useRef(false);
|
|
18
|
+
const debounceTimeoutRef = useRef(null);
|
|
18
19
|
// Inject Google Maps script
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
if (!apiKey) {
|
|
@@ -97,25 +98,32 @@ export function useGoogleAutocomplete(options) {
|
|
|
97
98
|
setPredictions([]);
|
|
98
99
|
return;
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
// Clear existing timeout
|
|
102
|
+
if (debounceTimeoutRef.current) {
|
|
103
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
104
|
+
}
|
|
105
|
+
// Set new timeout
|
|
106
|
+
debounceTimeoutRef.current = setTimeout(() => {
|
|
107
|
+
setIsLoading(true);
|
|
108
|
+
const request = {
|
|
109
|
+
input,
|
|
110
|
+
...(countryRestriction && {
|
|
111
|
+
componentRestrictions: { country: countryRestriction.toLowerCase() },
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
114
|
+
autocompleteServiceRef.current.getPlacePredictions(request, (results, status) => {
|
|
115
|
+
setIsLoading(false);
|
|
116
|
+
if (status === window.google.maps.places.PlacesServiceStatus.OK && results) {
|
|
117
|
+
setPredictions(results);
|
|
116
118
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
else {
|
|
120
|
+
setPredictions([]);
|
|
121
|
+
if (status !== window.google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
|
|
122
|
+
// Prediction failed but not zero results
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}, 300); // 300ms debounce
|
|
119
127
|
}, [initializeServices, isScriptLoaded]);
|
|
120
128
|
// Get detailed place information
|
|
121
129
|
const getPlaceDetails = useCallback((placeId) => {
|
|
@@ -25,8 +25,10 @@ export function useISOData(language = 'en', autoImport = true, disputeSetting =
|
|
|
25
25
|
}, [language, autoImport]);
|
|
26
26
|
const data = useMemo(() => {
|
|
27
27
|
try {
|
|
28
|
+
console.log('[SDK] Loading ISO data for language:', language, 'autoImport:', autoImport);
|
|
28
29
|
// Get countries from pre-built data with language support
|
|
29
30
|
const countriesArray = getCountries(language);
|
|
31
|
+
console.log('[SDK] Loaded countries count:', countriesArray.length);
|
|
30
32
|
// Transform to our expected format (Record<string, ISOCountry>)
|
|
31
33
|
const countries = {};
|
|
32
34
|
countriesArray.forEach((country) => {
|
|
@@ -85,8 +87,8 @@ export function useISOData(language = 'en', autoImport = true, disputeSetting =
|
|
|
85
87
|
registeredLanguages,
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
|
-
catch (
|
|
89
|
-
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('[SDK] Error loading ISO data:', error);
|
|
90
92
|
return {
|
|
91
93
|
countries: {},
|
|
92
94
|
getRegions: () => [],
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Offers Hook using TanStack Query
|
|
3
3
|
* Handles offers with automatic caching
|
|
4
4
|
*/
|
|
5
|
-
import { Offer } from '../../core/resources/offers';
|
|
6
|
-
import {
|
|
5
|
+
import { Offer, OfferSummary } from '../../core/resources/offers';
|
|
6
|
+
import { CurrencyOptions, OrderSummary } from '../../core/resources/postPurchases';
|
|
7
7
|
export interface UseOffersQueryOptions {
|
|
8
8
|
/**
|
|
9
9
|
* Array of offer IDs to fetch
|
|
@@ -18,32 +18,27 @@ export interface UseOffersQueryOptions {
|
|
|
18
18
|
* Return URL for checkout sessions
|
|
19
19
|
*/
|
|
20
20
|
returnUrl?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Order ID to associate with the offers (required for payments)
|
|
23
|
+
*/
|
|
24
|
+
orderId?: string;
|
|
21
25
|
}
|
|
22
26
|
export interface UseOffersQueryResult {
|
|
23
27
|
offers: Offer[];
|
|
24
28
|
isLoading: boolean;
|
|
25
29
|
error: Error | null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
checkoutUrl: string;
|
|
31
|
-
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Pay for an offer
|
|
32
|
+
* Initializes a checkout session and pays it
|
|
33
|
+
*/
|
|
32
34
|
payOffer: (offerId: string, orderId?: string) => Promise<void>;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
getTotalSavings: () => number;
|
|
41
|
-
payWithCheckoutSession: (checkoutSessionId: string, orderId?: string) => Promise<void>;
|
|
42
|
-
initCheckoutSession: (offerId: string, orderId: string, customerId?: string) => Promise<{
|
|
43
|
-
checkoutSessionId: string;
|
|
44
|
-
}>;
|
|
45
|
-
getCheckoutSessionState: (offerId: string) => CheckoutSessionState | null;
|
|
46
|
-
initializeOfferCheckout: (offerId: string) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Preview an offer's price/summary
|
|
37
|
+
*/
|
|
38
|
+
preview: (offerId: string) => Promise<OrderSummary | OfferSummary | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Get available variants for a product in an offer
|
|
41
|
+
*/
|
|
47
42
|
getAvailableVariants: (offerId: string, productId: string) => {
|
|
48
43
|
variantId: string;
|
|
49
44
|
variantName: string;
|
|
@@ -53,13 +48,13 @@ export interface UseOffersQueryResult {
|
|
|
53
48
|
priceId: string;
|
|
54
49
|
currencyOptions: CurrencyOptions;
|
|
55
50
|
}[];
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Select a variant for a product in an offer
|
|
53
|
+
*/
|
|
54
|
+
selectVariant: (offerId: string, productId: string, variantId: string) => Promise<OrderSummary | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if variants are loading for a product
|
|
57
|
+
*/
|
|
58
58
|
isLoadingVariants: (offerId: string, productId: string) => boolean;
|
|
59
|
-
isUpdatingOrderSummary: (offerId: string) => boolean;
|
|
60
|
-
confirmPurchase: (offerId: string, options?: {
|
|
61
|
-
draft?: boolean;
|
|
62
|
-
returnUrl?: string;
|
|
63
|
-
}) => Promise<void>;
|
|
64
59
|
}
|
|
65
60
|
export declare function useOffersQuery(options?: UseOffersQueryOptions): UseOffersQueryResult;
|