@product7/product7-js 0.6.0 → 0.6.3

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.0",
3
+ "version": "0.6.3",
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",
@@ -6,13 +6,13 @@
6
6
  }
7
7
 
8
8
  .liveChat-launcher-right {
9
- bottom: var(--spacing-5);
10
- right: var(--spacing-5);
9
+ bottom: var(--liveChat-v-padding, var(--spacing-5));
10
+ right: var(--liveChat-h-padding, var(--spacing-5));
11
11
  }
12
12
 
13
13
  .liveChat-launcher-left {
14
- bottom: var(--spacing-5);
15
- left: var(--spacing-5);
14
+ bottom: var(--liveChat-v-padding, var(--spacing-5));
15
+ left: var(--liveChat-h-padding, var(--spacing-5));
16
16
  }
17
17
 
18
18
  .liveChat-launcher-btn {
@@ -92,14 +92,14 @@
92
92
  }
93
93
 
94
94
  .liveChat-panel-right {
95
- bottom: 90px;
96
- right: var(--spacing-5);
95
+ bottom: calc(var(--liveChat-v-padding, var(--spacing-5)) + 70px);
96
+ right: var(--liveChat-h-padding, var(--spacing-5));
97
97
  transform-origin: bottom right;
98
98
  }
99
99
 
100
100
  .liveChat-panel-left {
101
- bottom: 90px;
102
- left: var(--spacing-5);
101
+ bottom: calc(var(--liveChat-v-padding, var(--spacing-5)) + 70px);
102
+ left: var(--liveChat-h-padding, var(--spacing-5));
103
103
  transform-origin: bottom left;
104
104
  }
105
105
 
@@ -152,6 +152,15 @@ export class LiveChatWidget extends BaseWidget {
152
152
  const container = document.createElement('div');
153
153
  container.className = `liveChat-widget theme-${this.liveChatOptions.theme}`;
154
154
  container.style.zIndex = '999999';
155
+ container.style.setProperty(
156
+ '--liveChat-h-padding',
157
+ `${this.liveChatOptions.horizontalPadding ?? 20}px`
158
+ );
159
+ container.style.setProperty(
160
+ '--liveChat-v-padding',
161
+ `${this.liveChatOptions.verticalPadding ?? 20}px`
162
+ );
163
+ this._widgetContainer = container;
155
164
 
156
165
  applyliveChatCustomStyles({
157
166
  primaryColor: this.liveChatOptions.primaryColor,
@@ -248,7 +257,10 @@ export class LiveChatWidget extends BaseWidget {
248
257
 
249
258
  async _handleStartConversation(messageContent, pendingAttachments) {
250
259
  try {
251
- if (!this.LiveChatState.isIdentified) {
260
+ if (
261
+ this.LiveChatState.requireEmailBeforeChat &&
262
+ !this.LiveChatState.isIdentified
263
+ ) {
252
264
  this.LiveChatState.pendingMessage = {
253
265
  content: messageContent,
254
266
  attachments: pendingAttachments,
@@ -854,8 +866,9 @@ export class LiveChatWidget extends BaseWidget {
854
866
  if (!response?.status || !response?.data) return;
855
867
 
856
868
  const s = response.data;
869
+ let stylesChanged = false;
857
870
 
858
- // Only apply values that were NOT explicitly passed in options
871
+ // Team info
859
872
  if (s.team_name && !this._hasExplicitOption('teamName')) {
860
873
  this.liveChatOptions.teamName = s.team_name;
861
874
  this.LiveChatState.teamName = s.team_name;
@@ -866,12 +879,165 @@ export class LiveChatWidget extends BaseWidget {
866
879
  if (s.greeting_message && !this._hasExplicitOption('greetingMessage')) {
867
880
  this.LiveChatState.greetingMessage = s.greeting_message;
868
881
  }
882
+ if (s.welcome_message && !this._hasExplicitOption('welcomeMessage')) {
883
+ this.LiveChatState.welcomeMessage = s.welcome_message;
884
+ }
869
885
  if (s.response_time && !this._hasExplicitOption('responseTime')) {
870
886
  this.LiveChatState.responseTime = s.response_time;
871
887
  }
888
+ if (
889
+ Array.isArray(s.team_avatars) &&
890
+ s.team_avatars.length > 0 &&
891
+ !this._hasExplicitOption('teamAvatars')
892
+ ) {
893
+ this.LiveChatState.teamAvatars = s.team_avatars;
894
+ }
895
+
896
+ // Colors / theme
897
+ if (s.primary_color && !this._hasExplicitOption('primaryColor')) {
898
+ this.liveChatOptions.primaryColor = s.primary_color;
899
+ stylesChanged = true;
900
+ }
901
+ if (s.background_color && !this._hasExplicitOption('backgroundColor')) {
902
+ this.liveChatOptions.backgroundColor = s.background_color;
903
+ stylesChanged = true;
904
+ }
905
+ if (s.text_color && !this._hasExplicitOption('textColor')) {
906
+ this.liveChatOptions.textColor = s.text_color;
907
+ stylesChanged = true;
908
+ }
909
+ if (s.theme && !this._hasExplicitOption('theme')) {
910
+ this.liveChatOptions.theme = s.theme;
911
+ stylesChanged = true;
912
+ }
913
+ if (stylesChanged) {
914
+ applyliveChatCustomStyles({
915
+ primaryColor: this.liveChatOptions.primaryColor,
916
+ textColor: this.liveChatOptions.textColor,
917
+ backgroundColor: this.liveChatOptions.backgroundColor,
918
+ theme: this.liveChatOptions.theme,
919
+ });
920
+ }
921
+
922
+ // Launcher position & padding
923
+ if (s.launcher_position && !this._hasExplicitOption('position')) {
924
+ const parts = s.launcher_position.split('-');
925
+ this.liveChatOptions.position = parts[parts.length - 1] || 'right';
926
+ }
927
+ if (
928
+ typeof s.horizontal_padding === 'number' &&
929
+ !this._hasExplicitOption('horizontalPadding')
930
+ ) {
931
+ this.liveChatOptions.horizontalPadding = s.horizontal_padding;
932
+ }
933
+ if (
934
+ typeof s.vertical_padding === 'number' &&
935
+ !this._hasExplicitOption('verticalPadding')
936
+ ) {
937
+ this.liveChatOptions.verticalPadding = s.vertical_padding;
938
+ }
939
+ if (this._widgetContainer) {
940
+ this._widgetContainer.style.setProperty(
941
+ '--liveChat-h-padding',
942
+ `${this.liveChatOptions.horizontalPadding ?? 20}px`
943
+ );
944
+ this._widgetContainer.style.setProperty(
945
+ '--liveChat-v-padding',
946
+ `${this.liveChatOptions.verticalPadding ?? 20}px`
947
+ );
948
+ }
949
+
950
+ // Module visibility
951
+ if (
952
+ typeof s.home_module_enabled === 'boolean' &&
953
+ !this._hasExplicitOption('homeModuleEnabled')
954
+ ) {
955
+ this.LiveChatState.homeModuleEnabled = s.home_module_enabled;
956
+ if (
957
+ !s.home_module_enabled &&
958
+ this.LiveChatState.currentView === 'home'
959
+ ) {
960
+ this.LiveChatState.currentView =
961
+ this.LiveChatState.messagesModuleEnabled !== false
962
+ ? 'messages'
963
+ : 'help';
964
+ }
965
+ }
966
+ if (
967
+ typeof s.messages_module_enabled === 'boolean' &&
968
+ !this._hasExplicitOption('messagesModuleEnabled')
969
+ ) {
970
+ this.LiveChatState.messagesModuleEnabled = s.messages_module_enabled;
971
+ if (
972
+ !s.messages_module_enabled &&
973
+ this.LiveChatState.currentView === 'messages'
974
+ ) {
975
+ this.LiveChatState.currentView =
976
+ this.LiveChatState.homeModuleEnabled !== false ? 'home' : 'help';
977
+ }
978
+ }
979
+
980
+ // Feature flags
981
+ if (
982
+ typeof s.show_avatars === 'boolean' &&
983
+ !this._hasExplicitOption('showAvatars')
984
+ ) {
985
+ this.LiveChatState.showAvatars = s.show_avatars;
986
+ }
987
+ if (s.start_button_text && !this._hasExplicitOption('startButtonText')) {
988
+ this.LiveChatState.startButtonText = s.start_button_text;
989
+ }
990
+ if (
991
+ typeof s.require_email_before_chat === 'boolean' &&
992
+ !this._hasExplicitOption('requireEmailBeforeChat')
993
+ ) {
994
+ this.LiveChatState.requireEmailBeforeChat = s.require_email_before_chat;
995
+ }
996
+ if (
997
+ typeof s.allow_attachments === 'boolean' &&
998
+ !this._hasExplicitOption('allowAttachments')
999
+ ) {
1000
+ this.LiveChatState.allowAttachments = s.allow_attachments;
1001
+ }
1002
+ if (
1003
+ typeof s.allow_emoji === 'boolean' &&
1004
+ !this._hasExplicitOption('allowEmoji')
1005
+ ) {
1006
+ this.LiveChatState.allowEmoji = s.allow_emoji;
1007
+ }
1008
+ if (
1009
+ typeof s.show_reply_time === 'boolean' &&
1010
+ !this._hasExplicitOption('showReplyTime')
1011
+ ) {
1012
+ this.LiveChatState.showReplyTime = s.show_reply_time;
1013
+ }
1014
+
1015
+ // URLs
1016
+ if (s.feedback_url && !this._hasExplicitOption('feedbackUrl')) {
1017
+ this.liveChatOptions.feedbackUrl = s.feedback_url;
1018
+ this.LiveChatState.urls.feedback = s.feedback_url;
1019
+ }
1020
+ if (s.help_url && !this._hasExplicitOption('helpUrl')) {
1021
+ this.liveChatOptions.helpUrl = s.help_url;
1022
+ this.LiveChatState.urls.help = s.help_url;
1023
+ }
1024
+ if (s.changelog_url && !this._hasExplicitOption('changelogUrl')) {
1025
+ this.liveChatOptions.changelogUrl = s.changelog_url;
1026
+ this.LiveChatState.urls.changelog = s.changelog_url;
1027
+ }
1028
+ if (s.roadmap_url && !this._hasExplicitOption('roadmapUrl')) {
1029
+ this.liveChatOptions.roadmapUrl = s.roadmap_url;
1030
+ this.LiveChatState.urls.roadmap = s.roadmap_url;
1031
+ }
1032
+
1033
+ // Business hours availability
1034
+ if (s.availability && typeof s.availability === 'object') {
1035
+ this.LiveChatState.businessHoursState = s.availability.state || null;
1036
+ this.LiveChatState.nextOpenAt = s.availability.next_open_at || null;
1037
+ this.LiveChatState.holidayName = s.availability.holiday_name || null;
1038
+ }
872
1039
 
873
- // Notify views to re-render with new values
874
- this.LiveChatState._notify('availabilityUpdate', {});
1040
+ this.LiveChatState._notify('settingsUpdate', {});
875
1041
  } catch (e) {
876
1042
  // non-fatal
877
1043
  }
@@ -893,7 +1059,7 @@ export class LiveChatWidget extends BaseWidget {
893
1059
  this.LiveChatState.onlineCount = response.data.online_count || 0;
894
1060
  this.LiveChatState.responseTime = response.data.response_time || '';
895
1061
 
896
- if (response.data.available_agents) {
1062
+ if (response.data.available_agents && this.LiveChatState.showAvatars !== false) {
897
1063
  this.LiveChatState.setTeamAvatarsFromAgents(
898
1064
  response.data.available_agents
899
1065
  );
@@ -18,6 +18,8 @@
18
18
  this.teamAvatars = options.teamAvatars || [];
19
19
  this.greetingMessage = options.greetingMessage || 'Hi there 👋';
20
20
  this.welcomeMessage = options.welcomeMessage || 'How can we help?';
21
+ this.startButtonText = options.startButtonText || 'Send us a message';
22
+ this.showAvatars = options.showAvatars !== false;
21
23
 
22
24
  this.metadata = options.metadata || null;
23
25
  this.isIdentified = false;
@@ -25,6 +27,13 @@
25
27
 
26
28
  this.enableHelp = options.enableHelp !== false;
27
29
  this.enableChangelog = options.enableChangelog !== false;
30
+ this.homeModuleEnabled = options.homeModuleEnabled !== false;
31
+ this.messagesModuleEnabled = options.messagesModuleEnabled !== false;
32
+
33
+ this.requireEmailBeforeChat = options.requireEmailBeforeChat || false;
34
+ this.allowAttachments = options.allowAttachments !== false;
35
+ this.allowEmoji = options.allowEmoji !== false;
36
+ this.showReplyTime = options.showReplyTime !== false;
28
37
 
29
38
  this.agentsOnline = false;
30
39
  this.onlineCount = 0;
@@ -32,6 +41,10 @@
32
41
  this.responseTime =
33
42
  options.responseTime || 'We typically reply within a few minutes';
34
43
 
44
+ this.businessHoursState = options.businessHoursState || null;
45
+ this.nextOpenAt = options.nextOpenAt || null;
46
+ this.holidayName = options.holidayName || null;
47
+
35
48
  this.typingUsers = {};
36
49
 
37
50
  this.isLoading = false;
@@ -16,6 +16,9 @@
16
16
  this._unsubscribe = this.state.subscribe((type) => {
17
17
  if (type === 'viewChange' || type === 'unreadCountChange') {
18
18
  this._updateActiveTab();
19
+ } else if (type === 'settingsUpdate') {
20
+ this._updateContent();
21
+ this._updateActiveTab();
19
22
  }
20
23
  });
21
24
 
@@ -23,15 +26,20 @@
23
26
  }
24
27
 
25
28
  _getTabs() {
26
- const tabs = [
27
- { id: 'home', label: 'Home', icon: 'ph:house-simple' },
28
- {
29
+ const tabs = [];
30
+
31
+ if (this.state.homeModuleEnabled !== false) {
32
+ tabs.push({ id: 'home', label: 'Home', icon: 'ph:house-simple' });
33
+ }
34
+
35
+ if (this.state.messagesModuleEnabled !== false) {
36
+ tabs.push({
29
37
  id: 'messages',
30
38
  label: 'Messages',
31
39
  icon: 'ph:chats',
32
40
  badge: this.state.unreadCount,
33
- },
34
- ];
41
+ });
42
+ }
35
43
 
36
44
  if (this.state.enableHelp) {
37
45
  tabs.push({ id: 'help', label: 'Help', icon: 'ph:books' });
@@ -56,6 +56,8 @@
56
56
  data.conversationId === this.state.activeConversationId
57
57
  ) {
58
58
  this._updateContent();
59
+ } else if (type === 'settingsUpdate') {
60
+ this._updateContent();
59
61
  }
60
62
  });
61
63
 
@@ -102,7 +104,7 @@
102
104
  </div>
103
105
  <div class="liveChat-chat-header-info">
104
106
  <span class="liveChat-chat-title">${this._escapeHtml(teamName)}</span>
105
- <span class="liveChat-chat-subtitle">${isClosed ? 'Conversation resolved' : this.state.responseTime || 'Typically replies within minutes'}</span>
107
+ ${isClosed || this.state.showReplyTime !== false ? `<span class="liveChat-chat-subtitle">${isClosed ? 'Conversation resolved' : this.state.responseTime || 'Typically replies within minutes'}</span>` : ''}
106
108
  </div>
107
109
  <div class="liveChat-chat-header-actions">
108
110
  <div class="liveChat-chat-menu-wrapper">
@@ -164,18 +166,14 @@
164
166
  </div>
165
167
  <div class="liveChat-compose-bottom">
166
168
  <div class="liveChat-compose-actions">
167
- <button class="sdk-btn-icon liveChat-compose-attach" aria-label="Attach file">
168
- <iconify-icon icon="ph:paperclip" width="20" height="20"></iconify-icon>
169
- </button>
170
- <button class="sdk-btn-icon liveChat-emoji-btn" aria-label="Emoji">
171
- <iconify-icon icon="ph:smiley" width="20" height="20"></iconify-icon>
172
- </button>
169
+ ${this.state.allowAttachments !== false ? `<button class="sdk-btn-icon liveChat-compose-attach" aria-label="Attach file"><iconify-icon icon="ph:paperclip" width="20" height="20"></iconify-icon></button>` : ''}
170
+ ${this.state.allowEmoji !== false ? `<button class="sdk-btn-icon liveChat-emoji-btn" aria-label="Emoji"><iconify-icon icon="ph:smiley" width="20" height="20"></iconify-icon></button>` : ''}
173
171
  </div>
174
172
  <button class="liveChat-compose-send" aria-label="Send" disabled>
175
173
  <iconify-icon icon="ph:paper-plane-right" width="20" height="20"></iconify-icon>
176
174
  </button>
177
175
  </div>
178
- <input type="file" class="liveChat-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
176
+ ${this.state.allowAttachments !== false ? `<input type="file" class="liveChat-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />` : ''}
179
177
  </div>
180
178
  `
181
179
  }
@@ -16,7 +16,8 @@
16
16
  if (
17
17
  type === 'homeChangelogUpdate' ||
18
18
  type === 'conversationsUpdate' ||
19
- type === 'availabilityUpdate'
19
+ type === 'availabilityUpdate' ||
20
+ type === 'settingsUpdate'
20
21
  ) {
21
22
  this._updateContent();
22
23
  }
@@ -26,7 +27,7 @@
26
27
  }
27
28
 
28
29
  _updateContent() {
29
- const avatarsHtml = this._renderAvatarStack();
30
+ const avatarsHtml = this.state.showAvatars ? this._renderAvatarStack() : '';
30
31
  const recentChangelogHtml = this._renderRecentChangelog();
31
32
 
32
33
  this.element.innerHTML = `
@@ -77,9 +78,38 @@
77
78
  }
78
79
 
79
80
  _renderAvailabilityStatus() {
80
- const isOnline = this.state.agentsOnline;
81
+ const businessState = this.state.businessHoursState;
82
+
83
+ if (businessState === 'offline') {
84
+ let offlineText = "We're currently closed";
85
+ if (this.state.holidayName) {
86
+ offlineText = `Closed for ${this.state.holidayName}`;
87
+ } else if (this.state.nextOpenAt) {
88
+ const opens = new Date(this.state.nextOpenAt);
89
+ const now = new Date();
90
+ const diffHours = Math.round((opens - now) / 3600000);
91
+ offlineText = diffHours < 24
92
+ ? `Opens in ${diffHours}h`
93
+ : `Opens ${opens.toLocaleDateString('en-US', { weekday: 'short', hour: 'numeric', hour12: true })}`;
94
+ }
95
+ return `
96
+ <div class="liveChat-home-availability">
97
+ <span class="liveChat-availability-dot liveChat-availability-away"></span>
98
+ <span class="liveChat-availability-text">${offlineText}</span>
99
+ </div>
100
+ `;
101
+ }
102
+
103
+ if (businessState === 'away') {
104
+ return `
105
+ <div class="liveChat-home-availability">
106
+ <span class="liveChat-availability-dot liveChat-availability-away"></span>
107
+ <span class="liveChat-availability-text">${this.state.responseTime}</span>
108
+ </div>
109
+ `;
110
+ }
81
111
 
82
- if (isOnline) {
112
+ if (this.state.agentsOnline) {
83
113
  return `
84
114
  <div class="liveChat-home-availability">
85
115
  <span class="liveChat-availability-dot liveChat-availability-online"></span>
@@ -115,7 +145,7 @@
115
145
  ${recentCardHtml}
116
146
  <button class="liveChat-home-message-btn">
117
147
  <div class="liveChat-home-continue-info">
118
- <span class="liveChat-home-continue-label">Send us a message</span>
148
+ <span class="liveChat-home-continue-label">${this.state.startButtonText}</span>
119
149
  <span class="liveChat-home-message-subtext">${responseTime}</span>
120
150
  </div>
121
151
  ${sendIcon}