@tagadapay/plugin-sdk 3.1.11 → 3.1.12
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 +1129 -1129
- package/build-cdn.js +113 -228
- package/dist/external-tracker.js +2 -3
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +3 -3
- package/dist/react/hooks/useShippingRates.d.ts +6 -0
- package/dist/react/hooks/useShippingRates.js +38 -0
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/react/services/apiService.d.ts +21 -0
- package/dist/react/services/apiService.js +10 -0
- package/dist/v2/core/funnelClient.d.ts +14 -15
- package/dist/v2/core/funnelClient.js +1 -1
- package/dist/v2/core/resources/shippingRates.d.ts +15 -0
- package/dist/v2/core/resources/shippingRates.js +11 -0
- package/dist/v2/core/utils/currency.d.ts +0 -14
- package/dist/v2/core/utils/currency.js +0 -40
- package/dist/v2/core/utils/index.d.ts +0 -1
- package/dist/v2/core/utils/index.js +0 -2
- package/dist/v2/core/utils/pluginConfig.d.ts +0 -8
- package/dist/v2/core/utils/pluginConfig.js +0 -28
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/index.d.ts +1 -1
- package/dist/v2/react/components/ApplePayButton.js +13 -4
- package/dist/v2/react/components/FunnelScriptInjector.js +30 -30
- package/dist/v2/react/hooks/useFunnel.d.ts +1 -2
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +82 -33
- package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -0
- package/dist/v2/react/hooks/usePixelTracking.js +108 -0
- package/dist/v2/react/hooks/useShippingRatesQuery.d.ts +6 -0
- package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -1
- package/dist/v2/react/hooks/useStepConfig.d.ts +2 -8
- package/dist/v2/react/hooks/useStepConfig.js +1 -1
- package/dist/v2/react/providers/TagadaProvider.js +5 -5
- package/dist/v2/standalone/index.js +1 -1
- package/package.json +112 -112
- package/dist/tagada-sdk.js +0 -10166
- package/dist/tagada-sdk.min.js +0 -48
- package/dist/tagada-sdk.min.js.map +0 -7
|
@@ -41,11 +41,20 @@ export const ApplePayButton = ({ checkout, onSuccess, onError, onCancel }) => {
|
|
|
41
41
|
// Check Apple Pay availability (matches CMS pattern - useApplePayAvailable hook)
|
|
42
42
|
useEffect(() => {
|
|
43
43
|
const addExpress = () => handleAddExpressId('apple_pay');
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
try {
|
|
45
|
+
// Apple Pay requires a secure context (HTTPS). On HTTP (like localhost),
|
|
46
|
+
// calling canMakePayments() throws InvalidAccessError
|
|
47
|
+
if (window?.ApplePaySession && ApplePaySession.canMakePayments()) {
|
|
48
|
+
setIsApplePayAvailable(true);
|
|
49
|
+
addExpress();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
setIsApplePayAvailable(false);
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
|
-
|
|
55
|
+
catch (error) {
|
|
56
|
+
// Likely "Trying to start an Apple Pay session from an insecure document"
|
|
57
|
+
console.warn('[ApplePay] Apple Pay not available:', error);
|
|
49
58
|
setIsApplePayAvailable(false);
|
|
50
59
|
}
|
|
51
60
|
}, [handleAddExpressId]);
|
|
@@ -199,27 +199,27 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
199
199
|
existingScript.remove();
|
|
200
200
|
}
|
|
201
201
|
// Wrap script content with error handling and context checks
|
|
202
|
-
const wrappedScript = `
|
|
203
|
-
(function() {
|
|
204
|
-
try {
|
|
205
|
-
// Check if we have basic DOM access
|
|
206
|
-
if (typeof document === 'undefined') {
|
|
207
|
-
console.error('[TagadaPay] Document not available');
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Check if we have Tagada
|
|
212
|
-
if (!window.Tagada) {
|
|
213
|
-
console.error('[TagadaPay] Tagada not available');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Execute the original script
|
|
218
|
-
${scriptBody}
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.error('[TagadaPay] Script execution error:', error);
|
|
221
|
-
}
|
|
222
|
-
})();
|
|
202
|
+
const wrappedScript = `
|
|
203
|
+
(function() {
|
|
204
|
+
try {
|
|
205
|
+
// Check if we have basic DOM access
|
|
206
|
+
if (typeof document === 'undefined') {
|
|
207
|
+
console.error('[TagadaPay] Document not available');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if we have Tagada
|
|
212
|
+
if (!window.Tagada) {
|
|
213
|
+
console.error('[TagadaPay] Tagada not available');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Execute the original script
|
|
218
|
+
${scriptBody}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[TagadaPay] Script execution error:', error);
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
223
223
|
`;
|
|
224
224
|
// Create and inject new script element
|
|
225
225
|
const scriptElement = document.createElement('script');
|
|
@@ -273,15 +273,15 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
273
273
|
if (!scriptBody)
|
|
274
274
|
return;
|
|
275
275
|
// Wrap script content with error handling
|
|
276
|
-
const wrappedScript = `
|
|
277
|
-
(function() {
|
|
278
|
-
try {
|
|
279
|
-
// Script: ${script.name}
|
|
280
|
-
${scriptBody}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
console.error('[TagadaPay] StepConfig script "${script.name}" error:', error);
|
|
283
|
-
}
|
|
284
|
-
})();
|
|
276
|
+
const wrappedScript = `
|
|
277
|
+
(function() {
|
|
278
|
+
try {
|
|
279
|
+
// Script: ${script.name}
|
|
280
|
+
${scriptBody}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('[TagadaPay] StepConfig script "${script.name}" error:', error);
|
|
283
|
+
}
|
|
284
|
+
})();
|
|
285
285
|
`;
|
|
286
286
|
// Create script element
|
|
287
287
|
const scriptElement = document.createElement('script');
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* const paymentFlowId = stepConfig.paymentFlowId;
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
import { FunnelState, GTMTrackingConfig,
|
|
22
|
+
import { FunnelState, GTMTrackingConfig, PixelTrackingConfig, RuntimeStepConfig, SnapchatTrackingConfig, TrackingProvider } from '../../core/funnelClient';
|
|
23
23
|
import { FunnelAction, FunnelNavigationResult, SimpleFunnelContext } from '../../core/resources/funnel';
|
|
24
24
|
/**
|
|
25
25
|
* Step configuration from HTML injection (for current step/variant)
|
|
@@ -52,7 +52,6 @@ export interface StepConfigValue {
|
|
|
52
52
|
[TrackingProvider.FACEBOOK]?: PixelTrackingConfig[];
|
|
53
53
|
[TrackingProvider.TIKTOK]?: PixelTrackingConfig[];
|
|
54
54
|
[TrackingProvider.SNAPCHAT]?: SnapchatTrackingConfig[];
|
|
55
|
-
[TrackingProvider.META_CONVERSION]?: MetaConversionTrackingConfig[];
|
|
56
55
|
[TrackingProvider.GTM]?: GTMTrackingConfig[];
|
|
57
56
|
};
|
|
58
57
|
}
|
|
@@ -165,6 +165,15 @@ export function useGoogleAutocomplete(options) {
|
|
|
165
165
|
postalCode: '',
|
|
166
166
|
fullStreetAddress: '',
|
|
167
167
|
};
|
|
168
|
+
// Temporary variables for comprehensive address component extraction
|
|
169
|
+
let premise = '';
|
|
170
|
+
let neighborhood = '';
|
|
171
|
+
let sublocality5 = '';
|
|
172
|
+
let sublocality4 = '';
|
|
173
|
+
let sublocality3 = '';
|
|
174
|
+
let sublocality2 = '';
|
|
175
|
+
let sublocality1 = '';
|
|
176
|
+
let postalTown = '';
|
|
168
177
|
place.address_components?.forEach((component) => {
|
|
169
178
|
const types = component.types;
|
|
170
179
|
if (types.includes('subpremise')) {
|
|
@@ -174,19 +183,47 @@ export function useGoogleAutocomplete(options) {
|
|
|
174
183
|
if (types.includes('street_number')) {
|
|
175
184
|
extracted.streetNumber = component.long_name;
|
|
176
185
|
}
|
|
186
|
+
if (types.includes('premise')) {
|
|
187
|
+
// Building number (used in Asian addresses instead of street_number)
|
|
188
|
+
premise = component.long_name;
|
|
189
|
+
}
|
|
177
190
|
if (types.includes('route')) {
|
|
178
191
|
extracted.route = component.long_name;
|
|
179
192
|
}
|
|
193
|
+
if (types.includes('neighborhood')) {
|
|
194
|
+
// Neighborhood (alternative to sublocality in some regions)
|
|
195
|
+
neighborhood = component.long_name;
|
|
196
|
+
}
|
|
197
|
+
if (types.includes('sublocality_level_5')) {
|
|
198
|
+
// Fifth-level sublocality (most granular)
|
|
199
|
+
sublocality5 = component.long_name;
|
|
200
|
+
}
|
|
201
|
+
if (types.includes('sublocality_level_4')) {
|
|
202
|
+
// Street name in Asian addresses (e.g., Korean: "Yulgok-ro 27-gil", Japanese block)
|
|
203
|
+
sublocality4 = component.long_name;
|
|
204
|
+
}
|
|
205
|
+
if (types.includes('sublocality_level_3')) {
|
|
206
|
+
// Neighborhood (e.g., Japanese chōme: "3-chōme")
|
|
207
|
+
sublocality3 = component.long_name;
|
|
208
|
+
}
|
|
209
|
+
if (types.includes('sublocality_level_2')) {
|
|
210
|
+
// Second-level sublocality (e.g., Japanese area: "Hirano")
|
|
211
|
+
sublocality2 = component.long_name;
|
|
212
|
+
}
|
|
213
|
+
if (types.includes('sublocality_level_1')) {
|
|
214
|
+
// District (fallback for city in some Asian countries)
|
|
215
|
+
sublocality1 = component.long_name;
|
|
216
|
+
}
|
|
180
217
|
if (types.includes('locality')) {
|
|
181
218
|
extracted.locality = component.long_name;
|
|
182
219
|
}
|
|
220
|
+
if (types.includes('postal_town')) {
|
|
221
|
+
// Postal town (used in UK, Ireland instead of locality)
|
|
222
|
+
postalTown = component.long_name;
|
|
223
|
+
}
|
|
183
224
|
if (types.includes('administrative_area_level_2')) {
|
|
184
225
|
extracted.administrativeAreaLevel2 = component.short_name;
|
|
185
226
|
extracted.administrativeAreaLevel2Long = component.long_name;
|
|
186
|
-
// Use level_2 as fallback for locality if locality is not set
|
|
187
|
-
if (!extracted.locality) {
|
|
188
|
-
extracted.locality = component.long_name;
|
|
189
|
-
}
|
|
190
227
|
}
|
|
191
228
|
if (types.includes('administrative_area_level_1')) {
|
|
192
229
|
extracted.administrativeAreaLevel1 = component.short_name;
|
|
@@ -199,45 +236,57 @@ export function useGoogleAutocomplete(options) {
|
|
|
199
236
|
extracted.postalCode = component.long_name;
|
|
200
237
|
}
|
|
201
238
|
});
|
|
239
|
+
// === APPLY COMPREHENSIVE FALLBACK LOGIC ===
|
|
240
|
+
// 1. Building/House Number with fallbacks
|
|
241
|
+
// Priority: street_number (Western) > premise (Asian)
|
|
242
|
+
const buildingNumber = extracted.streetNumber || premise;
|
|
243
|
+
// 2. Street Name with comprehensive fallbacks
|
|
244
|
+
// Priority: route (Western) > sublocality_level_5 > sublocality_level_4 (Asian streets)
|
|
245
|
+
// > sublocality_level_3 (Neighborhood) > neighborhood
|
|
246
|
+
const streetName = extracted.route || sublocality5 || sublocality4 || sublocality3 || neighborhood;
|
|
247
|
+
// 3. Update locality with fallbacks if not set
|
|
248
|
+
// Priority: locality > postal_town > sublocality_level_2 > sublocality_level_1
|
|
249
|
+
if (!extracted.locality) {
|
|
250
|
+
extracted.locality = postalTown || sublocality2 || sublocality1 || '';
|
|
251
|
+
}
|
|
252
|
+
// 4. State fallback for French addresses
|
|
202
253
|
// For countries like France where administrative_area_level_1 (région) may be missing,
|
|
203
254
|
// use administrative_area_level_2 (département) as the primary state/province value
|
|
204
|
-
// We prefer the long_name (e.g., "Bouches-du-Rhône") over short_name (e.g., "13")
|
|
205
|
-
// because it's more likely to match our state database entries
|
|
206
255
|
if (!extracted.administrativeAreaLevel1 && extracted.administrativeAreaLevel2) {
|
|
207
|
-
// Use long name as the primary value (e.g., "Bouches-du-Rhône" instead of "13")
|
|
208
256
|
extracted.administrativeAreaLevel1 = extracted.administrativeAreaLevel2Long || extracted.administrativeAreaLevel2;
|
|
209
257
|
extracted.administrativeAreaLevel1Long = extracted.administrativeAreaLevel2Long;
|
|
210
258
|
}
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const streetParts = [];
|
|
217
|
-
if (extracted.subpremise) {
|
|
218
|
-
// Check if subpremise already contains formatting (e.g., "Unit 711")
|
|
219
|
-
const normalizedSubpremise = extracted.subpremise.trim();
|
|
220
|
-
streetParts.push(normalizedSubpremise);
|
|
221
|
-
}
|
|
222
|
-
if (extracted.streetNumber) {
|
|
223
|
-
streetParts.push(extracted.streetNumber);
|
|
259
|
+
// === CONSTRUCT FULL STREET ADDRESS ===
|
|
260
|
+
let addressValue = '';
|
|
261
|
+
if (extracted.subpremise && buildingNumber && streetName) {
|
|
262
|
+
// Unit + Building + Street (e.g., "711/3 Network Place", "5/3 Yulgok-ro 27-gil")
|
|
263
|
+
addressValue = `${extracted.subpremise}/${buildingNumber} ${streetName}`;
|
|
224
264
|
}
|
|
225
|
-
if (
|
|
226
|
-
|
|
265
|
+
else if (buildingNumber && streetName) {
|
|
266
|
+
// Building + Street (e.g., "3 Network Place", "3-8 Hirano" for Japanese)
|
|
267
|
+
// For Japanese addresses, check if we have multiple sublocality levels to combine
|
|
268
|
+
if (premise && sublocality4 && sublocality3) {
|
|
269
|
+
// Japanese format: "3-chōme-8-3" (chome-block-building)
|
|
270
|
+
addressValue = `${sublocality3}-${sublocality4}-${premise}`;
|
|
271
|
+
// Add area name if available (e.g., "3-chōme-8-3 Hirano")
|
|
272
|
+
if (sublocality2) {
|
|
273
|
+
addressValue = `${addressValue} ${sublocality2}`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// Standard format
|
|
278
|
+
addressValue = `${buildingNumber} ${streetName}`;
|
|
279
|
+
}
|
|
227
280
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
// Remove "Unit", "Apt", etc. prefixes for cleaner format
|
|
232
|
-
const cleanSubpremise = extracted.subpremise
|
|
233
|
-
.replace(/^(Unit|Apt|Apartment|Suite|#)\s*/i, '')
|
|
234
|
-
.trim();
|
|
235
|
-
extracted.fullStreetAddress = `${cleanSubpremise}/${extracted.streetNumber} ${extracted.route}`;
|
|
281
|
+
else if (streetName) {
|
|
282
|
+
// Only street name available (e.g., "Pyeongtaek 3-ro 56beon-gil")
|
|
283
|
+
addressValue = streetName;
|
|
236
284
|
}
|
|
237
|
-
else {
|
|
238
|
-
//
|
|
239
|
-
|
|
285
|
+
else if (buildingNumber) {
|
|
286
|
+
// Edge case: Only building number (rare)
|
|
287
|
+
addressValue = buildingNumber;
|
|
240
288
|
}
|
|
289
|
+
extracted.fullStreetAddress = addressValue.trim();
|
|
241
290
|
return extracted;
|
|
242
291
|
}, []);
|
|
243
292
|
// Extract address in the format expected by shipping/billing address forms
|
|
@@ -46,11 +46,16 @@ declare global {
|
|
|
46
46
|
handleRequest?: (...args: unknown[]) => void;
|
|
47
47
|
queue?: unknown[];
|
|
48
48
|
}
|
|
49
|
+
interface PinterestPixelFunction {
|
|
50
|
+
(...args: unknown[]): void;
|
|
51
|
+
queue?: unknown[];
|
|
52
|
+
}
|
|
49
53
|
interface Window {
|
|
50
54
|
fbq?: FacebookPixelFunction;
|
|
51
55
|
_fbq?: FacebookPixelFunction;
|
|
52
56
|
ttq?: TikTokPixelFunction;
|
|
53
57
|
snaptr?: SnapchatPixelFunction;
|
|
58
|
+
pintrk?: PinterestPixelFunction;
|
|
54
59
|
dataLayer?: any[];
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -87,6 +87,17 @@ export function PixelTrackingProvider({ children }) {
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
// Pinterest - support multiple pixels
|
|
91
|
+
const pinterestPixels = pixels.pinterest;
|
|
92
|
+
if (pinterestPixels) {
|
|
93
|
+
if (Array.isArray(pinterestPixels)) {
|
|
94
|
+
pinterestPixels.forEach((pixel) => {
|
|
95
|
+
if (pixel.enabled && pixel.pixelId) {
|
|
96
|
+
initPinterestPixel(pixel.pixelId);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
90
101
|
// GTM - support multiple containers
|
|
91
102
|
const gtmPixels = pixels.gtm;
|
|
92
103
|
if (gtmPixels) {
|
|
@@ -163,6 +174,21 @@ export function PixelTrackingProvider({ children }) {
|
|
|
163
174
|
});
|
|
164
175
|
}
|
|
165
176
|
}
|
|
177
|
+
// Pinterest - track to all enabled pixels
|
|
178
|
+
const pinterestPixels = pixels.pinterest;
|
|
179
|
+
if (pinterestPixels) {
|
|
180
|
+
const pixelArray = Array.isArray(pinterestPixels)
|
|
181
|
+
? pinterestPixels
|
|
182
|
+
: [pinterestPixels];
|
|
183
|
+
const enabledPixels = pixelArray.filter((p) => p.enabled);
|
|
184
|
+
if (enabledPixels.length > 0) {
|
|
185
|
+
const { name, params } = mapPinterestEvent(eventName, parameters);
|
|
186
|
+
// Track to all enabled Pinterest pixels
|
|
187
|
+
enabledPixels.forEach(() => {
|
|
188
|
+
trackPinterestEvent(name, params);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
166
192
|
// GTM - track to all enabled containers
|
|
167
193
|
const gtmPixels = pixels.gtm;
|
|
168
194
|
if (gtmPixels) {
|
|
@@ -355,6 +381,35 @@ function trackSnapchatEvent(name, params) {
|
|
|
355
381
|
const snaptr = window.snaptr;
|
|
356
382
|
snaptr?.('track', name, params);
|
|
357
383
|
}
|
|
384
|
+
function initPinterestPixel(pixelId) {
|
|
385
|
+
if (typeof window === 'undefined')
|
|
386
|
+
return;
|
|
387
|
+
if (window.pintrk)
|
|
388
|
+
return;
|
|
389
|
+
(function (w, d, tagName) {
|
|
390
|
+
if (w.pintrk)
|
|
391
|
+
return;
|
|
392
|
+
const a = (function (...args) {
|
|
393
|
+
a.queue.push(args);
|
|
394
|
+
});
|
|
395
|
+
a.queue = [];
|
|
396
|
+
w.pintrk = a;
|
|
397
|
+
const s = d.createElement(tagName);
|
|
398
|
+
s.async = true;
|
|
399
|
+
s.src = 'https://s.pinimg.com/ct/core.js';
|
|
400
|
+
const u = d.getElementsByTagName(tagName)[0];
|
|
401
|
+
u.parentNode?.insertBefore(s, u);
|
|
402
|
+
})(window, document, 'script');
|
|
403
|
+
const pintrk = window.pintrk;
|
|
404
|
+
pintrk?.('load', pixelId);
|
|
405
|
+
pintrk?.('page');
|
|
406
|
+
}
|
|
407
|
+
function trackPinterestEvent(name, params) {
|
|
408
|
+
if (typeof window === 'undefined' || !window.pintrk)
|
|
409
|
+
return;
|
|
410
|
+
const pintrk = window.pintrk;
|
|
411
|
+
pintrk?.('track', name, params);
|
|
412
|
+
}
|
|
358
413
|
function initGTM(containerId) {
|
|
359
414
|
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
360
415
|
return;
|
|
@@ -444,6 +499,59 @@ function mapTikTokEvent(eventName, parameters) {
|
|
|
444
499
|
function mapSnapchatEvent(eventName, parameters) {
|
|
445
500
|
return { name: eventName, params: parameters };
|
|
446
501
|
}
|
|
502
|
+
function mapPinterestEvent(eventName, parameters) {
|
|
503
|
+
// Pinterest uses lowercase event names
|
|
504
|
+
const pinterestEventMap = {
|
|
505
|
+
PageView: 'pagevisit',
|
|
506
|
+
ViewContent: 'viewcontent',
|
|
507
|
+
AddToCart: 'addtocart',
|
|
508
|
+
InitiateCheckout: 'initiatecheckout',
|
|
509
|
+
AddPaymentInfo: 'addpaymentinfo',
|
|
510
|
+
Purchase: 'checkout',
|
|
511
|
+
Lead: 'lead',
|
|
512
|
+
CompleteRegistration: 'signup',
|
|
513
|
+
};
|
|
514
|
+
const pinterestEventName = pinterestEventMap[eventName] ?? eventName.toLowerCase();
|
|
515
|
+
// Transform parameters for Pinterest
|
|
516
|
+
const pinterestParams = transformPinterestParameters(eventName, parameters);
|
|
517
|
+
return { name: pinterestEventName, params: pinterestParams };
|
|
518
|
+
}
|
|
519
|
+
function transformPinterestParameters(eventName, parameters) {
|
|
520
|
+
const pinParams = { ...parameters };
|
|
521
|
+
// Pinterest uses 'value' and 'order_quantity' as primary event data
|
|
522
|
+
// Ensure currency is uppercase
|
|
523
|
+
if (pinParams.currency) {
|
|
524
|
+
pinParams.currency = String(pinParams.currency).toUpperCase();
|
|
525
|
+
}
|
|
526
|
+
// Convert to major units if currency is present
|
|
527
|
+
if (pinParams.currency && pinParams.value) {
|
|
528
|
+
try {
|
|
529
|
+
pinParams.value = minorUnitsToMajorUnits(Number(pinParams.value), String(pinParams.currency));
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
// Fallback to simple division by 100
|
|
533
|
+
pinParams.value = Number(pinParams.value) / 100;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// Ensure numeric values
|
|
537
|
+
if (pinParams.value !== undefined) {
|
|
538
|
+
pinParams.value = Number(pinParams.value);
|
|
539
|
+
}
|
|
540
|
+
if (pinParams.order_quantity !== undefined) {
|
|
541
|
+
pinParams.order_quantity = Number(pinParams.order_quantity);
|
|
542
|
+
}
|
|
543
|
+
// Transform contents to Pinterest format (product_id array)
|
|
544
|
+
if (pinParams.contents && Array.isArray(pinParams.contents)) {
|
|
545
|
+
pinParams.line_items = pinParams.contents.map((item) => ({
|
|
546
|
+
product_id: item.content_id,
|
|
547
|
+
product_name: item.content_name,
|
|
548
|
+
product_category: item.content_category,
|
|
549
|
+
product_price: item.price ? Number(item.price) : undefined,
|
|
550
|
+
product_quantity: item.quantity ? Number(item.quantity) : undefined,
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
return pinParams;
|
|
554
|
+
}
|
|
447
555
|
function mapGTMEvent(eventName, parameters) {
|
|
448
556
|
// Map standard event names to GTM event names
|
|
449
557
|
const gtmEventMap = {
|
|
@@ -18,5 +18,11 @@ export interface UseShippingRatesQueryResult {
|
|
|
18
18
|
error: Error | null;
|
|
19
19
|
clearError: () => void;
|
|
20
20
|
refetch: () => Promise<void>;
|
|
21
|
+
/** Preview shipping rates for a country without updating session */
|
|
22
|
+
previewRates: (countryCode: string, stateCode?: string) => Promise<ShippingRate[]>;
|
|
23
|
+
/** Rates from the last previewRates call */
|
|
24
|
+
previewedRates: ShippingRate[] | undefined;
|
|
25
|
+
/** Loading state for previewRates */
|
|
26
|
+
isPreviewLoading: boolean;
|
|
21
27
|
}
|
|
22
28
|
export declare function useShippingRatesQuery(options?: UseShippingRatesQueryOptions): UseShippingRatesQueryResult;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Simplified shipping rates management with automatic cache invalidation
|
|
4
4
|
*/
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { ShippingRatesResource } from '../../core/resources/shippingRates';
|
|
8
8
|
import { getGlobalApiClient } from './useApiQuery';
|
|
9
9
|
export function useShippingRatesQuery(options = {}) {
|
|
@@ -23,6 +23,9 @@ export function useShippingRatesQuery(options = {}) {
|
|
|
23
23
|
const effectiveSessionId = sessionId || checkout?.checkoutSession?.id;
|
|
24
24
|
// Track if we've synced the initial selection from checkout
|
|
25
25
|
const hasSyncedInitialSelectionRef = useRef(false);
|
|
26
|
+
// Preview rates state
|
|
27
|
+
const [previewedRates, setPreviewedRates] = useState();
|
|
28
|
+
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
|
|
26
29
|
// Main shipping rates query
|
|
27
30
|
const { data: shippingRatesData, isLoading: isFetching, error: fetchError, refetch: refetchRates, } = useQuery({
|
|
28
31
|
queryKey: ['shipping-rates', effectiveSessionId],
|
|
@@ -122,6 +125,35 @@ export function useShippingRatesQuery(options = {}) {
|
|
|
122
125
|
// We can trigger a refetch which will clear the error
|
|
123
126
|
void refetch();
|
|
124
127
|
}, [refetch]);
|
|
128
|
+
// Preview shipping rates for a country without updating session
|
|
129
|
+
const previewRates = useCallback(async (countryCode, stateCode) => {
|
|
130
|
+
if (!effectiveSessionId)
|
|
131
|
+
return [];
|
|
132
|
+
try {
|
|
133
|
+
setIsPreviewLoading(true);
|
|
134
|
+
const response = await shippingRatesResource.previewShippingRates(effectiveSessionId, {
|
|
135
|
+
countryCode,
|
|
136
|
+
stateCode,
|
|
137
|
+
});
|
|
138
|
+
// Sort rates: free first, then by ascending amount
|
|
139
|
+
const sortedRates = [...response.rates].sort((a, b) => {
|
|
140
|
+
if (a.isFree && !b.isFree)
|
|
141
|
+
return -1;
|
|
142
|
+
if (!a.isFree && b.isFree)
|
|
143
|
+
return 1;
|
|
144
|
+
return (a.amount ?? 0) - (b.amount ?? 0);
|
|
145
|
+
});
|
|
146
|
+
setPreviewedRates(sortedRates);
|
|
147
|
+
return sortedRates;
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('[useShippingRatesQuery] Error previewing shipping rates:', error);
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
setIsPreviewLoading(false);
|
|
155
|
+
}
|
|
156
|
+
}, [effectiveSessionId, shippingRatesResource]);
|
|
125
157
|
return {
|
|
126
158
|
shippingRates,
|
|
127
159
|
selectedRate,
|
|
@@ -130,5 +162,8 @@ export function useShippingRatesQuery(options = {}) {
|
|
|
130
162
|
error: (fetchError || setShippingRateMutation.error),
|
|
131
163
|
clearError,
|
|
132
164
|
refetch,
|
|
165
|
+
previewRates,
|
|
166
|
+
previewedRates,
|
|
167
|
+
isPreviewLoading,
|
|
133
168
|
};
|
|
134
169
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* const offerId = staticResources?.offer;
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import {
|
|
24
|
+
import { PixelsConfig, RuntimeStepConfig } from '../../core/funnelClient';
|
|
25
25
|
export interface UseStepConfigResult {
|
|
26
26
|
/**
|
|
27
27
|
* Full step configuration object
|
|
@@ -45,13 +45,7 @@ export interface UseStepConfigResult {
|
|
|
45
45
|
* @param position - Where the scripts should be injected
|
|
46
46
|
*/
|
|
47
47
|
getScripts: (position?: 'head-start' | 'head-end' | 'body-start' | 'body-end') => RuntimeStepConfig['scripts'];
|
|
48
|
-
pixels:
|
|
49
|
-
[TrackingProvider.FACEBOOK]?: PixelTrackingConfig[];
|
|
50
|
-
[TrackingProvider.TIKTOK]?: PixelTrackingConfig[];
|
|
51
|
-
[TrackingProvider.SNAPCHAT]?: SnapchatTrackingConfig[];
|
|
52
|
-
[TrackingProvider.META_CONVERSION]?: MetaConversionTrackingConfig[];
|
|
53
|
-
[TrackingProvider.GTM]?: GTMTrackingConfig[];
|
|
54
|
-
} | undefined;
|
|
48
|
+
pixels: PixelsConfig | undefined;
|
|
55
49
|
}
|
|
56
50
|
/**
|
|
57
51
|
* Hook to access runtime step configuration injected via HTML
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
import { useMemo } from 'react';
|
|
25
|
-
import { getAssignedPaymentFlowId, getAssignedPixels, getAssignedScripts, getAssignedStaticResources, getAssignedStepConfig,
|
|
25
|
+
import { getAssignedPaymentFlowId, getAssignedPixels, getAssignedScripts, getAssignedStaticResources, getAssignedStepConfig, } from '../../core/funnelClient';
|
|
26
26
|
/**
|
|
27
27
|
* Hook to access runtime step configuration injected via HTML
|
|
28
28
|
*
|
|
@@ -38,11 +38,11 @@ const InitializationLoader = () => (_jsxs("div", { style: {
|
|
|
38
38
|
borderTop: '1.5px solid #9ca3af',
|
|
39
39
|
borderRadius: '50%',
|
|
40
40
|
animation: 'tagada-spin 1s linear infinite',
|
|
41
|
-
} }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
|
|
42
|
-
@keyframes tagada-spin {
|
|
43
|
-
0% { transform: rotate(0deg); }
|
|
44
|
-
100% { transform: rotate(360deg); }
|
|
45
|
-
}
|
|
41
|
+
} }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
|
|
42
|
+
@keyframes tagada-spin {
|
|
43
|
+
0% { transform: rotate(0deg); }
|
|
44
|
+
100% { transform: rotate(360deg); }
|
|
45
|
+
}
|
|
46
46
|
` })] }));
|
|
47
47
|
const TagadaContext = createContext(null);
|
|
48
48
|
export function TagadaProvider({ children, environment, customApiConfig, debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, features, funnelId, autoInitializeFunnel = true, onNavigate, onFunnelError, debugScripts = [], }) {
|
|
@@ -19,7 +19,7 @@ export function createTagadaClient(config = {}) {
|
|
|
19
19
|
// Re-export Core Classes
|
|
20
20
|
export { TagadaClient, ApiClient, CheckoutResource };
|
|
21
21
|
export { FunnelActionType } from '../core/resources/funnel';
|
|
22
|
-
// Re-export Utilities
|
|
22
|
+
// Re-export Utilities
|
|
23
23
|
export * from '../core/utils';
|
|
24
24
|
// ============================================================================
|
|
25
25
|
// EXTERNAL PAGE TRACKER
|