@tagadapay/plugin-sdk 2.1.2 → 2.2.0

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.
@@ -1,9 +1,9 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * TagadaProvider - Main provider component for the Tagada Pay React SDK
5
5
  */
6
- import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
6
+ import { createContext, useContext, useState, useEffect, useRef, useCallback, useMemo, } from 'react';
7
7
  import { getEnvironmentConfig, detectEnvironment } from '../config/environment';
8
8
  import { ApiService } from '../services/apiService';
9
9
  import { setClientToken, getClientToken, clearClientToken } from '../utils/tokenStorage';
@@ -12,39 +12,58 @@ import { collectDeviceInfo, getBrowserLocale, getUrlParams } from '../utils/devi
12
12
  import DebugDrawer from '../components/DebugDrawer';
13
13
  import { loadPluginConfig } from '../hooks/usePluginConfig';
14
14
  import { formatMoney, getCurrencyInfo, moneyStringOrNumberToMinorUnits, minorUnitsToMajorUnits, formatMoneyWithoutSymbol, convertCurrency, formatSimpleMoney, } from '../utils/money';
15
- // Subtle loading component for initialization
16
- const InitializationLoader = () => (_jsx("div", { style: {
15
+ // Professional, subtle loading component for initialization
16
+ const InitializationLoader = () => (_jsxs("div", { style: {
17
17
  position: 'fixed',
18
- top: 0,
19
- left: 0,
20
- right: 0,
21
- height: '2px',
22
- background: 'linear-gradient(90deg, #3b82f6 0%, #8b5cf6 50%, #3b82f6 100%)',
23
- backgroundSize: '200% 100%',
24
- animation: 'tagada-loading 1.5s ease-in-out infinite',
25
- zIndex: 9999,
26
- }, children: _jsx("style", { children: `
27
- @keyframes tagada-loading {
28
- 0% { background-position: 200% 0; }
29
- 100% { background-position: -200% 0; }
18
+ top: '24px',
19
+ right: '24px',
20
+ display: 'flex',
21
+ alignItems: 'center',
22
+ gap: '8px',
23
+ padding: '8px 12px',
24
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
25
+ border: '1px solid rgba(229, 231, 235, 0.8)',
26
+ borderRadius: '8px',
27
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)',
28
+ backdropFilter: 'blur(8px)',
29
+ zIndex: 999999,
30
+ fontSize: '13px',
31
+ color: '#6b7280',
32
+ fontFamily: 'system-ui, -apple-system, sans-serif',
33
+ fontWeight: '500',
34
+ }, children: [_jsx("div", { style: {
35
+ width: '12px',
36
+ height: '12px',
37
+ border: '1.5px solid #e5e7eb',
38
+ borderTop: '1.5px solid #9ca3af',
39
+ borderRadius: '50%',
40
+ animation: 'tagada-spin 1s linear infinite',
41
+ } }), _jsx("span", { children: "Loading..." }), _jsx("style", { children: `
42
+ @keyframes tagada-spin {
43
+ 0% { transform: rotate(0deg); }
44
+ 100% { transform: rotate(360deg); }
30
45
  }
31
- ` }) }));
46
+ ` })] }));
32
47
  const TagadaContext = createContext(null);
33
48
  export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
34
- storeId: propStoreId, accountId: propAccountId, localConfig, }) {
49
+ localConfig, }) {
35
50
  // LOCAL DEV ONLY: Use localConfig override if in local development, otherwise use default
36
51
  const isLocalDev = typeof window !== 'undefined' &&
37
52
  (window.location.hostname === 'localhost' ||
38
53
  window.location.hostname.includes('.localhost') ||
39
54
  window.location.hostname.includes('127.0.0.1'));
40
55
  const configVariant = isLocalDev ? localConfig || 'default' : 'default';
41
- // Debug logging
42
- console.log('🔍 TagadaProvider Config Debug:', {
43
- hostname: typeof window !== 'undefined' ? window.location.hostname : 'SSR',
44
- isLocalDev,
45
- localConfig,
46
- configVariant,
47
- });
56
+ // Debug logging (only log once during initial render)
57
+ const hasLoggedRef = useRef(false);
58
+ if (!hasLoggedRef.current) {
59
+ console.log('🔍 TagadaProvider Config Debug:', {
60
+ hostname: typeof window !== 'undefined' ? window.location.hostname : 'SSR',
61
+ isLocalDev,
62
+ localConfig,
63
+ configVariant,
64
+ });
65
+ hasLoggedRef.current = true;
66
+ }
48
67
  // Load plugin configuration directly (not using hook to avoid circular dependency)
49
68
  const [pluginConfig, setPluginConfig] = useState({ basePath: '/', config: {} });
50
69
  const [configLoading, setConfigLoading] = useState(true);
@@ -53,10 +72,20 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
53
72
  const loadConfig = async () => {
54
73
  try {
55
74
  const config = await loadPluginConfig(configVariant);
75
+ // Ensure we have required store ID before proceeding
76
+ if (!config.storeId) {
77
+ console.warn('⚠️ No store ID found in plugin config. This may cause hooks to fail.');
78
+ }
56
79
  setPluginConfig(config);
80
+ console.log('✅ Plugin config loaded:', {
81
+ storeId: config.storeId,
82
+ accountId: config.accountId,
83
+ basePath: config.basePath,
84
+ hasConfig: !!config.config,
85
+ });
57
86
  }
58
87
  catch (error) {
59
- console.warn('Failed to load plugin config in TagadaProvider:', error);
88
+ console.error('Failed to load plugin config in TagadaProvider:', error);
60
89
  setPluginConfig({ basePath: '/', config: {} });
61
90
  }
62
91
  finally {
@@ -65,12 +94,9 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
65
94
  };
66
95
  void loadConfig();
67
96
  }, [configVariant]);
68
- // Extract store/account IDs from plugin config
69
- const configStoreId = pluginConfig.storeId;
70
- const configAccountId = pluginConfig.accountId;
71
- // Use props if provided, otherwise use config from hook
72
- const storeId = propStoreId || configStoreId;
73
- const accountId = propAccountId || configAccountId;
97
+ // Extract store/account IDs from plugin config (only source now)
98
+ const storeId = pluginConfig.storeId;
99
+ const accountId = pluginConfig.accountId;
74
100
  const [isLoading, setIsLoading] = useState(true);
75
101
  const [isInitialized, setIsInitialized] = useState(false);
76
102
  const [token, setToken] = useState(null);
@@ -117,10 +143,9 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
117
143
  clearClientToken();
118
144
  },
119
145
  });
120
- // Store the provided store ID immediately if available
146
+ // Store ID is managed by plugin configuration system
121
147
  if (storeId) {
122
- console.log(`[SDK] Using provided store ID: ${storeId}`);
123
- service.storeStoreId(storeId);
148
+ console.log(`[SDK] Using store ID from config: ${storeId}`);
124
149
  }
125
150
  return service;
126
151
  });
@@ -319,8 +344,8 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
319
344
  if (isInitializing.current) {
320
345
  return;
321
346
  }
322
- // Wait for plugin config to load before initializing
323
- if (configLoading) {
347
+ // Wait for plugin config to load AND ensure we have a store ID before initializing
348
+ if (configLoading || !storeId) {
324
349
  return;
325
350
  }
326
351
  isInitializing.current = true;
@@ -388,7 +413,7 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
388
413
  // Refresh coordinator for bidirectional hook communication
389
414
  const checkoutRefreshRef = useRef(null);
390
415
  const orderBumpRefreshRef = useRef(null);
391
- const refreshCoordinator = {
416
+ const refreshCoordinator = useMemo(() => ({
392
417
  registerCheckoutRefresh: (refreshFn) => {
393
418
  checkoutRefreshRef.current = refreshFn;
394
419
  console.log('🔄 [RefreshCoordinator] Checkout refresh function registered');
@@ -429,8 +454,9 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
429
454
  orderBumpRefreshRef.current = null;
430
455
  console.log('🧹 [RefreshCoordinator] Order bump refresh function unregistered');
431
456
  },
432
- };
433
- const contextValue = {
457
+ }), []);
458
+ // Memoize the context value to prevent unnecessary re-renders
459
+ const contextValue = useMemo(() => ({
434
460
  auth,
435
461
  session,
436
462
  customer,
@@ -464,8 +490,28 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
464
490
  convertCurrency,
465
491
  formatSimpleMoney,
466
492
  },
467
- };
468
- return (_jsxs(TagadaContext.Provider, { value: contextValue, children: [isLoading && !isInitialized && _jsx(InitializationLoader, {}), finalDebugMode && isInitialized && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsDebugDrawerOpen(true), style: {
493
+ }), [
494
+ auth,
495
+ session,
496
+ customer,
497
+ locale,
498
+ currency,
499
+ store,
500
+ environmentConfig,
501
+ apiService,
502
+ isLoading,
503
+ isInitialized,
504
+ finalDebugMode,
505
+ pluginConfig,
506
+ configLoading,
507
+ debugCheckout,
508
+ refreshCoordinator,
509
+ ]);
510
+ // Determine if we should show loading
511
+ // Show loading if: config is loading OR (config loaded but no store ID and session not initialized)
512
+ const shouldShowLoading = configLoading || (!storeId && !isInitialized);
513
+ const canRenderChildren = !configLoading && storeId && isInitialized;
514
+ return (_jsxs(TagadaContext.Provider, { value: contextValue, children: [shouldShowLoading && _jsx(InitializationLoader, {}), finalDebugMode && canRenderChildren && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsDebugDrawerOpen(true), style: {
469
515
  position: 'fixed',
470
516
  bottom: '16px',
471
517
  right: '16px',
@@ -486,7 +532,7 @@ storeId: propStoreId, accountId: propAccountId, localConfig, }) {
486
532
  }, onMouseLeave: (e) => {
487
533
  e.currentTarget.style.backgroundColor = '#f97316';
488
534
  e.currentTarget.style.transform = 'scale(1)';
489
- }, title: "Open TagadaPay SDK Debug Panel", children: "\uD83D\uDC1B" }), _jsx(DebugDrawer, { isOpen: isDebugDrawerOpen, onClose: () => setIsDebugDrawerOpen(false) })] })), isInitialized && children] }));
535
+ }, title: "Open TagadaPay SDK Debug Panel", children: "\uD83D\uDC1B" }), _jsx(DebugDrawer, { isOpen: isDebugDrawerOpen, onClose: () => setIsDebugDrawerOpen(false) })] })), canRenderChildren && children] }));
490
536
  }
491
537
  export function useTagadaContext() {
492
538
  const context = useContext(TagadaContext);
@@ -51,11 +51,13 @@ export declare class ApiService {
51
51
  updateToken(token: string | null): void;
52
52
  updateConfig(config: EnvironmentConfig): void;
53
53
  /**
54
- * Store the store ID in localStorage
54
+ * Store the store ID in localStorage (deprecated - use usePluginConfig instead)
55
+ * @deprecated Use usePluginConfig hook to get storeId
55
56
  */
56
57
  storeStoreId(storeId: string): void;
57
58
  /**
58
- * Get the store ID from localStorage
59
+ * Get the store ID from localStorage (deprecated - use usePluginConfig instead)
60
+ * @deprecated Use usePluginConfig hook to get storeId
59
61
  */
60
62
  getStoredStoreId(): string | null;
61
63
  /**
@@ -14,9 +14,11 @@ export class ApiService {
14
14
  this.config = config;
15
15
  }
16
16
  /**
17
- * Store the store ID in localStorage
17
+ * Store the store ID in localStorage (deprecated - use usePluginConfig instead)
18
+ * @deprecated Use usePluginConfig hook to get storeId
18
19
  */
19
20
  storeStoreId(storeId) {
21
+ console.warn('[SDK] storeStoreId is deprecated. Use usePluginConfig hook to get storeId.');
20
22
  try {
21
23
  if (typeof window !== 'undefined' && window.localStorage) {
22
24
  window.localStorage.setItem('tagada_store_id', storeId);
@@ -27,9 +29,11 @@ export class ApiService {
27
29
  }
28
30
  }
29
31
  /**
30
- * Get the store ID from localStorage
32
+ * Get the store ID from localStorage (deprecated - use usePluginConfig instead)
33
+ * @deprecated Use usePluginConfig hook to get storeId
31
34
  */
32
35
  getStoredStoreId() {
36
+ console.warn('[SDK] getStoredStoreId is deprecated. Use usePluginConfig hook to get storeId.');
33
37
  try {
34
38
  if (typeof window !== 'undefined' && window.localStorage) {
35
39
  return window.localStorage.getItem('tagada_store_id');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,7 +51,6 @@
51
51
  "@basis-theory/web-threeds": "^1.0.1",
52
52
  "axios": "^1.6.0",
53
53
  "iso3166-2-db": "^2.3.11",
54
- "react-google-autocomplete": "^2.7.3",
55
54
  "react-intl": "^7.1.11"
56
55
  },
57
56
  "devDependencies": {
@@ -1 +0,0 @@
1
- export declare const AddressFormExample: () => import("react/jsx-runtime").JSX.Element;
@@ -1,32 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAddress } from '../hooks/useAddress';
3
- // Basic Address Form Example
4
- export const AddressFormExample = () => {
5
- const { fields, countries, states, setValue, validateAll, getAddressObject, getFormattedAddress } = useAddress({
6
- autoValidate: true,
7
- initialValues: {
8
- firstName: 'John',
9
- lastName: 'Doe',
10
- country: 'US',
11
- },
12
- });
13
- const handleSubmit = (e) => {
14
- e.preventDefault();
15
- if (validateAll()) {
16
- const addressData = getAddressObject();
17
- console.log('Address submitted:', addressData);
18
- console.log('Formatted address:', getFormattedAddress());
19
- }
20
- else {
21
- console.log('Form has validation errors');
22
- }
23
- };
24
- return (_jsxs("form", { onSubmit: handleSubmit, className: "mx-auto max-w-md space-y-4 p-6", children: [_jsx("h2", { className: "mb-4 text-xl font-bold", children: "Address Form" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "First Name" }), _jsx("input", { type: "text", value: fields.firstName.value, onChange: (e) => setValue('firstName', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.firstName.error ? 'border-red-500' : 'border-gray-300'}` }), fields.firstName.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.firstName.error })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "Last Name" }), _jsx("input", { type: "text", value: fields.lastName.value, onChange: (e) => setValue('lastName', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.lastName.error ? 'border-red-500' : 'border-gray-300'}` }), fields.lastName.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.lastName.error })] })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "Email" }), _jsx("input", { type: "email", value: fields.email.value, onChange: (e) => setValue('email', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.email.error ? 'border-red-500' : 'border-gray-300'}` }), fields.email.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.email.error })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "Phone" }), _jsx("input", { type: "tel", value: fields.phone.value, onChange: (e) => setValue('phone', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.phone.error ? 'border-red-500' : 'border-gray-300'}` }), fields.phone.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.phone.error })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "Address Line 1" }), _jsx("input", { type: "text", value: fields.address1.value, onChange: (e) => setValue('address1', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.address1.error ? 'border-red-500' : 'border-gray-300'}` }), fields.address1.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.address1.error })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "Address Line 2 (Optional)" }), _jsx("input", { type: "text", value: fields.address2.value, onChange: (e) => setValue('address2', e.target.value), className: "w-full rounded-md border border-gray-300 px-3 py-2" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "City" }), _jsx("input", { type: "text", value: fields.city.value, onChange: (e) => setValue('city', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.city.error ? 'border-red-500' : 'border-gray-300'}` }), fields.city.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.city.error })] }), _jsxs("div", { children: [_jsx("label", { className: "mb-1 block text-sm font-medium", children: "Postal Code" }), _jsx("input", { type: "text", value: fields.postal.value, onChange: (e) => setValue('postal', e.target.value), className: `w-full rounded-md border px-3 py-2 ${fields.postal.error ? 'border-red-500' : 'border-gray-300'}` }), fields.postal.error && _jsx("p", { className: "mt-1 text-sm text-red-500", children: fields.postal.error })] })] }), _jsxs("div", { className: "rounded-md bg-gray-100 p-4", children: [_jsx("h3", { className: "mb-2 text-sm font-semibold", children: "Form Data:" }), _jsx("pre", { className: "text-xs", children: JSON.stringify(getAddressObject(), null, 2) })] }), _jsxs("div", { className: "text-xs text-gray-500", children: [_jsxs("p", { children: ["Available Countries: ", countries.length] }), _jsxs("p", { children: ["Available States for ", fields.country.value, ": ", states.length] }), _jsxs("p", { children: ["Form Valid: ", validateAll() ? 'Yes' : 'No'] })] }), _jsx("button", { type: "button", onClick: () => {
25
- if (validateAll()) {
26
- alert('Form is valid! Data: ' + JSON.stringify(getAddressObject()));
27
- }
28
- else {
29
- alert('Please fix the errors in the form');
30
- }
31
- }, className: "w-full rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:opacity-50", children: "Submit" })] }));
32
- };
@@ -1,59 +0,0 @@
1
- export interface Country {
2
- code: string;
3
- name: string;
4
- }
5
- export interface State {
6
- code: string;
7
- name: string;
8
- countryCode: string;
9
- }
10
- export interface AddressField {
11
- value: string;
12
- isValid: boolean;
13
- error?: string;
14
- touched?: boolean;
15
- }
16
- export interface AddressData {
17
- firstName: string;
18
- lastName: string;
19
- email: string;
20
- phone: string;
21
- country: string;
22
- address1: string;
23
- address2: string;
24
- city: string;
25
- state: string;
26
- postal: string;
27
- }
28
- export interface UseAddressOptions {
29
- autoValidate?: boolean;
30
- enableGooglePlaces?: boolean;
31
- googlePlacesApiKey?: string;
32
- initialValues?: Partial<AddressData>;
33
- validation?: Partial<Record<keyof AddressData, (value: string) => string | undefined>>;
34
- countryRestrictions?: string[];
35
- onFieldsChange?: (addressData: AddressData) => void;
36
- debounceConfig?: {
37
- manualInputDelay?: number;
38
- googlePlacesDelay?: number;
39
- enabled?: boolean;
40
- };
41
- }
42
- export interface UseAddressResult {
43
- fields: Record<keyof AddressData, AddressField>;
44
- countries: Country[];
45
- states: State[];
46
- setValue: (field: keyof AddressData, value: string) => void;
47
- setValues: (values: Partial<AddressData>) => void;
48
- resetField: (field: keyof AddressData) => void;
49
- resetForm: () => void;
50
- validate: (field: keyof AddressData) => boolean;
51
- validateAll: () => boolean;
52
- getFormattedAddress: () => string;
53
- getAddressObject: () => AddressData;
54
- addressRef?: React.MutableRefObject<HTMLInputElement | null>;
55
- addressInputValue?: string;
56
- handleAddressChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
57
- isAddressSelected?: boolean;
58
- }
59
- export declare function useAddress(options?: UseAddressOptions): UseAddressResult;