shopify-chatbot-widget 0.8.2 β†’ 0.8.4

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.2",
4
+ "version": "0.8.4",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -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,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
 
@@ -1,8 +1,8 @@
1
1
 
2
2
 
3
3
  const config = {
4
- apiUrl:'https://crmtest-3lct.onrender.com/api/',
5
- websocketUrl:'wss://crmtest-3lct.onrender.com/ws' ,
4
+ apiUrl:'https://jawebcrm.onrender.com/api/',
5
+ websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
6
6
 
7
7
  // apiUrl:'http://localhost:8000/api/',
8
8
  // websocketUrl:'ws://localhost:8000/ws',
@@ -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
  }