@product7/feedback-sdk 1.3.7 → 1.3.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.
Files changed (39) hide show
  1. package/dist/feedback-sdk.js +3006 -2817
  2. package/dist/feedback-sdk.js.map +1 -1
  3. package/dist/feedback-sdk.min.js +1 -1
  4. package/dist/feedback-sdk.min.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/api/services/MessengerService.js +8 -2
  7. package/src/core/APIService.js +33 -14
  8. package/src/index.js +1 -1
  9. package/src/styles/base.js +1 -1
  10. package/src/styles/changelog.js +58 -40
  11. package/src/styles/components.js +19 -2
  12. package/src/styles/design-tokens.js +4 -4
  13. package/src/styles/feedback.js +3 -8
  14. package/src/styles/messenger-components.js +473 -0
  15. package/src/styles/messenger-core.js +37 -268
  16. package/src/styles/messenger-features.js +89 -267
  17. package/src/styles/messenger-views.js +391 -325
  18. package/src/styles/messenger.js +17 -558
  19. package/src/styles/styles.js +21 -24
  20. package/src/styles/{surveys.js → survey.js} +55 -20
  21. package/src/widgets/BaseWidget.js +1 -1
  22. package/src/widgets/ButtonWidget.js +1 -1
  23. package/src/widgets/ChangelogWidget.js +1 -1
  24. package/src/widgets/InlineWidget.js +1 -1
  25. package/src/widgets/MessengerWidget.js +74 -84
  26. package/src/widgets/SurveyWidget.js +1 -1
  27. package/src/widgets/TabWidget.js +1 -1
  28. package/src/widgets/messenger/MessengerState.js +50 -119
  29. package/src/widgets/messenger/components/MessengerLauncher.js +22 -18
  30. package/src/widgets/messenger/components/MessengerPanel.js +1 -1
  31. package/src/widgets/messenger/components/NavigationTabs.js +36 -15
  32. package/src/widgets/messenger/views/ChangelogView.js +8 -32
  33. package/src/widgets/messenger/views/ChatView.js +83 -219
  34. package/src/widgets/messenger/views/ConversationsView.js +67 -45
  35. package/src/widgets/messenger/views/HelpView.js +22 -32
  36. package/src/widgets/messenger/views/HomeView.js +58 -40
  37. package/src/widgets/messenger/views/PreChatFormView.js +47 -51
  38. package/src/styles/messenger-help.js +0 -298
  39. package/src/styles/messenger-themes.js +0 -500
@@ -8,8 +8,7 @@ export class ChatView {
8
8
  this._isTyping = false;
9
9
  this._typingIndicator = null;
10
10
  this._isConversationClosed = false;
11
- this._showEmailOverlayFlag = false;
12
- this._pendingAttachments = [];
11
+ this._pendingAttachments = [];
13
12
  }
14
13
 
15
14
  render() {
@@ -41,7 +40,10 @@ export class ChatView {
41
40
  data.conversationId === this.state.activeConversationId
42
41
  ) {
43
42
  this._updateContent();
44
- } else if (type === 'messagesUpdate' && data.conversationId === this.state.activeConversationId) {
43
+ } else if (
44
+ type === 'messagesUpdate' &&
45
+ data.conversationId === this.state.activeConversationId
46
+ ) {
45
47
  this._updateContent();
46
48
  }
47
49
  });
@@ -61,7 +63,6 @@ export class ChatView {
61
63
  ? this._renderEmptyState(isNewConversation)
62
64
  : messages.map((msg) => this._renderMessage(msg)).join('');
63
65
 
64
- const avatarHtml = this._renderConversationAvatar(conversation);
65
66
  const title = isNewConversation
66
67
  ? 'New conversation'
67
68
  : conversation?.title || 'Chat with team';
@@ -71,8 +72,6 @@ export class ChatView {
71
72
  ? 'Conversation closed'
72
73
  : 'Write a message...';
73
74
 
74
- const existingName = this.state.userContext?.name || '';
75
-
76
75
  this.element.innerHTML = `
77
76
  <div class="messenger-chat-header">
78
77
  <button class="sdk-btn-icon messenger-back-btn" aria-label="Back">
@@ -81,7 +80,6 @@ export class ChatView {
81
80
  </svg>
82
81
  </button>
83
82
  <div class="messenger-chat-header-info">
84
- ${avatarHtml}
85
83
  <span class="messenger-chat-title">${title}</span>
86
84
  </div>
87
85
  <button class="sdk-close-btn" aria-label="Close">
@@ -93,12 +91,16 @@ export class ChatView {
93
91
 
94
92
  <div class="messenger-chat-messages">
95
93
  ${messagesHtml}
96
- ${isClosed ? `
94
+ ${
95
+ isClosed
96
+ ? `
97
97
  <div class="messenger-closed-banner">
98
98
  <i class="ph ph-check-circle"></i>
99
99
  <span>This conversation has been resolved</span>
100
100
  </div>
101
- ` : ''}
101
+ `
102
+ : ''
103
+ }
102
104
  <div class="messenger-typing-indicator">
103
105
  <div class="messenger-typing-dots">
104
106
  <span></span><span></span><span></span>
@@ -107,7 +109,10 @@ export class ChatView {
107
109
  </div>
108
110
  </div>
109
111
 
110
- ${isClosed ? '' : `
112
+ ${
113
+ isClosed
114
+ ? ''
115
+ : `
111
116
  <div class="messenger-compose-attachments-preview"></div>
112
117
 
113
118
  <div class="messenger-chat-compose">
@@ -124,28 +129,8 @@ export class ChatView {
124
129
  </button>
125
130
  <input type="file" class="messenger-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
126
131
  </div>
127
- `}
128
-
129
- <div class="messenger-email-overlay">
130
- <div class="sdk-card messenger-email-card">
131
- <div class="sdk-card-header">
132
- <h4>What is your email address?</h4>
133
- <p>Enter your email to know when we reply:</p>
134
- </div>
135
- <div class="sdk-card-body">
136
- <div class="sdk-form-group">
137
- <input type="text" class="sdk-input messenger-email-name" placeholder="Name (optional)" value="${this._escapeHtml(existingName)}" autocomplete="name" />
138
- </div>
139
- <div class="sdk-form-group">
140
- <input type="email" class="sdk-input messenger-email-input" placeholder="Enter your email address..." autocomplete="email" />
141
- </div>
142
- </div>
143
- <div class="sdk-card-footer messenger-email-actions">
144
- <button class="sdk-btn sdk-btn-primary sdk-btn-block messenger-email-submit" disabled>Set my email</button>
145
- <button class="sdk-btn sdk-btn-secondary sdk-btn-block messenger-email-skip">Skip</button>
146
- </div>
147
- </div>
148
- </div>
132
+ `
133
+ }
149
134
  `;
150
135
 
151
136
  this._typingIndicator = this.element.querySelector(
@@ -154,11 +139,6 @@ export class ChatView {
154
139
  this._attachEvents();
155
140
  this._scrollToBottom();
156
141
  this._renderAttachmentPreviews();
157
-
158
- // Show email overlay after first message sent without email
159
- if (this._showEmailOverlayFlag) {
160
- this._showEmailOverlay();
161
- }
162
142
  }
163
143
 
164
144
  _renderEmptyState(isNewConversation = false) {
@@ -182,16 +162,18 @@ export class ChatView {
182
162
 
183
163
  _renderMessageAttachments(attachments) {
184
164
  if (!attachments || attachments.length === 0) return '';
185
- return attachments.map((att) => {
186
- if (att.type === 'image') {
187
- return `<img class="messenger-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
188
- }
189
- return `<a class="messenger-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
165
+ return attachments
166
+ .map((att) => {
167
+ if (att.type === 'image') {
168
+ return `<img class="messenger-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
169
+ }
170
+ return `<a class="messenger-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
190
171
  <i class="ph ph-file"></i>
191
172
  <span>${this._escapeHtml(att.name || 'file')}</span>
192
173
  <i class="ph ph-download-simple messenger-file-download-icon"></i>
193
174
  </a>`;
194
- }).join('');
175
+ })
176
+ .join('');
195
177
  }
196
178
 
197
179
  _renderMessage(message) {
@@ -202,9 +184,12 @@ export class ChatView {
202
184
  const timeStr = this._formatMessageTime(message.timestamp);
203
185
  const attachmentsHtml = this._renderMessageAttachments(message.attachments);
204
186
 
205
- const contentHtml = message.content ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>` : '';
206
-
207
- const bubbleHtml = contentHtml ? `<div class="messenger-message-bubble">${contentHtml}</div>` : '';
187
+ const contentHtml = message.content
188
+ ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>`
189
+ : '';
190
+ const bubbleHtml = contentHtml
191
+ ? `<div class="messenger-message-bubble">${contentHtml}</div>`
192
+ : '';
208
193
 
209
194
  if (isOwn) {
210
195
  return `
@@ -219,13 +204,15 @@ export class ChatView {
219
204
  const avatarHtml = this._renderSenderAvatar(message.sender);
220
205
  return `
221
206
  <div class="messenger-message ${messageClass}">
222
- <div class="messenger-message-avatar">${avatarHtml}</div>
223
- <div class="messenger-message-wrapper">
224
- <div class="messenger-message-sender">${message.sender?.name || 'Support'}</div>
225
- ${bubbleHtml}
226
- ${attachmentsHtml}
227
- <div class="messenger-message-time">${timeStr}</div>
207
+ <div class="messenger-message-sender">${message.sender?.name || 'Support'}</div>
208
+ <div class="messenger-message-row">
209
+ <div class="messenger-message-avatar">${avatarHtml}</div>
210
+ <div class="messenger-message-wrapper">
211
+ ${bubbleHtml}
212
+ ${attachmentsHtml}
213
+ </div>
228
214
  </div>
215
+ <div class="messenger-message-time">${timeStr}</div>
229
216
  </div>
230
217
  `;
231
218
  }
@@ -238,17 +225,6 @@ export class ChatView {
238
225
  return `<div class="sdk-avatar sdk-avatar-sm">${initial}</div>`;
239
226
  }
240
227
 
241
- _renderConversationAvatar(conversation) {
242
- if (!conversation?.participants?.length) {
243
- return `<div class="sdk-avatar sdk-avatar-sm">S</div>`;
244
- }
245
- const p = conversation.participants[0];
246
- if (p.avatarUrl) {
247
- return `<div class="sdk-avatar sdk-avatar-sm"><img src="${p.avatarUrl}" alt="${p.name}" /></div>`;
248
- }
249
- return `<div class="sdk-avatar sdk-avatar-sm">${(p.name || 'S').charAt(0).toUpperCase()}</div>`;
250
- }
251
-
252
228
  _renderTeamAvatars() {
253
229
  const avatars = this.state.teamAvatars;
254
230
  if (!avatars || avatars.length === 0) {
@@ -322,12 +298,15 @@ export class ChatView {
322
298
  const input = this.element.querySelector('.messenger-compose-input');
323
299
  const sendBtn = this.element.querySelector('.messenger-compose-send');
324
300
  if (input && sendBtn) {
325
- sendBtn.disabled = !input.value.trim() && this._pendingAttachments.length === 0;
301
+ sendBtn.disabled =
302
+ !input.value.trim() && this._pendingAttachments.length === 0;
326
303
  }
327
304
  }
328
305
 
329
306
  _renderAttachmentPreviews() {
330
- const container = this.element.querySelector('.messenger-compose-attachments-preview');
307
+ const container = this.element.querySelector(
308
+ '.messenger-compose-attachments-preview'
309
+ );
331
310
  if (!container) return;
332
311
 
333
312
  if (this._pendingAttachments.length === 0) {
@@ -337,28 +316,31 @@ export class ChatView {
337
316
  }
338
317
 
339
318
  container.style.display = 'flex';
340
- container.innerHTML = this._pendingAttachments.map((att, i) => {
341
- const isImage = att.type.startsWith('image');
342
- const thumb = isImage
343
- ? `<img class="messenger-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
344
- : `<div class="messenger-attachment-thumb messenger-attachment-file-icon"><i class="ph ph-file"></i></div>`;
345
- return `
319
+ container.innerHTML = this._pendingAttachments
320
+ .map((att, i) => {
321
+ const isImage = att.type.startsWith('image');
322
+ const thumb = isImage
323
+ ? `<img class="messenger-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
324
+ : `<div class="messenger-attachment-thumb messenger-attachment-file-icon"><i class="ph ph-file"></i></div>`;
325
+ return `
346
326
  <div class="messenger-attachment-preview" data-index="${i}">
347
327
  ${thumb}
348
328
  <button class="messenger-attachment-remove" data-index="${i}" aria-label="Remove">&times;</button>
349
329
  </div>
350
330
  `;
351
- }).join('');
352
-
353
- // Attach remove button events
354
- container.querySelectorAll('.messenger-attachment-remove').forEach((btn) => {
355
- btn.addEventListener('click', (e) => {
356
- const idx = parseInt(e.currentTarget.dataset.index, 10);
357
- this._pendingAttachments.splice(idx, 1);
358
- this._renderAttachmentPreviews();
359
- this._updateSendButtonState();
331
+ })
332
+ .join('');
333
+
334
+ container
335
+ .querySelectorAll('.messenger-attachment-remove')
336
+ .forEach((btn) => {
337
+ btn.addEventListener('click', (e) => {
338
+ const idx = parseInt(e.currentTarget.dataset.index, 10);
339
+ this._pendingAttachments.splice(idx, 1);
340
+ this._renderAttachmentPreviews();
341
+ this._updateSendButtonState();
342
+ });
360
343
  });
361
- });
362
344
  }
363
345
 
364
346
  _attachEvents() {
@@ -374,20 +356,15 @@ export class ChatView {
374
356
  this.state.setOpen(false);
375
357
  });
376
358
 
377
- // Compose input (not rendered when conversation is closed)
378
359
  const input = this.element.querySelector('.messenger-compose-input');
379
360
  const sendBtn = this.element.querySelector('.messenger-compose-send');
380
361
 
381
362
  if (input && sendBtn) {
382
363
  input.addEventListener('input', () => {
383
- // Auto-resize textarea
384
364
  input.style.height = 'auto';
385
365
  input.style.height = Math.min(input.scrollHeight, 120) + 'px';
386
-
387
- // Enable/disable send button
388
366
  this._updateSendButtonState();
389
367
 
390
- // Send typing indicator
391
368
  if (input.value.trim()) {
392
369
  this._startTyping();
393
370
  }
@@ -405,9 +382,10 @@ export class ChatView {
405
382
  });
406
383
  }
407
384
 
408
- // Attach button + file input
409
385
  const attachBtn = this.element.querySelector('.messenger-compose-attach');
410
- const fileInput = this.element.querySelector('.messenger-compose-file-input');
386
+ const fileInput = this.element.querySelector(
387
+ '.messenger-compose-file-input'
388
+ );
411
389
 
412
390
  if (attachBtn && fileInput) {
413
391
  attachBtn.addEventListener('click', () => {
@@ -434,42 +412,11 @@ export class ChatView {
434
412
  });
435
413
  }
436
414
 
437
- // Email overlay events
438
- const emailInput = this.element.querySelector('.messenger-email-input');
439
- const emailSubmit = this.element.querySelector('.messenger-email-submit');
440
- const emailSkip = this.element.querySelector('.messenger-email-skip');
441
-
442
- if (emailInput) {
443
- emailInput.addEventListener('input', () => {
444
- const isValid = this._isValidEmail(emailInput.value.trim());
445
- emailSubmit.disabled = !isValid;
446
- });
447
-
448
- emailInput.addEventListener('keydown', (e) => {
449
- if (e.key === 'Enter' && !emailSubmit.disabled) {
450
- e.preventDefault();
451
- this._handleEmailSubmit();
452
- }
453
- });
454
- }
455
-
456
- if (emailSubmit) {
457
- emailSubmit.addEventListener('click', () => {
458
- this._handleEmailSubmit();
459
- });
460
- }
461
-
462
- if (emailSkip) {
463
- emailSkip.addEventListener('click', () => {
464
- this._hideEmailOverlay();
465
- });
466
- }
467
-
468
- // Delegated events for attachment clicks
469
- const messagesContainer = this.element.querySelector('.messenger-chat-messages');
415
+ const messagesContainer = this.element.querySelector(
416
+ '.messenger-chat-messages'
417
+ );
470
418
  if (messagesContainer) {
471
419
  messagesContainer.addEventListener('click', (e) => {
472
- // File click -> download
473
420
  const fileLink = e.target.closest('.messenger-message-file');
474
421
  if (fileLink) {
475
422
  e.preventDefault();
@@ -479,7 +426,6 @@ export class ChatView {
479
426
  return;
480
427
  }
481
428
 
482
- // Image click -> open in new tab
483
429
  const img = e.target.closest('.messenger-message-image');
484
430
  if (img) {
485
431
  const url = img.dataset.url || img.src;
@@ -503,82 +449,22 @@ export class ChatView {
503
449
  document.body.removeChild(a);
504
450
  URL.revokeObjectURL(blobUrl);
505
451
  } catch {
506
- // Fallback: open in new tab
507
452
  window.open(url, '_blank');
508
453
  }
509
454
  }
510
455
 
511
456
  _escapeHtml(text) {
512
457
  if (!text) return '';
513
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
514
- }
515
-
516
- _isValidEmail(email) {
517
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
518
- }
519
-
520
- _showEmailOverlay() {
521
- const overlay = this.element.querySelector('.messenger-email-overlay');
522
- if (overlay) {
523
- overlay.style.display = 'flex';
524
- const emailInput = overlay.querySelector('.messenger-email-input');
525
- if (emailInput) {
526
- setTimeout(() => emailInput.focus(), 100);
527
- }
528
- }
529
- }
530
-
531
- _startPendingConversation() {
532
- if (this._pendingMessage && this.options.onStartConversation) {
533
- this.options.onStartConversation(this._pendingMessage, this._pendingAttachmentsForSend || []);
534
- this._pendingMessage = null;
535
- this._pendingAttachmentsForSend = null;
536
- }
537
- }
538
-
539
- _hideEmailOverlay() {
540
- this._showEmailOverlayFlag = false;
541
- const overlay = this.element.querySelector('.messenger-email-overlay');
542
- if (overlay) {
543
- overlay.style.display = 'none';
544
- }
545
- }
546
-
547
- async _handleEmailSubmit() {
548
- const nameInput = this.element.querySelector('.messenger-email-name');
549
- const emailInput = this.element.querySelector('.messenger-email-input');
550
- const submitBtn = this.element.querySelector('.messenger-email-submit');
551
-
552
- const name = nameInput?.value.trim() || '';
553
- const email = emailInput?.value.trim();
554
-
555
- if (!email || !this._isValidEmail(email)) return;
556
-
557
- submitBtn.disabled = true;
558
- submitBtn.textContent = 'Saving...';
559
-
560
- try {
561
- if (this.options.onIdentifyContact) {
562
- await this.options.onIdentifyContact({ name, email });
563
- }
564
-
565
- if (!this.state.userContext) {
566
- this.state.userContext = {};
567
- }
568
- this.state.userContext.name = name;
569
- this.state.userContext.email = email;
570
-
571
- this._hideEmailOverlay();
572
- this._startPendingConversation();
573
- } catch (error) {
574
- console.error('[ChatView] Failed to save email:', error);
575
- submitBtn.disabled = false;
576
- submitBtn.textContent = 'Set my email';
577
- }
458
+ return text
459
+ .replace(/&/g, '&amp;')
460
+ .replace(/</g, '&lt;')
461
+ .replace(/>/g, '&gt;')
462
+ .replace(/"/g, '&quot;');
578
463
  }
579
464
 
580
465
  async _sendMessage() {
581
466
  if (this._isConversationClosed) return;
467
+
582
468
  const input = this.element.querySelector('.messenger-compose-input');
583
469
  const content = input.value.trim();
584
470
  const hasAttachments = this._pendingAttachments.length > 0;
@@ -587,37 +473,12 @@ export class ChatView {
587
473
 
588
474
  this._stopTyping();
589
475
 
590
- // Collect attachments to upload
591
476
  const attachmentsToSend = [...this._pendingAttachments];
592
-
593
477
  const isNewConversation = !this.state.activeConversationId;
594
- const needsContactInfo = !this.state.userContext?.email;
595
478
 
596
479
  if (isNewConversation) {
597
- // Show user's message in chat immediately
598
- const localMessage = {
599
- id: 'msg_' + Date.now(),
600
- content: content,
601
- isOwn: true,
602
- timestamp: new Date().toISOString(),
603
- attachments: attachmentsToSend.map((a) => ({
604
- url: a.preview,
605
- type: a.type.startsWith('image') ? 'image' : 'file',
606
- name: a.file.name,
607
- })),
608
- };
609
- this._appendMessage(localMessage);
610
- this._scrollToBottom();
611
-
612
- if (needsContactInfo) {
613
- this._pendingMessage = content;
614
- this._pendingAttachmentsForSend = attachmentsToSend;
615
- this._showEmailOverlayFlag = true;
616
- setTimeout(() => this._showEmailOverlay(), 300);
617
- } else {
618
- if (this.options.onStartConversation) {
619
- this.options.onStartConversation(content, attachmentsToSend);
620
- }
480
+ if (this.options.onStartConversation) {
481
+ this.options.onStartConversation(content, attachmentsToSend);
621
482
  }
622
483
  } else {
623
484
  const message = {
@@ -635,11 +496,14 @@ export class ChatView {
635
496
  this.state.addMessage(this.state.activeConversationId, message);
636
497
 
637
498
  if (this.options.onSendMessage) {
638
- this.options.onSendMessage(this.state.activeConversationId, message, attachmentsToSend);
499
+ this.options.onSendMessage(
500
+ this.state.activeConversationId,
501
+ message,
502
+ attachmentsToSend
503
+ );
639
504
  }
640
505
  }
641
506
 
642
- // Clear input and attachments
643
507
  input.value = '';
644
508
  input.style.height = 'auto';
645
509
  this._pendingAttachments = [];
@@ -708,4 +572,4 @@ export class ChatView {
708
572
  this.element.parentNode.removeChild(this.element);
709
573
  }
710
574
  }
711
- }
575
+ }
@@ -4,6 +4,15 @@ export class ConversationsView {
4
4
  this.options = options;
5
5
  this.element = null;
6
6
  this._unsubscribe = null;
7
+ this.avatarColors = [
8
+ '#155EEF',
9
+ '#8b5cf6',
10
+ '#10b981',
11
+ '#f59e0b',
12
+ '#ef4444',
13
+ '#ec4899',
14
+ '#06b6d4',
15
+ ];
7
16
  }
8
17
 
9
18
  render() {
@@ -27,55 +36,61 @@ export class ConversationsView {
27
36
  return this.element;
28
37
  }
29
38
 
39
+ _getAvatarColor(name) {
40
+ // Generate consistent color based on name
41
+ const charCode = (name || 'S').charCodeAt(0);
42
+ return this.avatarColors[charCode % this.avatarColors.length];
43
+ }
44
+
30
45
  _updateContent() {
31
46
  const conversations = this.state.conversations;
32
- const avatarsHtml = this._renderAvatarStack();
33
47
 
34
48
  let conversationsHtml;
35
49
  if (conversations.length === 0) {
36
50
  conversationsHtml = `
37
- <div class="messenger-empty-state">
38
- <div class="messenger-empty-state-icon">
39
- <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" viewBox="0 0 256 256">
40
- <path d="M216,48H40A16,16,0,0,0,24,64V224a15.85,15.85,0,0,0,9.24,14.5A16.13,16.13,0,0,0,40,240a15.89,15.89,0,0,0,10.25-3.78.69.69,0,0,0,.13-.11L82.5,208H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,224h0ZM216,192H82.5a16,16,0,0,0-10.3,3.75l-.12.11L40,224V64H216Z"></path>
41
- </svg>
42
- </div>
43
- <h3>No conversations yet</h3>
44
- <p>Start a new conversation with our team</p>
45
- </div>
46
- `;
51
+ <div class="messenger-empty-state">
52
+ <div class="messenger-empty-state-icon">
53
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" viewBox="0 0 256 256">
54
+ <path d="M216,48H40A16,16,0,0,0,24,64V224a15.85,15.85,0,0,0,9.24,14.5A16.13,16.13,0,0,0,40,240a15.89,15.89,0,0,0,10.25-3.78.69.69,0,0,0,.13-.11L82.5,208H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,224h0ZM216,192H82.5a16,16,0,0,0-10.3,3.75l-.12.11L40,224V64H216Z"></path>
55
+ </svg>
56
+ </div>
57
+ <h3>No conversations yet</h3>
58
+ <p>Start a new conversation with our team</p>
59
+ </div>
60
+ `;
47
61
  } else {
48
62
  conversationsHtml = `
49
- <div class="messenger-conversations-list">
50
- ${conversations.map((conv) => this._renderConversationItem(conv)).join('')}
51
- </div>
52
- `;
63
+ <div class="messenger-conversations-list">
64
+ ${conversations.map((conv) => this._renderConversationItem(conv)).join('')}
65
+ </div>
66
+ `;
53
67
  }
54
68
 
55
69
  this.element.innerHTML = `
56
- <div class="messenger-conversations-header">
57
- <h2>Messages</h2>
58
- <button class="sdk-close-btn" aria-label="Close">
59
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
60
- <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
61
- </svg>
62
- </button>
63
- </div>
64
-
65
- <div class="messenger-conversations-body">
66
- ${conversationsHtml}
67
- </div>
68
-
69
- <div class="messenger-conversations-footer">
70
- <button class="messenger-new-message-btn">
71
- <div class="messenger-new-message-avatars">${avatarsHtml}</div>
72
- <span>Send us a message</span>
73
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256">
74
- <path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path>
75
- </svg>
76
- </button>
77
- </div>
78
- `;
70
+ <div class="messenger-conversations-header">
71
+ <h2>Messages</h2>
72
+ <button class="sdk-close-btn" aria-label="Close">
73
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
74
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
75
+ </svg>
76
+ </button>
77
+ </div>
78
+
79
+ <div class="messenger-conversations-body">
80
+ ${conversationsHtml}
81
+ </div>
82
+
83
+ <div class="messenger-conversations-footer">
84
+ <button class="messenger-new-message-btn">
85
+ <span>Send us a message</span>
86
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" style="flex-shrink: 0;">
87
+ <rect width="256" height="256" fill="none"/>
88
+ <line x1="144" y1="128" x2="80" y2="128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
89
+ <path d="M48.49,221.28A8,8,0,0,0,59.93,231l168-96.09a8,8,0,0,0,0-14l-168-95.85a8,8,0,0,0-11.44,9.67L80,128Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
90
+ </svg>
91
+ </button>
92
+ </div>
93
+ `;
79
94
 
80
95
  this._attachEvents();
81
96
  }
@@ -108,34 +123,41 @@ export class ConversationsView {
108
123
 
109
124
  _renderConversationAvatars(participants) {
110
125
  if (!participants || participants.length === 0) {
111
- return `<div class="sdk-avatar sdk-avatar-md">S</div>`;
126
+ const color = this._getAvatarColor('S');
127
+ return `<div class="sdk-avatar sdk-avatar-md" style="background-color: ${color};">S</div>`;
112
128
  }
113
129
 
114
130
  const p = participants[0];
115
131
  if (p.avatarUrl) {
116
132
  return `<div class="sdk-avatar sdk-avatar-md"><img src="${p.avatarUrl}" alt="${p.name}" /></div>`;
117
133
  }
118
- return `<div class="sdk-avatar sdk-avatar-md">${(p.name || 'S').charAt(0).toUpperCase()}</div>`;
134
+ const initial = (p.name || 'S').charAt(0).toUpperCase();
135
+ const color = this._getAvatarColor(p.name);
136
+ return `<div class="sdk-avatar sdk-avatar-md" style="background-color: ${color};">${initial}</div>`;
119
137
  }
120
138
 
121
139
  _renderAvatarStack() {
122
140
  const avatars = this.state.teamAvatars;
123
141
  if (!avatars || avatars.length === 0) {
142
+ const color1 = this._getAvatarColor('S');
143
+ const color2 = this._getAvatarColor('T');
124
144
  return `
125
145
  <div class="messenger-avatar-stack">
126
- <div class="sdk-avatar sdk-avatar-sm">S</div>
127
- <div class="sdk-avatar sdk-avatar-sm">T</div>
146
+ <div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color1};">S</div>
147
+ <div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color2};">T</div>
128
148
  </div>
129
149
  `;
130
150
  }
131
151
 
132
152
  const avatarItems = avatars
133
153
  .slice(0, 2)
134
- .map((avatar) => {
154
+ .map((avatar, index) => {
135
155
  if (typeof avatar === 'string' && avatar.startsWith('http')) {
136
156
  return `<div class="sdk-avatar sdk-avatar-sm"><img src="${avatar}" alt="Team member" /></div>`;
137
157
  }
138
- return `<div class="sdk-avatar sdk-avatar-sm">${avatar.charAt(0).toUpperCase()}</div>`;
158
+ const initial = avatar.charAt(0).toUpperCase();
159
+ const color = this._getAvatarColor(avatar);
160
+ return `<div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color};">${initial}</div>`;
139
161
  })
140
162
  .join('');
141
163
 
@@ -227,4 +249,4 @@ export class ConversationsView {
227
249
  this.element.parentNode.removeChild(this.element);
228
250
  }
229
251
  }
230
- }
252
+ }