shopify-chatbot-widget 0.8.6 → 0.8.9

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shopify-chatbot-widget",
3
3
  "private": false,
4
- "version": "0.8.6",
4
+ "version": "0.8.9",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -9,10 +9,11 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
9
9
  import { LoadingOutlined } from '@ant-design/icons';
10
10
  import { Spin } from 'antd';
11
11
  import { motion, AnimatePresence } from 'framer-motion';
12
+ import CartLayout from './components/Cart/Index';
12
13
 
13
14
  const Chatbot: React.FC = () => {
14
- const [open, setOpen] = useState(true);
15
- const [viewMode, setViewMode] = useState<'sessions' | 'create' | 'messenger'>('sessions');
15
+ const [open, setOpen] = useState(false);
16
+ const [viewMode, setViewMode] = useState<'sessions' | 'create' | 'messenger' | 'cartView' >('sessions');
16
17
  const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
17
18
  const [HasInfo, setHasInfo] = useState(false);
18
19
  const [activeAgentName, setActiveAgentName] = useState('');
@@ -31,6 +32,7 @@ const Chatbot: React.FC = () => {
31
32
  calendly,
32
33
  disbleCheckout,
33
34
  chatbotName,
35
+ setMessagesSuggestions
34
36
  } = useChatbotData();
35
37
 
36
38
  const [visitorData, setVisitorData] = useState<{ visitorId: string; country: string } | null>(null);
@@ -177,6 +179,34 @@ const Chatbot: React.FC = () => {
177
179
  </motion.div>
178
180
  )}
179
181
 
182
+ {viewMode === 'cartView' && (
183
+ <motion.div
184
+ key="create"
185
+ initial={{ opacity: 0, y: 30 }}
186
+ animate={{ opacity: 1, y: 0 }}
187
+ exit={{ opacity: 0, y: -30 }}
188
+ transition={{ duration: 0.3 }}
189
+ style={{height:'100%'}}
190
+
191
+
192
+ >
193
+ <CartLayout
194
+ setOpen={(v: boolean) => (v ? requestOpen() : requestClose())}
195
+ sessionId={activeSessionId}
196
+ Opened={open}
197
+ chatbotLogo={chatbotLogo}
198
+ colorCode={colorCode}
199
+ onBack={() => {
200
+ setNewSession(false);
201
+ setViewMode("messenger");
202
+ }}
203
+ />
204
+
205
+
206
+ </motion.div>
207
+ )}
208
+
209
+
180
210
  {viewMode === 'messenger' && activeSessionId && (
181
211
  <motion.div
182
212
  key="messenger"
@@ -198,12 +228,15 @@ const Chatbot: React.FC = () => {
198
228
  setOpen={(v: boolean) => (v ? requestOpen() : requestClose())}
199
229
  setNewSession={setNewSession}
200
230
  Opened={open}
201
- setView={() => setViewMode('sessions')}
231
+ setView={setViewMode}
202
232
  userType={userType}
203
233
  disbleCheckout={disbleCheckout}
234
+ setMessagesSuggestions={setMessagesSuggestions}
204
235
  />
205
236
  </motion.div>
206
237
  )}
238
+
239
+
207
240
  </AnimatePresence>
208
241
  )}
209
242
  </div>
@@ -0,0 +1,287 @@
1
+ import React, { useState } from "react";
2
+ import {
3
+ CloseOutlined,
4
+ ArrowLeftOutlined,
5
+ DeleteOutlined,
6
+ ShoppingOutlined,
7
+ LoadingOutlined
8
+ } from "@ant-design/icons";
9
+ import { Button, Spin, message } from "antd";
10
+ import { useCart } from "../../../hooks/useCart";
11
+ import { useCartAPI } from "../../../hooks/useCheckout";
12
+
13
+ type CartSessionProps = {
14
+ setOpen: (v: boolean) => void;
15
+ sessionId: string | null;
16
+ Opened: boolean;
17
+ chatbotLogo: string;
18
+ colorCode?: string;
19
+ onBack: () => void;
20
+ };
21
+
22
+ const CartLayout: React.FC<CartSessionProps> = ({
23
+ colorCode,
24
+ setOpen,
25
+ Opened,
26
+ onBack,
27
+ sessionId,
28
+ }) => {
29
+ const { items, loading, removeFromCart, refresh } = useCart(sessionId || "");
30
+ const { checkOut } = useCartAPI();
31
+ const [isLoading, setIsLoading] = useState(false);
32
+
33
+
34
+ const handleCheckout = async () => {
35
+ if (!sessionId) {
36
+ message.warning("No session found — please start a chat first.");
37
+ return;
38
+ }
39
+
40
+ setIsLoading(true);
41
+ try {
42
+ await checkOut(sessionId);
43
+ } catch (err: any) {
44
+ message.error("Checkout failed. Please try again.");
45
+ console.error("Checkout error:", err);
46
+ } finally {
47
+ setIsLoading(false);
48
+ }
49
+ };
50
+
51
+ const loadingIcon = <LoadingOutlined style={{ fontSize: 24, color: colorCode||'#7F28F8' }} spin />;
52
+
53
+
54
+ const handleRemove = async (variantId: string) => {
55
+ await removeFromCart(variantId, 1);
56
+ message.info("Item removed from cart");
57
+ await refresh();
58
+ };
59
+
60
+ return (
61
+ <div
62
+ style={{
63
+ maxWidth: "100%",
64
+ height: "100%",
65
+ border: "1px solid #f0f0f0",
66
+ borderRadius: 8,
67
+ display: "flex",
68
+ flexDirection: "column",
69
+ backgroundColor: "#fff",
70
+ overflow: "hidden",
71
+ }}
72
+ >
73
+ {/* Header */}
74
+ <div
75
+ style={{
76
+ padding: "16px",
77
+ display: "flex",
78
+ alignItems: "center",
79
+ borderBottom: "1px solid #f5f5f5",
80
+ boxShadow: "0 2px 6px rgba(0,0,0,0.03)",
81
+ zIndex: 5,
82
+ height:'8%'
83
+ }}
84
+ >
85
+ <Button
86
+ type="text"
87
+ icon={<ArrowLeftOutlined />}
88
+ onClick={onBack}
89
+ style={{ color: "#333", fontWeight: 500 }}
90
+ >
91
+ Back
92
+ </Button>
93
+
94
+ <div style={{ marginLeft: "auto" }}>
95
+ <CloseOutlined
96
+ onClick={() => setOpen(!Opened)}
97
+ style={{
98
+ fontSize: 18,
99
+ cursor: "pointer",
100
+ color: "#555",
101
+ transition: "color 0.2s ease",
102
+ }}
103
+ onMouseEnter={(e) =>
104
+ ((e.currentTarget as HTMLElement).style.color =
105
+ colorCode || "#7F28F8")
106
+ }
107
+ onMouseLeave={(e) =>
108
+ ((e.currentTarget as HTMLElement).style.color = "#555")
109
+ }
110
+ />
111
+ </div>
112
+ </div>
113
+
114
+ {/* Scrollable Content */}
115
+ <div
116
+ style={{
117
+ flex: 1,
118
+ overflowY: "auto",
119
+ padding: "16px",
120
+ height:'66%'
121
+
122
+ }}
123
+
124
+ >
125
+ <h3 style={{ fontWeight: 600, marginBottom: 16 }}>
126
+ <ShoppingOutlined style={{ marginRight: 8 }} />
127
+ Your Cart ({items.length})
128
+ </h3>
129
+
130
+ {loading ? (
131
+ <div style={{ textAlign: "center", marginTop: 40 }}>
132
+ <Spin indicator={loadingIcon} />
133
+ </div>
134
+ ) : items.length === 0 ? (
135
+ <div style={{ textAlign: "center", color: "#888", marginTop: 60 }}>
136
+ Your cart is empty 🛒
137
+ </div>
138
+ ) : (
139
+ <div
140
+ style={{
141
+ flex: 1, // fill remaining space
142
+ overflowY: "auto", // scroll only this part
143
+ display: "flex",
144
+ flexDirection: "column",
145
+ height:'80%',
146
+ gap: "12px",
147
+ paddingRight: "4px",
148
+ }}
149
+ >
150
+ {items.map((item) => (
151
+
152
+ <div
153
+ key={item.variantId}
154
+ style={{
155
+ display: "flex",
156
+ justifyContent: "space-between",
157
+ alignItems: "center",
158
+ background: "#fafafa",
159
+ borderRadius: 10,
160
+ padding: "12px 10px",
161
+ transition: "all 0.2s ease",
162
+ }}
163
+ className="hover:shadow-sm"
164
+ >
165
+ {/* ✅ Left side */}
166
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
167
+ <img
168
+ src={item.variantImage}
169
+ alt={item.name}
170
+ style={{
171
+ width: 64,
172
+ height: 64,
173
+ borderRadius: 8,
174
+ objectFit: "cover",
175
+ backgroundColor: "#f3f3f3",
176
+ }}
177
+ />
178
+ <div>
179
+ <p style={{ fontWeight: 600, color: "#222", marginBottom: 4 }}>
180
+ {item.name}
181
+ </p>
182
+ <p style={{ fontSize: 13, color: "#777", margin: 0 }}>
183
+ Qty: {item.quantity}
184
+ </p>
185
+ </div>
186
+ </div>
187
+
188
+ {/* ✅ Right side */}
189
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
190
+ <span style={{ fontWeight: 600 }}>£29.99</span>
191
+ <DeleteOutlined
192
+ onClick={() => handleRemove(item.variantId)}
193
+ style={{
194
+ cursor: "pointer",
195
+ color: "#999",
196
+ fontSize: 16,
197
+ transition: "color 0.2s ease",
198
+ }}
199
+ onMouseEnter={(e) =>
200
+ ((e.currentTarget as HTMLElement).style.color = "#e74c3c")
201
+ }
202
+ onMouseLeave={(e) =>
203
+ ((e.currentTarget as HTMLElement).style.color = "#999")
204
+ }
205
+ />
206
+ </div>
207
+ </div>
208
+
209
+ ))}
210
+ </div>
211
+ )}
212
+
213
+ {/* <Divider style={{ margin: "20px 0" }} /> */}
214
+
215
+ {/* Discount Section */}
216
+ {/* <div style={{ display: "flex", gap: "8px", marginBottom: "16px" }}>
217
+ <Input placeholder="Enter discount code" size="large" />
218
+ <Button
219
+ type="primary"
220
+ size="large"
221
+ style={{
222
+ backgroundColor: colorCode || "black",
223
+ borderColor: colorCode || "black",
224
+ borderRadius: 6,
225
+ }}
226
+ >
227
+ APPLY
228
+ </Button>
229
+ </div> */}
230
+
231
+ {/* Summary */}
232
+
233
+ </div>
234
+
235
+ {/* Sticky Footer */}
236
+ <div
237
+ style={{
238
+ position: "sticky",
239
+ bottom: 0,
240
+ left: 0,
241
+ right: 0,
242
+ background: "#fff",
243
+ borderTop: "1px solid #f0f0f0",
244
+ padding: "16px 20px",
245
+ boxShadow: "0 -3px 10px rgba(0,0,0,0.05)",
246
+ zIndex: 10,
247
+ }}
248
+ >
249
+ <Button
250
+ type="primary"
251
+ block
252
+ size="large"
253
+ onClick={handleCheckout}
254
+ loading={isLoading}
255
+ disabled={items.length === 0}
256
+ style={{
257
+ backgroundColor: colorCode || "black",
258
+ borderColor: colorCode || "black",
259
+ borderRadius: 10,
260
+ fontWeight: 600,
261
+ }}
262
+ >
263
+ <span style={{color:'white'}}>
264
+ CHECKOUT
265
+
266
+ </span>
267
+ </Button>
268
+ </div>
269
+
270
+ {/* Scrollbar */}
271
+ <style>{`
272
+ .custom-scrollbar::-webkit-scrollbar {
273
+ width: 6px;
274
+ }
275
+ .custom-scrollbar::-webkit-scrollbar-thumb {
276
+ background-color: rgba(0,0,0,0.2);
277
+ border-radius: 3px;
278
+ }
279
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
280
+ background-color: rgba(0,0,0,0.4);
281
+ }
282
+ `}</style>
283
+ </div>
284
+ );
285
+ };
286
+
287
+ export default CartLayout;
@@ -300,18 +300,23 @@ const ChatInput: React.FC<ChatInputProps> = ({
300
300
  padding: "0"
301
301
  }}
302
302
  onKeyDown={(e) => {
303
- if (e.key === "Enter") {
303
+ if (e.key === "Enter" && !isSending) {
304
304
  e.preventDefault();
305
- if (selectedImage) {
305
+
306
+ const hasImage = !!selectedImage;
307
+ const hasText = !!userMessage.trim();
308
+
309
+ if (hasImage) {
306
310
  handleSendImage(selectedImage, userMessage);
307
311
  removeSelectedImage();
308
312
  setUserMessage("");
309
- } else if (userMessage.trim()) {
313
+ } else if (hasText) {
310
314
  handleSendMessage();
311
315
  setUserMessage("");
312
316
  }
313
317
  }
314
318
  }}
319
+
315
320
  />
316
321
  )}
317
322
  </div>
@@ -22,7 +22,7 @@ interface AssistantMessageProps {
22
22
  colorCode: string;
23
23
  isBusiness?: boolean;
24
24
  disableCheckout?:boolean;
25
- handleAddToCart: (variantId: string, name: string, qty?: number) => Promise<void>;
25
+ handleAddToCart: (variantId: string, name: string,variantImage:string, qty?: number) => Promise<void>;
26
26
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
27
27
  }
28
28
 
@@ -20,7 +20,7 @@ interface Props {
20
20
  setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
21
21
  counts: Record<string, number>;
22
22
  setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
23
- handleAddToCart: (variantId: string, name: string, qty: number) => Promise<void>;
23
+ handleAddToCart: (variantId: string, name: string,variantImage:string, qty?: number) => Promise<void>;
24
24
  handleRemoveFromCart: (variantId: string, qty: number) => Promise<void>;
25
25
  colorCode?: string;
26
26
  shortenName: (name: string) => string;
@@ -88,7 +88,7 @@ const ProductCarousel: FC<Props> = ({
88
88
  ...prev,
89
89
  [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
90
90
  }));
91
- await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
91
+ await handleAddToCart(currentVariant.variant_id,currentVariant.name,currentVariant.image, 1);
92
92
  };
93
93
 
94
94
  const decrement = async () => {
@@ -23,7 +23,7 @@ interface Props {
23
23
  setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
24
24
  counts: Record<string, number>;
25
25
  setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
26
- handleAddToCart: (variantId: string, name: string, qty?: number) => Promise<void>;
26
+ handleAddToCart: (variantId: string, name: string, variantImage:string, qty?: number) => Promise<void>;
27
27
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
28
28
  colorCode?: string;
29
29
  shortenName: (name: string) => string;
@@ -101,11 +101,8 @@ const ProductCarousel: FC<Props> = ({
101
101
  [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
102
102
  }));
103
103
  try {
104
- // wait for backend to confirm success
105
- await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
104
+ await handleAddToCart(currentVariant.variant_id, currentVariant.name,currentVariant.image, 1);
106
105
 
107
- // only update counts if the request succeeded
108
-
109
106
  } catch (err: any) {
110
107
  console.warn("Add to cart failed:", err.message);
111
108
  setCounts(prev => ({
@@ -21,7 +21,7 @@ interface Props {
21
21
  setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
22
22
  counts: Record<string, number>;
23
23
  setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
24
- handleAddToCart: (variantId: string, name: string, qty: number) => Promise<void>;
24
+ handleAddToCart: (variantId: string, name: string,variantImage:string, qty: number) => Promise<void>;
25
25
  handleRemoveFromCart: (variantId: string, qty: number) => Promise<void>;
26
26
  colorCode?: string; // accent
27
27
  shortenName: (name: string) => string;
@@ -21,7 +21,7 @@ interface Props {
21
21
  setSelectedVariants: React.Dispatch<React.SetStateAction<Record<string, string>>>;
22
22
  counts: Record<string, number>;
23
23
  setCounts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
24
- handleAddToCart: (variantId: string, name: string, qty: number) => Promise<void>;
24
+ handleAddToCart: (variantId: string, name: string, variantImage:string,qty: number) => Promise<void>;
25
25
  handleRemoveFromCart: (variantId: string, qty: number) => Promise<void>;
26
26
  colorCode?: string; // accent
27
27
  shortenName: (name: string) => string;
@@ -33,11 +33,15 @@ interface ChatMessagesProps {
33
33
  messages: Message[];
34
34
  colorCode: string;
35
35
  isLoading: boolean;
36
+
36
37
  handleAddToCart: (
37
38
  variantId: string,
38
39
  name: string,
39
- qty?: number
40
+ variantImage:string,
41
+ qty?: number,
42
+
40
43
  ) => Promise<void>;
44
+
41
45
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
42
46
  sessionId:string;
43
47
  isBusiness?: boolean;
@@ -20,7 +20,7 @@ interface AssistantMessageProps {
20
20
  message: string;
21
21
  colorCode: string;
22
22
  isBusiness?: boolean;
23
- handleAddToCart: (variantId: string, name: string, qty?: number) => Promise<void>;
23
+ handleAddToCart: (variantId: string, name: string,variantImage:string, qty?: number) => Promise<void>;
24
24
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
25
25
  }
26
26
 
@@ -105,7 +105,7 @@ const SupportConnect: React.FC<SupportConnectProps> = ({
105
105
  >
106
106
  <div className="flex items-center space-x-4">
107
107
  <img
108
- src={data.userImage}
108
+ src={data.userImage || 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSaC0jamaBcOGTbS86459UezKUs2Wo0lPU_mw&s'}
109
109
  alt="User"
110
110
  className="w-12 h-12 rounded-full border-2"
111
111
  style={{ borderColor: colorCode }}
@@ -117,7 +117,7 @@ const SupportConnect: React.FC<SupportConnectProps> = ({
117
117
  </span>
118
118
  </p>
119
119
  <img
120
- src={data.companyImage}
120
+ src={data.companyImage || 'https://cdn-icons-png.flaticon.com/512/4812/4812244.png'}
121
121
  alt="Company"
122
122
  className="w-12 h-12 rounded-full border"
123
123
  />
@@ -20,7 +20,7 @@ interface AssistantMessageProps {
20
20
  message: string;
21
21
  colorCode: string;
22
22
  isBusiness?: boolean;
23
- handleAddToCart: (variantId: string, name: string, qty?: number) => Promise<void>;
23
+ handleAddToCart: (variantId: string, name: string,variantImage:string, qty?: number) => Promise<void>;
24
24
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
25
25
  }
26
26