@tagadapay/plugin-sdk 1.0.30 β 2.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 +546 -414
- package/dist/data/iso3166.d.ts +30 -0
- package/dist/data/iso3166.js +102 -0
- package/dist/react/hooks/useAddress.js +19 -19
- package/dist/react/hooks/useAddressV2.d.ts +53 -0
- package/dist/react/hooks/useAddressV2.js +379 -0
- package/dist/react/hooks/useGoogleAutocomplete.d.ts +69 -0
- package/dist/react/hooks/useGoogleAutocomplete.js +219 -0
- package/dist/react/hooks/useISOData.d.ts +41 -0
- package/dist/react/hooks/useISOData.js +127 -0
- package/dist/react/hooks/usePluginConfig.d.ts +53 -0
- package/dist/react/hooks/usePluginConfig.js +190 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/providers/TagadaProvider.d.ts +5 -1
- package/dist/react/providers/TagadaProvider.js +53 -7
- package/package.json +67 -66
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* React hook for Google Places Autocomplete with automatic script injection
|
|
4
|
+
* Automatically loads the Google Maps JavaScript API with Places library
|
|
5
|
+
*/
|
|
6
|
+
export function useGoogleAutocomplete(options) {
|
|
7
|
+
const { apiKey, libraries = ['places'], version = 'weekly', language, region } = options;
|
|
8
|
+
const [predictions, setPredictions] = useState([]);
|
|
9
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
10
|
+
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
|
|
11
|
+
const autocompleteServiceRef = useRef(null);
|
|
12
|
+
const placesServiceRef = useRef(null);
|
|
13
|
+
const scriptLoadedRef = useRef(false);
|
|
14
|
+
// Inject Google Maps script
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
console.error('Google Maps API key is required');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (scriptLoadedRef.current || window.google?.maps) {
|
|
21
|
+
setIsScriptLoaded(true);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Check if script is already being loaded
|
|
25
|
+
const existingScript = document.querySelector('script[src*="maps.googleapis.com"]');
|
|
26
|
+
if (existingScript) {
|
|
27
|
+
// Wait for existing script to load
|
|
28
|
+
const checkLoaded = () => {
|
|
29
|
+
if (window.google?.maps?.places?.AutocompleteService) {
|
|
30
|
+
setIsScriptLoaded(true);
|
|
31
|
+
scriptLoadedRef.current = true;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
setTimeout(checkLoaded, 100);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
checkLoaded();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Create and inject the script
|
|
41
|
+
const script = document.createElement('script');
|
|
42
|
+
const params = new URLSearchParams({
|
|
43
|
+
key: apiKey,
|
|
44
|
+
libraries: libraries.join(','),
|
|
45
|
+
v: version,
|
|
46
|
+
});
|
|
47
|
+
if (language)
|
|
48
|
+
params.append('language', language);
|
|
49
|
+
if (region)
|
|
50
|
+
params.append('region', region);
|
|
51
|
+
script.src = `https://maps.googleapis.com/maps/api/js?${params.toString()}`;
|
|
52
|
+
script.async = true;
|
|
53
|
+
script.defer = true;
|
|
54
|
+
script.onload = () => {
|
|
55
|
+
console.log('πΊοΈ Google Maps API loaded successfully');
|
|
56
|
+
setIsScriptLoaded(true);
|
|
57
|
+
scriptLoadedRef.current = true;
|
|
58
|
+
};
|
|
59
|
+
script.onerror = () => {
|
|
60
|
+
console.error('Failed to load Google Maps API');
|
|
61
|
+
};
|
|
62
|
+
document.head.appendChild(script);
|
|
63
|
+
// Cleanup function
|
|
64
|
+
return () => {
|
|
65
|
+
// Note: We don't remove the script as it might be used by other components
|
|
66
|
+
// The script will remain in the DOM for the session
|
|
67
|
+
};
|
|
68
|
+
}, [apiKey, libraries, version, language, region]);
|
|
69
|
+
// Initialize Google Places services
|
|
70
|
+
const initializeServices = useCallback(() => {
|
|
71
|
+
if (typeof window === 'undefined')
|
|
72
|
+
return false;
|
|
73
|
+
if (!window.google?.maps?.places?.AutocompleteService) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (!autocompleteServiceRef.current) {
|
|
77
|
+
autocompleteServiceRef.current = new window.google.maps.places.AutocompleteService();
|
|
78
|
+
}
|
|
79
|
+
if (!placesServiceRef.current) {
|
|
80
|
+
// Create a dummy map for PlacesService (required by Google API)
|
|
81
|
+
const map = new window.google.maps.Map(document.createElement('div'));
|
|
82
|
+
placesServiceRef.current = new window.google.maps.places.PlacesService(map);
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}, []);
|
|
86
|
+
// Search for place predictions
|
|
87
|
+
const searchPlaces = useCallback((input, countryRestriction) => {
|
|
88
|
+
if (!isScriptLoaded) {
|
|
89
|
+
console.warn('Google Maps API not yet loaded');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!initializeServices()) {
|
|
93
|
+
console.error('Google Places services not available');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (input.length < 3) {
|
|
97
|
+
setPredictions([]);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
setIsLoading(true);
|
|
101
|
+
const request = {
|
|
102
|
+
input,
|
|
103
|
+
...(countryRestriction && {
|
|
104
|
+
componentRestrictions: { country: countryRestriction.toLowerCase() },
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
autocompleteServiceRef.current.getPlacePredictions(request, (results, status) => {
|
|
108
|
+
setIsLoading(false);
|
|
109
|
+
if (status === window.google.maps.places.PlacesServiceStatus.OK && results) {
|
|
110
|
+
setPredictions(results);
|
|
111
|
+
console.log(`π Found ${results.length} predictions for "${input}"`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
setPredictions([]);
|
|
115
|
+
if (status !== window.google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
|
|
116
|
+
console.warn('Google Places prediction failed:', status);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}, [initializeServices, isScriptLoaded]);
|
|
121
|
+
// Get detailed place information
|
|
122
|
+
const getPlaceDetails = useCallback((placeId) => {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
if (!isScriptLoaded) {
|
|
125
|
+
console.warn('Google Maps API not yet loaded');
|
|
126
|
+
resolve(null);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!initializeServices()) {
|
|
130
|
+
console.error('Google Places services not available');
|
|
131
|
+
resolve(null);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const request = {
|
|
135
|
+
placeId,
|
|
136
|
+
fields: ['address_components', 'formatted_address'],
|
|
137
|
+
};
|
|
138
|
+
placesServiceRef.current.getDetails(request, (place, status) => {
|
|
139
|
+
if (status === window.google.maps.places.PlacesServiceStatus.OK && place) {
|
|
140
|
+
console.log('π Got place details:', place);
|
|
141
|
+
resolve(place);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.error('Failed to get place details:', status);
|
|
145
|
+
resolve(null);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}, [initializeServices, isScriptLoaded]);
|
|
150
|
+
// Extract structured address components from Google place
|
|
151
|
+
const extractAddressComponents = useCallback((place) => {
|
|
152
|
+
const extracted = {
|
|
153
|
+
streetNumber: '',
|
|
154
|
+
route: '',
|
|
155
|
+
locality: '',
|
|
156
|
+
administrativeAreaLevel1: '',
|
|
157
|
+
administrativeAreaLevel1Long: '',
|
|
158
|
+
country: '',
|
|
159
|
+
postalCode: '',
|
|
160
|
+
};
|
|
161
|
+
console.log('ποΈ Extracted address components:', place.address_components);
|
|
162
|
+
place.address_components?.forEach((component) => {
|
|
163
|
+
const types = component.types;
|
|
164
|
+
if (types.includes('street_number')) {
|
|
165
|
+
extracted.streetNumber = component.long_name;
|
|
166
|
+
}
|
|
167
|
+
if (types.includes('route')) {
|
|
168
|
+
extracted.route = component.long_name;
|
|
169
|
+
}
|
|
170
|
+
if (types.includes('locality') || types.includes('administrative_area_level_2')) {
|
|
171
|
+
extracted.locality = component.long_name;
|
|
172
|
+
}
|
|
173
|
+
if (types.includes('administrative_area_level_1')) {
|
|
174
|
+
extracted.administrativeAreaLevel1 = component.short_name;
|
|
175
|
+
extracted.administrativeAreaLevel1Long = component.long_name;
|
|
176
|
+
}
|
|
177
|
+
if (types.includes('country')) {
|
|
178
|
+
extracted.country = component.short_name;
|
|
179
|
+
}
|
|
180
|
+
if (types.includes('postal_code')) {
|
|
181
|
+
extracted.postalCode = component.long_name;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
console.log('ποΈ Extracted address components:', extracted);
|
|
185
|
+
return extracted;
|
|
186
|
+
}, []);
|
|
187
|
+
// Clear predictions
|
|
188
|
+
const clearPredictions = useCallback(() => {
|
|
189
|
+
setPredictions([]);
|
|
190
|
+
}, []);
|
|
191
|
+
return {
|
|
192
|
+
predictions,
|
|
193
|
+
isLoading,
|
|
194
|
+
isScriptLoaded,
|
|
195
|
+
searchPlaces,
|
|
196
|
+
getPlaceDetails,
|
|
197
|
+
extractAddressComponents,
|
|
198
|
+
clearPredictions,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Hook to check if Google Maps API is loaded
|
|
203
|
+
* @deprecated Use useGoogleAutocomplete with isScriptLoaded instead
|
|
204
|
+
*/
|
|
205
|
+
export function useGoogleMapsLoaded() {
|
|
206
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const checkLoaded = () => {
|
|
209
|
+
const loaded = !!window.google?.maps?.places?.AutocompleteService;
|
|
210
|
+
setIsLoaded(loaded);
|
|
211
|
+
if (!loaded) {
|
|
212
|
+
// Check again in 100ms
|
|
213
|
+
setTimeout(checkLoaded, 100);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
checkLoaded();
|
|
217
|
+
}, []);
|
|
218
|
+
return isLoaded;
|
|
219
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface ISOCountry {
|
|
2
|
+
iso: string;
|
|
3
|
+
iso3: string;
|
|
4
|
+
numeric: number;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ISORegion {
|
|
8
|
+
iso: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
export interface UseISODataResult {
|
|
12
|
+
countries: Record<string, ISOCountry>;
|
|
13
|
+
getRegions: (countryCode: string) => ISORegion[];
|
|
14
|
+
findRegion: (countryCode: string, regionCode: string) => ISORegion | null;
|
|
15
|
+
mapGoogleToISO: (googleState: string, googleStateLong: string, countryCode: string) => ISORegion | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* React hook for accessing ISO3166 countries and regions data
|
|
19
|
+
* @param language - Language code (en, fr, de, es, etc.)
|
|
20
|
+
* @param disputeSetting - Territorial dispute perspective (UN, RU, UA, TR)
|
|
21
|
+
* @returns Object with countries data and helper functions
|
|
22
|
+
*/
|
|
23
|
+
export declare function useISOData(language?: string, disputeSetting?: string): UseISODataResult;
|
|
24
|
+
/**
|
|
25
|
+
* Get available languages for ISO data
|
|
26
|
+
*/
|
|
27
|
+
export declare function getAvailableLanguages(): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Get list of countries as options for select components
|
|
30
|
+
*/
|
|
31
|
+
export declare function useCountryOptions(language?: string): {
|
|
32
|
+
value: string;
|
|
33
|
+
label: string;
|
|
34
|
+
}[];
|
|
35
|
+
/**
|
|
36
|
+
* Get list of regions/states for a country as options for select components
|
|
37
|
+
*/
|
|
38
|
+
export declare function useRegionOptions(countryCode: string, language?: string): {
|
|
39
|
+
value: string;
|
|
40
|
+
label: string;
|
|
41
|
+
}[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
4
|
+
import { getDataSet, reduce } from 'iso3166-2-db';
|
|
5
|
+
/**
|
|
6
|
+
* React hook for accessing ISO3166 countries and regions data
|
|
7
|
+
* @param language - Language code (en, fr, de, es, etc.)
|
|
8
|
+
* @param disputeSetting - Territorial dispute perspective (UN, RU, UA, TR)
|
|
9
|
+
* @returns Object with countries data and helper functions
|
|
10
|
+
*/
|
|
11
|
+
export function useISOData(language = 'en', disputeSetting = 'UN') {
|
|
12
|
+
const data = useMemo(() => {
|
|
13
|
+
try {
|
|
14
|
+
// Get the dataset using the iso3166-2-db library
|
|
15
|
+
const worldDatabase = reduce(getDataSet(), language);
|
|
16
|
+
// Transform to our expected format
|
|
17
|
+
const countries = {};
|
|
18
|
+
Object.keys(worldDatabase).forEach((countryCode) => {
|
|
19
|
+
const countryData = worldDatabase[countryCode];
|
|
20
|
+
countries[countryData.iso] = {
|
|
21
|
+
iso: countryData.iso,
|
|
22
|
+
iso3: countryData.iso3,
|
|
23
|
+
numeric: countryData.numeric,
|
|
24
|
+
name: countryData.name,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
// Helper to load regions for a specific country
|
|
28
|
+
const getRegions = (countryCode) => {
|
|
29
|
+
try {
|
|
30
|
+
const countryData = worldDatabase[countryCode];
|
|
31
|
+
if (!countryData?.regions) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return Object.keys(countryData.regions).map((regionCode) => {
|
|
35
|
+
const regionData = countryData.regions[regionCode];
|
|
36
|
+
return {
|
|
37
|
+
iso: regionData.iso,
|
|
38
|
+
name: regionData.name,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return []; // Return empty array if no regions
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// Find a specific region by ISO code
|
|
47
|
+
const findRegion = (countryCode, regionCode) => {
|
|
48
|
+
const regions = getRegions(countryCode);
|
|
49
|
+
return regions.find((region) => region.iso === regionCode) ?? null;
|
|
50
|
+
};
|
|
51
|
+
// Map Google Places state to ISO region (proven 100% success rate)
|
|
52
|
+
const mapGoogleToISO = (googleState, googleStateLong, countryCode) => {
|
|
53
|
+
const regions = getRegions(countryCode);
|
|
54
|
+
if (regions.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
// Strategy 1: Exact ISO code match (86% success rate)
|
|
57
|
+
let match = regions.find((r) => r.iso === googleState);
|
|
58
|
+
if (match)
|
|
59
|
+
return match;
|
|
60
|
+
// Strategy 2: Name matching (14% success rate)
|
|
61
|
+
match = regions.find((r) => r.name.toLowerCase() === googleState.toLowerCase());
|
|
62
|
+
if (match)
|
|
63
|
+
return match;
|
|
64
|
+
match = regions.find((r) => r.name.toLowerCase() === googleStateLong.toLowerCase());
|
|
65
|
+
if (match)
|
|
66
|
+
return match;
|
|
67
|
+
// Strategy 3: Partial name matching (fallback)
|
|
68
|
+
match = regions.find((r) => r.name.toLowerCase().includes(googleStateLong.toLowerCase()) ||
|
|
69
|
+
googleStateLong.toLowerCase().includes(r.name.toLowerCase()));
|
|
70
|
+
return match ?? null;
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
countries,
|
|
74
|
+
getRegions,
|
|
75
|
+
findRegion,
|
|
76
|
+
mapGoogleToISO,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`Failed to load ISO data for language: ${language}`, error);
|
|
81
|
+
return {
|
|
82
|
+
countries: {},
|
|
83
|
+
getRegions: () => [],
|
|
84
|
+
findRegion: () => null,
|
|
85
|
+
mapGoogleToISO: () => null,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}, [language, disputeSetting]);
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get available languages for ISO data
|
|
93
|
+
*/
|
|
94
|
+
export function getAvailableLanguages() {
|
|
95
|
+
return ['en', 'fr', 'de', 'es', 'ru', 'zh', 'ar', 'it', 'pt', 'ja', 'hi'];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get list of countries as options for select components
|
|
99
|
+
*/
|
|
100
|
+
export function useCountryOptions(language = 'en') {
|
|
101
|
+
const { countries } = useISOData(language);
|
|
102
|
+
return useMemo(() => {
|
|
103
|
+
return Object.entries(countries)
|
|
104
|
+
.map(([code, country]) => ({
|
|
105
|
+
value: code,
|
|
106
|
+
label: country.name,
|
|
107
|
+
}))
|
|
108
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
109
|
+
}, [countries]);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get list of regions/states for a country as options for select components
|
|
113
|
+
*/
|
|
114
|
+
export function useRegionOptions(countryCode, language = 'en') {
|
|
115
|
+
const { getRegions } = useISOData(language);
|
|
116
|
+
return useMemo(() => {
|
|
117
|
+
if (!countryCode)
|
|
118
|
+
return [];
|
|
119
|
+
const regions = getRegions(countryCode);
|
|
120
|
+
return regions
|
|
121
|
+
.map((region) => ({
|
|
122
|
+
value: region.iso,
|
|
123
|
+
label: region.name,
|
|
124
|
+
}))
|
|
125
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
126
|
+
}, [countryCode, getRegions]);
|
|
127
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Configuration Hook
|
|
3
|
+
*
|
|
4
|
+
* Professional SDK approach to access plugin configuration:
|
|
5
|
+
* - Store ID, Account ID, Base Path: from .local.json (dev) or headers (production)
|
|
6
|
+
* - Deployment Config: from config/*.json (dev) or meta tags (production)
|
|
7
|
+
*/
|
|
8
|
+
export interface PluginConfig {
|
|
9
|
+
storeId?: string;
|
|
10
|
+
accountId?: string;
|
|
11
|
+
basePath?: string;
|
|
12
|
+
config?: Record<string, any>;
|
|
13
|
+
}
|
|
14
|
+
export interface LocalDevConfig {
|
|
15
|
+
storeId: string;
|
|
16
|
+
accountId: string;
|
|
17
|
+
basePath: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load plugin configuration (cached)
|
|
21
|
+
* Tries local dev config first, then production config
|
|
22
|
+
*/
|
|
23
|
+
export declare const loadPluginConfig: (configVariant?: string) => Promise<PluginConfig>;
|
|
24
|
+
/**
|
|
25
|
+
* Main hook for plugin configuration
|
|
26
|
+
* Gets config from TagadaProvider context (no parameters needed)
|
|
27
|
+
*/
|
|
28
|
+
export declare const usePluginConfig: () => {
|
|
29
|
+
storeId: string | undefined;
|
|
30
|
+
accountId: string | undefined;
|
|
31
|
+
basePath: string;
|
|
32
|
+
config: Record<string, any>;
|
|
33
|
+
loading: boolean;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Specific hook for basePath only
|
|
37
|
+
*/
|
|
38
|
+
export declare const useBasePath: () => {
|
|
39
|
+
basePath: string;
|
|
40
|
+
loading: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Get cached config directly (for non-React usage)
|
|
44
|
+
*/
|
|
45
|
+
export declare const getPluginConfig: (configVariant?: string) => Promise<PluginConfig>;
|
|
46
|
+
/**
|
|
47
|
+
* Clear the config cache (useful for testing)
|
|
48
|
+
*/
|
|
49
|
+
export declare const clearPluginConfigCache: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Development helper to log current configuration
|
|
52
|
+
*/
|
|
53
|
+
export declare const debugPluginConfig: (configVariant?: string) => Promise<void>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Configuration Hook
|
|
3
|
+
*
|
|
4
|
+
* Professional SDK approach to access plugin configuration:
|
|
5
|
+
* - Store ID, Account ID, Base Path: from .local.json (dev) or headers (production)
|
|
6
|
+
* - Deployment Config: from config/*.json (dev) or meta tags (production)
|
|
7
|
+
*/
|
|
8
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
+
// Simple cache for plugin configuration
|
|
10
|
+
let cachedConfig = null;
|
|
11
|
+
let configPromise = null;
|
|
12
|
+
/**
|
|
13
|
+
* Load local development configuration
|
|
14
|
+
* Combines .local.json + config/default.tgd.json (or specified variant)
|
|
15
|
+
*/
|
|
16
|
+
const loadLocalDevConfig = async (configVariant = 'default') => {
|
|
17
|
+
try {
|
|
18
|
+
// Only try to load local config in development
|
|
19
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
// Load local store/account config
|
|
23
|
+
const localResponse = await fetch('/.local.json');
|
|
24
|
+
if (!localResponse.ok) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const localConfig = await localResponse.json();
|
|
28
|
+
// Load deployment config (specified variant or fallback to default)
|
|
29
|
+
let config = {};
|
|
30
|
+
let configLoaded = false;
|
|
31
|
+
try {
|
|
32
|
+
// Try .tgd.json first (new format), then fallback to .json
|
|
33
|
+
let deploymentResponse = await fetch(`/config/${configVariant}.tgd.json`);
|
|
34
|
+
if (!deploymentResponse.ok) {
|
|
35
|
+
deploymentResponse = await fetch(`/config/${configVariant}.json`);
|
|
36
|
+
}
|
|
37
|
+
if (deploymentResponse.ok) {
|
|
38
|
+
config = await deploymentResponse.json();
|
|
39
|
+
configLoaded = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Config fetch failed, will try fallback
|
|
44
|
+
}
|
|
45
|
+
// If config didn't load and it's not 'default', try fallback to default
|
|
46
|
+
if (!configLoaded && configVariant !== 'default') {
|
|
47
|
+
console.warn(`β οΈ Config variant '${configVariant}' not found, falling back to 'default'`);
|
|
48
|
+
try {
|
|
49
|
+
let defaultResponse = await fetch('/config/default.tgd.json');
|
|
50
|
+
if (!defaultResponse.ok) {
|
|
51
|
+
defaultResponse = await fetch('/config/default.json');
|
|
52
|
+
}
|
|
53
|
+
if (defaultResponse.ok) {
|
|
54
|
+
config = await defaultResponse.json();
|
|
55
|
+
configLoaded = true;
|
|
56
|
+
console.log(`β
Fallback to 'default' config successful`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Default config also failed
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Final warning if no config was loaded
|
|
64
|
+
if (!configLoaded) {
|
|
65
|
+
if (configVariant === 'default') {
|
|
66
|
+
console.warn(`β οΈ No 'default' config found. Create /config/default.tgd.json`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.warn(`β οΈ Neither '${configVariant}' nor 'default' config found. Create /config/default.tgd.json`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.log('π οΈ Using local development plugin config:', {
|
|
73
|
+
configName: config.configName || configVariant,
|
|
74
|
+
local: localConfig,
|
|
75
|
+
deployment: config
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
storeId: localConfig.storeId,
|
|
79
|
+
accountId: localConfig.accountId,
|
|
80
|
+
basePath: localConfig.basePath,
|
|
81
|
+
config,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Load production config from headers and meta tags
|
|
90
|
+
*/
|
|
91
|
+
const loadProductionConfig = async () => {
|
|
92
|
+
try {
|
|
93
|
+
// Get headers
|
|
94
|
+
const response = await fetch(window.location.href, { method: 'HEAD' });
|
|
95
|
+
const storeId = response.headers.get('X-Plugin-Store-Id') || undefined;
|
|
96
|
+
const accountId = response.headers.get('X-Plugin-Account-Id') || undefined;
|
|
97
|
+
const basePath = response.headers.get('X-Plugin-Base-Path') || '/';
|
|
98
|
+
// Get deployment config from meta tags
|
|
99
|
+
let config = {};
|
|
100
|
+
try {
|
|
101
|
+
const configMeta = document.querySelector('meta[name="x-plugin-config"]');
|
|
102
|
+
const encodedConfig = configMeta?.getAttribute('content');
|
|
103
|
+
if (encodedConfig) {
|
|
104
|
+
const decodedConfig = decodeURIComponent(encodedConfig);
|
|
105
|
+
config = JSON.parse(decodedConfig);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Deployment config is optional
|
|
110
|
+
}
|
|
111
|
+
return { storeId, accountId, basePath, config };
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return { basePath: '/', config: {} };
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Load plugin configuration (cached)
|
|
119
|
+
* Tries local dev config first, then production config
|
|
120
|
+
*/
|
|
121
|
+
export const loadPluginConfig = async (configVariant = 'default') => {
|
|
122
|
+
// Try local development config first
|
|
123
|
+
const localConfig = await loadLocalDevConfig(configVariant);
|
|
124
|
+
if (localConfig) {
|
|
125
|
+
return localConfig;
|
|
126
|
+
}
|
|
127
|
+
// Fall back to production config
|
|
128
|
+
return loadProductionConfig();
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Main hook for plugin configuration
|
|
132
|
+
* Gets config from TagadaProvider context (no parameters needed)
|
|
133
|
+
*/
|
|
134
|
+
export const usePluginConfig = () => {
|
|
135
|
+
const context = useTagadaContext();
|
|
136
|
+
const { pluginConfig, pluginConfigLoading } = context;
|
|
137
|
+
return {
|
|
138
|
+
storeId: pluginConfig.storeId,
|
|
139
|
+
accountId: pluginConfig.accountId,
|
|
140
|
+
basePath: pluginConfig.basePath ?? '/',
|
|
141
|
+
config: pluginConfig.config ?? {},
|
|
142
|
+
loading: pluginConfigLoading,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Specific hook for basePath only
|
|
147
|
+
*/
|
|
148
|
+
export const useBasePath = () => {
|
|
149
|
+
const { basePath, loading } = usePluginConfig();
|
|
150
|
+
return { basePath, loading };
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Get cached config directly (for non-React usage)
|
|
154
|
+
*/
|
|
155
|
+
export const getPluginConfig = async (configVariant = 'default') => {
|
|
156
|
+
if (cachedConfig) {
|
|
157
|
+
return cachedConfig;
|
|
158
|
+
}
|
|
159
|
+
if (configPromise) {
|
|
160
|
+
return configPromise;
|
|
161
|
+
}
|
|
162
|
+
configPromise = loadPluginConfig(configVariant);
|
|
163
|
+
const result = await configPromise;
|
|
164
|
+
cachedConfig = result;
|
|
165
|
+
configPromise = null;
|
|
166
|
+
return result;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Clear the config cache (useful for testing)
|
|
170
|
+
*/
|
|
171
|
+
export const clearPluginConfigCache = () => {
|
|
172
|
+
cachedConfig = null;
|
|
173
|
+
configPromise = null;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Development helper to log current configuration
|
|
177
|
+
*/
|
|
178
|
+
export const debugPluginConfig = async (configVariant = 'default') => {
|
|
179
|
+
if (process.env.NODE_ENV !== 'development') {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const config = await getPluginConfig(configVariant);
|
|
183
|
+
console.group('π§ Plugin Configuration Debug');
|
|
184
|
+
console.log('Store ID:', config.storeId || 'Not available');
|
|
185
|
+
console.log('Account ID:', config.accountId || 'Not available');
|
|
186
|
+
console.log('Base Path:', config.basePath || 'Not available');
|
|
187
|
+
console.log('Config Keys:', config.config ? Object.keys(config.config) : 'None');
|
|
188
|
+
console.log('Full Config:', config);
|
|
189
|
+
console.groupEnd();
|
|
190
|
+
};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export { useOrderBump } from './hooks/useOrderBump';
|
|
|
15
15
|
export { usePostPurchases } from './hooks/usePostPurchases';
|
|
16
16
|
export { useProducts } from './hooks/useProducts';
|
|
17
17
|
export { useSession } from './hooks/useSession';
|
|
18
|
+
export { usePluginConfig, useBasePath, getPluginConfig, clearPluginConfigCache, debugPluginConfig } from './hooks/usePluginConfig';
|
|
19
|
+
export type { PluginConfig } from './hooks/usePluginConfig';
|
|
20
|
+
export { useGoogleAutocomplete, useGoogleMapsLoaded } from './hooks/useGoogleAutocomplete';
|
|
21
|
+
export type { GooglePrediction, GoogleAddressComponent, GooglePlaceDetails, ExtractedAddress, UseGoogleAutocompleteOptions, UseGoogleAutocompleteResult } from './hooks/useGoogleAutocomplete';
|
|
22
|
+
export { useISOData, useCountryOptions, useRegionOptions, getAvailableLanguages } from './hooks/useISOData';
|
|
23
|
+
export type { ISOCountry, ISORegion, UseISODataResult } from './hooks/useISOData';
|
|
18
24
|
export { useOrder } from './hooks/useOrder';
|
|
19
25
|
export type { UseOrderOptions, UseOrderResult } from './hooks/useOrder';
|
|
20
26
|
export { usePayment } from './hooks/usePayment';
|
package/dist/react/index.js
CHANGED
|
@@ -18,6 +18,12 @@ export { useOrderBump } from './hooks/useOrderBump';
|
|
|
18
18
|
export { usePostPurchases } from './hooks/usePostPurchases';
|
|
19
19
|
export { useProducts } from './hooks/useProducts';
|
|
20
20
|
export { useSession } from './hooks/useSession';
|
|
21
|
+
// Plugin configuration hooks
|
|
22
|
+
export { usePluginConfig, useBasePath, getPluginConfig, clearPluginConfigCache, debugPluginConfig } from './hooks/usePluginConfig';
|
|
23
|
+
// Google Places hooks
|
|
24
|
+
export { useGoogleAutocomplete, useGoogleMapsLoaded } from './hooks/useGoogleAutocomplete';
|
|
25
|
+
// ISO Data hooks
|
|
26
|
+
export { useISOData, useCountryOptions, useRegionOptions, getAvailableLanguages } from './hooks/useISOData';
|
|
21
27
|
// Order hook exports
|
|
22
28
|
export { useOrder } from './hooks/useOrder';
|
|
23
29
|
// Payment hooks exports
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { ReactNode } from 'react';
|
|
5
5
|
import { Customer, Session, AuthState, Locale, Currency, Store, Environment, EnvironmentConfig } from '../types';
|
|
6
6
|
import { ApiService } from '../services/apiService';
|
|
7
|
+
import { PluginConfig } from '../hooks/usePluginConfig';
|
|
7
8
|
import { formatMoney, getCurrencyInfo, moneyStringOrNumberToMinorUnits, minorUnitsToMajorUnits, formatMoneyWithoutSymbol, convertCurrency, formatSimpleMoney } from '../utils/money';
|
|
8
9
|
interface TagadaContextValue {
|
|
9
10
|
auth: AuthState;
|
|
@@ -17,6 +18,8 @@ interface TagadaContextValue {
|
|
|
17
18
|
isLoading: boolean;
|
|
18
19
|
isInitialized: boolean;
|
|
19
20
|
debugMode: boolean;
|
|
21
|
+
pluginConfig: PluginConfig;
|
|
22
|
+
pluginConfigLoading: boolean;
|
|
20
23
|
debugCheckout: {
|
|
21
24
|
isActive: boolean;
|
|
22
25
|
data: any;
|
|
@@ -50,8 +53,9 @@ interface TagadaProviderProps {
|
|
|
50
53
|
debugMode?: boolean;
|
|
51
54
|
storeId?: string;
|
|
52
55
|
accountId?: string;
|
|
56
|
+
localConfig?: string;
|
|
53
57
|
}
|
|
54
58
|
export declare function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
|
|
55
|
-
storeId, accountId, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
59
|
+
storeId: propStoreId, accountId: propAccountId, localConfig, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
56
60
|
export declare function useTagadaContext(): TagadaContextValue;
|
|
57
61
|
export {};
|