shopify-chatbot-widget 0.7.9 → 0.8.2

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 +111 -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,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
+