shopify-chatbot-widget 0.7.9 → 0.8.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.
Files changed (58) hide show
  1. package/dist/jaweb-chatbot.css +1 -1
  2. package/dist/jaweb-chatbot.iife.js +3 -3
  3. package/package.json +1 -1
  4. package/src/App.css +42 -0
  5. package/src/App.tsx +13 -0
  6. package/src/Chatbot/Chatbot.css +270 -0
  7. package/src/Chatbot/Chatbot.tsx +214 -0
  8. package/src/Chatbot/ChatbotTransition.css +19 -0
  9. package/src/Chatbot/OLDCHAT.tsx +259 -0
  10. package/src/Chatbot/components/ChatInput.css +127 -0
  11. package/src/Chatbot/components/ChatInput.tsx +354 -0
  12. package/src/Chatbot/components/Messages/Addcart.tsx +70 -0
  13. package/src/Chatbot/components/Messages/AgentStatus.tsx +37 -0
  14. package/src/Chatbot/components/Messages/AssistantMesage.tsx +206 -0
  15. package/src/Chatbot/components/Messages/AudioMessage.tsx +123 -0
  16. package/src/Chatbot/components/Messages/CalendlyEmbeds.tsx +66 -0
  17. package/src/Chatbot/components/Messages/CardSwiper.tsx +194 -0
  18. package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +264 -0
  19. package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +208 -0
  20. package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +208 -0
  21. package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +245 -0
  22. package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +108 -0
  23. package/src/Chatbot/components/Messages/CartWidget.tsx +151 -0
  24. package/src/Chatbot/components/Messages/ChatImage.tsx +46 -0
  25. package/src/Chatbot/components/Messages/ChatImageText.tsx +74 -0
  26. package/src/Chatbot/components/Messages/ChatImageTextAssis.tsx +72 -0
  27. package/src/Chatbot/components/Messages/ChatMessages.css +31 -0
  28. package/src/Chatbot/components/Messages/ChatMessages.tsx +334 -0
  29. package/src/Chatbot/components/Messages/ProductCard.css +149 -0
  30. package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +196 -0
  31. package/src/Chatbot/components/Messages/Support.tsx +177 -0
  32. package/src/Chatbot/components/Messages/Typing.css +31 -0
  33. package/src/Chatbot/components/Messages/Typing.tsx +19 -0
  34. package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +196 -0
  35. package/src/Chatbot/components/Messages/cartwidget.css +123 -0
  36. package/src/Chatbot/components/Messenger/Messenger.tsx +531 -0
  37. package/src/Chatbot/components/Notification.tsx +28 -0
  38. package/src/Chatbot/components/Powered.css +29 -0
  39. package/src/Chatbot/components/Powered.tsx +12 -0
  40. package/src/Chatbot/components/Sessions/CreateSession.tsx +201 -0
  41. package/src/Chatbot/components/Sessions/RenderList.tsx +212 -0
  42. package/src/Chatbot/components/Suggestions.css +35 -0
  43. package/src/Chatbot/components/Suggestions.tsx +57 -0
  44. package/src/Chatbot/notification.css +17 -0
  45. package/src/assets/react.svg +1 -0
  46. package/src/chatbot-widget/index.tsx +14 -0
  47. package/src/hooks/config.tsx +13 -0
  48. package/src/hooks/useCart.tsx +107 -0
  49. package/src/hooks/useChatbotAPI.tsx +167 -0
  50. package/src/hooks/useChatbotData.tsx +132 -0
  51. package/src/hooks/useSessionMessages.tsx +50 -0
  52. package/src/hooks/useWebsocke.tsx +45 -0
  53. package/src/i18n.tsx +30 -0
  54. package/src/index.css +2 -0
  55. package/src/main.tsx +10 -0
  56. package/src/types/chatbot.ts +40 -0
  57. package/src/utils/cookies.tsx +14 -0
  58. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,334 @@
1
+ import React,{useEffect, useRef} from 'react';
2
+ import { Spin } from 'antd';
3
+ import { LoadingOutlined ,CheckCircleOutlined,CloseCircleOutlined} from '@ant-design/icons';
4
+ import Typing from './Typing';
5
+ import AssistantMessage from './AssistantMesage';
6
+ import ChatImage from './ChatImage';
7
+ import './ChatMessages.css'
8
+ import ChatImageText from './ChatImageText';
9
+ import AudioVisualizer from './AudioMessage';
10
+ import AgentStatusBanner from './AgentStatus';
11
+ import AddToCartConfirmation from './Addcart';
12
+ import SupportConnect from './Support';
13
+ import ChatImageTextAssist from './ChatImageTextAssis';
14
+ import ZidAssistantChatMessage from './ZidAssistantMesage';
15
+ import SallaAsssiatnt from './SallaAssistantMessage';
16
+ import CalendlyEmbeds from './CalendlyEmbeds';
17
+
18
+
19
+ interface Message {
20
+ role?: string;
21
+ content?: any;
22
+ text?: string;
23
+ typing?: boolean;
24
+ image_url?:string;
25
+ status?:string;
26
+ audio_url?:string;
27
+ isConnect?:boolean;
28
+ calendly?:boolean;
29
+ }
30
+
31
+ interface ChatMessagesProps {
32
+ messages: Message[];
33
+ colorCode: string;
34
+ isLoading: boolean;
35
+ handleAddToCart: (
36
+ variantId: string,
37
+ name: string,
38
+ qty?: number
39
+ ) => Promise<void>;
40
+ handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
41
+ sessionId:string;
42
+ isBusiness?: boolean;
43
+ agentStatus?: {
44
+ status?: string;
45
+ user?: string;
46
+ };
47
+ userType:string;
48
+ calendly:string;
49
+ disableCheckout: boolean;
50
+ isfetchingMessage:boolean;
51
+ }
52
+
53
+
54
+
55
+
56
+
57
+ const ChatMessages: React.FC<ChatMessagesProps> = ({
58
+ messages,
59
+ colorCode,
60
+ handleAddToCart,
61
+ handleRemoveFromCart,
62
+ isLoading,
63
+ agentStatus,
64
+ sessionId,
65
+ userType,
66
+ calendly,
67
+ disableCheckout,
68
+ isfetchingMessage,
69
+
70
+ }) => {
71
+
72
+
73
+
74
+
75
+ const bottomRef = useRef<HTMLDivElement>(null);
76
+
77
+ useEffect(() => {
78
+ if (!bottomRef.current) return;
79
+
80
+ const timeout = setTimeout(() => {
81
+ bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
82
+ }, 150); // Slight delay ensures DOM settles in Shopify
83
+
84
+ return () => clearTimeout(timeout);
85
+ }, [messages]);
86
+
87
+
88
+
89
+ if (isfetchingMessage || isLoading || !messages) {
90
+ return (
91
+ <div
92
+ className="loader-wrapper"
93
+ style={{
94
+ display: "flex",
95
+ alignItems: "center",
96
+ justifyContent: "center",
97
+ height: "100%",
98
+ gap: "8px",
99
+ }}
100
+ >
101
+ <div style={{backgroundColor:colorCode}} className="box-loader"></div>
102
+ <div style={{backgroundColor:colorCode}} className="box-loader"></div>
103
+ <div style={{backgroundColor:colorCode}} className="box-loader"></div>
104
+ </div>
105
+ );
106
+ }
107
+
108
+
109
+ return (
110
+ <div className="chatbot-messages space-y-4">
111
+ {messages.map((msg, i) => {
112
+ if (msg.typing) {
113
+ return (
114
+ <div key={i} className="chat-msg typing">
115
+ <Typing color={colorCode} />
116
+ </div>
117
+ );
118
+ }
119
+
120
+ if (msg.isConnect){
121
+ return(
122
+ <SupportConnect sessionId={sessionId}/>
123
+ )
124
+ }
125
+
126
+
127
+
128
+
129
+ if (msg.role === 'assistant') {
130
+ // Handle array content separately
131
+ if (Array.isArray(msg.content)) {
132
+ return (
133
+ <div key={i} className="chat-msg assistant" style={{ backgroundColor: 'transparent', padding: 0 }}>
134
+ {msg.image_url && (
135
+ <img
136
+ src={msg.image_url}
137
+ alt="Chat image"
138
+ style={{ width: 200 }}
139
+ className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
140
+ />
141
+ )}
142
+ <ChatImage message={msg.content} />
143
+ <div className="flex justify-start">
144
+ <ChatImageTextAssist message={msg.content} />
145
+ </div>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ if (msg.calendly) {
151
+ return (
152
+ <div
153
+ key={i}
154
+ style={{
155
+ borderRadius: 5,
156
+ color: "white",
157
+ width: "100%",
158
+ }}
159
+ >
160
+ <CalendlyEmbeds calendlyLink={calendly} />
161
+ </div>
162
+ );
163
+ }
164
+
165
+
166
+
167
+ return (
168
+ <div key={i} className="chat-msg assistant">
169
+ {msg.image_url && (
170
+ <img
171
+ src={msg.image_url}
172
+ alt="Chat image"
173
+ style={{ width: 200 }}
174
+ className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
175
+ />
176
+ )}
177
+
178
+ {userType === "shopify" ? (
179
+ <AssistantMessage
180
+ message={msg.content || msg.text || ""}
181
+ colorCode={colorCode}
182
+ disableCheckout={disableCheckout}
183
+ handleAddToCart={handleAddToCart}
184
+ handleRemoveFromCart={handleRemoveFromCart}
185
+ />
186
+ ) : userType === "salla" ? (
187
+ <SallaAsssiatnt
188
+ message={msg.content || msg.text || ""}
189
+ colorCode={colorCode}
190
+ handleAddToCart={handleAddToCart}
191
+ handleRemoveFromCart={handleRemoveFromCart}
192
+ />
193
+ ) : (
194
+ <ZidAssistantChatMessage
195
+ message={msg.content || msg.text || ""}
196
+ colorCode={colorCode}
197
+ handleAddToCart={handleAddToCart}
198
+ handleRemoveFromCart={handleRemoveFromCart}
199
+ />
200
+ )}
201
+
202
+
203
+
204
+ </div>
205
+ );
206
+ }
207
+
208
+
209
+
210
+ if(msg?.audio_url){
211
+
212
+ return(
213
+ <div
214
+ className={`chat-msg user`}
215
+ style={{
216
+ backgroundColor:'transparent',
217
+ padding:0
218
+ }}
219
+ >
220
+
221
+ <div style={{
222
+ borderRadius:12
223
+ }}>
224
+
225
+ <AudioVisualizer link={msg.audio_url} transcription={msg.content} colorCode={colorCode} />
226
+
227
+ {/* Upload/Delivery Status */}
228
+
229
+ </div>
230
+
231
+ <div className="mt-1 mr-2 flex items-end justify-end">
232
+ {msg.status === 'uploading' ? (
233
+ <Spin indicator={<LoadingOutlined spin />} size="small" />
234
+ ) : msg.status === 'delivered' ? (
235
+ <CheckCircleOutlined style={{ color: 'green' }} />
236
+ ) : msg.status === 'failed' ? (
237
+ <CloseCircleOutlined style={{ color: 'red' }} />
238
+ ) : null}
239
+ </div>
240
+ </div>
241
+ )
242
+ }
243
+
244
+
245
+ // ✅ IMAGE MESSAGE (non-assistant)
246
+ if (msg.image_url) {
247
+ return (
248
+ <div key={i} className="chat-msg user" style={{ backgroundColor: 'transparent' }}>
249
+ <img
250
+ src={msg.image_url}
251
+ alt="Chat image"
252
+ style={{ width: 200 }}
253
+ className={`rounded-md mt-2 max-w-xs ${
254
+ msg.content === 'user'
255
+ ? 'border border-gray-300 shadow-sm'
256
+ : 'border border-green-400 shadow-sm'
257
+ }`}
258
+ />
259
+
260
+ <div className="mt-1 mr-2 flex items-end justify-end">
261
+ {msg.status === 'uploading' ? (
262
+ <Spin indicator={<LoadingOutlined spin />} size="small" />
263
+ ) : msg.status === 'delivered' ? (
264
+ <CheckCircleOutlined style={{ color: 'green' }} />
265
+ ) : msg.status === 'failed' ? (
266
+ <CloseCircleOutlined style={{ color: 'red' }} />
267
+ ) : null}
268
+ </div>
269
+
270
+ {msg.content && (
271
+ <div className="flex justify-end mt-2">
272
+ <div
273
+ style={{
274
+ color: 'white',
275
+ border: 1,
276
+ backgroundColor: colorCode,
277
+ paddingTop: 10,
278
+ paddingBottom: 10,
279
+ paddingInline: 14,
280
+ maxWidth: '85%',
281
+ lineHeight: 1.4,
282
+ borderRadius: '16px',
283
+ wordBreak: 'break-word',
284
+ }}
285
+ >
286
+ <span>{msg.content}</span>
287
+ </div>
288
+ </div>
289
+ )}
290
+ </div>
291
+ );
292
+ }
293
+
294
+
295
+ // ✅ MULTIPLE IMAGES (array)
296
+ if (Array.isArray(msg.content)) {
297
+ return (
298
+ <div key={i} className='chat-msg user' style={{backgroundColor:'transparent',padding:0}}>
299
+ <div style={{backgroundColor:'transparent',padding:0}}>
300
+ <ChatImage message={msg.content} />
301
+ </div>
302
+ <div key={i} className='flex justify-end'>
303
+ <ChatImageText message={msg.content} colorCode={colorCode} />
304
+ </div>
305
+
306
+ </div>
307
+ );
308
+ }
309
+
310
+
311
+ return (
312
+ <div
313
+ key={i}
314
+ className={`chat-msg ${msg.role}`}
315
+ style={
316
+ msg.role === 'user'
317
+ ? { backgroundColor: colorCode, color: '#fff' }
318
+ : {}
319
+ }
320
+ >
321
+
322
+ <AddToCartConfirmation message={msg.content} colorCode={colorCode}/>
323
+
324
+ </div>
325
+ );
326
+ })}
327
+ <AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
328
+ <div ref={bottomRef} />
329
+
330
+ </div>
331
+ );
332
+ };
333
+
334
+ export default ChatMessages;
@@ -0,0 +1,149 @@
1
+ .product-card {
2
+ background-color: white;
3
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
4
+ border-radius: 16px;
5
+ padding: 16px;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 16px;
9
+ }
10
+
11
+ @media (min-width: 640px) {
12
+ .product-card {
13
+ flex-direction: row;
14
+ align-items: flex-start;
15
+ }
16
+ }
17
+
18
+ .product-image-container {
19
+ width: 128px;
20
+ height: 128px;
21
+ }
22
+
23
+ @media (min-width: 640px) {
24
+ .product-image-container {
25
+ width: 160px;
26
+ height: 160px;
27
+ }
28
+ }
29
+
30
+ .product-image {
31
+ width: 100%;
32
+ height: 100%;
33
+ object-fit: cover;
34
+ border-radius: 12px;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .product-details {
39
+ flex: 1;
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: 12px;
43
+ }
44
+
45
+ .product-name {
46
+ font-size: 1.125rem;
47
+ font-weight: 600;
48
+ color: #111827;
49
+ text-decoration: none;
50
+ }
51
+
52
+ .product-price {
53
+ font-size: 1rem;
54
+ font-weight: 500;
55
+ color: #374151;
56
+ }
57
+
58
+ .label {
59
+ font-size: 0.875rem;
60
+ font-weight: 500;
61
+ color: #6b7280;
62
+ }
63
+
64
+ .color-options,
65
+ .size-options {
66
+ display: flex;
67
+ flex-wrap: wrap;
68
+ gap: 8px;
69
+ margin-top: 4px;
70
+ }
71
+
72
+ .color-button,
73
+ .size-button {
74
+ padding: 4px 12px;
75
+ font-size: 0.875rem;
76
+ border-radius: 9999px;
77
+ border: 1px solid #d1d5db;
78
+ background-color: #f3f4f6;
79
+ color: #1f2937;
80
+ cursor: pointer;
81
+ }
82
+
83
+ .color-button.disabled,
84
+ .size-button.disabled {
85
+ opacity: 0.4;
86
+ cursor: not-allowed;
87
+ }
88
+
89
+ .color-button.active,
90
+ .size-button.active {
91
+ color: white;
92
+ }
93
+
94
+ .cart-controls {
95
+ display: inline-flex;
96
+ align-items: center;
97
+ gap: 12px;
98
+ margin-top: 12px;
99
+ }
100
+
101
+ .cart-button {
102
+ padding: 4px 12px;
103
+ border: 1px solid #d1d5db;
104
+ border-radius: 8px;
105
+ background: none;
106
+ cursor: pointer;
107
+ }
108
+
109
+ .cart-count {
110
+ min-width: 20px;
111
+ text-align: center;
112
+ font-weight: 500;
113
+ }
114
+
115
+ .increment {
116
+ background-color: #7f28f8;
117
+ color: white;
118
+ }
119
+
120
+
121
+ /* ProductCard.css */
122
+ .carousel-container {
123
+ position: relative;
124
+ overflow: hidden;
125
+ }
126
+
127
+ .carousel-track {
128
+ display: flex;
129
+ transition: transform 0.3s ease-in-out;
130
+ }
131
+
132
+ .carousel-slide {
133
+ flex: 0 0 100%;
134
+ padding: 0 1rem;
135
+ }
136
+
137
+ .carousel-nav {
138
+ position: absolute;
139
+ top: 50%;
140
+ transform: translateY(-50%);
141
+ background: rgba(255,255,255,0.8);
142
+ border: none;
143
+ font-size: 2rem;
144
+ cursor: pointer;
145
+ z-index: 10;
146
+ }
147
+
148
+ .carousel-nav.prev { left: 0.5rem; }
149
+ .carousel-nav.next { right: 0.5rem; }
@@ -0,0 +1,196 @@
1
+ import React, { useState, useMemo, type FC } from 'react';
2
+ import './ProductCard.css';
3
+ import SallaProductCarousel from './Carousel/CardSwiperSalla';
4
+ import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
5
+ import { Button } from 'antd';
6
+ import { useKeenSlider } from 'keen-slider/react';
7
+ import 'keen-slider/keen-slider.min.css';
8
+
9
+ interface ProductVariant {
10
+ name: string;
11
+ price: string;
12
+ link: string;
13
+ image: string;
14
+ variant_id: string;
15
+ variant_tag_name: string;
16
+ option?: string;
17
+ }
18
+
19
+ interface AssistantMessageProps {
20
+ message: string;
21
+ colorCode: string;
22
+ isBusiness?: boolean;
23
+ handleAddToCart: (variantId: string, name: string, qty?: number) => Promise<void>;
24
+ handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
25
+ }
26
+
27
+ const SallaAsssiatnt: FC<AssistantMessageProps> = ({
28
+ message,
29
+ colorCode,
30
+ handleAddToCart,
31
+ handleRemoveFromCart,
32
+ }) => {
33
+ const [counts, setCounts] = useState<Record<string, number>>({});
34
+ const [selectedVariants, setSelectedVariants] = useState<Record<string, string>>({});
35
+
36
+ const formatTextWithLinks = (text: string): React.ReactNode[] => {
37
+ const urlRegex = /(https?:\/\/[^\s]+)/g;
38
+ return text.split(urlRegex).map((part, i) =>
39
+ urlRegex.test(part) ? (
40
+ <a
41
+ key={i}
42
+ href={part.trim()}
43
+ style={{ color: '#00b3bc' }}
44
+ target="_blank"
45
+ rel="noopener noreferrer"
46
+ className="underline break-words"
47
+ >
48
+ {part}
49
+ </a>
50
+ ) : (
51
+ <span key={i}>{part}</span>
52
+ )
53
+ );
54
+ };
55
+
56
+ const extractProductDetails = (text: string): ProductVariant | null => {
57
+ const normalized = text.trim();
58
+ if (!normalized.includes('Name:') || !normalized.includes('VariantId')) return null;
59
+ const nameMatch = normalized.match(/Name\s*[:\-]\s*(.+)/i);
60
+ const priceMatch = normalized.match(/Price\s*[:\-]\s*(.+)/i);
61
+ const linkMatch = normalized.match(/Link\s*[:\-]\s*(https?:\/\/[^\s]+)/i);
62
+ const variantMatch = normalized.match(/VariantId\s*[:\-]\s*(.+)/i);
63
+ const tagNameMatch = normalized.match(/variant_tag_name\s*[:\-]\s*(.+)/i);
64
+ const OptionMatch = normalized.match(/options\s*[:\-]\s*([^|\r\n]+)/i);
65
+ if (!nameMatch || !priceMatch || !linkMatch || !variantMatch || !tagNameMatch) return null;
66
+ return {
67
+ name: nameMatch[1].trim(),
68
+ price: priceMatch[1].trim(),
69
+ link: linkMatch[1].trim(),
70
+ image: '',
71
+ variant_id: variantMatch[1].trim(),
72
+ variant_tag_name: tagNameMatch[1].trim(),
73
+ option: OptionMatch?.[1]?.trim(),
74
+ };
75
+ };
76
+
77
+ const parsedSegments = useMemo(() => {
78
+ const parts = message.split('|||').map(p => p.trim()).filter(Boolean);
79
+ const segments: { type: 'text' | 'product'; data: any }[] = [];
80
+ let lastProduct: ProductVariant | null = null;
81
+
82
+ parts.forEach(part => {
83
+ if (part.startsWith('img - ')) {
84
+ const url = part.replace(/^img\s*[-:]\s*/, '').trim();
85
+ if (lastProduct) lastProduct.image = url;
86
+ else {
87
+ const lastSeg = [...segments].reverse().find(s => s.type === 'product');
88
+ if (lastSeg) (lastSeg.data as ProductVariant).image = url;
89
+ }
90
+ } else if (part.includes('Name:') && part.includes('VariantId')) {
91
+ const product = extractProductDetails(part);
92
+ if (product) {
93
+ segments.push({ type: 'product', data: product });
94
+ lastProduct = product;
95
+ }
96
+ } else {
97
+ segments.push({ type: 'text', data: part });
98
+ lastProduct = null;
99
+ }
100
+ });
101
+
102
+ return segments;
103
+ }, [message]);
104
+
105
+ const groupedProducts = useMemo(() => {
106
+ const groups: Record<string, ProductVariant[]> = {};
107
+ parsedSegments.forEach(seg => {
108
+ if (seg.type === 'product') {
109
+ const p = seg.data as ProductVariant;
110
+ groups[p.variant_tag_name] = groups[p.variant_tag_name] || [];
111
+ groups[p.variant_tag_name].push(p);
112
+ }
113
+ });
114
+ return groups;
115
+ }, [parsedSegments]);
116
+
117
+ const shortenName = (name: string, maxLength = 22) =>
118
+ name.length > maxLength ? name.slice(0, maxLength) + '...' : name;
119
+
120
+
121
+
122
+ const [currentSlide, setCurrentSlide] = useState(0);
123
+ const totalSlides =Object.keys(groupedProducts).length ;
124
+ const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
125
+ loop: false,
126
+ mode: 'free',
127
+ slides: { perView: 1, spacing: 16 },
128
+ slideChanged(s) {
129
+ setCurrentSlide(s.track.details.rel);
130
+ },
131
+ });
132
+
133
+
134
+ return (
135
+ <div className="space-y-6 mt-4 h-fit">
136
+ {parsedSegments
137
+ .filter(seg => seg.type === 'text')
138
+ .map((seg, i) => (
139
+ <p key={i} className="whitespace-pre-wrap break-words">
140
+ {formatTextWithLinks(seg.data)}
141
+ </p>
142
+ ))}
143
+
144
+
145
+
146
+ {Object.keys(groupedProducts).length > 0 && (
147
+ <>
148
+ {Object.keys(groupedProducts).length > 1 && (
149
+ <div
150
+ style={{
151
+ display: 'flex',
152
+ margin: 0,
153
+ marginBottom: '6px',
154
+ justifyContent: 'space-between',
155
+ paddingInline: '4px',
156
+ }}
157
+ >
158
+ <Button
159
+ shape="circle"
160
+ icon={<ArrowLeftOutlined />}
161
+ onClick={() => slider.current?.prev()}
162
+ disabled={currentSlide === 0}
163
+ />
164
+ <Button
165
+ shape="circle"
166
+ icon={<ArrowRightOutlined />}
167
+ onClick={() => slider.current?.next()}
168
+ disabled={currentSlide === totalSlides - 1}
169
+ />
170
+ </div>
171
+ )}
172
+ <div ref={sliderRef} className="keen-slider">
173
+ <SallaProductCarousel
174
+ groupedProducts={groupedProducts}
175
+ selectedVariants={selectedVariants}
176
+ setSelectedVariants={setSelectedVariants}
177
+ counts={counts}
178
+ setCounts={setCounts}
179
+ handleAddToCart={handleAddToCart}
180
+ handleRemoveFromCart={handleRemoveFromCart}
181
+ colorCode={colorCode}
182
+ shortenName={shortenName}
183
+ sliderInstance={slider} // pass it down if needed
184
+ currentSlide={currentSlide}
185
+ totalSlides={totalSlides}
186
+ sliderRef={sliderRef}
187
+
188
+ />
189
+ </div>
190
+ </>
191
+ )}
192
+ </div>
193
+ );
194
+ };
195
+
196
+ export default SallaAsssiatnt;