shopify-chatbot-widget 0.9.7 → 1.0.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.
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import { List, Avatar, Typography, Button, Spin } from 'antd';
3
- import { UserOutlined, LoadingOutlined,CloseOutlined,WhatsAppOutlined } from '@ant-design/icons';
2
+ import { List, Avatar, Typography, Spin } from 'antd';
3
+ import { UserOutlined, LoadingOutlined, CloseOutlined, WhatsAppOutlined, MessageOutlined } from '@ant-design/icons';
4
4
  import config from '../../../hooks/config';
5
5
 
6
6
  const { Text, Title } = Typography;
@@ -11,25 +11,32 @@ type ChatSession = {
11
11
  createdAt: string;
12
12
  isBlurred?: boolean;
13
13
  agentImage?: string;
14
-
15
14
  };
16
15
 
17
16
  type ChatSessionsProps = {
18
17
  setOpen: (v: boolean) => void;
19
18
  Opened: boolean;
20
- Phone:string;
19
+ Phone: string;
21
20
  chatbotLogo: string;
22
21
  colorCode?: string;
23
22
  onNewSession: () => void;
24
- visitorId: string;
25
- onSessionSelect: (sessionId: string,agentName:string,agentImage:string) => void;
26
-
23
+ visitorId: string;
24
+ onSessionSelect: (sessionId: string, agentName: string, agentImage: string) => void;
27
25
  };
28
26
 
29
- const ChatSessions: React.FC<ChatSessionsProps> = ({ chatbotLogo, colorCode, onNewSession, visitorId ,onSessionSelect, setOpen,
30
- Opened,Phone}) => {
27
+ const ChatSessions: React.FC<ChatSessionsProps> = ({
28
+ chatbotLogo,
29
+ colorCode,
30
+ onNewSession,
31
+ visitorId,
32
+ onSessionSelect,
33
+ setOpen,
34
+ Opened,
35
+ Phone,
36
+ }) => {
31
37
  const [sessions, setSessions] = useState<ChatSession[]>([]);
32
38
  const [loading, setLoading] = useState(true);
39
+ const [mounted, setMounted] = useState(false);
33
40
 
34
41
  const fetchSessions = async () => {
35
42
  try {
@@ -38,25 +45,23 @@ const ChatSessions: React.FC<ChatSessionsProps> = ({ chatbotLogo, colorCode, onN
38
45
  `${config.apiUrl}get-chatlogs-by-ip/?visitor_id=${visitorId}&company_username=${localStorage.getItem('company_username')}`
39
46
  );
40
47
  const data = await res.json();
41
-
48
+
42
49
  const mapped: ChatSession[] = data.map((item: any) => {
43
50
  const createdDate = new Date(item.date);
44
51
  const now = new Date();
45
- const diffDays = Math.floor(
46
- (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24)
47
- );
48
-
52
+ const diffDays = Math.floor((now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
53
+
49
54
  const isOlderThan5Days = diffDays >= 5;
50
-
55
+
51
56
  return {
52
57
  id: item.user_session_id,
53
58
  agentName: item.assignee || 'AI Agent',
54
59
  createdAt: item.date,
55
- isBlurred: item.closed || isOlderThan5Days, // ✅ auto-close if 3+ days old
60
+ isBlurred: item.closed || isOlderThan5Days,
56
61
  agentImage: item.assignee_picture,
57
62
  };
58
63
  });
59
-
64
+
60
65
  setSessions(mapped);
61
66
  } catch (err) {
62
67
  console.error('Failed to load sessions:', err);
@@ -64,149 +69,448 @@ const ChatSessions: React.FC<ChatSessionsProps> = ({ chatbotLogo, colorCode, onN
64
69
  setLoading(false);
65
70
  }
66
71
  };
72
+
67
73
  useEffect(() => {
68
74
  if (visitorId) {
69
75
  fetchSessions();
70
76
  }
77
+ setMounted(true);
71
78
  }, [visitorId]);
72
79
 
73
- const loadingIcon = <LoadingOutlined style={{ fontSize: 24, color: colorCode||'#7F28F8' }} spin />;
80
+ const loadingIcon = <LoadingOutlined style={{ fontSize: 24, color: colorCode || '#7F28F8' }} spin />;
81
+ const primaryColor = colorCode || '#7F28F8';
74
82
 
75
- // const canCreateNewSession =
76
- // sessions.length === 0 ||
77
- // sessions[sessions.length - 1].isBlurred === true;
83
+ return (
84
+ <>
85
+ <style>
86
+ {`
87
+ @keyframes slideInUp {
88
+ from {
89
+ opacity: 0;
90
+ transform: translateY(30px);
91
+ }
92
+ to {
93
+ opacity: 1;
94
+ transform: translateY(0);
95
+ }
96
+ }
78
97
 
98
+ @keyframes fadeIn {
99
+ from {
100
+ opacity: 0;
101
+ }
102
+ to {
103
+ opacity: 1;
104
+ }
105
+ }
79
106
 
80
- return (
81
- <div
82
- style={{
83
- maxWidth: '100%',
84
- height: '100%',
85
- border: '1px solid #f0f0f0',
86
- borderRadius: 8,
87
- overflow: 'hidden',
88
- display: 'flex',
89
- flexDirection: 'column',
90
- paddingTop: 20,
91
- }}
92
- >
93
- <div style={{ padding: '0 16px 10px', display:'flex' }}>
94
- <Title level={4} style={{ margin: 0 }}>
95
- Conversations
96
- </Title>
97
-
98
-
99
- <CloseOutlined
100
- onClick={() => setOpen(!Opened)}
101
- className="chatbot-close"
102
- style={{ marginLeft: 'auto' }}
103
- />
104
- </div>
107
+ @keyframes slideInRight {
108
+ from {
109
+ opacity: 0;
110
+ transform: translateX(-15px);
111
+ }
112
+ to {
113
+ opacity: 1;
114
+ transform: translateX(0);
115
+ }
116
+ }
105
117
 
118
+ @keyframes scaleIn {
119
+ from {
120
+ opacity: 0;
121
+ transform: scale(0.95);
122
+ }
123
+ to {
124
+ opacity: 1;
125
+ transform: scale(1);
126
+ }
127
+ }
106
128
 
107
- <div style={{ flex: 1, overflowY: 'auto' }}>
108
- {loading ? (
109
- <div style={{ textAlign: 'center', padding: '2rem' }}>
110
- <Spin indicator={loadingIcon} />
111
- </div>
112
- ) : sessions.length > 0 ? (
113
- <List
114
- itemLayout="horizontal"
115
- dataSource={sessions}
116
- split={true}
117
- renderItem={(session) => (
118
- <List.Item
119
- key={session.id}
120
- onClick={() => !session.isBlurred && onSessionSelect(session.id,session.agentName||'Ai Agent',session.agentImage||chatbotLogo)} // ✅ Click logic
121
- style={{
122
- padding: '12px',
123
- opacity: session.isBlurred ? 0.5 : 1,
124
- cursor: session.isBlurred ? 'not-allowed' : 'pointer',
125
- borderBottom: '1px solid #f0f0f0',
126
- }}
127
- >
128
- <List.Item.Meta
129
- avatar={
130
- <Avatar
131
- size="large"
132
- src={session.agentImage || chatbotLogo}
133
- icon={!session.agentImage && !chatbotLogo ? <UserOutlined /> : undefined}
134
- />
135
- }
136
- title={
137
- <span style={{ fontWeight: 600 }}>
138
- {session.agentName || 'AI Agent'}
139
- </span>
140
- }
141
- description={
142
- <>
143
- <Text type="secondary">
144
- Created: {new Date(session.createdAt).toLocaleDateString()}
145
- </Text>
146
- {session.isBlurred && (
147
- <div style={{ color: 'red', fontSize: '12px', marginTop: 4 }}>
148
- This session is closed and cannot be reopened.
149
- </div>
150
- )}
151
- </>
152
- }
153
- />
154
- </List.Item>
155
- )}
129
+ @keyframes spinnerPulse {
130
+ 0%, 100% {
131
+ opacity: 1;
132
+ transform: scale(1);
133
+ }
134
+ 50% {
135
+ opacity: 0.8;
136
+ transform: scale(1.05);
137
+ }
138
+ }
139
+
140
+ .chat-sessions-container {
141
+ animation: ${mounted ? 'slideInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1)' : 'none'};
142
+ }
143
+
144
+ .session-list-item {
145
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
146
+ position: relative;
147
+ overflow: hidden;
148
+ }
149
+
150
+ .session-list-item::before {
151
+ content: '';
152
+ position: absolute;
153
+ left: 0;
154
+ top: 0;
155
+ height: 100%;
156
+ width: 3px;
157
+ background: ${primaryColor};
158
+ transform: scaleY(0);
159
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
160
+ }
161
+
162
+ .session-list-item:hover:not(.disabled)::before {
163
+ transform: scaleY(1);
164
+ }
165
+
166
+ .session-list-item:hover:not(.disabled) {
167
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0.3) 100%);
168
+ backdropFilter: blur(10px);
169
+ transform: translateX(4px);
170
+ }
171
+
172
+ .session-list-item:active:not(.disabled) {
173
+ transform: translateX(2px) scale(0.99);
174
+ }
175
+
176
+ .session-list-item.stagger-1 { animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.05s both; }
177
+ .session-list-item.stagger-2 { animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both; }
178
+ .session-list-item.stagger-3 { animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both; }
179
+ .session-list-item.stagger-4 { animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.2s both; }
180
+ .session-list-item.stagger-5 { animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.25s both; }
181
+ .session-list-item.stagger-6 { animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both; }
182
+
183
+ .avatar-wrapper {
184
+ position: relative;
185
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
186
+ }
187
+
188
+ .session-list-item:hover:not(.disabled) .avatar-wrapper {
189
+ transform: scale(1.08);
190
+ }
191
+
192
+ .avatar-wrapper::after {
193
+ content: '';
194
+ position: absolute;
195
+ inset: -2px;
196
+ border-radius: 50%;
197
+ background: ${primaryColor};
198
+ opacity: 0;
199
+ filter: blur(8px);
200
+ transition: opacity 0.3s ease;
201
+ z-index: -1;
202
+ }
203
+
204
+ .session-list-item:hover:not(.disabled) .avatar-wrapper::after {
205
+ opacity: 0.2;
206
+ }
207
+
208
+ .session-title {
209
+ transition: color 0.3s ease;
210
+ }
211
+
212
+ .session-list-item:hover:not(.disabled) .session-title {
213
+ color: ${primaryColor};
214
+ }
215
+
216
+ .header-section {
217
+ animation: fadeIn 0.5s ease 0.2s both;
218
+ }
219
+
220
+ .close-icon {
221
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
222
+ cursor: pointer;
223
+ }
224
+
225
+ .close-icon:hover {
226
+ transform: rotate(90deg) scale(1.1);
227
+ color: ${primaryColor};
228
+ }
229
+
230
+ .close-icon:active {
231
+ transform: rotate(90deg) scale(0.95);
232
+ }
233
+
234
+ .action-button {
235
+ position: relative;
236
+ overflow: hidden;
237
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
238
+ }
239
+
240
+ .action-button::before {
241
+ content: '';
242
+ position: absolute;
243
+ top: 50%;
244
+ left: 50%;
245
+ width: 0;
246
+ height: 0;
247
+ border-radius: 50%;
248
+ background: rgba(255, 255, 255, 0.3);
249
+ transform: translate(-50%, -50%);
250
+ transition: width 0.6s ease, height 0.6s ease;
251
+ }
252
+
253
+ .action-button:active::before {
254
+ width: 300px;
255
+ height: 300px;
256
+ }
257
+
258
+ .action-button:hover {
259
+ transform: translateY(-2px);
260
+ }
261
+
262
+ .action-button:active {
263
+ transform: translateY(0);
264
+ }
265
+
266
+ .new-chat-button {
267
+ box-shadow: 0 4px 12px rgba(10, 10, 10, 0.25);
268
+ }
269
+
270
+ .new-chat-button:hover {
271
+ box-shadow: 0 6px 20px rgba(10, 10, 10, 0.35);
272
+ }
273
+
274
+ .whatsapp-button {
275
+ box-shadow: 0 4px 12px rgba(37, 211, 102, 0.3);
276
+ }
277
+
278
+ .whatsapp-button:hover {
279
+ box-shadow: 0 6px 20px rgba(37, 211, 102, 0.45);
280
+ transform: translateY(-2px) scale(1.03);
281
+ }
282
+
283
+ .button-icon {
284
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
285
+ }
286
+
287
+ .action-button:hover .button-icon {
288
+ transform: scale(1.1);
289
+ }
290
+
291
+ .loading-container {
292
+ animation: scaleIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
293
+ }
294
+
295
+ .loading-spinner {
296
+ animation: spinnerPulse 2s ease-in-out infinite;
297
+ }
298
+
299
+ .empty-state {
300
+ animation: fadeIn 0.5s ease 0.2s both;
301
+ }
302
+
303
+ .action-buttons-container {
304
+ animation: slideInUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both;
305
+ }
306
+
307
+ .disabled {
308
+ pointer-events: none;
309
+ }
310
+
311
+ .closed-badge {
312
+ animation: fadeIn 0.4s ease 0.1s both;
313
+ }
314
+
315
+ /* Smooth scrollbar */
316
+ .sessions-scroll::-webkit-scrollbar {
317
+ width: 6px;
318
+ }
319
+
320
+ .sessions-scroll::-webkit-scrollbar-track {
321
+ background: transparent;
322
+ }
323
+
324
+ .sessions-scroll::-webkit-scrollbar-thumb {
325
+ background: ${primaryColor}40;
326
+ border-radius: 3px;
327
+ transition: background 0.3s ease;
328
+ }
329
+
330
+ .sessions-scroll::-webkit-scrollbar-thumb:hover {
331
+ background: ${primaryColor}60;
332
+ }
333
+ `}
334
+ </style>
335
+
336
+ <div
337
+ className="chat-sessions-container"
338
+ style={{
339
+ maxWidth: '100%',
340
+ height: '100%',
341
+ background: 'rgba(255, 255, 255, 0.7)',
342
+ backdropFilter: 'blur(20px) saturate(180%)',
343
+ WebkitBackdropFilter: 'blur(20px) saturate(180%)',
344
+ border: '1px solid rgba(255, 255, 255, 0.18)',
345
+ borderRadius: 8,
346
+ overflow: 'hidden',
347
+ display: 'flex',
348
+ flexDirection: 'column',
349
+ paddingTop: 20,
350
+ boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.1)',
351
+ }}
352
+ >
353
+ <div className="header-section" style={{
354
+ padding: '0 16px 10px',
355
+ display: 'flex',
356
+ background: 'rgba(255, 255, 255, 0.4)',
357
+ backdropFilter: 'blur(10px)',
358
+ WebkitBackdropFilter: 'blur(10px)',
359
+ borderBottom: '1px solid rgba(255, 255, 255, 0.18)',
360
+ marginBottom: '8px',
361
+ }}>
362
+ <Title level={4} style={{ margin: 0 }}>
363
+ Conversations
364
+ </Title>
365
+
366
+ <CloseOutlined
367
+ onClick={() => setOpen(!Opened)}
368
+ className="close-icon"
369
+ style={{ marginLeft: 'auto', fontSize: 16 }}
156
370
  />
157
-
158
- ) : (
159
- <div style={{ padding: '1rem', textAlign: 'center', color: '#888' }}>
160
- No conversations yet.
161
- </div>
162
- )}
163
- </div>
371
+ </div>
164
372
 
165
-
166
- <div style={{ padding: '12px', borderTop: '1px solid #f0f0f0',display:'flex',gap:4 }}>
167
- <Button
168
- type="primary"
169
- size="large"
170
- block
171
- style={{
172
- backgroundColor: colorCode,
173
- color: '#fff',
174
- }}
175
- onClick={onNewSession}
176
- >
177
- Quick Question
178
- </Button>
373
+ <div className="sessions-scroll" style={{ flex: 1, overflowY: 'auto' }}>
374
+ {loading ? (
375
+ <div className="loading-container" style={{
376
+ textAlign: 'center',
377
+ padding: '2rem',
378
+ background: 'rgba(255, 255, 255, 0.3)',
379
+ backdropFilter: 'blur(10px)',
380
+ WebkitBackdropFilter: 'blur(10px)',
381
+ borderRadius: '12px',
382
+ margin: '1rem',
383
+ }}>
384
+ <Spin indicator={<div className="loading-spinner">{loadingIcon}</div>} />
385
+ </div>
386
+ ) : sessions.length > 0 ? (
387
+ <List
388
+ itemLayout="horizontal"
389
+ dataSource={sessions}
390
+ split={true}
391
+ renderItem={(session, index) => (
392
+ <List.Item
393
+ key={session.id}
394
+ className={`session-list-item ${session.isBlurred ? 'disabled' : ''} stagger-${Math.min(index + 1, 6)}`}
395
+ onClick={() =>
396
+ !session.isBlurred &&
397
+ onSessionSelect(session.id, session.agentName || 'Ai Agent', session.agentImage || chatbotLogo)
398
+ }
399
+ style={{
400
+ padding: '12px',
401
+ opacity: session.isBlurred ? 0.5 : 1,
402
+ cursor: session.isBlurred ? 'not-allowed' : 'pointer',
403
+ borderBottom: '1px solid #f0f0f0',
404
+ }}
405
+ >
406
+ <List.Item.Meta
407
+ avatar={
408
+ <div className="avatar-wrapper">
409
+ <Avatar
410
+ size="large"
411
+ src={session.agentImage || chatbotLogo}
412
+ icon={!session.agentImage && !chatbotLogo ? <UserOutlined /> : undefined}
413
+ />
414
+ </div>
415
+ }
416
+ title={
417
+ <span className="session-title" style={{ fontWeight: 600 }}>
418
+ {session.agentName || 'AI Agent'}
419
+ </span>
420
+ }
421
+ description={
422
+ <>
423
+ <Text type="secondary">Created: {new Date(session.createdAt).toLocaleDateString()}</Text>
424
+ {session.isBlurred && (
425
+ <div className="closed-badge" style={{ color: '#ff4d4f', fontSize: '12px', marginTop: 4 }}>
426
+ This session is closed and cannot be reopened.
427
+ </div>
428
+ )}
429
+ </>
430
+ }
431
+ />
432
+ </List.Item>
433
+ )}
434
+ />
435
+ ) : (
436
+ <div className="empty-state" style={{
437
+ padding: '2rem 1rem',
438
+ textAlign: 'center',
439
+ color: '#666',
440
+ background: 'rgba(255, 255, 255, 0.3)',
441
+ backdropFilter: 'blur(10px)',
442
+ WebkitBackdropFilter: 'blur(10px)',
443
+ borderRadius: '12px',
444
+ margin: '1rem',
445
+ }}>
446
+ No conversations yet.
447
+ </div>
448
+ )}
449
+ </div>
179
450
 
180
- {Phone?
181
- (
182
- <Button
183
- type="default"
451
+ <div
452
+ className="action-buttons-container"
184
453
  style={{
185
- backgroundColor: '#25D366',
186
- color: 'white',
187
- }}
188
- className='hover:border-black'
189
- size="large"
190
- icon={<WhatsAppOutlined style={{color:'white'}}/>}
191
- onClick={() => {
192
- const waLink = `https://wa.me/${Phone}`;
193
- window.open(waLink, '_blank');
454
+ padding: '16px',
455
+ borderTop: '1px solid rgba(255, 255, 255, 0.18)',
456
+ display: 'flex',
457
+ gap: 12,
458
+ background: 'rgba(255, 255, 255, 0.5)',
459
+ backdropFilter: 'blur(20px) saturate(180%)',
460
+ WebkitBackdropFilter: 'blur(20px) saturate(180%)',
194
461
  }}
195
462
  >
196
- Chat on WhatsApp
197
- </Button>
198
-
199
- ):(
200
- <></>
201
- )
202
- }
203
-
463
+ <button
464
+ onClick={onNewSession}
465
+ className="action-button new-chat-button"
466
+ style={{
467
+ flex: 1,
468
+ height: '56px',
469
+ border: 'none',
470
+ borderRadius: '16px',
471
+ background: 'linear-gradient(135deg, #0A0A0A, #0A0A0Add)',
472
+ color: 'white',
473
+ fontSize: '20px',
474
+ cursor: 'pointer',
475
+ display: 'flex',
476
+ alignItems: 'center',
477
+ justifyContent: 'center',
478
+ gap: '8px',
479
+ }}
480
+ >
481
+ <MessageOutlined className="button-icon" style={{ fontSize: '22px' }} />
482
+ <span style={{ fontSize: '15px', fontWeight: 600 }}>New Chat</span>
483
+ </button>
204
484
 
485
+ {Phone && (
486
+ <button
487
+ onClick={() => {
488
+ const waLink = `https://wa.me/${Phone}`;
489
+ window.open(waLink, '_blank');
490
+ }}
491
+ className="action-button whatsapp-button"
492
+ style={{
493
+ width: '56px',
494
+ height: '56px',
495
+ border: 'none',
496
+ borderRadius: '16px',
497
+ background: 'linear-gradient(135deg, #25D366, #20BA5A)',
498
+ color: 'white',
499
+ fontSize: '24px',
500
+ cursor: 'pointer',
501
+ display: 'flex',
502
+ alignItems: 'center',
503
+ justifyContent: 'center',
504
+ flexShrink: 0,
505
+ }}
506
+ >
507
+ <WhatsAppOutlined className="button-icon" />
508
+ </button>
509
+ )}
510
+ </div>
205
511
  </div>
206
-
207
-
208
- </div>
512
+ </>
209
513
  );
210
514
  };
211
515
 
212
- export default ChatSessions;
516
+ export default ChatSessions;
@@ -1,11 +1,11 @@
1
1
 
2
2
 
3
3
  const config = {
4
- apiUrl:'https://jawebcrm.onrender.com/api/',
5
- websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
4
+ // apiUrl:'https://jawebcrm.onrender.com/api/',
5
+ // websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
6
6
 
7
- // apiUrl:'http://localhost:8000/api/',
8
- // websocketUrl:'ws://localhost:8000/ws',
7
+ apiUrl:'http://localhost:8000/api/',
8
+ websocketUrl:'ws://localhost:8000/ws',
9
9
 
10
10
  };
11
11
  export default config;