@product7/feedback-sdk 1.3.2 → 1.3.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.
Files changed (37) hide show
  1. package/dist/feedback-sdk.js +2343 -3194
  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 +20 -0
  7. package/src/index.js +2 -3
  8. package/src/styles/base.js +27 -52
  9. package/src/styles/changelog.js +152 -269
  10. package/src/styles/components.js +489 -0
  11. package/src/styles/design-tokens.js +104 -0
  12. package/src/styles/feedback.js +59 -369
  13. package/src/styles/messenger-core.js +390 -0
  14. package/src/styles/messenger-features.js +347 -0
  15. package/src/styles/messenger-help.js +298 -0
  16. package/src/styles/messenger-themes.js +500 -0
  17. package/src/styles/messenger-views.js +618 -0
  18. package/src/styles/messenger.js +558 -0
  19. package/src/styles/styles.js +24 -2
  20. package/src/styles/surveys.js +354 -0
  21. package/src/widgets/BaseWidget.js +25 -58
  22. package/src/widgets/ButtonWidget.js +3 -18
  23. package/src/widgets/ChangelogWidget.js +7 -48
  24. package/src/widgets/InlineWidget.js +16 -13
  25. package/src/widgets/MessengerWidget.js +51 -54
  26. package/src/widgets/SurveyWidget.js +42 -146
  27. package/src/widgets/TabWidget.js +2 -22
  28. package/src/widgets/messenger/components/MessengerLauncher.js +10 -5
  29. package/src/widgets/messenger/components/MessengerPanel.js +5 -27
  30. package/src/widgets/messenger/components/NavigationTabs.js +5 -14
  31. package/src/widgets/messenger/views/ChangelogView.js +13 -14
  32. package/src/widgets/messenger/views/ChatView.js +43 -36
  33. package/src/widgets/messenger/views/ConversationsView.js +16 -21
  34. package/src/widgets/messenger/views/HelpView.js +10 -10
  35. package/src/widgets/messenger/views/HomeView.js +11 -16
  36. package/src/widgets/messenger/views/PreChatFormView.js +18 -30
  37. package/src/styles/messengerStyles.js +0 -2328
@@ -12,7 +12,7 @@ export class ChangelogWidget extends BaseWidget {
12
12
 
13
13
  _render() {
14
14
  const trigger = document.createElement('div');
15
- trigger.className = `feedback-widget changelog-widget theme-${this.options.theme} position-${this.options.position}`;
15
+ trigger.className = `changelog-widget position-${this.options.position}`;
16
16
  trigger.innerHTML = `
17
17
  <button class="changelog-trigger-btn" type="button" aria-label="View Updates">
18
18
  <svg class="changelog-icon" width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
@@ -33,29 +33,17 @@ export class ChangelogWidget extends BaseWidget {
33
33
 
34
34
  _attachEvents() {
35
35
  const button = this.element.querySelector('.changelog-trigger-btn');
36
-
37
36
  button.addEventListener('click', () => {
38
37
  this.openSidebar();
39
38
  });
40
-
41
- button.addEventListener('mouseenter', () => {
42
- button.style.transform = 'translateY(-2px)';
43
- });
44
-
45
- button.addEventListener('mouseleave', () => {
46
- button.style.transform = 'translateY(0)';
47
- });
48
39
  }
49
40
 
50
- // ==================== POPUP MODAL ====================
51
-
52
41
  async openModal() {
53
42
  if (this.modalElement) return;
54
43
 
55
44
  this.state.isOpen = true;
56
45
  this._createModal();
57
46
 
58
- // Load changelogs if not already loaded
59
47
  if (this.changelogs.length === 0) {
60
48
  await this._loadChangelogs();
61
49
  }
@@ -63,7 +51,6 @@ export class ChangelogWidget extends BaseWidget {
63
51
  this.currentIndex = 0;
64
52
  this._renderCurrentChangelog();
65
53
 
66
- // Prevent body scroll
67
54
  document.body.style.overflow = 'hidden';
68
55
 
69
56
  requestAnimationFrame(() => {
@@ -77,7 +64,6 @@ export class ChangelogWidget extends BaseWidget {
77
64
  }
78
65
 
79
66
  closeModal() {
80
- // Restore body scroll
81
67
  document.body.style.overflow = '';
82
68
 
83
69
  if (this.modalElement) {
@@ -101,21 +87,19 @@ export class ChangelogWidget extends BaseWidget {
101
87
  }
102
88
 
103
89
  _createModal() {
104
- // Create backdrop
105
90
  this.backdropElement = document.createElement('div');
106
91
  this.backdropElement.className = 'changelog-modal-backdrop';
107
92
  document.body.appendChild(this.backdropElement);
108
93
  this.backdropElement.addEventListener('click', () => this.closeModal());
109
94
 
110
- // Create modal
111
95
  this.modalElement = document.createElement('div');
112
- this.modalElement.className = `changelog-modal theme-${this.options.theme}`;
96
+ this.modalElement.className = 'changelog-modal';
113
97
  this.modalElement.innerHTML = `
114
98
  <div class="changelog-modal-container">
115
99
  <button class="changelog-modal-close" type="button" aria-label="Close">&times;</button>
116
100
  <div class="changelog-modal-content">
117
101
  <div class="changelog-loading">
118
- <div class="changelog-loading-spinner"></div>
102
+ <div class="sdk-spinner"></div>
119
103
  </div>
120
104
  </div>
121
105
  </div>
@@ -123,19 +107,16 @@ export class ChangelogWidget extends BaseWidget {
123
107
 
124
108
  document.body.appendChild(this.modalElement);
125
109
 
126
- // Prevent clicks inside modal from closing it
127
110
  this.modalElement
128
111
  .querySelector('.changelog-modal-container')
129
112
  .addEventListener('click', (e) => {
130
113
  e.stopPropagation();
131
114
  });
132
115
 
133
- // Attach close button event
134
116
  this.modalElement
135
117
  .querySelector('.changelog-modal-close')
136
118
  .addEventListener('click', () => this.closeModal());
137
119
 
138
- // Handle escape key
139
120
  this._escapeHandler = (e) => {
140
121
  if (e.key === 'Escape') {
141
122
  this.closeModal();
@@ -144,12 +125,9 @@ export class ChangelogWidget extends BaseWidget {
144
125
  document.addEventListener('keydown', this._escapeHandler);
145
126
  }
146
127
 
147
- // ==================== LIST MODAL ====================
148
-
149
128
  async openSidebar() {
150
129
  if (this.listModalElement) return;
151
130
 
152
- // Close popup modal if open
153
131
  if (this.modalElement) {
154
132
  this.closeModal();
155
133
  await new Promise((resolve) => setTimeout(resolve, 350));
@@ -158,20 +136,17 @@ export class ChangelogWidget extends BaseWidget {
158
136
  this.state.isOpen = true;
159
137
  this._createListModal();
160
138
 
161
- // Load changelogs if not already loaded
162
139
  if (this.changelogs.length === 0) {
163
140
  await this._loadChangelogs();
164
141
  }
165
142
 
166
143
  this._renderChangelogList();
167
144
 
168
- // Prevent body scroll
169
145
  document.body.style.overflow = 'hidden';
170
146
 
171
147
  requestAnimationFrame(() => {
172
148
  if (this.listModalElement) {
173
149
  this.listModalElement.classList.add('open');
174
- // Trigger confetti animation
175
150
  this._showConfetti();
176
151
  }
177
152
  if (this.listModalBackdropElement) {
@@ -205,7 +180,6 @@ export class ChangelogWidget extends BaseWidget {
205
180
  confetti.style.animationDelay = Math.random() * 0.5 + 's';
206
181
  confetti.style.animationDuration = Math.random() * 1 + 1.5 + 's';
207
182
 
208
- // Random shapes
209
183
  const shapes = ['circle', 'square', 'rectangle'];
210
184
  const shape = shapes[Math.floor(Math.random() * shapes.length)];
211
185
  if (shape === 'circle') {
@@ -223,7 +197,6 @@ export class ChangelogWidget extends BaseWidget {
223
197
  container.appendChild(confetti);
224
198
  }
225
199
 
226
- // Remove confetti after animation
227
200
  setTimeout(() => {
228
201
  if (container.parentNode) {
229
202
  container.parentNode.removeChild(container);
@@ -232,7 +205,6 @@ export class ChangelogWidget extends BaseWidget {
232
205
  }
233
206
 
234
207
  closeSidebar() {
235
- // Restore body scroll
236
208
  document.body.style.overflow = '';
237
209
 
238
210
  if (this.listModalElement) {
@@ -264,7 +236,6 @@ export class ChangelogWidget extends BaseWidget {
264
236
  }
265
237
 
266
238
  _createListModal() {
267
- // Create backdrop
268
239
  this.listModalBackdropElement = document.createElement('div');
269
240
  this.listModalBackdropElement.className = 'changelog-list-modal-backdrop';
270
241
  document.body.appendChild(this.listModalBackdropElement);
@@ -272,9 +243,8 @@ export class ChangelogWidget extends BaseWidget {
272
243
  this.closeSidebar()
273
244
  );
274
245
 
275
- // Create list modal
276
246
  this.listModalElement = document.createElement('div');
277
- this.listModalElement.className = `changelog-list-modal theme-${this.options.theme}`;
247
+ this.listModalElement.className = 'changelog-list-modal';
278
248
  this.listModalElement.innerHTML = `
279
249
  <div class="changelog-list-modal-container">
280
250
  <div class="changelog-list-modal-header">
@@ -283,7 +253,7 @@ export class ChangelogWidget extends BaseWidget {
283
253
  </div>
284
254
  <div class="changelog-list-modal-body">
285
255
  <div class="changelog-loading">
286
- <div class="changelog-loading-spinner"></div>
256
+ <div class="sdk-spinner"></div>
287
257
  </div>
288
258
  </div>
289
259
  </div>
@@ -291,19 +261,16 @@ export class ChangelogWidget extends BaseWidget {
291
261
 
292
262
  document.body.appendChild(this.listModalElement);
293
263
 
294
- // Prevent clicks inside modal from closing it
295
264
  this.listModalElement
296
265
  .querySelector('.changelog-list-modal-container')
297
266
  .addEventListener('click', (e) => {
298
267
  e.stopPropagation();
299
268
  });
300
269
 
301
- // Attach close button event
302
270
  this.listModalElement
303
271
  .querySelector('.changelog-list-modal-close')
304
272
  .addEventListener('click', () => this.closeSidebar());
305
273
 
306
- // Handle escape key
307
274
  this._listModalEscapeHandler = (e) => {
308
275
  if (e.key === 'Escape') {
309
276
  this.closeSidebar();
@@ -340,7 +307,6 @@ export class ChangelogWidget extends BaseWidget {
340
307
  </div>
341
308
  `;
342
309
 
343
- // Attach click events to items
344
310
  body.querySelectorAll('.changelog-list-item').forEach((item, index) => {
345
311
  item.addEventListener('click', () => {
346
312
  const changelog = this.changelogs[index];
@@ -420,7 +386,7 @@ export class ChangelogWidget extends BaseWidget {
420
386
  const content = this.modalElement.querySelector('.changelog-modal-content');
421
387
 
422
388
  if (this.isLoading) {
423
- return; // Keep showing loading spinner
389
+ return;
424
390
  }
425
391
 
426
392
  if (this.changelogs.length === 0) {
@@ -491,14 +457,12 @@ export class ChangelogWidget extends BaseWidget {
491
457
  </div>
492
458
  `;
493
459
 
494
- // Attach view button event
495
460
  content
496
461
  .querySelector('.changelog-popup-btn')
497
462
  .addEventListener('click', () => {
498
463
  this._handleViewUpdate(changelog);
499
464
  });
500
465
 
501
- // Attach view all button event
502
466
  content
503
467
  .querySelector('.changelog-view-all-btn')
504
468
  .addEventListener('click', () => {
@@ -506,7 +470,6 @@ export class ChangelogWidget extends BaseWidget {
506
470
  setTimeout(() => this.openSidebar(), 350);
507
471
  });
508
472
 
509
- // Attach dot navigation events
510
473
  if (hasMultiple) {
511
474
  content.querySelectorAll('.changelog-dot').forEach((dot) => {
512
475
  dot.addEventListener('click', (e) => {
@@ -521,7 +484,6 @@ export class ChangelogWidget extends BaseWidget {
521
484
  _handleViewUpdate(changelog) {
522
485
  this.sdk.eventBus.emit('changelog:view', { changelog });
523
486
 
524
- // If there's a URL, open it
525
487
  if (changelog.url || changelog.slug) {
526
488
  const url =
527
489
  changelog.url ||
@@ -533,7 +495,6 @@ export class ChangelogWidget extends BaseWidget {
533
495
  }
534
496
  }
535
497
 
536
- // Custom callback if provided
537
498
  if (typeof this.options.onViewUpdate === 'function') {
538
499
  this.options.onViewUpdate(changelog);
539
500
  }
@@ -546,12 +507,10 @@ export class ChangelogWidget extends BaseWidget {
546
507
  }
547
508
 
548
509
  _getContrastColor(hexColor) {
549
- // Remove # if present
550
510
  const hex = hexColor.replace('#', '');
551
511
  const r = parseInt(hex.substr(0, 2), 16);
552
512
  const g = parseInt(hex.substr(2, 2), 16);
553
513
  const b = parseInt(hex.substr(4, 2), 16);
554
- // Calculate luminance
555
514
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
556
515
  return luminance > 0.5 ? '#1F2937' : '#FFFFFF';
557
516
  }
@@ -611,4 +570,4 @@ export class ChangelogWidget extends BaseWidget {
611
570
  this.closeSidebar();
612
571
  super.destroy();
613
572
  }
614
- }
573
+ }
@@ -7,38 +7,41 @@ export class InlineWidget extends BaseWidget {
7
7
 
8
8
  _render() {
9
9
  const widget = document.createElement('div');
10
- widget.className = `feedback-widget feedback-widget-inline theme-${this.options.theme}`;
10
+ widget.className = 'feedback-widget feedback-widget-inline';
11
11
  widget.innerHTML = `
12
12
  <div class="feedback-inline-content">
13
13
  <h3>Send us your feedback</h3>
14
14
  <form class="feedback-inline-form">
15
- <div class="feedback-form-group">
15
+ <div class="sdk-form-group">
16
16
  <input
17
17
  type="text"
18
18
  name="title"
19
+ class="sdk-input"
19
20
  placeholder="Title (optional)"
20
21
  value="${this.state.title}"
21
22
  />
22
23
  </div>
23
- <div class="feedback-form-group">
24
+ <div class="sdk-form-group">
24
25
  <textarea
25
26
  name="content"
27
+ class="sdk-textarea"
26
28
  placeholder="Your feedback..."
27
29
  required
28
30
  >${this.state.content}</textarea>
29
31
  </div>
30
- <div class="feedback-form-group">
32
+ <div class="sdk-form-group">
31
33
  <input
32
34
  type="email"
33
35
  name="email"
36
+ class="sdk-input"
34
37
  placeholder="Email (optional)"
35
38
  value="${this.state.email}"
36
39
  />
37
40
  </div>
38
- <button type="submit" class="feedback-btn feedback-btn-submit">
41
+ <button type="submit" class="sdk-btn-primary sdk-btn-block">
39
42
  Send Feedback
40
43
  </button>
41
- <div class="feedback-error" style="display: none;"></div>
44
+ <div class="feedback-error"></div>
42
45
  </form>
43
46
  </div>
44
47
  `;
@@ -90,14 +93,14 @@ export class InlineWidget extends BaseWidget {
90
93
 
91
94
  widget.innerHTML = `
92
95
  <div class="feedback-success">
93
- <div class="feedback-success-icon">✓</div>
96
+ <div class="sdk-notification-icon">✓</div>
94
97
  <h3>Thank you!</h3>
95
98
  <p>Your feedback has been submitted successfully.</p>
96
- <button class="feedback-btn feedback-btn-reset">Send Another</button>
99
+ <button class="sdk-btn-primary">Send Another</button>
97
100
  </div>
98
101
  `;
99
102
 
100
- const resetBtn = widget.querySelector('.feedback-btn-reset');
103
+ const resetBtn = widget.querySelector('.sdk-btn-primary');
101
104
  resetBtn.addEventListener('click', () => {
102
105
  widget.innerHTML = originalContent;
103
106
  this._attachEvents();
@@ -109,18 +112,18 @@ export class InlineWidget extends BaseWidget {
109
112
  const errorElement = this.element.querySelector('.feedback-error');
110
113
  if (errorElement) {
111
114
  errorElement.textContent = message;
112
- errorElement.style.display = 'block';
115
+ errorElement.classList.add('show');
113
116
 
114
117
  setTimeout(() => {
115
118
  if (errorElement) {
116
- errorElement.style.display = 'none';
119
+ errorElement.classList.remove('show');
117
120
  }
118
121
  }, 5000);
119
122
  }
120
123
  }
121
124
 
122
125
  _updateSubmitButton() {
123
- const submitBtn = this.element.querySelector('.feedback-btn-submit');
126
+ const submitBtn = this.element.querySelector('.sdk-btn-primary');
124
127
  if (submitBtn) {
125
128
  submitBtn.textContent = this.state.isSubmitting
126
129
  ? 'Sending...'
@@ -142,4 +145,4 @@ export class InlineWidget extends BaseWidget {
142
145
  input.placeholder = placeholder;
143
146
  }
144
147
  }
145
- }
148
+ }
@@ -132,44 +132,56 @@ export class MessengerWidget extends BaseWidget {
132
132
  }
133
133
  }
134
134
 
135
- async _handleStartConversation(messageContent, pendingAttachments) {
136
- try {
137
- const userContext = this.messengerState.userContext;
138
- const isIdentified = userContext?.email;
135
+ async _handleStartConversation(messageContent, pendingAttachments) {
136
+ try {
137
+ const userContext = this.messengerState.userContext;
138
+ const isAuthenticated = userContext?.email && userContext?.name;
139
139
 
140
- if (!isIdentified) {
141
- this.messengerState.pendingMessage = {
142
- content: messageContent,
143
- attachments: pendingAttachments,
144
- };
145
- this.messengerState.setView('prechat');
146
- return null;
140
+ if (isAuthenticated) {
141
+ try {
142
+ await this.apiService.identifyContact({
143
+ name: userContext.name,
144
+ email: userContext.email,
145
+ });
146
+ console.log('[MessengerWidget] User auto-identified for conversation');
147
+ } catch (error) {
148
+ if (error?.code !== 'ALREADY_IDENTIFIED') {
149
+ console.warn('[MessengerWidget] Auto-identification failed:', error);
150
+ }
147
151
  }
152
+ } else {
153
+ this.messengerState.pendingMessage = {
154
+ content: messageContent,
155
+ attachments: pendingAttachments,
156
+ };
157
+ this.messengerState.setView('prechat');
158
+ return null;
159
+ }
148
160
 
149
- const openConversation = this.messengerState.conversations.find(
150
- (c) => c.status === 'open'
151
- );
152
-
153
- if (openConversation) {
154
- this.messengerState.setActiveConversation(openConversation.id);
155
- await this._handleSendMessage(
156
- openConversation.id,
157
- { content: messageContent },
158
- pendingAttachments
159
- );
160
- return openConversation;
161
- }
161
+ const openConversation = this.messengerState.conversations.find(
162
+ (c) => c.status === 'open'
163
+ );
162
164
 
163
- return await this.startNewConversation(
164
- messageContent,
165
- '',
165
+ if (openConversation) {
166
+ this.messengerState.setActiveConversation(openConversation.id);
167
+ await this._handleSendMessage(
168
+ openConversation.id,
169
+ { content: messageContent },
166
170
  pendingAttachments
167
171
  );
168
- } catch (error) {
169
- console.error('[MessengerWidget] Failed to start conversation:', error);
170
- return null;
172
+ return openConversation;
171
173
  }
174
+
175
+ return await this.startNewConversation(
176
+ messageContent,
177
+ '',
178
+ pendingAttachments
179
+ );
180
+ } catch (error) {
181
+ console.error('[MessengerWidget] Failed to start conversation:', error);
182
+ return null;
172
183
  }
184
+ }
173
185
 
174
186
  async _handleSelectConversation(conversationId) {
175
187
  try {
@@ -791,34 +803,19 @@ export class MessengerWidget extends BaseWidget {
791
803
  };
792
804
  }
793
805
 
794
- async onMount() {
795
- const userContext = this.messengerState.userContext;
796
- if (userContext?.email && userContext?.name) {
797
- try {
798
- await this.apiService.identifyContact({
799
- name: userContext.name,
800
- email: userContext.email,
801
- });
802
- console.log('[MessengerWidget] User identified successfully');
803
- } catch (error) {
804
- if (error?.code !== 'ALREADY_IDENTIFIED') {
805
- console.warn('[MessengerWidget] Identification failed:', error);
806
- }
807
- }
808
- }
806
+ async onMount() {
807
+ this.loadInitialData();
809
808
 
810
- this.loadInitialData();
809
+ if (this.apiService?.sessionToken) {
810
+ this._initWebSocket();
811
+ }
811
812
 
812
- if (this.apiService?.sessionToken) {
813
- this._initWebSocket();
814
- }
813
+ this.checkAgentAvailability();
815
814
 
815
+ this._availabilityInterval = setInterval(() => {
816
816
  this.checkAgentAvailability();
817
-
818
- this._availabilityInterval = setInterval(() => {
819
- this.checkAgentAvailability();
820
- }, 60000);
821
- }
817
+ }, 60000);
818
+ }
822
819
 
823
820
  onDestroy() {
824
821
  if (this._stateUnsubscribe) {