shopify-chatbot-widget 0.7.8 → 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.
- package/dist/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +117 -117
- package/package.json +3 -1
- package/src/App.css +42 -0
- package/src/App.tsx +13 -0
- package/src/Chatbot/Chatbot.css +270 -0
- package/src/Chatbot/Chatbot.tsx +214 -0
- package/src/Chatbot/ChatbotTransition.css +19 -0
- package/src/Chatbot/OLDCHAT.tsx +259 -0
- package/src/Chatbot/components/ChatInput.css +127 -0
- package/src/Chatbot/components/ChatInput.tsx +354 -0
- package/src/Chatbot/components/Messages/Addcart.tsx +70 -0
- package/src/Chatbot/components/Messages/AgentStatus.tsx +37 -0
- package/src/Chatbot/components/Messages/AssistantMesage.tsx +206 -0
- package/src/Chatbot/components/Messages/AudioMessage.tsx +123 -0
- package/src/Chatbot/components/Messages/CalendlyEmbeds.tsx +66 -0
- package/src/Chatbot/components/Messages/CardSwiper.tsx +194 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +264 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +208 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +208 -0
- package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +245 -0
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +108 -0
- package/src/Chatbot/components/Messages/CartWidget.tsx +151 -0
- package/src/Chatbot/components/Messages/ChatImage.tsx +46 -0
- package/src/Chatbot/components/Messages/ChatImageText.tsx +74 -0
- package/src/Chatbot/components/Messages/ChatImageTextAssis.tsx +72 -0
- package/src/Chatbot/components/Messages/ChatMessages.css +31 -0
- package/src/Chatbot/components/Messages/ChatMessages.tsx +334 -0
- package/src/Chatbot/components/Messages/ProductCard.css +149 -0
- package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +196 -0
- package/src/Chatbot/components/Messages/Support.tsx +177 -0
- package/src/Chatbot/components/Messages/Typing.css +31 -0
- package/src/Chatbot/components/Messages/Typing.tsx +19 -0
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +196 -0
- package/src/Chatbot/components/Messages/cartwidget.css +123 -0
- package/src/Chatbot/components/Messenger/Messenger.tsx +531 -0
- package/src/Chatbot/components/Notification.tsx +28 -0
- package/src/Chatbot/components/Powered.css +29 -0
- package/src/Chatbot/components/Powered.tsx +12 -0
- package/src/Chatbot/components/Sessions/CreateSession.tsx +201 -0
- package/src/Chatbot/components/Sessions/RenderList.tsx +212 -0
- package/src/Chatbot/components/Suggestions.css +35 -0
- package/src/Chatbot/components/Suggestions.tsx +57 -0
- package/src/Chatbot/notification.css +17 -0
- package/src/assets/react.svg +1 -0
- package/src/chatbot-widget/index.tsx +14 -0
- package/src/hooks/config.tsx +13 -0
- package/src/hooks/useCart.tsx +107 -0
- package/src/hooks/useChatbotAPI.tsx +167 -0
- package/src/hooks/useChatbotData.tsx +132 -0
- package/src/hooks/useSessionMessages.tsx +50 -0
- package/src/hooks/useWebsocke.tsx +45 -0
- package/src/i18n.tsx +30 -0
- package/src/index.css +2 -0
- package/src/main.tsx +10 -0
- package/src/types/chatbot.ts +40 -0
- package/src/utils/cookies.tsx +14 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { Mail } from 'lucide-react';
|
|
4
|
+
import config from '../../../hooks/config';
|
|
5
|
+
|
|
6
|
+
interface SupportConnectProps {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
colorCode?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Stage = 'email_sent' | 'connecting' | 'auto';
|
|
12
|
+
|
|
13
|
+
interface SupportData {
|
|
14
|
+
userImage?: string;
|
|
15
|
+
agentName?: string;
|
|
16
|
+
companyImage?: string;
|
|
17
|
+
agentAvatar?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SupportConnect: React.FC<SupportConnectProps> = ({
|
|
21
|
+
sessionId,
|
|
22
|
+
colorCode = '#000',
|
|
23
|
+
}) => {
|
|
24
|
+
const [stage, setStage] = useState<Stage | null>(null);
|
|
25
|
+
const [data, setData] = useState<SupportData>({});
|
|
26
|
+
const [progress, setProgress] = useState<number>(0);
|
|
27
|
+
const dots = [0, 1, 2];
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!sessionId) return;
|
|
31
|
+
const ws = new WebSocket(
|
|
32
|
+
`${config.websocketUrl}/support/${sessionId}/`
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
ws.onmessage = ({ data: text }) => {
|
|
36
|
+
const msg = JSON.parse(text);
|
|
37
|
+
setStage(msg.stage as Stage);
|
|
38
|
+
setData(msg.data || {});
|
|
39
|
+
};
|
|
40
|
+
ws.onclose = () => console.log('Support socket closed');
|
|
41
|
+
return () => ws.close();
|
|
42
|
+
}, [sessionId]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let timerId: number;
|
|
46
|
+
let start: number;
|
|
47
|
+
|
|
48
|
+
if (stage === 'connecting') {
|
|
49
|
+
start = Date.now();
|
|
50
|
+
setProgress(100);
|
|
51
|
+
|
|
52
|
+
timerId = window.setInterval(() => {
|
|
53
|
+
const elapsed = (Date.now() - start) / 1000; // seconds
|
|
54
|
+
const pct = Math.max(100 - (elapsed / 7) * 100, 0);
|
|
55
|
+
setProgress(pct);
|
|
56
|
+
if (pct <= 0) window.clearInterval(timerId);
|
|
57
|
+
}, 100);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
if (timerId) window.clearInterval(timerId);
|
|
62
|
+
};
|
|
63
|
+
}, [stage]);
|
|
64
|
+
|
|
65
|
+
if (!stage) return null;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="bg-opacity-50 flex items-center justify-center z-50">
|
|
69
|
+
<AnimatePresence>
|
|
70
|
+
{stage === 'email_sent' && (
|
|
71
|
+
<motion.div
|
|
72
|
+
key="email"
|
|
73
|
+
className="bg-white rounded-2xl shadow-xl p-6 flex flex-col items-center"
|
|
74
|
+
initial={{ scale: 0.9, opacity: 0 }}
|
|
75
|
+
animate={{ scale: 1, opacity: 1 }}
|
|
76
|
+
exit={{ scale: 0.8, opacity: 0 }}
|
|
77
|
+
>
|
|
78
|
+
<Mail size={32} className="mb-2" style={{ color: colorCode }} />
|
|
79
|
+
<p className="text-lg font-semibold">
|
|
80
|
+
Sending your request to support…
|
|
81
|
+
</p>
|
|
82
|
+
<div className="flex space-x-2 mt-2">
|
|
83
|
+
{dots.map((_, i) => (
|
|
84
|
+
<motion.span
|
|
85
|
+
key={i}
|
|
86
|
+
className="text-2xl"
|
|
87
|
+
style={{ color: colorCode }}
|
|
88
|
+
animate={{ scale: [0.7, 1.3, 0.7] }}
|
|
89
|
+
transition={{ delay: i * 0.2, repeat: Infinity, duration: 0.8 }}
|
|
90
|
+
>
|
|
91
|
+
•
|
|
92
|
+
</motion.span>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
</motion.div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{stage === 'connecting' && (
|
|
99
|
+
<motion.div
|
|
100
|
+
key="connecting"
|
|
101
|
+
className="bg-white rounded-2xl shadow-xl p-6 flex flex-col items-center"
|
|
102
|
+
initial={{ scale: 0.9, opacity: 0 }}
|
|
103
|
+
animate={{ scale: 1, opacity: 1 }}
|
|
104
|
+
exit={{ scale: 0.8, opacity: 0 }}
|
|
105
|
+
>
|
|
106
|
+
<div className="flex items-center space-x-4">
|
|
107
|
+
<img
|
|
108
|
+
src={data.userImage}
|
|
109
|
+
alt="User"
|
|
110
|
+
className="w-12 h-12 rounded-full border-2"
|
|
111
|
+
style={{ borderColor: colorCode }}
|
|
112
|
+
/>
|
|
113
|
+
<p className="text-lg">
|
|
114
|
+
Connecting you to{' '}
|
|
115
|
+
<span className="text-indigo-600">
|
|
116
|
+
{data.agentName || 'Support Agent'}
|
|
117
|
+
</span>
|
|
118
|
+
</p>
|
|
119
|
+
<img
|
|
120
|
+
src={data.companyImage}
|
|
121
|
+
alt="Company"
|
|
122
|
+
className="w-12 h-12 rounded-full border"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="flex space-x-2 mt-3">
|
|
126
|
+
{dots.map((_, i) => (
|
|
127
|
+
<motion.span
|
|
128
|
+
key={i}
|
|
129
|
+
className="text-2xl"
|
|
130
|
+
style={{ color: colorCode }}
|
|
131
|
+
animate={{ scale: [0.7, 1.3, 0.7] }}
|
|
132
|
+
transition={{ delay: i * 0.2, repeat: Infinity, duration: 0.8 }}
|
|
133
|
+
>
|
|
134
|
+
•
|
|
135
|
+
</motion.span>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
<div className="w-full bg-gray-200 h-1 rounded-full mt-4 overflow-hidden">
|
|
139
|
+
<motion.div
|
|
140
|
+
className="h-full"
|
|
141
|
+
style={{ width: `${progress}%`, backgroundColor: colorCode }}
|
|
142
|
+
transition={{ ease: 'linear', duration: 1 }}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
</motion.div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{stage === 'auto' && (
|
|
149
|
+
<motion.div
|
|
150
|
+
key="auto"
|
|
151
|
+
className="bg-white rounded-2xl shadow-xl p-6 flex items-center space-x-4"
|
|
152
|
+
initial={{ y: 50, opacity: 0 }}
|
|
153
|
+
animate={{ y: 0, opacity: 1 }}
|
|
154
|
+
exit={{ y: -50, opacity: 0 }}
|
|
155
|
+
>
|
|
156
|
+
<img
|
|
157
|
+
src={
|
|
158
|
+
data.agentAvatar ||
|
|
159
|
+
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSaC0jamaBcOGTbS86459UezKUs2Wo0lPU_mw&s'
|
|
160
|
+
}
|
|
161
|
+
alt="Auto Support"
|
|
162
|
+
className="w-12 h-12 rounded-2xl"
|
|
163
|
+
/>
|
|
164
|
+
<p className="text-lg">
|
|
165
|
+
You’re now connected to{' '}
|
|
166
|
+
<span style={{ color: colorCode }}>
|
|
167
|
+
{data.agentName || 'Auto-Support'}
|
|
168
|
+
</span>
|
|
169
|
+
</p>
|
|
170
|
+
</motion.div>
|
|
171
|
+
)}
|
|
172
|
+
</AnimatePresence>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export default SupportConnect;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.typing-boxes {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 6px;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: flex-start;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.typing-boxes span {
|
|
9
|
+
border-radius: 3px; /* keep boxy feel */
|
|
10
|
+
display: inline-block;
|
|
11
|
+
animation: pulseBox 0.8s infinite alternate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.typing-boxes span:nth-child(2) {
|
|
15
|
+
animation-delay: 0.2s;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.typing-boxes span:nth-child(3) {
|
|
19
|
+
animation-delay: 0.4s;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes pulseBox {
|
|
23
|
+
from {
|
|
24
|
+
transform: scale(1);
|
|
25
|
+
opacity: 0.5;
|
|
26
|
+
}
|
|
27
|
+
to {
|
|
28
|
+
transform: scale(1.3);
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './Typing.css';
|
|
3
|
+
|
|
4
|
+
interface TypingProps {
|
|
5
|
+
color?: string; // optional, defaults to black
|
|
6
|
+
size?: number; // optional, lets you control box size
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Typing: React.FC<TypingProps> = ({ color, size = 5 }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="typing-boxes">
|
|
12
|
+
<span style={{ backgroundColor: color, width: size, height: size }}></span>
|
|
13
|
+
<span style={{ backgroundColor: color, width: size, height: size }}></span>
|
|
14
|
+
<span style={{ backgroundColor: color, width: size, height: size }}></span>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Typing;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useState, useMemo, type FC } from 'react';
|
|
2
|
+
import './ProductCard.css';
|
|
3
|
+
import ZidProductCarousel from './Carousel/CardSwiperZid';
|
|
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
|
+
<ZidProductCarousel
|
|
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;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
.jawebchatbotcss-cart-widget {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-block;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.jawebchatbotcss-cart-button {
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: 6px;
|
|
10
|
+
padding: 6px 12px;
|
|
11
|
+
border: none;
|
|
12
|
+
background: none;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
border-radius: 6px;
|
|
15
|
+
transition: background 0.2s ease-in-out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.jawebchatbotcss-cart-button:hover {
|
|
19
|
+
background-color: #f1f1f1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.jawebchatbotcss-cart-icon {
|
|
23
|
+
font-size: 18px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.jawebchatbotcss-cart-label {
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.jawebchatbotcss-cart-dropdown {
|
|
31
|
+
position: absolute;
|
|
32
|
+
right: 0;
|
|
33
|
+
margin-top: 16px;
|
|
34
|
+
width: 320px;
|
|
35
|
+
background-color: white;
|
|
36
|
+
border: 1px solid #e5e7eb;
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
|
39
|
+
z-index: 999;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.jawebchatbotcss-cart-header {
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: space-between;
|
|
45
|
+
align-items: center;
|
|
46
|
+
padding: 12px 16px;
|
|
47
|
+
border-bottom: 1px solid #f3f4f6;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.jawebchatbotcss-cart-title {
|
|
51
|
+
font-size: 16px;
|
|
52
|
+
font-weight: 500;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.jawebchatbotcss-refresh-button {
|
|
56
|
+
font-size: 14px;
|
|
57
|
+
color: #2563eb;
|
|
58
|
+
background: none;
|
|
59
|
+
border: none;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.jawebchatbotcss-refresh-button:disabled {
|
|
64
|
+
opacity: 0.5;
|
|
65
|
+
cursor: not-allowed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.jawebchatbotcss-cart-empty {
|
|
69
|
+
padding: 16px;
|
|
70
|
+
text-align: center;
|
|
71
|
+
color: #6b7280;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.jawebchatbotcss-cart-list {
|
|
75
|
+
max-height: 240px;
|
|
76
|
+
overflow-y: auto;
|
|
77
|
+
margin: 0;
|
|
78
|
+
padding: 0;
|
|
79
|
+
list-style: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.jawebchatbotcss-cart-item {
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
align-items: center;
|
|
86
|
+
padding: 12px 16px;
|
|
87
|
+
border-bottom: 1px solid #f9fafb;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.jawebchatbotcss-item-name {
|
|
91
|
+
width: 160px;
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
text-overflow: ellipsis;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.jawebchatbotcss-item-controls {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 8px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.jawebchatbotcss-item-qty {
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
color: #374151;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.jawebchatbotcss-remove-icon {
|
|
111
|
+
color: #ef4444;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.jawebchatbotcss-remove-icon:hover {
|
|
116
|
+
color: #b91c1c;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.jawebchatbotcss-cart-footer {
|
|
120
|
+
padding: 16px;
|
|
121
|
+
border-top: 1px solid #f3f4f6;
|
|
122
|
+
}
|
|
123
|
+
|