@tagadapay/plugin-sdk 2.6.14 → 2.6.17

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.
@@ -30,10 +30,14 @@ export interface RawPluginConfig<TConfig = Record<string, any>> {
30
30
  basePath?: string;
31
31
  config?: TConfig;
32
32
  }
33
+ export interface LocalDevConfig {
34
+ storeId: string;
35
+ accountId: string;
36
+ basePath: string;
37
+ }
33
38
  /**
34
39
  * Load plugin configuration (cached)
35
- * Tries raw config first, then production config
36
- * Note: Local dev config should be passed via rawConfig parameter
40
+ * Tries raw config first, then local dev config, then production config
37
41
  */
38
42
  export declare const loadPluginConfig: (configVariant?: string, rawConfig?: RawPluginConfig) => Promise<PluginConfig>;
39
43
  /**
@@ -22,6 +22,88 @@ import { useTagadaContext } from '../providers/TagadaProvider';
22
22
  // Simple cache for plugin configuration
23
23
  let cachedConfig = null;
24
24
  let configPromise = null;
25
+ /**
26
+ * Load local development configuration
27
+ * Combines .local.json + config/default.tgd.json (or specified variant)
28
+ */
29
+ const loadLocalDevConfig = async (configVariant = 'default') => {
30
+ try {
31
+ // Only try to load local config in development
32
+ // Use hostname-based detection for better Vite compatibility
33
+ const isLocalDev = typeof window !== 'undefined' &&
34
+ (window.location.hostname === 'localhost' ||
35
+ window.location.hostname.includes('ngrok-free.app') ||
36
+ window.location.hostname.includes('.localhost') ||
37
+ window.location.hostname.includes('127.0.0.1'));
38
+ if (!isLocalDev) {
39
+ return null;
40
+ }
41
+ // Load local store/account config
42
+ const localResponse = await fetch('/.local.json');
43
+ if (!localResponse.ok) {
44
+ return null;
45
+ }
46
+ const localConfig = await localResponse.json();
47
+ // Load deployment config (specified variant or fallback to default)
48
+ let config = {};
49
+ let configLoaded = false;
50
+ try {
51
+ // Try .tgd.json first (new format), then fallback to .json
52
+ let deploymentResponse = await fetch(`/config/${configVariant}.tgd.json`);
53
+ if (!deploymentResponse.ok) {
54
+ deploymentResponse = await fetch(`/config/${configVariant}.json`);
55
+ }
56
+ if (deploymentResponse.ok) {
57
+ config = await deploymentResponse.json();
58
+ configLoaded = true;
59
+ }
60
+ }
61
+ catch {
62
+ // Config fetch failed, will try fallback
63
+ }
64
+ // If config didn't load and it's not 'default', try fallback to default
65
+ if (!configLoaded && configVariant !== 'default') {
66
+ console.warn(`⚠️ Config variant '${configVariant}' not found, falling back to 'default'`);
67
+ try {
68
+ let defaultResponse = await fetch('/config/default.tgd.json');
69
+ if (!defaultResponse.ok) {
70
+ defaultResponse = await fetch('/config/default.json');
71
+ }
72
+ if (defaultResponse.ok) {
73
+ config = await defaultResponse.json();
74
+ configLoaded = true;
75
+ console.log(`✅ Fallback to 'default' config successful`);
76
+ }
77
+ }
78
+ catch {
79
+ // Default config also failed
80
+ }
81
+ }
82
+ // Final warning if no config was loaded
83
+ if (!configLoaded) {
84
+ if (configVariant === 'default') {
85
+ console.warn(`⚠️ No 'default' config found. Create /config/default.tgd.json`);
86
+ }
87
+ else {
88
+ console.warn(`⚠️ Neither '${configVariant}' nor 'default' config found. Create /config/default.tgd.json`);
89
+ }
90
+ }
91
+ console.log('🛠️ Using local development plugin config:', {
92
+ configName: config.configName || configVariant,
93
+ local: localConfig,
94
+ deployment: config
95
+ });
96
+ return {
97
+ storeId: localConfig.storeId,
98
+ accountId: localConfig.accountId,
99
+ basePath: localConfig.basePath,
100
+ config,
101
+ };
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ };
25
107
  /**
26
108
  * Load production config from headers and meta tags
27
109
  */
@@ -53,18 +135,12 @@ const loadProductionConfig = async () => {
53
135
  };
54
136
  /**
55
137
  * Load plugin configuration (cached)
56
- * Tries raw config first, then production config
57
- * Note: Local dev config should be passed via rawConfig parameter
138
+ * Tries raw config first, then local dev config, then production config
58
139
  */
59
140
  export const loadPluginConfig = async (configVariant = 'default', rawConfig) => {
60
141
  // If raw config is provided, use it directly
61
142
  if (rawConfig) {
62
- console.log('🛠️ Using raw plugin config:', {
63
- hasStoreId: !!rawConfig.storeId,
64
- hasAccountId: !!rawConfig.accountId,
65
- hasConfig: !!rawConfig.config,
66
- configKeys: rawConfig.config ? Object.keys(rawConfig.config) : [],
67
- });
143
+ console.log('🛠️ Using raw plugin config:', rawConfig);
68
144
  return {
69
145
  storeId: rawConfig.storeId,
70
146
  accountId: rawConfig.accountId,
@@ -72,7 +148,12 @@ export const loadPluginConfig = async (configVariant = 'default', rawConfig) =>
72
148
  config: rawConfig.config ?? {},
73
149
  };
74
150
  }
75
- // Fall back to production config (from headers/meta tags)
151
+ // Try local development config
152
+ const localConfig = await loadLocalDevConfig(configVariant);
153
+ if (localConfig) {
154
+ return localConfig;
155
+ }
156
+ // Fall back to production config
76
157
  return loadProductionConfig();
77
158
  };
78
159
  /**
@@ -21,5 +21,5 @@ export type { ApplyDiscountResponse, Discount, DiscountCodeValidation, RemoveDis
21
21
  export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './core/resources/vipOffers';
22
22
  export type { StoreConfig } from './core/resources/storeConfig';
23
23
  export type { FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from './core/resources/funnel';
24
- export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useCheckout, useCheckoutToken, useCountryOptions, useCurrency, useDiscount, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useVipOffers } from './react';
25
- export type { StoreDiscount } from './react';
24
+ export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useCheckout, useCheckoutToken, useCountryOptions, useCurrency, useDiscount, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
25
+ export type { StoreDiscount, TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './react';
package/dist/v2/index.js CHANGED
@@ -12,4 +12,4 @@ export * from './core/utils/currency';
12
12
  export * from './core/utils/pluginConfig';
13
13
  export * from './core/utils/products';
14
14
  // React exports (hooks and components only, types are exported above)
15
- export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useCheckout, useCheckoutToken, useCountryOptions, useCurrency, useDiscount, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useVipOffers } from './react';
15
+ export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useCheckout, useCheckoutToken, useCountryOptions, useCurrency, useDiscount, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
@@ -0,0 +1,46 @@
1
+ import { LanguageCode } from '../../../data/languages';
2
+ export type TranslationText = {
3
+ [key in LanguageCode | string]?: string;
4
+ };
5
+ export interface UseTranslationOptions {
6
+ defaultLanguage?: string;
7
+ }
8
+ export type TranslateFunction = (text: TranslationText | string | undefined, fallback?: string, languageCode?: string) => string;
9
+ export interface UseTranslationResult {
10
+ /**
11
+ * Translate function that handles multi-language text objects
12
+ * @param text - The text to translate (can be a string or MultiLangText object)
13
+ * @param fallback - Optional fallback text if translation is not found
14
+ * @param languageCode - Optional language code to override the current locale
15
+ * @returns The translated string
16
+ */
17
+ t: TranslateFunction;
18
+ /**
19
+ * Current locale code (e.g., 'en', 'fr', 'es')
20
+ */
21
+ locale: string;
22
+ }
23
+ /**
24
+ * Hook for translating multi-language text objects
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * const { t, locale } = useTranslation({ defaultLanguage: 'en' });
29
+ *
30
+ * const multiLangText = {
31
+ * en: 'Hello',
32
+ * fr: 'Bonjour',
33
+ * es: 'Hola'
34
+ * };
35
+ *
36
+ * // Translates to current locale
37
+ * const translated = t(multiLangText);
38
+ *
39
+ * // With fallback
40
+ * const withFallback = t(multiLangText, 'Default text');
41
+ *
42
+ * // Override locale
43
+ * const inFrench = t(multiLangText, '', 'fr');
44
+ * ```
45
+ */
46
+ export declare const useTranslation: (options?: UseTranslationOptions) => UseTranslationResult;
@@ -0,0 +1,61 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ /**
3
+ * Hook for translating multi-language text objects
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * const { t, locale } = useTranslation({ defaultLanguage: 'en' });
8
+ *
9
+ * const multiLangText = {
10
+ * en: 'Hello',
11
+ * fr: 'Bonjour',
12
+ * es: 'Hola'
13
+ * };
14
+ *
15
+ * // Translates to current locale
16
+ * const translated = t(multiLangText);
17
+ *
18
+ * // With fallback
19
+ * const withFallback = t(multiLangText, 'Default text');
20
+ *
21
+ * // Override locale
22
+ * const inFrench = t(multiLangText, '', 'fr');
23
+ * ```
24
+ */
25
+ export const useTranslation = (options = {}) => {
26
+ const { defaultLanguage = 'en' } = options;
27
+ // Get the current language from query params, browser, or fallback to default
28
+ const locale = useMemo(() => {
29
+ // Check for language query parameter
30
+ if (typeof window !== 'undefined') {
31
+ const urlParams = new URLSearchParams(window.location.search);
32
+ const langFromQuery = urlParams.get('language');
33
+ if (langFromQuery)
34
+ return langFromQuery;
35
+ }
36
+ // Try to get browser language
37
+ if (typeof navigator !== 'undefined') {
38
+ const browserLang = navigator.language.split('-')[0]; // e.g., 'en-US' -> 'en'
39
+ return browserLang;
40
+ }
41
+ return defaultLanguage;
42
+ }, [defaultLanguage]);
43
+ const t = useCallback((text, fallback, languageCode) => {
44
+ if (!text)
45
+ return fallback || '';
46
+ if (typeof text === 'string')
47
+ return text;
48
+ const targetLocale = languageCode || locale;
49
+ if (text[targetLocale])
50
+ return text[targetLocale];
51
+ if (fallback)
52
+ return fallback;
53
+ if (text[defaultLanguage])
54
+ return text[defaultLanguage];
55
+ const firstAvailable = Object.values(text).find((value) => value !== undefined && value !== '');
56
+ if (firstAvailable)
57
+ return firstAvailable;
58
+ return '';
59
+ }, [locale, defaultLanguage]);
60
+ return { t, locale };
61
+ };
@@ -12,6 +12,7 @@ export { useGeoLocation } from './hooks/useGeoLocation';
12
12
  export { useGoogleAutocomplete } from './hooks/useGoogleAutocomplete';
13
13
  export { getAvailableLanguages, useCountryOptions, useISOData, useLanguageImport, useRegionOptions } from './hooks/useISOData';
14
14
  export { usePluginConfig } from './hooks/usePluginConfig';
15
+ export { useTranslation } from './hooks/useTranslation';
15
16
  export { queryKeys, useApiMutation, useApiQuery, useInvalidateQuery, usePreloadQuery } from './hooks/useApiQuery';
16
17
  export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
17
18
  export { useCurrency } from './hooks/useCurrency';
@@ -38,6 +39,7 @@ export type { GeoLocationData, UseGeoLocationOptions, UseGeoLocationReturn } fro
38
39
  export type { ExtractedAddress, GooglePlaceDetails, GooglePrediction, UseGoogleAutocompleteOptions, UseGoogleAutocompleteResult } from './hooks/useGoogleAutocomplete';
39
40
  export type { ISOCountry, ISORegion, UseISODataResult } from './hooks/useISOData';
40
41
  export type { UsePluginConfigOptions, UsePluginConfigResult } from './hooks/usePluginConfig';
42
+ export type { TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './hooks/useTranslation';
41
43
  export type { UseCheckoutQueryOptions as UseCheckoutOptions, UseCheckoutQueryResult as UseCheckoutResult } from './hooks/useCheckoutQuery';
42
44
  export type { StoreDiscount, UseDiscountQueryOptions as UseDiscountOptions, UseDiscountQueryResult as UseDiscountResult } from './hooks/useDiscountQuery';
43
45
  export type { UseDiscountsQueryOptions as UseDiscountsOptions, UseDiscountsQueryResult as UseDiscountsResult } from './hooks/useDiscountsQuery';
@@ -15,6 +15,7 @@ export { useGeoLocation } from './hooks/useGeoLocation';
15
15
  export { useGoogleAutocomplete } from './hooks/useGoogleAutocomplete';
16
16
  export { getAvailableLanguages, useCountryOptions, useISOData, useLanguageImport, useRegionOptions } from './hooks/useISOData';
17
17
  export { usePluginConfig } from './hooks/usePluginConfig';
18
+ export { useTranslation } from './hooks/useTranslation';
18
19
  // TanStack Query hooks (recommended)
19
20
  export { queryKeys, useApiMutation, useApiQuery, useInvalidateQuery, usePreloadQuery } from './hooks/useApiQuery';
20
21
  export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
@@ -52,11 +52,12 @@ interface TagadaProviderProps {
52
52
  environment?: Environment;
53
53
  customApiConfig?: Partial<EnvironmentConfig>;
54
54
  debugMode?: boolean;
55
+ localConfig?: string;
55
56
  blockUntilSessionReady?: boolean;
56
57
  rawPluginConfig?: RawPluginConfig;
57
58
  }
58
59
  export declare function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
59
- blockUntilSessionReady, // Default to new non-blocking behavior
60
+ localConfig, blockUntilSessionReady, // Default to new non-blocking behavior
60
61
  rawPluginConfig, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
61
62
  export declare function useTagadaContext(): TagadaContextValue;
62
63
  export {};
@@ -52,7 +52,7 @@ const TagadaContext = createContext(null);
52
52
  let globalTagadaInstance = null;
53
53
  let globalTagadaInitialized = false;
54
54
  export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
55
- blockUntilSessionReady = false, // Default to new non-blocking behavior
55
+ localConfig, blockUntilSessionReady = false, // Default to new non-blocking behavior
56
56
  rawPluginConfig, }) {
57
57
  // Instance tracking
58
58
  const [instanceId] = useState(() => {
@@ -81,13 +81,20 @@ rawPluginConfig, }) {
81
81
  }
82
82
  };
83
83
  }, [instanceId]);
84
+ // LOCAL DEV ONLY: Use localConfig override if in local development, otherwise use default
85
+ const isLocalDev = typeof window !== 'undefined' &&
86
+ (window.location.hostname === 'localhost' ||
87
+ window.location.hostname.includes('.localhost') ||
88
+ window.location.hostname.includes('127.0.0.1'));
89
+ const configVariant = isLocalDev ? localConfig || 'default' : 'default';
84
90
  // Debug logging (only log once during initial render)
85
91
  const hasLoggedRef = useRef(false);
86
92
  if (!hasLoggedRef.current) {
87
- console.log(`🔍 [TagadaProvider] Instance ${instanceId} Initializing with:`, {
93
+ console.log(`🔍 [TagadaProvider] Instance ${instanceId} Config Debug:`, {
88
94
  hostname: typeof window !== 'undefined' ? window.location.hostname : 'SSR',
89
- hasRawConfig: !!rawPluginConfig,
90
- rawConfigStoreId: rawPluginConfig?.storeId,
95
+ isLocalDev,
96
+ localConfig,
97
+ configVariant,
91
98
  });
92
99
  hasLoggedRef.current = true;
93
100
  }
@@ -95,10 +102,6 @@ rawPluginConfig, }) {
95
102
  // Initialize with raw config if available to avoid empty config during loading
96
103
  const [pluginConfig, setPluginConfig] = useState(() => {
97
104
  if (rawPluginConfig) {
98
- console.log('🛠️ [TagadaProvider] Using raw plugin config immediately:', {
99
- storeId: rawPluginConfig.storeId,
100
- accountId: rawPluginConfig.accountId,
101
- });
102
105
  return {
103
106
  storeId: rawPluginConfig.storeId,
104
107
  accountId: rawPluginConfig.accountId,
@@ -106,11 +109,9 @@ rawPluginConfig, }) {
106
109
  config: rawPluginConfig.config ?? {},
107
110
  };
108
111
  }
109
- console.log('⏳ [TagadaProvider] No raw config, will load from headers/files');
110
112
  return { basePath: '/', config: {} };
111
113
  });
112
114
  const [configLoading, setConfigLoading] = useState(!rawPluginConfig);
113
- const [apiClientReady, setApiClientReady] = useState(false);
114
115
  // Load plugin config on mount with the specified variant
115
116
  useEffect(() => {
116
117
  // Prevent multiple config loads
@@ -120,8 +121,8 @@ rawPluginConfig, }) {
120
121
  }
121
122
  const loadConfig = async () => {
122
123
  try {
123
- // Use the v2 core loadPluginConfig function (only used when no rawPluginConfig)
124
- const config = await loadPluginConfig('default', rawPluginConfig);
124
+ // Use the v2 core loadPluginConfig function
125
+ const config = await loadPluginConfig(configVariant, rawPluginConfig);
125
126
  // Ensure we have required store ID before proceeding
126
127
  if (!config.storeId) {
127
128
  console.warn('⚠️ No store ID found in plugin config. This may cause hooks to fail.');
@@ -132,7 +133,7 @@ rawPluginConfig, }) {
132
133
  accountId: config.accountId,
133
134
  basePath: config.basePath,
134
135
  hasConfig: !!config.config,
135
- source: rawPluginConfig ? 'raw' : 'headers/files',
136
+ source: rawPluginConfig ? 'raw' : 'file',
136
137
  });
137
138
  if (blockUntilSessionReady) {
138
139
  console.log('⏳ Blocking mode: Children will render after Phase 3 (session init) completes');
@@ -150,7 +151,7 @@ rawPluginConfig, }) {
150
151
  }
151
152
  };
152
153
  void loadConfig();
153
- }, [rawPluginConfig]);
154
+ }, [configVariant, rawPluginConfig]);
154
155
  // Extract store/account IDs from plugin config (only source now)
155
156
  const storeId = pluginConfig.storeId;
156
157
  const _accountId = pluginConfig.accountId;
@@ -216,8 +217,7 @@ rawPluginConfig, }) {
216
217
  });
217
218
  // Set the global client for TanStack Query hooks
218
219
  setGlobalApiClient(client);
219
- setApiClientReady(true);
220
- console.log('[SDK] ✅ ApiClient initialized with baseURL:', environmentConfig.apiConfig.baseUrl);
220
+ console.log('[SDK] ApiClient initialized with baseURL:', environmentConfig.apiConfig.baseUrl);
221
221
  }, [environmentConfig]);
222
222
  // Sync token updates between ApiService and ApiClient
223
223
  useEffect(() => {
@@ -669,16 +669,9 @@ rawPluginConfig, }) {
669
669
  // debugCheckout removed from deps to prevent unnecessary re-renders
670
670
  ]);
671
671
  // Determine if we should show loading
672
- // Always block until BOTH config is loaded AND API client is ready
673
- const shouldShowLoading = configLoading || !apiClientReady;
674
- const canRenderChildren = !configLoading && apiClientReady;
675
- if (!hasLoggedRef.current && canRenderChildren) {
676
- console.log('✅ [TagadaProvider] All initialization complete, ready to render children:', {
677
- configLoading,
678
- apiClientReady,
679
- hasStoreId: !!pluginConfig.storeId,
680
- });
681
- }
672
+ // Always block until config is loaded (even if empty)
673
+ const shouldShowLoading = configLoading;
674
+ const canRenderChildren = !configLoading;
682
675
  // Initialize TanStack Query client
683
676
  const [queryClient] = useState(() => new QueryClient({
684
677
  defaultOptions: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "2.6.14",
3
+ "version": "2.6.17",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",