@product7/product7-js 0.1.0

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 (58) hide show
  1. package/README.md +1025 -0
  2. package/dist/README.md +1025 -0
  3. package/dist/product7-js.js +14658 -0
  4. package/dist/product7-js.js.map +1 -0
  5. package/dist/product7-js.min.js +2 -0
  6. package/dist/product7-js.min.js.map +1 -0
  7. package/package.json +114 -0
  8. package/src/api/mock-data/index.js +360 -0
  9. package/src/api/services/ChangelogService.js +28 -0
  10. package/src/api/services/FeedbackService.js +44 -0
  11. package/src/api/services/HelpService.js +50 -0
  12. package/src/api/services/MessengerService.js +279 -0
  13. package/src/api/services/SurveyService.js +127 -0
  14. package/src/api/utils/helpers.js +30 -0
  15. package/src/core/APIService.js +303 -0
  16. package/src/core/BaseAPIService.js +298 -0
  17. package/src/core/EventBus.js +54 -0
  18. package/src/core/Product7.js +812 -0
  19. package/src/core/WebSocketService.js +275 -0
  20. package/src/docs/api.md +226 -0
  21. package/src/docs/example.md +461 -0
  22. package/src/docs/framework-integrations.md +714 -0
  23. package/src/docs/installation.md +281 -0
  24. package/src/index.js +894 -0
  25. package/src/styles/base.js +50 -0
  26. package/src/styles/changelog.js +665 -0
  27. package/src/styles/components.js +553 -0
  28. package/src/styles/design-tokens.js +124 -0
  29. package/src/styles/feedback.js +325 -0
  30. package/src/styles/messenger-components.js +632 -0
  31. package/src/styles/messenger-core.js +233 -0
  32. package/src/styles/messenger-features.js +169 -0
  33. package/src/styles/messenger-views.js +877 -0
  34. package/src/styles/messenger.js +17 -0
  35. package/src/styles/messengerCustomStyles.js +114 -0
  36. package/src/styles/styles.js +26 -0
  37. package/src/styles/survey.js +894 -0
  38. package/src/utils/errors.js +142 -0
  39. package/src/utils/helpers.js +219 -0
  40. package/src/widgets/BaseWidget.js +548 -0
  41. package/src/widgets/ButtonWidget.js +104 -0
  42. package/src/widgets/ChangelogWidget.js +615 -0
  43. package/src/widgets/InlineWidget.js +148 -0
  44. package/src/widgets/MessengerWidget.js +979 -0
  45. package/src/widgets/SurveyWidget.js +1325 -0
  46. package/src/widgets/TabWidget.js +45 -0
  47. package/src/widgets/WidgetFactory.js +70 -0
  48. package/src/widgets/messenger/MessengerState.js +323 -0
  49. package/src/widgets/messenger/components/MessengerLauncher.js +124 -0
  50. package/src/widgets/messenger/components/MessengerPanel.js +111 -0
  51. package/src/widgets/messenger/components/NavigationTabs.js +130 -0
  52. package/src/widgets/messenger/views/ChangelogView.js +167 -0
  53. package/src/widgets/messenger/views/ChatView.js +592 -0
  54. package/src/widgets/messenger/views/ConversationsView.js +244 -0
  55. package/src/widgets/messenger/views/HelpView.js +239 -0
  56. package/src/widgets/messenger/views/HomeView.js +300 -0
  57. package/src/widgets/messenger/views/PreChatFormView.js +109 -0
  58. package/types/index.d.ts +341 -0
@@ -0,0 +1,300 @@
1
+ export class HomeView {
2
+ constructor(state, options = {}) {
3
+ this.state = state;
4
+ this.options = options;
5
+ this.element = null;
6
+ this._unsubscribe = null;
7
+ }
8
+
9
+ render() {
10
+ this.element = document.createElement('div');
11
+ this.element.className = 'messenger-view messenger-home-view';
12
+
13
+ this._updateContent();
14
+
15
+ this._unsubscribe = this.state.subscribe((type) => {
16
+ if (
17
+ type === 'homeChangelogUpdate' ||
18
+ type === 'conversationsUpdate' ||
19
+ type === 'availabilityUpdate'
20
+ ) {
21
+ this._updateContent();
22
+ }
23
+ });
24
+
25
+ return this.element;
26
+ }
27
+
28
+ _updateContent() {
29
+ const avatarsHtml = this._renderAvatarStack();
30
+ const recentChangelogHtml = this._renderRecentChangelog();
31
+
32
+ this.element.innerHTML = `
33
+ <div class="messenger-home-scroll">
34
+ <div class="messenger-home-header">
35
+ <div class="messenger-home-header-top">
36
+ <div class="messenger-home-logo">
37
+ ${this.options.logoUrl ? `<img src="${this.options.logoUrl}" alt="${this.state.teamName}" />` : ''}
38
+ </div>
39
+ <div class="messenger-home-avatars">${avatarsHtml}</div>
40
+ <button class="sdk-close-btn" aria-label="Close">
41
+ <iconify-icon icon="ph:x-duotone" width="18" height="18"></iconify-icon>
42
+ </button>
43
+ </div>
44
+ <div class="messenger-home-welcome">
45
+ <span class="messenger-home-greeting">${this.state.greetingMessage}</span>
46
+ <span class="messenger-home-question">${this.state.welcomeMessage}</span>
47
+ </div>
48
+ </div>
49
+
50
+ <div class="messenger-home-body">
51
+ ${this._renderMessageButton()}
52
+ ${this._renderFeaturedCard()}
53
+ ${recentChangelogHtml}
54
+ </div>
55
+ </div>
56
+ `;
57
+
58
+ this._attachEvents();
59
+ }
60
+
61
+ _renderAvatarStack() {
62
+ const avatars = this.state.teamAvatars;
63
+ const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500'];
64
+
65
+ if (!avatars || avatars.length === 0) {
66
+ return `
67
+ <div class="messenger-avatar-stack">
68
+ <div class="sdk-avatar sdk-avatar-lg" style="background: ${colors[0]};">S</div>
69
+ <div class="sdk-avatar sdk-avatar-lg" style="background: ${colors[1]};">T</div>
70
+ </div>
71
+ `;
72
+ }
73
+
74
+ const avatarItems = avatars
75
+ .slice(0, 4)
76
+ .map((avatar, index) => {
77
+ if (typeof avatar === 'string' && avatar.startsWith('http')) {
78
+ return `<div class="sdk-avatar sdk-avatar-lg"><img src="${avatar}" alt="Team member" /></div>`;
79
+ }
80
+ return `<div class="sdk-avatar sdk-avatar-lg" style="background: ${colors[index % colors.length]};">${avatar.charAt(0).toUpperCase()}</div>`;
81
+ })
82
+ .join('');
83
+
84
+ return `<div class="messenger-avatar-stack">${avatarItems}</div>`;
85
+ }
86
+
87
+ _renderAvailabilityStatus() {
88
+ const isOnline = this.state.agentsOnline;
89
+ const responseTime =
90
+ this.state.responseTime || 'We typically reply within a few minutes';
91
+
92
+ if (isOnline) {
93
+ return `
94
+ <div class="messenger-home-availability">
95
+ <span class="messenger-availability-dot messenger-availability-online"></span>
96
+ <span class="messenger-availability-text">We're online now</span>
97
+ </div>
98
+ `;
99
+ }
100
+
101
+ return `
102
+ <div class="messenger-home-availability">
103
+ <span class="messenger-availability-dot messenger-availability-away"></span>
104
+ <span class="messenger-availability-text">${responseTime}</span>
105
+ </div>
106
+ `;
107
+ }
108
+
109
+ _renderMessageButton() {
110
+ const openConversation = this.state.conversations.find(
111
+ (c) => c.status === 'open'
112
+ );
113
+
114
+ const sendIcon = `<iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
115
+ const caretIcon = `<iconify-icon icon="ph:caret-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
116
+
117
+ if (openConversation) {
118
+ return `
119
+ <button class="messenger-home-message-btn messenger-home-continue-btn" data-conversation-id="${openConversation.id}">
120
+ <div class="messenger-home-continue-info">
121
+ <span class="messenger-home-continue-label">Continue conversation</span>
122
+ </div>
123
+ ${sendIcon}
124
+ </button>
125
+ <button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
126
+ <span class="messenger-home-continue-label">Leave us feedback</span>
127
+ ${caretIcon}
128
+ </button>
129
+ `;
130
+ }
131
+
132
+ return `
133
+ <button class="messenger-home-message-btn">
134
+ <span>Start a conversation</span>
135
+ ${sendIcon}
136
+ </button>
137
+ <button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
138
+ <span>Leave us feedback</span>
139
+ ${caretIcon}
140
+ </button>
141
+ `;
142
+ }
143
+
144
+ _renderFeaturedCard() {
145
+ if (!this.options.featuredContent) {
146
+ return '';
147
+ }
148
+
149
+ const { title, description, imageUrl, action } =
150
+ this.options.featuredContent;
151
+
152
+ return `
153
+ <div class="messenger-home-featured">
154
+ ${imageUrl ? `<img src="${imageUrl}" alt="${title}" class="messenger-home-featured-image" onerror="this.style.display='none';" />` : ''}
155
+ <div class="messenger-home-featured-divider"></div>
156
+ <div class="messenger-home-featured-content">
157
+ <h3>${title}</h3>
158
+ ${description ? `<p>${description}</p>` : ''}
159
+ ${action ? `<button class="sdk-btn sdk-btn-primary messenger-home-featured-btn" data-action="${action.type}" data-value="${action.value}">${action.label}</button>` : ''}
160
+ </div>
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ _renderRecentChangelog() {
166
+ const changelogItems = this.state.homeChangelogItems;
167
+ if (changelogItems.length === 0) {
168
+ return '';
169
+ }
170
+
171
+ const changelogHtml = changelogItems
172
+ .map(
173
+ (item) => `
174
+ <div class="messenger-home-changelog-card" data-changelog-id="${item.id}">
175
+ ${
176
+ item.coverImage
177
+ ? `
178
+ <div class="messenger-home-changelog-cover">
179
+ <img src="${item.coverImage}" alt="${item.title}" onerror="this.style.display='none';" />
180
+ ${item.coverText ? `<span class="messenger-home-changelog-cover-text">${item.coverText}</span>` : ''}
181
+ </div>
182
+ `
183
+ : ''
184
+ }
185
+ <div class="messenger-home-changelog-card-content">
186
+ <h4 class="messenger-home-changelog-card-title">${item.title}</h4>
187
+ <p class="messenger-home-changelog-card-desc">${item.description || ''}</p>
188
+ </div>
189
+ </div>
190
+ `
191
+ )
192
+ .join('');
193
+
194
+ return `
195
+ <div class="messenger-home-changelog-section">
196
+ ${changelogHtml}
197
+ </div>
198
+ `;
199
+ }
200
+
201
+ _formatDate(dateString) {
202
+ if (!dateString) return '';
203
+ const date = new Date(dateString);
204
+ const now = new Date();
205
+ const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
206
+
207
+ if (diffDays === 0) return 'Today';
208
+ if (diffDays === 1) return 'Yesterday';
209
+ if (diffDays < 7) return `${diffDays}d ago`;
210
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
211
+ }
212
+
213
+ _attachEvents() {
214
+ this.element
215
+ .querySelector('.sdk-close-btn')
216
+ .addEventListener('click', () => {
217
+ this.state.setOpen(false);
218
+ });
219
+
220
+ const msgBtn = this.element.querySelector(
221
+ '.messenger-home-message-btn:not(.messenger-feedback-btn)'
222
+ );
223
+ if (msgBtn) {
224
+ msgBtn.addEventListener('click', () => {
225
+ const convId = msgBtn.dataset.conversationId;
226
+ if (convId) {
227
+ this.state.setActiveConversation(convId);
228
+ this.state.setView('chat');
229
+ if (this.options.onSelectConversation) {
230
+ this.options.onSelectConversation(convId);
231
+ }
232
+ } else {
233
+ this.state.setActiveConversation(null);
234
+ this.state.setView('chat');
235
+ }
236
+ });
237
+ }
238
+
239
+ const feedbackBtn = this.element.querySelector('.messenger-feedback-btn');
240
+ if (feedbackBtn) {
241
+ feedbackBtn.addEventListener('click', () => {
242
+ if (this.options.onFeedbackClick) {
243
+ // Close the messenger panel first, then open the feedback modal
244
+ this.state.setOpen(false);
245
+ setTimeout(() => this.options.onFeedbackClick(), 200);
246
+ } else if (this.state.urls?.feedback) {
247
+ window.open(this.state.urls.feedback, '_blank');
248
+ }
249
+ });
250
+ }
251
+
252
+ this.element
253
+ .querySelectorAll('.messenger-home-changelog-card')
254
+ .forEach((card) => {
255
+ card.addEventListener('click', () => {
256
+ const item = this.state.homeChangelogItems.find(
257
+ (i) => i.id === card.dataset.changelogId
258
+ );
259
+ if (item?.url) {
260
+ window.open(item.url, '_blank');
261
+ } else {
262
+ this.state.setView('changelog');
263
+ }
264
+ });
265
+ });
266
+
267
+ const seeAllBtn = this.element.querySelector(
268
+ '.messenger-home-changelog-all'
269
+ );
270
+ if (seeAllBtn) {
271
+ seeAllBtn.addEventListener('click', () => {
272
+ this.state.setView('changelog');
273
+ });
274
+ }
275
+
276
+ const featuredBtn = this.element.querySelector(
277
+ '.messenger-home-featured-btn'
278
+ );
279
+ if (featuredBtn) {
280
+ featuredBtn.addEventListener('click', () => {
281
+ const action = featuredBtn.dataset.action;
282
+ const value = featuredBtn.dataset.value;
283
+ if (action === 'url') {
284
+ window.open(value, '_blank');
285
+ } else if (action === 'view') {
286
+ this.state.setView(value);
287
+ }
288
+ });
289
+ }
290
+ }
291
+
292
+ destroy() {
293
+ if (this._unsubscribe) {
294
+ this._unsubscribe();
295
+ }
296
+ if (this.element && this.element.parentNode) {
297
+ this.element.parentNode.removeChild(this.element);
298
+ }
299
+ }
300
+ }
@@ -0,0 +1,109 @@
1
+ export class PreChatFormView {
2
+ constructor(state, options = {}) {
3
+ this.state = state;
4
+ this.options = options;
5
+ this.element = null;
6
+ this._isSubmitting = false;
7
+ }
8
+
9
+ render() {
10
+ this.element = document.createElement('div');
11
+ this.element.className = 'messenger-view messenger-prechat-view';
12
+
13
+ this._updateContent();
14
+
15
+ return this.element;
16
+ }
17
+
18
+ _updateContent() {
19
+ this.element.innerHTML = `
20
+ <div class="messenger-prechat-overlay">
21
+ <div class="messenger-prechat-card">
22
+ <div class="messenger-prechat-icon">
23
+ <iconify-icon icon="ph:bell-ringing-duotone" width="36" height="36"></iconify-icon>
24
+ </div>
25
+ <h4 class="messenger-prechat-title">Get notified when we reply</h4>
26
+ <p class="messenger-prechat-subtitle">We'll send you an email so you never miss a response.</p>
27
+ <div class="messenger-prechat-actions">
28
+ <button type="button" class="messenger-prechat-yes">
29
+ <iconify-icon icon="ph:bell-ringing-duotone" width="16" height="16"></iconify-icon>
30
+ Yes, notify me
31
+ </button>
32
+ <button type="button" class="messenger-prechat-no">
33
+ <iconify-icon icon="ph:bell-slash-duotone" width="16" height="16"></iconify-icon>
34
+ No thanks
35
+ </button>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ `;
40
+
41
+ this._attachEvents();
42
+ }
43
+
44
+ _attachEvents() {
45
+ this.element
46
+ .querySelector('.messenger-prechat-yes')
47
+ .addEventListener('click', async () => {
48
+ await this._handleYes();
49
+ });
50
+
51
+ this.element
52
+ .querySelector('.messenger-prechat-no')
53
+ .addEventListener('click', () => {
54
+ this._handleNo();
55
+ });
56
+ }
57
+
58
+ async _handleYes() {
59
+ if (this._isSubmitting) return;
60
+ this._isSubmitting = true;
61
+
62
+ const yesBtn = this.element.querySelector('.messenger-prechat-yes');
63
+ yesBtn.disabled = true;
64
+ yesBtn.innerHTML = `
65
+ <iconify-icon icon="ph:circle-notch" width="16" height="16" style="animation: spin 0.8s linear infinite;"></iconify-icon>
66
+ Just a moment...
67
+ `;
68
+
69
+ try {
70
+ const { name, email } = this.state.metadata || {};
71
+
72
+ if (this.options.onIdentifyContact) {
73
+ await this.options.onIdentifyContact({ name, email });
74
+ }
75
+
76
+ this.state.setIdentified(true, { name, email });
77
+
78
+ const pendingMessage = this.state.pendingMessage;
79
+ if (pendingMessage && this.options.onStartConversation) {
80
+ this.state.pendingMessage = null;
81
+ this.state.setView('chat');
82
+ await this.options.onStartConversation(
83
+ pendingMessage.content,
84
+ pendingMessage.attachments
85
+ );
86
+ } else {
87
+ this.state.setView('chat');
88
+ }
89
+ } catch (error) {
90
+ console.error('[PreChatFormView] Error:', error);
91
+ this._isSubmitting = false;
92
+ yesBtn.disabled = false;
93
+ yesBtn.innerHTML = `
94
+ <iconify-icon icon="ph:bell-ringing-duotone" width="16" height="16"></iconify-icon>
95
+ Yes, notify me
96
+ `;
97
+ }
98
+ }
99
+
100
+ _handleNo() {
101
+ this.state.setView('chat');
102
+ }
103
+
104
+ destroy() {
105
+ if (this.element && this.element.parentNode) {
106
+ this.element.parentNode.removeChild(this.element);
107
+ }
108
+ }
109
+ }