shopify-chatbot-widget 1.0.9 → 1.0.10
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/.env.development +2 -0
- package/dist/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +192 -599
- package/package.json +1 -1
- package/src/App.tsx +9 -0
- package/src/Chatbot/Chatbot.css +193 -113
- package/src/Chatbot/Chatbot.tsx +26 -19
- package/src/Chatbot/components/Cart/Index.tsx +10 -8
- package/src/Chatbot/components/ChatInput.tsx +11 -11
- package/src/Chatbot/components/Home/Home.css +456 -0
- package/src/Chatbot/components/Home/Home.tsx +293 -0
- package/src/Chatbot/components/Messages/Addcart.tsx +1 -1
- package/src/Chatbot/components/Messages/AssistantMesage.tsx +78 -37
- package/src/Chatbot/components/Messages/CardSwiper.tsx +1 -1
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +87 -92
- package/src/Chatbot/components/Messages/Carousel/ProductSwiper.css +361 -0
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +2 -2
- package/src/Chatbot/components/Messages/ChatMessages.css +32 -1
- package/src/Chatbot/components/Messages/ChatMessages.tsx +21 -38
- package/src/Chatbot/components/Messages/OrderCard.css +177 -0
- package/src/Chatbot/components/Messages/OrderCard.tsx +120 -0
- package/src/Chatbot/components/Messages/ProductCard.css +1 -1
- package/src/Chatbot/components/Messages/Support.tsx +311 -118
- package/src/Chatbot/components/Messages/Typing.css +31 -35
- package/src/Chatbot/components/Messages/Typing.tsx +19 -18
- package/src/Chatbot/components/Messenger/Messenger.tsx +67 -14
- package/src/Chatbot/components/Powered.css +26 -28
- package/src/Chatbot/components/Powered.tsx +1 -1
- package/src/Chatbot/components/Sessions/CreateSession.tsx +12 -10
- package/src/Chatbot/components/TryOn/View.tsx +2 -2
- package/src/Chatbot/components/VoiceMode.css +247 -0
- package/src/Chatbot/components/VoiceMode.tsx +613 -0
- package/src/hooks/config.tsx +9 -10
- package/src/hooks/useChatbotAPI.tsx +146 -61
- package/src/hooks/useChatbotData.tsx +8 -5
- package/src/hooks/useSessionMessages.tsx +0 -1
- package/src/hooks/useWebsocke.tsx +40 -18
- package/src/i18n.tsx +384 -10
- package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +0 -187
- package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +0 -208
- package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +0 -245
- package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +0 -202
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +0 -203
- package/src/Chatbot/components/Sessions/RenderList.tsx +0 -519
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { FC } from 'react';
|
|
3
|
-
import 'keen-slider/keen-slider.min.css';
|
|
4
|
-
import type { KeenSliderInstance } from 'keen-slider/react';
|
|
5
|
-
import { Button } from 'antd';
|
|
6
|
-
import './SallaSwiper.css';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
interface ProductVariant {
|
|
10
|
-
variant_id: string;
|
|
11
|
-
name: string;
|
|
12
|
-
image: string;
|
|
13
|
-
link: string;
|
|
14
|
-
price: string;
|
|
15
|
-
option?: string; // e.g. "Red - XL" or "500ml"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface Props {
|
|
19
|
-
groupedProducts: Record<string, ProductVariant[]>;
|
|
20
|
-
selectedVariants: Record<string, string>;
|
|
21
|
-
setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
22
|
-
counts: Record<string, number>;
|
|
23
|
-
setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
|
|
24
|
-
handleAddToCart: (variantId: string, name: string, variantImage:string,price:string,qty: number) => Promise<void>;
|
|
25
|
-
handleRemoveFromCart: (variantId: string, qty: number) => Promise<void>;
|
|
26
|
-
colorCode?: string; // accent
|
|
27
|
-
shortenName: (name: string) => string;
|
|
28
|
-
|
|
29
|
-
// slider
|
|
30
|
-
sliderRef: React.Ref<HTMLDivElement>;
|
|
31
|
-
sliderInstance: React.MutableRefObject<KeenSliderInstance | null>;
|
|
32
|
-
currentSlide: number;
|
|
33
|
-
totalSlides: number;
|
|
34
|
-
onTryOnSelect: (product: ProductVariant) => void;
|
|
35
|
-
enableTry:boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const ZidProductCarousel: FC<Props> = ({
|
|
41
|
-
groupedProducts,
|
|
42
|
-
selectedVariants,
|
|
43
|
-
setSelectedVariants,
|
|
44
|
-
colorCode = '#7F28F8',
|
|
45
|
-
shortenName,
|
|
46
|
-
sliderRef,
|
|
47
|
-
totalSlides,
|
|
48
|
-
onTryOnSelect,
|
|
49
|
-
enableTry
|
|
50
|
-
}) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!totalSlides || totalSlides === 0) return null;
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<section
|
|
57
|
-
className="sallaX"
|
|
58
|
-
style={
|
|
59
|
-
{
|
|
60
|
-
// allow live accent theming from prop
|
|
61
|
-
// @ts-ignore
|
|
62
|
-
'--sallaX-accent': colorCode,
|
|
63
|
-
} as React.CSSProperties
|
|
64
|
-
}
|
|
65
|
-
aria-label="Recommended products"
|
|
66
|
-
>
|
|
67
|
-
<div ref={sliderRef} className="keen-slider sallaX-slider">
|
|
68
|
-
{Object.entries(groupedProducts).map(([tag, group]) => {
|
|
69
|
-
const currentVariantId =
|
|
70
|
-
selectedVariants[tag] || (group.length > 0 ? group[0].variant_id : '');
|
|
71
|
-
const currentVariant =
|
|
72
|
-
group.find(v => v.variant_id === currentVariantId) || group[0];
|
|
73
|
-
|
|
74
|
-
if (!currentVariant) return null;
|
|
75
|
-
|
|
76
|
-
// const count = counts[currentVariant.variant_id] || 0;
|
|
77
|
-
|
|
78
|
-
// Build unique, cleaned list of options (exclude empty/'none')
|
|
79
|
-
const options = Array.from(
|
|
80
|
-
new Set(
|
|
81
|
-
group
|
|
82
|
-
.map(v => (v.option?.trim() ?? ''))
|
|
83
|
-
.filter(o => o && o.toLowerCase() !== 'none')
|
|
84
|
-
)
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
// Helper to update active variant by option
|
|
88
|
-
const setActiveVariant = (option?: string) => {
|
|
89
|
-
const match = group.find(v => v.option === option);
|
|
90
|
-
if (match) {
|
|
91
|
-
setSelectedVariants(prev => ({
|
|
92
|
-
...prev,
|
|
93
|
-
[tag]: match.variant_id,
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// const increment = async () => {
|
|
99
|
-
// setCounts(prev => ({
|
|
100
|
-
// ...prev,
|
|
101
|
-
// [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
|
|
102
|
-
// }));
|
|
103
|
-
// await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
|
|
104
|
-
// };
|
|
105
|
-
|
|
106
|
-
// const decrement = async () => {
|
|
107
|
-
// if (count === 0) return;
|
|
108
|
-
// setCounts(prev => ({
|
|
109
|
-
// ...prev,
|
|
110
|
-
// [currentVariant.variant_id]: Math.max(
|
|
111
|
-
// 0,
|
|
112
|
-
// (prev[currentVariant.variant_id] || 0) - 1
|
|
113
|
-
// ),
|
|
114
|
-
// }));
|
|
115
|
-
// await handleRemoveFromCart(currentVariant.variant_id, 1);
|
|
116
|
-
// };
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Ensure the select has a stable value (fallback to first option)
|
|
120
|
-
const selectValue = (currentVariant.option ?? options[0] ?? '');
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<article key={tag} className="keen-slider__slide sallaX-card" role="group" aria-roledescription="slide">
|
|
124
|
-
<div className="sallaX-media" onClick={() => window.open(currentVariant.link, '_blank')}>
|
|
125
|
-
<img
|
|
126
|
-
src={currentVariant.image}
|
|
127
|
-
alt={currentVariant.name}
|
|
128
|
-
className="sallaX-img"
|
|
129
|
-
loading="lazy"
|
|
130
|
-
/>
|
|
131
|
-
<a
|
|
132
|
-
href={currentVariant.link}
|
|
133
|
-
target="_blank"
|
|
134
|
-
rel="noopener noreferrer"
|
|
135
|
-
className="sallaX-visit"
|
|
136
|
-
aria-label="Open product page"
|
|
137
|
-
onClick={(e) => e.stopPropagation()}
|
|
138
|
-
>
|
|
139
|
-
View
|
|
140
|
-
</a>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<div className="sallaX-body">
|
|
144
|
-
<a
|
|
145
|
-
href={currentVariant.link}
|
|
146
|
-
target="_blank"
|
|
147
|
-
rel="noopener noreferrer"
|
|
148
|
-
className="sallaX-name"
|
|
149
|
-
title={currentVariant.name}
|
|
150
|
-
>
|
|
151
|
-
{shortenName(currentVariant.name)}
|
|
152
|
-
</a>
|
|
153
|
-
|
|
154
|
-
<div className="sallaX-row">
|
|
155
|
-
<span className="sallaX-price" aria-label="Price">
|
|
156
|
-
{currentVariant.price}
|
|
157
|
-
</span>
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
{/* NEW: Select-based options */}
|
|
161
|
-
{options.length > 0 && (
|
|
162
|
-
<div className="sallaX-selectWrap">
|
|
163
|
-
<label htmlFor={`sallaX-select-${tag}`} className="sallaX-selectLabel">
|
|
164
|
-
Choose an option
|
|
165
|
-
</label>
|
|
166
|
-
<select
|
|
167
|
-
id={`sallaX-select-${tag}`}
|
|
168
|
-
className="sallaX-select"
|
|
169
|
-
aria-label="Choose an option"
|
|
170
|
-
value={selectValue}
|
|
171
|
-
onChange={(e) => setActiveVariant(e.target.value)}
|
|
172
|
-
>
|
|
173
|
-
{options.map((option) => (
|
|
174
|
-
<option key={option} value={option}>
|
|
175
|
-
{option}
|
|
176
|
-
</option>
|
|
177
|
-
))}
|
|
178
|
-
</select>
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
{enableTry?
|
|
185
|
-
<Button
|
|
186
|
-
style={{ backgroundColor: colorCode, color: "white" }}
|
|
187
|
-
className="px-6 py-2 rounded-lg shadow-md transition"
|
|
188
|
-
onClick={() => {
|
|
189
|
-
onTryOnSelect(currentVariant);
|
|
190
|
-
}}
|
|
191
|
-
>
|
|
192
|
-
Try On
|
|
193
|
-
</Button>
|
|
194
|
-
:<></>
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
</div>
|
|
200
|
-
</article>
|
|
201
|
-
);
|
|
202
|
-
})}
|
|
203
|
-
</div>
|
|
204
|
-
</section>
|
|
205
|
-
);
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
export default ZidProductCarousel;
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/* Root */
|
|
2
|
-
.sallaX {
|
|
3
|
-
--sallaX-accent: #7F28F8;
|
|
4
|
-
--sallaX-bg: #ffffff;
|
|
5
|
-
--sallaX-text: #0f0f14;
|
|
6
|
-
--sallaX-muted: #6b7280;
|
|
7
|
-
--sallaX-border: rgba(15, 15, 20, 0.08);
|
|
8
|
-
--sallaX-shadow: 0 1px 2px rgba(0,0,0,.06), 0 8px 24px rgba(0,0,0,.08);
|
|
9
|
-
|
|
10
|
-
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
|
|
11
|
-
color: var(--sallaX-text);
|
|
12
|
-
background: transparent;
|
|
13
|
-
padding: 4px 0;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/* Slider spacing */
|
|
17
|
-
.sallaX-slider {
|
|
18
|
-
padding: 8px 4px 24px;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/* Card */
|
|
22
|
-
.sallaX-card {
|
|
23
|
-
background: var(--sallaX-bg);
|
|
24
|
-
border: 1px solid var(--sallaX-border);
|
|
25
|
-
border-radius: 18px;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
box-shadow: var(--sallaX-shadow);
|
|
28
|
-
transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
|
|
29
|
-
max-width: 520px;
|
|
30
|
-
margin: 0 auto;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.sallaX-card:hover {
|
|
34
|
-
transform: translateY(-2px);
|
|
35
|
-
box-shadow: 0 2px 6px rgba(0,0,0,.08), 0 12px 28px rgba(0,0,0,.12);
|
|
36
|
-
border-color: rgba(15, 15, 20, 0.12);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* Media */
|
|
40
|
-
.sallaX-media {
|
|
41
|
-
position: relative;
|
|
42
|
-
aspect-ratio: 1 / 1;
|
|
43
|
-
background: #f6f7f9;
|
|
44
|
-
cursor: pointer;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.sallaX-img {
|
|
48
|
-
width: 100%;
|
|
49
|
-
height: 100%;
|
|
50
|
-
object-fit: cover;
|
|
51
|
-
display: block;
|
|
52
|
-
transition: transform .4s ease;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.sallaX-media:hover .sallaX-img {
|
|
56
|
-
transform: scale(1.02);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/* Subtle call-to-action floating button */
|
|
60
|
-
.sallaX-visit {
|
|
61
|
-
position: absolute;
|
|
62
|
-
right: 12px;
|
|
63
|
-
bottom: 12px;
|
|
64
|
-
font-size: 13px;
|
|
65
|
-
line-height: 1;
|
|
66
|
-
padding: 10px 12px;
|
|
67
|
-
border-radius: 999px;
|
|
68
|
-
background: rgba(255,255,255,.75);
|
|
69
|
-
backdrop-filter: saturate(180%) blur(8px);
|
|
70
|
-
border: 1px solid var(--sallaX-border);
|
|
71
|
-
color: var(--sallaX-text);
|
|
72
|
-
text-decoration: none;
|
|
73
|
-
transition: background .2s ease, transform .2s ease;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.sallaX-visit:hover {
|
|
77
|
-
background: rgba(255,255,255,.95);
|
|
78
|
-
transform: translateY(-1px);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/* Body */
|
|
82
|
-
.sallaX-body {
|
|
83
|
-
padding: 14px 14px 16px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* Name */
|
|
87
|
-
.sallaX-name {
|
|
88
|
-
display: -webkit-box;
|
|
89
|
-
-webkit-line-clamp: 2;
|
|
90
|
-
-webkit-box-orient: vertical;
|
|
91
|
-
overflow: hidden;
|
|
92
|
-
text-overflow: ellipsis;
|
|
93
|
-
|
|
94
|
-
font-weight: 600;
|
|
95
|
-
font-size: 16px;
|
|
96
|
-
letter-spacing: -0.01em;
|
|
97
|
-
color: var(--sallaX-text);
|
|
98
|
-
text-decoration: none;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.sallaX-name:hover {
|
|
102
|
-
text-decoration: underline;
|
|
103
|
-
text-underline-offset: 2px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* Price row */
|
|
107
|
-
.sallaX-row {
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: baseline;
|
|
110
|
-
justify-content: space-between;
|
|
111
|
-
gap: 8px;
|
|
112
|
-
margin-top: 8px;
|
|
113
|
-
margin-bottom: 8px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.sallaX-price {
|
|
117
|
-
font-weight: 700;
|
|
118
|
-
font-size: 18px;
|
|
119
|
-
letter-spacing: -0.01em;
|
|
120
|
-
color: var(--sallaX-text);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/* --- NEW: Options (select) --- */
|
|
124
|
-
.sallaX-selectWrap {
|
|
125
|
-
margin: 10px 0 12px;
|
|
126
|
-
position: relative;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.sallaX-selectLabel {
|
|
130
|
-
font-size: 12px;
|
|
131
|
-
color: var(--sallaX-muted);
|
|
132
|
-
display: inline-block;
|
|
133
|
-
margin-bottom: 6px;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.sallaX-select {
|
|
137
|
-
width: 100%;
|
|
138
|
-
appearance: none;
|
|
139
|
-
background: #fff;
|
|
140
|
-
border: 1px solid var(--sallaX-border);
|
|
141
|
-
border-radius: 12px;
|
|
142
|
-
padding: 10px 12px;
|
|
143
|
-
font-size: 14px;
|
|
144
|
-
line-height: 1.2;
|
|
145
|
-
color: var(--sallaX-text);
|
|
146
|
-
box-shadow: var(--sallaX-shadow);
|
|
147
|
-
transition: border-color .2s ease, box-shadow .2s ease;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.sallaX-select:focus {
|
|
151
|
-
outline: none;
|
|
152
|
-
border-color: var(--sallaX-accent);
|
|
153
|
-
box-shadow: 0 0 0 3px rgba(127, 40, 248, .15);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* Optional: custom caret */
|
|
157
|
-
.sallaX-selectWrap::after {
|
|
158
|
-
content: "▾";
|
|
159
|
-
position: absolute;
|
|
160
|
-
right: 12px;
|
|
161
|
-
top: calc(50% + 8px);
|
|
162
|
-
transform: translateY(-50%);
|
|
163
|
-
pointer-events: none;
|
|
164
|
-
font-size: 12px;
|
|
165
|
-
color: var(--sallaX-muted);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/* (Old chip styles retained but unused) */
|
|
169
|
-
.sallaX-options { display: none; }
|
|
170
|
-
.sallaX-chip { display: none; }
|
|
171
|
-
|
|
172
|
-
/* CTA (quantity controls) */
|
|
173
|
-
.sallaX-cta {
|
|
174
|
-
display: inline-flex;
|
|
175
|
-
align-items: center;
|
|
176
|
-
gap: 2px;
|
|
177
|
-
background: #fff;
|
|
178
|
-
border: 1px solid var(--sallaX-border);
|
|
179
|
-
border-radius: 999px;
|
|
180
|
-
padding: 4px;
|
|
181
|
-
width: max-content;
|
|
182
|
-
user-select: none;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.sallaX-qty {
|
|
186
|
-
appearance: none;
|
|
187
|
-
border: none;
|
|
188
|
-
background: transparent;
|
|
189
|
-
width: 36px;
|
|
190
|
-
height: 36px;
|
|
191
|
-
border-radius: 999px;
|
|
192
|
-
font-size: 18px;
|
|
193
|
-
font-weight: 600;
|
|
194
|
-
line-height: 36px;
|
|
195
|
-
text-align: center;
|
|
196
|
-
cursor: pointer;
|
|
197
|
-
transition: background .2s ease, transform .1s ease;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.sallaX-qty[disabled] {
|
|
201
|
-
opacity: .5;
|
|
202
|
-
cursor: not-allowed;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.sallaX-qty:hover:not([disabled]) {
|
|
206
|
-
background: rgba(15, 15, 20, 0.05);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.sallaX-qty:active:not([disabled]) {
|
|
210
|
-
transform: scale(0.98);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.sallaX-qtyCount {
|
|
214
|
-
min-width: 34px;
|
|
215
|
-
text-align: center;
|
|
216
|
-
font-weight: 600;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/* Plus highlighted by accent */
|
|
220
|
-
.sallaX-qty--plus {
|
|
221
|
-
color: #fff;
|
|
222
|
-
background: var(--sallaX-accent);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.sallaX-qty--plus:hover {
|
|
226
|
-
filter: brightness(0.98);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/* Motion respect */
|
|
230
|
-
@media (prefers-reduced-motion: reduce) {
|
|
231
|
-
.sallaX-card,
|
|
232
|
-
.sallaX-img,
|
|
233
|
-
.sallaX-visit,
|
|
234
|
-
.sallaX-qty {
|
|
235
|
-
transition: none !important;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/* Responsive polish */
|
|
240
|
-
@media (min-width: 768px) {
|
|
241
|
-
.sallaX-body { padding: 16px 16px 18px; }
|
|
242
|
-
.sallaX-name { font-size: 17px; }
|
|
243
|
-
.sallaX-price { font-size: 19px; }
|
|
244
|
-
}
|
|
245
|
-
|
|
@@ -1,202 +0,0 @@
|
|
|
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,variantImage:string,price:string, qty?: number) => Promise<void>;
|
|
24
|
-
handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
|
|
25
|
-
onTryOnSelect: (product: any) => void; // Add this
|
|
26
|
-
enableTry:boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const SallaAsssiatnt: FC<AssistantMessageProps> = ({
|
|
30
|
-
message,
|
|
31
|
-
colorCode,
|
|
32
|
-
handleAddToCart,
|
|
33
|
-
handleRemoveFromCart,
|
|
34
|
-
onTryOnSelect,
|
|
35
|
-
enableTry
|
|
36
|
-
}) => {
|
|
37
|
-
const [counts, setCounts] = useState<Record<string, number>>({});
|
|
38
|
-
const [selectedVariants, setSelectedVariants] = useState<Record<string, string>>({});
|
|
39
|
-
|
|
40
|
-
const formatTextWithLinks = (text: string): React.ReactNode[] => {
|
|
41
|
-
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
42
|
-
return text.split(urlRegex).map((part, i) =>
|
|
43
|
-
urlRegex.test(part) ? (
|
|
44
|
-
<a
|
|
45
|
-
key={i}
|
|
46
|
-
href={part.trim()}
|
|
47
|
-
style={{ color: '#00b3bc' }}
|
|
48
|
-
target="_blank"
|
|
49
|
-
rel="noopener noreferrer"
|
|
50
|
-
className="underline break-words"
|
|
51
|
-
>
|
|
52
|
-
{part}
|
|
53
|
-
</a>
|
|
54
|
-
) : (
|
|
55
|
-
<span key={i}>{part}</span>
|
|
56
|
-
)
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const extractProductDetails = (text: string): ProductVariant | null => {
|
|
61
|
-
const normalized = text.trim();
|
|
62
|
-
if (!normalized.includes('Name:') || !normalized.includes('VariantId')) return null;
|
|
63
|
-
const nameMatch = normalized.match(/Name\s*[:\-]\s*(.+)/i);
|
|
64
|
-
const priceMatch = normalized.match(/Price\s*[:\-]\s*(.+)/i);
|
|
65
|
-
const linkMatch = normalized.match(/Link\s*[:\-]\s*(https?:\/\/[^\s]+)/i);
|
|
66
|
-
const variantMatch = normalized.match(/VariantId\s*[:\-]\s*(.+)/i);
|
|
67
|
-
const tagNameMatch = normalized.match(/variant_tag_name\s*[:\-]\s*(.+)/i);
|
|
68
|
-
const OptionMatch = normalized.match(/options\s*[:\-]\s*([^|\r\n]+)/i);
|
|
69
|
-
if (!nameMatch || !priceMatch || !linkMatch || !variantMatch || !tagNameMatch) return null;
|
|
70
|
-
return {
|
|
71
|
-
name: nameMatch[1].trim(),
|
|
72
|
-
price: priceMatch[1].trim(),
|
|
73
|
-
link: linkMatch[1].trim(),
|
|
74
|
-
image: '',
|
|
75
|
-
variant_id: variantMatch[1].trim(),
|
|
76
|
-
variant_tag_name: tagNameMatch[1].trim(),
|
|
77
|
-
option: OptionMatch?.[1]?.trim(),
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const parsedSegments = useMemo(() => {
|
|
82
|
-
const parts = message.split('|||').map(p => p.trim()).filter(Boolean);
|
|
83
|
-
const segments: { type: 'text' | 'product'; data: any }[] = [];
|
|
84
|
-
let lastProduct: ProductVariant | null = null;
|
|
85
|
-
|
|
86
|
-
parts.forEach(part => {
|
|
87
|
-
if (part.startsWith('img - ')) {
|
|
88
|
-
const url = part.replace(/^img\s*[-:]\s*/, '').trim();
|
|
89
|
-
if (lastProduct) lastProduct.image = url;
|
|
90
|
-
else {
|
|
91
|
-
const lastSeg = [...segments].reverse().find(s => s.type === 'product');
|
|
92
|
-
if (lastSeg) (lastSeg.data as ProductVariant).image = url;
|
|
93
|
-
}
|
|
94
|
-
} else if (part.includes('Name:') && part.includes('VariantId')) {
|
|
95
|
-
const product = extractProductDetails(part);
|
|
96
|
-
if (product) {
|
|
97
|
-
segments.push({ type: 'product', data: product });
|
|
98
|
-
lastProduct = product;
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
segments.push({ type: 'text', data: part });
|
|
102
|
-
lastProduct = null;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return segments;
|
|
107
|
-
}, [message]);
|
|
108
|
-
|
|
109
|
-
const groupedProducts = useMemo(() => {
|
|
110
|
-
const groups: Record<string, ProductVariant[]> = {};
|
|
111
|
-
parsedSegments.forEach(seg => {
|
|
112
|
-
if (seg.type === 'product') {
|
|
113
|
-
const p = seg.data as ProductVariant;
|
|
114
|
-
groups[p.variant_tag_name] = groups[p.variant_tag_name] || [];
|
|
115
|
-
groups[p.variant_tag_name].push(p);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
return groups;
|
|
119
|
-
}, [parsedSegments]);
|
|
120
|
-
|
|
121
|
-
const shortenName = (name: string, maxLength = 22) =>
|
|
122
|
-
name.length > maxLength ? name.slice(0, maxLength) + '...' : name;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const [currentSlide, setCurrentSlide] = useState(0);
|
|
127
|
-
const totalSlides =Object.keys(groupedProducts).length ;
|
|
128
|
-
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
|
|
129
|
-
loop: false,
|
|
130
|
-
mode: 'free',
|
|
131
|
-
slides: { perView: 1, spacing: 16 },
|
|
132
|
-
slideChanged(s) {
|
|
133
|
-
setCurrentSlide(s.track.details.rel);
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<div className="space-y-6 mt-4 h-fit">
|
|
140
|
-
{parsedSegments
|
|
141
|
-
.filter(seg => seg.type === 'text')
|
|
142
|
-
.map((seg, i) => (
|
|
143
|
-
<p key={i} className="whitespace-pre-wrap break-words">
|
|
144
|
-
{formatTextWithLinks(seg.data)}
|
|
145
|
-
</p>
|
|
146
|
-
))}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{Object.keys(groupedProducts).length > 0 && (
|
|
151
|
-
<>
|
|
152
|
-
{Object.keys(groupedProducts).length > 1 && (
|
|
153
|
-
<div
|
|
154
|
-
style={{
|
|
155
|
-
display: 'flex',
|
|
156
|
-
margin: 0,
|
|
157
|
-
marginBottom: '6px',
|
|
158
|
-
justifyContent: 'space-between',
|
|
159
|
-
paddingInline: '4px',
|
|
160
|
-
}}
|
|
161
|
-
>
|
|
162
|
-
<Button
|
|
163
|
-
shape="circle"
|
|
164
|
-
icon={<ArrowLeftOutlined />}
|
|
165
|
-
onClick={() => slider.current?.prev()}
|
|
166
|
-
disabled={currentSlide === 0}
|
|
167
|
-
/>
|
|
168
|
-
<Button
|
|
169
|
-
shape="circle"
|
|
170
|
-
icon={<ArrowRightOutlined />}
|
|
171
|
-
onClick={() => slider.current?.next()}
|
|
172
|
-
disabled={currentSlide === totalSlides - 1}
|
|
173
|
-
/>
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
|
-
<div ref={sliderRef} className="keen-slider">
|
|
177
|
-
<SallaProductCarousel
|
|
178
|
-
groupedProducts={groupedProducts}
|
|
179
|
-
selectedVariants={selectedVariants}
|
|
180
|
-
setSelectedVariants={setSelectedVariants}
|
|
181
|
-
counts={counts}
|
|
182
|
-
setCounts={setCounts}
|
|
183
|
-
handleAddToCart={handleAddToCart}
|
|
184
|
-
handleRemoveFromCart={handleRemoveFromCart}
|
|
185
|
-
colorCode={colorCode}
|
|
186
|
-
shortenName={shortenName}
|
|
187
|
-
sliderInstance={slider} // pass it down if needed
|
|
188
|
-
currentSlide={currentSlide}
|
|
189
|
-
totalSlides={totalSlides}
|
|
190
|
-
sliderRef={sliderRef}
|
|
191
|
-
onTryOnSelect={onTryOnSelect}
|
|
192
|
-
enableTry={enableTry}
|
|
193
|
-
|
|
194
|
-
/>
|
|
195
|
-
</div>
|
|
196
|
-
</>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
export default SallaAsssiatnt;
|