@tagadapay/plugin-sdk 3.1.10 → 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.
Files changed (38) hide show
  1. package/README.md +1129 -1129
  2. package/build-cdn.js +113 -223
  3. package/dist/external-tracker.js +2 -3
  4. package/dist/external-tracker.min.js +2 -2
  5. package/dist/external-tracker.min.js.map +3 -3
  6. package/dist/react/hooks/useShippingRates.d.ts +6 -0
  7. package/dist/react/hooks/useShippingRates.js +38 -0
  8. package/dist/react/providers/TagadaProvider.js +5 -5
  9. package/dist/react/services/apiService.d.ts +21 -0
  10. package/dist/react/services/apiService.js +10 -0
  11. package/dist/v2/core/funnelClient.d.ts +14 -15
  12. package/dist/v2/core/funnelClient.js +1 -1
  13. package/dist/v2/core/resources/shippingRates.d.ts +15 -0
  14. package/dist/v2/core/resources/shippingRates.js +11 -0
  15. package/dist/v2/core/utils/currency.d.ts +0 -14
  16. package/dist/v2/core/utils/currency.js +0 -40
  17. package/dist/v2/core/utils/index.d.ts +0 -1
  18. package/dist/v2/core/utils/index.js +0 -2
  19. package/dist/v2/core/utils/pluginConfig.d.ts +0 -8
  20. package/dist/v2/core/utils/pluginConfig.js +0 -28
  21. package/dist/v2/core/utils/previewModeIndicator.js +101 -101
  22. package/dist/v2/index.d.ts +1 -1
  23. package/dist/v2/react/components/ApplePayButton.js +13 -4
  24. package/dist/v2/react/components/FunnelScriptInjector.js +30 -30
  25. package/dist/v2/react/hooks/useFunnel.d.ts +1 -2
  26. package/dist/v2/react/hooks/useGoogleAutocomplete.js +82 -33
  27. package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -0
  28. package/dist/v2/react/hooks/usePixelTracking.js +108 -0
  29. package/dist/v2/react/hooks/useShippingRatesQuery.d.ts +6 -0
  30. package/dist/v2/react/hooks/useShippingRatesQuery.js +36 -1
  31. package/dist/v2/react/hooks/useStepConfig.d.ts +2 -8
  32. package/dist/v2/react/hooks/useStepConfig.js +1 -1
  33. package/dist/v2/react/providers/TagadaProvider.js +5 -5
  34. package/dist/v2/standalone/index.js +1 -1
  35. package/package.json +112 -112
  36. package/dist/tagada-sdk.js +0 -10164
  37. package/dist/tagada-sdk.min.js +0 -45
  38. 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
- if (window?.ApplePaySession && ApplePaySession.canMakePayments()) {
45
- setIsApplePayAvailable(true);
46
- addExpress();
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
- else {
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, MetaConversionTrackingConfig, PixelTrackingConfig, RuntimeStepConfig, SnapchatTrackingConfig, TrackingProvider } from '../../core/funnelClient';
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
- // Construct full street address
212
- // Handle different formats:
213
- // - "711/3 Network Place" (Australian unit format)
214
- // - "3 Network Place" (house number only)
215
- // - "Unit 711, 3 Network Place" (alternative format)
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 (extracted.route) {
226
- streetParts.push(extracted.route);
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
- // For Australian format, if we have both subpremise and streetNumber,
229
- // format as "subpremise/streetNumber route" (e.g., "711/3 Network Place")
230
- if (extracted.subpremise && extracted.streetNumber && extracted.route) {
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
- // Standard format: join all parts with spaces
239
- extracted.fullStreetAddress = streetParts.join(' ');
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 { GTMTrackingConfig, MetaConversionTrackingConfig, PixelTrackingConfig, RuntimeStepConfig, SnapchatTrackingConfig, TrackingProvider } from '../../core/funnelClient';
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, TrackingProvider } from '../../core/funnelClient';
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 (includes formatMoney from core/utils/currency)
22
+ // Re-export Utilities
23
23
  export * from '../core/utils';
24
24
  // ============================================================================
25
25
  // EXTERNAL PAGE TRACKER