@tagadapay/plugin-sdk 2.7.18 → 2.7.21
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/dist/react/components/DebugDrawer.js +80 -2
- package/dist/react/hooks/useFunnel.d.ts +87 -3
- package/dist/react/hooks/useFunnel.js +77 -5
- package/dist/react/providers/TagadaProvider.d.ts +8 -0
- package/dist/react/providers/TagadaProvider.js +19 -0
- package/dist/v2/core/resources/funnel.d.ts +71 -5
- package/dist/v2/core/resources/funnel.js +27 -3
- package/dist/v2/index.d.ts +3 -2
- package/dist/v2/index.js +2 -1
- package/dist/v2/react/components/DebugDrawer.js +229 -15
- package/dist/v2/react/hooks/useFunnel.d.ts +4 -4
- package/dist/v2/react/hooks/useFunnel.js +37 -5
- package/dist/v2/react/hooks/usePostPurchasesQuery.js +77 -39
- package/dist/v2/react/index.d.ts +1 -0
- package/dist/v2/react/index.js +2 -0
- package/dist/v2/react/providers/TagadaProvider.d.ts +8 -0
- package/dist/v2/react/providers/TagadaProvider.js +94 -55
- package/package.json +1 -1
|
@@ -77,6 +77,54 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
77
77
|
const context = useTagadaContext();
|
|
78
78
|
const pluginConfig = usePluginConfig();
|
|
79
79
|
const [activeTab, setActiveTab] = useState('overview');
|
|
80
|
+
// Handler to jump to a specific step using direct_navigation
|
|
81
|
+
const handleJumpToStep = async (stepId, stepName) => {
|
|
82
|
+
// Try to get sessionId from debug data or context
|
|
83
|
+
const sessionId = context.debugFunnel.data?.session?.sessionId || context.session?.sessionId;
|
|
84
|
+
if (!sessionId) {
|
|
85
|
+
alert('No active funnel session found');
|
|
86
|
+
console.error('Debug data:', context.debugFunnel.data);
|
|
87
|
+
console.error('Context:', context);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(`🎯 [DebugDrawer] Jumping to step ${stepId} (${stepName}) in session ${sessionId}`);
|
|
91
|
+
try {
|
|
92
|
+
// Use direct_navigation event type to jump to any step
|
|
93
|
+
const response = await context.apiService.fetch('/api/v1/funnel/navigate', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
body: {
|
|
96
|
+
sessionId: sessionId,
|
|
97
|
+
event: {
|
|
98
|
+
type: 'direct_navigation',
|
|
99
|
+
data: {
|
|
100
|
+
targetStepId: stepId,
|
|
101
|
+
},
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
},
|
|
104
|
+
contextUpdates: {
|
|
105
|
+
metadata: {
|
|
106
|
+
debugJump: true,
|
|
107
|
+
debugJumpFrom: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
currentUrl: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
console.log('🎯 [DebugDrawer] Jump response:', response);
|
|
114
|
+
if (response.success && response.result?.url) {
|
|
115
|
+
// Navigate to the step URL
|
|
116
|
+
console.log(`🎯 [DebugDrawer] Navigating to: ${response.result.url}`);
|
|
117
|
+
window.location.href = response.result.url;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
alert(`Failed to jump to ${stepName}: ${response.error || 'Unknown error'}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error('Failed to jump to step:', error);
|
|
125
|
+
alert(`Failed to jump to ${stepName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
80
128
|
useEffect(() => {
|
|
81
129
|
const handleEscape = (e) => {
|
|
82
130
|
if (e.key === 'Escape' && isOpen) {
|
|
@@ -95,13 +143,32 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
95
143
|
{ id: 'session', label: 'Session' },
|
|
96
144
|
{ id: 'auth', label: 'Auth' },
|
|
97
145
|
];
|
|
146
|
+
// Add funnel tab if funnel is active
|
|
147
|
+
const funnelTab = context.debugFunnel.isActive ? [{ id: 'funnel', label: 'Funnel' }] : [];
|
|
148
|
+
// Debug: Log comprehensive debug drawer state
|
|
149
|
+
if (isOpen) {
|
|
150
|
+
console.group('🐛 [DebugDrawer V2] Comprehensive State Check');
|
|
151
|
+
console.log('1️⃣ Debug Mode:', context.debugMode);
|
|
152
|
+
console.log('2️⃣ Funnel Debug State:', {
|
|
153
|
+
isActive: context.debugFunnel.isActive,
|
|
154
|
+
hasData: !!context.debugFunnel.data,
|
|
155
|
+
hasError: !!context.debugFunnel.error,
|
|
156
|
+
isLoading: context.debugFunnel.isLoading,
|
|
157
|
+
lastUpdated: context.debugFunnel.lastUpdated,
|
|
158
|
+
});
|
|
159
|
+
console.log('3️⃣ Funnel Data (if any):', context.debugFunnel.data);
|
|
160
|
+
console.log('4️⃣ Funnel Error (if any):', context.debugFunnel.error);
|
|
161
|
+
console.log('5️⃣ Update Function Available:', typeof context.updateFunnelDebugData);
|
|
162
|
+
console.log('6️⃣ Will show Funnel tab:', funnelTab.length > 0);
|
|
163
|
+
console.groupEnd();
|
|
164
|
+
}
|
|
98
165
|
// Add checkout tab if checkout is active
|
|
99
166
|
const checkoutTab = context.debugCheckout.isActive ? [{ id: 'checkout', label: 'Checkout' }] : [];
|
|
100
167
|
// Add items tab if checkout has summary data
|
|
101
168
|
const itemsTab = context.debugCheckout.isActive && context.debugCheckout.data?.checkout?.summary
|
|
102
169
|
? [{ id: 'items', label: 'Items' }]
|
|
103
170
|
: [];
|
|
104
|
-
const tabs = [...baseTabs, ...checkoutTab, ...itemsTab, { id: 'raw', label: 'Raw Data' }];
|
|
171
|
+
const tabs = [...baseTabs, ...funnelTab, ...checkoutTab, ...itemsTab, { id: 'raw', label: 'Raw Data' }];
|
|
105
172
|
return (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
106
173
|
position: 'fixed',
|
|
107
174
|
top: 0,
|
|
@@ -115,33 +182,33 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
115
182
|
top: 0,
|
|
116
183
|
right: 0,
|
|
117
184
|
bottom: 0,
|
|
118
|
-
width: '
|
|
119
|
-
minWidth: '
|
|
120
|
-
maxWidth: '
|
|
185
|
+
width: '35vw',
|
|
186
|
+
minWidth: '400px',
|
|
187
|
+
maxWidth: '550px',
|
|
121
188
|
backgroundColor: '#1f2937',
|
|
122
189
|
color: '#f9fafb',
|
|
123
190
|
boxShadow: '-4px 0 24px rgba(0, 0, 0, 0.3)',
|
|
124
191
|
zIndex: 999999,
|
|
125
192
|
display: 'flex',
|
|
126
193
|
flexDirection: 'column',
|
|
127
|
-
fontSize: '
|
|
194
|
+
fontSize: '13px',
|
|
128
195
|
fontFamily: 'ui-monospace, SFMono-Regular, Consolas, monospace',
|
|
129
196
|
}, children: [_jsxs("div", { style: {
|
|
130
|
-
padding: '
|
|
197
|
+
padding: '12px 14px',
|
|
131
198
|
borderBottom: '1px solid #374151',
|
|
132
199
|
display: 'flex',
|
|
133
200
|
justifyContent: 'space-between',
|
|
134
201
|
alignItems: 'center',
|
|
135
202
|
backgroundColor: '#111827',
|
|
136
|
-
}, children: [_jsxs("div", { children: [_jsx("h2", { style: { margin: 0, fontSize: '
|
|
203
|
+
}, children: [_jsxs("div", { children: [_jsx("h2", { style: { margin: 0, fontSize: '16px', fontWeight: 'bold' }, children: "\uD83D\uDC1B SDK Debug" }), _jsx("p", { style: { margin: '2px 0 0 0', fontSize: '11px', color: '#9ca3af' }, children: "Real-time state inspection" })] }), _jsx("button", { onClick: onClose, style: {
|
|
137
204
|
background: 'none',
|
|
138
205
|
border: 'none',
|
|
139
206
|
color: '#9ca3af',
|
|
140
207
|
cursor: 'pointer',
|
|
141
|
-
fontSize: '
|
|
142
|
-
padding: '
|
|
208
|
+
fontSize: '22px',
|
|
209
|
+
padding: '2px',
|
|
143
210
|
}, children: "\u00D7" })] }), _jsx("div", { style: {
|
|
144
|
-
padding: '0
|
|
211
|
+
padding: '0 12px',
|
|
145
212
|
borderBottom: '1px solid #374151',
|
|
146
213
|
backgroundColor: '#111827',
|
|
147
214
|
}, children: _jsx("div", { style: { display: 'flex', gap: '0' }, children: tabs.map((tab) => (_jsx("button", { onClick: () => setActiveTab(tab.id), style: {
|
|
@@ -149,15 +216,15 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
149
216
|
border: 'none',
|
|
150
217
|
color: activeTab === tab.id ? '#60a5fa' : '#9ca3af',
|
|
151
218
|
cursor: 'pointer',
|
|
152
|
-
padding: '12px
|
|
153
|
-
fontSize: '
|
|
219
|
+
padding: '10px 12px',
|
|
220
|
+
fontSize: '12px',
|
|
154
221
|
borderBottom: activeTab === tab.id ? '2px solid #60a5fa' : '2px solid transparent',
|
|
155
222
|
}, children: tab.label }, tab.id))) }) }), _jsxs("div", { style: {
|
|
156
223
|
flex: 1,
|
|
157
|
-
padding: '
|
|
224
|
+
padding: '12px',
|
|
158
225
|
overflow: 'auto',
|
|
159
226
|
backgroundColor: '#1f2937',
|
|
160
|
-
}, children: [activeTab === 'overview' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0
|
|
227
|
+
}, children: [activeTab === 'overview' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 12px 0', color: '#60a5fa', fontSize: '14px' }, children: "SDK Overview" }), _jsxs("div", { style: { display: 'grid', gap: '10px', fontSize: '12px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Initialized:" }), _jsx("span", { style: { color: context.isInitialized ? '#10b981' : '#ef4444' }, children: context.isInitialized ? '✅ Yes' : '❌ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Loading:" }), _jsx("span", { style: { color: context.isLoading ? '#f59e0b' : '#10b981' }, children: context.isLoading ? '⏳ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Environment:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.environment.apiConfig.baseUrl.includes('dev') ? 'Development' : 'Production' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "API Base URL:" }), _jsx("span", { style: { color: '#10b981' }, children: context.environment.apiConfig.baseUrl })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Store ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.store?.id || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Customer ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.customer?.id || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Authenticated:" }), _jsx("span", { style: { color: context.auth.isAuthenticated ? '#10b981' : '#ef4444' }, children: context.auth.isAuthenticated ? '✅ Yes' : '❌ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Currency:" }), _jsxs("span", { style: { color: '#f59e0b' }, children: [context.currency.code, " (", context.currency.symbol, ")"] })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Locale:" }), _jsx("span", { style: { color: '#8b5cf6' }, children: context.locale.locale })] })] })] })), activeTab === 'config' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 12px 0', color: '#60a5fa', fontSize: '14px' }, children: "Plugin Configuration" }), _jsxs("div", { style: { marginBottom: '16px' }, children: [_jsx("h4", { style: { margin: '0 0 10px 0', color: '#9ca3af', fontSize: '13px' }, children: "Configuration Summary" }), _jsxs("div", { style: { display: 'grid', gap: '8px', fontSize: '13px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Store ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: pluginConfig.storeId || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Account ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: pluginConfig.accountId || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Base Path:" }), _jsx("span", { style: { color: '#10b981' }, children: pluginConfig.basePath || '/' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Config Name:" }), _jsx("span", { style: { color: '#f59e0b' }, children: pluginConfig.config?.configName || 'default' })] }), pluginConfig.config?.branding?.primaryColor && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Primary Color:" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("div", { style: {
|
|
161
228
|
width: '16px',
|
|
162
229
|
height: '16px',
|
|
163
230
|
backgroundColor: pluginConfig.config.branding.primaryColor,
|
|
@@ -368,7 +435,154 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
368
435
|
catch {
|
|
369
436
|
return String(Math.abs(Number(adjustment.amount)));
|
|
370
437
|
}
|
|
371
|
-
})() })] }, index))) })] }))] })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No checkout summary data available" }))] })), activeTab === '
|
|
438
|
+
})() })] }, index))) })] }))] })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No checkout summary data available" }))] })), activeTab === 'funnel' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 12px 0', color: '#60a5fa', fontSize: '14px' }, children: "\uD83D\uDE80 Funnel Debug" }), context.debugFunnel.isActive ? (_jsxs("div", { children: [_jsxs("div", { style: {
|
|
439
|
+
display: 'flex',
|
|
440
|
+
gap: '10px',
|
|
441
|
+
padding: '6px 10px',
|
|
442
|
+
backgroundColor: '#111827',
|
|
443
|
+
borderRadius: '4px',
|
|
444
|
+
marginBottom: '12px',
|
|
445
|
+
fontSize: '10px',
|
|
446
|
+
alignItems: 'center',
|
|
447
|
+
}, children: [_jsx("span", { style: { color: '#10b981' }, children: "\u2705 Active" }), _jsx("span", { style: { color: '#4b5563' }, children: "\u2022" }), _jsx("span", { style: { color: context.debugFunnel.isLoading ? '#f59e0b' : '#6b7280' }, children: context.debugFunnel.isLoading ? '⏳ Loading' : '✓ Ready' }), context.debugFunnel.error && (_jsxs(_Fragment, { children: [_jsx("span", { style: { color: '#4b5563' }, children: "\u2022" }), _jsx("span", { style: { color: '#ef4444' }, children: "\u274C Error" })] })), _jsx("span", { style: { color: '#4b5563', marginLeft: 'auto' }, children: context.debugFunnel.lastUpdated?.toLocaleTimeString() || 'Never' })] }), context.debugFunnel.error && (_jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#ef4444' }, children: "Error Details" }), _jsxs("div", { style: {
|
|
448
|
+
backgroundColor: '#372c2c',
|
|
449
|
+
padding: '12px',
|
|
450
|
+
borderRadius: '4px',
|
|
451
|
+
border: '1px solid #ef4444',
|
|
452
|
+
}, children: [_jsxs("div", { style: { color: '#ef4444', fontWeight: 'bold', marginBottom: '8px' }, children: [context.debugFunnel.error.name, ": ", context.debugFunnel.error.message] }), context.debugFunnel.error.stack && (_jsx("pre", { style: {
|
|
453
|
+
color: '#9ca3af',
|
|
454
|
+
fontSize: '11px',
|
|
455
|
+
margin: 0,
|
|
456
|
+
whiteSpace: 'pre-wrap',
|
|
457
|
+
}, children: context.debugFunnel.error.stack }))] })] })), context.debugFunnel.data?.funnel && (_jsxs("div", { style: {
|
|
458
|
+
padding: '8px 10px',
|
|
459
|
+
backgroundColor: '#111827',
|
|
460
|
+
borderRadius: '4px',
|
|
461
|
+
marginBottom: '12px',
|
|
462
|
+
fontSize: '11px',
|
|
463
|
+
}, children: [_jsxs("div", { style: { marginBottom: '10px' }, children: [_jsxs("div", { style: {
|
|
464
|
+
color: '#60a5fa',
|
|
465
|
+
fontSize: '11px',
|
|
466
|
+
fontWeight: 'bold',
|
|
467
|
+
marginBottom: '6px',
|
|
468
|
+
}, children: ["\uD83D\uDCCA ", context.debugFunnel.data.funnel.name] }), _jsxs("div", { style: {
|
|
469
|
+
display: 'grid',
|
|
470
|
+
gridTemplateColumns: '1fr 1fr',
|
|
471
|
+
gap: '6px',
|
|
472
|
+
fontSize: '11px',
|
|
473
|
+
}, children: [_jsxs("div", { children: [_jsx("span", { style: { color: '#6b7280' }, children: "ID: " }), _jsx("span", { style: { color: '#f59e0b', fontFamily: 'monospace', fontSize: '10px' }, children: context.debugFunnel.data.funnel.id })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Steps: " }), _jsx("span", { style: { color: '#60a5fa', fontWeight: 'bold' }, children: context.debugFunnel.data.funnel.steps?.length || 0 })] })] })] }), context.debugFunnel.data?.session && (_jsxs("div", { style: {
|
|
474
|
+
paddingTop: '10px',
|
|
475
|
+
borderTop: '1px solid #374151',
|
|
476
|
+
}, children: [_jsx("div", { style: {
|
|
477
|
+
color: '#60a5fa',
|
|
478
|
+
fontSize: '11px',
|
|
479
|
+
fontWeight: 'bold',
|
|
480
|
+
marginBottom: '6px',
|
|
481
|
+
}, children: "\uD83C\uDFAF Session" }), _jsxs("div", { style: { display: 'grid', gap: '4px', fontSize: '11px' }, children: [context.debugFunnel.data.session.sessionId && (_jsxs("div", { style: { marginBottom: '6px' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "ID: " }), _jsx("span", { style: {
|
|
482
|
+
color: '#f59e0b',
|
|
483
|
+
fontFamily: 'monospace',
|
|
484
|
+
fontSize: '10px',
|
|
485
|
+
}, children: context.debugFunnel.data.session.sessionId })] })), _jsxs("div", { style: {
|
|
486
|
+
display: 'flex',
|
|
487
|
+
justifyContent: 'space-between',
|
|
488
|
+
alignItems: 'center',
|
|
489
|
+
}, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Current:" }), _jsx("span", { style: {
|
|
490
|
+
color: '#10b981',
|
|
491
|
+
fontWeight: 'bold',
|
|
492
|
+
fontFamily: 'monospace',
|
|
493
|
+
fontSize: '10px',
|
|
494
|
+
backgroundColor: '#065f46',
|
|
495
|
+
padding: '2px 6px',
|
|
496
|
+
borderRadius: '3px',
|
|
497
|
+
}, children: context.debugFunnel.data.session.currentStepId })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }, children: [_jsxs("div", { children: [_jsx("span", { style: { color: '#6b7280' }, children: "Furthest: " }), _jsx("span", { style: { color: '#60a5fa', fontSize: '10px' }, children: context.debugFunnel.data.session.furthestStepId })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("span", { style: { color: '#6b7280' }, children: "Previous: " }), _jsx("span", { style: { color: '#9ca3af', fontSize: '10px' }, children: context.debugFunnel.data.session.previousStepId || 'None' })] })] })] })] }))] })), context.debugFunnel.data?.funnel?.steps && (_jsxs("div", { style: { marginBottom: '16px' }, children: [_jsxs("h4", { style: { margin: '0 0 8px 0', color: '#60a5fa', fontSize: '13px' }, children: ["\uD83D\uDCCB Steps (", context.debugFunnel.data.funnel.steps.length, ")"] }), _jsx("div", { style: { display: 'grid', gap: '8px' }, children: context.debugFunnel.data.funnel.steps.map((step, index) => {
|
|
498
|
+
const isCurrent = step.id === context.debugFunnel.data?.session?.currentStepId;
|
|
499
|
+
const isVisited = context.debugFunnel.data?.session?.visitedSteps?.includes(step.id);
|
|
500
|
+
const isEntry = step.id === context.debugFunnel.data?.funnel?.entryStepId;
|
|
501
|
+
return (_jsxs("div", { style: {
|
|
502
|
+
border: isCurrent ? '2px solid #10b981' : '1px solid #374151',
|
|
503
|
+
borderRadius: '6px',
|
|
504
|
+
padding: '8px 10px',
|
|
505
|
+
backgroundColor: isCurrent ? '#065f46' : '#111827',
|
|
506
|
+
}, children: [_jsxs("div", { style: {
|
|
507
|
+
display: 'flex',
|
|
508
|
+
alignItems: 'center',
|
|
509
|
+
gap: '6px',
|
|
510
|
+
marginBottom: '4px',
|
|
511
|
+
}, children: [_jsxs("span", { style: {
|
|
512
|
+
color: isCurrent ? '#fff' : '#9ca3af',
|
|
513
|
+
fontSize: '10px',
|
|
514
|
+
minWidth: '18px',
|
|
515
|
+
}, children: [index + 1, "."] }), _jsx("span", { style: {
|
|
516
|
+
color: isCurrent ? '#fff' : '#f9fafb',
|
|
517
|
+
fontSize: '12px',
|
|
518
|
+
fontWeight: 'bold',
|
|
519
|
+
flex: 1,
|
|
520
|
+
}, children: step.name }), isCurrent && (_jsx("span", { style: {
|
|
521
|
+
fontSize: '9px',
|
|
522
|
+
backgroundColor: '#10b981',
|
|
523
|
+
color: '#000',
|
|
524
|
+
padding: '2px 5px',
|
|
525
|
+
borderRadius: '3px',
|
|
526
|
+
fontWeight: 'bold',
|
|
527
|
+
}, children: "CURRENT" })), isEntry && (_jsx("span", { style: {
|
|
528
|
+
fontSize: '9px',
|
|
529
|
+
backgroundColor: '#8b5cf6',
|
|
530
|
+
color: '#fff',
|
|
531
|
+
padding: '2px 5px',
|
|
532
|
+
borderRadius: '3px',
|
|
533
|
+
}, children: "ENTRY" })), isVisited && !isCurrent && (_jsx("span", { style: { fontSize: '12px', color: '#10b981' }, children: "\u2713" })), !isCurrent && (_jsx("button", { onClick: () => handleJumpToStep(step.id, step.name), style: {
|
|
534
|
+
fontSize: '9px',
|
|
535
|
+
backgroundColor: '#3b82f6',
|
|
536
|
+
color: '#fff',
|
|
537
|
+
padding: '3px 8px',
|
|
538
|
+
borderRadius: '4px',
|
|
539
|
+
border: 'none',
|
|
540
|
+
cursor: 'pointer',
|
|
541
|
+
fontWeight: 'bold',
|
|
542
|
+
transition: 'background-color 0.2s',
|
|
543
|
+
}, onMouseEnter: (e) => {
|
|
544
|
+
e.currentTarget.style.backgroundColor = '#2563eb';
|
|
545
|
+
}, onMouseLeave: (e) => {
|
|
546
|
+
e.currentTarget.style.backgroundColor = '#3b82f6';
|
|
547
|
+
}, title: `Jump to ${step.name}`, children: "\u2197 JUMP" }))] }), _jsx("div", { style: {
|
|
548
|
+
fontSize: '10px',
|
|
549
|
+
color: isCurrent ? '#d1fae5' : '#9ca3af',
|
|
550
|
+
fontFamily: 'monospace',
|
|
551
|
+
marginBottom: '2px',
|
|
552
|
+
marginLeft: '24px',
|
|
553
|
+
}, children: step.id }), _jsx("div", { style: {
|
|
554
|
+
fontSize: '10px',
|
|
555
|
+
color: isCurrent ? '#a7f3d0' : '#6b7280',
|
|
556
|
+
marginLeft: '24px',
|
|
557
|
+
}, children: step.type }), step.urls && step.urls.length > 0 && (_jsxs("div", { style: { marginTop: '6px', marginLeft: '24px' }, children: [_jsxs("div", { style: {
|
|
558
|
+
fontSize: '9px',
|
|
559
|
+
color: isCurrent ? '#a7f3d0' : '#9ca3af',
|
|
560
|
+
marginBottom: '2px',
|
|
561
|
+
}, children: ["\uD83D\uDD17 ", step.urls.length, " URL", step.urls.length > 1 ? 's' : ''] }), step.urls.map((url, urlIdx) => (_jsxs("div", { style: {
|
|
562
|
+
fontSize: '9px',
|
|
563
|
+
color: isCurrent ? '#6ee7b7' : '#6b7280',
|
|
564
|
+
fontFamily: 'monospace',
|
|
565
|
+
marginLeft: '12px',
|
|
566
|
+
overflow: 'hidden',
|
|
567
|
+
textOverflow: 'ellipsis',
|
|
568
|
+
whiteSpace: 'nowrap',
|
|
569
|
+
}, children: ["\u2022 ", url] }, urlIdx)))] })), step.conditions && step.conditions.length > 0 && (_jsxs("div", { style: {
|
|
570
|
+
marginTop: '6px',
|
|
571
|
+
padding: '6px',
|
|
572
|
+
backgroundColor: isCurrent ? '#047857' : '#1f2937',
|
|
573
|
+
borderRadius: '4px',
|
|
574
|
+
marginLeft: '24px',
|
|
575
|
+
}, children: [_jsxs("div", { style: {
|
|
576
|
+
fontSize: '9px',
|
|
577
|
+
color: '#f59e0b',
|
|
578
|
+
fontWeight: 'bold',
|
|
579
|
+
marginBottom: '3px',
|
|
580
|
+
}, children: ["\u2699\uFE0F ", step.conditions.length, " Condition", step.conditions.length > 1 ? 's' : ''] }), step.conditions.map((cond, condIdx) => (_jsxs("div", { style: {
|
|
581
|
+
fontSize: '9px',
|
|
582
|
+
color: isCurrent ? '#d1fae5' : '#9ca3af',
|
|
583
|
+
marginLeft: '8px',
|
|
584
|
+
}, children: ["\u2022 ", cond.type, " \u2192 ", cond.nextStepId] }, condIdx)))] }))] }, step.id));
|
|
585
|
+
}) })] })), _jsxs("div", { style: { marginTop: '12px' }, children: [_jsx("h4", { style: { margin: '0 0 8px 0', color: '#60a5fa', fontSize: '13px' }, children: "\uD83D\uDD0D Raw Data" }), _jsx("div", { style: { fontSize: '11px' }, children: _jsx(TreeView, { data: context.debugFunnel.data, name: "funnelData" }) })] })] })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No funnel hook active" }))] })), activeTab === 'checkout' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Checkout Debug" }), context.debugCheckout.isActive ? (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#60a5fa' }, children: "Status" }), _jsxs("div", { style: { display: 'grid', gap: '8px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Hook Active:" }), _jsx("span", { style: { color: '#10b981' }, children: "\u2705 Yes" })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Loading:" }), _jsx("span", { style: { color: context.debugCheckout.isLoading ? '#f59e0b' : '#10b981' }, children: context.debugCheckout.isLoading ? '⏳ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Has Error:" }), _jsx("span", { style: { color: context.debugCheckout.error ? '#ef4444' : '#10b981' }, children: context.debugCheckout.error ? '❌ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Last Updated:" }), _jsx("span", { style: { color: '#9ca3af', fontSize: '12px' }, children: context.debugCheckout.lastUpdated?.toLocaleTimeString() || 'Never' })] })] })] }), context.debugCheckout.error && (_jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#ef4444' }, children: "Error Details" }), _jsxs("div", { style: {
|
|
372
586
|
backgroundColor: '#372c2c',
|
|
373
587
|
padding: '12px',
|
|
374
588
|
borderRadius: '4px',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Modern implementation using TanStack Query for state management
|
|
5
5
|
* and the v2 ApiClient for API calls.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { FunnelAction, FunnelNavigationResult, SimpleFunnelContext } from '../../core/resources/funnel';
|
|
8
8
|
export interface UseFunnelOptions {
|
|
9
9
|
funnelId?: string;
|
|
10
10
|
currentStepId?: string;
|
|
@@ -14,7 +14,7 @@ export interface UseFunnelOptions {
|
|
|
14
14
|
enabled?: boolean;
|
|
15
15
|
}
|
|
16
16
|
export interface UseFunnelResult {
|
|
17
|
-
next: (event:
|
|
17
|
+
next: (event: FunnelAction) => Promise<any>;
|
|
18
18
|
goToStep: (stepId: string) => Promise<any>;
|
|
19
19
|
updateContext: (updates: Partial<SimpleFunnelContext>) => Promise<void>;
|
|
20
20
|
currentStep: {
|
|
@@ -43,9 +43,9 @@ export declare function useFunnel(options: UseFunnelOptions): UseFunnelResult;
|
|
|
43
43
|
*/
|
|
44
44
|
export declare function useSimpleFunnel(funnelId: string, initialStepId?: string): {
|
|
45
45
|
currentStepId: string;
|
|
46
|
-
next: (event:
|
|
46
|
+
next: (event: FunnelAction) => Promise<any>;
|
|
47
47
|
goToStep: (stepId: string) => Promise<any>;
|
|
48
48
|
isLoading: boolean;
|
|
49
49
|
context: SimpleFunnelContext | null;
|
|
50
50
|
};
|
|
51
|
-
export type { FunnelEvent, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from '../../core/resources/funnel';
|
|
51
|
+
export type { FunnelAction as FunnelEvent, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from '../../core/resources/funnel';
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Modern implementation using TanStack Query for state management
|
|
5
5
|
* and the v2 ApiClient for API calls.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
8
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
|
+
import { FunnelEventType, FunnelResource } from '../../core/resources/funnel';
|
|
9
10
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
10
|
-
import { FunnelResource } from '../../core/resources/funnel';
|
|
11
11
|
import { getGlobalApiClient } from './useApiQuery';
|
|
12
12
|
// Query keys for funnel operations
|
|
13
13
|
const funnelQueryKeys = {
|
|
@@ -21,7 +21,7 @@ const funnelQueryKeys = {
|
|
|
21
21
|
* and the v2 ApiClient architecture.
|
|
22
22
|
*/
|
|
23
23
|
export function useFunnel(options) {
|
|
24
|
-
const { auth, store } = useTagadaContext();
|
|
24
|
+
const { auth, store, debugMode, updateFunnelDebugData } = useTagadaContext();
|
|
25
25
|
const queryClient = useQueryClient();
|
|
26
26
|
const apiClient = getGlobalApiClient();
|
|
27
27
|
const funnelResource = useMemo(() => new FunnelResource(apiClient), [apiClient]);
|
|
@@ -34,6 +34,30 @@ export function useFunnel(options) {
|
|
|
34
34
|
const urlParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams();
|
|
35
35
|
const urlFunnelId = urlParams.get('funnelId') || undefined;
|
|
36
36
|
const effectiveFunnelId = urlFunnelId || options.funnelId;
|
|
37
|
+
/**
|
|
38
|
+
* Fetch debug data for the funnel debugger
|
|
39
|
+
*/
|
|
40
|
+
const fetchFunnelDebugData = useCallback(async (sessionId) => {
|
|
41
|
+
if (!debugMode) {
|
|
42
|
+
console.log('🐛 [useFunnel V2] Debug mode is OFF, skipping debug data fetch');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log('🐛 [useFunnel V2] Fetching debug data for session:', sessionId);
|
|
46
|
+
try {
|
|
47
|
+
const response = await funnelResource.getSession(sessionId, undefined, true);
|
|
48
|
+
console.log('🐛 [useFunnel V2] Debug data response:', response);
|
|
49
|
+
if (response.success && response.debugData) {
|
|
50
|
+
updateFunnelDebugData(response.debugData, null, false);
|
|
51
|
+
console.log('🐛 [useFunnel V2] Debug data updated successfully:', response.debugData);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.warn('🐛 [useFunnel V2] No debug data in response');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('🐛 [useFunnel V2] Failed to fetch funnel debug data:', error);
|
|
59
|
+
}
|
|
60
|
+
}, [funnelResource, debugMode, updateFunnelDebugData]);
|
|
37
61
|
// Session query - only enabled when we have a session ID
|
|
38
62
|
const { data: sessionData, isLoading: isSessionLoading, error: sessionError, refetch: refetchSession } = useQuery({
|
|
39
63
|
queryKey: funnelQueryKeys.session(context?.sessionId || ''),
|
|
@@ -115,6 +139,10 @@ export function useFunnel(options) {
|
|
|
115
139
|
// Set session cookie for persistence across page reloads
|
|
116
140
|
setSessionCookie(newContext.sessionId);
|
|
117
141
|
console.log(`🍪 Funnel: Initialized session for funnel ${effectiveFunnelId || 'default'}`, newContext);
|
|
142
|
+
// Fetch debug data if in debug mode
|
|
143
|
+
if (debugMode) {
|
|
144
|
+
void fetchFunnelDebugData(newContext.sessionId);
|
|
145
|
+
}
|
|
118
146
|
// Invalidate session query to refetch with new session ID
|
|
119
147
|
void queryClient.invalidateQueries({
|
|
120
148
|
queryKey: funnelQueryKeys.session(newContext.sessionId)
|
|
@@ -222,6 +250,10 @@ export function useFunnel(options) {
|
|
|
222
250
|
performNavigation(updatedAction);
|
|
223
251
|
}
|
|
224
252
|
console.log(`🍪 Funnel: Navigated from ${context.currentStepId} to ${result.stepId}`);
|
|
253
|
+
// Fetch debug data if in debug mode
|
|
254
|
+
if (debugMode) {
|
|
255
|
+
void fetchFunnelDebugData(newContext.sessionId);
|
|
256
|
+
}
|
|
225
257
|
// Invalidate and refetch session data
|
|
226
258
|
void queryClient.invalidateQueries({
|
|
227
259
|
queryKey: funnelQueryKeys.session(newContext.sessionId)
|
|
@@ -373,7 +405,7 @@ export function useFunnel(options) {
|
|
|
373
405
|
}, [navigateMutation]);
|
|
374
406
|
const goToStep = useCallback(async (stepId) => {
|
|
375
407
|
return next({
|
|
376
|
-
type:
|
|
408
|
+
type: FunnelEventType.DIRECT_NAVIGATION,
|
|
377
409
|
data: { targetStepId: stepId },
|
|
378
410
|
timestamp: new Date().toISOString()
|
|
379
411
|
});
|
|
@@ -3,16 +3,25 @@
|
|
|
3
3
|
* Handles post-purchase offers with automatic caching
|
|
4
4
|
*/
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { PostPurchasesResource } from '../../core/resources/postPurchases';
|
|
8
8
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
9
|
import { getGlobalApiClient } from './useApiQuery';
|
|
10
10
|
export function usePostPurchasesQuery(options) {
|
|
11
|
+
console.log('usePostPurchasesQuery');
|
|
11
12
|
const { orderId, enabled = true, autoInitializeCheckout = false } = options;
|
|
12
13
|
const queryClient = useQueryClient();
|
|
13
14
|
const { session } = useTagadaContext();
|
|
14
15
|
// State for checkout sessions per offer
|
|
15
16
|
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
17
|
+
// Ref to track checkout sessions for synchronous access
|
|
18
|
+
const checkoutSessionsRef = useRef({});
|
|
19
|
+
// Ref to track offers being initialized to prevent duplicate initialization
|
|
20
|
+
const initializingOffers = useRef(new Set());
|
|
21
|
+
// Keep ref in sync with state
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
checkoutSessionsRef.current = checkoutSessions;
|
|
24
|
+
}, [checkoutSessions]);
|
|
16
25
|
// Create post purchases resource client
|
|
17
26
|
const postPurchasesResource = useMemo(() => {
|
|
18
27
|
try {
|
|
@@ -70,6 +79,62 @@ export function usePostPurchasesQuery(options) {
|
|
|
70
79
|
throw error;
|
|
71
80
|
}
|
|
72
81
|
}, [postPurchasesResource]);
|
|
82
|
+
// Initialize checkout session for an offer
|
|
83
|
+
const initializeOfferCheckout = useCallback(async (offerId) => {
|
|
84
|
+
try {
|
|
85
|
+
// Check if customer ID is available
|
|
86
|
+
if (!session?.customerId) {
|
|
87
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
88
|
+
}
|
|
89
|
+
// Check if already initializing to avoid race conditions
|
|
90
|
+
if (initializingOffers.current.has(offerId)) {
|
|
91
|
+
return; // Already initializing, skip
|
|
92
|
+
}
|
|
93
|
+
// Check if already initialized using ref for synchronous access
|
|
94
|
+
if (checkoutSessionsRef.current[offerId]?.checkoutSessionId) {
|
|
95
|
+
return; // Already initialized, exit early
|
|
96
|
+
}
|
|
97
|
+
// Mark as initializing
|
|
98
|
+
initializingOffers.current.add(offerId);
|
|
99
|
+
try {
|
|
100
|
+
// Initialize checkout session
|
|
101
|
+
const initResult = await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
|
|
102
|
+
if (!initResult.checkoutSessionId) {
|
|
103
|
+
throw new Error('Failed to initialize checkout session');
|
|
104
|
+
}
|
|
105
|
+
const sessionId = initResult.checkoutSessionId;
|
|
106
|
+
// Initialize session state
|
|
107
|
+
setCheckoutSessions(prev => {
|
|
108
|
+
// Double-check we're not overwriting an existing session
|
|
109
|
+
if (prev[offerId]?.checkoutSessionId) {
|
|
110
|
+
return prev;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...prev,
|
|
114
|
+
[offerId]: {
|
|
115
|
+
checkoutSessionId: sessionId,
|
|
116
|
+
orderSummary: null,
|
|
117
|
+
selectedVariants: {},
|
|
118
|
+
loadingVariants: {},
|
|
119
|
+
isUpdatingSummary: false,
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
// Fetch order summary with variant options
|
|
124
|
+
await fetchOrderSummary(offerId, sessionId);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
// Remove from initializing set
|
|
128
|
+
initializingOffers.current.delete(offerId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// Remove from initializing set on error
|
|
133
|
+
initializingOffers.current.delete(offerId);
|
|
134
|
+
console.error(`[SDK] Failed to initialize checkout for offer ${offerId}:`, error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}, [session?.customerId, orderId, postPurchasesResource, fetchOrderSummary]);
|
|
73
138
|
// Main post-purchase offers query
|
|
74
139
|
const { data: offers = [], isLoading, error, refetch, } = useQuery({
|
|
75
140
|
queryKey: ['post-purchase-offers', orderId],
|
|
@@ -80,18 +145,17 @@ export function usePostPurchasesQuery(options) {
|
|
|
80
145
|
});
|
|
81
146
|
// Auto-initialize checkout sessions if enabled
|
|
82
147
|
useEffect(() => {
|
|
83
|
-
if (autoInitializeCheckout && offers.length > 0) {
|
|
148
|
+
if (autoInitializeCheckout && offers.length > 0 && session?.customerId) {
|
|
84
149
|
// Initialize checkout sessions for all offers
|
|
85
150
|
offers.forEach((offer) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
151
|
+
// The initializeOfferCheckout function will check if already initialized or initializing
|
|
152
|
+
void initializeOfferCheckout(offer.id).catch((error) => {
|
|
153
|
+
// Log errors but don't throw - auto-initialization failures shouldn't break the UI
|
|
154
|
+
console.error(`[SDK] Failed to auto-initialize checkout for offer ${offer.id}:`, error);
|
|
155
|
+
});
|
|
92
156
|
});
|
|
93
157
|
}
|
|
94
|
-
}, [autoInitializeCheckout, offers]);
|
|
158
|
+
}, [autoInitializeCheckout, offers, session?.customerId, initializeOfferCheckout]);
|
|
95
159
|
// Accept offer mutation
|
|
96
160
|
const acceptMutation = useMutation({
|
|
97
161
|
mutationFn: ({ offerId, items }) => {
|
|
@@ -201,40 +265,14 @@ export function usePostPurchasesQuery(options) {
|
|
|
201
265
|
getCheckoutSessionState: (offerId) => {
|
|
202
266
|
return checkoutSessions[offerId] || null;
|
|
203
267
|
},
|
|
204
|
-
initializeOfferCheckout
|
|
205
|
-
try {
|
|
206
|
-
// Check if customer ID is available
|
|
207
|
-
if (!session?.customerId) {
|
|
208
|
-
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
209
|
-
}
|
|
210
|
-
// Initialize checkout session
|
|
211
|
-
const initResult = await postPurchasesResource.initCheckoutSession(offerId, orderId, session.customerId);
|
|
212
|
-
if (!initResult.checkoutSessionId) {
|
|
213
|
-
throw new Error('Failed to initialize checkout session');
|
|
214
|
-
}
|
|
215
|
-
const sessionId = initResult.checkoutSessionId;
|
|
216
|
-
// Initialize session state
|
|
217
|
-
setCheckoutSessions(prev => ({
|
|
218
|
-
...prev,
|
|
219
|
-
[offerId]: {
|
|
220
|
-
checkoutSessionId: sessionId,
|
|
221
|
-
orderSummary: null,
|
|
222
|
-
selectedVariants: {},
|
|
223
|
-
loadingVariants: {},
|
|
224
|
-
isUpdatingSummary: false,
|
|
225
|
-
}
|
|
226
|
-
}));
|
|
227
|
-
// Fetch order summary with variant options
|
|
228
|
-
await fetchOrderSummary(offerId, sessionId);
|
|
229
|
-
}
|
|
230
|
-
catch (_error) {
|
|
231
|
-
throw _error;
|
|
232
|
-
}
|
|
233
|
-
},
|
|
268
|
+
initializeOfferCheckout,
|
|
234
269
|
getAvailableVariants: (offerId, productId) => {
|
|
235
270
|
const sessionState = checkoutSessions[offerId];
|
|
271
|
+
console.log('sessionState', sessionState);
|
|
236
272
|
if (!sessionState?.orderSummary?.options?.[productId])
|
|
237
273
|
return [];
|
|
274
|
+
console.log('getAvailableVariants', offerId, productId);
|
|
275
|
+
console.log('sessionState.orderSummary.options', sessionState.orderSummary.options);
|
|
238
276
|
return sessionState.orderSummary.options[productId].map((variant) => ({
|
|
239
277
|
variantId: variant.id,
|
|
240
278
|
variantName: variant.name,
|