@product7/feedback-sdk 1.3.6 → 1.3.8

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.
@@ -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() {
@@ -71,8 +70,6 @@ export class ChatView {
71
70
  ? 'Conversation closed'
72
71
  : 'Write a message...';
73
72
 
74
- const existingName = this.state.userContext?.name || '';
75
-
76
73
  this.element.innerHTML = `
77
74
  <div class="messenger-chat-header">
78
75
  <button class="sdk-btn-icon messenger-back-btn" aria-label="Back">
@@ -125,46 +122,17 @@ export class ChatView {
125
122
  <input type="file" class="messenger-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
126
123
  </div>
127
124
  `}
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>
149
125
  `;
150
126
 
151
- this._typingIndicator = this.element.querySelector(
152
- '.messenger-typing-indicator'
153
- );
127
+ this._typingIndicator = this.element.querySelector('.messenger-typing-indicator');
154
128
  this._attachEvents();
155
129
  this._scrollToBottom();
156
130
  this._renderAttachmentPreviews();
157
-
158
- // Show email overlay after first message sent without email
159
- if (this._showEmailOverlayFlag) {
160
- this._showEmailOverlay();
161
- }
162
131
  }
163
132
 
164
133
  _renderEmptyState(isNewConversation = false) {
165
134
  const avatarHtml = this._renderTeamAvatars();
166
- const responseTime =
167
- this.state.responseTime || 'We typically reply within a few minutes';
135
+ const responseTime = this.state.responseTime || 'We typically reply within a few minutes';
168
136
  const isOnline = this.state.agentsOnline;
169
137
 
170
138
  return `
@@ -196,14 +164,11 @@ export class ChatView {
196
164
 
197
165
  _renderMessage(message) {
198
166
  const isOwn = message.isOwn;
199
- const messageClass = isOwn
200
- ? 'messenger-message-own'
201
- : 'messenger-message-received';
167
+ const messageClass = isOwn ? 'messenger-message-own' : 'messenger-message-received';
202
168
  const timeStr = this._formatMessageTime(message.timestamp);
203
169
  const attachmentsHtml = this._renderMessageAttachments(message.attachments);
204
170
 
205
171
  const contentHtml = message.content ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>` : '';
206
-
207
172
  const bubbleHtml = contentHtml ? `<div class="messenger-message-bubble">${contentHtml}</div>` : '';
208
173
 
209
174
  if (isOwn) {
@@ -293,9 +258,7 @@ export class ChatView {
293
258
  }
294
259
 
295
260
  _appendMessage(message) {
296
- const messagesContainer = this.element.querySelector(
297
- '.messenger-chat-messages'
298
- );
261
+ const messagesContainer = this.element.querySelector('.messenger-chat-messages');
299
262
  const emptyState = messagesContainer.querySelector('.messenger-chat-empty');
300
263
  if (emptyState) {
301
264
  emptyState.remove();
@@ -308,9 +271,7 @@ export class ChatView {
308
271
  }
309
272
 
310
273
  _scrollToBottom() {
311
- const messagesContainer = this.element.querySelector(
312
- '.messenger-chat-messages'
313
- );
274
+ const messagesContainer = this.element.querySelector('.messenger-chat-messages');
314
275
  if (messagesContainer) {
315
276
  setTimeout(() => {
316
277
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
@@ -350,7 +311,6 @@ export class ChatView {
350
311
  `;
351
312
  }).join('');
352
313
 
353
- // Attach remove button events
354
314
  container.querySelectorAll('.messenger-attachment-remove').forEach((btn) => {
355
315
  btn.addEventListener('click', (e) => {
356
316
  const idx = parseInt(e.currentTarget.dataset.index, 10);
@@ -374,20 +334,15 @@ export class ChatView {
374
334
  this.state.setOpen(false);
375
335
  });
376
336
 
377
- // Compose input (not rendered when conversation is closed)
378
337
  const input = this.element.querySelector('.messenger-compose-input');
379
338
  const sendBtn = this.element.querySelector('.messenger-compose-send');
380
339
 
381
340
  if (input && sendBtn) {
382
341
  input.addEventListener('input', () => {
383
- // Auto-resize textarea
384
342
  input.style.height = 'auto';
385
343
  input.style.height = Math.min(input.scrollHeight, 120) + 'px';
386
-
387
- // Enable/disable send button
388
344
  this._updateSendButtonState();
389
345
 
390
- // Send typing indicator
391
346
  if (input.value.trim()) {
392
347
  this._startTyping();
393
348
  }
@@ -405,7 +360,6 @@ export class ChatView {
405
360
  });
406
361
  }
407
362
 
408
- // Attach button + file input
409
363
  const attachBtn = this.element.querySelector('.messenger-compose-attach');
410
364
  const fileInput = this.element.querySelector('.messenger-compose-file-input');
411
365
 
@@ -434,42 +388,9 @@ export class ChatView {
434
388
  });
435
389
  }
436
390
 
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
391
  const messagesContainer = this.element.querySelector('.messenger-chat-messages');
470
392
  if (messagesContainer) {
471
393
  messagesContainer.addEventListener('click', (e) => {
472
- // File click -> download
473
394
  const fileLink = e.target.closest('.messenger-message-file');
474
395
  if (fileLink) {
475
396
  e.preventDefault();
@@ -479,7 +400,6 @@ export class ChatView {
479
400
  return;
480
401
  }
481
402
 
482
- // Image click -> open in new tab
483
403
  const img = e.target.closest('.messenger-message-image');
484
404
  if (img) {
485
405
  const url = img.dataset.url || img.src;
@@ -503,7 +423,6 @@ export class ChatView {
503
423
  document.body.removeChild(a);
504
424
  URL.revokeObjectURL(blobUrl);
505
425
  } catch {
506
- // Fallback: open in new tab
507
426
  window.open(url, '_blank');
508
427
  }
509
428
  }
@@ -513,72 +432,9 @@ export class ChatView {
513
432
  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
514
433
  }
515
434
 
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
- }
578
- }
579
-
580
435
  async _sendMessage() {
581
436
  if (this._isConversationClosed) return;
437
+
582
438
  const input = this.element.querySelector('.messenger-compose-input');
583
439
  const content = input.value.trim();
584
440
  const hasAttachments = this._pendingAttachments.length > 0;
@@ -587,37 +443,12 @@ export class ChatView {
587
443
 
588
444
  this._stopTyping();
589
445
 
590
- // Collect attachments to upload
591
446
  const attachmentsToSend = [...this._pendingAttachments];
592
-
593
447
  const isNewConversation = !this.state.activeConversationId;
594
- const needsContactInfo = !this.state.userContext?.email;
595
448
 
596
449
  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
- }
450
+ if (this.options.onStartConversation) {
451
+ this.options.onStartConversation(content, attachmentsToSend);
621
452
  }
622
453
  } else {
623
454
  const message = {
@@ -639,7 +470,6 @@ export class ChatView {
639
470
  }
640
471
  }
641
472
 
642
- // Clear input and attachments
643
473
  input.value = '';
644
474
  input.style.height = 'auto';
645
475
  this._pendingAttachments = [];
@@ -680,9 +510,7 @@ export class ChatView {
680
510
  _showTypingIndicator(userName) {
681
511
  if (this._typingIndicator) {
682
512
  this._typingIndicator.style.display = 'flex';
683
- const textEl = this._typingIndicator.querySelector(
684
- '.messenger-typing-text'
685
- );
513
+ const textEl = this._typingIndicator.querySelector('.messenger-typing-text');
686
514
  if (textEl) {
687
515
  textEl.textContent = `${userName || 'Support'} is typing...`;
688
516
  }
@@ -99,7 +99,6 @@ export class PreChatFormView {
99
99
  }
100
100
 
101
101
  _attachEvents() {
102
- // Form validation
103
102
  const form = this.element.querySelector('form');
104
103
  const emailInput = this.element.querySelector('#messenger-prechat-email');
105
104
  const submitBtn = this.element.querySelector('.messenger-prechat-submit');
@@ -184,15 +183,22 @@ export class PreChatFormView {
184
183
  await this.options.onIdentifyContact({ name, email });
185
184
  }
186
185
 
187
- if (!this.state.userContext) {
188
- this.state.userContext = {};
189
- }
190
- this.state.userContext.name = name;
191
- this.state.userContext.email = email;
186
+ this.state.setIdentified(true, { name, email });
192
187
 
193
188
  this._isSubmitting = false;
194
189
 
195
- this.state.setView('chat');
190
+ const pendingMessage = this.state.pendingMessage;
191
+ if (pendingMessage && this.options.onStartConversation) {
192
+ this.state.pendingMessage = null;
193
+ this.state.setView('chat');
194
+
195
+ await this.options.onStartConversation(
196
+ pendingMessage.content,
197
+ pendingMessage.attachments
198
+ );
199
+ } else {
200
+ this.state.setView('chat');
201
+ }
196
202
  } catch (error) {
197
203
  console.error('[PreChatFormView] Error submitting form:', error);
198
204
  this._showError('messenger-email-error', 'Something went wrong. Please try again.');
@@ -209,4 +215,4 @@ export class PreChatFormView {
209
215
  this.element.parentNode.removeChild(this.element);
210
216
  }
211
217
  }
212
- }
218
+ }