소스 검색

Merge branch 'brian_dev_141' of fyindr/siimee into dev

tags/0.0.3^2
maeda 3 년 전
부모
커밋
2664b39d35

+ 21
- 22
backend/db/data-generator/index.js 파일 보기

115
     // Generate responses first, before filling in details
115
     // Generate responses first, before filling in details
116
     let responses = generate(
116
     let responses = generate(
117
         classes.Response,
117
         classes.Response,
118
-        (config.batchSize + extraProfilesToGenerate) * mock.response_keys.length,
118
+        (config.batchSize + extraProfilesToGenerate) *
119
+            mock.response_keys.length,
119
         { starting: generatedResponseCount + config.batchSize },
120
         { starting: generatedResponseCount + config.batchSize },
120
     )
121
     )
121
     profiles.forEach((profile, i) => {
122
     profiles.forEach((profile, i) => {
125
             resToEdit.response_key_id = k + 1
126
             resToEdit.response_key_id = k + 1
126
             resToEdit.profile_id = profile.profile_id
127
             resToEdit.profile_id = profile.profile_id
127
 
128
 
128
-            if(resToEdit.response_key_id < config.zipcodeKey) {
129
+            if (resToEdit.response_key_id < config.zipcodeKey) {
129
                 resToEdit.val = random.valFrom(Object.values(possibleResponses))
130
                 resToEdit.val = random.valFrom(Object.values(possibleResponses))
130
-            }
131
-            else if(resToEdit.response_key_id == config.zipcodeKey) {
131
+            } else if (resToEdit.response_key_id == config.zipcodeKey) {
132
                 resToEdit.val = random.valFrom(config.possibleZipcodes)
132
                 resToEdit.val = random.valFrom(config.possibleZipcodes)
133
-            }
134
-            else {
133
+            } else {
135
                 switch (resToEdit.response_key_id) {
134
                 switch (resToEdit.response_key_id) {
136
-                case config.mediaKey:
137
-                    resToEdit.val = random.media()
138
-                    break
139
-                case config.langKey:
140
-                    resToEdit.val = random.language()
141
-                    break
142
-                case 10:
143
-                    resToEdit.val = random.duration()
144
-                    break
145
-                case 11:
146
-                    resToEdit.val = random.location()
147
-                    break
148
-                case config.blurbKey:
149
-                    resToEdit.val = random.blurb()
150
-                    break
135
+                    case config.mediaKey:
136
+                        resToEdit.val = random.media()
137
+                        break
138
+                    case config.langKey:
139
+                        resToEdit.val = random.language()
140
+                        break
141
+                    case 10:
142
+                        resToEdit.val = random.duration()
143
+                        break
144
+                    case 11:
145
+                        resToEdit.val = random.location()
146
+                        break
147
+                    case config.blurbKey:
148
+                        resToEdit.val = random.blurb()
149
+                        break
151
                 }
150
                 }
152
             }
151
             }
153
         }
152
         }
170
     let jobPosterIds = users
169
     let jobPosterIds = users
171
         .filter(user => user.is_poster > 0)
170
         .filter(user => user.is_poster > 0)
172
         .map(user => user.user_id)
171
         .map(user => user.user_id)
173
-    // Guarentee ONE job poster
172
+    // Guarantee ONE job poster
174
     if (!jobPosterIds.length) {
173
     if (!jobPosterIds.length) {
175
         random.valFrom(users).is_poster = 1
174
         random.valFrom(users).is_poster = 1
176
         jobPosterIds = users
175
         jobPosterIds = users

+ 120
- 36
backend/db/data-generator/mock.js 파일 보기

430
         {
430
         {
431
             response_key_id: 1,
431
             response_key_id: 1,
432
             response_key_category: 'visionary_vs_implementer',
432
             response_key_category: 'visionary_vs_implementer',
433
-            response_key_prompt:
434
-                'While managing your team, do you find success in your employees being more Visionary or Implementer?',
433
+            response_key_prompt: 'Do you prefer to work with those who are driven by their Visionary insights, or those who are driven more by their Implementation?',
435
             response_key_description: 'first round draft scoring question',
434
             response_key_description: 'first round draft scoring question',
435
+            aspect: 'visionary_vs_implementer',
436
+            category: 'aspect',
437
+            placeholder: null,
438
+            invalidInputPrompt: null,
436
         },
439
         },
437
         {
440
         {
438
             response_key_id: 2,
441
             response_key_id: 2,
439
             response_key_category: 'creative_vs_methodical',
442
             response_key_category: 'creative_vs_methodical',
440
-            response_key_prompt:
441
-                'In your department, do you find more success in your employees being Creative or Methodical in their job duties?',
443
+            response_key_prompt: 'Have you found more success working with employees that are more Creative or those that are more Methodical?',
442
             response_key_description: 'first round draft scoring question',
444
             response_key_description: 'first round draft scoring question',
445
+            aspect: 'creative_vs_methodical',
446
+            category: 'aspect',
447
+            placeholder: null,
448
+            invalidInputPrompt: null,
443
         },
449
         },
444
         {
450
         {
445
             response_key_id: 3,
451
             response_key_id: 3,
446
             response_key_category: 'dynamic_vs_ordered',
452
             response_key_category: 'dynamic_vs_ordered',
447
-            response_key_prompt:
448
-                'Do you structure and encourage your team to be a Collaborative or Independent environment? (NEEDS UPDATED COPY)',
453
+            response_key_prompt: 'Which do you find to be the ideal working environment, one that is more Collaborative or one that is more Independent?',
449
             response_key_description: 'first round draft scoring question',
454
             response_key_description: 'first round draft scoring question',
455
+            aspect: 'dynamic_vs_ordered',
456
+            category: 'aspect',
457
+            placeholder: null,
458
+            invalidInputPrompt: null,
450
         },
459
         },
451
         {
460
         {
452
             response_key_id: 4,
461
             response_key_id: 4,
453
             response_key_category: 'precise_vs_resourceful',
462
             response_key_category: 'precise_vs_resourceful',
454
-            response_key_prompt:
455
-                'Do you find higher success in employees on your team that are Innovative or Conventional? (NEEDS UPDATED COPY)',
463
+            response_key_prompt: 'Is the success of your team more likely if it includes individuals who are more Innovative, or those that are more Conventional when fulfilling their job duties?',
456
             response_key_description: 'first round draft scoring question',
464
             response_key_description: 'first round draft scoring question',
465
+            aspect: 'precise_vs_resourceful',
466
+            category: 'aspect',
467
+            placeholder: null,
468
+            invalidInputPrompt: null,
457
         },
469
         },
458
         {
470
         {
459
             response_key_id: 5,
471
             response_key_id: 5,
460
             response_key_category: 'big_Picture_vs_focused',
472
             response_key_category: 'big_Picture_vs_focused',
461
-            response_key_prompt:
462
-                'As a hiring leader, are you a Big Picture or Focused thinker when it comes to how you operate in your job duties?',
473
+            response_key_prompt: 'When fulfilling the role of the hiring leader, do you find yourself focusing more on the Big Picture or The Task At Hand?',
463
             response_key_description: 'first round draft scoring question',
474
             response_key_description: 'first round draft scoring question',
475
+            aspect: 'big_Picture_vs_focused',
476
+            category: 'aspect',
477
+            placeholder: null,
478
+            invalidInputPrompt: null,
464
         },
479
         },
465
         {
480
         {
466
             response_key_id: 6,
481
             response_key_id: 6,
467
             response_key_category: 'guided_vs_self-managed',
482
             response_key_category: 'guided_vs_self-managed',
468
-            response_key_prompt:
469
-                'Do you prefer your employees to be Guided or Self-managed in achieving completion of their responsibilities?',
483
+            response_key_prompt: 'Do you prefer to Guide your employees towards achieving the team goals, or do you prefer your employees to be Self-Managed?',
470
             response_key_description: 'first round draft scoring question',
484
             response_key_description: 'first round draft scoring question',
485
+            aspect: 'guided_vs_self-managed',
486
+            category: 'aspect',
487
+            placeholder: null,
488
+            invalidInputPrompt: null,
471
         },
489
         },
472
         {
490
         {
473
             response_key_id: 7,
491
             response_key_id: 7,
474
             response_key_category: 'profile',
492
             response_key_category: 'profile',
475
-            response_key_prompt: 'zipcode',
476
-            response_key_description: 'required for distance calculations',
493
+            response_key_prompt: 'First things first, could you provide us with your name? [break] I am called [break] when others address me.',
494
+            response_key_description: 'required for profile creation',
495
+            aspect: null,
496
+            category: 'input',
497
+            placeholder: 'Joe Doe',
498
+            invalidInputPrompt: 'So sorry, but what is your name?',
477
         },
499
         },
478
         {
500
         {
479
             response_key_id: 8,
501
             response_key_id: 8,
480
             response_key_category: 'profile',
502
             response_key_category: 'profile',
481
-            response_key_prompt: 'image',
482
-            response_key_description: 'required for profile pictures',
503
+            response_key_prompt: 'In order for others to reach out to you on Siimee, we will need you to provide your email address.[break]When reaching out to me, [break] is my preferred email.',
504
+            response_key_description: 'required for profile creation',
505
+            aspect: null,
506
+            category: 'input',
507
+            placeholder: 'joe@mailme.com',
508
+            invalidInputPrompt: 'It looks like that email is not valid, try en email that is formatted like so: joe@joe.com',
483
         },
509
         },
484
         {
510
         {
485
             response_key_id: 9,
511
             response_key_id: 9,
486
             response_key_category: 'profile',
512
             response_key_category: 'profile',
487
-            response_key_prompt: 'language',
488
-            response_key_description:
489
-                'programming and spoken language preference',
513
+            response_key_prompt: 'So far so good! Next we will need you to establish a super secret password. Your password should be at least 14 characters long and have at least 2 special characters.[break]My [break] is a very secure passcode that only I will have access to!',
514
+            response_key_description: 'required for profile creation',
515
+            aspect: null,
516
+            category: 'input',
517
+            placeholder: 'supersecr3tp@ssword',
518
+            invalidInputPrompt: 'That password does not fit our requirements, please follow the above instructions to generate a secure password.',
490
         },
519
         },
491
         {
520
         {
492
             response_key_id: 10,
521
             response_key_id: 10,
493
             response_key_category: 'profile',
522
             response_key_category: 'profile',
494
-            response_key_prompt: 'duration',
495
-            response_key_description:
496
-                'duration preference for hours able to dedicate to work',
523
+            response_key_prompt: 'Looking good! Doing great. The next piece of info needed is your zip code. That way we can be sure to only show you other people in your area.[break]My zip code, [break] is the general area where I wish to see results in.',
524
+            response_key_description: 'required for distance calculations',
525
+            aspect: null,
526
+            category: 'input',
527
+            placeholder: '90012',
528
+            invalidInputPrompt: 'Oops! That is not a recognized zipcode, please enter a 5 digit zipcode like: 97869',
497
         },
529
         },
498
         {
530
         {
499
             response_key_id: 11,
531
             response_key_id: 11,
500
             response_key_category: 'profile',
532
             response_key_category: 'profile',
501
-            response_key_prompt: 'presence',
502
-            response_key_description:
503
-                'location preference for where work happens',
533
+            response_key_prompt: 'What are you seeking? Are you looking to find a position to be employed in, or are you looking to employ a candidate?[break] I am a [break] seeking an employer/employee.',
534
+            response_key_description: 'required for profile generation',
535
+            aspect: null,
536
+            category: 'choice',
537
+            placeholder: null,
538
+            invalidInputPrompt: 'In order to provide you with the best results, Siimee will need to know whether you are an employer looking to fill a position, or a candidate looking for an employment. Please take a look at our above options and choose one.',
504
         },
539
         },
505
         {
540
         {
506
             response_key_id: 12,
541
             response_key_id: 12,
507
             response_key_category: 'profile',
542
             response_key_category: 'profile',
508
-            response_key_prompt: 'blurb',
509
-            response_key_description: 'required for profile description',
543
+            response_key_prompt: 'Hey, you are almost done! Please provide an image of yourself so others can recognize you if you ever meet up IRL:',
544
+            response_key_description: 'required for profile pictures',
545
+            aspect: null,
546
+            category: 'input',
547
+            placeholder: null,
548
+            invalidInputPrompt: 'It appears you have yet to upload an image. Please provide Siimee with an image in case you want to show others what you look like.',
510
         },
549
         },
511
         {
550
         {
512
             response_key_id: 13,
551
             response_key_id: 13,
513
             response_key_category: 'profile',
552
             response_key_category: 'profile',
514
-            response_key_prompt: 'urgency',
515
-            response_key_description: 'urgency for when work is required',
553
+            response_key_prompt: 'What language is your native language?[break] I consider [break] language as my native language.',
554
+            response_key_description: 'programming and spoken language preference',
555
+            aspect: null,
556
+            category: 'choice',
557
+            placeholder: null,
558
+            invalidInputPrompt: 'We try our best to provide results in the language of your choosing. ¿Prefieres ver resultados en español? Ou peut-être parlez-vous français? Or would you prefer to see results in english?',
516
         },
559
         },
517
         {
560
         {
518
             response_key_id: 14,
561
             response_key_id: 14,
519
             response_key_category: 'profile',
562
             response_key_category: 'profile',
520
-            response_key_prompt: 'role',
521
-            response_key_description: 'current and desired role',
563
+            response_key_prompt: 'What kind of duration would you prefer? Are you looking for part-time, full-time, other?[break] Currently, I am looking for a [break] job at this time.',
564
+            response_key_description: 'duration preference for hours able to dedicate to work',
565
+            aspect: null,
566
+            category: 'choice',
567
+            placeholder: null,
568
+            invalidInputPrompt: 'Looks like you have yet to  fill out what kind of work you are most interested in. As in, part-time, full-time. Take a look at our above options and choose whatever feels right for you right now. You can always edit them later!',
522
         },
569
         },
523
         {
570
         {
524
             response_key_id: 15,
571
             response_key_id: 15,
525
             response_key_category: 'profile',
572
             response_key_category: 'profile',
526
-            response_key_prompt: 'pronouns',
527
-            response_key_description: 'required for profile pronouns',
573
+            response_key_prompt: 'Would you prefer remote, hybrid, in-person work?[break] Personally I would prefer a [break] job right now. It is just what works best for me.',
574
+            response_key_description: 'location preference for where work happens',
575
+            aspect: null,
576
+            category: 'choice',
577
+            placeholder: null,
578
+            invalidInputPrompt: 'Hold up! So sorry to put a pause here, but it looks like you have not chosen whether to work remotely or in person. No worries, if you are unsure, just choose the flexible option.',
528
         },
579
         },
529
         {
580
         {
530
             response_key_id: 16,
581
             response_key_id: 16,
531
             response_key_category: 'profile',
582
             response_key_category: 'profile',
532
-            response_key_prompt: 'distance',
533
-            response_key_description:
534
-                'preference for commuting distance cutoff',
583
+            response_key_prompt: 'Please provide us with a short blurb about yourself. What is your backstory?[break] My origin story starts like this:[break]',
584
+            response_key_description: 'required for profile description',
585
+            aspect: null,
586
+            category: 'input',
587
+            placeholder: 'my backstory starts long long ago...',
588
+            invalidInputPrompt: 'Whoa! Cool story. Unfortunately your backstory is either too long or too short. Please tell us a bit about yourself between 1 and 100 characters.',
589
+        },
590
+        {
591
+            response_key_id: 17,
592
+            response_key_category: 'profile',
593
+            response_key_prompt: 'How soon do you need the position filled or you need to be employed? [break]I am currently [break] when it comes to employment opportunities right now.',
594
+            response_key_description: 'urgency for when work is required',
595
+            aspect: null,
596
+            category: 'choice',
597
+            placeholder: null,
598
+            invalidInputPrompt: 'Looks like you left this field blank. Take a look at our provided options and tell us when you would like be employed.',
599
+        },
600
+        {
601
+            response_key_id: 18,
602
+            response_key_category: 'profile',
603
+            response_key_prompt: 'When others refer to you, what pronouns do you prefer they use?[break]I prefer to be called [break] when others refer to me.',
604
+            response_key_description: 'required for profile pronouns',
605
+            aspect: null,
606
+            category: 'choice',
607
+            placeholder: null,
608
+            invalidInputPrompt: 'Ensuring that others on our platform are aware of what your preferred pronouns are is important to us. Please choose from one of the above options.',
609
+        },
610
+        {
611
+            response_key_id: 19,
612
+            response_key_category: 'profile',
613
+            response_key_prompt: 'What distance from your home are you looking to work in?[break] Preferably, I would like to work [break] from my place of residence.',
614
+            response_key_description: 'preference for commuting distance cutoff',
615
+            aspect: null,
616
+            category: 'input',
617
+            placeholder: '5 mi',
618
+            invalidInputPrompt: 'Whoa! You either left this field blank or tried to input an astronomically large distance you would like to see results from. Please input a distance you would like to see results in.',
535
         },
619
         },
536
     ],
620
     ],
537
     responses: [],
621
     responses: [],

+ 4
- 0
backend/db/migrations/20210527174416_create_response_keys_table.js 파일 보기

4
         table.string('response_key_category').notNullable()
4
         table.string('response_key_category').notNullable()
5
         table.string('response_key_prompt')
5
         table.string('response_key_prompt')
6
         table.string('response_key_description')
6
         table.string('response_key_description')
7
+        table.string('aspect')
8
+        table.string('category')
9
+        table.string('placeholder')
10
+        table.string('invalidInputPrompt')
7
     })
11
     })
8
 }
12
 }
9
 
13
 

+ 5
- 0
backend/lib/schemas/responses.js 파일 보기

14
     response_key_category: Joi.string().required(),
14
     response_key_category: Joi.string().required(),
15
     response_key_prompt: Joi.string().required(),
15
     response_key_prompt: Joi.string().required(),
16
     response_key_description: Joi.any(),
16
     response_key_description: Joi.any(),
17
+    aspect: Joi.string().allow(null, ''),
18
+    category: Joi.string().allow(null, ''),
19
+    placeholder: Joi.string().allow(null, ''),
20
+    invalidInputPrompt: Joi.string().allow(null, ''),
21
+
17
 }).label('question_single')
22
 }).label('question_single')
18
 
23
 
19
 module.exports = {
24
 module.exports = {

+ 8346
- 11
backend/package-lock.json
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 0
- 48
frontend/src/components/onboarding/Aspects.vue 파일 보기

1
-<template lang="pug">
2
-w-card.aspects.w-flex.column
3
-    form.questionnaire(@submit.prevent='this.$emit("handle-submit")')
4
-        QuestionResponse(
5
-            :question='question'
6
-            @updated='updateRadio'
7
-            v-for='question in aspectQuestions'
8
-        )
9
-        w-button.ma1.grow(bg-color='success' type='submit')
10
-            w-icon.mr1 wi-check
11
-            | SUBMIT ANSWERS
12
-</template>
13
-
14
-<script>
15
-import QuestionResponse from './QuestionResponse.vue'
16
-const answered = [null, null, null, null, null, null]
17
-
18
-export default {
19
-    name: 'Aspects',
20
-    components: {
21
-        QuestionResponse,
22
-    },
23
-    props: {
24
-        aspectQuestions: {
25
-            required: true,
26
-            type: Array,
27
-        },
28
-    },
29
-    emits: ['handle-submit', 'update-answers'],
30
-    async created() {
31
-        this.aspectQuestions.forEach((q, i) => {
32
-            console.log(`Aspect #${i}: ${JSON.stringify(q)}`)
33
-        })
34
-    },
35
-    methods: {
36
-        updateRadio(onRadioSelect) {
37
-            answered[onRadioSelect.id - 1] = onRadioSelect.answer
38
-            this.$emit('update-answers', {
39
-                key: 'Aspects',
40
-                question: {
41
-                    response_key_prompt: 'aspects',
42
-                },
43
-                answer: answered,
44
-            })
45
-        },
46
-    },
47
-}
48
-</script>

+ 12
- 7
frontend/src/components/onboarding/FormDropdown.vue 파일 보기

1
 <template lang="pug">
1
 <template lang="pug">
2
 .role
2
 .role
3
-    h3 {{ question.response_key_category }}
4
-    p {{ question.response_key_prompt }}
3
+    span(style='text-align:center;') {{ parsedPrompt.start }}
4
+    span(style='text-align:center;') {{ parsedPrompt.mid }}
5
     w-select.mt4(:items='items' placeholder='i am' v-model='selection')
5
     w-select.mt4(:items='items' placeholder='i am' v-model='selection')
6
+    br
7
+    p(style='text-align:center;') {{ parsedPrompt.end }}
6
     w-button.ma1.grow(@click='handleSubmit') NEXT
8
     w-button.ma1.grow(@click='handleSubmit') NEXT
7
 </template>
9
 </template>
8
 
10
 
18
     emits: ['update-answers'],
20
     emits: ['update-answers'],
19
     data: () => ({
21
     data: () => ({
20
         selection: null,
22
         selection: null,
23
+        parsedPrompt: {},
21
     }),
24
     }),
22
     computed: {
25
     computed: {
23
         items() {
26
         items() {
24
             return this.question.responses.map(res => ({ label: res }))
27
             return this.question.responses.map(res => ({ label: res }))
25
         },
28
         },
26
     },
29
     },
30
+    created() {
31
+        const parsedPromptArr = this.question.response_key_prompt.split('[break]')
32
+        this.parsedPrompt.start = parsedPromptArr[0]
33
+        this.parsedPrompt.mid = parsedPromptArr[1]
34
+        this.parsedPrompt.end = parsedPromptArr[2]
35
+    },
27
     methods: {
36
     methods: {
28
         handleSubmit() {
37
         handleSubmit() {
29
-            if (!this.selection) {
30
-                console.warn('Please select a role.')
31
-                return
32
-            }
33
             let payload = {
38
             let payload = {
34
                 question: this.question,
39
                 question: this.question,
35
-                answer: this.selection,
40
+                input: this.selection,
36
             }
41
             }
37
             this.$emit('update-answers', payload)
42
             this.$emit('update-answers', payload)
38
         },
43
         },

+ 62
- 11
frontend/src/components/onboarding/FormInput.vue 파일 보기

1
 <template lang="pug">
1
 <template lang="pug">
2
 .form-input
2
 .form-input
3
-    h3 {{ question.response_key_category }}
4
-    p {{ question.response_key_prompt }}
5
-    input(placeholder='i am a little teapot' type='text' v-model='input')
6
-    w-button.ma1.grow(@click='handleSubmit') NEXT
3
+    span(style='text-align: center') {{ parsedPrompt.start }}
4
+    br
5
+    br
6
+    span(style='text-align: center') {{ parsedPrompt.mid }}
7
+    input(
8
+        :placeholder='question.placeholder'
9
+        @keyup.enter='handleSubmit({ question, input })'
10
+        type='text'
11
+        v-if='question.survey_stage !== "image" && question.survey_stage !== "blurb" && question.survey_stage !== "password"'
12
+        v-model='input'
13
+        v-focus
14
+    )
15
+    input.pass(
16
+        :placeholder='question.placeholder'
17
+        @keyup.enter='handleSubmit({ question, input })'
18
+        style='-webkit-text-security: circle'
19
+        type='password'
20
+        v-else-if='question.survey_stage === "password"'
21
+        v-model='input'
22
+        v-focus
23
+    )
24
+    w-button.ma1.grow(
25
+        @click='submitImage'
26
+        v-else-if='question.survey_stage === "image"'
27
+    ) UPLOAD IMAGE
28
+    textarea(
29
+        :placeholder='`${question.placeholder}`'
30
+        @keyup.enter='handleSubmit({ question, input })'
31
+        cols='50'
32
+        rows='4'
33
+        v-else-if='question.survey_stage === "blurb"'
34
+        v-model='input'
35
+    )
36
+    span(style='text-align: center') {{ parsedPrompt.end }}
37
+    w-button.ma1.grow(@click='handleSubmit({ question, input })') NEXT
7
 </template>
38
 </template>
8
 <script>
39
 <script>
9
 export default {
40
 export default {
17
     emits: ['update-answers'],
48
     emits: ['update-answers'],
18
     data: () => ({
49
     data: () => ({
19
         input: null,
50
         input: null,
51
+        parsedPrompt: {},
20
     }),
52
     }),
53
+    created() {
54
+        const parsedPromptArr =
55
+            this.question.response_key_prompt.split('[break]')
56
+        this.parsedPrompt.start = parsedPromptArr[0]
57
+        this.parsedPrompt.mid = parsedPromptArr[1]
58
+        this.parsedPrompt.end = parsedPromptArr[2]
59
+    },
21
     methods: {
60
     methods: {
22
-        handleSubmit() {
23
-            if(this.question.response_key_prompt === 'password') {
24
-                this.$emit('update-answers') // no password collection
25
-                return
26
-            }
27
-
61
+        handleSubmit(answerInfo) {
62
+            this.$emit('update-answers', answerInfo)
63
+        },
64
+        submitImage() {
28
             let payload = {
65
             let payload = {
29
                 question: this.question,
66
                 question: this.question,
30
-                answer: this.input,
67
+                input: 'placeholder for image',
31
             }
68
             }
32
             this.$emit('update-answers', payload)
69
             this.$emit('update-answers', payload)
33
         },
70
         },
34
     },
71
     },
35
 }
72
 }
36
 </script>
73
 </script>
74
+
75
+<style>
76
+.form-input,
77
+input[placeholder],
78
+textarea[placeholder] {
79
+    text-align: center;
80
+}
81
+input {
82
+    border: 0;
83
+    outline: 0;
84
+    background: transparent;
85
+    border-bottom: 1px solid black;
86
+}
87
+</style>

+ 37
- 13
frontend/src/components/onboarding/QuestionResponse.vue 파일 보기

1
 <template lang="pug">
1
 <template lang="pug">
2
 w-card.question
2
 w-card.question
3
-    p {{question.question}} 
3
+    p {{ question.response_key_prompt }}
4
     section.radio-buttons.w-flex.row.justify-space-between
4
     section.radio-buttons.w-flex.row.justify-space-between
5
-        p(v-for="label in question.labels") {{label}}
6
-    w-radios.w-flex.row.justify-space-between(@update:model-value="onUpdate" :items="radioItems" color="red")
5
+        p(v-for='label in question.labels') {{ label }}
6
+    w-radios.w-flex.row.justify-space-between(
7
+        :items='radioItems'
8
+        @update:model-value='onUpdate'
9
+        color='red'
10
+    )
11
+    w-button.ma1.grow(
12
+        @click='handleSubmit'
13
+        @keyup.enter='handlSubmit'
14
+        v-if='currentStep !== surveyStepsCount'
15
+    ) NEXT
16
+    w-button.ma1.grow(@click='handleSubmit' v-else) SUBMIT ANSWERS
17
+    p(v-if='noChoiceMade') Tough choices, we know! Just answer to the best of your ability. The team over at Siimee values the answers you provide us so that we can show you results catered to your specific needs.
7
 </template>
18
 </template>
8
 
19
 
9
 <script>
20
 <script>
13
             type: Object,
24
             type: Object,
14
             required: true,
25
             required: true,
15
         },
26
         },
27
+        currentSteps: {
28
+            type: Number,
29
+            required: true,
30
+        },
31
+        surveyStepsCount: {
32
+            type: Number,
33
+            required: true,
34
+        },
16
     },
35
     },
17
-    emits: ['updated'],
36
+    emits: ['update-answers'],
18
     data: () => ({
37
     data: () => ({
19
-        radioItems: [
20
-            { answer: 1 },
21
-            { answer: 2 },
22
-            { answer: 3 },
23
-            { answer: 4 },
24
-            { answer: 5 },
25
-        ],
38
+        radioItems: [1, 2, 3, 4, 5],
39
+        answer: null,
40
+        noChoiceMade: null,
26
     }),
41
     }),
27
     methods: {
42
     methods: {
28
-        onUpdate(e) {
29
-            this.$emit('updated', { ...this.question, answer: e + 1 })
43
+        onUpdate(index) {
44
+            this.noChoiceMade = false
45
+            this.answer = this.radioItems[index]
46
+        },
47
+        handleSubmit() {
48
+            const payload = {
49
+                question: this.question,
50
+                input: this.answer,
51
+            }
52
+            this.noChoiceMade = payload.input === null ? true : false
53
+            this.$emit('update-answers', payload)
30
         },
54
         },
31
     },
55
     },
32
 }
56
 }

+ 2
- 2
frontend/src/components/onboarding/index.js 파일 보기

6
 import Location from './Location.vue'
6
 import Location from './Location.vue'
7
 import Interests from './Interests.vue'
7
 import Interests from './Interests.vue'
8
 import LicensesAndCertifications from './LicensesAndCertifications.vue'
8
 import LicensesAndCertifications from './LicensesAndCertifications.vue'
9
-import Aspects from './Aspects.vue'
10
 import FormInput from './FormInput.vue'
9
 import FormInput from './FormInput.vue'
11
 import FormTags from './FormTags.vue'
10
 import FormTags from './FormTags.vue'
12
 import FormDropdown from './FormDropdown.vue'
11
 import FormDropdown from './FormDropdown.vue'
12
+import QuestionResponse from './QuestionResponse.vue'
13
 
13
 
14
 export default {
14
 export default {
15
     Splash,
15
     Splash,
20
     Location,
20
     Location,
21
     Interests,
21
     Interests,
22
     LicensesAndCertifications,
22
     LicensesAndCertifications,
23
-    Aspects,
24
     FormDropdown,
23
     FormDropdown,
25
     FormTags,
24
     FormTags,
26
     FormInput,
25
     FormInput,
26
+    QuestionResponse,
27
 }
27
 }

+ 2
- 2
frontend/src/entities/card/card.js 파일 보기

1
 /** @module card/card */
1
 /** @module card/card */
2
+import { aspectsArr } from "../../utils/lang.js"
2
 
3
 
3
 const DEFAULT_ABOUT =
4
 const DEFAULT_ABOUT =
4
     'Hello! My name is L.L. and I am a nurse from New York. I have been in the healthcare industry for over 6 years.'
5
     'Hello! My name is L.L. and I am a nurse from New York. I have been in the healthcare industry for over 6 years.'
133
     c.locale = `${profile.city}, ${profile.state}`
134
     c.locale = `${profile.city}, ${profile.state}`
134
     c.email = profile.user_email
135
     c.email = profile.user_email
135
 
136
 
136
-
137
-    let aspectResponses = profile?.responses.filter(r => [1,2,3,4,5,6].indexOf(r.response_key_id) !== -1)
137
+    let aspectResponses = profile?.responses.filter(r => aspectsArr.indexOf(r.response_key_id) !== -1)
138
     if(aspectResponses.length){ // if user has responses for aspects we overwrite default percentages
138
     if(aspectResponses.length){ // if user has responses for aspects we overwrite default percentages
139
         c.aspects.map(a => {
139
         c.aspects.map(a => {
140
             a.percentage = Number(aspectResponses.find(r => responseKeyIdToAspectName[r.response_key_id] == a.name).val )
140
             a.percentage = Number(aspectResponses.find(r => responseKeyIdToAspectName[r.response_key_id] == a.name).val )

+ 40
- 0
frontend/src/entities/survey/survey.answer.validator.js 파일 보기

1
+import Joi from 'joi'
2
+import domains from './tlds-alpha-by-domain.js'
3
+
4
+const answerValidator = {
5
+    name: Joi.string().min(2).max(50).required(),
6
+    email: Joi.string().email({
7
+        minDomainSegments: 2,
8
+        tlds: { allow: domains },
9
+    }),
10
+    // Comment out and uncomment below for more robust password testing
11
+    password: Joi.string().min(14).max(30),
12
+    // TODO: consider using a more robust library for password validation
13
+    // password: Joi.string()
14
+        // .min(14)
15
+        // .max(30)
16
+        // .pattern(
17
+            // new RegExp(
18
+                // '^(?=.*[!@#$%^&*()_+\\-=[\\]{};\':"\\\\|,.<>\\/?])(?=.*[!@#$%^&*()_+\\-=[\\]{};\':"\\\\|,.<>\\/?])[a-zA-Z0-9!@#$%^&*()_+\\-=[\\]{};\':"\\\\|,.<>\\/?]{14,}$',
19
+            // ),
20
+        // ),
21
+    // TODO: Change if going international (only works in usa)
22
+    zipcode: Joi.string().min(5).max(5).pattern(new RegExp('^[0-9]{5}$')),
23
+    seeking: Joi.string(),
24
+    urgency: Joi.string(),
25
+    presence: Joi.string(),
26
+    duration: Joi.string(),
27
+    pronouns: Joi.string(),
28
+    language: Joi.string(),
29
+    image: Joi.any(),
30
+    // NOTE: Allows 1 to 3 digits and then distance metric
31
+    distance: Joi.string()
32
+        .min(4)
33
+        .max(15)
34
+        .pattern(new RegExp('^\\d{1,3}(\\.\\d{1,2})?\\s?(mi|km|mile|miles|kilometer|kilometers)$')),
35
+    blurb: Joi.string().max(200),
36
+    value: Joi.string(),
37
+    aspect: Joi.number(),
38
+}
39
+
40
+export { answerValidator }

+ 22
- 2
frontend/src/entities/survey/survey.js 파일 보기

1
 /** @module survey/survey */
1
 /** @module survey/survey */
2
 import { _baseRecord } from '../index.js'
2
 import { _baseRecord } from '../index.js'
3
 import { surveySchema } from './survey.schema.js'
3
 import { surveySchema } from './survey.schema.js'
4
+import { answerValidator } from './survey.answer.validator.js'
5
+import { aspectsArr } from '../../utils/lang.js'
4
 
6
 
5
-const SCORED = [1, 2, 3, 4, 5, 6]
7
+const SCORED = aspectsArr
6
 const _isScored = id => SCORED.includes(id)
8
 const _isScored = id => SCORED.includes(id)
7
 const _makeCategoryFriendly = responseCategory => {
9
 const _makeCategoryFriendly = responseCategory => {
8
     const labels = responseCategory.split('_vs_')
10
     const labels = responseCategory.split('_vs_')
35
         /**  Fields */
37
         /**  Fields */
36
         this.steps = [...questionSteps] // ! required
38
         this.steps = [...questionSteps] // ! required
37
         this.aspectQuestions = _formatAspectQuestions(this.steps)
39
         this.aspectQuestions = _formatAspectQuestions(this.steps)
38
-        console.log('this.aspectQuestions: ', JSON.stringify(this.aspectQuestions))
40
+        console.log(
41
+            'this.aspectQuestions: ',
42
+            JSON.stringify(this.aspectQuestions),
43
+        )
44
+    }
45
+
46
+    validateAnswer(payload) {
47
+        const { question, input } = payload
48
+
49
+        // Continue our ugly hacks
50
+        const validationType =
51
+            question.category == 'aspect'
52
+                ? question.category
53
+                : question.survey_stage
54
+        const validate = answerValidator[validationType].validate(input)
55
+        if (validate.error) {
56
+            console.error(`error: ${validate.error}`)
57
+        }
58
+        return !validate.error ? true : false
39
     }
59
     }
40
 
60
 
41
     isValid() {
61
     isValid() {

+ 1486
- 0
frontend/src/entities/survey/tlds-alpha-by-domain.js
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 1
- 1
frontend/src/utils/aspects.js 파일 보기

32
     },
32
     },
33
 ]
33
 ]
34
 
34
 
35
-export default Aspects
35
+export default Aspects

+ 80
- 39
frontend/src/utils/lang.js 파일 보기

1
-const DELIMITER = '_'
1
+const aspectsArr = [1, 2, 3, 4, 5, 6]
2
 
2
 
3
-// TODO: Combine these two
3
+// Splash page is unique in survey steps and therefore is simply spliced in
4
+// during survey generation
5
+const splash = {
6
+    response_key_id: 20,
7
+    response_key_category: 'splash',
8
+    response_key_prompt: 'splash page',
9
+    response_key_description: 'required for splash page rendering',
10
+    aspect: null,
11
+    category: 'splash',
12
+    component: 'Splash',
13
+    survey_stage: 'splash',
14
+    placeholder: null,
15
+    invalidInputPrompt: null,
16
+}
17
+
18
+// Easily reorder steps of survey here:
4
 const allSteps = {
19
 const allSteps = {
5
     usa: {
20
     usa: {
6
-        splash: 'splash',
7
         name: 'name',
21
         name: 'name',
8
-        email: 'email',
22
+        aspect01: 'aspect-1',
23
+        aspect02: 'aspect-2',
24
+        aspect03: 'aspect-3',
9
         password: 'password',
25
         password: 'password',
10
-        // pronouns: 'pronouns',
11
-        // seeking: 'seeking',
12
-        // urgency: 'urgency',
13
-        // experience: 'experience',
14
-        // roles: 'role',
15
-        // duration: 'duration',
16
-        // presence: 'presence',
17
-        // language: 'language',
18
         zipcode: 'zipcode',
26
         zipcode: 'zipcode',
19
-        // distance: 'distance',
20
-        blurb: 'blurb',
27
+        seeking: 'seeking',
28
+        urgency: 'urgency',
29
+        aspect04: 'aspect-4',
30
+        aspect05: 'aspect-5',
31
+        aspect06: 'aspect-6',
32
+        email: 'email',
33
+        presence: 'presence',
34
+        duration: 'duration',
35
+        pronouns: 'pronouns',
36
+        language: 'language',
21
         image: 'image',
37
         image: 'image',
22
-        aspects: 'aspects'
38
+        distance: 'distance',
39
+        blurb: 'blurb',
40
+        // experience: 'experience',
41
+        // roles: 'role',
23
     },
42
     },
24
 }
43
 }
44
+const surveyStages = {
45
+    7: allSteps.usa.name,
46
+    8: allSteps.usa.email,
47
+    9: allSteps.usa.password,
48
+    10: allSteps.usa.zipcode,
49
+    11: allSteps.usa.seeking,
50
+    12: allSteps.usa.image,
51
+    13: allSteps.usa.language,
52
+    14: allSteps.usa.duration,
53
+    15: allSteps.usa.presence,
54
+    16: allSteps.usa.blurb,
55
+    17: allSteps.usa.urgency,
56
+    18: allSteps.usa.pronouns,
57
+    19: allSteps.usa.distance,
58
+    1: allSteps.usa.aspect01,
59
+    2: allSteps.usa.aspect02,
60
+    3: allSteps.usa.aspect03,
61
+    4: allSteps.usa.aspect04,
62
+    5: allSteps.usa.aspect05,
63
+    6: allSteps.usa.aspect06,
64
+}
25
 
65
 
26
 const aspectResponses = {
66
 const aspectResponses = {
27
     usa: {
67
     usa: {
52
     // key 10
92
     // key 10
53
     duration: ['full-time', 'part-time', 'contract', 'flexible'],
93
     duration: ['full-time', 'part-time', 'contract', 'flexible'],
54
     // Experience and roles concat, key: 14
94
     // Experience and roles concat, key: 14
55
-    experience: ['associate', 'junior', 'mid-level', 'senior', 'staff'],
56
-    roles: {
57
-        type: [
58
-            'back-end',
59
-            'database',
60
-            'front-end',
61
-            'full-stack',
62
-            'qa',
63
-            'security',
64
-            'system',
65
-            'test',
66
-        ],
67
-        position: [
68
-            'administrator',
69
-            'analyst',
70
-            'architect',
71
-            'developer',
72
-            'engineer',
73
-            'manager',
74
-            'technician',
75
-        ],
76
-        candidate: ['hiring_manager', 'recruiter'],
77
-    },
95
+    //     experience: ['associate', 'junior', 'mid-level', 'senior', 'staff'],
96
+    // roles: {
97
+    // type: [
98
+    // 'back-end',
99
+    // 'database',
100
+    // 'front-end',
101
+    // 'full-stack',
102
+    // 'qa',
103
+    // 'security',
104
+    // 'system',
105
+    // 'test',
106
+    // ],
107
+    // position: [
108
+    // 'administrator',
109
+    // 'analyst',
110
+    // 'architect',
111
+    // 'developer',
112
+    // 'engineer',
113
+    // 'manager',
114
+    // 'technician',
115
+    // ],
116
+    // candidate: ['hiring_manager', 'recruiter'],
117
+    // },
78
     pronouns: ['she/her', 'she/they', 'he/him', 'he/they', 'they/them'],
118
     pronouns: ['she/her', 'she/they', 'he/him', 'he/they', 'they/them'],
119
+    //    role: ['role1', 'role2'],
79
     image: [],
120
     image: [],
80
     zipcode: [],
121
     zipcode: [],
81
     blurb: [],
122
     blurb: [],
82
 }
123
 }
83
 
124
 
84
-export { allSteps, aspectResponses, possible, DELIMITER }
125
+export { allSteps, splash, surveyStages, aspectResponses, aspectsArr, possible }

+ 68
- 66
frontend/src/utils/survey.js 파일 보기

1
 import { Survey } from '../entities/index.js'
1
 import { Survey } from '../entities/index.js'
2
 import { fetchQuestions } from '../services/index.js'
2
 import { fetchQuestions } from '../services/index.js'
3
-import { possible } from './lang.js'
4
-
5
-const promptToComponent = {
6
-    splash: 'Splash',
7
-    name: 'FormInput',
8
-    email: 'FormInput',
9
-    password: 'FormInput',
10
-    zipcode: 'FormInput',
11
-    // seeking: 'FormDropdown',
12
-    // urgency: 'FormDropdown',
13
-    // presence: 'FormDropdown',
14
-    // duration: 'FormDropdown',
15
-    // experience: 'FormTags',
16
-    // pronouns: 'FormDropdown',
17
-    // language: 'FormDropdown',
18
-    image: 'FormInput',
19
-    // distance: 'FormInput',
20
-    blurb: 'FormInput',
21
-    aspects: 'Aspects'
22
-}
23
-/**
24
- * Make a step from match or step information
25
- * @param {object} match
26
- * @param {object} step
27
- * @returns something like a response_key with possible responses
28
- */
29
-const formatStep = (match, step) => {
30
-    const responsesByCategory = possible['usa']
31
-    const responseKey = {
32
-        response_key_id: match ? match.response_key_id : null,
33
-        response_key_category: match ? match.response_key_category : 'profile',
34
-        response_key_prompt: match ? match.response_key_prompt : step,
35
-        response_key_description: match ? match.response_key_description : null,
36
-    }
37
-    return {
38
-        ...responseKey,
39
-        responses: responsesByCategory[step] ? responsesByCategory[step] : [],
40
-    }
41
-}
42
-const associateWithComponent = responseKeyLike => {
43
-    let component = promptToComponent[responseKeyLike.response_key_prompt]
44
-    return { ...responseKeyLike, component }
45
-}
46
-
47
-const hasMatch = (step, inArray) => {
48
-    return inArray.find(q => q.response_key_prompt == step)
49
-}
3
+import { splash, possible, surveyStages, allSteps } from './lang.js'
50
 
4
 
51
 class SurveyFactory {
5
 class SurveyFactory {
52
     constructor() {
6
     constructor() {
53
         this.questionsFromDb = []
7
         this.questionsFromDb = []
54
     }
8
     }
55
-    _setSteps(langFile) {
56
-        const stepsToProcess = [...Object.values(langFile)]
57
-        const seenIds = []
58
-        const stepsInCommon = stepsToProcess.map(step => {
59
-            // Match question to step
60
-            const match = hasMatch(step, this.questionsFromDb)
61
-            if (match) {
62
-                seenIds.push(match.response_key_id)
9
+    _addResponses(responseKeys, responsesByCategory) {
10
+        const existingResponses = {}
11
+        // Removes empty form drop down options from possible['usa']
12
+        Object.keys(responsesByCategory).forEach(categoryKey => {
13
+            if (responsesByCategory[categoryKey].length) {
14
+                existingResponses[categoryKey] = responsesByCategory[categoryKey]
63
             }
15
             }
64
-            const responseKeyLike = formatStep(match, step)
65
-            const withComponent = associateWithComponent(responseKeyLike)
66
-            console.log('withComponent :>> ', withComponent)
67
-            return withComponent
68
         })
16
         })
69
-        // temporary extra condition in filter 
70
-        let unseen = this.questionsFromDb.filter(
71
-            q => !seenIds.includes(q.response_key_id) && [1,2,3,4,5,6].includes(q.response_key_id),
72
-        )
73
-        return [...stepsInCommon, ...unseen]
17
+        // Adds form drop down options to each responseKey
18
+        Object.keys(existingResponses).forEach(inputKey => {
19
+            responseKeys.forEach(responseKey => {
20
+                if (responseKey.survey_stage == inputKey) {
21
+                    responseKey.responses = existingResponses[inputKey]
22
+                }
23
+            })
24
+        })
25
+        return responseKeys
26
+    }
27
+    _addComponents(responseKeys) {
28
+        responseKeys.forEach(responseKey => {
29
+            switch (responseKey.category) {
30
+                case 'input':
31
+                    responseKey.component = 'FormInput'
32
+                    break
33
+                case 'choice':
34
+                    responseKey.component = 'FormDropdown'
35
+                    break
36
+                case 'aspect':
37
+                    responseKey.component = 'QuestionResponse'
38
+                    break
39
+            }
40
+        })
41
+        return responseKeys
42
+    }
43
+    _addSurveySteps(responseKeys, surveyStages) {
44
+        responseKeys.forEach(responseKey => {
45
+            Object.keys(surveyStages).forEach((stage, i) => {
46
+                if (responseKey.response_key_id == stage) {
47
+                    responseKey.survey_stage = surveyStages[i + 1]
48
+                }
49
+            })
50
+        })
51
+        return responseKeys
52
+    }
53
+    // TODO: Don't nest the for loop...
54
+    _sortSurveySteps(mutatedResponseKeys, allSteps) {
55
+        const reordered = []
56
+        Object.values(allSteps).forEach(step => {
57
+            Object.values(mutatedResponseKeys).forEach(response => {
58
+                if (surveyStages[response.response_key_id] === step)  {
59
+                    response.survey_stage = step
60
+                    reordered.push(response)
61
+                }
62
+            })
63
+        })
64
+        return reordered
65
+    }
66
+    _setSteps() {
67
+        const responseKeys = this.questionsFromDb
68
+        const responsesByCategory = possible['usa']
69
+        let mutatedResponseKeys = this._addSurveySteps(responseKeys, surveyStages)
70
+        mutatedResponseKeys = this._addResponses(mutatedResponseKeys, responsesByCategory)
71
+        mutatedResponseKeys = this._addComponents(responseKeys)
72
+        mutatedResponseKeys = this._sortSurveySteps(mutatedResponseKeys, allSteps['usa'])
73
+        // Splash page is placed at beginning of survey
74
+        mutatedResponseKeys.unshift(splash)
75
+        return mutatedResponseKeys
74
     }
76
     }
75
     async getQuestions() {
77
     async getQuestions() {
76
         try {
78
         try {
80
             console.error(err)
82
             console.error(err)
81
         }
83
         }
82
     }
84
     }
83
-    async createSurvey(langFile, roleTree) {
85
+    async createSurvey(roleTree) {
84
         if (!this.questionsFromDb.length) {
86
         if (!this.questionsFromDb.length) {
85
             const res = await this.getQuestions()
87
             const res = await this.getQuestions()
86
             console.warn(
88
             console.warn(
87
                 `Attempted to create a survey before getting questions: retrieved ${res.length} questions`,
89
                 `Attempted to create a survey before getting questions: retrieved ${res.length} questions`,
88
             )
90
             )
89
         }
91
         }
90
-        const steps = this._setSteps(langFile)
92
+        const steps = this._setSteps()
91
         return new Survey(steps, roleTree)
93
         return new Survey(steps, roleTree)
92
     }
94
     }
93
 }
95
 }

+ 43
- 8
frontend/src/views/OnboardingView.vue 파일 보기

2
 main.view--onboarding
2
 main.view--onboarding
3
     article(
3
     article(
4
         style='display: flex; flex-direction: column; align-items: center'
4
         style='display: flex; flex-direction: column; align-items: center'
5
-        v-if='survey'
5
+        v-if='currentStep !== survey.steps.length'
6
     )
6
     )
7
+        .answers(v-for='(value, key) in answered')
8
+            span(v-if='key == "name" && value && currentStep == 2') Hi {{ value }}!
9
+            span(v-if='key == "email" && value && currentStep == 3') Thanks for the contact info, {{ answered.name }}!
10
+
11
+        //- h3(v-if='currentStep == 1') Welcome to Siimee Onboarding! Let's get started!
12
+        br
7
         .step(v-for='(step, i) in survey.steps')
13
         .step(v-for='(step, i) in survey.steps')
8
             component(
14
             component(
9
-                :aspect-questions='step.component == "Aspects" ? survey.aspectQuestions : null'
10
                 :is='step.component'
15
                 :is='step.component'
11
                 :question='step'
16
                 :question='step'
17
+                :currentStep='currentStep'
18
+                :surveyStepsCount='survey.steps.length'
12
                 @handle-submit='onSubmit'
19
                 @handle-submit='onSubmit'
13
                 @update-answers='updateAnswers'
20
                 @update-answers='updateAnswers'
14
                 v-if='step && currentStep == i'
21
                 v-if='step && currentStep == i'
15
             )
22
             )
23
+        .invalidResponseMessage(
24
+            style='text-align: center'
25
+            v-if='invalidResponse'
26
+        )
27
+            p {{ survey.steps[currentStep].invalidInputPrompt }}
28
+
29
+        footer
30
+            p(v-if='currentStep != 0') You have completed: {{ currentStep }} / {{ survey.steps.length }} survey steps
31
+
32
+    article(v-else)
33
+        SurveyCompleteView(:answers='answered' :surveySteps='survey.steps')
16
 </template>
34
 </template>
17
 
35
 
18
 <script>
36
 <script>
19
 import { surveyFactory } from '@/utils'
37
 import { surveyFactory } from '@/utils'
20
-import { allSteps } from '@/utils/lang'
21
 import stepViews from '@/components/onboarding'
38
 import stepViews from '@/components/onboarding'
39
+import SurveyCompleteView from './SurveyCompleteView.vue'
22
 
40
 
23
 // import savesurveybyprfileid - call it on submit
41
 // import savesurveybyprfileid - call it on submit
24
 // paginate to save every steps answers
42
 // paginate to save every steps answers
26
     name: 'OnboardingView',
44
     name: 'OnboardingView',
27
     components: {
45
     components: {
28
         ...stepViews,
46
         ...stepViews,
47
+        SurveyCompleteView,
29
     },
48
     },
30
     data: () => ({
49
     data: () => ({
31
         answered: {},
50
         answered: {},
32
         aspectQuestions: [],
51
         aspectQuestions: [],
33
         currentStep: 0,
52
         currentStep: 0,
34
         survey: null,
53
         survey: null,
54
+        invalidResponse: false,
35
     }),
55
     }),
36
     async created() {
56
     async created() {
37
-        this.survey = await surveyFactory.createSurvey(allSteps['usa'])
57
+        this.survey = await surveyFactory.createSurvey()
38
     },
58
     },
39
     methods: {
59
     methods: {
40
         onSubmit() {
60
         onSubmit() {
46
         updateAnswers(payload) {
66
         updateAnswers(payload) {
47
             // null payload is passed on splash page
67
             // null payload is passed on splash page
48
             if (payload) {
68
             if (payload) {
49
-                const k = payload.question.response_key_prompt
50
-                this.answered[k] = payload.answer
51
-                console.log(`${k}:`, this.answered[k])
69
+                this.invalidResponse = false
70
+                const k = payload.question.survey_stage
71
+                this.answered[k] = payload.input
72
+
73
+                if (!this.survey.validateAnswer(payload)) {
74
+                    this.invalidResponse = true
75
+                    return
76
+                }
77
+
78
+                // once validated, don't log password in answered object
79
+                this.answered[k] = k === 'password' ? undefined : payload.input
52
                 console.log(`Updated answers: ${JSON.stringify(this.answered)}`)
80
                 console.log(`Updated answers: ${JSON.stringify(this.answered)}`)
53
                 if (k === 'aspects') return
81
                 if (k === 'aspects') return
54
             }
82
             }
55
-            this.goToStep(this.currentStep + 1)
83
+            if (this.currentStep > this.survey.steps.length) {
84
+                this.onSubmit(this.answered)
85
+            } else {
86
+                this.goToStep(this.currentStep + 1)
87
+            }
56
         },
88
         },
57
     },
89
     },
58
 }
90
 }
70
     article
102
     article
71
         height: 100vh
103
         height: 100vh
72
 
104
 
105
+    .answers
106
+        text-align: center
107
+
73
     .w-button
108
     .w-button
74
             display: flex
109
             display: flex
75
             width: 315px
110
             width: 315px

+ 62
- 0
frontend/src/views/SurveyCompleteView.vue 파일 보기

1
+<template lang="pug">
2
+main.view--surveycomplete
3
+    article(style='display: flex; flex-direction: column; align-items: center; text-align: center;')
4
+        h2 Thanks for Completing Our Survey!!
5
+        h1 Please review your answers and let us know if you need to change anything.
6
+        br
7
+        p(v-for='input in formInputs')
8
+            p(v-for='(value, key) in answers')
9
+                p(v-if='input.survey_stage == key && key !== "password"')
10
+                    p Your {{ key }}: {{ value }}
11
+        br
12
+        p(v-for='input in formDropdowns')
13
+            p(v-for='(value, key) in answers')
14
+                p(v-if='input.survey_stage == key')
15
+                    p Your {{ key }}: {{ value }}
16
+        br
17
+        p(v-for='(response, responseIndex) in questionResponses')
18
+            p(v-for='(value, key) in answers')
19
+                p(v-if='response.survey_stage == key') 
20
+                    p Survey Question {{ responseIndex + 1 }}: 
21
+                    p {{ response.response_key_prompt }}
22
+                    p You Answered: {{ value }}
23
+                    br
24
+        w-button.ma1(@click="changeAnswers") Change Answers
25
+        w-button.ma1(@click="finalSubmit") Submit Answers
26
+</template>
27
+
28
+<script>
29
+export default {
30
+    props: {
31
+        answers: {
32
+            type: Object,
33
+            default: () => ({}),
34
+        },
35
+        surveySteps: {
36
+            type: Array,
37
+            default: () => [],
38
+        },
39
+    },
40
+    data: () => ({
41
+        surveyObjects: [],
42
+        formInputs: [],
43
+        questionResponses: [],
44
+        formDropdowns: [],
45
+    }),
46
+    created() {
47
+        this.surveySteps.forEach((step) => {
48
+            switch (step.component) {
49
+                case 'FormInput':
50
+                    this.formInputs.push(step)
51
+                    break
52
+                case 'FormDropdown':
53
+                    this.formDropdowns.push(step)
54
+                    break
55
+                case 'QuestionResponse':
56
+                    this.questionResponses.push(step)
57
+                    break
58
+            }
59
+        })
60
+    },
61
+}
62
+</script>

Loading…
취소
저장