@product7/feedback-sdk 1.3.3 → 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 +2321 -3173
  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 +0 -4
  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
+ }
@@ -137,7 +137,6 @@ async _handleStartConversation(messageContent, pendingAttachments) {
137
137
  const userContext = this.messengerState.userContext;
138
138
  const isAuthenticated = userContext?.email && userContext?.name;
139
139
 
140
- // If user is authenticated, silently identify them first
141
140
  if (isAuthenticated) {
142
141
  try {
143
142
  await this.apiService.identifyContact({
@@ -146,13 +145,11 @@ async _handleStartConversation(messageContent, pendingAttachments) {
146
145
  });
147
146
  console.log('[MessengerWidget] User auto-identified for conversation');
148
147
  } catch (error) {
149
- // If identification fails with a non-critical error, continue anyway
150
148
  if (error?.code !== 'ALREADY_IDENTIFIED') {
151
149
  console.warn('[MessengerWidget] Auto-identification failed:', error);
152
150
  }
153
151
  }
154
152
  } else {
155
- // Unauthenticated user - show pre-chat form
156
153
  this.messengerState.pendingMessage = {
157
154
  content: messageContent,
158
155
  attachments: pendingAttachments,
@@ -161,7 +158,6 @@ async _handleStartConversation(messageContent, pendingAttachments) {
161
158
  return null;
162
159
  }
163
160
 
164
- // Check for existing open conversation
165
161
  const openConversation = this.messengerState.conversations.find(
166
162
  (c) => c.status === 'open'
167
163
  );