@tagadapay/plugin-sdk 2.4.39 → 2.5.2

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 (106) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2 -0
  3. package/dist/react/hooks/useCheckout.js +19 -2
  4. package/dist/react/hooks/useCheckoutSession.d.ts +19 -0
  5. package/dist/react/hooks/useCheckoutSession.js +108 -0
  6. package/dist/react/hooks/useCheckoutToken.d.ts +17 -0
  7. package/dist/react/hooks/useCheckoutToken.js +80 -0
  8. package/dist/react/hooks/useOrderBump.js +92 -13
  9. package/dist/react/hooks/useOrderBumpV2.d.ts +17 -0
  10. package/dist/react/hooks/useOrderBumpV2.js +95 -0
  11. package/dist/react/hooks/useOrderBumpV3.d.ts +23 -0
  12. package/dist/react/hooks/useOrderBumpV3.js +109 -0
  13. package/dist/react/hooks/usePostPurchases.js +11 -5
  14. package/dist/react/index.d.ts +8 -0
  15. package/dist/react/index.js +4 -0
  16. package/dist/react/services/apiService.d.ts +1 -0
  17. package/dist/react/services/apiService.js +3 -0
  18. package/dist/v2/core/googleAutocomplete.d.ts +65 -0
  19. package/dist/v2/core/googleAutocomplete.js +94 -0
  20. package/dist/v2/core/index.d.ts +8 -0
  21. package/dist/v2/core/index.js +11 -0
  22. package/dist/v2/core/isoData.d.ts +50 -0
  23. package/dist/v2/core/isoData.js +103 -0
  24. package/dist/v2/core/resources/apiClient.d.ts +25 -0
  25. package/dist/v2/core/resources/apiClient.js +95 -0
  26. package/dist/v2/core/resources/checkout.d.ts +189 -0
  27. package/dist/v2/core/resources/checkout.js +119 -0
  28. package/dist/v2/core/resources/index.d.ts +13 -0
  29. package/dist/v2/core/resources/index.js +13 -0
  30. package/dist/v2/core/resources/offers.d.ts +98 -0
  31. package/dist/v2/core/resources/offers.js +115 -0
  32. package/dist/v2/core/resources/orders.d.ts +40 -0
  33. package/dist/v2/core/resources/orders.js +59 -0
  34. package/dist/v2/core/resources/payments.d.ts +140 -0
  35. package/dist/v2/core/resources/payments.js +126 -0
  36. package/dist/v2/core/resources/postPurchases.d.ts +182 -0
  37. package/dist/v2/core/resources/postPurchases.js +116 -0
  38. package/dist/v2/core/resources/products.d.ts +29 -0
  39. package/dist/v2/core/resources/products.js +49 -0
  40. package/dist/v2/core/resources/promotions.d.ts +45 -0
  41. package/dist/v2/core/resources/promotions.js +87 -0
  42. package/dist/v2/core/resources/threeds.d.ts +23 -0
  43. package/dist/v2/core/resources/threeds.js +15 -0
  44. package/dist/v2/core/utils/checkout.d.ts +24 -0
  45. package/dist/v2/core/utils/checkout.js +30 -0
  46. package/dist/v2/core/utils/currency.d.ts +28 -0
  47. package/dist/v2/core/utils/currency.js +272 -0
  48. package/dist/v2/core/utils/index.d.ts +12 -0
  49. package/dist/v2/core/utils/index.js +12 -0
  50. package/dist/v2/core/utils/order.d.ts +159 -0
  51. package/dist/v2/core/utils/order.js +42 -0
  52. package/dist/v2/core/utils/orderBump.d.ts +40 -0
  53. package/dist/v2/core/utils/orderBump.js +47 -0
  54. package/dist/v2/core/utils/pluginConfig.d.ts +43 -0
  55. package/dist/v2/core/utils/pluginConfig.js +155 -0
  56. package/dist/v2/core/utils/postPurchases.d.ts +32 -0
  57. package/dist/v2/core/utils/postPurchases.js +42 -0
  58. package/dist/v2/core/utils/products.d.ts +58 -0
  59. package/dist/v2/core/utils/products.js +64 -0
  60. package/dist/v2/core/utils/promotions.d.ts +24 -0
  61. package/dist/v2/core/utils/promotions.js +30 -0
  62. package/dist/v2/index.d.ts +19 -0
  63. package/dist/v2/index.js +15 -0
  64. package/dist/v2/react/components/DebugDrawer.d.ts +7 -0
  65. package/dist/v2/react/components/DebugDrawer.js +383 -0
  66. package/dist/v2/react/hooks/useApiQuery.d.ts +28 -0
  67. package/dist/v2/react/hooks/useApiQuery.js +84 -0
  68. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +39 -0
  69. package/dist/v2/react/hooks/useCheckoutQuery.js +194 -0
  70. package/dist/v2/react/hooks/useCheckoutToken.d.ts +17 -0
  71. package/dist/v2/react/hooks/useCheckoutToken.js +65 -0
  72. package/dist/v2/react/hooks/useCurrency.d.ts +9 -0
  73. package/dist/v2/react/hooks/useCurrency.js +21 -0
  74. package/dist/v2/react/hooks/useGeoLocation.d.ts +138 -0
  75. package/dist/v2/react/hooks/useGeoLocation.js +123 -0
  76. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +74 -0
  77. package/dist/v2/react/hooks/useGoogleAutocomplete.js +196 -0
  78. package/dist/v2/react/hooks/useISOData.d.ts +61 -0
  79. package/dist/v2/react/hooks/useISOData.js +175 -0
  80. package/dist/v2/react/hooks/useOffersQuery.d.ts +65 -0
  81. package/dist/v2/react/hooks/useOffersQuery.js +342 -0
  82. package/dist/v2/react/hooks/useOrderBumpQuery.d.ts +20 -0
  83. package/dist/v2/react/hooks/useOrderBumpQuery.js +92 -0
  84. package/dist/v2/react/hooks/useOrderQuery.d.ts +29 -0
  85. package/dist/v2/react/hooks/useOrderQuery.js +98 -0
  86. package/dist/v2/react/hooks/usePaymentPolling.d.ts +45 -0
  87. package/dist/v2/react/hooks/usePaymentPolling.js +140 -0
  88. package/dist/v2/react/hooks/usePaymentQuery.d.ts +19 -0
  89. package/dist/v2/react/hooks/usePaymentQuery.js +272 -0
  90. package/dist/v2/react/hooks/usePluginConfig.d.ts +16 -0
  91. package/dist/v2/react/hooks/usePluginConfig.js +35 -0
  92. package/dist/v2/react/hooks/usePostPurchasesQuery.d.ts +63 -0
  93. package/dist/v2/react/hooks/usePostPurchasesQuery.js +343 -0
  94. package/dist/v2/react/hooks/useProductsQuery.d.ts +31 -0
  95. package/dist/v2/react/hooks/useProductsQuery.js +102 -0
  96. package/dist/v2/react/hooks/usePromotionsQuery.d.ts +28 -0
  97. package/dist/v2/react/hooks/usePromotionsQuery.js +97 -0
  98. package/dist/v2/react/hooks/useThreeds.d.ts +36 -0
  99. package/dist/v2/react/hooks/useThreeds.js +150 -0
  100. package/dist/v2/react/hooks/useThreedsModal.d.ts +13 -0
  101. package/dist/v2/react/hooks/useThreedsModal.js +343 -0
  102. package/dist/v2/react/index.d.ts +38 -0
  103. package/dist/v2/react/index.js +27 -0
  104. package/dist/v2/react/providers/TagadaProvider.d.ts +63 -0
  105. package/dist/v2/react/providers/TagadaProvider.js +680 -0
  106. package/package.json +10 -3
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Checkout Hook using TanStack Query
3
+ * Replaces the coordinator pattern with automatic cache invalidation
4
+ */
5
+ import { useCallback, useState, useEffect, useMemo } from 'react';
6
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
7
+ import { CheckoutResource } from '../../core/resources/checkout';
8
+ import { useTagadaContext } from '../providers/TagadaProvider';
9
+ import { usePluginConfig } from './usePluginConfig';
10
+ import { useCurrency } from './useCurrency';
11
+ import { getGlobalApiClient } from './useApiQuery';
12
+ export function useCheckoutQuery(options = {}) {
13
+ const { checkoutToken: providedToken, enabled = true } = options;
14
+ const { storeId } = usePluginConfig();
15
+ const currency = useCurrency();
16
+ const queryClient = useQueryClient();
17
+ const { isSessionInitialized } = useTagadaContext();
18
+ // Create checkout resource client
19
+ const checkoutResource = useMemo(() => {
20
+ try {
21
+ return new CheckoutResource(getGlobalApiClient());
22
+ }
23
+ catch (error) {
24
+ throw new Error('Failed to initialize checkout resource: ' + (error instanceof Error ? error.message : 'Unknown error'));
25
+ }
26
+ }, []);
27
+ // Internal token state that can be updated after init
28
+ const [internalToken, setInternalToken] = useState(providedToken);
29
+ // Update internal token when provided token changes
30
+ useEffect(() => {
31
+ if (providedToken && providedToken !== internalToken) {
32
+ setInternalToken(providedToken);
33
+ }
34
+ }, [providedToken, internalToken]);
35
+ // Use provided token or internal token
36
+ const checkoutToken = providedToken || internalToken;
37
+ // Main checkout query
38
+ const { data: checkout, isLoading, error, isSuccess, refetch, } = useQuery({
39
+ queryKey: ['checkout', checkoutToken, currency.code],
40
+ queryFn: () => checkoutResource.getCheckout(checkoutToken, currency.code),
41
+ enabled: enabled && !!checkoutToken && isSessionInitialized,
42
+ staleTime: 30000, // 30 seconds
43
+ refetchOnWindowFocus: false,
44
+ });
45
+ // Refresh function
46
+ const refresh = useCallback(async () => {
47
+ if (checkoutToken) {
48
+ await refetch();
49
+ }
50
+ }, [refetch, checkoutToken]);
51
+ // Initialize checkout mutation
52
+ const initMutation = useMutation({
53
+ mutationFn: (params) => {
54
+ const requestBody = {
55
+ ...params,
56
+ storeId: params.storeId || storeId,
57
+ returnUrl: params.returnUrl || window.location.origin,
58
+ customer: {
59
+ ...params.customer,
60
+ currency: params.customer?.currency ?? currency.code,
61
+ },
62
+ };
63
+ return checkoutResource.initCheckout(requestBody);
64
+ },
65
+ onSuccess: (response) => {
66
+ // Update URL with checkout token
67
+ if (typeof window !== 'undefined' && response.checkoutToken) {
68
+ const currentUrl = new URL(window.location.href);
69
+ if (!currentUrl.searchParams.has('checkoutToken')) {
70
+ const newUrl = new URL(window.location.href);
71
+ newUrl.searchParams.set('checkoutToken', response.checkoutToken);
72
+ window.history.replaceState(null, '', newUrl.toString());
73
+ }
74
+ }
75
+ },
76
+ });
77
+ // Order bump functionality removed - use useOrderBumpQuery instead
78
+ // Line items mutation
79
+ const lineItemsMutation = useMutation({
80
+ mutationFn: ({ lineItems }) => {
81
+ if (!checkout?.checkoutSession?.id) {
82
+ throw new Error('No checkout session available');
83
+ }
84
+ return checkoutResource.updateLineItems(checkout.checkoutSession.id, lineItems);
85
+ },
86
+ onSuccess: () => {
87
+ if (checkoutToken) {
88
+ void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
89
+ }
90
+ },
91
+ });
92
+ // Item quantity mutation
93
+ const quantityMutation = useMutation({
94
+ mutationFn: ({ variantId, quantity, priceId }) => {
95
+ if (!checkout?.checkoutSession?.id) {
96
+ throw new Error('No checkout session available');
97
+ }
98
+ return checkoutResource.setItemQuantity(checkout.checkoutSession.id, variantId, quantity, priceId);
99
+ },
100
+ onSuccess: () => {
101
+ if (checkoutToken) {
102
+ void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
103
+ }
104
+ },
105
+ });
106
+ // Customer update mutation
107
+ const customerMutation = useMutation({
108
+ mutationFn: (data) => {
109
+ if (!checkout?.checkoutSession?.id) {
110
+ throw new Error('No checkout session available');
111
+ }
112
+ return checkoutResource.updateCustomer(checkout.checkoutSession.id, data);
113
+ },
114
+ onSuccess: () => {
115
+ if (checkoutToken) {
116
+ void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
117
+ }
118
+ },
119
+ });
120
+ // Customer and session info update mutation
121
+ const customerAndSessionMutation = useMutation({
122
+ mutationFn: (data) => {
123
+ if (!checkout?.checkoutSession?.id) {
124
+ throw new Error('No checkout session available');
125
+ }
126
+ return checkoutResource.updateCustomerAndSessionInfo(checkout.checkoutSession.id, data);
127
+ },
128
+ onSuccess: () => {
129
+ if (checkoutToken) {
130
+ void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
131
+ }
132
+ },
133
+ });
134
+ // Promotion code mutation
135
+ const promotionMutation = useMutation({
136
+ mutationFn: ({ code }) => {
137
+ if (!checkout?.checkoutSession?.id) {
138
+ throw new Error('No checkout session available');
139
+ }
140
+ return checkoutResource.applyPromotionCode(checkout.checkoutSession.id, code);
141
+ },
142
+ onSuccess: () => {
143
+ if (checkoutToken) {
144
+ void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
145
+ }
146
+ if (checkout?.checkoutSession?.id) {
147
+ void queryClient.invalidateQueries({ queryKey: ['promotions', checkout.checkoutSession.id] });
148
+ }
149
+ },
150
+ });
151
+ // Remove promotion mutation
152
+ const removePromotionMutation = useMutation({
153
+ mutationFn: ({ promotionId }) => {
154
+ if (!checkout?.checkoutSession?.id) {
155
+ throw new Error('No checkout session available');
156
+ }
157
+ return checkoutResource.removePromotion(checkout.checkoutSession.id, promotionId);
158
+ },
159
+ onSuccess: () => {
160
+ if (checkoutToken) {
161
+ void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
162
+ }
163
+ if (checkout?.checkoutSession?.id) {
164
+ void queryClient.invalidateQueries({ queryKey: ['promotions', checkout.checkoutSession.id] });
165
+ }
166
+ },
167
+ });
168
+ return {
169
+ // Query data
170
+ checkout,
171
+ isLoading,
172
+ error,
173
+ isSuccess,
174
+ // Actions
175
+ init: async (params) => {
176
+ const result = await initMutation.mutateAsync(params);
177
+ // Update internal token state so the query can fetch the checkout data
178
+ setInternalToken(result.checkoutToken);
179
+ return {
180
+ checkoutUrl: result.checkoutUrl,
181
+ checkoutSession: checkout?.checkoutSession ?? {},
182
+ checkoutToken: result.checkoutToken,
183
+ };
184
+ },
185
+ refresh,
186
+ // Checkout operations
187
+ updateLineItems: (lineItems) => lineItemsMutation.mutateAsync({ lineItems }),
188
+ setItemQuantity: (variantId, quantity, priceId) => quantityMutation.mutateAsync({ variantId, quantity, priceId }),
189
+ updateCustomer: (data) => customerMutation.mutateAsync(data),
190
+ updateCustomerAndSessionInfo: (data) => customerAndSessionMutation.mutateAsync(data),
191
+ applyPromotionCode: (code) => promotionMutation.mutateAsync({ code }),
192
+ removePromotion: (promotionId) => removePromotionMutation.mutateAsync({ promotionId }),
193
+ };
194
+ }
@@ -0,0 +1,17 @@
1
+ export interface UseCheckoutTokenOptions {
2
+ checkoutToken?: string;
3
+ autoLoadFromToken?: boolean;
4
+ }
5
+ export interface UseCheckoutTokenResult {
6
+ checkoutToken: string | null;
7
+ isLoading: boolean;
8
+ error: Error | null;
9
+ isInitialized: boolean;
10
+ setToken: (token: string | null) => void;
11
+ clearToken: () => void;
12
+ }
13
+ /**
14
+ * React hook for managing checkout token state
15
+ * Handles token management without fetching checkout data
16
+ */
17
+ export declare function useCheckoutToken(options?: UseCheckoutTokenOptions): UseCheckoutTokenResult;
@@ -0,0 +1,65 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useTagadaContext } from '../providers/TagadaProvider';
3
+ /**
4
+ * React hook for managing checkout token state
5
+ * Handles token management without fetching checkout data
6
+ */
7
+ export function useCheckoutToken(options = {}) {
8
+ const { isSessionInitialized } = useTagadaContext();
9
+ const { checkoutToken: providedToken, autoLoadFromToken = true } = options;
10
+ const [checkoutToken, setCheckoutToken] = useState(providedToken || null);
11
+ const [isLoading, _setIsLoading] = useState(false);
12
+ const [error, setError] = useState(null);
13
+ const [isInitialized, setIsInitialized] = useState(false);
14
+ const hasAutoLoadedRef = useRef(false);
15
+ const isSessionInitializedRef = useRef(isSessionInitialized);
16
+ // Keep ref in sync with state
17
+ useEffect(() => {
18
+ isSessionInitializedRef.current = isSessionInitialized;
19
+ }, [isSessionInitialized]);
20
+ // Update token when provided token changes
21
+ useEffect(() => {
22
+ if (providedToken && providedToken !== checkoutToken) {
23
+ setCheckoutToken(providedToken);
24
+ setIsInitialized(false);
25
+ hasAutoLoadedRef.current = false;
26
+ }
27
+ }, [providedToken, checkoutToken]);
28
+ // Auto-load token from URL if no token provided
29
+ useEffect(() => {
30
+ if (!providedToken && autoLoadFromToken && !checkoutToken && !hasAutoLoadedRef.current) {
31
+ const urlParams = new URLSearchParams(window.location.search);
32
+ const urlToken = urlParams.get('checkoutToken') || urlParams.get('token');
33
+ if (urlToken) {
34
+ setCheckoutToken(urlToken);
35
+ hasAutoLoadedRef.current = true;
36
+ }
37
+ }
38
+ }, [providedToken, autoLoadFromToken, checkoutToken]);
39
+ // Wait for session initialization
40
+ useEffect(() => {
41
+ if (!isSessionInitialized || !checkoutToken || isInitialized) {
42
+ return;
43
+ }
44
+ setIsInitialized(true);
45
+ }, [isSessionInitialized, checkoutToken, isInitialized]);
46
+ const setToken = useCallback((token) => {
47
+ setCheckoutToken(token);
48
+ setIsInitialized(!!token);
49
+ setError(null);
50
+ }, []);
51
+ const clearToken = useCallback(() => {
52
+ setCheckoutToken(null);
53
+ setIsInitialized(false);
54
+ setError(null);
55
+ hasAutoLoadedRef.current = false;
56
+ }, []);
57
+ return {
58
+ checkoutToken,
59
+ isLoading,
60
+ error,
61
+ isInitialized,
62
+ setToken,
63
+ clearToken,
64
+ };
65
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Currency Hook
3
+ * Uses CurrencyCore for business logic
4
+ */
5
+ import { Currency } from '../../core/utils/currency';
6
+ export interface UseCurrencyResult extends Currency {
7
+ format: (amount: number) => string;
8
+ }
9
+ export declare function useCurrency(defaultCurrency?: string): UseCurrencyResult;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Currency Hook
3
+ * Uses CurrencyCore for business logic
4
+ */
5
+ import { useMemo } from 'react';
6
+ import { useTagadaContext } from '../providers/TagadaProvider';
7
+ import { CurrencyUtils } from '../../core/utils/currency';
8
+ export function useCurrency(defaultCurrency = 'USD') {
9
+ const context = useTagadaContext();
10
+ const currency = useMemo(() => {
11
+ return CurrencyUtils.getCurrency(context, defaultCurrency);
12
+ }, [context, defaultCurrency]);
13
+ const format = (amount) => {
14
+ const symbol = currency.symbol || currency.code;
15
+ return `${symbol}${amount.toFixed(currency.decimalPlaces)}`;
16
+ };
17
+ return {
18
+ ...currency,
19
+ format,
20
+ };
21
+ }
@@ -0,0 +1,138 @@
1
+ export interface GeoLocationData {
2
+ ip_address?: string | null;
3
+ city?: string | null;
4
+ city_geoname_id?: number | null;
5
+ region?: string | null;
6
+ region_iso_code?: string | null;
7
+ region_geoname_id?: number | null;
8
+ postal_code?: string | null;
9
+ country?: string | null;
10
+ country_code?: string | null;
11
+ country_geoname_id?: number | null;
12
+ country_is_eu?: boolean | null;
13
+ continent?: string | null;
14
+ continent_code?: string | null;
15
+ continent_geoname_id?: number | null;
16
+ latitude?: number | null;
17
+ longitude?: number | null;
18
+ security?: {
19
+ is_vpn?: boolean | null;
20
+ } | null;
21
+ timezone?: {
22
+ name?: string | null;
23
+ abbreviation?: string | null;
24
+ gmt_offset?: number | null;
25
+ current_time?: string | null;
26
+ is_dst?: boolean | null;
27
+ } | null;
28
+ currency?: {
29
+ currency_name?: string | null;
30
+ currency_code?: string | null;
31
+ } | null;
32
+ connection?: {
33
+ autonomous_system_number?: number | null;
34
+ autonomous_system_organization?: string | null;
35
+ connection_type?: string | null;
36
+ isp_name?: string | null;
37
+ organization_name?: string | null;
38
+ } | null;
39
+ flag?: {
40
+ emoji?: string | null;
41
+ unicode?: string | null;
42
+ png?: string | null;
43
+ svg?: string | null;
44
+ } | null;
45
+ as?: string | null;
46
+ isp?: string | null;
47
+ lat?: number | null;
48
+ lon?: number | null;
49
+ org?: string | null;
50
+ query?: string | null;
51
+ regionName?: string | null;
52
+ status?: string | null;
53
+ zip?: string | null;
54
+ error?: string;
55
+ }
56
+ export interface UseGeoLocationOptions {
57
+ /**
58
+ * Whether to automatically fetch geolocation data on mount
59
+ * @default true
60
+ */
61
+ autoFetch?: boolean;
62
+ /**
63
+ * Custom IP address to fetch geolocation for
64
+ * If not provided, will use the client's IP
65
+ */
66
+ ip?: string;
67
+ /**
68
+ * Whether to refetch data when the hook mounts
69
+ * @default false
70
+ */
71
+ refetchOnMount?: boolean;
72
+ }
73
+ export interface UseGeoLocationReturn {
74
+ /**
75
+ * The geolocation data
76
+ */
77
+ data: GeoLocationData | null;
78
+ /**
79
+ * Whether the request is currently loading
80
+ */
81
+ isLoading: boolean;
82
+ /**
83
+ * Any error that occurred during the request
84
+ */
85
+ error: string | null;
86
+ /**
87
+ * Function to manually fetch geolocation data
88
+ */
89
+ fetchGeoData: (ip?: string) => Promise<void>;
90
+ /**
91
+ * Function to clear the current data and error state
92
+ */
93
+ clearData: () => void;
94
+ /**
95
+ * Whether the data is from localhost/development
96
+ */
97
+ isLocalhost: boolean;
98
+ /**
99
+ * Whether the current IP is valid
100
+ */
101
+ isValidIP: boolean;
102
+ }
103
+ /**
104
+ * Hook to fetch and manage geolocation data - V2 Implementation
105
+ * Compatible with V1 interface while using V2 provider architecture
106
+ *
107
+ * @example
108
+ * ```tsx
109
+ * function MyComponent() {
110
+ * const { data, isLoading, error, fetchGeoData } = useGeoLocation();
111
+ *
112
+ * if (isLoading) return <div>Loading location...</div>;
113
+ * if (error) return <div>Error: {error}</div>;
114
+ *
115
+ * return (
116
+ * <div>
117
+ * <p>Country: {data?.country}</p>
118
+ * <p>City: {data?.city}</p>
119
+ * <p>IP: {data?.ip_address}</p>
120
+ * </div>
121
+ * );
122
+ * }
123
+ * ```
124
+ *
125
+ * @example
126
+ * ```tsx
127
+ * function MyComponent() {
128
+ * const { fetchGeoData } = useGeoLocation({ autoFetch: false });
129
+ *
130
+ * const handleClick = () => {
131
+ * fetchGeoData('8.8.8.8'); // Fetch for specific IP
132
+ * };
133
+ *
134
+ * return <button onClick={handleClick}>Get Location for Google DNS</button>;
135
+ * }
136
+ * ```
137
+ */
138
+ export declare function useGeoLocation(options?: UseGeoLocationOptions): UseGeoLocationReturn;
@@ -0,0 +1,123 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { useTagadaContext } from '../providers/TagadaProvider';
4
+ /**
5
+ * Hook to fetch and manage geolocation data - V2 Implementation
6
+ * Compatible with V1 interface while using V2 provider architecture
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * function MyComponent() {
11
+ * const { data, isLoading, error, fetchGeoData } = useGeoLocation();
12
+ *
13
+ * if (isLoading) return <div>Loading location...</div>;
14
+ * if (error) return <div>Error: {error}</div>;
15
+ *
16
+ * return (
17
+ * <div>
18
+ * <p>Country: {data?.country}</p>
19
+ * <p>City: {data?.city}</p>
20
+ * <p>IP: {data?.ip_address}</p>
21
+ * </div>
22
+ * );
23
+ * }
24
+ * ```
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function MyComponent() {
29
+ * const { fetchGeoData } = useGeoLocation({ autoFetch: false });
30
+ *
31
+ * const handleClick = () => {
32
+ * fetchGeoData('8.8.8.8'); // Fetch for specific IP
33
+ * };
34
+ *
35
+ * return <button onClick={handleClick}>Get Location for Google DNS</button>;
36
+ * }
37
+ * ```
38
+ */
39
+ export function useGeoLocation(options = {}) {
40
+ const { autoFetch = true, ip, refetchOnMount = false } = options;
41
+ const { apiService } = useTagadaContext();
42
+ const [data, setData] = useState(null);
43
+ const [isLoading, setIsLoading] = useState(false);
44
+ const [error, setError] = useState(null);
45
+ // Helper function to check if IP is localhost
46
+ const isLocalhost = useCallback((ipAddress) => {
47
+ if (!ipAddress)
48
+ return false;
49
+ return ipAddress === '127.0.0.1' || ipAddress === '::1' || ipAddress === 'localhost';
50
+ }, []);
51
+ // Helper function to validate IP address format
52
+ const isValidIP = useCallback((ipAddress) => {
53
+ if (!ipAddress)
54
+ return false;
55
+ // IPv4 regex pattern
56
+ const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
57
+ // IPv6 regex pattern (simplified)
58
+ const ipv6Pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
59
+ if (!ipv4Pattern.test(ipAddress) && !ipv6Pattern.test(ipAddress)) {
60
+ return false;
61
+ }
62
+ // Additional validation for IPv4
63
+ if (ipv4Pattern.test(ipAddress)) {
64
+ const parts = ipAddress.split('.');
65
+ return parts.every((part) => {
66
+ const num = parseInt(part, 10);
67
+ return num >= 0 && num <= 255;
68
+ });
69
+ }
70
+ return true;
71
+ }, []);
72
+ // Function to fetch geolocation data
73
+ const fetchGeoData = useCallback(async (customIp) => {
74
+ if (!apiService) {
75
+ setError('API service not available');
76
+ return;
77
+ }
78
+ setIsLoading(true);
79
+ setError(null);
80
+ try {
81
+ const targetIp = customIp || ip;
82
+ const endpoint = targetIp ? `/api/v1/geo?ip=${encodeURIComponent(targetIp)}` : '/api/v1/geo';
83
+ const geoData = await apiService.fetch(endpoint, {
84
+ method: 'GET',
85
+ skipAuth: true, // Geolocation endpoint doesn't require authentication
86
+ });
87
+ setData(geoData);
88
+ }
89
+ catch (err) {
90
+ const errorMessage = err instanceof Error ? err.message : 'Failed to fetch geolocation data';
91
+ setError(errorMessage);
92
+ }
93
+ finally {
94
+ setIsLoading(false);
95
+ }
96
+ }, [apiService, ip]);
97
+ // Function to clear data and error state
98
+ const clearData = useCallback(() => {
99
+ setData(null);
100
+ setError(null);
101
+ }, []);
102
+ // Auto-fetch on mount if enabled
103
+ useEffect(() => {
104
+ if (autoFetch && apiService) {
105
+ void fetchGeoData();
106
+ }
107
+ }, [autoFetch, apiService, fetchGeoData]);
108
+ // Refetch on mount if enabled
109
+ useEffect(() => {
110
+ if (refetchOnMount && apiService) {
111
+ void fetchGeoData();
112
+ }
113
+ }, [refetchOnMount, apiService, fetchGeoData]);
114
+ return {
115
+ data,
116
+ isLoading,
117
+ error,
118
+ fetchGeoData,
119
+ clearData,
120
+ isLocalhost: isLocalhost(data?.ip_address),
121
+ isValidIP: isValidIP(data?.ip_address),
122
+ };
123
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Google Autocomplete Hook - V2 Implementation
3
+ * Compatible with V1 interface while using V2 core architecture
4
+ */
5
+ declare global {
6
+ interface Window {
7
+ google?: {
8
+ maps?: {
9
+ places?: {
10
+ AutocompleteService: new () => any;
11
+ PlacesService: new (map: any) => any;
12
+ PlacesServiceStatus: {
13
+ OK: string;
14
+ ZERO_RESULTS: string;
15
+ };
16
+ };
17
+ Map: new (element: HTMLElement) => any;
18
+ };
19
+ };
20
+ }
21
+ }
22
+ export interface GooglePrediction {
23
+ place_id: string;
24
+ description: string;
25
+ structured_formatting?: {
26
+ main_text: string;
27
+ secondary_text: string;
28
+ };
29
+ }
30
+ export interface GooglePlaceDetails {
31
+ place_id: string;
32
+ formatted_address: string;
33
+ address_components: {
34
+ long_name: string;
35
+ short_name: string;
36
+ types: string[];
37
+ }[];
38
+ geometry?: {
39
+ location: {
40
+ lat: number;
41
+ lng: number;
42
+ };
43
+ };
44
+ }
45
+ export interface ExtractedAddress {
46
+ streetNumber: string;
47
+ route: string;
48
+ locality: string;
49
+ administrativeAreaLevel1: string;
50
+ administrativeAreaLevel1Long: string;
51
+ country: string;
52
+ postalCode: string;
53
+ }
54
+ export interface UseGoogleAutocompleteOptions {
55
+ apiKey: string;
56
+ libraries?: string[];
57
+ version?: string;
58
+ language?: string;
59
+ region?: string;
60
+ }
61
+ export interface UseGoogleAutocompleteResult {
62
+ predictions: GooglePrediction[];
63
+ isLoading: boolean;
64
+ isScriptLoaded: boolean;
65
+ searchPlaces: (input: string, countryRestriction?: string) => void;
66
+ getPlaceDetails: (placeId: string) => Promise<GooglePlaceDetails | null>;
67
+ extractAddressComponents: (place: GooglePlaceDetails) => ExtractedAddress;
68
+ clearPredictions: () => void;
69
+ }
70
+ /**
71
+ * React hook for Google Places Autocomplete with automatic script injection
72
+ * Automatically loads the Google Maps JavaScript API with Places library
73
+ */
74
+ export declare function useGoogleAutocomplete(options: UseGoogleAutocompleteOptions): UseGoogleAutocompleteResult;