@product7/feedback-sdk 1.3.8 → 1.4.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.
Files changed (39) hide show
  1. package/dist/feedback-sdk.js +3008 -2568
  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 +5 -5
  7. package/src/core/APIService.js +37 -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} +56 -21
  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 +111 -122
  26. package/src/widgets/SurveyWidget.js +1 -1
  27. package/src/widgets/TabWidget.js +1 -1
  28. package/src/widgets/messenger/MessengerState.js +5 -2
  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 +96 -60
  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 +12 -5
  38. package/src/styles/messenger-help.js +0 -298
  39. package/src/styles/messenger-themes.js +0 -500
@@ -8,7 +8,7 @@ export class NavigationTabs {
8
8
 
9
9
  render() {
10
10
  this.element = document.createElement('div');
11
- this.element.className = 'messenger-nav';
11
+ this.element.className = 'messenger-panel-nav';
12
12
 
13
13
  this._updateContent();
14
14
  this._attachEvents();
@@ -68,7 +68,22 @@ export class NavigationTabs {
68
68
  })
69
69
  .join('');
70
70
 
71
- this.element.innerHTML = tabsHtml;
71
+ this.element.innerHTML = `
72
+ <div class="messenger-nav-tabs">
73
+ ${tabsHtml}
74
+ </div>
75
+ <div class="messenger-nav-footer">
76
+ <a href="https://product7.io" target="_blank" rel="noopener noreferrer" class="messenger-powered-by">
77
+ <svg width="12" height="14" viewBox="0 0 28 32" fill="none" xmlns="http://www.w3.org/2000/svg">
78
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.0615 5.28044C8.5161 4.42949 3.30825 11.1456 5.89967 17.6588C6.9321 20.2538 9.06268 22.2644 11.8777 23.1968C16.2682 24.6507 18.4038 22.3222 19.0483 23.9691C19.4055 24.8894 18.7282 25.3209 17.988 25.4938C10.9146 27.15 5.15304 22.7566 3.5869 17.5531C1.52205 10.6941 5.98684 4.6667 11.3483 3.41065C17.8801 1.88094 24.0325 6.19355 24.3926 12.7175C24.7448 19.0921 18.6217 24.5978 11.927 22.2036C10.8789 21.8285 8.8419 20.6682 8.46823 19.858C8.06026 18.9727 8.80261 18.1725 9.68285 18.3576C10.2223 18.4726 10.3116 18.8706 11.3161 19.5372C14.4549 21.6213 19.1276 20.6132 21.2046 17.0972C23.991 12.3817 21.0481 6.05351 15.06 5.27758L15.0615 5.28044Z" fill="#21244A"/>
79
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.2492 2.19833C11.944 1.71463 8.88819 3.07214 6.91479 4.49682C2.27067 7.85488 0.76169 14.5038 3.49672 19.8731C4.08535 21.0096 4.84379 22.0497 5.7459 22.9576L7.16343 24.2515C7.67214 24.9131 7.27203 25.7176 6.64115 25.9269C5.13502 26.4271 2.0499 21.8172 1.42044 20.5383C0.0872204 17.8297 -0.312889 14.9047 0.242977 11.503C1.66908 2.77063 11.221 -2.51652 19.7197 1.21021C27.7548 4.73331 30.2733 15.4555 23.9351 22.0773C23.3107 22.7296 21.6352 24.4823 20.6278 23.8907C20.0076 23.5263 19.8933 22.6446 20.5192 22.1238C21.0301 21.6986 21.4759 21.435 21.9896 20.9734C23.6665 19.4688 25.2562 16.8752 25.3477 13.5636C25.4427 10.2055 24.1266 7.5848 22.3904 5.74859C20.6392 3.89665 18.6751 2.69919 15.2456 2.19691L15.2492 2.19833Z" fill="#F69F06"/>
80
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8.48332 27.2217C7.93817 26.265 8.89987 25.3776 10.1352 25.8641C15.5653 27.9926 18.3081 25.5269 19.0255 27.0823C19.2655 27.6039 19.0448 28.1619 18.7354 28.3863C17.9895 28.9257 14.82 28.9343 13.9262 28.8714C12.9071 28.8053 11.897 28.6377 10.9111 28.3713C10.0888 28.1348 8.88057 27.9247 8.48189 27.2281L8.48332 27.2217Z" fill="#21244A"/>
81
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.8722 31.0607C15.7765 32.1381 14.579 32.0331 13.5766 31.9545C12.5742 31.8759 11.5203 31.8502 11.601 30.7013C11.6789 29.5882 12.8035 29.7532 13.8274 29.8332C14.4425 29.8811 15.9951 29.681 15.8722 31.0607Z" fill="#21244A"/>
82
+ </svg>
83
+ Powered by <strong>Product7</strong>
84
+ </a>
85
+ </div>
86
+ `;
72
87
  }
73
88
 
74
89
  _attachEvents() {
@@ -103,27 +118,33 @@ export class NavigationTabs {
103
118
  }
104
119
 
105
120
  _getHomeIcon() {
106
- return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256">
107
- <path d="M218.83,103.77l-80-75.48a1.14,1.14,0,0,1-.11-.11,16,16,0,0,0-21.53,0l-.11.11L37.17,103.77A16,16,0,0,0,32,115.55V208a16,16,0,0,0,16,16H96a16,16,0,0,0,16-16V160h32v48a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V115.55A16,16,0,0,0,218.83,103.77ZM208,208H160V160a16,16,0,0,0-16-16H112a16,16,0,0,0-16,16v48H48V115.55l.11-.1L128,40l79.9,75.43.11.1Z"></path>
108
- </svg>`;
121
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 256">
122
+ <path d="M40,216H216V120a8,8,0,0,0-2.34-5.66l-80-80a8,8,0,0,0-11.32,0l-80,80A8,8,0,0,0,40,120Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
123
+ </svg>`;
109
124
  }
110
125
 
111
126
  _getMessagesIcon() {
112
- return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256">
113
- <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>
114
- </svg>`;
127
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 256">
128
+ <circle cx="128" cy="128" r="12"/>
129
+ <circle cx="84" cy="128" r="12"/>
130
+ <circle cx="172" cy="128" r="12"/>
131
+ <path d="M79.93,211.11a96,96,0,1,0-35-35h0L32.42,213.46a8,8,0,0,0,10.12,10.12l37.39-12.47Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
132
+ </svg>`;
115
133
  }
116
134
 
117
135
  _getHelpIcon() {
118
- return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256">
119
- <path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path>
120
- </svg>`;
136
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 256">
137
+ <circle cx="128" cy="128" r="96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
138
+ <circle cx="128" cy="180" r="12"/>
139
+ <path d="M128,144v-8a28,28,0,1,0-28-28" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
140
+ </svg>`;
121
141
  }
122
142
 
123
143
  _getChangelogIcon() {
124
- return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256">
125
- <path d="M228.54,86.66l-26.46,23.07A40,40,0,0,0,168,72.13L120.89,46.5a40,40,0,0,0-75.44-4l-22-19.2a8,8,0,0,0-10.5,12L35.44,54.77a40,40,0,0,0,50,61.07l47.1,25.64a40,40,0,0,0,75.41,4.07l26.46-23.07a8,8,0,0,0-10.5-12ZM56,96A24,24,0,1,1,77.25,82.75,24,24,0,0,1,56,96Zm144,64a24,24,0,1,1,24-24A24,24,0,0,1,200,160Z"></path>
126
- </svg>`;
144
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 256">
145
+ <path d="M160,80V200.67a8,8,0,0,0,3.56,6.65l11,7.33a8,8,0,0,0,12.2-4.72L200,160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
146
+ <path d="M40,200a8,8,0,0,0,13.15,6.12C105.55,162.16,160,160,160,160h40a40,40,0,0,0,0-80H160S105.55,77.84,53.15,33.89A8,8,0,0,0,40,40Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
147
+ </svg>`;
127
148
  }
128
149
 
129
150
  destroy() {
@@ -134,4 +155,4 @@ export class NavigationTabs {
134
155
  this.element.parentNode.removeChild(this.element);
135
156
  }
136
157
  }
137
- }
158
+ }
@@ -22,30 +22,15 @@ export class ChangelogView {
22
22
  }
23
23
 
24
24
  _updateContent() {
25
- const avatarsHtml = this._renderTeamAvatars();
26
-
27
25
  this.element.innerHTML = `
28
- <div class="messenger-changelog-header">
29
- <h2>Changelog</h2>
30
- <button class="sdk-close-btn" aria-label="Close">
31
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
32
- <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>
33
- </svg>
34
- </button>
35
- </div>
26
+ <div class="messenger-changelog-header">
27
+ <h2>Latest Updates</h2>
28
+ </div>
36
29
 
37
- <div class="messenger-changelog-subheader">
38
- <span class="messenger-changelog-latest">Latest</span>
39
- <div class="messenger-changelog-team">
40
- <span>From ${this.state.teamName}</span>
41
- ${avatarsHtml}
42
- </div>
43
- </div>
44
-
45
- <div class="messenger-changelog-body">
46
- <div class="messenger-changelog-list"></div>
47
- </div>
48
- `;
30
+ <div class="messenger-changelog-body">
31
+ <div class="messenger-changelog-list"></div>
32
+ </div>
33
+ `;
49
34
 
50
35
  this._updateChangelogList();
51
36
  this._attachEvents();
@@ -96,9 +81,6 @@ export class ChangelogView {
96
81
  ${item.description ? `<p class="messenger-changelog-description">${this._truncateText(item.description, 100)}</p>` : ''}
97
82
  <div class="messenger-changelog-meta">
98
83
  <span class="messenger-changelog-date">${dateStr}</span>
99
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256" class="messenger-changelog-arrow">
100
- <path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path>
101
- </svg>
102
84
  </div>
103
85
  </div>
104
86
  </div>
@@ -159,12 +141,6 @@ export class ChangelogView {
159
141
  }
160
142
 
161
143
  _attachEvents() {
162
- this.element
163
- .querySelector('.sdk-close-btn')
164
- .addEventListener('click', () => {
165
- this.state.setOpen(false);
166
- });
167
-
168
144
  this._attachChangelogEvents();
169
145
  }
170
146
 
@@ -194,4 +170,4 @@ export class ChangelogView {
194
170
  this.element.parentNode.removeChild(this.element);
195
171
  }
196
172
  }
197
- }
173
+ }
@@ -40,7 +40,10 @@ export class ChatView {
40
40
  data.conversationId === this.state.activeConversationId
41
41
  ) {
42
42
  this._updateContent();
43
- } else if (type === 'messagesUpdate' && data.conversationId === this.state.activeConversationId) {
43
+ } else if (
44
+ type === 'messagesUpdate' &&
45
+ data.conversationId === this.state.activeConversationId
46
+ ) {
44
47
  this._updateContent();
45
48
  }
46
49
  });
@@ -60,7 +63,6 @@ export class ChatView {
60
63
  ? this._renderEmptyState(isNewConversation)
61
64
  : messages.map((msg) => this._renderMessage(msg)).join('');
62
65
 
63
- const avatarHtml = this._renderConversationAvatar(conversation);
64
66
  const title = isNewConversation
65
67
  ? 'New conversation'
66
68
  : conversation?.title || 'Chat with team';
@@ -78,7 +80,6 @@ export class ChatView {
78
80
  </svg>
79
81
  </button>
80
82
  <div class="messenger-chat-header-info">
81
- ${avatarHtml}
82
83
  <span class="messenger-chat-title">${title}</span>
83
84
  </div>
84
85
  <button class="sdk-close-btn" aria-label="Close">
@@ -90,12 +91,16 @@ export class ChatView {
90
91
 
91
92
  <div class="messenger-chat-messages">
92
93
  ${messagesHtml}
93
- ${isClosed ? `
94
+ ${
95
+ isClosed
96
+ ? `
94
97
  <div class="messenger-closed-banner">
95
98
  <i class="ph ph-check-circle"></i>
96
99
  <span>This conversation has been resolved</span>
97
100
  </div>
98
- ` : ''}
101
+ `
102
+ : ''
103
+ }
99
104
  <div class="messenger-typing-indicator">
100
105
  <div class="messenger-typing-dots">
101
106
  <span></span><span></span><span></span>
@@ -104,7 +109,10 @@ export class ChatView {
104
109
  </div>
105
110
  </div>
106
111
 
107
- ${isClosed ? '' : `
112
+ ${
113
+ isClosed
114
+ ? ''
115
+ : `
108
116
  <div class="messenger-compose-attachments-preview"></div>
109
117
 
110
118
  <div class="messenger-chat-compose">
@@ -121,10 +129,13 @@ export class ChatView {
121
129
  </button>
122
130
  <input type="file" class="messenger-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
123
131
  </div>
124
- `}
132
+ `
133
+ }
125
134
  `;
126
135
 
127
- this._typingIndicator = this.element.querySelector('.messenger-typing-indicator');
136
+ this._typingIndicator = this.element.querySelector(
137
+ '.messenger-typing-indicator'
138
+ );
128
139
  this._attachEvents();
129
140
  this._scrollToBottom();
130
141
  this._renderAttachmentPreviews();
@@ -132,7 +143,8 @@ export class ChatView {
132
143
 
133
144
  _renderEmptyState(isNewConversation = false) {
134
145
  const avatarHtml = this._renderTeamAvatars();
135
- const responseTime = this.state.responseTime || 'We typically reply within a few minutes';
146
+ const responseTime =
147
+ this.state.responseTime || 'We typically reply within a few minutes';
136
148
  const isOnline = this.state.agentsOnline;
137
149
 
138
150
  return `
@@ -150,26 +162,34 @@ export class ChatView {
150
162
 
151
163
  _renderMessageAttachments(attachments) {
152
164
  if (!attachments || attachments.length === 0) return '';
153
- return attachments.map((att) => {
154
- if (att.type === 'image') {
155
- return `<img class="messenger-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
156
- }
157
- 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')}">
158
171
  <i class="ph ph-file"></i>
159
172
  <span>${this._escapeHtml(att.name || 'file')}</span>
160
173
  <i class="ph ph-download-simple messenger-file-download-icon"></i>
161
174
  </a>`;
162
- }).join('');
175
+ })
176
+ .join('');
163
177
  }
164
178
 
165
179
  _renderMessage(message) {
166
180
  const isOwn = message.isOwn;
167
- const messageClass = isOwn ? 'messenger-message-own' : 'messenger-message-received';
181
+ const messageClass = isOwn
182
+ ? 'messenger-message-own'
183
+ : 'messenger-message-received';
168
184
  const timeStr = this._formatMessageTime(message.timestamp);
169
185
  const attachmentsHtml = this._renderMessageAttachments(message.attachments);
170
186
 
171
- const contentHtml = message.content ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>` : '';
172
- 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
+ : '';
173
193
 
174
194
  if (isOwn) {
175
195
  return `
@@ -184,13 +204,15 @@ export class ChatView {
184
204
  const avatarHtml = this._renderSenderAvatar(message.sender);
185
205
  return `
186
206
  <div class="messenger-message ${messageClass}">
187
- <div class="messenger-message-avatar">${avatarHtml}</div>
188
- <div class="messenger-message-wrapper">
189
- <div class="messenger-message-sender">${message.sender?.name || 'Support'}</div>
190
- ${bubbleHtml}
191
- ${attachmentsHtml}
192
- <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>
193
214
  </div>
215
+ <div class="messenger-message-time">${timeStr}</div>
194
216
  </div>
195
217
  `;
196
218
  }
@@ -203,17 +225,6 @@ export class ChatView {
203
225
  return `<div class="sdk-avatar sdk-avatar-sm">${initial}</div>`;
204
226
  }
205
227
 
206
- _renderConversationAvatar(conversation) {
207
- if (!conversation?.participants?.length) {
208
- return `<div class="sdk-avatar sdk-avatar-sm">S</div>`;
209
- }
210
- const p = conversation.participants[0];
211
- if (p.avatarUrl) {
212
- return `<div class="sdk-avatar sdk-avatar-sm"><img src="${p.avatarUrl}" alt="${p.name}" /></div>`;
213
- }
214
- return `<div class="sdk-avatar sdk-avatar-sm">${(p.name || 'S').charAt(0).toUpperCase()}</div>`;
215
- }
216
-
217
228
  _renderTeamAvatars() {
218
229
  const avatars = this.state.teamAvatars;
219
230
  if (!avatars || avatars.length === 0) {
@@ -258,7 +269,9 @@ export class ChatView {
258
269
  }
259
270
 
260
271
  _appendMessage(message) {
261
- const messagesContainer = this.element.querySelector('.messenger-chat-messages');
272
+ const messagesContainer = this.element.querySelector(
273
+ '.messenger-chat-messages'
274
+ );
262
275
  const emptyState = messagesContainer.querySelector('.messenger-chat-empty');
263
276
  if (emptyState) {
264
277
  emptyState.remove();
@@ -271,7 +284,9 @@ export class ChatView {
271
284
  }
272
285
 
273
286
  _scrollToBottom() {
274
- const messagesContainer = this.element.querySelector('.messenger-chat-messages');
287
+ const messagesContainer = this.element.querySelector(
288
+ '.messenger-chat-messages'
289
+ );
275
290
  if (messagesContainer) {
276
291
  setTimeout(() => {
277
292
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
@@ -283,12 +298,15 @@ export class ChatView {
283
298
  const input = this.element.querySelector('.messenger-compose-input');
284
299
  const sendBtn = this.element.querySelector('.messenger-compose-send');
285
300
  if (input && sendBtn) {
286
- sendBtn.disabled = !input.value.trim() && this._pendingAttachments.length === 0;
301
+ sendBtn.disabled =
302
+ !input.value.trim() && this._pendingAttachments.length === 0;
287
303
  }
288
304
  }
289
305
 
290
306
  _renderAttachmentPreviews() {
291
- const container = this.element.querySelector('.messenger-compose-attachments-preview');
307
+ const container = this.element.querySelector(
308
+ '.messenger-compose-attachments-preview'
309
+ );
292
310
  if (!container) return;
293
311
 
294
312
  if (this._pendingAttachments.length === 0) {
@@ -298,27 +316,31 @@ export class ChatView {
298
316
  }
299
317
 
300
318
  container.style.display = 'flex';
301
- container.innerHTML = this._pendingAttachments.map((att, i) => {
302
- const isImage = att.type.startsWith('image');
303
- const thumb = isImage
304
- ? `<img class="messenger-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
305
- : `<div class="messenger-attachment-thumb messenger-attachment-file-icon"><i class="ph ph-file"></i></div>`;
306
- 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 `
307
326
  <div class="messenger-attachment-preview" data-index="${i}">
308
327
  ${thumb}
309
328
  <button class="messenger-attachment-remove" data-index="${i}" aria-label="Remove">&times;</button>
310
329
  </div>
311
330
  `;
312
- }).join('');
331
+ })
332
+ .join('');
313
333
 
314
- container.querySelectorAll('.messenger-attachment-remove').forEach((btn) => {
315
- btn.addEventListener('click', (e) => {
316
- const idx = parseInt(e.currentTarget.dataset.index, 10);
317
- this._pendingAttachments.splice(idx, 1);
318
- this._renderAttachmentPreviews();
319
- this._updateSendButtonState();
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
+ });
320
343
  });
321
- });
322
344
  }
323
345
 
324
346
  _attachEvents() {
@@ -361,7 +383,9 @@ export class ChatView {
361
383
  }
362
384
 
363
385
  const attachBtn = this.element.querySelector('.messenger-compose-attach');
364
- const fileInput = this.element.querySelector('.messenger-compose-file-input');
386
+ const fileInput = this.element.querySelector(
387
+ '.messenger-compose-file-input'
388
+ );
365
389
 
366
390
  if (attachBtn && fileInput) {
367
391
  attachBtn.addEventListener('click', () => {
@@ -388,7 +412,9 @@ export class ChatView {
388
412
  });
389
413
  }
390
414
 
391
- const messagesContainer = this.element.querySelector('.messenger-chat-messages');
415
+ const messagesContainer = this.element.querySelector(
416
+ '.messenger-chat-messages'
417
+ );
392
418
  if (messagesContainer) {
393
419
  messagesContainer.addEventListener('click', (e) => {
394
420
  const fileLink = e.target.closest('.messenger-message-file');
@@ -429,12 +455,16 @@ export class ChatView {
429
455
 
430
456
  _escapeHtml(text) {
431
457
  if (!text) return '';
432
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
458
+ return text
459
+ .replace(/&/g, '&amp;')
460
+ .replace(/</g, '&lt;')
461
+ .replace(/>/g, '&gt;')
462
+ .replace(/"/g, '&quot;');
433
463
  }
434
464
 
435
465
  async _sendMessage() {
436
466
  if (this._isConversationClosed) return;
437
-
467
+
438
468
  const input = this.element.querySelector('.messenger-compose-input');
439
469
  const content = input.value.trim();
440
470
  const hasAttachments = this._pendingAttachments.length > 0;
@@ -466,7 +496,11 @@ export class ChatView {
466
496
  this.state.addMessage(this.state.activeConversationId, message);
467
497
 
468
498
  if (this.options.onSendMessage) {
469
- this.options.onSendMessage(this.state.activeConversationId, message, attachmentsToSend);
499
+ this.options.onSendMessage(
500
+ this.state.activeConversationId,
501
+ message,
502
+ attachmentsToSend
503
+ );
470
504
  }
471
505
  }
472
506
 
@@ -510,7 +544,9 @@ export class ChatView {
510
544
  _showTypingIndicator(userName) {
511
545
  if (this._typingIndicator) {
512
546
  this._typingIndicator.style.display = 'flex';
513
- const textEl = this._typingIndicator.querySelector('.messenger-typing-text');
547
+ const textEl = this._typingIndicator.querySelector(
548
+ '.messenger-typing-text'
549
+ );
514
550
  if (textEl) {
515
551
  textEl.textContent = `${userName || 'Support'} is typing...`;
516
552
  }
@@ -536,4 +572,4 @@ export class ChatView {
536
572
  this.element.parentNode.removeChild(this.element);
537
573
  }
538
574
  }
539
- }
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
+ }