@product7/product7-js 0.6.7 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@product7/product7-js",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "JavaScript SDK for integrating Product7 feedback widgets into any website",
5
5
  "main": "dist/product7-js.js",
6
6
  "module": "src/index.js",
@@ -4,6 +4,7 @@ import { HelpService } from '../api/services/HelpService.js';
4
4
  import { LiveChatService } from '../api/services/LiveChatService.js';
5
5
  import { SurveyService } from '../api/services/SurveyService.js';
6
6
  import { BaseAPIService } from './BaseAPIService.js';
7
+ import { getIpInfo } from '../utils/getIpInfo.js';
7
8
 
8
9
  export class APIService extends BaseAPIService {
9
10
  constructor(config = {}) {
@@ -108,6 +109,9 @@ export class APIService extends BaseAPIService {
108
109
  if (metadata.attributes) payload.attributes = metadata.attributes;
109
110
  if (metadata.company) payload.company = metadata.company;
110
111
 
112
+ const ipInfo = await getIpInfo();
113
+ if (ipInfo) payload.ip_info = ipInfo;
114
+
111
115
  const response = await this._makeRequest('/widget/identify', {
112
116
  method: 'POST',
113
117
  headers: {
@@ -839,4 +839,163 @@
839
839
  opacity: 0.7;
840
840
  cursor: not-allowed;
841
841
  }
842
+
843
+ /* ========================================
844
+ FEEDBACK FORM VIEW
845
+ ======================================== */
846
+
847
+ .liveChat-feedback-view {
848
+ display: flex;
849
+ flex-direction: column;
850
+ height: 100%;
851
+ overflow: hidden;
852
+ }
853
+
854
+ .liveChat-feedback-header {
855
+ display: flex;
856
+ align-items: center;
857
+ gap: var(--spacing-3);
858
+ padding: var(--spacing-4) var(--spacing-4);
859
+ border-bottom: 1px solid var(--border-color);
860
+ flex-shrink: 0;
861
+ }
862
+
863
+ .liveChat-feedback-title {
864
+ font-size: var(--font-size-base);
865
+ font-weight: var(--font-weight-semibold);
866
+ color: var(--text-primary);
867
+ }
868
+
869
+ .liveChat-feedback-body {
870
+ display: flex;
871
+ flex-direction: column;
872
+ gap: var(--spacing-4);
873
+ padding: var(--spacing-5) var(--spacing-4);
874
+ flex: 1;
875
+ overflow-y: auto;
876
+ }
877
+
878
+ .liveChat-feedback-prompt {
879
+ margin: 0;
880
+ font-size: var(--font-size-sm);
881
+ color: var(--text-secondary);
882
+ }
883
+
884
+
885
+ .liveChat-feedback-input {
886
+ width: 100%;
887
+ border: 1px solid var(--border-color);
888
+ border-radius: var(--radius-md);
889
+ padding: var(--spacing-3);
890
+ font-size: var(--font-size-sm);
891
+ font-family: inherit;
892
+ color: var(--text-primary);
893
+ background: var(--msg-bg);
894
+ box-sizing: border-box;
895
+ transition: border-color var(--transition-fast);
896
+ }
897
+
898
+ .liveChat-feedback-input::placeholder {
899
+ color: var(--msg-text-secondary);
900
+ }
901
+
902
+ .liveChat-feedback-input:focus {
903
+ outline: none;
904
+ border-color: var(--color-primary);
905
+ }
906
+
907
+ .liveChat-feedback-textarea {
908
+ width: 100%;
909
+ resize: none;
910
+ border: 1px solid var(--border-color);
911
+ border-radius: var(--radius-md);
912
+ padding: var(--spacing-3);
913
+ font-size: var(--font-size-sm);
914
+ font-family: inherit;
915
+ color: var(--text-primary);
916
+ background: var(--msg-bg);
917
+ box-sizing: border-box;
918
+ transition: border-color var(--transition-fast);
919
+ }
920
+
921
+ .liveChat-feedback-textarea::placeholder {
922
+ color: var(--msg-text-secondary);
923
+ }
924
+
925
+ .liveChat-feedback-textarea:focus {
926
+ outline: none;
927
+ border-color: var(--color-primary);
928
+ }
929
+
930
+ .liveChat-feedback-submit {
931
+ display: flex;
932
+ align-items: center;
933
+ justify-content: center;
934
+ padding: var(--spacing-3);
935
+ border-radius: var(--radius-md);
936
+ font-size: var(--font-size-sm);
937
+ font-weight: var(--font-weight-medium);
938
+ font-family: inherit;
939
+ cursor: pointer;
940
+ border: none;
941
+ background: var(--color-primary);
942
+ color: #ffffff;
943
+ transition: all var(--transition-fast);
944
+ }
945
+
946
+ .liveChat-feedback-submit:hover:not(:disabled) {
947
+ background: var(--color-primary-hover);
948
+ }
949
+
950
+ .liveChat-feedback-submit:disabled {
951
+ opacity: 0.5;
952
+ cursor: not-allowed;
953
+ }
954
+
955
+ .liveChat-feedback-thankyou {
956
+ display: flex;
957
+ flex-direction: column;
958
+ align-items: center;
959
+ justify-content: center;
960
+ flex: 1;
961
+ padding: var(--spacing-6) var(--spacing-4);
962
+ text-align: center;
963
+ gap: var(--spacing-3);
964
+ }
965
+
966
+ .liveChat-feedback-thankyou-emoji {
967
+ font-size: 48px;
968
+ line-height: 1;
969
+ }
970
+
971
+ .liveChat-feedback-thankyou h3 {
972
+ margin: 0;
973
+ font-size: var(--font-size-lg);
974
+ font-weight: var(--font-weight-semibold);
975
+ color: var(--text-primary);
976
+ }
977
+
978
+ .liveChat-feedback-thankyou p {
979
+ margin: 0;
980
+ font-size: var(--font-size-sm);
981
+ color: var(--text-secondary);
982
+ }
983
+
984
+ .liveChat-feedback-done-btn {
985
+ margin-top: var(--spacing-2);
986
+ padding: var(--spacing-2) var(--spacing-5);
987
+ border-radius: var(--radius-md);
988
+ font-size: var(--font-size-sm);
989
+ font-weight: var(--font-weight-medium);
990
+ font-family: inherit;
991
+ cursor: pointer;
992
+ border: 1px solid var(--border-color);
993
+ background: transparent;
994
+ color: var(--text-primary);
995
+ transition: all var(--transition-fast);
996
+ }
997
+
998
+ .liveChat-feedback-done-btn:hover {
999
+ background: var(--bg-secondary);
1000
+ }
842
1001
  `;
@@ -1,5 +1,9 @@
1
1
  export const liveChatCoreStyles = `
2
2
 
3
+ .liveChat-widget * {
4
+ font-weight: 500;
5
+ }
6
+
3
7
  .liveChat-launcher {
4
8
  position: fixed;
5
9
  z-index: var(--z-modal);
@@ -0,0 +1,17 @@
1
+ let _cached = null;
2
+
3
+ export async function getIpInfo() {
4
+ if (_cached) return _cached;
5
+
6
+ try {
7
+ const res = await fetch('http://ip-api.com/json');
8
+ if (!res.ok) return null;
9
+ const data = await res.json();
10
+ if (data.status === 'success') {
11
+ _cached = data;
12
+ }
13
+ return _cached;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
@@ -13,6 +13,7 @@ import { ChatView } from './liveChat/views/ChatView.js';
13
13
  import { ConversationsView } from './liveChat/views/ConversationsView.js';
14
14
  import { HelpView } from './liveChat/views/HelpView.js';
15
15
  import { HomeView } from './liveChat/views/HomeView.js';
16
+ import { FeedbackFormView } from './liveChat/views/FeedbackFormView.js';
16
17
  import { PreChatFormView } from './liveChat/views/PreChatFormView.js';
17
18
 
18
19
  export class LiveChatWidget extends BaseWidget {
@@ -208,6 +209,7 @@ export class LiveChatWidget extends BaseWidget {
208
209
  this.panel.registerView('messages', ConversationsView);
209
210
  this.panel.registerView('chat', ChatView);
210
211
  this.panel.registerView('prechat', PreChatFormView);
212
+ this.panel.registerView('feedback', FeedbackFormView);
211
213
  this.panel.registerView('help', HelpView);
212
214
  this.panel.registerView('changelog', ChangelogView);
213
215
 
@@ -95,11 +95,12 @@ export class LiveChatPanel {
95
95
  </div>`;
96
96
  }
97
97
 
98
- // Hide nav in chat and prechat views
98
+ // Hide nav in chat, prechat and feedback views
99
99
  if (navContainer) {
100
100
  const hideNav =
101
101
  this.state.currentView === 'chat' ||
102
- this.state.currentView === 'prechat';
102
+ this.state.currentView === 'prechat' ||
103
+ this.state.currentView === 'feedback';
103
104
  navContainer.style.display = hideNav ? 'none' : '';
104
105
  }
105
106
  }
@@ -0,0 +1,104 @@
1
+ export class FeedbackFormView {
2
+ constructor(state, options = {}) {
3
+ this.state = state;
4
+ this.options = options;
5
+ this.element = null;
6
+ this._isSubmitting = false;
7
+ this._selectedRating = null;
8
+ }
9
+
10
+ render() {
11
+ this.element = document.createElement('div');
12
+ this.element.className = 'liveChat-view liveChat-feedback-view';
13
+ this._renderForm();
14
+ return this.element;
15
+ }
16
+
17
+ _renderForm() {
18
+ this.element.innerHTML = `
19
+ <div class="liveChat-feedback-header">
20
+ <button class="sdk-btn-icon liveChat-feedback-back-btn">
21
+ <iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
22
+ </button>
23
+ <span class="liveChat-feedback-title">Leave us feedback</span>
24
+ </div>
25
+ <div class="liveChat-feedback-body">
26
+ <p class="liveChat-feedback-prompt">Share your thoughts with us. We read every message.</p>
27
+ <input
28
+ type="text"
29
+ class="liveChat-feedback-input"
30
+ placeholder="Title"
31
+ />
32
+ <textarea
33
+ class="liveChat-feedback-textarea"
34
+ placeholder="Your feedback..."
35
+ rows="5"
36
+ ></textarea>
37
+ <button class="liveChat-feedback-submit">Send feedback</button>
38
+ </div>
39
+ `;
40
+ this._attachEvents();
41
+ }
42
+
43
+ _renderThankYou() {
44
+ this.element.innerHTML = `
45
+ <div class="liveChat-feedback-header">
46
+ <button class="sdk-btn-icon liveChat-feedback-back-btn">
47
+ <iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
48
+ </button>
49
+ <span class="liveChat-feedback-title">Leave us feedback</span>
50
+ </div>
51
+ <div class="liveChat-feedback-thankyou">
52
+ <span class="liveChat-feedback-thankyou-emoji">🙏</span>
53
+ <h3>Thanks for your feedback!</h3>
54
+ <p>We appreciate you taking the time to share your thoughts.</p>
55
+ <button class="liveChat-feedback-done-btn">Done</button>
56
+ </div>
57
+ `;
58
+ this.element.querySelector('.liveChat-feedback-back-btn').addEventListener('click', () => {
59
+ this.state.setView('home');
60
+ });
61
+ this.element.querySelector('.liveChat-feedback-done-btn').addEventListener('click', () => {
62
+ this.state.setView('home');
63
+ });
64
+ }
65
+
66
+ _attachEvents() {
67
+ this.element.querySelector('.liveChat-feedback-back-btn').addEventListener('click', () => {
68
+ this.state.setView('home');
69
+ });
70
+
71
+ const submitBtn = this.element.querySelector('.liveChat-feedback-submit');
72
+ submitBtn.addEventListener('click', async () => {
73
+ if (this._isSubmitting) return;
74
+ const title = this.element.querySelector('.liveChat-feedback-input').value.trim();
75
+ const message = this.element.querySelector('.liveChat-feedback-textarea').value.trim();
76
+ await this._submit(title, message);
77
+ });
78
+ }
79
+
80
+ async _submit(title, message) {
81
+ this._isSubmitting = true;
82
+ const submitBtn = this.element.querySelector('.liveChat-feedback-submit');
83
+ if (submitBtn) {
84
+ submitBtn.disabled = true;
85
+ submitBtn.textContent = 'Sending...';
86
+ }
87
+
88
+ try {
89
+ if (this.options.onSubmitFeedback) {
90
+ await this.options.onSubmitFeedback({ title, message });
91
+ }
92
+ } catch (e) {
93
+ console.warn('[FeedbackFormView] Submit error:', e);
94
+ }
95
+
96
+ this._renderThankYou();
97
+ }
98
+
99
+ destroy() {
100
+ if (this.element && this.element.parentNode) {
101
+ this.element.parentNode.removeChild(this.element);
102
+ }
103
+ }
104
+ }
@@ -136,8 +136,11 @@
136
136
  const sendIcon = `<iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
137
137
  const caretIcon = `<iconify-icon icon="ph:caret-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
138
138
 
139
- const responseTime =
140
- this.state.responseTime || 'We typically reply within a few minutes';
139
+ const isUnavailable = this.state.businessHoursState === 'offline' || this.state.businessHoursState === 'away';
140
+ const buttonLabel = isUnavailable ? 'Leave us a message' : this.state.startButtonText;
141
+ const buttonSubtext = isUnavailable
142
+ ? "We'll get back to you when we're back"
143
+ : (this.state.responseTime || 'We typically reply within a few minutes');
141
144
 
142
145
  const recentCardHtml = openConversation
143
146
  ? this._renderRecentMessageCard(openConversation)
@@ -147,8 +150,8 @@
147
150
  ${recentCardHtml}
148
151
  <button class="liveChat-home-message-btn">
149
152
  <div class="liveChat-home-continue-info">
150
- <span class="liveChat-home-continue-label">${this.state.startButtonText}</span>
151
- <span class="liveChat-home-message-subtext">${responseTime}</span>
153
+ <span class="liveChat-home-continue-label">${buttonLabel}</span>
154
+ <span class="liveChat-home-message-subtext">${buttonSubtext}</span>
152
155
  </div>
153
156
  ${sendIcon}
154
157
  </button>
@@ -312,12 +315,7 @@
312
315
  const feedbackBtn = this.element.querySelector('.liveChat-feedback-btn');
313
316
  if (feedbackBtn) {
314
317
  feedbackBtn.addEventListener('click', () => {
315
- if (this.options.onFeedbackClick) {
316
- this.state.setOpen(false);
317
- this.options.onFeedbackClick();
318
- } else if (this.state.urls?.feedback) {
319
- window.open(this.state.urls.feedback, '_blank');
320
- }
318
+ this.state.setView('feedback');
321
319
  });
322
320
  }
323
321