@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.
- package/README.md +1025 -0
- package/dist/README.md +1025 -0
- package/dist/product7-js.js +14658 -0
- package/dist/product7-js.js.map +1 -0
- package/dist/product7-js.min.js +2 -0
- package/dist/product7-js.min.js.map +1 -0
- package/package.json +114 -0
- package/src/api/mock-data/index.js +360 -0
- package/src/api/services/ChangelogService.js +28 -0
- package/src/api/services/FeedbackService.js +44 -0
- package/src/api/services/HelpService.js +50 -0
- package/src/api/services/MessengerService.js +279 -0
- package/src/api/services/SurveyService.js +127 -0
- package/src/api/utils/helpers.js +30 -0
- package/src/core/APIService.js +303 -0
- package/src/core/BaseAPIService.js +298 -0
- package/src/core/EventBus.js +54 -0
- package/src/core/Product7.js +812 -0
- package/src/core/WebSocketService.js +275 -0
- package/src/docs/api.md +226 -0
- package/src/docs/example.md +461 -0
- package/src/docs/framework-integrations.md +714 -0
- package/src/docs/installation.md +281 -0
- package/src/index.js +894 -0
- package/src/styles/base.js +50 -0
- package/src/styles/changelog.js +665 -0
- package/src/styles/components.js +553 -0
- package/src/styles/design-tokens.js +124 -0
- package/src/styles/feedback.js +325 -0
- package/src/styles/messenger-components.js +632 -0
- package/src/styles/messenger-core.js +233 -0
- package/src/styles/messenger-features.js +169 -0
- package/src/styles/messenger-views.js +877 -0
- package/src/styles/messenger.js +17 -0
- package/src/styles/messengerCustomStyles.js +114 -0
- package/src/styles/styles.js +26 -0
- package/src/styles/survey.js +894 -0
- package/src/utils/errors.js +142 -0
- package/src/utils/helpers.js +219 -0
- package/src/widgets/BaseWidget.js +548 -0
- package/src/widgets/ButtonWidget.js +104 -0
- package/src/widgets/ChangelogWidget.js +615 -0
- package/src/widgets/InlineWidget.js +148 -0
- package/src/widgets/MessengerWidget.js +979 -0
- package/src/widgets/SurveyWidget.js +1325 -0
- package/src/widgets/TabWidget.js +45 -0
- package/src/widgets/WidgetFactory.js +70 -0
- package/src/widgets/messenger/MessengerState.js +323 -0
- package/src/widgets/messenger/components/MessengerLauncher.js +124 -0
- package/src/widgets/messenger/components/MessengerPanel.js +111 -0
- package/src/widgets/messenger/components/NavigationTabs.js +130 -0
- package/src/widgets/messenger/views/ChangelogView.js +167 -0
- package/src/widgets/messenger/views/ChatView.js +592 -0
- package/src/widgets/messenger/views/ConversationsView.js +244 -0
- package/src/widgets/messenger/views/HelpView.js +239 -0
- package/src/widgets/messenger/views/HomeView.js +300 -0
- package/src/widgets/messenger/views/PreChatFormView.js +109 -0
- package/types/index.d.ts +341 -0
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
import { BaseWidget } from './BaseWidget.js';
|
|
2
|
+
|
|
3
|
+
export class SurveyWidget extends BaseWidget {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super({ ...options, type: 'survey' });
|
|
6
|
+
|
|
7
|
+
this.surveyOptions = {
|
|
8
|
+
surveyId: options.surveyId || null,
|
|
9
|
+
surveyType: options.surveyType || 'nps',
|
|
10
|
+
position: options.position || 'center',
|
|
11
|
+
theme: options.theme || 'light',
|
|
12
|
+
enabled:
|
|
13
|
+
typeof options.enabled === 'boolean' ? options.enabled : undefined,
|
|
14
|
+
title: options.title || null,
|
|
15
|
+
description: options.description || null,
|
|
16
|
+
lowLabel: options.lowLabel || null,
|
|
17
|
+
highLabel: options.highLabel || null,
|
|
18
|
+
ratingScale: options.ratingScale || options.scale || null,
|
|
19
|
+
showFeedbackInput:
|
|
20
|
+
typeof options.showFeedbackInput === 'boolean'
|
|
21
|
+
? options.showFeedbackInput
|
|
22
|
+
: null,
|
|
23
|
+
showSubmitButton:
|
|
24
|
+
typeof options.showSubmitButton === 'boolean'
|
|
25
|
+
? options.showSubmitButton
|
|
26
|
+
: null,
|
|
27
|
+
autoSubmitOnSelect:
|
|
28
|
+
typeof options.autoSubmitOnSelect === 'boolean'
|
|
29
|
+
? options.autoSubmitOnSelect
|
|
30
|
+
: null,
|
|
31
|
+
showTitle:
|
|
32
|
+
typeof options.showTitle === 'boolean' ? options.showTitle : null,
|
|
33
|
+
showDescription:
|
|
34
|
+
typeof options.showDescription === 'boolean'
|
|
35
|
+
? options.showDescription
|
|
36
|
+
: null,
|
|
37
|
+
customQuestions: options.customQuestions || [],
|
|
38
|
+
pages: Array.isArray(options.pages) ? options.pages : [],
|
|
39
|
+
respondentId: options.respondentId || null,
|
|
40
|
+
email: options.email || null,
|
|
41
|
+
onSubmit: options.onSubmit || null,
|
|
42
|
+
onDismiss: options.onDismiss || null,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.surveyState = {
|
|
46
|
+
score: null,
|
|
47
|
+
feedback: '',
|
|
48
|
+
customAnswers: {},
|
|
49
|
+
pageAnswers: {},
|
|
50
|
+
currentPageIndex: 0,
|
|
51
|
+
isSubmitting: false,
|
|
52
|
+
isVisible: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static removeDanglingElements() {
|
|
57
|
+
if (typeof document === 'undefined') return;
|
|
58
|
+
|
|
59
|
+
document
|
|
60
|
+
.querySelectorAll(
|
|
61
|
+
'.feedback-survey, .feedback-survey-backdrop, .feedback-survey-success, .product7-notification'
|
|
62
|
+
)
|
|
63
|
+
.forEach((el) => {
|
|
64
|
+
if (el && el.parentNode) {
|
|
65
|
+
el.parentNode.removeChild(el);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_render() {
|
|
71
|
+
const container = document.createElement('div');
|
|
72
|
+
container.className = 'feedback-survey-container';
|
|
73
|
+
return container;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_attachEvents() {
|
|
77
|
+
// Events are attached when survey is shown
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
show() {
|
|
81
|
+
if (
|
|
82
|
+
this.options.enabled === false ||
|
|
83
|
+
this.surveyOptions.enabled === false
|
|
84
|
+
) {
|
|
85
|
+
this.sdk.eventBus.emit('survey:suppressed', {
|
|
86
|
+
widget: this,
|
|
87
|
+
surveyId: this.surveyOptions.surveyId,
|
|
88
|
+
reason: 'disabled',
|
|
89
|
+
});
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
SurveyWidget.removeDanglingElements();
|
|
94
|
+
this._renderSurvey();
|
|
95
|
+
this.surveyState.isVisible = true;
|
|
96
|
+
this.sdk.eventBus.emit('survey:shown', {
|
|
97
|
+
widget: this,
|
|
98
|
+
type: this.surveyOptions.surveyType,
|
|
99
|
+
});
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
hide() {
|
|
104
|
+
this._closeSurvey();
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_renderSurvey() {
|
|
109
|
+
this._closeSurvey(false);
|
|
110
|
+
|
|
111
|
+
const config = this._getSurveyConfig();
|
|
112
|
+
if (!config) return;
|
|
113
|
+
const isMultiPage = this._isMultiPageSurvey();
|
|
114
|
+
const isLastPage = this._isLastPage();
|
|
115
|
+
const showFeedbackInput = this._shouldShowFeedbackInput();
|
|
116
|
+
const showActions = this._shouldShowActions();
|
|
117
|
+
const showTitle = this._shouldShowTitle(config);
|
|
118
|
+
const showDescription = this._shouldShowDescription(config);
|
|
119
|
+
const progressPercent = isMultiPage
|
|
120
|
+
? Math.round(
|
|
121
|
+
((this.surveyState.currentPageIndex + 1) /
|
|
122
|
+
this.surveyOptions.pages.length) *
|
|
123
|
+
100
|
|
124
|
+
)
|
|
125
|
+
: 0;
|
|
126
|
+
const submitLabel = isMultiPage && !isLastPage ? 'Next' : 'Submit';
|
|
127
|
+
const iconArrowRight = `<iconify-icon icon="ph:arrow-right" width="16" height="16"></iconify-icon>`;
|
|
128
|
+
const iconCheck = `<iconify-icon icon="ph:arrow-right" width="16" height="16"></iconify-icon>`;
|
|
129
|
+
const iconArrowLeft = `<iconify-icon icon="ph:arrow-left" width="16" height="16"></iconify-icon>`;
|
|
130
|
+
const backButton =
|
|
131
|
+
isMultiPage && this.surveyState.currentPageIndex > 0
|
|
132
|
+
? `<button class="feedback-survey-back">${iconArrowLeft} Back</button>`
|
|
133
|
+
: '';
|
|
134
|
+
|
|
135
|
+
if (this.surveyOptions.position === 'center') {
|
|
136
|
+
this.backdropElement = document.createElement('div');
|
|
137
|
+
this.backdropElement.className = `feedback-survey-backdrop feedback-survey-backdrop-${this.surveyOptions.theme}`;
|
|
138
|
+
document.body.appendChild(this.backdropElement);
|
|
139
|
+
this.backdropElement.addEventListener('click', () =>
|
|
140
|
+
this._handleDismiss()
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.surveyElement = document.createElement('div');
|
|
145
|
+
this.surveyElement.className = `feedback-survey feedback-survey-${this.surveyOptions.position} feedback-survey-theme-${this.surveyOptions.theme}${
|
|
146
|
+
showDescription && !showTitle
|
|
147
|
+
? ' feedback-survey-description-primary'
|
|
148
|
+
: ''
|
|
149
|
+
}`;
|
|
150
|
+
|
|
151
|
+
this.surveyElement.innerHTML = `
|
|
152
|
+
<button class="feedback-survey-close"><iconify-icon icon="ph:x-duotone" width="16" height="16"></iconify-icon></button>
|
|
153
|
+
${showTitle ? `<h3 class="feedback-survey-title">${config.title}</h3>` : ''}
|
|
154
|
+
${showDescription ? `<p class="feedback-survey-description">${config.description}</p>` : ''}
|
|
155
|
+
${isMultiPage && this.surveyOptions.pages.length > 1 ? `<div class="feedback-survey-progress"><div class="feedback-survey-progress-track"><div class="feedback-survey-progress-fill" style="width:${progressPercent}%"></div></div></div>` : ''}
|
|
156
|
+
<div class="feedback-survey-content">${config.html}</div>
|
|
157
|
+
${
|
|
158
|
+
showFeedbackInput
|
|
159
|
+
? `<div class="feedback-survey-feedback">
|
|
160
|
+
<textarea class="feedback-survey-textarea" placeholder="Any additional feedback? (optional)">${this.surveyState.feedback || ''}</textarea>
|
|
161
|
+
</div>`
|
|
162
|
+
: ''
|
|
163
|
+
}
|
|
164
|
+
${
|
|
165
|
+
showActions
|
|
166
|
+
? `<div class="feedback-survey-actions">
|
|
167
|
+
${backButton}
|
|
168
|
+
<button class="feedback-survey-submit">${submitLabel} ${isLastPage ? iconCheck : iconArrowRight}</button>
|
|
169
|
+
</div>`
|
|
170
|
+
: ''
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
document.body.appendChild(this.surveyElement);
|
|
175
|
+
this._attachSurveyEvents();
|
|
176
|
+
|
|
177
|
+
requestAnimationFrame(() => {
|
|
178
|
+
this.surveyElement.style.opacity = '1';
|
|
179
|
+
this.surveyElement.style.transform =
|
|
180
|
+
this.surveyOptions.position === 'center'
|
|
181
|
+
? 'translate(-50%, -50%) scale(1)'
|
|
182
|
+
: 'translateY(0)';
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_getSurveyConfig() {
|
|
187
|
+
if (this._isMultiPageSurvey()) {
|
|
188
|
+
return this._getCurrentPageConfig();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const npsScale = this._getNPSScale();
|
|
192
|
+
const npsUsesSegmentedScale = npsScale.values.length <= 7;
|
|
193
|
+
const npsContainerClass = npsUsesSegmentedScale
|
|
194
|
+
? 'feedback-survey-ces feedback-survey-rating-scale'
|
|
195
|
+
: 'feedback-survey-nps';
|
|
196
|
+
const npsButtonClass = npsUsesSegmentedScale
|
|
197
|
+
? 'feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
|
|
198
|
+
: 'feedback-survey-nps-btn';
|
|
199
|
+
const npsLowLabel =
|
|
200
|
+
this.surveyOptions.lowLabel ||
|
|
201
|
+
(npsScale.start === 0 ? 'Not likely' : 'Strongly Disagree');
|
|
202
|
+
const npsHighLabel =
|
|
203
|
+
this.surveyOptions.highLabel ||
|
|
204
|
+
(npsScale.start === 0 ? 'Very likely' : 'Strongly Agree');
|
|
205
|
+
const npsPrompt =
|
|
206
|
+
this.surveyOptions.description ||
|
|
207
|
+
this.surveyOptions.title ||
|
|
208
|
+
'How likely are you to recommend us?';
|
|
209
|
+
const csatPrompt =
|
|
210
|
+
this.surveyOptions.description ||
|
|
211
|
+
this.surveyOptions.title ||
|
|
212
|
+
'How satisfied are you?';
|
|
213
|
+
const cesPrompt =
|
|
214
|
+
this.surveyOptions.description ||
|
|
215
|
+
this.surveyOptions.title ||
|
|
216
|
+
'How easy was it?';
|
|
217
|
+
|
|
218
|
+
const configs = {
|
|
219
|
+
nps: {
|
|
220
|
+
title: this.surveyOptions.title || '',
|
|
221
|
+
description: npsPrompt,
|
|
222
|
+
html: `
|
|
223
|
+
<div class="${npsContainerClass}">
|
|
224
|
+
${npsScale.values
|
|
225
|
+
.map(
|
|
226
|
+
(n) => `
|
|
227
|
+
<button class="${npsButtonClass}" data-score="${n}">${n}</button>
|
|
228
|
+
`
|
|
229
|
+
)
|
|
230
|
+
.join('')}
|
|
231
|
+
</div>
|
|
232
|
+
${this._renderScaleLabels(npsLowLabel, npsHighLabel)}
|
|
233
|
+
`,
|
|
234
|
+
},
|
|
235
|
+
csat: {
|
|
236
|
+
title: this.surveyOptions.title || '',
|
|
237
|
+
description: csatPrompt,
|
|
238
|
+
html: `
|
|
239
|
+
<div class="feedback-survey-csat">
|
|
240
|
+
${[
|
|
241
|
+
'\uD83D\uDE1E',
|
|
242
|
+
'\uD83D\uDE15',
|
|
243
|
+
'\uD83D\uDE10',
|
|
244
|
+
'\uD83D\uDE42',
|
|
245
|
+
'\uD83D\uDE04',
|
|
246
|
+
]
|
|
247
|
+
.map(
|
|
248
|
+
(emoji, i) => `
|
|
249
|
+
<button class="feedback-survey-csat-btn" data-score="${i + 1}">${emoji}</button>
|
|
250
|
+
`
|
|
251
|
+
)
|
|
252
|
+
.join('')}
|
|
253
|
+
</div>
|
|
254
|
+
${this._renderScaleLabels(
|
|
255
|
+
this.surveyOptions.lowLabel || 'Very dissatisfied',
|
|
256
|
+
this.surveyOptions.highLabel || 'Very satisfied'
|
|
257
|
+
)}
|
|
258
|
+
`,
|
|
259
|
+
},
|
|
260
|
+
ces: {
|
|
261
|
+
title: this.surveyOptions.title || '',
|
|
262
|
+
description: cesPrompt,
|
|
263
|
+
html: `
|
|
264
|
+
<div class="feedback-survey-ces-list">
|
|
265
|
+
${['Very Difficult', 'Difficult', 'Neutral', 'Easy', 'Very Easy']
|
|
266
|
+
.map(
|
|
267
|
+
(label, i) => `
|
|
268
|
+
<button class="feedback-survey-ces-btn feedback-survey-ces-list-btn" data-score="${i + 1}">${label}</button>
|
|
269
|
+
`
|
|
270
|
+
)
|
|
271
|
+
.join('')}
|
|
272
|
+
</div>
|
|
273
|
+
`,
|
|
274
|
+
},
|
|
275
|
+
custom: {
|
|
276
|
+
title: this.surveyOptions.title || 'Quick Feedback',
|
|
277
|
+
description:
|
|
278
|
+
this.surveyOptions.description ||
|
|
279
|
+
'Help us improve by answering a few questions.',
|
|
280
|
+
html: this._renderCustomQuestions(),
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return configs[this.surveyOptions.surveyType] || null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
_getNPSScale() {
|
|
288
|
+
const rawScale = Number(this.surveyOptions.ratingScale);
|
|
289
|
+
const scale = Number.isFinite(rawScale) && rawScale >= 2 ? rawScale : 5;
|
|
290
|
+
const start = scale === 11 ? 0 : 1;
|
|
291
|
+
return {
|
|
292
|
+
scale,
|
|
293
|
+
start,
|
|
294
|
+
values: Array.from({ length: scale }, (_, index) => start + index),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
_renderScaleLabels(lowLabel, highLabel) {
|
|
299
|
+
const low = lowLabel || '';
|
|
300
|
+
const high = highLabel || '';
|
|
301
|
+
if (!low && !high) {
|
|
302
|
+
return '';
|
|
303
|
+
}
|
|
304
|
+
return `
|
|
305
|
+
<div class="feedback-survey-labels">
|
|
306
|
+
<span>${low}</span>
|
|
307
|
+
<span>${high}</span>
|
|
308
|
+
</div>
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
_isRatingSurveyType(type = this.surveyOptions.surveyType) {
|
|
313
|
+
return type === 'nps' || type === 'csat' || type === 'ces';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_shouldShowTitle(config) {
|
|
317
|
+
if (!config || !config.title) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
if (typeof this.surveyOptions.showTitle === 'boolean') {
|
|
321
|
+
return this.surveyOptions.showTitle;
|
|
322
|
+
}
|
|
323
|
+
if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
|
|
324
|
+
return !config.description;
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_shouldShowDescription(config) {
|
|
330
|
+
if (typeof this.surveyOptions.showDescription === 'boolean') {
|
|
331
|
+
return this.surveyOptions.showDescription;
|
|
332
|
+
}
|
|
333
|
+
if (!this._isMultiPageSurvey() && this._isRatingSurveyType()) {
|
|
334
|
+
return Boolean(config && (config.description || config.title));
|
|
335
|
+
}
|
|
336
|
+
return Boolean(config && config.description);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_shouldShowFeedbackInput() {
|
|
340
|
+
if (this._isMultiPageSurvey()) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
if (typeof this.surveyOptions.showFeedbackInput === 'boolean') {
|
|
344
|
+
return this.surveyOptions.showFeedbackInput;
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
_shouldAutoSubmitOnSelect() {
|
|
350
|
+
if (typeof this.surveyOptions.autoSubmitOnSelect === 'boolean') {
|
|
351
|
+
return this.surveyOptions.autoSubmitOnSelect;
|
|
352
|
+
}
|
|
353
|
+
if (this._isMultiPageSurvey()) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
if (this.surveyOptions.showSubmitButton === true) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
return this._isRatingSurveyType() && !this._shouldShowFeedbackInput();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
_shouldShowActions() {
|
|
363
|
+
if (this._isMultiPageSurvey()) {
|
|
364
|
+
const page = this._getCurrentPage();
|
|
365
|
+
if (page && page.type === 'link') {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
if (typeof this.surveyOptions.showSubmitButton === 'boolean') {
|
|
371
|
+
return this.surveyOptions.showSubmitButton;
|
|
372
|
+
}
|
|
373
|
+
return !this._shouldAutoSubmitOnSelect();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
_isMultiPageSurvey() {
|
|
377
|
+
return (
|
|
378
|
+
Array.isArray(this.surveyOptions.pages) &&
|
|
379
|
+
this.surveyOptions.pages.length > 0
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
_getCurrentPage() {
|
|
384
|
+
if (!this._isMultiPageSurvey()) return null;
|
|
385
|
+
return this.surveyOptions.pages[this.surveyState.currentPageIndex] || null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
_isLastPage() {
|
|
389
|
+
if (!this._isMultiPageSurvey()) return true;
|
|
390
|
+
return (
|
|
391
|
+
this.surveyState.currentPageIndex >= this.surveyOptions.pages.length - 1
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
_getCurrentPageConfig() {
|
|
396
|
+
const page = this._getCurrentPage();
|
|
397
|
+
if (!page) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
title: page.title || '',
|
|
403
|
+
description: page.description || '',
|
|
404
|
+
html: this._renderSurveyPage(page),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
_renderSurveyPage(page) {
|
|
409
|
+
switch (page.type) {
|
|
410
|
+
case 'rating':
|
|
411
|
+
return this._renderRatingPage(page);
|
|
412
|
+
case 'multiple_choice':
|
|
413
|
+
return this._renderMultipleChoicePage(page);
|
|
414
|
+
case 'text':
|
|
415
|
+
return this._renderTextPage(page);
|
|
416
|
+
case 'link':
|
|
417
|
+
return this._renderLinkPage(page);
|
|
418
|
+
default:
|
|
419
|
+
return '';
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
_renderRatingPage(page) {
|
|
424
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
425
|
+
const config = page.ratingConfig || page.rating_config || {};
|
|
426
|
+
const scale = Number(config.scale) || 5;
|
|
427
|
+
const topSurveyType = this.surveyOptions.surveyType;
|
|
428
|
+
const configType = config.survey_type;
|
|
429
|
+
let ratingType;
|
|
430
|
+
if (topSurveyType === 'ces') {
|
|
431
|
+
ratingType = 'ces';
|
|
432
|
+
} else if (topSurveyType === 'nps') {
|
|
433
|
+
ratingType = 'nps';
|
|
434
|
+
} else if (topSurveyType === 'csat' && !configType) {
|
|
435
|
+
ratingType = 'emoji';
|
|
436
|
+
} else {
|
|
437
|
+
ratingType = configType || topSurveyType || 'csat';
|
|
438
|
+
}
|
|
439
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
440
|
+
const currentRating = pageAnswer.rating;
|
|
441
|
+
const defaultLowLabel =
|
|
442
|
+
ratingType === 'nps'
|
|
443
|
+
? scale === 11
|
|
444
|
+
? 'Not likely'
|
|
445
|
+
: 'Strongly Disagree'
|
|
446
|
+
: '';
|
|
447
|
+
const defaultHighLabel =
|
|
448
|
+
ratingType === 'nps'
|
|
449
|
+
? scale === 11
|
|
450
|
+
? 'Very likely'
|
|
451
|
+
: 'Strongly Agree'
|
|
452
|
+
: '';
|
|
453
|
+
const lowLabel =
|
|
454
|
+
config.low_label || this.surveyOptions.lowLabel || defaultLowLabel;
|
|
455
|
+
const highLabel =
|
|
456
|
+
config.high_label || this.surveyOptions.highLabel || defaultHighLabel;
|
|
457
|
+
const labels = this._renderScaleLabels(lowLabel, highLabel);
|
|
458
|
+
|
|
459
|
+
if (ratingType === 'nps') {
|
|
460
|
+
const npsScale = Number.isFinite(scale) && scale >= 2 ? scale : 5;
|
|
461
|
+
const start = npsScale === 11 ? 0 : 1;
|
|
462
|
+
const values = Array.from(
|
|
463
|
+
{ length: npsScale },
|
|
464
|
+
(_, index) => start + index
|
|
465
|
+
);
|
|
466
|
+
const usesSegmentedScale = values.length <= 7;
|
|
467
|
+
const containerClass = usesSegmentedScale
|
|
468
|
+
? 'feedback-survey-ces feedback-survey-rating-scale'
|
|
469
|
+
: 'feedback-survey-nps';
|
|
470
|
+
const buttonClass = usesSegmentedScale
|
|
471
|
+
? 'feedback-survey-page-rating-btn feedback-survey-nps-btn feedback-survey-ces-btn feedback-survey-rating-scale-btn'
|
|
472
|
+
: 'feedback-survey-page-rating-btn feedback-survey-nps-btn';
|
|
473
|
+
return `
|
|
474
|
+
<div class="${containerClass}" data-page-id="${pageId}">
|
|
475
|
+
${values
|
|
476
|
+
.map((n) => {
|
|
477
|
+
const selected = currentRating === n ? ' selected' : '';
|
|
478
|
+
return `<button class="${buttonClass}${selected}" data-page-id="${pageId}" data-score="${n}">${n}</button>`;
|
|
479
|
+
})
|
|
480
|
+
.join('')}
|
|
481
|
+
</div>
|
|
482
|
+
${labels}
|
|
483
|
+
`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (ratingType === 'ces') {
|
|
487
|
+
const cesLabels = [
|
|
488
|
+
'Very Difficult',
|
|
489
|
+
'Difficult',
|
|
490
|
+
'Neutral',
|
|
491
|
+
'Easy',
|
|
492
|
+
'Very Easy',
|
|
493
|
+
];
|
|
494
|
+
return `
|
|
495
|
+
<div class="feedback-survey-ces-list" data-page-id="${pageId}">
|
|
496
|
+
${cesLabels
|
|
497
|
+
.map((label, i) => {
|
|
498
|
+
const score = i + 1;
|
|
499
|
+
const selected = currentRating === score ? ' selected' : '';
|
|
500
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-ces-list-btn${selected}" data-page-id="${pageId}" data-score="${score}">${label}</button>`;
|
|
501
|
+
})
|
|
502
|
+
.join('')}
|
|
503
|
+
</div>
|
|
504
|
+
`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (ratingType === 'emoji' && scale === 5) {
|
|
508
|
+
const emojis = [
|
|
509
|
+
'\uD83D\uDE1E',
|
|
510
|
+
'\uD83D\uDE15',
|
|
511
|
+
'\uD83D\uDE10',
|
|
512
|
+
'\uD83D\uDE42',
|
|
513
|
+
'\uD83D\uDE04',
|
|
514
|
+
];
|
|
515
|
+
return `
|
|
516
|
+
<div class="feedback-survey-csat" data-page-id="${pageId}">
|
|
517
|
+
${emojis
|
|
518
|
+
.map((emoji, i) => {
|
|
519
|
+
const score = i + 1;
|
|
520
|
+
const selected = currentRating === score ? ' selected' : '';
|
|
521
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-csat-btn${selected}" data-page-id="${pageId}" data-score="${score}">${emoji}</button>`;
|
|
522
|
+
})
|
|
523
|
+
.join('')}
|
|
524
|
+
</div>
|
|
525
|
+
${labels}
|
|
526
|
+
`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return `
|
|
530
|
+
<div class="feedback-survey-rating-scale" data-page-id="${pageId}">
|
|
531
|
+
${[...Array(scale).keys()]
|
|
532
|
+
.map((i) => {
|
|
533
|
+
const score = i + 1;
|
|
534
|
+
const selected = currentRating === score ? ' selected' : '';
|
|
535
|
+
return `<button class="feedback-survey-page-rating-btn feedback-survey-rating-scale-btn${selected}" data-page-id="${pageId}" data-score="${score}">${score}</button>`;
|
|
536
|
+
})
|
|
537
|
+
.join('')}
|
|
538
|
+
</div>
|
|
539
|
+
${labels}
|
|
540
|
+
`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
_renderMultipleChoicePage(page) {
|
|
544
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
545
|
+
const config =
|
|
546
|
+
page.multipleChoiceConfig || page.multiple_choice_config || {};
|
|
547
|
+
const options = Array.isArray(config.options) ? config.options : [];
|
|
548
|
+
const allowMultiple =
|
|
549
|
+
config.allow_multiple === true ||
|
|
550
|
+
config.multiple === true ||
|
|
551
|
+
config.allow_multiple_selection === true;
|
|
552
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
553
|
+
const selectedValues = Array.isArray(pageAnswer.values)
|
|
554
|
+
? pageAnswer.values
|
|
555
|
+
: pageAnswer.value
|
|
556
|
+
? [pageAnswer.value]
|
|
557
|
+
: [];
|
|
558
|
+
|
|
559
|
+
return `
|
|
560
|
+
<div class="feedback-survey-multiple-choice" data-page-id="${pageId}" data-multiple="${allowMultiple}">
|
|
561
|
+
${options
|
|
562
|
+
.map((option, index) => {
|
|
563
|
+
const value =
|
|
564
|
+
option.value || option.id || option.key || `option_${index}`;
|
|
565
|
+
const label = option.label || option.text || String(value);
|
|
566
|
+
const selected = selectedValues.includes(value) ? ' selected' : '';
|
|
567
|
+
return `<button class="feedback-survey-page-choice-btn${selected}" data-page-id="${pageId}" data-value="${value}">${label}</button>`;
|
|
568
|
+
})
|
|
569
|
+
.join('')}
|
|
570
|
+
</div>
|
|
571
|
+
`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
_renderTextPage(page) {
|
|
575
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
576
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
577
|
+
const value = pageAnswer.text || '';
|
|
578
|
+
const placeholder = page.placeholder || 'Type your answer...';
|
|
579
|
+
|
|
580
|
+
return `
|
|
581
|
+
<div class="feedback-survey-text-page" data-page-id="${pageId}">
|
|
582
|
+
<textarea class="feedback-survey-page-textarea" data-page-id="${pageId}" placeholder="${placeholder}">${value}</textarea>
|
|
583
|
+
</div>
|
|
584
|
+
`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
_renderLinkPage(page) {
|
|
588
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
589
|
+
const config = page.link_config || page.linkConfig || {};
|
|
590
|
+
const buttonText = config.button_text || config.buttonText || 'Click here';
|
|
591
|
+
const linkText = config.link_text || config.linkText || '';
|
|
592
|
+
const redirectUrl = config.redirect_url || config.redirectUrl || '#';
|
|
593
|
+
const openIn = config.open_in || config.openIn || 'new_tab';
|
|
594
|
+
const target = openIn === 'new_tab' ? '_blank' : '_self';
|
|
595
|
+
const pageAnswer = this.surveyState.pageAnswers[pageId] || {};
|
|
596
|
+
const clicked = pageAnswer.clicked === true;
|
|
597
|
+
|
|
598
|
+
const iconExternalLink = `<iconify-icon icon="ph:arrow-square-out-duotone" width="15" height="15"></iconify-icon>`;
|
|
599
|
+
return `
|
|
600
|
+
<div class="feedback-survey-link-page" data-page-id="${pageId}">
|
|
601
|
+
${linkText ? `<p class="feedback-survey-link-text">${linkText}</p>` : ''}
|
|
602
|
+
<a class="feedback-survey-link-btn${clicked ? ' selected' : ''}"
|
|
603
|
+
href="${redirectUrl}"
|
|
604
|
+
target="${target}"
|
|
605
|
+
rel="noopener noreferrer"
|
|
606
|
+
data-page-id="${pageId}">
|
|
607
|
+
${buttonText} ${iconExternalLink}
|
|
608
|
+
</a>
|
|
609
|
+
</div>
|
|
610
|
+
`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
_renderCustomQuestions() {
|
|
614
|
+
if (
|
|
615
|
+
!this.surveyOptions.customQuestions ||
|
|
616
|
+
this.surveyOptions.customQuestions.length === 0
|
|
617
|
+
) {
|
|
618
|
+
return `
|
|
619
|
+
<div class="sdk-form-group">
|
|
620
|
+
<label class="sdk-label">What feature do you use most?</label>
|
|
621
|
+
<select class="feedback-survey-select" data-question="feature">
|
|
622
|
+
<option value="">Select a feature</option>
|
|
623
|
+
<option value="feedback">Feedback Collection</option>
|
|
624
|
+
<option value="surveys">Surveys</option>
|
|
625
|
+
<option value="analytics">Analytics</option>
|
|
626
|
+
<option value="integrations">Integrations</option>
|
|
627
|
+
</select>
|
|
628
|
+
</div>
|
|
629
|
+
<div class="sdk-form-group">
|
|
630
|
+
<label class="sdk-label">How often do you use it?</label>
|
|
631
|
+
<div class="feedback-survey-frequency">
|
|
632
|
+
${['Daily', 'Weekly', 'Monthly', 'Rarely']
|
|
633
|
+
.map(
|
|
634
|
+
(freq) => `
|
|
635
|
+
<button class="feedback-survey-freq-btn" data-freq="${freq}">${freq}</button>
|
|
636
|
+
`
|
|
637
|
+
)
|
|
638
|
+
.join('')}
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return this.surveyOptions.customQuestions
|
|
645
|
+
.map(
|
|
646
|
+
(q, index) => `
|
|
647
|
+
<div class="sdk-form-group">
|
|
648
|
+
<label class="sdk-label">${q.label}</label>
|
|
649
|
+
${this._renderQuestionInput(q, index)}
|
|
650
|
+
</div>
|
|
651
|
+
`
|
|
652
|
+
)
|
|
653
|
+
.join('');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_renderQuestionInput(question, index) {
|
|
657
|
+
switch (question.type) {
|
|
658
|
+
case 'select':
|
|
659
|
+
return `
|
|
660
|
+
<select class="feedback-survey-select" data-question="${question.id || index}">
|
|
661
|
+
<option value="">${question.placeholder || 'Select an option'}</option>
|
|
662
|
+
${question.options.map((opt) => `<option value="${opt.value}">${opt.label}</option>`).join('')}
|
|
663
|
+
</select>
|
|
664
|
+
`;
|
|
665
|
+
case 'text':
|
|
666
|
+
return `
|
|
667
|
+
<input type="text" class="feedback-survey-input" data-question="${question.id || index}" placeholder="${question.placeholder || ''}">
|
|
668
|
+
`;
|
|
669
|
+
default:
|
|
670
|
+
return '';
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
_attachSurveyEvents() {
|
|
675
|
+
if (!this.surveyElement) return;
|
|
676
|
+
|
|
677
|
+
const closeBtn = this.surveyElement.querySelector('.feedback-survey-close');
|
|
678
|
+
closeBtn.addEventListener('click', () => this._handleDismiss());
|
|
679
|
+
|
|
680
|
+
const submitBtn = this.surveyElement.querySelector(
|
|
681
|
+
'.feedback-survey-submit'
|
|
682
|
+
);
|
|
683
|
+
if (submitBtn) {
|
|
684
|
+
submitBtn.addEventListener('click', () => this._handleSubmit());
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const backBtn = this.surveyElement.querySelector('.feedback-survey-back');
|
|
688
|
+
if (backBtn) {
|
|
689
|
+
backBtn.addEventListener('click', () => this._handleBack());
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const textarea = this.surveyElement.querySelector(
|
|
693
|
+
'.feedback-survey-textarea'
|
|
694
|
+
);
|
|
695
|
+
if (textarea) {
|
|
696
|
+
textarea.addEventListener('input', (e) => {
|
|
697
|
+
this.surveyState.feedback = e.target.value;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
this._attachTypeSpecificEvents();
|
|
702
|
+
|
|
703
|
+
this._escapeHandler = (e) => {
|
|
704
|
+
if (e.key === 'Escape') {
|
|
705
|
+
this._handleDismiss();
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
document.addEventListener('keydown', this._escapeHandler);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
_attachTypeSpecificEvents() {
|
|
712
|
+
if (this._isMultiPageSurvey()) {
|
|
713
|
+
this._attachCurrentPageEvents();
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const type = this.surveyOptions.surveyType;
|
|
718
|
+
|
|
719
|
+
if (type === 'nps') {
|
|
720
|
+
this.surveyElement
|
|
721
|
+
.querySelectorAll('.feedback-survey-nps-btn')
|
|
722
|
+
.forEach((btn) => {
|
|
723
|
+
btn.addEventListener('click', () =>
|
|
724
|
+
this._selectNPS(parseInt(btn.dataset.score))
|
|
725
|
+
);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (type === 'csat') {
|
|
730
|
+
this.surveyElement
|
|
731
|
+
.querySelectorAll('.feedback-survey-csat-btn')
|
|
732
|
+
.forEach((btn) => {
|
|
733
|
+
btn.addEventListener('click', () =>
|
|
734
|
+
this._selectCSAT(parseInt(btn.dataset.score))
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (type === 'ces') {
|
|
740
|
+
this.surveyElement
|
|
741
|
+
.querySelectorAll('.feedback-survey-ces-btn')
|
|
742
|
+
.forEach((btn) => {
|
|
743
|
+
btn.addEventListener('click', () =>
|
|
744
|
+
this._selectCES(parseInt(btn.dataset.score))
|
|
745
|
+
);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (type === 'custom') {
|
|
750
|
+
this.surveyElement
|
|
751
|
+
.querySelectorAll('.feedback-survey-freq-btn')
|
|
752
|
+
.forEach((btn) => {
|
|
753
|
+
btn.addEventListener('click', () =>
|
|
754
|
+
this._selectFrequency(btn.dataset.freq)
|
|
755
|
+
);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
this.surveyElement
|
|
759
|
+
.querySelectorAll('.feedback-survey-select')
|
|
760
|
+
.forEach((select) => {
|
|
761
|
+
select.addEventListener('change', (e) => {
|
|
762
|
+
this.surveyState.customAnswers[select.dataset.question] =
|
|
763
|
+
e.target.value;
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
this.surveyElement
|
|
768
|
+
.querySelectorAll('.feedback-survey-input')
|
|
769
|
+
.forEach((input) => {
|
|
770
|
+
input.addEventListener('input', (e) => {
|
|
771
|
+
this.surveyState.customAnswers[input.dataset.question] =
|
|
772
|
+
e.target.value;
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
_attachCurrentPageEvents() {
|
|
779
|
+
const page = this._getCurrentPage();
|
|
780
|
+
if (!page || !this.surveyElement) return;
|
|
781
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
782
|
+
|
|
783
|
+
if (page.type === 'rating') {
|
|
784
|
+
this.surveyElement
|
|
785
|
+
.querySelectorAll('.feedback-survey-page-rating-btn')
|
|
786
|
+
.forEach((btn) => {
|
|
787
|
+
btn.addEventListener('click', () => {
|
|
788
|
+
const score = parseInt(btn.dataset.score);
|
|
789
|
+
if (Number.isNaN(score)) return;
|
|
790
|
+
this._setPageAnswer(pageId, { rating: score });
|
|
791
|
+
|
|
792
|
+
this.surveyElement
|
|
793
|
+
.querySelectorAll('.feedback-survey-page-rating-btn')
|
|
794
|
+
.forEach((item) => item.classList.remove('selected'));
|
|
795
|
+
btn.classList.add('selected');
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (page.type === 'multiple_choice') {
|
|
801
|
+
const container = this.surveyElement.querySelector(
|
|
802
|
+
'.feedback-survey-multiple-choice'
|
|
803
|
+
);
|
|
804
|
+
const allowMultiple = container
|
|
805
|
+
? container.dataset.multiple === 'true'
|
|
806
|
+
: false;
|
|
807
|
+
|
|
808
|
+
this.surveyElement
|
|
809
|
+
.querySelectorAll('.feedback-survey-page-choice-btn')
|
|
810
|
+
.forEach((btn) => {
|
|
811
|
+
btn.addEventListener('click', () => {
|
|
812
|
+
const value = btn.dataset.value;
|
|
813
|
+
if (!value) return;
|
|
814
|
+
|
|
815
|
+
const existing = this.surveyState.pageAnswers[pageId] || {};
|
|
816
|
+
const existingValues = Array.isArray(existing.values)
|
|
817
|
+
? existing.values
|
|
818
|
+
: existing.value
|
|
819
|
+
? [existing.value]
|
|
820
|
+
: [];
|
|
821
|
+
|
|
822
|
+
let nextValues = [];
|
|
823
|
+
if (allowMultiple) {
|
|
824
|
+
const hasValue = existingValues.includes(value);
|
|
825
|
+
nextValues = hasValue
|
|
826
|
+
? existingValues.filter((v) => v !== value)
|
|
827
|
+
: [...existingValues, value];
|
|
828
|
+
} else {
|
|
829
|
+
nextValues = [value];
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
this._setPageAnswer(pageId, {
|
|
833
|
+
value: nextValues[0] || null,
|
|
834
|
+
values: nextValues,
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
if (!allowMultiple) {
|
|
838
|
+
this.surveyElement
|
|
839
|
+
.querySelectorAll('.feedback-survey-page-choice-btn')
|
|
840
|
+
.forEach((item) => item.classList.remove('selected'));
|
|
841
|
+
}
|
|
842
|
+
btn.classList.toggle('selected', nextValues.includes(value));
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (page.type === 'text') {
|
|
848
|
+
const textarea = this.surveyElement.querySelector(
|
|
849
|
+
'.feedback-survey-page-textarea'
|
|
850
|
+
);
|
|
851
|
+
if (textarea) {
|
|
852
|
+
textarea.addEventListener('input', (e) => {
|
|
853
|
+
this._setPageAnswer(pageId, { text: e.target.value });
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (page.type === 'link') {
|
|
859
|
+
this.surveyElement
|
|
860
|
+
.querySelectorAll('.feedback-survey-link-btn')
|
|
861
|
+
.forEach((btn) => {
|
|
862
|
+
btn.addEventListener('click', () => {
|
|
863
|
+
const pId = btn.dataset.pageId;
|
|
864
|
+
this._setPageAnswer(pId, { clicked: true });
|
|
865
|
+
btn.classList.add('selected');
|
|
866
|
+
|
|
867
|
+
const navigation = page.afterThisPage || page.after_this_page;
|
|
868
|
+
const goesTo = navigation ? navigation.default : null;
|
|
869
|
+
if (!goesTo || goesTo === 'end_survey') {
|
|
870
|
+
setTimeout(() => this._handleSubmit(), 400);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
_selectNPS(score) {
|
|
878
|
+
this.surveyState.score = score;
|
|
879
|
+
this.surveyElement
|
|
880
|
+
.querySelectorAll('.feedback-survey-nps-btn')
|
|
881
|
+
.forEach((btn) => {
|
|
882
|
+
const btnScore = parseInt(btn.dataset.score);
|
|
883
|
+
if (btnScore === score) {
|
|
884
|
+
btn.classList.add('selected');
|
|
885
|
+
} else {
|
|
886
|
+
btn.classList.remove('selected');
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
this._maybeAutoSubmit();
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
_selectCSAT(score) {
|
|
893
|
+
this.surveyState.score = score;
|
|
894
|
+
this.surveyElement
|
|
895
|
+
.querySelectorAll('.feedback-survey-csat-btn')
|
|
896
|
+
.forEach((btn) => {
|
|
897
|
+
const btnScore = parseInt(btn.dataset.score);
|
|
898
|
+
if (btnScore === score) {
|
|
899
|
+
btn.classList.add('selected');
|
|
900
|
+
} else {
|
|
901
|
+
btn.classList.remove('selected');
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
this._maybeAutoSubmit();
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
_selectCES(score) {
|
|
908
|
+
this.surveyState.score = score;
|
|
909
|
+
this.surveyElement
|
|
910
|
+
.querySelectorAll('.feedback-survey-ces-btn')
|
|
911
|
+
.forEach((btn) => {
|
|
912
|
+
const btnScore = parseInt(btn.dataset.score);
|
|
913
|
+
if (btnScore === score) {
|
|
914
|
+
btn.classList.add('selected');
|
|
915
|
+
} else {
|
|
916
|
+
btn.classList.remove('selected');
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
this._maybeAutoSubmit();
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
_maybeAutoSubmit() {
|
|
923
|
+
if (!this._shouldAutoSubmitOnSelect()) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
this._handleSubmit();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
_selectFrequency(freq) {
|
|
930
|
+
this.surveyState.customAnswers.frequency = freq;
|
|
931
|
+
this.surveyElement
|
|
932
|
+
.querySelectorAll('.feedback-survey-freq-btn')
|
|
933
|
+
.forEach((btn) => {
|
|
934
|
+
if (btn.dataset.freq === freq) {
|
|
935
|
+
btn.classList.add('selected');
|
|
936
|
+
} else {
|
|
937
|
+
btn.classList.remove('selected');
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
_setSubmitLoading(loading) {
|
|
943
|
+
const btn = this.surveyElement?.querySelector('.feedback-survey-submit');
|
|
944
|
+
if (!btn) return;
|
|
945
|
+
btn.disabled = loading;
|
|
946
|
+
btn.innerHTML = loading
|
|
947
|
+
? `<iconify-icon icon="ph:circle-notch" width="16" height="16" style="animation: spin 0.8s linear infinite;"></iconify-icon> Submitting...`
|
|
948
|
+
: `Submit <iconify-icon icon="ph:arrow-right" width="16" height="16"></iconify-icon>`;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async _handleSubmit() {
|
|
952
|
+
const type = this.surveyOptions.surveyType;
|
|
953
|
+
if (this.surveyState.isSubmitting) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (this._isMultiPageSurvey()) {
|
|
958
|
+
if (!this._validateCurrentPage()) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const nextPageIndex = this._getNextPageIndex();
|
|
963
|
+
if (
|
|
964
|
+
nextPageIndex !== -1 &&
|
|
965
|
+
nextPageIndex !== this.surveyState.currentPageIndex
|
|
966
|
+
) {
|
|
967
|
+
this.surveyState.currentPageIndex = nextPageIndex;
|
|
968
|
+
this._renderSurvey();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (
|
|
974
|
+
!this._isMultiPageSurvey() &&
|
|
975
|
+
(type === 'nps' || type === 'csat' || type === 'ces') &&
|
|
976
|
+
this.surveyState.score === null
|
|
977
|
+
) {
|
|
978
|
+
this._showError('Please select a rating');
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
this.surveyState.isSubmitting = true;
|
|
983
|
+
this._setSubmitLoading(true);
|
|
984
|
+
|
|
985
|
+
const respondent = this._getRespondentContext();
|
|
986
|
+
const normalizedPageAnswers = this._normalizePageAnswersForSubmit();
|
|
987
|
+
const mergedAnswers = {
|
|
988
|
+
...this.surveyState.customAnswers,
|
|
989
|
+
...(Object.keys(normalizedPageAnswers).length > 0 && {
|
|
990
|
+
page_answers: normalizedPageAnswers,
|
|
991
|
+
}),
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const responseData = {
|
|
995
|
+
rating: this._getSubmissionRating(),
|
|
996
|
+
feedback: this.surveyState.feedback,
|
|
997
|
+
answers: mergedAnswers,
|
|
998
|
+
...(respondent.respondentId && { respondentId: respondent.respondentId }),
|
|
999
|
+
...(respondent.email && { email: respondent.email }),
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const response = {
|
|
1003
|
+
type: type,
|
|
1004
|
+
score: this._getSubmissionRating(),
|
|
1005
|
+
feedback: this.surveyState.feedback,
|
|
1006
|
+
customAnswers: mergedAnswers,
|
|
1007
|
+
pageAnswers: normalizedPageAnswers,
|
|
1008
|
+
timestamp: new Date().toISOString(),
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
try {
|
|
1012
|
+
if (this.surveyOptions.onSubmit) {
|
|
1013
|
+
this.surveyOptions.onSubmit(response);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const surveyId =
|
|
1017
|
+
this.surveyOptions.surveyId || `local_${type}_${Date.now()}`;
|
|
1018
|
+
await this.apiService.submitSurveyResponse(surveyId, responseData);
|
|
1019
|
+
|
|
1020
|
+
this.sdk.eventBus.emit('survey:submitted', { widget: this, response });
|
|
1021
|
+
this._closeSurvey();
|
|
1022
|
+
this._showSuccessNotification();
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
console.error('[SurveyWidget] Failed to submit survey:', error);
|
|
1025
|
+
this._showError('Something went wrong. Please try again.');
|
|
1026
|
+
} finally {
|
|
1027
|
+
this.surveyState.isSubmitting = false;
|
|
1028
|
+
this._setSubmitLoading(false);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
_handleBack() {
|
|
1033
|
+
if (!this._isMultiPageSurvey()) return;
|
|
1034
|
+
if (this.surveyState.currentPageIndex <= 0) return;
|
|
1035
|
+
this.surveyState.currentPageIndex -= 1;
|
|
1036
|
+
this._renderSurvey();
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
_setPageAnswer(pageId, data) {
|
|
1040
|
+
if (!pageId) return;
|
|
1041
|
+
this.surveyState.pageAnswers[pageId] = {
|
|
1042
|
+
...(this.surveyState.pageAnswers[pageId] || {}),
|
|
1043
|
+
...data,
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// Emit question answered event
|
|
1047
|
+
const page = this._getCurrentPage();
|
|
1048
|
+
this.sdk.eventBus.emit('survey:questionAnswered', {
|
|
1049
|
+
widget: this,
|
|
1050
|
+
pageId,
|
|
1051
|
+
pageType: page?.type,
|
|
1052
|
+
answer: this.surveyState.pageAnswers[pageId],
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
_validateCurrentPage() {
|
|
1057
|
+
const page = this._getCurrentPage();
|
|
1058
|
+
if (!page || page.required === false) return true;
|
|
1059
|
+
const pageId = page.id || `page_${this.surveyState.currentPageIndex}`;
|
|
1060
|
+
|
|
1061
|
+
const answer = this.surveyState.pageAnswers[pageId] || {};
|
|
1062
|
+
|
|
1063
|
+
if (page.type === 'rating') {
|
|
1064
|
+
if (typeof answer.rating !== 'number') {
|
|
1065
|
+
this._showError('Please select a rating');
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (page.type === 'multiple_choice') {
|
|
1071
|
+
const hasSingle = Boolean(answer.value);
|
|
1072
|
+
const hasMany = Array.isArray(answer.values) && answer.values.length > 0;
|
|
1073
|
+
if (!hasSingle && !hasMany) {
|
|
1074
|
+
this._showError('Please select an option');
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (page.type === 'text') {
|
|
1080
|
+
if (!answer.text || !String(answer.text).trim()) {
|
|
1081
|
+
this._showError('Please enter an answer');
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (page.type === 'link') {
|
|
1087
|
+
if (page.required && !answer.clicked) {
|
|
1088
|
+
this._showError('Please click the link to continue');
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
_getNextPageIndex() {
|
|
1097
|
+
if (!this._isMultiPageSurvey()) {
|
|
1098
|
+
return -1;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const page = this._getCurrentPage();
|
|
1102
|
+
const total = this.surveyOptions.pages.length;
|
|
1103
|
+
const currentIndex = this.surveyState.currentPageIndex;
|
|
1104
|
+
const fallbackNext = currentIndex + 1 < total ? currentIndex + 1 : -1;
|
|
1105
|
+
const navigation = page ? page.afterThisPage || page.after_this_page : null;
|
|
1106
|
+
if (!navigation) {
|
|
1107
|
+
return fallbackNext;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const nextValue = navigation.default;
|
|
1111
|
+
if (!nextValue) {
|
|
1112
|
+
return fallbackNext;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (nextValue === 'end_survey') {
|
|
1116
|
+
return -1;
|
|
1117
|
+
}
|
|
1118
|
+
if (nextValue === 'next_page' || nextValue === 'next') {
|
|
1119
|
+
return fallbackNext;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (typeof nextValue === 'number') {
|
|
1123
|
+
return nextValue >= 0 && nextValue < total ? nextValue : fallbackNext;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (typeof nextValue === 'string') {
|
|
1127
|
+
const normalizedId = nextValue.replace(/^page:/, '');
|
|
1128
|
+
const pageIndex = this.surveyOptions.pages.findIndex(
|
|
1129
|
+
(item) => item.id === normalizedId
|
|
1130
|
+
);
|
|
1131
|
+
return pageIndex >= 0 ? pageIndex : fallbackNext;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return fallbackNext;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
_getSubmissionRating() {
|
|
1138
|
+
if (typeof this.surveyState.score === 'number') {
|
|
1139
|
+
return this.surveyState.score;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (!this._isMultiPageSurvey()) {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
for (const page of this.surveyOptions.pages) {
|
|
1147
|
+
const pageId =
|
|
1148
|
+
page.id || `page_${this.surveyOptions.pages.indexOf(page)}`;
|
|
1149
|
+
const answer = this.surveyState.pageAnswers[pageId];
|
|
1150
|
+
if (answer && typeof answer.rating === 'number') {
|
|
1151
|
+
return answer.rating;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
_normalizePageAnswersForSubmit() {
|
|
1159
|
+
const output = {};
|
|
1160
|
+
for (const [pageId, answer] of Object.entries(
|
|
1161
|
+
this.surveyState.pageAnswers
|
|
1162
|
+
)) {
|
|
1163
|
+
if (answer == null) continue;
|
|
1164
|
+
if (Array.isArray(answer.values) && answer.values.length > 0) {
|
|
1165
|
+
output[pageId] = answer.values;
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
if (answer.value != null && answer.value !== '') {
|
|
1169
|
+
output[pageId] = answer.value;
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (typeof answer.rating === 'number') {
|
|
1173
|
+
output[pageId] = answer.rating;
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (typeof answer.text === 'string' && answer.text.trim()) {
|
|
1177
|
+
output[pageId] = answer.text.trim();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return output;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
_getRespondentContext() {
|
|
1184
|
+
const sdkMetadata =
|
|
1185
|
+
typeof this.sdk.getMetadata === 'function'
|
|
1186
|
+
? this.sdk.getMetadata() || {}
|
|
1187
|
+
: {};
|
|
1188
|
+
const apiMetadata =
|
|
1189
|
+
this.apiService && typeof this.apiService.getMetadata === 'function'
|
|
1190
|
+
? this.apiService.getMetadata() || {}
|
|
1191
|
+
: {};
|
|
1192
|
+
const localMetadata = this.options.metadata || {};
|
|
1193
|
+
|
|
1194
|
+
return {
|
|
1195
|
+
respondentId:
|
|
1196
|
+
this.surveyOptions.respondentId ||
|
|
1197
|
+
localMetadata.user_id ||
|
|
1198
|
+
sdkMetadata.user_id ||
|
|
1199
|
+
apiMetadata.user_id ||
|
|
1200
|
+
null,
|
|
1201
|
+
email:
|
|
1202
|
+
this.surveyOptions.email ||
|
|
1203
|
+
localMetadata.email ||
|
|
1204
|
+
sdkMetadata.email ||
|
|
1205
|
+
apiMetadata.email ||
|
|
1206
|
+
null,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async _handleDismiss() {
|
|
1211
|
+
if (this.surveyOptions.surveyId) {
|
|
1212
|
+
try {
|
|
1213
|
+
await this.apiService.dismissSurvey(this.surveyOptions.surveyId);
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
console.error('[SurveyWidget] Failed to dismiss survey:', error);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (this.surveyOptions.onDismiss) {
|
|
1220
|
+
this.surveyOptions.onDismiss();
|
|
1221
|
+
}
|
|
1222
|
+
this.sdk.eventBus.emit('survey:dismissed', { widget: this });
|
|
1223
|
+
this._closeSurvey();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
_showError(message) {
|
|
1227
|
+
const existing = this.surveyElement.querySelector('.feedback-survey-error');
|
|
1228
|
+
if (existing) existing.remove();
|
|
1229
|
+
|
|
1230
|
+
const error = document.createElement('div');
|
|
1231
|
+
error.className = 'feedback-survey-error';
|
|
1232
|
+
error.innerHTML = `<iconify-icon icon="ph:warning-duotone" width="15" height="15"></iconify-icon><span>${message}</span>`;
|
|
1233
|
+
|
|
1234
|
+
this.surveyElement.prepend(error);
|
|
1235
|
+
|
|
1236
|
+
setTimeout(() => error.remove(), 3000);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
_showSuccessNotification() {
|
|
1240
|
+
const notification = document.createElement('div');
|
|
1241
|
+
notification.className =
|
|
1242
|
+
'product7-notification product7-notification-success';
|
|
1243
|
+
notification.innerHTML = `
|
|
1244
|
+
<div>
|
|
1245
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z" fill="currentColor"/></svg>
|
|
1246
|
+
<span>Thank you for your feedback!</span>
|
|
1247
|
+
</div>
|
|
1248
|
+
`;
|
|
1249
|
+
document.body.appendChild(notification);
|
|
1250
|
+
|
|
1251
|
+
setTimeout(() => {
|
|
1252
|
+
notification.style.opacity = '0';
|
|
1253
|
+
notification.style.transition = 'opacity 0.3s ease';
|
|
1254
|
+
setTimeout(() => notification.remove(), 300);
|
|
1255
|
+
}, 3000);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
_closeSurvey(resetState = true, immediate = false) {
|
|
1259
|
+
if (this._escapeHandler) {
|
|
1260
|
+
document.removeEventListener('keydown', this._escapeHandler);
|
|
1261
|
+
this._escapeHandler = null;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const surveyEl = this.surveyElement;
|
|
1265
|
+
const backdropEl = this.backdropElement;
|
|
1266
|
+
|
|
1267
|
+
if (surveyEl) {
|
|
1268
|
+
surveyEl.style.opacity = '0';
|
|
1269
|
+
const pos = this.surveyOptions.position;
|
|
1270
|
+
surveyEl.style.transform =
|
|
1271
|
+
pos === 'center'
|
|
1272
|
+
? 'translate(-50%, -50%) scale(0.95)'
|
|
1273
|
+
: pos === 'bottom'
|
|
1274
|
+
? 'translateY(100%)'
|
|
1275
|
+
: 'translateY(10px)';
|
|
1276
|
+
if (immediate) {
|
|
1277
|
+
if (surveyEl.parentNode) {
|
|
1278
|
+
surveyEl.parentNode.removeChild(surveyEl);
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
setTimeout(() => {
|
|
1282
|
+
if (surveyEl && surveyEl.parentNode) {
|
|
1283
|
+
surveyEl.parentNode.removeChild(surveyEl);
|
|
1284
|
+
}
|
|
1285
|
+
}, 300);
|
|
1286
|
+
}
|
|
1287
|
+
this.surveyElement = null;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
if (backdropEl) {
|
|
1291
|
+
backdropEl.style.opacity = '0';
|
|
1292
|
+
if (immediate) {
|
|
1293
|
+
if (backdropEl.parentNode) {
|
|
1294
|
+
backdropEl.parentNode.removeChild(backdropEl);
|
|
1295
|
+
}
|
|
1296
|
+
} else {
|
|
1297
|
+
setTimeout(() => {
|
|
1298
|
+
if (backdropEl && backdropEl.parentNode) {
|
|
1299
|
+
backdropEl.parentNode.removeChild(backdropEl);
|
|
1300
|
+
}
|
|
1301
|
+
}, 300);
|
|
1302
|
+
}
|
|
1303
|
+
this.backdropElement = null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (resetState) {
|
|
1307
|
+
this.surveyState = {
|
|
1308
|
+
score: null,
|
|
1309
|
+
feedback: '',
|
|
1310
|
+
customAnswers: {},
|
|
1311
|
+
pageAnswers: {},
|
|
1312
|
+
currentPageIndex: 0,
|
|
1313
|
+
isSubmitting: false,
|
|
1314
|
+
isVisible: false,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
this.sdk.eventBus.emit('survey:closed', { widget: this });
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
destroy() {
|
|
1322
|
+
this._closeSurvey(true, true);
|
|
1323
|
+
super.destroy();
|
|
1324
|
+
}
|
|
1325
|
+
}
|