@product7/feedback-sdk 1.5.3 → 1.5.7
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/dist/feedback-sdk.js +695 -231
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/FeedbackSDK.js +60 -30
- package/src/styles/changelog.js +32 -25
- package/src/styles/components.js +20 -12
- package/src/styles/feedback.js +1 -1
- package/src/styles/messenger-components.js +32 -18
- package/src/styles/survey.js +124 -26
- package/src/widgets/BaseWidget.js +11 -5
- package/src/widgets/ChangelogWidget.js +8 -3
- package/src/widgets/SurveyWidget.js +281 -73
- package/src/widgets/messenger/components/NavigationTabs.js +1 -6
- package/src/widgets/messenger/views/ChatView.js +6 -4
- package/src/widgets/messenger/views/ConversationsView.js +7 -5
- package/src/widgets/messenger/views/HelpView.js +112 -23
|
@@ -12,6 +12,25 @@ export class SurveyWidget extends BaseWidget {
|
|
|
12
12
|
description: options.description || null,
|
|
13
13
|
lowLabel: options.lowLabel || null,
|
|
14
14
|
highLabel: options.highLabel || null,
|
|
15
|
+
ratingScale: options.ratingScale || options.scale || null,
|
|
16
|
+
showFeedbackInput:
|
|
17
|
+
typeof options.showFeedbackInput === 'boolean'
|
|
18
|
+
? options.showFeedbackInput
|
|
19
|
+
: null,
|
|
20
|
+
showSubmitButton:
|
|
21
|
+
typeof options.showSubmitButton === 'boolean'
|
|
22
|
+
? options.showSubmitButton
|
|
23
|
+
: null,
|
|
24
|
+
autoSubmitOnSelect:
|
|
25
|
+
typeof options.autoSubmitOnSelect === 'boolean'
|
|
26
|
+
? options.autoSubmitOnSelect
|
|
27
|
+
: null,
|
|
28
|
+
showTitle:
|
|
29
|
+
typeof options.showTitle === 'boolean' ? options.showTitle : null,
|
|
30
|
+
showDescription:
|
|
31
|
+
typeof options.showDescription === 'boolean'
|
|
32
|
+
? options.showDescription
|
|
33
|
+
: null,
|
|
15
34
|
customQuestions: options.customQuestions || [],
|
|
16
35
|
pages: Array.isArray(options.pages) ? options.pages : [],
|
|
17
36
|
respondentId: options.respondentId || null,
|
|
@@ -26,6 +45,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
26
45
|
customAnswers: {},
|
|
27
46
|
pageAnswers: {},
|
|
28
47
|
currentPageIndex: 0,
|
|
48
|
+
isSubmitting: false,
|
|
29
49
|
isVisible: false,
|
|
30
50
|
};
|
|
31
51
|
}
|
|
@@ -61,6 +81,10 @@ export class SurveyWidget extends BaseWidget {
|
|
|
61
81
|
const config = this._getSurveyConfig();
|
|
62
82
|
const isMultiPage = this._isMultiPageSurvey();
|
|
63
83
|
const isLastPage = this._isLastPage();
|
|
84
|
+
const showFeedbackInput = this._shouldShowFeedbackInput();
|
|
85
|
+
const showActions = this._shouldShowActions();
|
|
86
|
+
const showTitle = this._shouldShowTitle(config);
|
|
87
|
+
const showDescription = this._shouldShowDescription(config);
|
|
64
88
|
const pageProgress = isMultiPage
|
|
65
89
|
? `Page ${this.surveyState.currentPageIndex + 1} of ${this.surveyOptions.pages.length}`
|
|
66
90
|
: '';
|
|
@@ -80,21 +104,33 @@ export class SurveyWidget extends BaseWidget {
|
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
this.surveyElement = document.createElement('div');
|
|
83
|
-
this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position}
|
|
107
|
+
this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position}${
|
|
108
|
+
showDescription && !showTitle
|
|
109
|
+
? ' feedback-survey-description-primary'
|
|
110
|
+
: ''
|
|
111
|
+
}`;
|
|
84
112
|
|
|
85
113
|
this.surveyElement.innerHTML = `
|
|
86
|
-
<button class="feedback-survey-close"
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
<button class="feedback-survey-close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg></button>
|
|
115
|
+
${showTitle ? `<h3 class="feedback-survey-title">${config.title}</h3>` : ''}
|
|
116
|
+
${showDescription ? `<p class="feedback-survey-description">${config.description}</p>` : ''}
|
|
89
117
|
${isMultiPage ? `<div class="feedback-survey-progress">${pageProgress}</div>` : ''}
|
|
90
118
|
<div class="feedback-survey-content">${config.html}</div>
|
|
91
|
-
|
|
119
|
+
${
|
|
120
|
+
showFeedbackInput
|
|
121
|
+
? `<div class="feedback-survey-feedback">
|
|
92
122
|
<textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)">${this.surveyState.feedback || ''}</textarea>
|
|
93
|
-
</div
|
|
94
|
-
|
|
123
|
+
</div>`
|
|
124
|
+
: ''
|
|
125
|
+
}
|
|
126
|
+
${
|
|
127
|
+
showActions
|
|
128
|
+
? `<div class="feedback-survey-actions">
|
|
95
129
|
${backButton}
|
|
96
130
|
<button class="feedback-survey-submit">${submitLabel}</button>
|
|
97
|
-
</div
|
|
131
|
+
</div>`
|
|
132
|
+
: ''
|
|
133
|
+
}
|
|
98
134
|
`;
|
|
99
135
|
|
|
100
136
|
document.body.appendChild(this.surveyElement);
|
|
@@ -114,37 +150,62 @@ export class SurveyWidget extends BaseWidget {
|
|
|
114
150
|
return this._getCurrentPageConfig();
|
|
115
151
|
}
|
|
116
152
|
|
|
153
|
+
const npsScale = this._getNPSScale();
|
|
154
|
+
const npsUsesSegmentedScale = npsScale.values.length <= 7;
|
|
155
|
+
const npsContainerClass = npsUsesSegmentedScale
|
|
156
|
+
? 'feedback-survey-ces feedback-survey-rating-scale'
|
|
157
|
+
: 'feedback-survey-nps';
|
|
158
|
+
const npsButtonClass = npsUsesSegmentedScale
|
|
159
|
+
? 'feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
|
|
160
|
+
: 'feedback-survey-nps-btn';
|
|
161
|
+
const npsLowLabel =
|
|
162
|
+
this.surveyOptions.lowLabel ||
|
|
163
|
+
(npsScale.start === 0 ? 'Not likely' : 'Strongly Disagree');
|
|
164
|
+
const npsHighLabel =
|
|
165
|
+
this.surveyOptions.highLabel ||
|
|
166
|
+
(npsScale.start === 0 ? 'Very likely' : 'Strongly Agree');
|
|
167
|
+
const npsPrompt =
|
|
168
|
+
this.surveyOptions.description ||
|
|
169
|
+
this.surveyOptions.title ||
|
|
170
|
+
'How likely are you to recommend us?';
|
|
171
|
+
const csatPrompt =
|
|
172
|
+
this.surveyOptions.description ||
|
|
173
|
+
this.surveyOptions.title ||
|
|
174
|
+
'How satisfied are you?';
|
|
175
|
+
const cesPrompt =
|
|
176
|
+
this.surveyOptions.description ||
|
|
177
|
+
this.surveyOptions.title ||
|
|
178
|
+
'How easy was it?';
|
|
179
|
+
|
|
117
180
|
const configs = {
|
|
118
181
|
nps: {
|
|
119
|
-
title:
|
|
120
|
-
|
|
121
|
-
description:
|
|
122
|
-
this.surveyOptions.description ||
|
|
123
|
-
'On a scale of 0-10, how likely are you to recommend our product to a friend or colleague?',
|
|
182
|
+
title: this.surveyOptions.title || '',
|
|
183
|
+
description: npsPrompt,
|
|
124
184
|
html: `
|
|
125
|
-
<div class="
|
|
126
|
-
${
|
|
185
|
+
<div class="${npsContainerClass}">
|
|
186
|
+
${npsScale.values
|
|
127
187
|
.map(
|
|
128
188
|
(n) => `
|
|
129
|
-
<button class="
|
|
189
|
+
<button class="${npsButtonClass}" data-score="${n}">${n}</button>
|
|
130
190
|
`
|
|
131
191
|
)
|
|
132
192
|
.join('')}
|
|
133
193
|
</div>
|
|
134
|
-
|
|
135
|
-
<span>${this.surveyOptions.lowLabel || 'Not likely'}</span>
|
|
136
|
-
<span>${this.surveyOptions.highLabel || 'Very likely'}</span>
|
|
137
|
-
</div>
|
|
194
|
+
${this._renderScaleLabels(npsLowLabel, npsHighLabel)}
|
|
138
195
|
`,
|
|
139
196
|
},
|
|
140
197
|
csat: {
|
|
141
|
-
title: this.surveyOptions.title || '
|
|
142
|
-
description:
|
|
143
|
-
this.surveyOptions.description ||
|
|
144
|
-
'How would you rate your overall satisfaction with our product?',
|
|
198
|
+
title: this.surveyOptions.title || '',
|
|
199
|
+
description: csatPrompt,
|
|
145
200
|
html: `
|
|
146
201
|
<div class="feedback-survey-csat">
|
|
147
|
-
${[
|
|
202
|
+
${[
|
|
203
|
+
'\uD83D\uDE1E',
|
|
204
|
+
'\uD83D\uDE15',
|
|
205
|
+
'\uD83D\uDE10',
|
|
206
|
+
'\uD83D\uDE42',
|
|
207
|
+
'\uD83D\uDE04',
|
|
208
|
+
]
|
|
148
209
|
.map(
|
|
149
210
|
(emoji, i) => `
|
|
150
211
|
<button class="feedback-survey-csat-btn" data-score="${i + 1}">${emoji}</button>
|
|
@@ -152,17 +213,15 @@ export class SurveyWidget extends BaseWidget {
|
|
|
152
213
|
)
|
|
153
214
|
.join('')}
|
|
154
215
|
</div>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
216
|
+
${this._renderScaleLabels(
|
|
217
|
+
this.surveyOptions.lowLabel || 'Very dissatisfied',
|
|
218
|
+
this.surveyOptions.highLabel || 'Very satisfied'
|
|
219
|
+
)}
|
|
159
220
|
`,
|
|
160
221
|
},
|
|
161
222
|
ces: {
|
|
162
|
-
title: this.surveyOptions.title || '
|
|
163
|
-
description:
|
|
164
|
-
this.surveyOptions.description ||
|
|
165
|
-
'How easy was it to accomplish your task today?',
|
|
223
|
+
title: this.surveyOptions.title || '',
|
|
224
|
+
description: cesPrompt,
|
|
166
225
|
html: `
|
|
167
226
|
<div class="feedback-survey-ces">
|
|
168
227
|
${['Very Difficult', 'Difficult', 'Neutral', 'Easy', 'Very Easy']
|
|
@@ -187,6 +246,91 @@ export class SurveyWidget extends BaseWidget {
|
|
|
187
246
|
return configs[this.surveyOptions.surveyType] || configs.nps;
|
|
188
247
|
}
|
|
189
248
|
|
|
249
|
+
_getNPSScale() {
|
|
250
|
+
const rawScale = Number(this.surveyOptions.ratingScale);
|
|
251
|
+
const scale = Number.isFinite(rawScale) && rawScale >= 2 ? rawScale : 5;
|
|
252
|
+
const start = scale === 11 ? 0 : 1;
|
|
253
|
+
return {
|
|
254
|
+
scale,
|
|
255
|
+
start,
|
|
256
|
+
values: Array.from({ length: scale }, (_, index) => start + index),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_renderScaleLabels(lowLabel, highLabel) {
|
|
261
|
+
const low = lowLabel || '';
|
|
262
|
+
const high = highLabel || '';
|
|
263
|
+
if (!low && !high) {
|
|
264
|
+
return '';
|
|
265
|
+
}
|
|
266
|
+
return `
|
|
267
|
+
<div class="feedback-survey-labels">
|
|
268
|
+
<span>${low}</span>
|
|
269
|
+
<span>${high}</span>
|
|
270
|
+
</div>
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_isRatingSurveyType(type = this.surveyOptions.surveyType) {
|
|
275
|
+
return type === 'nps' || type === 'csat' || type === 'ces';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_shouldShowTitle(config) {
|
|
279
|
+
if (!config || !config.title) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (typeof this.surveyOptions.showTitle === 'boolean') {
|
|
283
|
+
return this.surveyOptions.showTitle;
|
|
284
|
+
}
|
|
285
|
+
if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
|
|
286
|
+
return !config.description;
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_shouldShowDescription(config) {
|
|
292
|
+
if (typeof this.surveyOptions.showDescription === 'boolean') {
|
|
293
|
+
return this.surveyOptions.showDescription;
|
|
294
|
+
}
|
|
295
|
+
if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
|
|
296
|
+
return Boolean(config && (config.description || config.title));
|
|
297
|
+
}
|
|
298
|
+
return Boolean(config && config.description);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_shouldShowFeedbackInput() {
|
|
302
|
+
if (this._isMultiPageSurvey()) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (typeof this.surveyOptions.showFeedbackInput === 'boolean') {
|
|
306
|
+
return this.surveyOptions.showFeedbackInput;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_shouldAutoSubmitOnSelect() {
|
|
312
|
+
if (typeof this.surveyOptions.autoSubmitOnSelect === 'boolean') {
|
|
313
|
+
return this.surveyOptions.autoSubmitOnSelect;
|
|
314
|
+
}
|
|
315
|
+
if (this._isMultiPageSurvey()) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
if (this.surveyOptions.showSubmitButton === true) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return this._isRatingSurveyType() && !this._shouldShowFeedbackInput();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_shouldShowActions() {
|
|
325
|
+
if (this._isMultiPageSurvey()) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
if (typeof this.surveyOptions.showSubmitButton === 'boolean') {
|
|
329
|
+
return this.surveyOptions.showSubmitButton;
|
|
330
|
+
}
|
|
331
|
+
return !this._shouldAutoSubmitOnSelect();
|
|
332
|
+
}
|
|
333
|
+
|
|
190
334
|
_isMultiPageSurvey() {
|
|
191
335
|
return (
|
|
192
336
|
Array.isArray(this.surveyOptions.pages) &&
|
|
@@ -201,7 +345,9 @@ export class SurveyWidget extends BaseWidget {
|
|
|
201
345
|
|
|
202
346
|
_isLastPage() {
|
|
203
347
|
if (!this._isMultiPageSurvey()) return true;
|
|
204
|
-
return
|
|
348
|
+
return (
|
|
349
|
+
this.surveyState.currentPageIndex >= this.surveyOptions.pages.length - 1
|
|
350
|
+
);
|
|
205
351
|
}
|
|
206
352
|
|
|
207
353
|
_getCurrentPageConfig() {
|
|
@@ -212,10 +358,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
212
358
|
|
|
213
359
|
return {
|
|
214
360
|
title: page.title || this.surveyOptions.title || 'Quick Feedback',
|
|
215
|
-
description:
|
|
216
|
-
page.description ||
|
|
217
|
-
this.surveyOptions.description ||
|
|
218
|
-
'Help us improve by answering this question.',
|
|
361
|
+
description: page.description || this.surveyOptions.description || '',
|
|
219
362
|
html: this._renderSurveyPage(page),
|
|
220
363
|
};
|
|
221
364
|
}
|
|
@@ -223,9 +366,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
223
366
|
_getFallbackSurveyConfig() {
|
|
224
367
|
return {
|
|
225
368
|
title: this.surveyOptions.title || 'Quick Feedback',
|
|
226
|
-
description:
|
|
227
|
-
this.surveyOptions.description ||
|
|
228
|
-
'Help us improve by answering a few questions.',
|
|
369
|
+
description: this.surveyOptions.description || '',
|
|
229
370
|
html: this._renderCustomQuestions(),
|
|
230
371
|
};
|
|
231
372
|
}
|
|
@@ -247,24 +388,48 @@ export class SurveyWidget extends BaseWidget {
|
|
|
247
388
|
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
248
389
|
const config = page.ratingConfig || page.rating_config || {};
|
|
249
390
|
const scale = Number(config.scale) || 5;
|
|
250
|
-
const ratingType =
|
|
391
|
+
const ratingType =
|
|
392
|
+
config.survey_type || this.surveyOptions.surveyType || 'csat';
|
|
251
393
|
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
252
394
|
const currentRating = pageAnswer.rating;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
395
|
+
const defaultLowLabel =
|
|
396
|
+
ratingType === 'nps'
|
|
397
|
+
? scale === 11
|
|
398
|
+
? 'Not likely'
|
|
399
|
+
: 'Strongly Disagree'
|
|
400
|
+
: '';
|
|
401
|
+
const defaultHighLabel =
|
|
402
|
+
ratingType === 'nps'
|
|
403
|
+
? scale === 11
|
|
404
|
+
? 'Very likely'
|
|
405
|
+
: 'Strongly Agree'
|
|
406
|
+
: '';
|
|
407
|
+
const lowLabel =
|
|
408
|
+
config.low_label || this.surveyOptions.lowLabel || defaultLowLabel;
|
|
409
|
+
const highLabel =
|
|
410
|
+
config.high_label || this.surveyOptions.highLabel || defaultHighLabel;
|
|
411
|
+
const labels = this._renderScaleLabels(lowLabel, highLabel);
|
|
412
|
+
|
|
413
|
+
if (ratingType === 'nps') {
|
|
414
|
+
const npsScale = Number.isFinite(scale) && scale >= 2 ? scale : 5;
|
|
415
|
+
const start = npsScale === 11 ? 0 : 1;
|
|
416
|
+
const values = Array.from(
|
|
417
|
+
{ length: npsScale },
|
|
418
|
+
(_, index) => start + index
|
|
419
|
+
);
|
|
420
|
+
const usesSegmentedScale = values.length <= 7;
|
|
421
|
+
const containerClass = usesSegmentedScale
|
|
422
|
+
? 'feedback-survey-ces feedback-survey-rating-scale'
|
|
423
|
+
: 'feedback-survey-nps';
|
|
424
|
+
const buttonClass = usesSegmentedScale
|
|
425
|
+
? 'feedback-survey-page-rating-btn feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
|
|
426
|
+
: 'feedback-survey-page-rating-btn feedback-survey-nps-btn';
|
|
262
427
|
return `
|
|
263
|
-
<div class="
|
|
264
|
-
${
|
|
428
|
+
<div class="${containerClass}" data-page-id="${pageId}">
|
|
429
|
+
${values
|
|
265
430
|
.map((n) => {
|
|
266
431
|
const selected = currentRating === n ? ' selected' : '';
|
|
267
|
-
return `<button class="
|
|
432
|
+
return `<button class="${buttonClass}${selected}" data-page-id="${pageId}" data-score="${n}">${n}</button>`;
|
|
268
433
|
})
|
|
269
434
|
.join('')}
|
|
270
435
|
</div>
|
|
@@ -273,7 +438,13 @@ export class SurveyWidget extends BaseWidget {
|
|
|
273
438
|
}
|
|
274
439
|
|
|
275
440
|
if (ratingType === 'emoji' && scale === 5) {
|
|
276
|
-
const emojis = [
|
|
441
|
+
const emojis = [
|
|
442
|
+
'\uD83D\uDE1E',
|
|
443
|
+
'\uD83D\uDE15',
|
|
444
|
+
'\uD83D\uDE10',
|
|
445
|
+
'\uD83D\uDE42',
|
|
446
|
+
'\uD83D\uDE04',
|
|
447
|
+
];
|
|
277
448
|
return `
|
|
278
449
|
<div class="feedback-survey-csat" data-page-id="${pageId}">
|
|
279
450
|
${emojis
|
|
@@ -289,12 +460,12 @@ export class SurveyWidget extends BaseWidget {
|
|
|
289
460
|
}
|
|
290
461
|
|
|
291
462
|
return `
|
|
292
|
-
<div class="feedback-survey-ces" data-page-id="${pageId}">
|
|
463
|
+
<div class="feedback-survey-ces feedback-survey-rating-scale" data-page-id="${pageId}">
|
|
293
464
|
${[...Array(scale).keys()]
|
|
294
465
|
.map((i) => {
|
|
295
466
|
const score = i + 1;
|
|
296
467
|
const selected = currentRating === score ? ' selected' : '';
|
|
297
|
-
return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
|
|
468
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
|
|
298
469
|
})
|
|
299
470
|
.join('')}
|
|
300
471
|
</div>
|
|
@@ -304,9 +475,11 @@ export class SurveyWidget extends BaseWidget {
|
|
|
304
475
|
|
|
305
476
|
_renderMultipleChoicePage(page) {
|
|
306
477
|
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
307
|
-
const config =
|
|
478
|
+
const config =
|
|
479
|
+
page.multipleChoiceConfig || page.multiple_choice_config || {};
|
|
308
480
|
const options = Array.isArray(config.options) ? config.options : [];
|
|
309
|
-
const allowMultiple =
|
|
481
|
+
const allowMultiple =
|
|
482
|
+
config.allow_multiple === true || config.multiple === true;
|
|
310
483
|
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
311
484
|
const selectedValues = Array.isArray(pageAnswer.values)
|
|
312
485
|
? pageAnswer.values
|
|
@@ -411,7 +584,9 @@ export class SurveyWidget extends BaseWidget {
|
|
|
411
584
|
const submitBtn = this.surveyElement.querySelector(
|
|
412
585
|
'.feedback-survey-submit'
|
|
413
586
|
);
|
|
414
|
-
|
|
587
|
+
if (submitBtn) {
|
|
588
|
+
submitBtn.addEventListener('click', () => this._handleSubmit());
|
|
589
|
+
}
|
|
415
590
|
|
|
416
591
|
const backBtn = this.surveyElement.querySelector('.feedback-survey-back');
|
|
417
592
|
if (backBtn) {
|
|
@@ -421,9 +596,11 @@ export class SurveyWidget extends BaseWidget {
|
|
|
421
596
|
const textarea = this.surveyElement.querySelector(
|
|
422
597
|
'.feedback-survey-textarea'
|
|
423
598
|
);
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
599
|
+
if (textarea) {
|
|
600
|
+
textarea.addEventListener('input', (e) => {
|
|
601
|
+
this.surveyState.feedback = e.target.value;
|
|
602
|
+
});
|
|
603
|
+
}
|
|
427
604
|
|
|
428
605
|
this._attachTypeSpecificEvents();
|
|
429
606
|
|
|
@@ -595,6 +772,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
595
772
|
btn.classList.remove('selected');
|
|
596
773
|
}
|
|
597
774
|
});
|
|
775
|
+
this._maybeAutoSubmit();
|
|
598
776
|
}
|
|
599
777
|
|
|
600
778
|
_selectCSAT(score) {
|
|
@@ -609,6 +787,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
609
787
|
btn.classList.remove('selected');
|
|
610
788
|
}
|
|
611
789
|
});
|
|
790
|
+
this._maybeAutoSubmit();
|
|
612
791
|
}
|
|
613
792
|
|
|
614
793
|
_selectCES(score) {
|
|
@@ -623,6 +802,14 @@ export class SurveyWidget extends BaseWidget {
|
|
|
623
802
|
btn.classList.remove('selected');
|
|
624
803
|
}
|
|
625
804
|
});
|
|
805
|
+
this._maybeAutoSubmit();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
_maybeAutoSubmit() {
|
|
809
|
+
if (!this._shouldAutoSubmitOnSelect()) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
this._handleSubmit();
|
|
626
813
|
}
|
|
627
814
|
|
|
628
815
|
_selectFrequency(freq) {
|
|
@@ -640,6 +827,9 @@ export class SurveyWidget extends BaseWidget {
|
|
|
640
827
|
|
|
641
828
|
async _handleSubmit() {
|
|
642
829
|
const type = this.surveyOptions.surveyType;
|
|
830
|
+
if (this.surveyState.isSubmitting) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
643
833
|
|
|
644
834
|
if (this._isMultiPageSurvey()) {
|
|
645
835
|
if (!this._validateCurrentPage()) {
|
|
@@ -647,7 +837,10 @@ export class SurveyWidget extends BaseWidget {
|
|
|
647
837
|
}
|
|
648
838
|
|
|
649
839
|
const nextPageIndex = this._getNextPageIndex();
|
|
650
|
-
if (
|
|
840
|
+
if (
|
|
841
|
+
nextPageIndex !== -1 &&
|
|
842
|
+
nextPageIndex !== this.surveyState.currentPageIndex
|
|
843
|
+
) {
|
|
651
844
|
this.surveyState.currentPageIndex = nextPageIndex;
|
|
652
845
|
this._renderSurvey();
|
|
653
846
|
return;
|
|
@@ -663,6 +856,8 @@ export class SurveyWidget extends BaseWidget {
|
|
|
663
856
|
return;
|
|
664
857
|
}
|
|
665
858
|
|
|
859
|
+
this.surveyState.isSubmitting = true;
|
|
860
|
+
|
|
666
861
|
const respondent = this._getRespondentContext();
|
|
667
862
|
const normalizedPageAnswers = this._normalizePageAnswersForSubmit();
|
|
668
863
|
const mergedAnswers = {
|
|
@@ -689,16 +884,18 @@ export class SurveyWidget extends BaseWidget {
|
|
|
689
884
|
timestamp: new Date().toISOString(),
|
|
690
885
|
};
|
|
691
886
|
|
|
692
|
-
if (this.surveyOptions.onSubmit) {
|
|
693
|
-
this.surveyOptions.onSubmit(response);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
887
|
try {
|
|
888
|
+
if (this.surveyOptions.onSubmit) {
|
|
889
|
+
this.surveyOptions.onSubmit(response);
|
|
890
|
+
}
|
|
891
|
+
|
|
697
892
|
const surveyId =
|
|
698
893
|
this.surveyOptions.surveyId || `local_${type}_${Date.now()}`;
|
|
699
894
|
await this.apiService.submitSurveyResponse(surveyId, responseData);
|
|
700
895
|
} catch (error) {
|
|
701
896
|
console.error('[SurveyWidget] Failed to submit survey:', error);
|
|
897
|
+
} finally {
|
|
898
|
+
this.surveyState.isSubmitting = false;
|
|
702
899
|
}
|
|
703
900
|
|
|
704
901
|
this.sdk.eventBus.emit('survey:submitted', { widget: this, response });
|
|
@@ -806,7 +1003,8 @@ export class SurveyWidget extends BaseWidget {
|
|
|
806
1003
|
}
|
|
807
1004
|
|
|
808
1005
|
for (const page of this.surveyOptions.pages) {
|
|
809
|
-
const pageId =
|
|
1006
|
+
const pageId =
|
|
1007
|
+
page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
|
|
810
1008
|
const answer = this.surveyState.pageAnswers[pageId];
|
|
811
1009
|
if (answer && typeof answer.rating === 'number') {
|
|
812
1010
|
return answer.rating;
|
|
@@ -818,7 +1016,9 @@ export class SurveyWidget extends BaseWidget {
|
|
|
818
1016
|
|
|
819
1017
|
_normalizePageAnswersForSubmit() {
|
|
820
1018
|
const output = {};
|
|
821
|
-
for (const [pageId, answer] of Object.entries(
|
|
1019
|
+
for (const [pageId, answer] of Object.entries(
|
|
1020
|
+
this.surveyState.pageAnswers
|
|
1021
|
+
)) {
|
|
822
1022
|
if (answer == null) continue;
|
|
823
1023
|
if (Array.isArray(answer.values) && answer.values.length > 0) {
|
|
824
1024
|
output[pageId] = answer.values;
|
|
@@ -845,8 +1045,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
845
1045
|
? this.sdk.getUserContext() || {}
|
|
846
1046
|
: {};
|
|
847
1047
|
const apiUserContext =
|
|
848
|
-
this.apiService &&
|
|
849
|
-
typeof this.apiService.getUserContext === 'function'
|
|
1048
|
+
this.apiService && typeof this.apiService.getUserContext === 'function'
|
|
850
1049
|
? this.apiService.getUserContext() || {}
|
|
851
1050
|
: {};
|
|
852
1051
|
const localUserContext = this.options.userContext || {};
|
|
@@ -894,7 +1093,15 @@ export class SurveyWidget extends BaseWidget {
|
|
|
894
1093
|
const submitBtn = this.surveyElement.querySelector(
|
|
895
1094
|
'.feedback-survey-submit'
|
|
896
1095
|
);
|
|
897
|
-
|
|
1096
|
+
const actions = this.surveyElement.querySelector(
|
|
1097
|
+
'.feedback-survey-actions'
|
|
1098
|
+
);
|
|
1099
|
+
const anchor = submitBtn || actions;
|
|
1100
|
+
if (anchor && anchor.parentNode) {
|
|
1101
|
+
anchor.parentNode.insertBefore(error, anchor);
|
|
1102
|
+
} else {
|
|
1103
|
+
this.surveyElement.appendChild(error);
|
|
1104
|
+
}
|
|
898
1105
|
|
|
899
1106
|
setTimeout(() => error.remove(), 3000);
|
|
900
1107
|
}
|
|
@@ -957,6 +1164,7 @@ export class SurveyWidget extends BaseWidget {
|
|
|
957
1164
|
customAnswers: {},
|
|
958
1165
|
pageAnswers: {},
|
|
959
1166
|
currentPageIndex: 0,
|
|
1167
|
+
isSubmitting: false,
|
|
960
1168
|
isVisible: false,
|
|
961
1169
|
};
|
|
962
1170
|
}
|
|
@@ -124,12 +124,7 @@ export class NavigationTabs {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
_getMessagesIcon() {
|
|
127
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
128
|
-
<circle cx="128" cy="128" r="12"/>
|
|
129
|
-
<circle cx="84" cy="128" r="12"/>
|
|
130
|
-
<circle cx="172" cy="128" r="12"/>
|
|
131
|
-
<path d="M79.93,211.11a96,96,0,1,0-35-35h0L32.42,213.46a8,8,0,0,0,10.12,10.12l37.39-12.47Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
|
132
|
-
</svg>`;
|
|
127
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M71.58,144,32,176V48a8,8,0,0,1,8-8H168a8,8,0,0,1,8,8v88a8,8,0,0,1-8,8Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M80,144v40a8,8,0,0,0,8,8h96.42L224,224V96a8,8,0,0,0-8-8H176" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>`;
|
|
133
128
|
}
|
|
134
129
|
|
|
135
130
|
_getHelpIcon() {
|
|
@@ -116,10 +116,12 @@ export class ChatView {
|
|
|
116
116
|
<div class="messenger-compose-attachments-preview"></div>
|
|
117
117
|
|
|
118
118
|
<div class="messenger-chat-compose">
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
<button class="sdk-btn-icon messenger-compose-attach" aria-label="Attach file">
|
|
120
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="20" height="20">
|
|
121
|
+
<rect width="256" height="256" fill="none"/>
|
|
122
|
+
<path d="M160,80,76.69,164.69a16,16,0,0,0,22.63,22.62L198.63,86.63a32,32,0,0,0-45.26-45.26L54.06,142.06a48,48,0,0,0,67.88,67.88L204,128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
|
123
|
+
</svg>
|
|
124
|
+
</button> <div class="messenger-compose-input-wrapper">
|
|
123
125
|
<textarea class="messenger-compose-input" placeholder="${placeholder}" rows="1"></textarea>
|
|
124
126
|
</div>
|
|
125
127
|
<button class="messenger-compose-send" aria-label="Send" disabled>
|
|
@@ -69,11 +69,13 @@ export class ConversationsView {
|
|
|
69
69
|
this.element.innerHTML = `
|
|
70
70
|
<div class="messenger-conversations-header">
|
|
71
71
|
<h2>Messages</h2>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
<button class="sdk-close-btn" aria-label="Close">
|
|
73
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="18" height="18">
|
|
74
|
+
<rect width="256" height="256" fill="none"/>
|
|
75
|
+
<line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
|
76
|
+
<line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
|
77
|
+
</svg>
|
|
78
|
+
</button>
|
|
77
79
|
</div>
|
|
78
80
|
|
|
79
81
|
<div class="messenger-conversations-body">
|