@tagadapay/plugin-sdk 2.4.34 → 2.4.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +623 -623
- package/dist/data/iso3166.d.ts +28 -0
- package/dist/data/iso3166.js +114 -49
- package/dist/data/languages.d.ts +79 -0
- package/dist/data/languages.js +151 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/hooks/useFunnel.d.ts +70 -0
- package/dist/react/hooks/useFunnel.js +385 -0
- package/dist/react/hooks/useISOData.d.ts +23 -4
- package/dist/react/hooks/useISOData.js +67 -11
- package/dist/react/hooks/useLogin.js +1 -1
- package/dist/react/hooks/usePluginConfig.d.ts +23 -4
- package/dist/react/hooks/usePluginConfig.js +50 -9
- package/dist/react/index.d.ts +3 -6
- package/dist/react/index.js +2 -5
- package/dist/react/providers/TagadaProvider.d.ts +5 -2
- package/dist/react/providers/TagadaProvider.js +65 -72
- package/dist/react/types.d.ts +0 -109
- package/dist/react/utils/tokenStorage.js +0 -1
- package/package.json +83 -83
- package/dist/data/countries.d.ts +0 -50
- package/dist/data/countries.js +0 -38181
- package/dist/react/hooks/useCustomerInfos.d.ts +0 -15
- package/dist/react/hooks/useCustomerInfos.js +0 -54
- package/dist/react/hooks/useCustomerOrders.d.ts +0 -14
- package/dist/react/hooks/useCustomerOrders.js +0 -51
- package/dist/react/hooks/useCustomerSubscriptions.d.ts +0 -56
- package/dist/react/hooks/useCustomerSubscriptions.js +0 -77
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
|
+
/**
|
|
4
|
+
* React Hook for Funnel Navigation (Plugin SDK Version)
|
|
5
|
+
*
|
|
6
|
+
* Simplified funnel navigation using KV storage for funnel sessions.
|
|
7
|
+
* Integrates with TagadaPay authentication and plugin routing.
|
|
8
|
+
*/
|
|
9
|
+
export function useFunnel(options) {
|
|
10
|
+
const { auth, apiService, store } = useTagadaContext();
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
13
|
+
const [context, setContext] = useState(null);
|
|
14
|
+
const [initializationAttempted, setInitializationAttempted] = useState(false);
|
|
15
|
+
const [initializationError, setInitializationError] = useState(null);
|
|
16
|
+
const currentStepId = options.currentStepId || context?.currentStepId;
|
|
17
|
+
// Check for URL parameter overrides
|
|
18
|
+
const urlParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams();
|
|
19
|
+
const urlFunnelId = urlParams.get('funnelId') || undefined;
|
|
20
|
+
const effectiveFunnelId = urlFunnelId || options.funnelId;
|
|
21
|
+
// Removed checkExistingSession - now handled in single initialize call
|
|
22
|
+
/**
|
|
23
|
+
* Set funnel session cookie
|
|
24
|
+
*/
|
|
25
|
+
const setSessionCookie = useCallback((sessionId) => {
|
|
26
|
+
const maxAge = 24 * 60 * 60; // 24 hours
|
|
27
|
+
const expires = new Date(Date.now() + maxAge * 1000).toUTCString();
|
|
28
|
+
// Set cookie for same-domain scenarios
|
|
29
|
+
document.cookie = `tgd-funnel-session-id=${sessionId}; path=/; expires=${expires}; SameSite=Lax`;
|
|
30
|
+
console.log(`🍪 Funnel: Set session cookie: ${sessionId}`);
|
|
31
|
+
}, []);
|
|
32
|
+
/**
|
|
33
|
+
* Initialize funnel session
|
|
34
|
+
*/
|
|
35
|
+
const initializeSession = useCallback(async (entryStepId) => {
|
|
36
|
+
if (!auth.session?.customerId || !store?.id) {
|
|
37
|
+
throw new Error('Authentication required for funnel session');
|
|
38
|
+
}
|
|
39
|
+
setIsLoading(true);
|
|
40
|
+
setInitializationAttempted(true);
|
|
41
|
+
setInitializationError(null);
|
|
42
|
+
try {
|
|
43
|
+
// Check for existing session ID in URL parameters
|
|
44
|
+
let existingSessionId = urlParams.get('funnelSessionId') || undefined;
|
|
45
|
+
if (existingSessionId) {
|
|
46
|
+
console.log(`🍪 Funnel: Found session ID in URL params: ${existingSessionId}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Fallback to cookie for same-domain scenarios
|
|
50
|
+
const funnelSessionCookie = document.cookie
|
|
51
|
+
.split('; ')
|
|
52
|
+
.find(row => row.startsWith('tgd-funnel-session-id='));
|
|
53
|
+
existingSessionId = funnelSessionCookie ? funnelSessionCookie.split('=')[1] : undefined;
|
|
54
|
+
if (existingSessionId) {
|
|
55
|
+
console.log(`🍪 Funnel: Found session in cookie: ${existingSessionId}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!existingSessionId) {
|
|
59
|
+
console.log(`🍪 Funnel: No existing session found in URL params or cookie`);
|
|
60
|
+
}
|
|
61
|
+
// Use URL funnel ID if provided, otherwise use options (or undefined for backend fallback)
|
|
62
|
+
const effectiveFunnelId = urlFunnelId || options.funnelId;
|
|
63
|
+
// Send minimal CMS session data
|
|
64
|
+
const cmsSessionData = {
|
|
65
|
+
customerId: auth.session.customerId,
|
|
66
|
+
storeId: store.id,
|
|
67
|
+
sessionId: auth.session.sessionId,
|
|
68
|
+
accountId: auth.session.accountId
|
|
69
|
+
};
|
|
70
|
+
// Get current pathname with fallback
|
|
71
|
+
const currentPathname = typeof window !== 'undefined' ? window.location.pathname : '/';
|
|
72
|
+
console.log(`🍪 Funnel: Current pathname: "${currentPathname}"`);
|
|
73
|
+
// Call API to initialize session - backend will restore existing or create new
|
|
74
|
+
const requestBody = {
|
|
75
|
+
cmsSession: cmsSessionData,
|
|
76
|
+
entryStepId, // Optional override
|
|
77
|
+
existingSessionId // Pass existing session ID from URL or cookie
|
|
78
|
+
};
|
|
79
|
+
// Only include funnelId if it's provided (for backend fallback)
|
|
80
|
+
if (effectiveFunnelId) {
|
|
81
|
+
requestBody.funnelId = effectiveFunnelId;
|
|
82
|
+
}
|
|
83
|
+
const response = await apiService.fetch('/api/v1/funnel/initialize', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
body: requestBody
|
|
86
|
+
});
|
|
87
|
+
if (response.success && response.context) {
|
|
88
|
+
// Backend returns the initialized session
|
|
89
|
+
setContext(response.context);
|
|
90
|
+
setIsInitialized(true);
|
|
91
|
+
// Set session cookie for persistence across page reloads
|
|
92
|
+
setSessionCookie(response.context.sessionId);
|
|
93
|
+
console.log(`🍪 Funnel: Initialized new session for funnel ${effectiveFunnelId || 'default'}`, response.context);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
throw new Error(response.error || 'Failed to initialize funnel session');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
101
|
+
setInitializationError(errorObj);
|
|
102
|
+
console.error('Error initializing funnel session:', error);
|
|
103
|
+
if (options.onError) {
|
|
104
|
+
options.onError(errorObj);
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
setIsLoading(false);
|
|
110
|
+
}
|
|
111
|
+
}, [auth.session?.customerId, store?.id, effectiveFunnelId, apiService, options.onError]);
|
|
112
|
+
/**
|
|
113
|
+
* Navigate to next step based on event
|
|
114
|
+
*/
|
|
115
|
+
const next = useCallback(async (event) => {
|
|
116
|
+
if (!context) {
|
|
117
|
+
throw new Error('Funnel session not initialized');
|
|
118
|
+
}
|
|
119
|
+
if (!context.sessionId) {
|
|
120
|
+
throw new Error('Funnel session ID missing - session may be corrupted');
|
|
121
|
+
}
|
|
122
|
+
setIsLoading(true);
|
|
123
|
+
try {
|
|
124
|
+
console.log(`🍪 Funnel: Navigating with session ID: ${context.sessionId}`);
|
|
125
|
+
const response = await apiService.fetch('/api/v1/funnel/navigate', {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
body: {
|
|
128
|
+
sessionId: context.sessionId, // Send session ID for navigation
|
|
129
|
+
event: {
|
|
130
|
+
type: event.type,
|
|
131
|
+
data: event.data,
|
|
132
|
+
timestamp: event.timestamp?.toISOString() || new Date().toISOString()
|
|
133
|
+
},
|
|
134
|
+
contextUpdates: {
|
|
135
|
+
lastActivityAt: Date.now()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (response.success && response.result) {
|
|
140
|
+
const result = response.result;
|
|
141
|
+
// Update local context
|
|
142
|
+
const newContext = {
|
|
143
|
+
...context,
|
|
144
|
+
currentStepId: result.stepId,
|
|
145
|
+
previousStepId: context.currentStepId,
|
|
146
|
+
lastActivityAt: Date.now(),
|
|
147
|
+
metadata: {
|
|
148
|
+
...context.metadata,
|
|
149
|
+
lastEvent: event.type,
|
|
150
|
+
lastTransition: new Date().toISOString()
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
setContext(newContext);
|
|
154
|
+
// Create typed navigation result
|
|
155
|
+
const navigationResult = {
|
|
156
|
+
stepId: result.stepId,
|
|
157
|
+
action: {
|
|
158
|
+
type: 'redirect', // Default action type
|
|
159
|
+
url: result.url
|
|
160
|
+
},
|
|
161
|
+
context: newContext,
|
|
162
|
+
tracking: result.tracking
|
|
163
|
+
};
|
|
164
|
+
// Handle navigation callback with override capability
|
|
165
|
+
let shouldPerformDefaultNavigation = true;
|
|
166
|
+
if (options.onNavigate) {
|
|
167
|
+
const callbackResult = options.onNavigate(navigationResult);
|
|
168
|
+
if (callbackResult === false) {
|
|
169
|
+
shouldPerformDefaultNavigation = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Perform default navigation if not overridden
|
|
173
|
+
if (shouldPerformDefaultNavigation && navigationResult.action.url) {
|
|
174
|
+
// Add URL parameters for cross-domain session continuity
|
|
175
|
+
const urlWithParams = addSessionParams(navigationResult.action.url, newContext.sessionId, effectiveFunnelId || options.funnelId);
|
|
176
|
+
const updatedAction = { ...navigationResult.action, url: urlWithParams };
|
|
177
|
+
performNavigation(updatedAction);
|
|
178
|
+
}
|
|
179
|
+
console.log(`🍪 Funnel: Navigated from ${context.currentStepId} to ${result.stepId}`);
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
throw new Error(response.error || 'Navigation failed');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
console.error('Funnel navigation error:', error);
|
|
188
|
+
if (options.onError) {
|
|
189
|
+
options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
setIsLoading(false);
|
|
195
|
+
}
|
|
196
|
+
}, [context, apiService, options.onNavigate, options.onError]);
|
|
197
|
+
/**
|
|
198
|
+
* Go directly to a specific step (bypass conditions)
|
|
199
|
+
*/
|
|
200
|
+
const goToStep = useCallback(async (stepId) => {
|
|
201
|
+
return next({
|
|
202
|
+
type: 'direct_navigation',
|
|
203
|
+
data: { targetStepId: stepId },
|
|
204
|
+
timestamp: new Date()
|
|
205
|
+
});
|
|
206
|
+
}, [next]);
|
|
207
|
+
/**
|
|
208
|
+
* Update funnel context
|
|
209
|
+
*/
|
|
210
|
+
const updateContext = useCallback(async (updates) => {
|
|
211
|
+
if (!context) {
|
|
212
|
+
throw new Error('Funnel session not initialized');
|
|
213
|
+
}
|
|
214
|
+
setIsLoading(true);
|
|
215
|
+
try {
|
|
216
|
+
const response = await apiService.fetch('/api/v1/funnel/context', {
|
|
217
|
+
method: 'PATCH',
|
|
218
|
+
body: {
|
|
219
|
+
contextUpdates: updates
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
if (response.success) {
|
|
223
|
+
const updatedContext = {
|
|
224
|
+
...context,
|
|
225
|
+
...updates,
|
|
226
|
+
lastActivityAt: Date.now()
|
|
227
|
+
};
|
|
228
|
+
setContext(updatedContext);
|
|
229
|
+
console.log(`🍪 Funnel: Updated context for step ${context.currentStepId}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
throw new Error(response.error || 'Context update failed');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
console.error('Error updating funnel context:', error);
|
|
237
|
+
if (options.onError) {
|
|
238
|
+
options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
239
|
+
}
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
setIsLoading(false);
|
|
244
|
+
}
|
|
245
|
+
}, [context, apiService, options.onError]);
|
|
246
|
+
/**
|
|
247
|
+
* End current funnel session
|
|
248
|
+
*/
|
|
249
|
+
const endSession = useCallback(async () => {
|
|
250
|
+
if (!context) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
await apiService.fetch(`/api/v1/funnel/session/${context.sessionId}`, {
|
|
255
|
+
method: 'DELETE'
|
|
256
|
+
});
|
|
257
|
+
setContext(null);
|
|
258
|
+
setIsInitialized(false);
|
|
259
|
+
console.log(`🍪 Funnel: Ended session ${context.sessionId}`);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
console.error('Error ending funnel session:', error);
|
|
263
|
+
// Don't throw here - session ending is best effort
|
|
264
|
+
}
|
|
265
|
+
}, [context, apiService]);
|
|
266
|
+
/**
|
|
267
|
+
* Add session parameters to URL for cross-domain continuity
|
|
268
|
+
*/
|
|
269
|
+
const addSessionParams = useCallback((url, sessionId, funnelId) => {
|
|
270
|
+
try {
|
|
271
|
+
const urlObj = new URL(url);
|
|
272
|
+
urlObj.searchParams.set('funnelSessionId', sessionId);
|
|
273
|
+
if (funnelId) {
|
|
274
|
+
urlObj.searchParams.set('funnelId', funnelId);
|
|
275
|
+
}
|
|
276
|
+
const urlWithParams = urlObj.toString();
|
|
277
|
+
console.log(`🍪 Funnel: Added session params to URL: ${url} → ${urlWithParams}`);
|
|
278
|
+
return urlWithParams;
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.warn('Failed to add session params to URL:', error);
|
|
282
|
+
return url; // Return original URL if parsing fails
|
|
283
|
+
}
|
|
284
|
+
}, []);
|
|
285
|
+
/**
|
|
286
|
+
* Perform navigation based on action type
|
|
287
|
+
*/
|
|
288
|
+
const performNavigation = useCallback((action) => {
|
|
289
|
+
if (!action.url)
|
|
290
|
+
return;
|
|
291
|
+
// Handle relative URLs by making them absolute
|
|
292
|
+
let targetUrl = action.url;
|
|
293
|
+
if (targetUrl.startsWith('/') && !targetUrl.startsWith('//')) {
|
|
294
|
+
// Relative URL - use current origin
|
|
295
|
+
targetUrl = window.location.origin + targetUrl;
|
|
296
|
+
}
|
|
297
|
+
switch (action.type) {
|
|
298
|
+
case 'redirect':
|
|
299
|
+
console.log(`🍪 Funnel: Redirecting to ${targetUrl}`);
|
|
300
|
+
window.location.href = targetUrl;
|
|
301
|
+
break;
|
|
302
|
+
case 'replace':
|
|
303
|
+
console.log(`🍪 Funnel: Replacing current page with ${targetUrl}`);
|
|
304
|
+
window.location.replace(targetUrl);
|
|
305
|
+
break;
|
|
306
|
+
case 'push':
|
|
307
|
+
console.log(`🍪 Funnel: Pushing to history: ${targetUrl}`);
|
|
308
|
+
window.history.pushState({}, '', targetUrl);
|
|
309
|
+
// Trigger a popstate event to update React Router
|
|
310
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
311
|
+
break;
|
|
312
|
+
case 'external':
|
|
313
|
+
console.log(`🍪 Funnel: Opening external URL: ${targetUrl}`);
|
|
314
|
+
window.open(targetUrl, '_blank');
|
|
315
|
+
break;
|
|
316
|
+
case 'none':
|
|
317
|
+
console.log(`🍪 Funnel: No navigation action required`);
|
|
318
|
+
break;
|
|
319
|
+
default:
|
|
320
|
+
console.warn(`🍪 Funnel: Unknown navigation action type: ${action.type}`);
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}, []);
|
|
324
|
+
/**
|
|
325
|
+
* Retry initialization after fixing errors
|
|
326
|
+
*/
|
|
327
|
+
const retryInitialization = useCallback(async () => {
|
|
328
|
+
setInitializationAttempted(false);
|
|
329
|
+
setInitializationError(null);
|
|
330
|
+
await initializeSession();
|
|
331
|
+
}, [initializeSession]);
|
|
332
|
+
// Auto-initialize if requested and not already initialized (prevent infinite loops)
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
if (options.autoInitialize &&
|
|
335
|
+
!isInitialized &&
|
|
336
|
+
!initializationAttempted &&
|
|
337
|
+
!initializationError &&
|
|
338
|
+
auth.session?.customerId &&
|
|
339
|
+
store?.id) {
|
|
340
|
+
console.log('🍪 Funnel: Auto-initializing session...');
|
|
341
|
+
initializeSession().catch(error => {
|
|
342
|
+
console.error('Auto-initialization failed - will not retry:', error);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}, [
|
|
346
|
+
options.autoInitialize,
|
|
347
|
+
isInitialized,
|
|
348
|
+
initializationAttempted,
|
|
349
|
+
initializationError,
|
|
350
|
+
auth.session?.customerId,
|
|
351
|
+
store?.id,
|
|
352
|
+
initializeSession
|
|
353
|
+
]);
|
|
354
|
+
return {
|
|
355
|
+
next,
|
|
356
|
+
goToStep,
|
|
357
|
+
updateContext,
|
|
358
|
+
currentStep: {
|
|
359
|
+
id: currentStepId || 'unknown'
|
|
360
|
+
},
|
|
361
|
+
context,
|
|
362
|
+
isLoading,
|
|
363
|
+
isInitialized,
|
|
364
|
+
initializeSession,
|
|
365
|
+
endSession,
|
|
366
|
+
retryInitialization,
|
|
367
|
+
initializationError
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Simplified funnel hook for basic step tracking
|
|
372
|
+
*/
|
|
373
|
+
export function useSimpleFunnel(funnelId, initialStepId) {
|
|
374
|
+
const funnel = useFunnel({
|
|
375
|
+
funnelId,
|
|
376
|
+
currentStepId: initialStepId,
|
|
377
|
+
autoInitialize: true
|
|
378
|
+
});
|
|
379
|
+
return {
|
|
380
|
+
currentStepId: funnel.currentStep.id,
|
|
381
|
+
next: funnel.next,
|
|
382
|
+
goToStep: funnel.goToStep,
|
|
383
|
+
isLoading: funnel.isLoading
|
|
384
|
+
};
|
|
385
|
+
}
|
|
@@ -14,29 +14,48 @@ export interface UseISODataResult {
|
|
|
14
14
|
getRegions: (countryCode: string) => ISORegion[];
|
|
15
15
|
findRegion: (countryCode: string, regionCode: string) => ISORegion | null;
|
|
16
16
|
mapGoogleToISO: (googleState: string, googleStateLong: string, countryCode: string) => ISORegion | null;
|
|
17
|
+
isLanguageLoaded: boolean;
|
|
18
|
+
registeredLanguages: SupportedLanguage[];
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
|
-
* React hook for accessing ISO3166 countries and regions data
|
|
21
|
+
* React hook for accessing ISO3166 countries and regions data with dynamic language loading
|
|
20
22
|
* @param language - Language code (supports: en, ru, de, fr, es, zh, hi, pt, ja, ar, it, he)
|
|
23
|
+
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
21
24
|
* @param disputeSetting - Territorial dispute perspective (currently only UN is supported)
|
|
22
25
|
* @returns Object with countries data and helper functions
|
|
23
26
|
*/
|
|
24
|
-
export declare function useISOData(language?: SupportedLanguage, disputeSetting?: string): UseISODataResult;
|
|
27
|
+
export declare function useISOData(language?: SupportedLanguage, autoImport?: boolean, disputeSetting?: string): UseISODataResult;
|
|
25
28
|
/**
|
|
26
29
|
* Get available languages for ISO data
|
|
27
30
|
*/
|
|
28
31
|
export declare function getAvailableLanguages(): SupportedLanguage[];
|
|
32
|
+
/**
|
|
33
|
+
* Hook to manually import and register a language
|
|
34
|
+
* @param language - Language code to import
|
|
35
|
+
* @returns Object with loading state and error handling
|
|
36
|
+
*/
|
|
37
|
+
export declare function useLanguageImport(language: SupportedLanguage): {
|
|
38
|
+
importLanguage: () => Promise<void>;
|
|
39
|
+
isLoading: boolean;
|
|
40
|
+
error: string | null;
|
|
41
|
+
isRegistered: boolean;
|
|
42
|
+
};
|
|
29
43
|
/**
|
|
30
44
|
* Get list of countries as options for select components
|
|
45
|
+
* @param language - Language code (defaults to 'en')
|
|
46
|
+
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
31
47
|
*/
|
|
32
|
-
export declare function useCountryOptions(language?: SupportedLanguage): {
|
|
48
|
+
export declare function useCountryOptions(language?: SupportedLanguage, autoImport?: boolean): {
|
|
33
49
|
value: string;
|
|
34
50
|
label: string;
|
|
35
51
|
}[];
|
|
36
52
|
/**
|
|
37
53
|
* Get list of regions/states for a country as options for select components
|
|
54
|
+
* @param countryCode - ISO country code
|
|
55
|
+
* @param language - Language code (defaults to 'en')
|
|
56
|
+
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
38
57
|
*/
|
|
39
|
-
export declare function useRegionOptions(countryCode: string, language?: SupportedLanguage): {
|
|
58
|
+
export declare function useRegionOptions(countryCode: string, language?: SupportedLanguage, autoImport?: boolean): {
|
|
40
59
|
value: string;
|
|
41
60
|
label: string;
|
|
42
61
|
}[];
|
|
@@ -1,16 +1,32 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useMemo, useEffect, useState, useCallback } from 'react';
|
|
2
2
|
// Import the pre-built ISO data functions
|
|
3
|
-
import { getCountries, getStatesForCountry } from '../../data/iso3166';
|
|
3
|
+
import { getCountries, getStatesForCountry, importLanguage, isLanguageRegistered, getRegisteredLanguages } from '../../data/iso3166';
|
|
4
4
|
/**
|
|
5
|
-
* React hook for accessing ISO3166 countries and regions data
|
|
5
|
+
* React hook for accessing ISO3166 countries and regions data with dynamic language loading
|
|
6
6
|
* @param language - Language code (supports: en, ru, de, fr, es, zh, hi, pt, ja, ar, it, he)
|
|
7
|
+
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
7
8
|
* @param disputeSetting - Territorial dispute perspective (currently only UN is supported)
|
|
8
9
|
* @returns Object with countries data and helper functions
|
|
9
10
|
*/
|
|
10
|
-
export function useISOData(language = 'en', disputeSetting = 'UN') {
|
|
11
|
+
export function useISOData(language = 'en', autoImport = true, disputeSetting = 'UN') {
|
|
12
|
+
const [isLanguageLoaded, setIsLanguageLoaded] = useState(isLanguageRegistered(language));
|
|
13
|
+
const [registeredLanguages, setRegisteredLanguages] = useState(getRegisteredLanguages);
|
|
14
|
+
// Auto-import language if not registered and autoImport is true
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!isLanguageRegistered(language) && autoImport && language !== 'en') {
|
|
17
|
+
importLanguage(language)
|
|
18
|
+
.then(() => {
|
|
19
|
+
setIsLanguageLoaded(true);
|
|
20
|
+
setRegisteredLanguages(getRegisteredLanguages());
|
|
21
|
+
})
|
|
22
|
+
.catch((error) => {
|
|
23
|
+
console.error(`Failed to auto-import language ${language}:`, error);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}, [language, autoImport]);
|
|
11
27
|
const data = useMemo(() => {
|
|
12
28
|
try {
|
|
13
|
-
// Get countries from pre-built data with language support
|
|
29
|
+
// Get countries from pre-built data with language support
|
|
14
30
|
const countriesArray = getCountries(language);
|
|
15
31
|
// Transform to our expected format (Record<string, ISOCountry>)
|
|
16
32
|
const countries = {};
|
|
@@ -66,6 +82,8 @@ export function useISOData(language = 'en', disputeSetting = 'UN') {
|
|
|
66
82
|
getRegions,
|
|
67
83
|
findRegion,
|
|
68
84
|
mapGoogleToISO,
|
|
85
|
+
isLanguageLoaded,
|
|
86
|
+
registeredLanguages,
|
|
69
87
|
};
|
|
70
88
|
}
|
|
71
89
|
catch (error) {
|
|
@@ -75,23 +93,58 @@ export function useISOData(language = 'en', disputeSetting = 'UN') {
|
|
|
75
93
|
getRegions: () => [],
|
|
76
94
|
findRegion: () => null,
|
|
77
95
|
mapGoogleToISO: () => null,
|
|
96
|
+
isLanguageLoaded,
|
|
97
|
+
registeredLanguages,
|
|
78
98
|
};
|
|
79
99
|
}
|
|
80
|
-
}, [language, disputeSetting]);
|
|
100
|
+
}, [language, disputeSetting, isLanguageLoaded, registeredLanguages]);
|
|
81
101
|
return data;
|
|
82
102
|
}
|
|
83
103
|
/**
|
|
84
104
|
* Get available languages for ISO data
|
|
85
105
|
*/
|
|
86
106
|
export function getAvailableLanguages() {
|
|
87
|
-
// Return all
|
|
107
|
+
// Return all available languages (not just registered ones)
|
|
88
108
|
return ['en', 'ru', 'de', 'fr', 'es', 'zh', 'hi', 'pt', 'ja', 'ar', 'it', 'he'];
|
|
89
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Hook to manually import and register a language
|
|
112
|
+
* @param language - Language code to import
|
|
113
|
+
* @returns Object with loading state and error handling
|
|
114
|
+
*/
|
|
115
|
+
export function useLanguageImport(language) {
|
|
116
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
117
|
+
const [error, setError] = useState(null);
|
|
118
|
+
const importLanguageData = useCallback(async () => {
|
|
119
|
+
if (isLanguageRegistered(language)) {
|
|
120
|
+
return; // Already registered
|
|
121
|
+
}
|
|
122
|
+
setIsLoading(true);
|
|
123
|
+
setError(null);
|
|
124
|
+
try {
|
|
125
|
+
await importLanguage(language);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
setError(err instanceof Error ? err.message : 'Failed to import language');
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
setIsLoading(false);
|
|
132
|
+
}
|
|
133
|
+
}, [language]);
|
|
134
|
+
return {
|
|
135
|
+
importLanguage: importLanguageData,
|
|
136
|
+
isLoading,
|
|
137
|
+
error,
|
|
138
|
+
isRegistered: isLanguageRegistered(language),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
90
141
|
/**
|
|
91
142
|
* Get list of countries as options for select components
|
|
143
|
+
* @param language - Language code (defaults to 'en')
|
|
144
|
+
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
92
145
|
*/
|
|
93
|
-
export function useCountryOptions(language = 'en') {
|
|
94
|
-
const { countries } = useISOData(language);
|
|
146
|
+
export function useCountryOptions(language = 'en', autoImport = true) {
|
|
147
|
+
const { countries } = useISOData(language, autoImport);
|
|
95
148
|
return useMemo(() => {
|
|
96
149
|
return Object.entries(countries)
|
|
97
150
|
.map(([code, country]) => ({
|
|
@@ -103,9 +156,12 @@ export function useCountryOptions(language = 'en') {
|
|
|
103
156
|
}
|
|
104
157
|
/**
|
|
105
158
|
* Get list of regions/states for a country as options for select components
|
|
159
|
+
* @param countryCode - ISO country code
|
|
160
|
+
* @param language - Language code (defaults to 'en')
|
|
161
|
+
* @param autoImport - Whether to automatically import the language if not registered (default: true)
|
|
106
162
|
*/
|
|
107
|
-
export function useRegionOptions(countryCode, language = 'en') {
|
|
108
|
-
const { getRegions } = useISOData(language);
|
|
163
|
+
export function useRegionOptions(countryCode, language = 'en', autoImport = true) {
|
|
164
|
+
const { getRegions } = useISOData(language, autoImport);
|
|
109
165
|
return useMemo(() => {
|
|
110
166
|
if (!countryCode)
|
|
111
167
|
return [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
2
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
|
-
import { setClientToken } from '../utils/tokenStorage';
|
|
4
3
|
import { usePluginConfig } from './usePluginConfig';
|
|
4
|
+
import { setClientToken } from '../utils/tokenStorage';
|
|
5
5
|
export function useLogin() {
|
|
6
6
|
const [isLoading, setIsLoading] = useState(false);
|
|
7
7
|
const [error, setError] = useState(null);
|
|
@@ -2,8 +2,21 @@
|
|
|
2
2
|
* Plugin Configuration Hook
|
|
3
3
|
*
|
|
4
4
|
* Professional SDK approach to access plugin configuration:
|
|
5
|
+
* - Raw Config: Direct configuration passed via TagadaProvider (highest priority)
|
|
5
6
|
* - Store ID, Account ID, Base Path: from .local.json (dev) or headers (production)
|
|
6
7
|
* - Deployment Config: from config/*.json (dev) or meta tags (production)
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* 1. Raw config (recommended for programmatic usage):
|
|
11
|
+
* <TagadaProvider rawPluginConfig={{ storeId: 'store_123', config: {...} }}>
|
|
12
|
+
*
|
|
13
|
+
* 2. File-based config (for development):
|
|
14
|
+
* - .local.json for store/account info
|
|
15
|
+
* - config/default.tgd.json for deployment config
|
|
16
|
+
*
|
|
17
|
+
* 3. Production config:
|
|
18
|
+
* - Headers for store/account info
|
|
19
|
+
* - Meta tags for deployment config
|
|
7
20
|
*/
|
|
8
21
|
export interface PluginConfig<TConfig = Record<string, any>> {
|
|
9
22
|
storeId?: string;
|
|
@@ -11,6 +24,12 @@ export interface PluginConfig<TConfig = Record<string, any>> {
|
|
|
11
24
|
basePath?: string;
|
|
12
25
|
config?: TConfig;
|
|
13
26
|
}
|
|
27
|
+
export interface RawPluginConfig<TConfig = Record<string, any>> {
|
|
28
|
+
storeId?: string;
|
|
29
|
+
accountId?: string;
|
|
30
|
+
basePath?: string;
|
|
31
|
+
config?: TConfig;
|
|
32
|
+
}
|
|
14
33
|
export interface LocalDevConfig {
|
|
15
34
|
storeId: string;
|
|
16
35
|
accountId: string;
|
|
@@ -18,9 +37,9 @@ export interface LocalDevConfig {
|
|
|
18
37
|
}
|
|
19
38
|
/**
|
|
20
39
|
* Load plugin configuration (cached)
|
|
21
|
-
* Tries local dev config
|
|
40
|
+
* Tries raw config first, then local dev config, then production config
|
|
22
41
|
*/
|
|
23
|
-
export declare const loadPluginConfig: (configVariant?: string) => Promise<PluginConfig>;
|
|
42
|
+
export declare const loadPluginConfig: (configVariant?: string, rawConfig?: RawPluginConfig) => Promise<PluginConfig>;
|
|
24
43
|
/**
|
|
25
44
|
* Main hook for plugin configuration
|
|
26
45
|
* Gets config from TagadaProvider context (no parameters needed)
|
|
@@ -42,7 +61,7 @@ export declare const useBasePath: () => {
|
|
|
42
61
|
/**
|
|
43
62
|
* Get cached config directly (for non-React usage)
|
|
44
63
|
*/
|
|
45
|
-
export declare const getPluginConfig: (configVariant?: string) => Promise<PluginConfig>;
|
|
64
|
+
export declare const getPluginConfig: (configVariant?: string, rawConfig?: RawPluginConfig) => Promise<PluginConfig>;
|
|
46
65
|
/**
|
|
47
66
|
* Clear the config cache (useful for testing)
|
|
48
67
|
*/
|
|
@@ -50,4 +69,4 @@ export declare const clearPluginConfigCache: () => void;
|
|
|
50
69
|
/**
|
|
51
70
|
* Development helper to log current configuration
|
|
52
71
|
*/
|
|
53
|
-
export declare const debugPluginConfig: (configVariant?: string) => Promise<void>;
|
|
72
|
+
export declare const debugPluginConfig: (configVariant?: string, rawConfig?: RawPluginConfig) => Promise<void>;
|