shopify-chatbot-widget 1.0.4 → 1.0.5
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/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +144 -139
- package/package.json +1 -1
- package/src/Chatbot/Chatbot.tsx +62 -5
- package/src/Chatbot/components/Messages/AssistantMesage.tsx +9 -2
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +19 -17
- package/src/Chatbot/components/Messages/ChatMessages.tsx +18 -3
- package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +1 -0
- package/src/Chatbot/components/Messages/Typing.css +32 -111
- package/src/Chatbot/components/Messages/Typing.tsx +29 -18
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +2 -0
- package/src/Chatbot/components/Messenger/Messenger.tsx +10 -3
- package/src/Chatbot/components/Sessions/RenderList.tsx +3 -0
- package/src/Chatbot/components/TryOn/View.tsx +408 -0
- package/src/hooks/config.tsx +4 -4
- package/src/hooks/useChatbotAPI.tsx +1 -1
- package/src/hooks/useChatbotData.tsx +4 -1
- package/src/types/chatbot.ts +1 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import React, { useState, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ArrowLeftOutlined,
|
|
4
|
+
CloseOutlined,
|
|
5
|
+
ThunderboltFilled,
|
|
6
|
+
LoadingOutlined,
|
|
7
|
+
CameraOutlined,
|
|
8
|
+
ReloadOutlined,
|
|
9
|
+
} from '@ant-design/icons';
|
|
10
|
+
import { Button, message, Spin } from 'antd';
|
|
11
|
+
import axios from 'axios';
|
|
12
|
+
import config from '../../../hooks/config';
|
|
13
|
+
|
|
14
|
+
// --- Interfaces ---
|
|
15
|
+
|
|
16
|
+
interface ProductData {
|
|
17
|
+
variant_id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
image: string;
|
|
20
|
+
price: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface TryOnProps {
|
|
25
|
+
product: ProductData | null;
|
|
26
|
+
colorCode: string;
|
|
27
|
+
onBack: () => void;
|
|
28
|
+
setOpen: (open: boolean) => void;
|
|
29
|
+
handleAddToCart: (variantId: string, name: string, variantImage: string, price: string, qty?: number) => Promise<void>;
|
|
30
|
+
handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
|
|
31
|
+
counts: Record<string, number>;
|
|
32
|
+
setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
|
|
33
|
+
visitorId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface UploadResponse {
|
|
37
|
+
message: string;
|
|
38
|
+
image_url: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface TryOnResponse {
|
|
42
|
+
resultImage: string;
|
|
43
|
+
detected_category?: string;
|
|
44
|
+
detail?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Component ---
|
|
48
|
+
|
|
49
|
+
const TryOnLayout: React.FC<TryOnProps> = ({
|
|
50
|
+
product,
|
|
51
|
+
colorCode,
|
|
52
|
+
onBack,
|
|
53
|
+
setOpen,
|
|
54
|
+
handleAddToCart,
|
|
55
|
+
handleRemoveFromCart,
|
|
56
|
+
counts,
|
|
57
|
+
setCounts,
|
|
58
|
+
}) => {
|
|
59
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
60
|
+
const [generatedImage, setGeneratedImage] = useState<string | null>(null);
|
|
61
|
+
const [userImageBlob, setUserImageBlob] = useState<string | null>(null);
|
|
62
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
63
|
+
const [imageLoaded, setImageLoaded] = useState(false);
|
|
64
|
+
|
|
65
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
66
|
+
|
|
67
|
+
const themeColor = colorCode || '#7F28F8';
|
|
68
|
+
|
|
69
|
+
const activeProduct = product || {
|
|
70
|
+
variant_id: '0',
|
|
71
|
+
name: 'Unknown Product',
|
|
72
|
+
price: '0.00',
|
|
73
|
+
image: '',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const count = counts[activeProduct.variant_id] || 0;
|
|
77
|
+
|
|
78
|
+
const currentState = !userImageBlob ? 'empty' : !generatedImage ? 'ready' : 'generated';
|
|
79
|
+
|
|
80
|
+
// --- Handlers (same pattern as ProductCarousel) ---
|
|
81
|
+
|
|
82
|
+
const increment = async () => {
|
|
83
|
+
setCounts(prev => ({
|
|
84
|
+
...prev,
|
|
85
|
+
[activeProduct.variant_id]: (prev[activeProduct.variant_id] || 0) + 1,
|
|
86
|
+
}));
|
|
87
|
+
try {
|
|
88
|
+
await handleAddToCart(
|
|
89
|
+
activeProduct.variant_id,
|
|
90
|
+
activeProduct.name,
|
|
91
|
+
activeProduct.image,
|
|
92
|
+
activeProduct.price,
|
|
93
|
+
1
|
|
94
|
+
);
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
console.warn("Add to cart failed:", err.message);
|
|
97
|
+
setCounts(prev => ({
|
|
98
|
+
...prev,
|
|
99
|
+
[activeProduct.variant_id]: (prev[activeProduct.variant_id] || 0) - 1,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const decrement = async () => {
|
|
105
|
+
if (count === 0) return;
|
|
106
|
+
setCounts(prev => ({
|
|
107
|
+
...prev,
|
|
108
|
+
[activeProduct.variant_id]: Math.max(0, (prev[activeProduct.variant_id] || 0) - 1),
|
|
109
|
+
}));
|
|
110
|
+
await handleRemoveFromCart(activeProduct.variant_id, 1);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
114
|
+
const file = event.target.files?.[0];
|
|
115
|
+
if (file) {
|
|
116
|
+
const blobUrl = URL.createObjectURL(file);
|
|
117
|
+
setUserImageBlob(blobUrl);
|
|
118
|
+
setSelectedFile(file);
|
|
119
|
+
setGeneratedImage(null);
|
|
120
|
+
setImageLoaded(false);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const triggerFileUpload = () => {
|
|
125
|
+
fileInputRef.current?.click();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleGenerateTryOn = async () => {
|
|
129
|
+
if (!selectedFile || !activeProduct.image) return;
|
|
130
|
+
|
|
131
|
+
setIsGenerating(true);
|
|
132
|
+
setImageLoaded(false);
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const formData = new FormData();
|
|
136
|
+
formData.append('image', selectedFile);
|
|
137
|
+
|
|
138
|
+
const uploadRes = await axios.post<UploadResponse>(
|
|
139
|
+
`${config.apiUrl}upload-image/`,
|
|
140
|
+
formData,
|
|
141
|
+
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const payload = {
|
|
145
|
+
userImageUrl: uploadRes.data.image_url,
|
|
146
|
+
productImageUrl: activeProduct.image,
|
|
147
|
+
username: localStorage.getItem('company_username'),
|
|
148
|
+
productId: activeProduct.variant_id,
|
|
149
|
+
aspect_ratio: "3:4"
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const response = await axios.post<TryOnResponse>(`${config.apiUrl}ark-tryon/`, payload);
|
|
153
|
+
|
|
154
|
+
if (response.data?.resultImage) {
|
|
155
|
+
setGeneratedImage(response.data.resultImage);
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error("No image returned");
|
|
158
|
+
}
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
message.error(error.response?.data?.detail || "Generation failed");
|
|
161
|
+
} finally {
|
|
162
|
+
setIsGenerating(false);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const handleReset = () => {
|
|
167
|
+
setUserImageBlob(null);
|
|
168
|
+
setSelectedFile(null);
|
|
169
|
+
setGeneratedImage(null);
|
|
170
|
+
setImageLoaded(false);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// --- Render States ---
|
|
174
|
+
|
|
175
|
+
const renderEmptyState = () => (
|
|
176
|
+
<div
|
|
177
|
+
onClick={triggerFileUpload}
|
|
178
|
+
style={{
|
|
179
|
+
position: 'absolute', inset: 0,
|
|
180
|
+
display: 'flex', flexDirection: 'column',
|
|
181
|
+
alignItems: 'center', justifyContent: 'center',
|
|
182
|
+
background: '#f5f5f7', cursor: 'pointer',
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{activeProduct.image && (
|
|
186
|
+
<img src={activeProduct.image} alt=""
|
|
187
|
+
style={{
|
|
188
|
+
position: 'absolute', inset: 0, width: '100%', height: '100%',
|
|
189
|
+
objectFit: 'cover', opacity: 0.08, filter: 'blur(20px)',
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
192
|
+
)}
|
|
193
|
+
<div style={{
|
|
194
|
+
width: 80, height: 80, borderRadius: '50%', background: themeColor,
|
|
195
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
196
|
+
marginBottom: 16, position: 'relative',
|
|
197
|
+
}}>
|
|
198
|
+
<CameraOutlined style={{ fontSize: 32, color: '#fff' }} />
|
|
199
|
+
</div>
|
|
200
|
+
<span style={{ fontSize: 17, fontWeight: 700, color: '#1a1a1a', marginBottom: 4 }}>
|
|
201
|
+
Upload Your Photo
|
|
202
|
+
</span>
|
|
203
|
+
<span style={{ fontSize: 13, color: '#999' }}>See yourself in this outfit</span>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const renderReadyState = () => (
|
|
208
|
+
<>
|
|
209
|
+
<div style={{ position: 'absolute', inset: 0, display: 'flex', overflow: 'hidden' }}>
|
|
210
|
+
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
|
211
|
+
<img src={userImageBlob!} alt="Your photo" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
212
|
+
<div style={{ position: 'absolute', top: 0, right: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,0.6)' }} />
|
|
213
|
+
<div style={{
|
|
214
|
+
position: 'absolute', bottom: 10, left: 10,
|
|
215
|
+
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(8px)',
|
|
216
|
+
color: '#fff', padding: '4px 10px', borderRadius: 14, fontSize: 11, fontWeight: 600,
|
|
217
|
+
}}>You</div>
|
|
218
|
+
<button
|
|
219
|
+
onClick={(e) => { e.stopPropagation(); triggerFileUpload(); }}
|
|
220
|
+
style={{
|
|
221
|
+
position: 'absolute', top: 10, left: 10,
|
|
222
|
+
background: 'rgba(255,255,255,0.85)', backdropFilter: 'blur(8px)',
|
|
223
|
+
border: 'none', borderRadius: 14, padding: '4px 10px',
|
|
224
|
+
fontSize: 11, fontWeight: 600, cursor: 'pointer',
|
|
225
|
+
display: 'flex', alignItems: 'center', gap: 4, color: '#333',
|
|
226
|
+
}}
|
|
227
|
+
>
|
|
228
|
+
<CameraOutlined style={{ fontSize: 11 }} /> Change
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
|
232
|
+
<img src={activeProduct.image} alt="Product" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
233
|
+
<div style={{
|
|
234
|
+
position: 'absolute', bottom: 10, right: 10,
|
|
235
|
+
background: themeColor, color: '#fff', padding: '4px 10px',
|
|
236
|
+
borderRadius: 14, fontSize: 11, fontWeight: 600,
|
|
237
|
+
}}>Outfit</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
<button
|
|
241
|
+
onClick={handleGenerateTryOn}
|
|
242
|
+
style={{
|
|
243
|
+
position: 'absolute', left: '50%', bottom: 16, transform: 'translateX(-50%)',
|
|
244
|
+
background: themeColor, color: '#fff', border: 'none', borderRadius: 28,
|
|
245
|
+
padding: '14px 28px', fontSize: 15, fontWeight: 700, cursor: 'pointer',
|
|
246
|
+
display: 'flex', alignItems: 'center', gap: 8,
|
|
247
|
+
boxShadow: `0 8px 24px -4px ${themeColor}99`, zIndex: 10,
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
<ThunderboltFilled style={{ fontSize: 18 }} /> Try It On
|
|
251
|
+
</button>
|
|
252
|
+
</>
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const renderGeneratingState = () => (
|
|
256
|
+
<div style={{
|
|
257
|
+
position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
|
|
258
|
+
alignItems: 'center', justifyContent: 'center', background: '#f5f5f7',
|
|
259
|
+
}}>
|
|
260
|
+
<Spin indicator={<LoadingOutlined style={{ fontSize: 48, color: themeColor }} spin />} />
|
|
261
|
+
<span style={{ marginTop: 20, fontSize: 16, fontWeight: 700, color: '#1a1a1a' }}>Creating your look...</span>
|
|
262
|
+
<span style={{ marginTop: 4, fontSize: 12, color: '#999' }}>About 10 seconds</span>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const renderGeneratedState = () => (
|
|
267
|
+
<>
|
|
268
|
+
<img
|
|
269
|
+
src={generatedImage!} alt="Try-on result"
|
|
270
|
+
style={{ width: '100%', height: '100%', objectFit: 'cover', opacity: imageLoaded ? 1 : 0, transition: 'opacity 0.4s ease' }}
|
|
271
|
+
onLoad={() => setImageLoaded(true)}
|
|
272
|
+
/>
|
|
273
|
+
{!imageLoaded && (
|
|
274
|
+
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f5f5f7' }}>
|
|
275
|
+
<Spin indicator={<LoadingOutlined style={{ fontSize: 32, color: themeColor }} spin />} />
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
<div style={{
|
|
279
|
+
position: 'absolute', bottom: 14, left: '50%', transform: 'translateX(-50%)',
|
|
280
|
+
display: 'flex', gap: 8, zIndex: 10,
|
|
281
|
+
}}>
|
|
282
|
+
<button onClick={handleReset} style={{
|
|
283
|
+
background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(12px)',
|
|
284
|
+
border: 'none', borderRadius: 22, padding: '10px 16px', fontSize: 13,
|
|
285
|
+
fontWeight: 600, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 5, color: '#333',
|
|
286
|
+
}}>
|
|
287
|
+
<ReloadOutlined style={{ fontSize: 13 }} /> New Photo
|
|
288
|
+
</button>
|
|
289
|
+
<button onClick={handleGenerateTryOn} style={{
|
|
290
|
+
background: themeColor, color: '#fff', border: 'none', borderRadius: 22,
|
|
291
|
+
padding: '10px 16px', fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
292
|
+
display: 'flex', alignItems: 'center', gap: 5,
|
|
293
|
+
}}>
|
|
294
|
+
<ThunderboltFilled style={{ fontSize: 13 }} /> Redo
|
|
295
|
+
</button>
|
|
296
|
+
</div>
|
|
297
|
+
</>
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const showCartControls = currentState === 'generated' && !isGenerating;
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div style={{
|
|
304
|
+
display: 'flex', flexDirection: 'column', height: '100%', background: '#fff',
|
|
305
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
306
|
+
position: 'relative', overflow: 'hidden',
|
|
307
|
+
}}>
|
|
308
|
+
|
|
309
|
+
{/* Header */}
|
|
310
|
+
<div style={{
|
|
311
|
+
padding: '14px 16px', display: 'flex', alignItems: 'center',
|
|
312
|
+
justifyContent: 'space-between', background: '#fff', position: 'relative',
|
|
313
|
+
zIndex: 20, flexShrink: 0, borderBottom: '1px solid #f0f0f0',
|
|
314
|
+
}}>
|
|
315
|
+
<Button type="text" icon={<ArrowLeftOutlined style={{ fontSize: 16 }} />} onClick={onBack} style={{ width: 36, height: 36 }} />
|
|
316
|
+
<span style={{ fontWeight: 700, fontSize: 16, color: '#1a1a1a' }}>Virtual Try-On</span>
|
|
317
|
+
<Button type="text" icon={<CloseOutlined style={{ fontSize: 14 }} />} onClick={() => setOpen(false)} style={{ color: '#bbb', width: 36, height: 36 }} />
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Image Area */}
|
|
321
|
+
<div style={{ flex: 1, position: 'relative', background: '#e8e8e8', overflow: 'hidden', minHeight: 0 }}>
|
|
322
|
+
{isGenerating
|
|
323
|
+
? renderGeneratingState()
|
|
324
|
+
: currentState === 'empty'
|
|
325
|
+
? renderEmptyState()
|
|
326
|
+
: currentState === 'ready'
|
|
327
|
+
? renderReadyState()
|
|
328
|
+
: renderGeneratedState()
|
|
329
|
+
}
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<input type="file" ref={fileInputRef} style={{ display: 'none' }} accept="image/*" onChange={handleFileUpload} />
|
|
333
|
+
|
|
334
|
+
{/* Bottom Bar */}
|
|
335
|
+
<div style={{
|
|
336
|
+
flexShrink: 0, background: '#fff', borderTop: '1px solid #f0f0f0',
|
|
337
|
+
padding: '12px 16px', paddingBottom: 'max(12px, env(safe-area-inset-bottom))', zIndex: 30,
|
|
338
|
+
}}>
|
|
339
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
340
|
+
{/* Thumbnail */}
|
|
341
|
+
<div style={{ width: 44, height: 44, borderRadius: 10, overflow: 'hidden', flexShrink: 0, background: '#f0f0f0' }}>
|
|
342
|
+
{activeProduct.image && <img src={activeProduct.image} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{/* Name + Price */}
|
|
346
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
347
|
+
<div style={{ fontSize: 14, fontWeight: 700, color: '#1a1a1a', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
348
|
+
{activeProduct.name}
|
|
349
|
+
</div>
|
|
350
|
+
<div style={{ fontSize: 15, fontWeight: 700, color: themeColor, marginTop: 1 }}>
|
|
351
|
+
{activeProduct.price}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
{/* +/- stepper (only after try-on generated) */}
|
|
356
|
+
{showCartControls ? (
|
|
357
|
+
<div style={{
|
|
358
|
+
display: 'flex', alignItems: 'center', background: '#f5f5f7',
|
|
359
|
+
borderRadius: 12, padding: 4, flexShrink: 0, animation: 'slideUp 0.3s ease',
|
|
360
|
+
}}>
|
|
361
|
+
<button
|
|
362
|
+
onClick={decrement} disabled={count === 0}
|
|
363
|
+
style={{
|
|
364
|
+
width: 36, height: 36, borderRadius: 10, border: 'none',
|
|
365
|
+
background: count > 0 ? '#fff' : 'transparent',
|
|
366
|
+
cursor: count > 0 ? 'pointer' : 'default',
|
|
367
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
368
|
+
color: count > 0 ? '#333' : '#ccc', fontSize: 16,
|
|
369
|
+
boxShadow: count > 0 ? '0 1px 3px rgba(0,0,0,0.08)' : 'none',
|
|
370
|
+
transition: 'all 0.15s ease',
|
|
371
|
+
}}
|
|
372
|
+
aria-label="Decrease quantity"
|
|
373
|
+
>–</button>
|
|
374
|
+
<span style={{ width: 34, textAlign: 'center', fontWeight: 700, fontSize: 15, color: '#1a1a1a' }}>
|
|
375
|
+
{count}
|
|
376
|
+
</span>
|
|
377
|
+
<button
|
|
378
|
+
onClick={increment}
|
|
379
|
+
style={{
|
|
380
|
+
width: 36, height: 36, borderRadius: 10, border: 'none',
|
|
381
|
+
background: '#fff', cursor: 'pointer',
|
|
382
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
383
|
+
color: themeColor, fontSize: 16,
|
|
384
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.08)',
|
|
385
|
+
transition: 'all 0.15s ease',
|
|
386
|
+
}}
|
|
387
|
+
aria-label="Increase quantity"
|
|
388
|
+
>+</button>
|
|
389
|
+
</div>
|
|
390
|
+
) : (
|
|
391
|
+
<div style={{ fontSize: 11, color: '#bbb', textAlign: 'right', lineHeight: 1.3, flexShrink: 0 }}>
|
|
392
|
+
Try it on first,<br/>then add to cart
|
|
393
|
+
</div>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
<style>{`
|
|
399
|
+
@keyframes slideUp {
|
|
400
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
401
|
+
to { opacity: 1; transform: translateY(0); }
|
|
402
|
+
}
|
|
403
|
+
`}</style>
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
export default TryOnLayout;
|
package/src/hooks/config.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
const config = {
|
|
4
|
-
apiUrl:'https://jawebcrm.onrender.com/api/',
|
|
5
|
-
websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
|
|
4
|
+
// apiUrl:'https://jawebcrm.onrender.com/api/',
|
|
5
|
+
// websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
apiUrl:'http://localhost:8000/api/',
|
|
8
|
+
websocketUrl:'ws://localhost:8000/ws',
|
|
9
9
|
|
|
10
10
|
};
|
|
11
11
|
export default config;
|
|
@@ -25,6 +25,7 @@ export const useChatbotData = () => {
|
|
|
25
25
|
const [restSuggestions, setRestSuggestions] = useState<string[]>([]);
|
|
26
26
|
const [userType,setUserType]=useState('')
|
|
27
27
|
const [disbleCheckout, setDisbleCheckout] = useState(false);
|
|
28
|
+
const [enableTryOn, setEnableTryon] = useState(true);
|
|
28
29
|
const [chatbotName,setchatbotName]=useState('')
|
|
29
30
|
|
|
30
31
|
|
|
@@ -70,6 +71,7 @@ export const useChatbotData = () => {
|
|
|
70
71
|
setCalendly(data.calendly_link ?? null);
|
|
71
72
|
setDisbleCheckout(data.disable_checkout)
|
|
72
73
|
setchatbotName(data.chatbot_name)
|
|
74
|
+
setEnableTryon(data.enable_try_on)
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
|
|
@@ -132,6 +134,7 @@ export const useChatbotData = () => {
|
|
|
132
134
|
restSuggestions,
|
|
133
135
|
userType,
|
|
134
136
|
disbleCheckout,
|
|
135
|
-
chatbotName
|
|
137
|
+
chatbotName,
|
|
138
|
+
enableTryOn
|
|
136
139
|
};
|
|
137
140
|
};
|