shopify-chatbot-widget 0.8.3 → 0.8.5

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.3",
4
+ "version": "0.8.5",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -11,7 +11,7 @@ import { Spin } from 'antd';
11
11
  import { motion, AnimatePresence } from 'framer-motion';
12
12
 
13
13
  const Chatbot: React.FC = () => {
14
- const [open, setOpen] = useState(false);
14
+ const [open, setOpen] = useState(true);
15
15
  const [viewMode, setViewMode] = useState<'sessions' | 'create' | 'messenger'>('sessions');
16
16
  const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
17
17
  const [HasInfo, setHasInfo] = useState(false);
@@ -129,11 +129,7 @@ const ProductCarousel: FC<Props> = ({
129
129
  };
130
130
 
131
131
  const isTryOnOpen = openTryOn === currentVariant.variant_id;
132
- const canTryOn =
133
- currentVariant.tag
134
- ? currentVariant.tag.toLowerCase() === "clothing"
135
- : true; // show by default unless you want to gate strictly by tag
136
-
132
+
137
133
  return (
138
134
  <article
139
135
  key={tag}
@@ -233,7 +229,7 @@ const ProductCarousel: FC<Props> = ({
233
229
  )}
234
230
 
235
231
  {/* Try On toggle button (kept) */}
236
- {canTryOn && (
232
+
237
233
  <Button
238
234
  style={{ backgroundColor: colorCode, color: "white" }}
239
235
  className="px-6 py-2 rounded-lg shadow-md transition"
@@ -243,11 +239,11 @@ const ProductCarousel: FC<Props> = ({
243
239
  >
244
240
  {isTryOnOpen ? "Close Try On" : "Try On"}
245
241
  </Button>
246
- )}
242
+
247
243
  </div>
248
244
 
249
245
  {/* Try On panel */}
250
- {canTryOn && isTryOnOpen && (
246
+ { isTryOnOpen && (
251
247
  <div className="mt-4">
252
248
  <TryOnBox productID={currentVariant.variant_id} productImage={currentVariant.image} />
253
249
  </div>
@@ -1,8 +1,10 @@
1
- import React from 'react';
1
+ import React,{useState} from 'react';
2
2
  import type { FC } from 'react';
3
3
  import 'keen-slider/keen-slider.min.css';
4
4
  import type { KeenSliderInstance } from 'keen-slider/react';
5
5
  import './SallaSwiper.css';
6
+ import { Button } from 'antd';
7
+ import TryOnBox from "./TryOn";
6
8
 
7
9
  interface ProductVariant {
8
10
  variant_id: string;
@@ -35,10 +37,10 @@ const SallaProductCarousel: FC<Props> = ({
35
37
  groupedProducts,
36
38
  selectedVariants,
37
39
  setSelectedVariants,
38
- counts,
39
- setCounts,
40
- handleAddToCart,
41
- handleRemoveFromCart,
40
+ // counts,
41
+ // setCounts,
42
+ // handleAddToCart,
43
+ // handleRemoveFromCart,
42
44
  colorCode = '#7F28F8',
43
45
  shortenName,
44
46
  sliderRef,
@@ -47,6 +49,7 @@ const SallaProductCarousel: FC<Props> = ({
47
49
  totalSlides,
48
50
  }) => {
49
51
  if (!totalSlides || totalSlides === 0) return null;
52
+ const [openTryOn, setOpenTryOn] = useState<string | null>(null);
50
53
 
51
54
  return (
52
55
  <section
@@ -69,7 +72,7 @@ const SallaProductCarousel: FC<Props> = ({
69
72
 
70
73
  if (!currentVariant) return null;
71
74
 
72
- const count = counts[currentVariant.variant_id] || 0;
75
+ // const count = counts[currentVariant.variant_id] || 0;
73
76
 
74
77
  // Build unique, cleaned list of options (exclude empty/'none')
75
78
  const options = Array.from(
@@ -91,27 +94,30 @@ const SallaProductCarousel: FC<Props> = ({
91
94
  }
92
95
  };
93
96
 
94
- const increment = async () => {
95
- setCounts(prev => ({
96
- ...prev,
97
- [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
98
- }));
99
- await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
100
- };
101
-
102
- const decrement = async () => {
103
- if (count === 0) return;
104
- setCounts(prev => ({
105
- ...prev,
106
- [currentVariant.variant_id]: Math.max(
107
- 0,
108
- (prev[currentVariant.variant_id] || 0) - 1
109
- ),
110
- }));
111
- await handleRemoveFromCart(currentVariant.variant_id, 1);
112
- };
97
+ // const increment = async () => {
98
+ // setCounts(prev => ({
99
+ // ...prev,
100
+ // [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
101
+ // }));
102
+ // await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
103
+ // };
104
+
105
+ // const decrement = async () => {
106
+ // if (count === 0) return;
107
+ // setCounts(prev => ({
108
+ // ...prev,
109
+ // [currentVariant.variant_id]: Math.max(
110
+ // 0,
111
+ // (prev[currentVariant.variant_id] || 0) - 1
112
+ // ),
113
+ // }));
114
+ // await handleRemoveFromCart(currentVariant.variant_id, 1);
115
+ // };
113
116
 
114
117
  // Ensure the select has a stable value (fallback to first option)
118
+
119
+ const isTryOnOpen = openTryOn === currentVariant.variant_id;
120
+
115
121
  const selectValue = (currentVariant.option ?? options[0] ?? '');
116
122
 
117
123
  return (
@@ -174,8 +180,17 @@ const SallaProductCarousel: FC<Props> = ({
174
180
  </div>
175
181
  )}
176
182
 
177
- <div className="sallaX-cta" aria-label="Quantity controls">
178
- <button
183
+
184
+ <Button
185
+ style={{ backgroundColor: colorCode, color: "white" }}
186
+ className="px-6 py-2 rounded-lg shadow-md transition"
187
+ onClick={() =>
188
+ setOpenTryOn(isTryOnOpen ? null : currentVariant.variant_id)
189
+ }
190
+ >
191
+ {isTryOnOpen ? "Close Try On" : "Try On"}
192
+ </Button>
193
+ {/* <button
179
194
  type="button"
180
195
  className="sallaX-qty sallaX-qty--minus"
181
196
  onClick={decrement}
@@ -194,8 +209,14 @@ const SallaProductCarousel: FC<Props> = ({
194
209
  aria-label="Increase quantity"
195
210
  >
196
211
  +
197
- </button>
198
- </div>
212
+ </button> */}
213
+
214
+ { isTryOnOpen && (
215
+ <div className="mt-4">
216
+ <TryOnBox productID={currentVariant.variant_id} productImage={currentVariant.image} />
217
+ </div>
218
+ )}
219
+
199
220
  </div>
200
221
  </article>
201
222
  );
@@ -1,8 +1,10 @@
1
- import React from 'react';
1
+ import React,{useState} from 'react';
2
2
  import type { FC } from 'react';
3
3
  import 'keen-slider/keen-slider.min.css';
4
4
  import type { KeenSliderInstance } from 'keen-slider/react';
5
+ import { Button } from 'antd';
5
6
  import './SallaSwiper.css';
7
+ import TryOnBox from "./TryOn";
6
8
 
7
9
  interface ProductVariant {
8
10
  variant_id: string;
@@ -31,14 +33,16 @@ interface Props {
31
33
  totalSlides: number;
32
34
  }
33
35
 
36
+
37
+
34
38
  const ZidProductCarousel: FC<Props> = ({
35
39
  groupedProducts,
36
40
  selectedVariants,
37
41
  setSelectedVariants,
38
- counts,
39
- setCounts,
40
- handleAddToCart,
41
- handleRemoveFromCart,
42
+ // counts,
43
+ // setCounts,
44
+ // handleAddToCart,
45
+ // handleRemoveFromCart,
42
46
  colorCode = '#7F28F8',
43
47
  shortenName,
44
48
  sliderRef,
@@ -46,6 +50,9 @@ const ZidProductCarousel: FC<Props> = ({
46
50
  // currentSlide, // currently unused
47
51
  totalSlides,
48
52
  }) => {
53
+
54
+ const [openTryOn, setOpenTryOn] = useState<string | null>(null);
55
+
49
56
  if (!totalSlides || totalSlides === 0) return null;
50
57
 
51
58
  return (
@@ -69,7 +76,7 @@ const ZidProductCarousel: FC<Props> = ({
69
76
 
70
77
  if (!currentVariant) return null;
71
78
 
72
- const count = counts[currentVariant.variant_id] || 0;
79
+ // const count = counts[currentVariant.variant_id] || 0;
73
80
 
74
81
  // Build unique, cleaned list of options (exclude empty/'none')
75
82
  const options = Array.from(
@@ -91,25 +98,27 @@ const ZidProductCarousel: FC<Props> = ({
91
98
  }
92
99
  };
93
100
 
94
- const increment = async () => {
95
- setCounts(prev => ({
96
- ...prev,
97
- [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
98
- }));
99
- await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
100
- };
101
-
102
- const decrement = async () => {
103
- if (count === 0) return;
104
- setCounts(prev => ({
105
- ...prev,
106
- [currentVariant.variant_id]: Math.max(
107
- 0,
108
- (prev[currentVariant.variant_id] || 0) - 1
109
- ),
110
- }));
111
- await handleRemoveFromCart(currentVariant.variant_id, 1);
112
- };
101
+ // const increment = async () => {
102
+ // setCounts(prev => ({
103
+ // ...prev,
104
+ // [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
105
+ // }));
106
+ // await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
107
+ // };
108
+
109
+ // const decrement = async () => {
110
+ // if (count === 0) return;
111
+ // setCounts(prev => ({
112
+ // ...prev,
113
+ // [currentVariant.variant_id]: Math.max(
114
+ // 0,
115
+ // (prev[currentVariant.variant_id] || 0) - 1
116
+ // ),
117
+ // }));
118
+ // await handleRemoveFromCart(currentVariant.variant_id, 1);
119
+ // };
120
+
121
+ const isTryOnOpen = openTryOn === currentVariant.variant_id;
113
122
 
114
123
  // Ensure the select has a stable value (fallback to first option)
115
124
  const selectValue = (currentVariant.option ?? options[0] ?? '');
@@ -174,7 +183,20 @@ const ZidProductCarousel: FC<Props> = ({
174
183
  </div>
175
184
  )}
176
185
 
177
- <div className="sallaX-cta" aria-label="Quantity controls">
186
+
187
+
188
+ <Button
189
+ style={{ backgroundColor: colorCode, color: "white" }}
190
+ className="px-6 py-2 rounded-lg shadow-md transition"
191
+ onClick={() =>
192
+ setOpenTryOn(isTryOnOpen ? null : currentVariant.variant_id)
193
+ }
194
+ >
195
+ {isTryOnOpen ? "Close Try On" : "Try On"}
196
+ </Button>
197
+
198
+
199
+ {/* <div className="sallaX-cta" aria-label="Quantity controls">
178
200
  <button
179
201
  type="button"
180
202
  className="sallaX-qty sallaX-qty--minus"
@@ -195,7 +217,13 @@ const ZidProductCarousel: FC<Props> = ({
195
217
  >
196
218
  +
197
219
  </button>
198
- </div>
220
+ </div> */}
221
+ { isTryOnOpen && (
222
+ <div className="mt-4">
223
+ <TryOnBox productID={currentVariant.variant_id} productImage={currentVariant.image} />
224
+ </div>
225
+ )}
226
+
199
227
  </div>
200
228
  </article>
201
229
  );
@@ -1,22 +1,50 @@
1
- import React, { useState } from 'react';
2
- import { Button, Spin } from 'antd';
3
- import { LoadingOutlined, CloudUploadOutlined } from '@ant-design/icons';
1
+ import React, { useState,useEffect } from 'react';
2
+ import { Button, Spin, Alert } from 'antd';
3
+ import { LoadingOutlined, CloudUploadOutlined, ReloadOutlined } from '@ant-design/icons';
4
4
  import config from '../../../../hooks/config';
5
5
 
6
6
  interface TryOnProps {
7
7
  productImage: string; // product variant image URL
8
- productID:string;
8
+ productID: string;
9
9
  }
10
10
 
11
- const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
11
+ const sweetMessages = [
12
+ "You look amazing in this! ✨",
13
+ "Wow, this really suits your style 💜",
14
+ "Looking sharp — that’s a vibe 😎",
15
+ "Perfect fit! This could be your new favorite 👌",
16
+ "You’re rocking this look 💯",
17
+ "That outfit has your name written all over it 💃",
18
+ "🔥 Stunning choice! You wear it so well.",
19
+ ];
20
+
21
+ const loadingMessages = [
22
+ "✨ Analyzing your style...",
23
+ "🧵 Matching the product perfectly...",
24
+ "🎨 Adjusting colors and lighting...",
25
+ "👗 Styling you up...",
26
+ "💎 Almost ready, polishing the details..."
27
+ ];
28
+
29
+
30
+ // cycle messages while loading
31
+
32
+
33
+
34
+ const TryOnBox: React.FC<TryOnProps> = ({ productImage, productID }) => {
12
35
  const [userImage, setUserImage] = useState<string | null>(null);
13
36
  const [resultImage, setResultImage] = useState<string | null>(null);
14
37
  const [loading, setLoading] = useState(false);
38
+ const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
39
+ const [sweetMessage, setSweetMessage] = useState<string | null>(null);
40
+ const [loadingIndex, setLoadingIndex] = useState(0);
15
41
 
16
- // Call your Django Ark API
42
+ // Call Django Ark API
17
43
  const sendToArk = async (file: File) => {
18
44
  setLoading(true);
19
45
  setResultImage(null);
46
+ setMessage(null);
47
+ setSweetMessage(null);
20
48
 
21
49
  const reader = new FileReader();
22
50
  reader.onload = async () => {
@@ -27,22 +55,30 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
27
55
  method: 'POST',
28
56
  headers: { 'Content-Type': 'application/json' },
29
57
  body: JSON.stringify({
30
- userImageUrl: base64UserImage, // can also be a hosted URL
31
- productImageUrl: productImage, // product image URL
32
- productId:productID,
33
- username:localStorage.getItem('company_username')
58
+ userImageUrl: base64UserImage,
59
+ productImageUrl: productImage,
60
+ productId: productID,
61
+ username: localStorage.getItem('company_username'),
34
62
  }),
35
63
  });
36
64
 
37
65
  const data = await response.json();
38
66
 
39
- if (data.resultImage) {
40
- setResultImage(data.resultImage); // base64 string or URL from backend
67
+ if (response.ok && data.resultImage) {
68
+ setResultImage(data.resultImage);
69
+
70
+ // Pick a random sweet message
71
+ const randomMsg = sweetMessages[Math.floor(Math.random() * sweetMessages.length)];
72
+ setSweetMessage(randomMsg);
41
73
  } else {
42
- console.error('Ark API error:', data);
74
+ setMessage({
75
+ type: 'error',
76
+ text: data.error || 'Something went wrong while generating the try-on.',
77
+ });
43
78
  }
44
79
  } catch (err) {
45
80
  console.error('Error calling Ark API:', err);
81
+ setMessage({ type: 'error', text: 'Failed to reach Try-On service. Please try again.' });
46
82
  } finally {
47
83
  setLoading(false);
48
84
  }
@@ -57,13 +93,39 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
57
93
  }
58
94
  };
59
95
 
96
+ useEffect(() => {
97
+ if (loading) {
98
+ setLoadingIndex(0);
99
+ const interval = setInterval(() => {
100
+ setLoadingIndex((prev) => {
101
+ if (prev < loadingMessages.length - 1) {
102
+ return prev + 1; // move forward
103
+ }
104
+ return prev; // stay at the last message
105
+ });
106
+ }, 1800); // change every 1.8s
107
+ return () => clearInterval(interval);
108
+ }
109
+ }, [loading]);
110
+
60
111
  return (
61
- <div className="mt-4 border rounded-lg p-4 bg-gray-50">
62
- {/* Upload prompt */}
112
+ <div className="mt-4 border rounded-xl p-6 bg-white shadow-sm">
113
+ {/* Messages */}
114
+ {message && (
115
+ <Alert
116
+ type={message.type}
117
+ message={message.text}
118
+ showIcon
119
+ closable
120
+ className="mb-4"
121
+ />
122
+ )}
123
+
124
+ {/* Upload Box */}
63
125
  {!userImage && !resultImage && !loading && (
64
- <label className="flex flex-col items-center justify-center border-2 border-dashed border-gray-400 rounded-lg w-full h-40 cursor-pointer hover:bg-gray-100 transition">
65
- <CloudUploadOutlined style={{ fontSize: 28, marginBottom: 8 }} />
66
- <span className="text-gray-500">Click or Drop your photo here</span>
126
+ <label className="flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl w-full h-44 cursor-pointer hover:border-purple-500 hover:bg-purple-50 transition">
127
+ <CloudUploadOutlined style={{ fontSize: 34, marginBottom: 8, color: '#7F28F8' }} />
128
+ <span className="text-gray-500 font-medium">Click to upload a picture</span>
67
129
  <input
68
130
  type="file"
69
131
  accept="image/*"
@@ -75,29 +137,37 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
75
137
 
76
138
  {/* Loader */}
77
139
  {loading && (
78
- <div className="flex flex-col items-center justify-center h-40">
79
- <Spin indicator={<LoadingOutlined style={{ fontSize: 32 }} spin />} />
80
- <p className="mt-2 text-gray-600">Generating your try-on...</p>
140
+ <div className="flex flex-col items-center justify-center h-44 text-center">
141
+ <Spin indicator={<LoadingOutlined style={{ fontSize: 36, color: '#7F28F8' }} spin />} />
142
+ <p className="mt-2 text-gray-700 font-medium">{loadingMessages[loadingIndex]}</p>
81
143
  </div>
82
144
  )}
83
145
 
146
+
147
+
84
148
  {/* Result Preview */}
85
149
  {!loading && resultImage && (
86
- <div className="flex flex-col items-center">
87
- <p className="mb-2 text-sm font-medium">✨ Try-On Result</p>
150
+ <div className="flex flex-col items-center text-center">
151
+ {/* <p className="mb-2 text-sm font-semibold text-gray-700">✨ Try-On Result</p> */}
88
152
  <img
89
153
  src={resultImage}
90
154
  alt="Result"
91
- className="max-h-64 object-contain rounded-md border"
155
+ className="max-h-72 object-contain rounded-lg border shadow-sm"
92
156
  />
93
157
 
158
+ {/* Sweet Message */}
159
+ {sweetMessage && (
160
+ <p className="mt-3 text-purple-600 font-medium italic">{sweetMessage}</p>
161
+ )}
162
+
94
163
  <Button
95
- style={{margin:'4px'}}
96
- type="default"
97
- className="mt-3"
164
+ icon={<ReloadOutlined />}
165
+ style={{ marginTop: '12px' }}
98
166
  onClick={() => {
99
167
  setUserImage(null);
100
168
  setResultImage(null);
169
+ setMessage(null);
170
+ setSweetMessage(null);
101
171
  }}
102
172
  >
103
173
  Try Another
@@ -26,6 +26,7 @@ interface Message {
26
26
  audio_url?:string;
27
27
  isConnect?:boolean;
28
28
  calendly?:boolean;
29
+ isBookings?:boolean;
29
30
  }
30
31
 
31
32
  interface ChatMessagesProps {
@@ -123,6 +124,20 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
123
124
  )
124
125
  }
125
126
 
127
+ if (msg.isBookings){
128
+ return (
129
+ <div
130
+ key={i}
131
+ style={{
132
+ borderRadius: 5,
133
+ color: "white",
134
+ width: "100%",
135
+ }}
136
+ >
137
+ <CalendlyEmbeds calendlyLink={calendly} />
138
+ </div>
139
+ );
140
+ }
126
141
 
127
142
 
128
143
 
@@ -147,20 +162,7 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
147
162
  );
148
163
  }
149
164
 
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
- }
165
+
164
166
 
165
167
 
166
168
 
@@ -83,23 +83,33 @@ export const useChatbotAPI = (params: Params) => {
83
83
  const data = await res.json();
84
84
 
85
85
  if (data.message_history) {
86
- const assistantMessage = data.isConnect
87
- ? {
88
- role: 'assistant',
89
- content: 'Your support request is on its way—please stand by!',
90
- isConnect: true,
91
- sender: 'Ai Agent',
92
- isBusiness: false,
93
- isHtml: data.isHtml,
94
- }
95
- : {
96
- role: 'assistant',
97
- content: data.message,
98
- sender: 'Ai Agent',
99
- isBusiness: false,
100
- isHtml: data.isHtml,
101
- };
102
86
 
87
+ const assistantMessage = data.isConnect
88
+ ? {
89
+ role: 'assistant',
90
+ content: data.message,
91
+ isConnect: true,
92
+ sender: 'Ai Agent',
93
+ isBusiness: false,
94
+ isHtml: data.isHtml,
95
+ }
96
+ : data.isBookings
97
+ ? {
98
+ role: 'assistant',
99
+ content: data.message,
100
+ isBookings: true,
101
+ sender: 'Ai Agent',
102
+ isBusiness: false,
103
+ isHtml: data.isHtml,
104
+ }
105
+ : {
106
+ role: 'assistant',
107
+ content: data.message,
108
+ sender: 'Ai Agent',
109
+ isBusiness: false,
110
+ isHtml: data.isHtml,
111
+ };
112
+
103
113
  // Send message through WebSocket if available
104
114
  if (socket?.readyState === WebSocket.OPEN) {
105
115
  socket.send(
@@ -121,7 +131,6 @@ export const useChatbotAPI = (params: Params) => {
121
131
  const filtered = prev.filter((msg) => !msg.typing);
122
132
  const updated = [...filtered, assistantMessage];
123
133
 
124
- console.log(calendlyLink)
125
134
  // Add Calendly message if applicable
126
135
  if (
127
136
  data.message &&
@@ -35,6 +35,7 @@ export interface ChatMessage {
35
35
  audio_url?: string;
36
36
  isConnect?:boolean;
37
37
  calendly?:boolean;
38
+ isBookings?:boolean;
38
39
 
39
40
 
40
41
  }