freemium-survey-components 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/.eslintrc +24 -0
- package/.prettierrc +10 -0
- package/.storybook/main.js +59 -0
- package/.storybook/manager.js +9 -0
- package/.storybook/preview.js +1 -0
- package/README.md +3 -0
- package/index.ts +3 -0
- package/lib/index.cjs.js +1 -0
- package/lib/index.esm.js +1 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/src/components/button/index.d.ts +25 -0
- package/lib/types/src/components/checkbox/index.d.ts +14 -0
- package/lib/types/src/components/index.d.ts +6 -0
- package/lib/types/src/components/nps/index.d.ts +23 -0
- package/lib/types/src/components/progressbar/index.d.ts +8 -0
- package/lib/types/src/components/radio-button/index.d.ts +25 -0
- package/lib/types/src/components/text-input/index.d.ts +52 -0
- package/lib/types/src/index.d.ts +1 -0
- package/lib/types/src/mock.d.ts +215 -0
- package/lib/types/src/survey/index.d.ts +4 -0
- package/lib/types/src/survey/question.d.ts +3 -0
- package/lib/types/src/survey/widget.d.ts +4 -0
- package/lib/types/src/utils.d.ts +3 -0
- package/package.json +97 -0
- package/postcss.config.js +4 -0
- package/rollup.config.ts +32 -0
- package/src/components/button/button.stories.tsx +23 -0
- package/src/components/button/index.tsx +67 -0
- package/src/components/button/style.css +41 -0
- package/src/components/checkbox/checkbox.stories.tsx +34 -0
- package/src/components/checkbox/index.tsx +118 -0
- package/src/components/checkbox/style.css +85 -0
- package/src/components/index.tsx +6 -0
- package/src/components/nps/index.tsx +69 -0
- package/src/components/nps/nps.stories.tsx +34 -0
- package/src/components/nps/style.css +154 -0
- package/src/components/progressbar/index.tsx +21 -0
- package/src/components/progressbar/progressbar.stories.tsx +22 -0
- package/src/components/progressbar/style.css +14 -0
- package/src/components/radio-button/index.tsx +66 -0
- package/src/components/radio-button/radio.stories.tsx +26 -0
- package/src/components/radio-button/style.css +60 -0
- package/src/components/text-input/index.tsx +155 -0
- package/src/components/text-input/style.css +102 -0
- package/src/components/text-input/text-input.stories.tsx +84 -0
- package/src/index.tsx +1 -0
- package/src/mock.ts +363 -0
- package/src/survey/index.tsx +269 -0
- package/src/survey/question.tsx +79 -0
- package/src/survey/style.css +58 -0
- package/src/survey/survey.stories.tsx +28 -0
- package/src/survey/widget.css +46 -0
- package/src/survey/widget.tsx +17 -0
- package/src/theme/index.css +72 -0
- package/src/typings/index.d.ts +1 -0
- package/src/utils.tsx +12 -0
- package/tsconfig.json +22 -0
package/src/mock.ts
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: 'new survey',
|
|
3
|
+
desc: 'description here',
|
|
4
|
+
type: 'NPS',
|
|
5
|
+
header_message:
|
|
6
|
+
"Thank you for your time. This shouldn't take more than 2 mins for you to complete the survey!",
|
|
7
|
+
gratitude_message: 'Thank you for the feedback',
|
|
8
|
+
ui_branding: {
|
|
9
|
+
logo_url:
|
|
10
|
+
'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Slack_Technologies_Logo.svg/1280px-Slack_Technologies_Logo.svg.png',
|
|
11
|
+
footer_html: 'We look forward to acting on your feedback!',
|
|
12
|
+
},
|
|
13
|
+
meta: {
|
|
14
|
+
blocks: [
|
|
15
|
+
{
|
|
16
|
+
name: 'NPS',
|
|
17
|
+
title: 'NPS',
|
|
18
|
+
order: 1,
|
|
19
|
+
question_names: ['Q_1'],
|
|
20
|
+
type: 'RANGE',
|
|
21
|
+
required: true,
|
|
22
|
+
is_based_on_score: false,
|
|
23
|
+
is_same_for_all: true,
|
|
24
|
+
jump_logic: null,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'B_1',
|
|
28
|
+
order: 2,
|
|
29
|
+
type: 'CHECKBOX',
|
|
30
|
+
title: 'Follow up 1',
|
|
31
|
+
question_names: ['Q_2', 'Q_21', 'Q_22'],
|
|
32
|
+
required: false,
|
|
33
|
+
is_based_on_score: true,
|
|
34
|
+
is_same_for_all: false,
|
|
35
|
+
jump_logic: null,
|
|
36
|
+
branchOption: 'specific',
|
|
37
|
+
nextBlock: 'B_3',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'B_2',
|
|
41
|
+
order: 3,
|
|
42
|
+
type: 'CHECKBOX',
|
|
43
|
+
title: 'Follow up 2',
|
|
44
|
+
question_names: ['Q_5', 'Q_51', 'Q_52'],
|
|
45
|
+
position: 5,
|
|
46
|
+
required: true,
|
|
47
|
+
is_based_on_score: true,
|
|
48
|
+
is_same_for_all: false,
|
|
49
|
+
jump_logic: null,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'B_3',
|
|
53
|
+
order: 4,
|
|
54
|
+
type: 'input',
|
|
55
|
+
title: 'Follow up 3',
|
|
56
|
+
question_names: ['Q_8'],
|
|
57
|
+
position: 9,
|
|
58
|
+
required: true,
|
|
59
|
+
is_based_on_score: false,
|
|
60
|
+
is_same_for_all: true,
|
|
61
|
+
jump_logic: null,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
question_details: {
|
|
66
|
+
questions: [
|
|
67
|
+
{
|
|
68
|
+
text: 'How likely are you to recommend bookmyshow to your friend or colleague?',
|
|
69
|
+
name: 'Q_1',
|
|
70
|
+
position: 0,
|
|
71
|
+
type_info: {
|
|
72
|
+
question_type: 'RANGE',
|
|
73
|
+
validation: {
|
|
74
|
+
min: 1,
|
|
75
|
+
max: 10,
|
|
76
|
+
},
|
|
77
|
+
choices: [
|
|
78
|
+
{
|
|
79
|
+
value: 0,
|
|
80
|
+
position: 0,
|
|
81
|
+
dependent_question_names: ['Q_2', 'Q_52'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
value: 1,
|
|
85
|
+
position: 1,
|
|
86
|
+
dependent_question_names: ['Q_2', 'Q_52'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
value: 2,
|
|
90
|
+
position: 2,
|
|
91
|
+
dependent_question_names: ['Q_2', 'Q_52'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
value: 3,
|
|
95
|
+
position: 3,
|
|
96
|
+
dependent_question_names: ['Q_21', 'Q_52'],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: 4,
|
|
100
|
+
position: 4,
|
|
101
|
+
dependent_question_names: ['Q_21', 'Q_52'],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: 5,
|
|
105
|
+
position: 5,
|
|
106
|
+
dependent_question_names: ['Q_21', 'Q_52'],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: 6,
|
|
110
|
+
position: 6,
|
|
111
|
+
dependent_question_names: ['Q_21', 'Q_52'],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
value: 7,
|
|
115
|
+
position: 7,
|
|
116
|
+
dependent_question_names: ['Q_22', 'Q_52'],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
value: 8,
|
|
120
|
+
position: 8,
|
|
121
|
+
dependent_question_names: ['Q_22', 'Q_52'],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
value: 9,
|
|
125
|
+
position: 9,
|
|
126
|
+
dependent_question_names: ['Q_22', 'Q_52'],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
linear_scale: {
|
|
130
|
+
button_style: 'standard',
|
|
131
|
+
button_shape: 'curved',
|
|
132
|
+
},
|
|
133
|
+
score_presets: {
|
|
134
|
+
start: 'Not Likely',
|
|
135
|
+
end: 'Very Likely',
|
|
136
|
+
},
|
|
137
|
+
footer_text: 'We look forward to acting on your feedback!',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
text: 'How well do you recomment our product to others ONE',
|
|
142
|
+
name: 'Q_2',
|
|
143
|
+
position: 1,
|
|
144
|
+
type_info: {
|
|
145
|
+
question_type: 'MESSAGE',
|
|
146
|
+
choices: [
|
|
147
|
+
{
|
|
148
|
+
id: 'opt-1',
|
|
149
|
+
label: 'Option 1',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'opt-2',
|
|
153
|
+
label: 'Option 2',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'opt-3',
|
|
157
|
+
label: 'Option 3',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
meta: {
|
|
161
|
+
value: 'Thank you for the feedback',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
question_type: 'CHECKBOX',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
text: 'How well do you recomment our product to others TWO',
|
|
168
|
+
name: 'Q_21',
|
|
169
|
+
position: 3,
|
|
170
|
+
type_info: {
|
|
171
|
+
question_type: 'PARAGRAPH',
|
|
172
|
+
choices: [
|
|
173
|
+
{
|
|
174
|
+
id: 'opt-1',
|
|
175
|
+
label: 'Option 1',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'opt-2',
|
|
179
|
+
label: 'Option 2',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'opt-3',
|
|
183
|
+
label: 'Option 3',
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
meta: {
|
|
187
|
+
value: 'Thank you for the feedback',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
question_type: 'CHECKBOX',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
text: 'How well do you recomment our product to others THREE',
|
|
194
|
+
name: 'Q_22',
|
|
195
|
+
position: 4,
|
|
196
|
+
type_info: {
|
|
197
|
+
question_type: 'CHECKBOX',
|
|
198
|
+
choices: [
|
|
199
|
+
{
|
|
200
|
+
id: 'opt-1',
|
|
201
|
+
label: 'Option 1',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'opt-2',
|
|
205
|
+
label: 'Option 2',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'opt-3',
|
|
209
|
+
label: 'Option 3',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
meta: {
|
|
213
|
+
value: 'Thank you for the feedback',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
question_type: 'CHECKBOX',
|
|
217
|
+
choices: [
|
|
218
|
+
{
|
|
219
|
+
id: 'opt-1',
|
|
220
|
+
label: 'Option 1',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'opt-2',
|
|
224
|
+
label: 'Option 2',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'opt-3',
|
|
228
|
+
label: 'Option 3',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'Q_5',
|
|
234
|
+
position: 5,
|
|
235
|
+
text: 'Which feature you like the most?',
|
|
236
|
+
type_info: {
|
|
237
|
+
question_type: 'CHECKBOX',
|
|
238
|
+
choices: [
|
|
239
|
+
{
|
|
240
|
+
id: 'opt-1',
|
|
241
|
+
label: 'Option 1',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: 'opt-2',
|
|
245
|
+
label: 'Option 2',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'opt-3',
|
|
249
|
+
label: 'Option 3',
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
meta: {},
|
|
253
|
+
},
|
|
254
|
+
question_type: 'CHECKBOX',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'Q_51',
|
|
258
|
+
position: 7,
|
|
259
|
+
text: 'Which feature you like the most Q_51?',
|
|
260
|
+
type_info: {
|
|
261
|
+
question_type: 'CHECKBOX',
|
|
262
|
+
choices: [
|
|
263
|
+
{
|
|
264
|
+
id: 'opt-1',
|
|
265
|
+
label: 'Option 1',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 'opt-2',
|
|
269
|
+
label: 'Option 2',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'opt-3',
|
|
273
|
+
label: 'Option 3',
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
meta: {},
|
|
277
|
+
},
|
|
278
|
+
question_type: 'CHECKBOX',
|
|
279
|
+
choices: [
|
|
280
|
+
{
|
|
281
|
+
id: 'opt-1',
|
|
282
|
+
label: 'Option 1',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'opt-2',
|
|
286
|
+
label: 'Option 2',
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'Q_52',
|
|
292
|
+
position: 8,
|
|
293
|
+
text: 'Which feature you like the most?',
|
|
294
|
+
type_info: {
|
|
295
|
+
question_type: 'CHECKBOX',
|
|
296
|
+
choices: [
|
|
297
|
+
{
|
|
298
|
+
id: 'opt-1',
|
|
299
|
+
label: 'Option 1',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'opt-2',
|
|
303
|
+
label: 'Option 2',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 'opt-3',
|
|
307
|
+
label: 'Option 3',
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
meta: {},
|
|
311
|
+
},
|
|
312
|
+
question_type: 'CHECKBOX',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'Q_8',
|
|
316
|
+
position: 8,
|
|
317
|
+
text: 'Which feature you like the most? Q_8 last block',
|
|
318
|
+
type_info: {
|
|
319
|
+
question_type: 'CHECKBOX',
|
|
320
|
+
choices: [
|
|
321
|
+
{
|
|
322
|
+
id: 'opt-1',
|
|
323
|
+
label: 'Option 1',
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: 'opt-2',
|
|
327
|
+
label: 'Option 2',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: 'opt-3',
|
|
331
|
+
label: 'Option 3',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
meta: {},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
state: 'DRAFT',
|
|
340
|
+
created_on: '2021-11-01T13:27:11.600Z',
|
|
341
|
+
updated_on: '2021-11-01T13:27:11.600Z',
|
|
342
|
+
schedule_info: {
|
|
343
|
+
start_date: '2021-11-31',
|
|
344
|
+
end_date: '2021-12-05',
|
|
345
|
+
preferred_time: {
|
|
346
|
+
hour: 15,
|
|
347
|
+
minute: 15,
|
|
348
|
+
},
|
|
349
|
+
remind_in: [30],
|
|
350
|
+
next_survey_when_responded: 30,
|
|
351
|
+
next_survey_when_not_responded: 30,
|
|
352
|
+
},
|
|
353
|
+
throttle_info: {
|
|
354
|
+
limit_per_day: 100,
|
|
355
|
+
},
|
|
356
|
+
ui_theme: {
|
|
357
|
+
body_background_color: '#000',
|
|
358
|
+
primary_font: '#FFF',
|
|
359
|
+
secondary_font: '#423233',
|
|
360
|
+
primary_button_color: '#123249',
|
|
361
|
+
secondary_button_color: '#981237',
|
|
362
|
+
},
|
|
363
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import React, {useState, useRef, useEffect, useMemo} from 'react'
|
|
2
|
+
import {ProgressBar, Button} from '../components'
|
|
3
|
+
import Question from './question'
|
|
4
|
+
import './style.css'
|
|
5
|
+
|
|
6
|
+
// these fields do not need next button to proceed
|
|
7
|
+
const AUTO_COMMIT_FIELD_TYPES = ['RANGE', 'NPS', 'RADIO']
|
|
8
|
+
const SurveyEndCard = () => {
|
|
9
|
+
return (
|
|
10
|
+
<div className="question-container" style={{gap: '16px'}}>
|
|
11
|
+
<h4>Thank you for taking our survey!.</h4>
|
|
12
|
+
<p> Your response is very important to us.</p>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
const Survey = ({survey, onSubmit}: any) => {
|
|
17
|
+
const {
|
|
18
|
+
meta: {blocks},
|
|
19
|
+
question_details: {questions},
|
|
20
|
+
} = survey
|
|
21
|
+
const [formValues, setFormValues] = useState<any>({})
|
|
22
|
+
const [isSurveyCompleted, setIsSurveyCompleted] = useState<boolean>(false)
|
|
23
|
+
const [blocksWithQns, setBlocksWithQns] = useState([] as any)
|
|
24
|
+
const currentBlock = useRef<any>(null)
|
|
25
|
+
const currentQuestion = useRef<any>(null)
|
|
26
|
+
const blockElementRef = useRef<HTMLDivElement | null>(null) // to focus on the newly added block
|
|
27
|
+
const currentBlockIndex = useRef(0) // to keep track of the index of the dirty block
|
|
28
|
+
const isValuesCommitted = useRef(false) // in case of non-auto commit fields, to check if the user commits the values or just modifying it
|
|
29
|
+
const tempBlocksWithQns = useRef([] as any)
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!blocks) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if (!currentBlock.current && blocks.length > 0) {
|
|
36
|
+
handleBlockProgression(blocks[0])
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
}, [blocks])
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
blockElementRef.current?.scrollIntoView({
|
|
43
|
+
block: 'center',
|
|
44
|
+
behavior: 'smooth',
|
|
45
|
+
})
|
|
46
|
+
tempBlocksWithQns.current = blocksWithQns
|
|
47
|
+
}, [blocksWithQns])
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
//if not the initial render
|
|
51
|
+
if (Object.keys(formValues).length > 0) {
|
|
52
|
+
// move to next block if current qn is a auto commit qn
|
|
53
|
+
// if current qn is committed
|
|
54
|
+
// and if the qn is not the final qn since for the final qn, submit should end the survey
|
|
55
|
+
if (
|
|
56
|
+
currentQuestion.current &&
|
|
57
|
+
(AUTO_COMMIT_FIELD_TYPES.includes(
|
|
58
|
+
currentQuestion.current.type_info.question_type,
|
|
59
|
+
) ||
|
|
60
|
+
isValuesCommitted.current) &&
|
|
61
|
+
!isCurrentBlockFinal()
|
|
62
|
+
) {
|
|
63
|
+
moveToNextBlock()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}, [formValues])
|
|
67
|
+
|
|
68
|
+
const isCurrentBlockFinal = () => {
|
|
69
|
+
return (
|
|
70
|
+
currentBlockIndex.current === blocks.length - 1 ||
|
|
71
|
+
currentBlock.current.branchOption === 'end'
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
//if the already filled block is modified flash the state associated with all the blocks below the block being edited
|
|
75
|
+
const resetFormBelowBlock = (block: any) => {
|
|
76
|
+
const blockIndex = blocksWithQns.findIndex(
|
|
77
|
+
(eachBlock) => eachBlock.name === block.name,
|
|
78
|
+
)
|
|
79
|
+
const modifiedBlocksWithQns = blocksWithQns.slice(0, blockIndex + 1)
|
|
80
|
+
const modifiedFormValues = modifiedBlocksWithQns.reduce(
|
|
81
|
+
(result, current) => {
|
|
82
|
+
result[current.question.name] = formValues[current.question.name]
|
|
83
|
+
return result
|
|
84
|
+
},
|
|
85
|
+
{},
|
|
86
|
+
)
|
|
87
|
+
currentBlockIndex.current = blocks.findIndex(
|
|
88
|
+
(eachBlock) => eachBlock.name === block.name,
|
|
89
|
+
)
|
|
90
|
+
return [modifiedBlocksWithQns, modifiedFormValues]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleBlockProgression = (block) => {
|
|
94
|
+
let question: any = {}
|
|
95
|
+
if (block.is_based_on_score) {
|
|
96
|
+
const possibleNextQuestions =
|
|
97
|
+
questions[0].type_info.choices[formValues?.[questions[0].name]]
|
|
98
|
+
?.dependent_question_names
|
|
99
|
+
const nextQuestionName = block.question_names.find((eachQuestion: any) =>
|
|
100
|
+
possibleNextQuestions?.includes(eachQuestion),
|
|
101
|
+
)
|
|
102
|
+
question = questions.find(
|
|
103
|
+
(eachQuestion: any) => eachQuestion.name === nextQuestionName,
|
|
104
|
+
)
|
|
105
|
+
} else {
|
|
106
|
+
question = questions.find(
|
|
107
|
+
(eachQuestion: any) => eachQuestion.name === block.question_names[0],
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
currentBlock.current = block
|
|
111
|
+
currentQuestion.current = question
|
|
112
|
+
const isPresent = tempBlocksWithQns.current.find(
|
|
113
|
+
(eachBlock) => eachBlock.question.name === question.name,
|
|
114
|
+
)
|
|
115
|
+
if (!isPresent)
|
|
116
|
+
setBlocksWithQns([
|
|
117
|
+
...tempBlocksWithQns.current,
|
|
118
|
+
{...block, question: question},
|
|
119
|
+
])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const handleFormValues = (block, value, isCommitting) => {
|
|
123
|
+
if (isCommitting) {
|
|
124
|
+
if (
|
|
125
|
+
formValues[block.question.name] != null &&
|
|
126
|
+
block.name !== blocksWithQns[blocksWithQns.length - 1].name
|
|
127
|
+
) {
|
|
128
|
+
const [modifiedBlocksWithQns, modifiedFormValues]: any =
|
|
129
|
+
resetFormBelowBlock(block)
|
|
130
|
+
setFormValues({
|
|
131
|
+
...modifiedFormValues,
|
|
132
|
+
...(value != null && {[block.question.name]: value}),
|
|
133
|
+
})
|
|
134
|
+
//setting the state of the blocksWithQns will trigger the useEffect unnecessarily which will scroll the view based on the temp block state
|
|
135
|
+
tempBlocksWithQns.current = modifiedBlocksWithQns
|
|
136
|
+
} else {
|
|
137
|
+
setFormValues({
|
|
138
|
+
...formValues,
|
|
139
|
+
...(value != null && {[block.question.name]: value}),
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
setFormValues({
|
|
144
|
+
...formValues,
|
|
145
|
+
...(value != null && {[block.question.name]: value}),
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const moveToNextBlock = () => {
|
|
151
|
+
if (isCurrentBlockFinal()) {
|
|
152
|
+
setIsSurveyCompleted(true)
|
|
153
|
+
} else {
|
|
154
|
+
if (currentBlock.current.nextBlock) {
|
|
155
|
+
currentBlockIndex.current = blocks.findIndex(
|
|
156
|
+
(block) => block.name === currentBlock.current.nextBlock,
|
|
157
|
+
)
|
|
158
|
+
} else {
|
|
159
|
+
currentBlockIndex.current += 1
|
|
160
|
+
}
|
|
161
|
+
handleBlockProgression(blocks[currentBlockIndex.current])
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const skipBlock = (block) => {
|
|
165
|
+
const blockIndex = blocksWithQns.findIndex(
|
|
166
|
+
(eachBlock) => eachBlock.name === block.name,
|
|
167
|
+
)
|
|
168
|
+
const modifiedBlocksWithQns = [...blocksWithQns]
|
|
169
|
+
modifiedBlocksWithQns.splice(blockIndex, 1)
|
|
170
|
+
//setting the state of the blocksWithQns will trigger the useEffect unnecessarily which will scroll the view based on the temporary block state
|
|
171
|
+
tempBlocksWithQns.current = modifiedBlocksWithQns
|
|
172
|
+
moveToNextBlock()
|
|
173
|
+
}
|
|
174
|
+
// do display next button, if the question is the current question being edited
|
|
175
|
+
// if the question is of type range, nps or radio
|
|
176
|
+
// if the block is not the final block by index and by End survey logic
|
|
177
|
+
const doDisplayNextButton = (block, question) => {
|
|
178
|
+
if (
|
|
179
|
+
question.name === currentQuestion.current.name &&
|
|
180
|
+
!AUTO_COMMIT_FIELD_TYPES.includes(question.type_info.question_type) &&
|
|
181
|
+
!isCurrentBlockFinal()
|
|
182
|
+
) {
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
const doDisplaySkipButton = (block, question) => {
|
|
188
|
+
if (!block.required && question.name === currentQuestion.current.name) {
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!currentBlock.current || !currentQuestion.current) {
|
|
195
|
+
return null
|
|
196
|
+
}
|
|
197
|
+
if (isSurveyCompleted) {
|
|
198
|
+
return <SurveyEndCard />
|
|
199
|
+
}
|
|
200
|
+
return (
|
|
201
|
+
<>
|
|
202
|
+
<ProgressBar
|
|
203
|
+
totalSteps={blocks.length}
|
|
204
|
+
completedSteps={currentBlockIndex.current}
|
|
205
|
+
/>
|
|
206
|
+
{blocksWithQns.map((block: any, index: number) => (
|
|
207
|
+
<div
|
|
208
|
+
className="question-container"
|
|
209
|
+
ref={blockElementRef}
|
|
210
|
+
id={block.title}
|
|
211
|
+
key={block.title}
|
|
212
|
+
>
|
|
213
|
+
<div className="question-text">{block.question.text}</div>
|
|
214
|
+
<Question
|
|
215
|
+
question={block.question}
|
|
216
|
+
formValues={formValues}
|
|
217
|
+
onChangeHandler={(value: any) => {
|
|
218
|
+
isValuesCommitted.current = false
|
|
219
|
+
currentBlockIndex.current = blocks.findIndex(
|
|
220
|
+
(eachBlock) => eachBlock.name === block.name,
|
|
221
|
+
)
|
|
222
|
+
currentBlock.current = block
|
|
223
|
+
currentQuestion.current = block.question
|
|
224
|
+
handleFormValues(
|
|
225
|
+
block,
|
|
226
|
+
value,
|
|
227
|
+
AUTO_COMMIT_FIELD_TYPES.includes(
|
|
228
|
+
block.question.type_info.question_type,
|
|
229
|
+
) && !isCurrentBlockFinal(),
|
|
230
|
+
)
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
<div className="action-buttons">
|
|
234
|
+
{doDisplayNextButton(block, block.question) && (
|
|
235
|
+
<Button
|
|
236
|
+
onClick={() => {
|
|
237
|
+
isValuesCommitted.current = true
|
|
238
|
+
handleFormValues(block, null, true)
|
|
239
|
+
}}
|
|
240
|
+
className="next-button"
|
|
241
|
+
>
|
|
242
|
+
Next
|
|
243
|
+
</Button>
|
|
244
|
+
)}
|
|
245
|
+
{doDisplaySkipButton(block, block.question) && (
|
|
246
|
+
<button className="skip-button" onClick={skipBlock}>
|
|
247
|
+
Skip
|
|
248
|
+
</button>
|
|
249
|
+
)}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
))}
|
|
253
|
+
{isCurrentBlockFinal() && (
|
|
254
|
+
<div className="submit">
|
|
255
|
+
<Button
|
|
256
|
+
onClick={() => {
|
|
257
|
+
setIsSurveyCompleted(true)
|
|
258
|
+
onSubmit(formValues)
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
Submit Survey
|
|
262
|
+
</Button>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
</>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default Survey
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, {useEffect} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
NPS,
|
|
4
|
+
CheckboxGroup,
|
|
5
|
+
Radio,
|
|
6
|
+
RadioGroup,
|
|
7
|
+
Input,
|
|
8
|
+
TextArea,
|
|
9
|
+
} from '../components'
|
|
10
|
+
|
|
11
|
+
const Question = ({question, formValues, onChangeHandler}: any) => {
|
|
12
|
+
switch (question.type_info.question_type) {
|
|
13
|
+
case 'RANGE':
|
|
14
|
+
return (
|
|
15
|
+
<NPS
|
|
16
|
+
type_info={question.type_info}
|
|
17
|
+
onChangeHandler={onChangeHandler}
|
|
18
|
+
npsValue={formValues[question.name]}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
case 'CHECKBOX':
|
|
22
|
+
return (
|
|
23
|
+
<CheckboxGroup
|
|
24
|
+
values={formValues[question.name]}
|
|
25
|
+
options={question.type_info.choices}
|
|
26
|
+
onChangeHandler={onChangeHandler}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
case 'CHECKBOX':
|
|
30
|
+
return (
|
|
31
|
+
<RadioGroup
|
|
32
|
+
name={question.name}
|
|
33
|
+
onChangeHandler={onChangeHandler}
|
|
34
|
+
options={question.type_info.choices}
|
|
35
|
+
value={formValues[question.name]}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
case 'MESSAGE':
|
|
39
|
+
return (
|
|
40
|
+
//in case of mobile device, we would render the textarea instead of input since we cannot line wrap the input field.
|
|
41
|
+
<div className="responsive-text-field">
|
|
42
|
+
<Input
|
|
43
|
+
autoFocus={true}
|
|
44
|
+
label=""
|
|
45
|
+
maxLength={300}
|
|
46
|
+
showCount={true}
|
|
47
|
+
isRequired={question.is_required}
|
|
48
|
+
value={formValues[question.name] || ''}
|
|
49
|
+
onChange={(e: any) => onChangeHandler(e.target.value)}
|
|
50
|
+
/>
|
|
51
|
+
<TextArea
|
|
52
|
+
autoFocus={true}
|
|
53
|
+
label=""
|
|
54
|
+
maxLength={300}
|
|
55
|
+
showCount={true}
|
|
56
|
+
isRequired={question.is_required}
|
|
57
|
+
value={formValues[question.name] || ''}
|
|
58
|
+
onChange={(e: any) => onChangeHandler(e.target.value)}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
case 'PARAGRAPH':
|
|
63
|
+
return (
|
|
64
|
+
<TextArea
|
|
65
|
+
autoFocus={true}
|
|
66
|
+
label=""
|
|
67
|
+
isRequired={question.is_required}
|
|
68
|
+
value={formValues[question.name] || ''}
|
|
69
|
+
onChange={(e: any) => onChangeHandler(e.target.value)}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
default:
|
|
73
|
+
console.error(
|
|
74
|
+
`Question type ${question.type_info.question_type} not found`,
|
|
75
|
+
)
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export default Question
|