@tagadapay/plugin-sdk 2.8.9 → 3.0.1
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 +14 -14
- package/dist/index.js +1 -1
- package/dist/react/hooks/usePluginConfig.d.ts +1 -0
- package/dist/react/hooks/usePluginConfig.js +69 -18
- package/dist/react/providers/TagadaProvider.js +1 -4
- package/dist/v2/core/client.d.ts +22 -0
- package/dist/v2/core/client.js +90 -1
- package/dist/v2/core/config/environment.d.ts +24 -2
- package/dist/v2/core/config/environment.js +58 -25
- package/dist/v2/core/funnelClient.d.ts +84 -0
- package/dist/v2/core/funnelClient.js +252 -0
- package/dist/v2/core/index.d.ts +2 -0
- package/dist/v2/core/index.js +3 -0
- package/dist/v2/core/resources/apiClient.d.ts +5 -0
- package/dist/v2/core/resources/apiClient.js +47 -0
- package/dist/v2/core/resources/funnel.d.ts +8 -0
- package/dist/v2/core/resources/offers.d.ts +182 -8
- package/dist/v2/core/resources/offers.js +25 -0
- package/dist/v2/core/resources/products.d.ts +5 -0
- package/dist/v2/core/resources/products.js +15 -1
- package/dist/v2/core/types.d.ts +1 -0
- package/dist/v2/core/utils/funnelQueryKeys.d.ts +23 -0
- package/dist/v2/core/utils/funnelQueryKeys.js +23 -0
- package/dist/v2/core/utils/index.d.ts +2 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
- package/dist/v2/core/utils/pluginConfig.js +84 -22
- package/dist/v2/core/utils/sessionStorage.d.ts +20 -0
- package/dist/v2/core/utils/sessionStorage.js +39 -0
- package/dist/v2/index.d.ts +3 -2
- package/dist/v2/index.js +1 -1
- package/dist/v2/react/components/ApplePayButton.js +1 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +3 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +4 -3
- package/dist/v2/react/hooks/useClubOffers.d.ts +2 -2
- package/dist/v2/react/hooks/useFunnel.d.ts +27 -38
- package/dist/v2/react/hooks/useFunnel.js +22 -660
- package/dist/v2/react/hooks/useFunnelLegacy.d.ts +52 -0
- package/dist/v2/react/hooks/useFunnelLegacy.js +733 -0
- package/dist/v2/react/hooks/useOfferQuery.d.ts +109 -0
- package/dist/v2/react/hooks/useOfferQuery.js +483 -0
- package/dist/v2/react/hooks/useOffersQuery.d.ts +10 -58
- package/dist/v2/react/hooks/useOffersQuery.js +110 -8
- package/dist/v2/react/hooks/useProductsQuery.d.ts +1 -0
- package/dist/v2/react/hooks/useProductsQuery.js +10 -6
- package/dist/v2/react/index.d.ts +7 -4
- package/dist/v2/react/index.js +4 -2
- package/dist/v2/react/providers/TagadaProvider.d.ts +45 -2
- package/dist/v2/react/providers/TagadaProvider.js +116 -3
- package/dist/v2/standalone/index.d.ts +20 -0
- package/dist/v2/standalone/index.js +22 -0
- package/dist/v2/vue/index.d.ts +6 -0
- package/dist/v2/vue/index.js +10 -0
- package/package.json +6 -1
|
@@ -3,18 +3,26 @@
|
|
|
3
3
|
* Handles offers with automatic caching
|
|
4
4
|
*/
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
|
-
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { OffersResource } from '../../core/resources/offers';
|
|
8
8
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
9
|
import { getGlobalApiClient } from './useApiQuery';
|
|
10
10
|
import { usePluginConfig } from './usePluginConfig';
|
|
11
11
|
export function useOffersQuery(options = {}) {
|
|
12
|
-
const { offerIds = [], enabled = true, returnUrl, orderId: defaultOrderId } = options;
|
|
12
|
+
const { offerIds = [], enabled = true, returnUrl, orderId: defaultOrderId, activeOfferId, skipPreview = false } = options;
|
|
13
13
|
const { storeId } = usePluginConfig();
|
|
14
14
|
const { isSessionInitialized, session } = useTagadaContext();
|
|
15
15
|
const _queryClient = useQueryClient();
|
|
16
|
+
// Version identifier for debugging
|
|
17
|
+
console.log('[useOffersQuery] Hook initialized - VERSION: 2.2-production-ready', {
|
|
18
|
+
activeOfferId,
|
|
19
|
+
skipPreview,
|
|
20
|
+
isSessionInitialized,
|
|
21
|
+
});
|
|
16
22
|
// State for checkout sessions per offer (similar to postPurchases)
|
|
17
23
|
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
24
|
+
const [isActiveSummaryLoading, setIsActiveSummaryLoading] = useState(false);
|
|
25
|
+
const lastPreviewedOfferRef = useRef(null);
|
|
18
26
|
// Use ref to break dependency cycles in callbacks
|
|
19
27
|
const checkoutSessionsRef = useRef(checkoutSessions);
|
|
20
28
|
// Update ref on every render
|
|
@@ -30,6 +38,8 @@ export function useOffersQuery(options = {}) {
|
|
|
30
38
|
}, []);
|
|
31
39
|
// Helper function for fetching order summary (similar to postPurchases)
|
|
32
40
|
const fetchOrderSummary = useCallback(async (offerId, sessionId) => {
|
|
41
|
+
if (!isSessionInitialized)
|
|
42
|
+
return null;
|
|
33
43
|
try {
|
|
34
44
|
// Set updating state
|
|
35
45
|
setCheckoutSessions(prev => ({
|
|
@@ -77,7 +87,7 @@ export function useOffersQuery(options = {}) {
|
|
|
77
87
|
}));
|
|
78
88
|
throw error;
|
|
79
89
|
}
|
|
80
|
-
}, [offersResource]);
|
|
90
|
+
}, [offersResource, isSessionInitialized]);
|
|
81
91
|
// Create query key based on options
|
|
82
92
|
const queryKey = useMemo(() => ['offers', { storeId, offerIds }], [storeId, offerIds]);
|
|
83
93
|
// Use TanStack Query for fetching offers
|
|
@@ -120,6 +130,9 @@ export function useOffersQuery(options = {}) {
|
|
|
120
130
|
return await payWithCheckoutSessionAsync({ checkoutSessionId, orderId });
|
|
121
131
|
}, [payWithCheckoutSessionAsync]);
|
|
122
132
|
const initCheckoutSession = useCallback(async (offerId, orderId, customerId) => {
|
|
133
|
+
if (!isSessionInitialized) {
|
|
134
|
+
throw new Error('Cannot initialize checkout session: CMS session is not initialized');
|
|
135
|
+
}
|
|
123
136
|
// Use customerId from session context if not provided
|
|
124
137
|
const effectiveCustomerId = customerId || session?.customerId;
|
|
125
138
|
if (!effectiveCustomerId) {
|
|
@@ -130,8 +143,11 @@ export function useOffersQuery(options = {}) {
|
|
|
130
143
|
orderId,
|
|
131
144
|
customerId: effectiveCustomerId
|
|
132
145
|
});
|
|
133
|
-
}, [initCheckoutSessionAsync, session?.customerId]);
|
|
146
|
+
}, [initCheckoutSessionAsync, session?.customerId, isSessionInitialized]);
|
|
134
147
|
const payOffer = useCallback(async (offerId, orderId) => {
|
|
148
|
+
if (!isSessionInitialized) {
|
|
149
|
+
throw new Error('Cannot pay offer: CMS session is not initialized');
|
|
150
|
+
}
|
|
135
151
|
const effectiveOrderId = orderId || defaultOrderId;
|
|
136
152
|
const effectiveCustomerId = session?.customerId;
|
|
137
153
|
if (!effectiveOrderId) {
|
|
@@ -144,18 +160,26 @@ export function useOffersQuery(options = {}) {
|
|
|
144
160
|
const { checkoutSessionId } = await initCheckoutSession(offerId, effectiveOrderId, effectiveCustomerId);
|
|
145
161
|
// 2. Pay
|
|
146
162
|
await payWithCheckoutSession(checkoutSessionId, effectiveOrderId);
|
|
147
|
-
}, [initCheckoutSession, payWithCheckoutSession, defaultOrderId, session?.customerId]);
|
|
163
|
+
}, [initCheckoutSession, payWithCheckoutSession, defaultOrderId, session?.customerId, isSessionInitialized]);
|
|
148
164
|
const preview = useCallback(async (offerId) => {
|
|
165
|
+
console.log('[useOffersQuery] preview() called for offer:', offerId);
|
|
166
|
+
if (!isSessionInitialized) {
|
|
167
|
+
console.log('[useOffersQuery] preview() - session not initialized, returning null');
|
|
168
|
+
// Return null silently to avoid errors during auto-initialization phases
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
149
171
|
const effectiveOrderId = defaultOrderId;
|
|
150
172
|
const effectiveCustomerId = session?.customerId;
|
|
151
173
|
// Use ref to check current state without creating dependency
|
|
152
174
|
const currentSessions = checkoutSessionsRef.current;
|
|
153
175
|
// If we already have a summary in state, return it
|
|
154
176
|
if (currentSessions[offerId]?.orderSummary) {
|
|
177
|
+
console.log('[useOffersQuery] preview() - using cached summary for offer:', offerId);
|
|
155
178
|
return currentSessions[offerId].orderSummary;
|
|
156
179
|
}
|
|
157
180
|
// Prevent duplicate initialization if already has a session and is updating
|
|
158
181
|
if (currentSessions[offerId]?.checkoutSessionId && currentSessions[offerId]?.isUpdatingSummary) {
|
|
182
|
+
console.log('[useOffersQuery] preview() - already updating, skipping for offer:', offerId);
|
|
159
183
|
return null;
|
|
160
184
|
}
|
|
161
185
|
// If we don't have orderId, fallback to static summary from offer object
|
|
@@ -189,7 +213,71 @@ export function useOffersQuery(options = {}) {
|
|
|
189
213
|
console.error("Failed to preview offer", e);
|
|
190
214
|
return null;
|
|
191
215
|
}
|
|
192
|
-
}, [offers, defaultOrderId, session?.customerId, initCheckoutSession, fetchOrderSummary]); // Removed checkoutSessions dependency
|
|
216
|
+
}, [offers, defaultOrderId, session?.customerId, initCheckoutSession, fetchOrderSummary, isSessionInitialized]); // Removed checkoutSessions dependency
|
|
217
|
+
// Auto-preview effect for activeOfferId
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
console.log('[useOffersQuery v2.2] Auto-preview effect triggered:', {
|
|
220
|
+
activeOfferId,
|
|
221
|
+
skipPreview,
|
|
222
|
+
isSessionInitialized,
|
|
223
|
+
lastPreviewed: lastPreviewedOfferRef.current,
|
|
224
|
+
});
|
|
225
|
+
if (!activeOfferId || skipPreview || !isSessionInitialized) {
|
|
226
|
+
console.log('[useOffersQuery] Skipping auto-preview - conditions not met');
|
|
227
|
+
setIsActiveSummaryLoading(false); // Reset loading state if conditions not met
|
|
228
|
+
// Reset the ref when conditions are not met
|
|
229
|
+
if (!activeOfferId) {
|
|
230
|
+
lastPreviewedOfferRef.current = null;
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Skip if we've already previewed this exact offer
|
|
235
|
+
if (lastPreviewedOfferRef.current === activeOfferId) {
|
|
236
|
+
console.log('[useOffersQuery] Skipping auto-preview - already previewed:', activeOfferId);
|
|
237
|
+
setIsActiveSummaryLoading(false); // Ensure loading is false
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
console.log('[useOffersQuery] Starting auto-preview for offer:', activeOfferId);
|
|
241
|
+
let isMounted = true;
|
|
242
|
+
setIsActiveSummaryLoading(true); // Set loading immediately
|
|
243
|
+
// Debounce the preview call
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
if (!isMounted) {
|
|
246
|
+
console.log('[useOffersQuery] Component unmounted before preview call');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
console.log('[useOffersQuery] Calling preview for offer:', activeOfferId);
|
|
250
|
+
preview(activeOfferId)
|
|
251
|
+
.then(() => {
|
|
252
|
+
if (isMounted) {
|
|
253
|
+
console.log('[useOffersQuery] Preview successful for offer:', activeOfferId);
|
|
254
|
+
lastPreviewedOfferRef.current = activeOfferId; // Mark as previewed on success
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.catch(err => {
|
|
258
|
+
console.error('[useOffersQuery] Failed to auto-preview offer:', activeOfferId, err);
|
|
259
|
+
// Don't mark as previewed on error, to avoid infinite retry loop
|
|
260
|
+
})
|
|
261
|
+
.finally(() => {
|
|
262
|
+
if (isMounted) {
|
|
263
|
+
console.log('[useOffersQuery] Preview finished for offer:', activeOfferId);
|
|
264
|
+
setIsActiveSummaryLoading(false);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}, 50);
|
|
268
|
+
return () => {
|
|
269
|
+
console.log('[useOffersQuery] Cleaning up auto-preview effect for offer:', activeOfferId);
|
|
270
|
+
isMounted = false;
|
|
271
|
+
clearTimeout(timer);
|
|
272
|
+
setIsActiveSummaryLoading(false); // Reset loading on unmount/cleanup
|
|
273
|
+
};
|
|
274
|
+
}, [activeOfferId, skipPreview, isSessionInitialized]); // FIXED: Removed 'preview' from dependencies to prevent infinite loop
|
|
275
|
+
const activeSummary = useMemo(() => {
|
|
276
|
+
if (!activeOfferId)
|
|
277
|
+
return null;
|
|
278
|
+
// Return dynamic summary if available, otherwise fallback to static summary
|
|
279
|
+
return checkoutSessions[activeOfferId]?.orderSummary || offers.find(o => o.id === activeOfferId)?.summaries?.[0] || null;
|
|
280
|
+
}, [activeOfferId, checkoutSessions, offers]);
|
|
193
281
|
const getAvailableVariants = useCallback((offerId, productId) => {
|
|
194
282
|
const sessionState = checkoutSessions[offerId]; // This hook needs to react to state changes
|
|
195
283
|
if (!sessionState?.orderSummary?.options?.[productId])
|
|
@@ -208,6 +296,9 @@ export function useOffersQuery(options = {}) {
|
|
|
208
296
|
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
209
297
|
}, [checkoutSessions]);
|
|
210
298
|
const selectVariant = useCallback(async (offerId, productId, variantId) => {
|
|
299
|
+
if (!isSessionInitialized) {
|
|
300
|
+
throw new Error('Cannot select variant: CMS session is not initialized');
|
|
301
|
+
}
|
|
211
302
|
// Use ref for initial check to avoid dependency but we might need latest state for logic
|
|
212
303
|
// Actually for actions it's better to use ref or just dependency if action is not called in useEffect
|
|
213
304
|
const currentSessions = checkoutSessionsRef.current;
|
|
@@ -286,12 +377,14 @@ export function useOffersQuery(options = {}) {
|
|
|
286
377
|
}
|
|
287
378
|
}));
|
|
288
379
|
}
|
|
289
|
-
}, [offersResource, fetchOrderSummary]); // Removed checkoutSessions dependency
|
|
290
|
-
|
|
380
|
+
}, [offersResource, fetchOrderSummary, isSessionInitialized]); // Removed checkoutSessions dependency
|
|
381
|
+
const result = {
|
|
291
382
|
// Query data
|
|
292
383
|
offers,
|
|
293
384
|
isLoading,
|
|
294
385
|
error,
|
|
386
|
+
activeSummary,
|
|
387
|
+
isActiveSummaryLoading,
|
|
295
388
|
// Actions
|
|
296
389
|
payOffer,
|
|
297
390
|
preview,
|
|
@@ -299,4 +392,13 @@ export function useOffersQuery(options = {}) {
|
|
|
299
392
|
selectVariant,
|
|
300
393
|
isLoadingVariants,
|
|
301
394
|
};
|
|
395
|
+
console.log('[useOffersQuery] Returning result:', {
|
|
396
|
+
offersCount: offers.length,
|
|
397
|
+
isLoading,
|
|
398
|
+
hasError: !!error,
|
|
399
|
+
activeOfferId,
|
|
400
|
+
hasActiveSummary: !!activeSummary,
|
|
401
|
+
isActiveSummaryLoading,
|
|
402
|
+
});
|
|
403
|
+
return result;
|
|
302
404
|
}
|
|
@@ -8,7 +8,7 @@ import { ProductsResource } from '../../core/resources/products';
|
|
|
8
8
|
import { getGlobalApiClient } from './useApiQuery';
|
|
9
9
|
import { usePluginConfig } from './usePluginConfig';
|
|
10
10
|
export function useProductsQuery(options = {}) {
|
|
11
|
-
const { productIds = [], enabled = true, includeVariants = true, includePrices = true } = options;
|
|
11
|
+
const { productIds = [], variantIds = [], enabled = true, includeVariants = true, includePrices = true } = options;
|
|
12
12
|
const { storeId } = usePluginConfig();
|
|
13
13
|
// Create products resource client
|
|
14
14
|
const productsResource = useMemo(() => {
|
|
@@ -20,7 +20,7 @@ export function useProductsQuery(options = {}) {
|
|
|
20
20
|
}
|
|
21
21
|
}, []);
|
|
22
22
|
// Create query key based on options
|
|
23
|
-
const queryKey = useMemo(() => ['products', { storeId, productIds, includeVariants, includePrices }], [storeId, productIds, includeVariants, includePrices]);
|
|
23
|
+
const queryKey = useMemo(() => ['products', { storeId, productIds, variantIds, includeVariants, includePrices }], [storeId, productIds, variantIds, includeVariants, includePrices]);
|
|
24
24
|
// Use TanStack Query for fetching products
|
|
25
25
|
const { data: products = [], isLoading, error, refetch } = useQuery({
|
|
26
26
|
queryKey,
|
|
@@ -29,18 +29,22 @@ export function useProductsQuery(options = {}) {
|
|
|
29
29
|
if (!storeId) {
|
|
30
30
|
throw new Error('Store ID not found. Make sure the TagadaProvider is properly configured.');
|
|
31
31
|
}
|
|
32
|
-
const
|
|
32
|
+
const baseOptions = {
|
|
33
33
|
storeId,
|
|
34
34
|
includeVariants,
|
|
35
35
|
includePrices,
|
|
36
36
|
};
|
|
37
|
-
if (
|
|
37
|
+
if (variantIds.length > 0) {
|
|
38
|
+
// Fetch products by variant IDs (most efficient)
|
|
39
|
+
return await productsResource.getProductsByVariantIds(variantIds, baseOptions);
|
|
40
|
+
}
|
|
41
|
+
else if (productIds.length > 0) {
|
|
38
42
|
// Fetch specific products
|
|
39
|
-
return await productsResource.getProductsByIds(productIds,
|
|
43
|
+
return await productsResource.getProductsByIds(productIds, baseOptions);
|
|
40
44
|
}
|
|
41
45
|
else {
|
|
42
46
|
// Fetch all products for the store
|
|
43
|
-
return await productsResource.getProducts(
|
|
47
|
+
return await productsResource.getProducts(baseOptions);
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
package/dist/v2/react/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export { queryKeys, useApiMutation, useApiQuery, useInvalidateQuery, usePreloadQ
|
|
|
25
25
|
export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
|
|
26
26
|
export { useCurrency } from './hooks/useCurrency';
|
|
27
27
|
export { useDiscountsQuery as useDiscounts } from './hooks/useDiscountsQuery';
|
|
28
|
-
export {
|
|
28
|
+
export { useOfferQuery as useOffer } from './hooks/useOfferQuery';
|
|
29
29
|
export { useOrderBumpQuery as useOrderBump } from './hooks/useOrderBumpQuery';
|
|
30
30
|
export { useOrderQuery as useOrder } from './hooks/useOrderQuery';
|
|
31
31
|
export { usePaymentQuery as usePayment } from './hooks/usePaymentQuery';
|
|
@@ -38,7 +38,8 @@ export { useThreeds } from './hooks/useThreeds';
|
|
|
38
38
|
export { useThreedsModal } from './hooks/useThreedsModal';
|
|
39
39
|
export { useTranslation } from './hooks/useTranslation';
|
|
40
40
|
export { useVipOffersQuery as useVipOffers } from './hooks/useVipOffersQuery';
|
|
41
|
-
export { useFunnel
|
|
41
|
+
export { useFunnel } from './hooks/useFunnel';
|
|
42
|
+
export { useFunnel as useFunnelLegacy, useSimpleFunnel } from './hooks/useFunnelLegacy';
|
|
42
43
|
export type { UseCheckoutTokenOptions, UseCheckoutTokenResult } from './hooks/useCheckoutToken';
|
|
43
44
|
export type { ClubOffer, ClubOfferItem, ClubOfferLineItem, ClubOfferSummary, UseClubOffersOptions, UseClubOffersResult } from './hooks/useClubOffers';
|
|
44
45
|
export type { UseCreditsOptions, UseCreditsResult } from './hooks/useCredits';
|
|
@@ -58,8 +59,10 @@ export type { TranslateFunction, UseTranslationOptions, UseTranslationResult } f
|
|
|
58
59
|
export { FunnelActionType } from '../core/resources/funnel';
|
|
59
60
|
export type { UseCheckoutQueryOptions as UseCheckoutOptions, UseCheckoutQueryResult as UseCheckoutResult } from './hooks/useCheckoutQuery';
|
|
60
61
|
export type { UseDiscountsQueryOptions as UseDiscountsOptions, UseDiscountsQueryResult as UseDiscountsResult } from './hooks/useDiscountsQuery';
|
|
61
|
-
export type {
|
|
62
|
-
export type {
|
|
62
|
+
export type { FunnelAction, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from '../core/resources/funnel';
|
|
63
|
+
export type { FunnelContextValue } from './hooks/useFunnel';
|
|
64
|
+
export type { UseFunnelOptions as UseFunnelLegacyOptions, UseFunnelResult as UseFunnelLegacyResult } from './hooks/useFunnelLegacy';
|
|
65
|
+
export type { AvailableVariant, LineItemSelection, OfferLineItem, OfferPreviewSummary, UseOfferQueryOptions as UseOfferOptions, UseOfferQueryResult as UseOfferResult } from './hooks/useOfferQuery';
|
|
63
66
|
export type { UseOrderBumpQueryOptions as UseOrderBumpOptions, UseOrderBumpQueryResult as UseOrderBumpResult } from './hooks/useOrderBumpQuery';
|
|
64
67
|
export type { UseOrderQueryOptions as UseOrderOptions, UseOrderQueryResult as UseOrderResult } from './hooks/useOrderQuery';
|
|
65
68
|
export type { PaymentHook as UsePaymentResult } from './hooks/usePaymentQuery';
|
package/dist/v2/react/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { queryKeys, useApiMutation, useApiQuery, useInvalidateQuery, usePreloadQ
|
|
|
29
29
|
export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
|
|
30
30
|
export { useCurrency } from './hooks/useCurrency';
|
|
31
31
|
export { useDiscountsQuery as useDiscounts } from './hooks/useDiscountsQuery';
|
|
32
|
-
export {
|
|
32
|
+
export { useOfferQuery as useOffer } from './hooks/useOfferQuery';
|
|
33
33
|
export { useOrderBumpQuery as useOrderBump } from './hooks/useOrderBumpQuery';
|
|
34
34
|
export { useOrderQuery as useOrder } from './hooks/useOrderQuery';
|
|
35
35
|
export { usePaymentQuery as usePayment } from './hooks/usePaymentQuery';
|
|
@@ -43,7 +43,9 @@ export { useThreedsModal } from './hooks/useThreedsModal';
|
|
|
43
43
|
export { useTranslation } from './hooks/useTranslation';
|
|
44
44
|
export { useVipOffersQuery as useVipOffers } from './hooks/useVipOffersQuery';
|
|
45
45
|
// Funnel hooks
|
|
46
|
-
export { useFunnel
|
|
46
|
+
export { useFunnel } from './hooks/useFunnel';
|
|
47
|
+
// Legacy funnel hooks (deprecated - use TagadaProvider + useFunnel instead)
|
|
48
|
+
export { useFunnel as useFunnelLegacy, useSimpleFunnel } from './hooks/useFunnelLegacy';
|
|
47
49
|
// TanStack Query types
|
|
48
50
|
export { FunnelActionType } from '../core/resources/funnel';
|
|
49
51
|
// Re-export utilities from main react
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { TagadaState } from '../../core/client';
|
|
2
|
+
import { TagadaClient, TagadaState, TagadaClientConfig } from '../../core/client';
|
|
3
3
|
import { RawPluginConfig } from '../../core/utils/pluginConfig';
|
|
4
4
|
import { Environment, EnvironmentConfig } from '../../core/types';
|
|
5
5
|
import { ApiService } from '../../../react/services/apiService';
|
|
6
6
|
import { formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, convertCurrency } from '../../../react/utils/money';
|
|
7
|
+
import { FunnelAction, FunnelNavigationResult, SimpleFunnelContext } from '../../core/resources/funnel';
|
|
8
|
+
import { FunnelState } from '../../core/funnelClient';
|
|
7
9
|
interface TagadaContextValue extends TagadaState {
|
|
10
|
+
client: TagadaClient;
|
|
8
11
|
apiService: ApiService;
|
|
12
|
+
pendingRedirect: boolean;
|
|
13
|
+
funnel: FunnelState & {
|
|
14
|
+
currentStep: {
|
|
15
|
+
id: string;
|
|
16
|
+
} | null;
|
|
17
|
+
next: (event: FunnelAction) => Promise<FunnelNavigationResult>;
|
|
18
|
+
goToStep: (stepId: string) => Promise<FunnelNavigationResult>;
|
|
19
|
+
updateContext: (updates: Partial<SimpleFunnelContext>) => Promise<void>;
|
|
20
|
+
initializeSession: (entryStepId?: string) => Promise<void>;
|
|
21
|
+
endSession: () => Promise<void>;
|
|
22
|
+
retryInitialization: () => Promise<void>;
|
|
23
|
+
refetch: () => Promise<void>;
|
|
24
|
+
};
|
|
9
25
|
debugCheckout: {
|
|
10
26
|
isActive: boolean;
|
|
11
27
|
data: any;
|
|
@@ -42,14 +58,41 @@ interface TagadaContextValue extends TagadaState {
|
|
|
42
58
|
}
|
|
43
59
|
interface TagadaProviderProps {
|
|
44
60
|
children: ReactNode;
|
|
61
|
+
/**
|
|
62
|
+
* Optional environment override.
|
|
63
|
+
* ⚠️ Leave undefined for automatic runtime detection (recommended).
|
|
64
|
+
* Only set this if you need to force a specific environment for testing.
|
|
65
|
+
*/
|
|
45
66
|
environment?: Environment;
|
|
46
67
|
customApiConfig?: Partial<EnvironmentConfig>;
|
|
47
68
|
debugMode?: boolean;
|
|
48
69
|
localConfig?: string;
|
|
49
70
|
blockUntilSessionReady?: boolean;
|
|
50
71
|
rawPluginConfig?: RawPluginConfig;
|
|
72
|
+
/**
|
|
73
|
+
* Optional feature flags to enable/disable subsystems (e.g. funnel).
|
|
74
|
+
* By default all features are enabled.
|
|
75
|
+
*/
|
|
76
|
+
features?: TagadaClientConfig['features'];
|
|
77
|
+
/**
|
|
78
|
+
* Funnel ID to initialize. If not provided, will be detected from URL query param.
|
|
79
|
+
*/
|
|
80
|
+
funnelId?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Auto-initialize funnel session when auth and store are ready.
|
|
83
|
+
* Default: true
|
|
84
|
+
*/
|
|
85
|
+
autoInitializeFunnel?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Callback fired after funnel navigation
|
|
88
|
+
*/
|
|
89
|
+
onNavigate?: (result: FunnelNavigationResult) => void | boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Callback fired on funnel errors
|
|
92
|
+
*/
|
|
93
|
+
onFunnelError?: (error: Error) => void;
|
|
51
94
|
}
|
|
52
95
|
export declare function TagadaProvider({ children, environment, customApiConfig, // Ignored for now in TagadaClient, or need to add support
|
|
53
|
-
debugMode, localConfig, blockUntilSessionReady, rawPluginConfig, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
96
|
+
debugMode, localConfig, blockUntilSessionReady, rawPluginConfig, features, funnelId, autoInitializeFunnel, onNavigate, onFunnelError, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
54
97
|
export declare function useTagadaContext(): TagadaContextValue;
|
|
55
98
|
export {};
|
|
@@ -44,18 +44,27 @@ const InitializationLoader = () => (_jsxs("div", { style: {
|
|
|
44
44
|
` })] }));
|
|
45
45
|
const TagadaContext = createContext(null);
|
|
46
46
|
export function TagadaProvider({ children, environment, customApiConfig, // Ignored for now in TagadaClient, or need to add support
|
|
47
|
-
debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, }) {
|
|
47
|
+
debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, features, funnelId, autoInitializeFunnel = true, onNavigate, onFunnelError, }) {
|
|
48
|
+
// Auto-detect debug mode from environment if not explicitly set
|
|
49
|
+
const effectiveDebugMode = debugMode !== undefined
|
|
50
|
+
? debugMode
|
|
51
|
+
: (typeof import.meta !== 'undefined' && import.meta.env?.DEV) ||
|
|
52
|
+
(typeof process !== 'undefined' && process.env?.NODE_ENV === 'development');
|
|
48
53
|
// Initialize client
|
|
49
54
|
const client = useMemo(() => {
|
|
50
55
|
const config = {
|
|
51
56
|
environment,
|
|
52
|
-
debugMode,
|
|
57
|
+
debugMode: effectiveDebugMode,
|
|
53
58
|
localConfig,
|
|
54
59
|
rawPluginConfig,
|
|
55
60
|
blockUntilSessionReady,
|
|
61
|
+
// For now we always enable funnel by default; callers can disable via features if needed
|
|
62
|
+
features: features ?? {
|
|
63
|
+
funnel: true,
|
|
64
|
+
},
|
|
56
65
|
};
|
|
57
66
|
return new TagadaClient(config);
|
|
58
|
-
}, []); // Singleton behavior
|
|
67
|
+
}, []); // Singleton behavior on first render
|
|
59
68
|
// State Sync
|
|
60
69
|
const [state, setState] = useState(client.getState());
|
|
61
70
|
useEffect(() => {
|
|
@@ -82,6 +91,32 @@ debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, }) {
|
|
|
82
91
|
apiService.updateToken(state.token);
|
|
83
92
|
apiService.updateConfig(state.environment);
|
|
84
93
|
}, [state.token, state.environment, apiService]);
|
|
94
|
+
// Funnel State Management
|
|
95
|
+
const [funnelState, setFunnelState] = useState(() => client.funnel
|
|
96
|
+
? client.funnel.getState()
|
|
97
|
+
: {
|
|
98
|
+
context: null,
|
|
99
|
+
isLoading: false,
|
|
100
|
+
isInitialized: false,
|
|
101
|
+
isNavigating: false,
|
|
102
|
+
error: null,
|
|
103
|
+
sessionError: null,
|
|
104
|
+
});
|
|
105
|
+
// Track pending redirect to prevent renders during navigation
|
|
106
|
+
const [pendingRedirect, setPendingRedirect] = useState(false);
|
|
107
|
+
// Subscribe to funnel state changes
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!client.funnel)
|
|
110
|
+
return;
|
|
111
|
+
// Keep funnel client in sync with latest config/environment
|
|
112
|
+
client.funnel.setConfig({
|
|
113
|
+
pluginConfig: state.pluginConfig,
|
|
114
|
+
environment: state.environment,
|
|
115
|
+
});
|
|
116
|
+
return client.funnel.subscribe(setFunnelState);
|
|
117
|
+
}, [client, state.pluginConfig, state.environment]);
|
|
118
|
+
// Note: Funnel auto-initialization is now handled by TagadaClient itself
|
|
119
|
+
// when the session is initialized. This happens automatically in the core layer.
|
|
85
120
|
// Debug State (React specific)
|
|
86
121
|
const [debugCheckout, setDebugCheckout] = useState({
|
|
87
122
|
isActive: false,
|
|
@@ -160,9 +195,87 @@ debugMode, localConfig, blockUntilSessionReady = false, rawPluginConfig, }) {
|
|
|
160
195
|
formatSimpleMoney,
|
|
161
196
|
}), []);
|
|
162
197
|
const [isDebugDrawerOpen, setIsDebugDrawerOpen] = useState(false);
|
|
198
|
+
// Funnel Methods
|
|
199
|
+
const funnelMethods = useMemo(() => {
|
|
200
|
+
if (!client.funnel) {
|
|
201
|
+
// Return no-op methods if funnel is disabled
|
|
202
|
+
const noopAsync = async () => {
|
|
203
|
+
throw new Error('Funnel feature is disabled');
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
next: noopAsync,
|
|
207
|
+
goToStep: noopAsync,
|
|
208
|
+
updateContext: noopAsync,
|
|
209
|
+
initializeSession: noopAsync,
|
|
210
|
+
endSession: noopAsync,
|
|
211
|
+
retryInitialization: noopAsync,
|
|
212
|
+
refetch: noopAsync,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const next = async (event) => {
|
|
216
|
+
const result = await client.funnel.navigate(event);
|
|
217
|
+
// Call custom onNavigate handler if provided
|
|
218
|
+
let shouldAutoRedirect = true;
|
|
219
|
+
if (onNavigate) {
|
|
220
|
+
const handlerResult = onNavigate(result);
|
|
221
|
+
// If handler explicitly returns false, skip auto-redirect
|
|
222
|
+
if (handlerResult === false) {
|
|
223
|
+
shouldAutoRedirect = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Default behavior: auto-redirect if result has a URL
|
|
227
|
+
if (shouldAutoRedirect && result?.url && typeof window !== 'undefined') {
|
|
228
|
+
console.log('🚀 [TagadaProvider] Auto-redirecting to:', result.url);
|
|
229
|
+
// Set pending redirect flag BEFORE navigation to prevent renders
|
|
230
|
+
setPendingRedirect(true);
|
|
231
|
+
window.location.href = result.url;
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
};
|
|
235
|
+
const goToStep = async (stepId) => {
|
|
236
|
+
return await next({
|
|
237
|
+
type: 'DIRECT_NAVIGATION',
|
|
238
|
+
data: { targetStepId: stepId },
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
// Helper to resolve accountId with fallbacks
|
|
242
|
+
const getAccountId = () => state.store?.accountId || state.pluginConfig?.accountId || state.session?.accountId || '';
|
|
243
|
+
return {
|
|
244
|
+
next,
|
|
245
|
+
goToStep,
|
|
246
|
+
updateContext: (updates) => client.funnel.updateContext(updates),
|
|
247
|
+
initializeSession: async (entryStepId) => {
|
|
248
|
+
const accountId = getAccountId();
|
|
249
|
+
if (!accountId || !state.auth.session || !state.store) {
|
|
250
|
+
throw new Error('Cannot initialize funnel: missing required data (accountId, session, or store)');
|
|
251
|
+
}
|
|
252
|
+
await client.funnel.initialize({ customerId: state.auth.session.customerId, sessionId: state.auth.session.sessionId }, { id: state.store.id, accountId }, funnelId, entryStepId);
|
|
253
|
+
},
|
|
254
|
+
endSession: () => client.funnel.endSession(),
|
|
255
|
+
retryInitialization: () => {
|
|
256
|
+
const accountId = getAccountId();
|
|
257
|
+
if (!accountId || !state.auth.session || !state.store) {
|
|
258
|
+
return Promise.reject(new Error('Cannot retry initialization: missing required data'));
|
|
259
|
+
}
|
|
260
|
+
return client
|
|
261
|
+
.funnel.autoInitialize({ customerId: state.auth.session.customerId, sessionId: state.auth.session.sessionId }, { id: state.store.id, accountId }, funnelId)
|
|
262
|
+
.then(() => { });
|
|
263
|
+
},
|
|
264
|
+
refetch: async () => {
|
|
265
|
+
await client.funnel.refreshSession();
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}, [client, state.auth.session, state.store, funnelId, onNavigate]);
|
|
163
269
|
const contextValue = {
|
|
270
|
+
client,
|
|
164
271
|
...state,
|
|
165
272
|
apiService,
|
|
273
|
+
pendingRedirect,
|
|
274
|
+
funnel: {
|
|
275
|
+
...funnelState,
|
|
276
|
+
currentStep: funnelState.context?.currentStepId ? { id: funnelState.context.currentStepId } : null,
|
|
277
|
+
...funnelMethods,
|
|
278
|
+
},
|
|
166
279
|
debugCheckout,
|
|
167
280
|
updateCheckoutDebugData,
|
|
168
281
|
debugFunnel,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone Adapter for TagadaPay SDK v2
|
|
3
|
+
*
|
|
4
|
+
* Provides a vanilla JS interface for using the SDK without a framework.
|
|
5
|
+
* Suitable for standard HTML/JS sites or other frameworks (Svelte, Angular, etc).
|
|
6
|
+
*
|
|
7
|
+
* This layer is a very thin wrapper over the Core SDK.
|
|
8
|
+
*/
|
|
9
|
+
import { TagadaClient, TagadaClientConfig, TagadaState } from '../core/client';
|
|
10
|
+
import { ApiClient } from '../core/resources/apiClient';
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create a Tagada Client instance.
|
|
13
|
+
* Features (like funnel) can be toggled via the config.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createTagadaClient(config?: TagadaClientConfig): TagadaClient;
|
|
16
|
+
export { TagadaClient, ApiClient };
|
|
17
|
+
export type { TagadaClientConfig, TagadaState };
|
|
18
|
+
export { FunnelActionType } from '../core/resources/funnel';
|
|
19
|
+
export type { FunnelAction, FunnelNavigationResult, SimpleFunnelContext } from '../core/resources/funnel';
|
|
20
|
+
export * from '../core/utils';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone Adapter for TagadaPay SDK v2
|
|
3
|
+
*
|
|
4
|
+
* Provides a vanilla JS interface for using the SDK without a framework.
|
|
5
|
+
* Suitable for standard HTML/JS sites or other frameworks (Svelte, Angular, etc).
|
|
6
|
+
*
|
|
7
|
+
* This layer is a very thin wrapper over the Core SDK.
|
|
8
|
+
*/
|
|
9
|
+
import { TagadaClient } from '../core/client';
|
|
10
|
+
import { ApiClient } from '../core/resources/apiClient';
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create a Tagada Client instance.
|
|
13
|
+
* Features (like funnel) can be toggled via the config.
|
|
14
|
+
*/
|
|
15
|
+
export function createTagadaClient(config = {}) {
|
|
16
|
+
return new TagadaClient(config);
|
|
17
|
+
}
|
|
18
|
+
// Re-export Core Classes
|
|
19
|
+
export { TagadaClient, ApiClient };
|
|
20
|
+
export { FunnelActionType } from '../core/resources/funnel';
|
|
21
|
+
// Re-export Utilities
|
|
22
|
+
export * from '../core/utils';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vue.js Adapter for TagadaPay SDK
|
|
4
|
+
*
|
|
5
|
+
* Provides Vue composables that wrap the Core SDK logic.
|
|
6
|
+
* Uses Vue's reactivity system to expose state from TagadaClient and FunnelClient.
|
|
7
|
+
*/
|
|
8
|
+
// TODO: Implement Vue adapter
|
|
9
|
+
// export function useTagada() { ... }
|
|
10
|
+
// export function useFunnel() { ... }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagadapay/plugin-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Modern React SDK for building Tagada Pay plugins",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"types": "./dist/v2/index.d.ts",
|
|
20
20
|
"import": "./dist/v2/index.js",
|
|
21
21
|
"require": "./dist/v2/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./v2/standalone": {
|
|
24
|
+
"types": "./dist/v2/standalone/index.d.ts",
|
|
25
|
+
"import": "./dist/v2/standalone/index.js",
|
|
26
|
+
"require": "./dist/v2/standalone/index.js"
|
|
22
27
|
}
|
|
23
28
|
},
|
|
24
29
|
"scripts": {
|