Bläddra i källkod

:recycle: removing older form ideas | cleaning-up survey into smaller funcs | moving response generator to utils

tags/0.0.1
J 4 år sedan
förälder
incheckning
32f70c0f22

+ 5
- 3
backend/lib/services/profile.js Visa fil

178
             .withGraphFetched('responses')
178
             .withGraphFetched('responses')
179
             .withGraphFetched('user')
179
             .withGraphFetched('user')
180
 
180
 
181
-        matchingProfile.tags = matchingProfile.tags.map(
182
-            tag => this.tagLookup[tag.tag_id],
183
-        )
181
+        if(matchingProfile?.tags.length){
182
+            matchingProfile.tags = matchingProfile.tags.map(
183
+                tag => this.tagLookup[tag.tag_id],
184
+            )
185
+        }
184
 
186
 
185
         return new CompleteProfile(matchingProfile)
187
         return new CompleteProfile(matchingProfile)
186
     }
188
     }

+ 1
- 1
frontend/src/App.vue Visa fil

42
         */
42
         */
43
         if(DEV_MODE) { this.setPid(DEV_PID) }
43
         if(DEV_MODE) { this.setPid(DEV_PID) }
44
 
44
 
45
-        if(currentProfile.isLoggedIn()) {
45
+        if(currentProfile.isLoggedIn) {
46
             console.warn(`setting up Chatter and Toaster for ${this.getPid}...`)
46
             console.warn(`setting up Chatter and Toaster for ${this.getPid}...`)
47
             this.setupChatter()
47
             this.setupChatter()
48
             this.setupToaster()
48
             this.setupToaster()

+ 0
- 219
frontend/src/components/form.vue Visa fil

1
-<template lang="pug">
2
-.form.f-col.start.mr-1.w-full(v-if="form.length")
3
-    header
4
-        p answers to save: {{ answers }}
5
-
6
-    ul.w-full(v-for="(step, i) in form")
7
-        li.f-row.start.b-solid.p-0(
8
-            v-for="prompt in step"
9
-            v-if="step && step.length"
10
-            v-show="(i + 1) == state.step"
11
-        )
12
-            h3 {{ prompt.question }}?
13
-            .response-wrapper(v-if="prompt.type === 'input-string'")
14
-                label {{ prompt.type }}
15
-                input(v-model="answers[makeKey(prompt)]")
16
-            .response-wrapper(v-else-if="prompt.type === 'tag-cloud'")
17
-                label {{ prompt.type }}
18
-                button.p-0(
19
-                    :disabled="response == answers[makeKey(prompt)]"
20
-                    :prompt-question="makeKey(prompt)"
21
-                    @click="respondFromTag"
22
-                    v-for="response in prompt.responses"
23
-                ) {{ response }}
24
-            //- Checklist
25
-            .response-wrapper(v-if="prompt.type === 'checklist'")
26
-                .checklist(
27
-                    v-for="(response, index) in prompt.responses"
28
-                    v-bind:key="response")
29
-                    input(
30
-                        type="checkbox"
31
-                        :id="response"
32
-                        :value="response"
33
-                        :name="response"
34
-                        :true-value="response"
35
-                        false-value=""
36
-                        v-model="answers[makeKey(prompt)]")
37
-                    label(:for="response") {{ response }}
38
-            //- Slider from -3 to 0 to +3 (increments of 1)
39
-            .response-wrapper(v-else-if="prompt.type === 'input-slide'")
40
-                label {{ prompt.type }}
41
-                input(
42
-                    type="range"
43
-                    min="-3"
44
-                    max="3"
45
-                    :prompt="makeKey(prompt)"
46
-                    :value="answers[makeKey(prompt)]"
47
-                    @input="setAnswer"
48
-                )
49
-                span {{ answers[makeKey(prompt)] }}
50
-
51
-    footer.f-row.w-full
52
-        button.p-1(:disabled="state.step == 1" @click="back") back
53
-        button.p-1(:disabled="state.step == form.length" @click="next") next
54
-        button.p-1(:disabled="state.step != form.length" @click="next") save
55
-        .f-grow
56
-        p step: {{ state.step }} of {{ form.length }} for {{ pid }}
57
-</template>
58
-
59
-<script setup>
60
-import Joi from 'joi'
61
-import { reactive } from 'vue'
62
-import { useRouter } from 'vue-router'
63
-import { validatorMapping, makeKebob } from '@/utils'
64
-import {
65
-    saveSurveyByProfileId,
66
-    scoreSurveyByProfileId,
67
-} from '../services/survey.service'
68
-import { scoreVals } from '../../../backend/db/data-generator/config.json'
69
-
70
-const props = defineProps({
71
-    form: {
72
-        type: Array,
73
-        required: true,
74
-    },
75
-    pid: {
76
-        required: true,
77
-        type: Number,
78
-    },
79
-})
80
-const makeKey = prompt => {
81
-    return `${prompt.id}-${prompt.type}-${makeKebob(prompt.question)}`
82
-}
83
-
84
-/**
85
- * Our form is comprised of steps, and each step has a series of questions
86
- */
87
-const state = reactive({ step: 1 })
88
-
89
-/**
90
- * Create state object to store answers based on the questions in formSteps
91
- */
92
-const answers = reactive({})
93
-
94
-const setAnswer = e => {
95
-    const prompt = e.target.attributes.prompt.nodeValue
96
-    answers[prompt] = e.target.value
97
-}
98
-const resetAnswers = () => {
99
-    Object.keys(answers).forEach(key => (answers[key] = null))
100
-}
101
-resetAnswers()
102
-const setupAnswers = () => {
103
-    props.form.forEach(step => {
104
-        step.forEach(prompt => {
105
-            answers[makeKey(prompt)] = '0'
106
-        })
107
-    })
108
-}
109
-setupAnswers()
110
-/**
111
- * Callback for clicking a tag to respond
112
- */
113
-const respondFromTag = e => {
114
-    const answer = e.target.textContent
115
-    const questionKey =
116
-        e.target.attributes.getNamedItem('prompt-question').nodeValue
117
-    answers[questionKey] = answer
118
-}
119
-/**
120
- * Check answered fields in current step, build a validator, then validate the answers by prompt.type
121
- * @param {number} step // The current step
122
- * @returns {object} // Joi object
123
- */
124
-const isValid = step => {
125
-    const schema = {}
126
-    const answeredThisStep = {}
127
-    props.form[step].forEach(prompt => {
128
-        const key = makeKey(prompt)
129
-        schema[key] = validatorMapping[prompt.type]
130
-        answeredThisStep[key] = answers[key]
131
-    })
132
-    return Joi.object(schema).validate(answeredThisStep)
133
-}
134
-/**
135
- * Vary the final answer format by input type
136
- */
137
-const formatAnswer = (inputType, answer) => {
138
-    let formattedAnswer = answer
139
-    if (inputType == 'slide') {
140
-        const offset = (scoreVals.length - 1) / 2
141
-        formattedAnswer = scoreVals[offset + parseInt(answer)].toString()
142
-    }
143
-    return formattedAnswer
144
-}
145
-/**
146
- * Save or take the-nNext step in the form
147
- */
148
-const getInputTypeFromKey = k => {
149
-    const inputIndex = k.split('-').indexOf('input')
150
-    return k.split('-')[inputIndex + 1]
151
-}
152
-
153
-const next = async e => {
154
-    const validity = isValid(state.step - 1)
155
-    if (validity.error) console.error(validity.error)
156
-
157
-    if (state.step === props.form.length) {
158
-        // Make key map of each prompt.id
159
-        const questiontoResponseKeyId = {}
160
-        props.form.forEach(step => {
161
-            step.forEach(prompt => {
162
-                questiontoResponseKeyId[makeKey(prompt)] = prompt.id
163
-            })
164
-        })
165
-
166
-        const idWithResponseVal = Object.keys(answers).map(answerKey => {
167
-            return {
168
-                response_key_id: questiontoResponseKeyId[answerKey],
169
-                val: formatAnswer(
170
-                    getInputTypeFromKey(answerKey),
171
-                    answers[answerKey],
172
-                ),
173
-            }
174
-        })
175
-
176
-        const maxDistance = 100
177
-
178
-        if (!props.pid) return console.error(`no pid: ${props.pid}`)
179
-
180
-        saveSurveyByProfileId(idWithResponseVal, props.pid)
181
-        resetAnswers()
182
-
183
-        scoreSurveyByProfileId(props.pid, maxDistance)
184
-
185
-        state.step = 1
186
-
187
-        const router = useRouter()
188
-        router.push({ name: 'HomeView' })
189
-    } else if (state.step < props.form.length) {
190
-        state.step++
191
-    } else {
192
-        console.error(
193
-            `Cannot perform action: next() | on form step: ${state.step} of ${props.formSteps.length}`,
194
-        )
195
-    }
196
-}
197
-/**
198
- * Back a step in the form
199
- */
200
-const back = e => {
201
-    if (state.step > 1) {
202
-        state.step--
203
-    } else {
204
-        console.error(
205
-            `Cannot perform action: back() | on form step: ${state.step} of ${props.formSteps.length}`,
206
-        )
207
-    }
208
-}
209
-</script>
210
-
211
-<style lang="postcss">
212
-// prettier-ignore
213
-@import '@/sss/theme.sss'
214
-
215
-.form
216
-    color: $light
217
-    ul
218
-        list-style: none
219
-</style>

+ 0
- 62
frontend/src/components/form/button-choice.vue Visa fil

1
-<template lang="pug">
2
-.form--step.button-choice
3
-    header
4
-        p selections {{selected}} {{opts}}
5
-    main(:class="{ 'f-row': opts.length > 2, 'f-col': opts.length <= 2 }")
6
-        button(v-for="op in opts" :class="isSelected(op)" @mouseover="hoverOption(op)" @click="selectOption(op)") {{ op }}
7
-</template>
8
-
9
-<script setup>
10
-import { ref } from 'vue'
11
-
12
-const emit = defineEmits(['selected', 'hovered'])
13
-
14
-const props = defineProps({
15
-    opts: {
16
-        required: true,
17
-        type: Array,
18
-        default: () => ['up', 'down'],
19
-        // default: () => ['up', 'left', 'right', 'down'],
20
-    },
21
-    prompt: {
22
-        required: true,
23
-        type: String,
24
-    },
25
-})
26
-
27
-const selected = ref([])
28
-
29
-const isSelected = val => {
30
-    return selected.value.includes(val) ? 'selected' : ''
31
-}
32
-
33
-const selectOption = optionVal => {
34
-    if (!selected.value.includes(optionVal)) {
35
-        selected.value.push(optionVal)
36
-    } else {
37
-        selected.value = selected.value.filter(val => val != optionVal)
38
-    }
39
-    const sel = {}
40
-    sel[props.prompt] = selected.value[0]
41
-    emit('selected', sel)
42
-}
43
-const hoverOption = optionVal => {
44
-    emit('hovered', props.prompt, optionVal)
45
-}
46
-</script>
47
-
48
-<style lang="postcss">
49
-// prettier-ignore
50
-@import '@/sss/theme.sss'
51
-
52
-.button-choice
53
-    color: white
54
-    main
55
-        flex-wrap: wrap
56
-        button
57
-            padding: 1em
58
-            border: 2px teal solid
59
-            border-radius: 4px
60
-            &.selected
61
-                border: 2px teal dotted
62
-</style>

+ 0
- 68
frontend/src/components/form/button-multi.vue Visa fil

1
-<template lang="pug">
2
-.form--step.button-multi
3
-    main(:class="{ 'f-row': opts.length > 2, 'f-col': opts.length <= 2 }")
4
-        button(v-for="op in opts" :class="isSelected(op)" @click="selectOption(op)" :disabled="selected.includes(op)") {{ op }}
5
-    button(@click="next") next
6
-</template>
7
-
8
-<script setup>
9
-import { ref } from 'vue'
10
-
11
-const emit = defineEmits(['onButtonSelect'])
12
-
13
-const props = defineProps({
14
-    opts: {
15
-        required: true,
16
-        type: Array,
17
-        default: () => [
18
-            ['up', 'down'],
19
-            ['left', 'right'],
20
-        ],
21
-    },
22
-    prompt: {
23
-        required: true,
24
-        type: String
25
-    },
26
-})
27
-
28
-
29
-const selected = ref([])
30
-
31
-const isSelected = val => {
32
-    return selected.value.includes(val) ? 'selected' : ''
33
-}
34
-
35
-const selectOption = optionVal => {
36
-    if (!selected.value.includes(optionVal)) {
37
-        selected.value.push(optionVal)
38
-    } else {
39
-        selected.value = selected.value.filter(val => val != optionVal)
40
-    }
41
-}
42
-
43
-const next = () => {
44
-    const sel = {}
45
-    sel[props.prompt] = selected.value
46
-    emit('selected', sel)
47
-    selected.value = []
48
-}
49
-</script>
50
-
51
-<style lang="postcss">
52
-// prettier-ignore
53
-@import '@/sss/theme.sss'
54
-
55
-.button-multi
56
-    color: white
57
-.step
58
-    flex-wrap: wrap
59
-    display: none
60
-    &.selected
61
-        display: block
62
-    button
63
-        padding: 1em
64
-        border: 2px teal solid
65
-        border-radius: 4px
66
-        &.selected
67
-            border: 2px teal dotted
68
-</style>

+ 0
- 62
frontend/src/components/form/input-string.vue Visa fil

1
-<template lang="pug">
2
-.form--step.input-string
3
-    header
4
-        p input {{input}}
5
-    main.f-col
6
-        input(v-if="!multiline" v-model="input")
7
-        textarea(v-else cols="50" rows="8" v-model="input")
8
-        button(@click="selectOption").w-full next
9
-</template>
10
-
11
-<script setup>
12
-import { ref, watch } from 'vue'
13
-
14
-const emit = defineEmits(['selected', 'input'])
15
-
16
-const props = defineProps({
17
-    default: {
18
-        required: false,
19
-        type: String,
20
-        default: () => 'Bob',
21
-    },
22
-    multiline: {
23
-        required: false,
24
-        type: Boolean,
25
-        default: true,
26
-    },
27
-    prompt: {
28
-        required: true,
29
-        type: String,
30
-    },
31
-})
32
-
33
-const input = ref(props.default)
34
-
35
-const selectOption = () => {
36
-    const sel = {}
37
-    sel[props.prompt] = input.value
38
-    emit('selected', sel)
39
-}
40
-watch(
41
-    () => input.value,
42
-    () => {
43
-        emit('input', props.prompt, input.value)
44
-    },
45
-)
46
-</script>
47
-
48
-<style lang="postcss">
49
-// prettier-ignore
50
-@import '@/sss/theme.sss'
51
-
52
-.input-string
53
-    color: white
54
-    main
55
-        flex-wrap: wrap
56
-        button
57
-            padding: 1em
58
-            border: 2px teal solid
59
-            border-radius: 4px
60
-            &.selected
61
-                border: 2px teal dotted
62
-</style>

+ 5
- 5
frontend/src/router/guards.js Visa fil

2
 
2
 
3
 
3
 
4
 const checkLoginStatus = (destination, nextCb) => {
4
 const checkLoginStatus = (destination, nextCb) => {
5
-    console.warn('currentProfile logged in:', currentProfile.isLoggedIn())
6
-    console.warn('currentProfile completed:', currentProfile.isComplete())
5
+    console.warn(`profile: ${currentProfile.id.value} | login:${currentProfile.isLoggedIn} | completed: ${currentProfile.isComplete}`)
6
+    
7
     if (
7
     if (
8
         destination.meta.requiresCompleteProfile &&
8
         destination.meta.requiresCompleteProfile &&
9
-        !currentProfile.isLoggedIn() &&
10
-        !currentProfile.isComplete() 
9
+        !currentProfile.isLoggedIn &&
10
+        !currentProfile.isComplete 
11
     ) {
11
     ) {
12
         nextCb('survey')
12
         nextCb('survey')
13
     } else if(
13
     } else if(
14
         destination.meta.requiresCompleteProfile &&
14
         destination.meta.requiresCompleteProfile &&
15
         destination.meta.requiresAuth &&
15
         destination.meta.requiresAuth &&
16
-        !currentProfile.isLoggedIn()
16
+        !currentProfile.isLoggedIn
17
     ) {
17
     ) {
18
         nextCb('login')
18
         nextCb('login')
19
     } else {
19
     } else {

+ 1
- 0
frontend/src/services/index.js Visa fil

1
+export * from './user.service'
1
 export * from './profile.service'
2
 export * from './profile.service'
2
 export * from './grouping.service'
3
 export * from './grouping.service'
3
 export * from './survey.service'
4
 export * from './survey.service'

+ 35
- 13
frontend/src/services/login.service.js Visa fil

5
 /**
5
 /**
6
  * Logged in profile state manager
6
  * Logged in profile state manager
7
  * Sort of a util and service hybrid
7
  * Sort of a util and service hybrid
8
- * @returns {array} profiles
9
  */
8
  */
10
 class Login {
9
 class Login {
11
     constructor() {
10
     constructor() {
17
         this.responses = []
16
         this.responses = []
18
         this.tags = []
17
         this.tags = []
19
     }
18
     }
20
-    isLoading() {
19
+    get isLoading() {
21
         return this._loading
20
         return this._loading
22
     }
21
     }
23
-    isLoggedIn() {
22
+    /**
23
+     * Track login separate from complete-ess
24
+     * so a user can login but attached profile
25
+     * can forward to survey
26
+     * @returns {boolean}
27
+     */
28
+    get isLoggedIn() {
24
         return this.id.value != null
29
         return this.id.value != null
25
     }
30
     }
26
-    isComplete() {
31
+    /**
32
+     * Combine questions retrieved from the database and
33
+     * questions defined in out lang file and 
34
+     * copare to responses stored
35
+     * @returns {boolean}
36
+     */
37
+    get isComplete() {
27
         return this.responses.length == surveyFactory.questionsFromDb.length && surveyFactory.questionsFromDb.length > 0
38
         return this.responses.length == surveyFactory.questionsFromDb.length && surveyFactory.questionsFromDb.length > 0
28
     }
39
     }
29
-    hasResponses() {
40
+    /**
41
+     * Check that some responses are set
42
+     * @returns {boolean}
43
+     */
44
+    get hasResponses() {
30
         return this.responses.length && this.responses.length > 0
45
         return this.responses.length && this.responses.length > 0
31
     } 
46
     } 
32
 
47
 
33
     /**
48
     /**
34
-     * Login a profile id
49
+     * Login a profile by id number
50
+     * @param {number} profileId
51
+     * @returns {number} stored reactive id
35
      */
52
      */
36
-     async login(pid) {
37
-        this.id.value = parseInt(pid)
38
-        console.warn('logging in:', this.id.value)
53
+     async login(profileId) {
54
+        console.warn('logging in:', profileId)
55
+        this.id.value = parseInt(profileId)
39
         return this.id.value
56
         return this.id.value
40
     }
57
     }
41
     logout() { this.id.value = null }
58
     logout() { this.id.value = null }
42
     
59
     
43
     async getTags() {
60
     async getTags() {
44
-        this.tags = []
45
-        return this.tags
61
+        try {
62
+            const tags = []
63
+            this.setTags(tags)
64
+        } catch (err) {
65
+            console.error(err)
66
+        }
46
     }
67
     }
68
+    setTags(tags) { this.tags = tags }
47
 
69
 
48
     async getResponses() {
70
     async getResponses() {
49
         try {
71
         try {
50
             const responseList = await fetchResponsesByProfileId(this.id)
72
             const responseList = await fetchResponsesByProfileId(this.id)
51
-            this._setResponses(responseList)
73
+            this.setResponses(responseList)
52
         } catch (err) {
74
         } catch (err) {
53
             console.error(err)
75
             console.error(err)
54
         }
76
         }
55
     }
77
     }
56
-    _setResponses(responses) { this.responses = responses }
78
+    setResponses(responses) { this.responses = responses }
57
 }
79
 }
58
 
80
 
59
 const currentProfile = new Login()
81
 const currentProfile = new Login()

+ 7
- 1
frontend/src/services/profile.service.js Visa fil

20
     return validProfileInstances
20
     return validProfileInstances
21
 }
21
 }
22
 
22
 
23
+const createProfileForUserId = async (userId, responses) => {
24
+    const profile = await db.post(`/user/${userId}/profile`, responses)
25
+    return profile
26
+}
27
+
23
 const fetchProfileByProfileId = async profileId => {
28
 const fetchProfileByProfileId = async profileId => {
24
     const profile = await db.get(`/profile/${profileId}`)
29
     const profile = await db.get(`/profile/${profileId}`)
25
     return profile
30
     return profile
26
 }
31
 }
27
-export { fetchProfilesByUserId, fetchProfileByProfileId }
32
+
33
+export { fetchProfilesByUserId, fetchProfileByProfileId, createProfileForUserId }

+ 8
- 7
frontend/src/services/survey.service.js Visa fil

16
     })
16
     })
17
 }
17
 }
18
 
18
 
19
+// TODO: Remove
19
 const saveSurveyByProfileId = async (surveyResponses, profileId) => {
20
 const saveSurveyByProfileId = async (surveyResponses, profileId) => {
20
     surveyResponses.forEach(responseKeyIdwithVal => {
21
     surveyResponses.forEach(responseKeyIdwithVal => {
21
         const keyId = responseKeyIdwithVal.response_key_id
22
         const keyId = responseKeyIdwithVal.response_key_id
22
         const val = responseKeyIdwithVal.val
23
         const val = responseKeyIdwithVal.val
23
-
24
+        console.log(keyId, val)
24
         // POST
25
         // POST
25
-        const myresponses = db.post(
26
-            `/profile/${profileId}/respond?response_key_id=${keyId}&val=${val}`,
27
-        )
28
-        return myresponses
26
+        // const myresponses = db.post(
27
+        //     `/profile/${profileId}/respond?response_key_id=${keyId}&val=${val}`,
28
+        // )
29
+        // return myresponses
29
     })
30
     })
30
 }
31
 }
31
 
32
 
34
         const keyId = responseKeyIdwithVal.response_key_id
35
         const keyId = responseKeyIdwithVal.response_key_id
35
         const val = responseKeyIdwithVal.val
36
         const val = responseKeyIdwithVal.val
36
         // PATCH
37
         // PATCH
37
-        const myresponses = db.patch(`/profile/${profileId}/update/${keyId}`, [
38
+        db.patch(`/profile/${profileId}/update/${keyId}`, [
38
             {
39
             {
39
                 response_id: 2,
40
                 response_id: 2,
40
                 profile_id: profileId,
41
                 profile_id: profileId,
45
     })
46
     })
46
 }
47
 }
47
 
48
 
48
-const scoreSurveyByProfileId = async (profileId, maxDistance) => {
49
+const scoreSurveyByProfileId = async (profileId, maxDistance = 99) => {
49
     const scoreSurvey = await db.get(
50
     const scoreSurvey = await db.get(
50
         `/profile/${profileId}/score?max_distance=${maxDistance}`,
51
         `/profile/${profileId}/score?max_distance=${maxDistance}`,
51
     )
52
     )

+ 16
- 0
frontend/src/services/user.service.js Visa fil

1
+import { db } from '../utils/db'
2
+
3
+/**
4
+ * Signup a new user
5
+ * @returns {User} instantiated User (see: /entites/user)
6
+ */
7
+const signupUser = async user => {
8
+    const payload = {
9
+        user_name: user.name,
10
+        user_email: user.email,
11
+        is_poster: user.seeking == 'position' ? 0 : 1
12
+    }
13
+    return await db.post(`/user/signup`, payload)
14
+}
15
+
16
+export { signupUser }

+ 83
- 1
frontend/src/utils/index.js Visa fil

5
 import { possible } from './lang'
5
 import { possible } from './lang'
6
 import { pidMixin, cardMixin } from './mixins'
6
 import { pidMixin, cardMixin } from './mixins'
7
 
7
 
8
+import { possibleZipcodes } from '../../../backend/db/data-generator/config.json'
9
+
8
 const api = new Connector('kittens')
10
 const api = new Connector('kittens')
9
 
11
 
10
 const validatorMapping = {
12
 const validatorMapping = {
22
 
24
 
23
 const mixins = { pidMixin, cardMixin }
25
 const mixins = { pidMixin, cardMixin }
24
 
26
 
27
+
28
+const randomNumber = max => {
29
+    return Math.floor(Math.random() * max) < 1
30
+        ? 1
31
+        : Math.floor(Math.random() * max)
32
+}
33
+const randomValFrom = arr => arr[randomNumber(arr.length)]
34
+const randomEmail = (length = 5) => {
35
+    let chars =
36
+        'abcdefghijklmnopqrstuvwxyz-_abcdefghijklmnopqrstuvwxyz0123456789'
37
+    let str = ''
38
+    for (let i = 0; i < length + randomNumber(9); i++) {
39
+        str += chars.charAt(Math.floor(Math.random() * chars.length))
40
+    }
41
+    const suffixs = [
42
+        '@gmail.com',
43
+        '@aol.com',
44
+        '@yahoo.com',
45
+        '@apple.com',
46
+        '@hotmail.com',
47
+        '@rocket-mail.com',
48
+        '@mail.com',
49
+    ]
50
+    return str + randomValFrom(suffixs)
51
+}
52
+const randomName = (length = 4) => {
53
+    let chars = 'aeiouaeiouabcdefghijklmnoprstuvwyabcdefghijklmnopqrstuvwxyz'
54
+    let str = ''
55
+    for (let i = 0; i < length + randomNumber(9); i++) {
56
+        str += chars.charAt(Math.floor(Math.random() * chars.length))
57
+    }
58
+    return str
59
+}
60
+const randomMedia = () => {
61
+    const stockimg = [
62
+        'https://i.imgur.com/a4wirDS.png',
63
+        'https://i.imgur.com/F6GxGXG.jpeg',
64
+        'https://i.imgur.com/ekrkdNt.jpeg',
65
+        'https://i.imgur.com/VtMTfDg.jpeg',
66
+        'https://i.imgur.com/9Fwouqm.jpeg',
67
+        'https://i.imgur.com/rOjRCgo.jpeg',
68
+        'https://i.imgur.com/FwSdQww.jpeg',
69
+        'https://i.imgur.com/JjZyzXL.jpeg',
70
+        'https://i.imgur.com/1DsSQ1s.jpeg',
71
+    ]
72
+    return randomValFrom(stockimg)
73
+}
74
+
75
+const randomSurveyResponses = count => {
76
+    const surveyResponses = [
77
+        { id: null ,"idOrPrompt": "email", "val": `${randomEmail()}` },
78
+        { id: null ,"idOrPrompt": "name", "val": `john test-${count}` },
79
+        { id: 99 ,"idOrPrompt": 15, "val": randomValFrom(possible.usa.pronouns) },
80
+        { id: null ,"idOrPrompt": "seeking", "val": Math.random() > 0.2 ? possible.usa.seeking[0] : possible.usa.seeking[1] },
81
+        { id: 99 ,"idOrPrompt": 13, "val": randomValFrom(possible.usa.urgency) },
82
+        { id: null ,"idOrPrompt": "experience", "val": randomValFrom(possible.usa.experience) },
83
+        { id: 99 ,"idOrPrompt": 14, "val": "swe" },
84
+        { id: 99 ,"idOrPrompt": 10, "val": randomValFrom(possible.usa.duration) },
85
+        { id: 99 ,"idOrPrompt": 9, "val": randomValFrom(possible.usa.language) },
86
+        { id: 99 ,"idOrPrompt": 11, "val": randomValFrom(possible.usa.presence) },
87
+        { id: 99 ,"idOrPrompt": 7, "val": `${randomValFrom(possibleZipcodes)}` },
88
+        { id: 99 ,"idOrPrompt": 16, "val": `${randomNumber(55)}` },
89
+        { id: 99 ,"idOrPrompt": 12, "val": "this is a test of the survey signup" },
90
+        { id: 99 ,"idOrPrompt": 8, "val": randomMedia() },
91
+        { id: 99 ,"idOrPrompt": 1, "val": `${randomNumber(3) - randomNumber(3)}` },
92
+        { id: 99 ,"idOrPrompt": 2, "val": `${randomNumber(3) - randomNumber(3)}` },
93
+        { id: 99 ,"idOrPrompt": 3, "val": `${randomNumber(3) - randomNumber(3)}` },
94
+        { id: 99 ,"idOrPrompt": 4, "val": `${randomNumber(3) - randomNumber(3)}` },
95
+        { id: 99 ,"idOrPrompt": 5, "val": `${randomNumber(3) - randomNumber(3)}` },
96
+        { id: 99 ,"idOrPrompt": 6, "val": `${randomNumber(3) - randomNumber(3)}` }
97
+    ]
98
+    return surveyResponses
99
+}
100
+
25
 export { 
101
 export { 
26
     api,
102
     api,
27
     validatorMapping,
103
     validatorMapping,
28
     surveyFactory,
104
     surveyFactory,
29
     makeKebob,
105
     makeKebob,
30
-    mixins
106
+    mixins,
107
+    randomSurveyResponses,
108
+    randomNumber,
109
+    randomValFrom,
110
+    randomMedia,
111
+    randomName,
112
+    randomEmail
31
 }
113
 }

+ 9
- 27
frontend/src/utils/lang.js Visa fil

1
 const DELIMITER = '_'
1
 const DELIMITER = '_'
2
 
2
 
3
-const stepToComponentMap = {
4
-    email: 'InputString',
5
-    name: 'InputString',
6
-    seeking: 'ButtonChoice',
7
-    urgency: 'ButtonChoice',
8
-    experience: 'ButtonChoice',
9
-    role: 'ButtonChoice',
10
-    duration: 'ButtonChoice',
11
-    presence: 'ButtonChoice',
12
-    language: 'ButtonMulti',
13
-    pronouns: 'ButtonMulti',
14
-    image: 'InputString',
15
-    zipcode: 'InputString',
16
-    blurb: 'InputString',
17
-    distance: 'InputRange',
18
-}
19
 // TODO: Combine these two
3
 // TODO: Combine these two
20
 const allSteps = {
4
 const allSteps = {
21
     usa: {
5
     usa: {
36
     }
20
     }
37
 }
21
 }
38
 
22
 
39
-const allResponses = {
23
+const aspectResponses = {
40
     usa: {
24
     usa: {
41
-        questionaire: {
42
-            never: 'never',
43
-            rarely: 'rarely',
44
-            not_really: 'not really',
45
-            occasionally: 'occasionally',
46
-            mostly: 'mostly',
47
-            often: 'often',
48
-            everytime: 'everytime',
49
-        }
25
+        never: 'never',
26
+        rarely: 'rarely',
27
+        not_really: 'not really',
28
+        occasionally: 'occasionally',
29
+        mostly: 'mostly',
30
+        often: 'often',
31
+        everytime: 'everytime',
50
     }
32
     }
51
 }
33
 }
52
 
34
 
131
     blurb: []
113
     blurb: []
132
 }
114
 }
133
 
115
 
134
-export { allSteps, allResponses, stepToComponentMap, possible }
116
+export { allSteps, aspectResponses, possible }

+ 1
- 1
frontend/src/views/LoginView.vue Visa fil

34
              * Profile needs a complete survey and
34
              * Profile needs a complete survey and
35
              * alters the url if it's incomplete
35
              * alters the url if it's incomplete
36
              */
36
              */
37
-            const alreadyLoggedIn = await currentProfile.isLoggedIn()
37
+            const alreadyLoggedIn = await currentProfile.isLoggedIn
38
             if(!alreadyLoggedIn) {
38
             if(!alreadyLoggedIn) {
39
                 await currentProfile.login(this.form.profileId)
39
                 await currentProfile.login(this.form.profileId)
40
             }
40
             }

+ 144
- 56
frontend/src/views/SurveyView.vue Visa fil

5
     article.match.w-full
5
     article.match.w-full
6
         ul.w-full
6
         ul.w-full
7
             template(v-for="(q, i) in profileQuestions" :key="q.response_key_prompt")
7
             template(v-for="(q, i) in profileQuestions" :key="q.response_key_prompt")
8
-                li(v-if="step == i")
8
+                li(v-if="step == i + 1")
9
                     p {{q.response_key_category}}: 
9
                     p {{q.response_key_category}}: 
10
                         span in db:
10
                         span in db:
11
                         span(v-if="q.response_key_id") true - id:{{q.response_key_id}} | 
11
                         span(v-if="q.response_key_id") true - id:{{q.response_key_id}} | 
18
                             button(
18
                             button(
19
                                 v-for="(res, index) in q.responses"
19
                                 v-for="(res, index) in q.responses"
20
                                 :key="index"
20
                                 :key="index"
21
-                                @click="profile[q.response_key_prompt] = res; step++"
22
-                                :disabled="profile[q.response_key_prompt] == res"
21
+                                @click="storeResponseLike(step, q.response_key_id, q.response_key_prompt, res); step++"
23
                             ).p-0 {{res}}
22
                             ).p-0 {{res}}
24
                         
23
                         
25
                         //- Fill in the blank
24
                         //- Fill in the blank
26
                         div(v-else-if="q.response_key_category === 'profile'")
25
                         div(v-else-if="q.response_key_category === 'profile'")
27
-                            input(v-model="profile[q.response_key_prompt]" @keyup.enter="step++") 
26
+                            input(@input="storeResponseLike(step, q.response_key_id, q.response_key_prompt, profile[q.response_key_prompt])" v-model="profile[q.response_key_prompt]" @keyup.enter="step++") 
28
                             label >{{ profile[q.response_key_prompt]}}
27
                             label >{{ profile[q.response_key_prompt]}}
29
                         
28
                         
30
-                        //- Questionaire
29
+                        //- Aspects
31
                         div(v-else).f-col
30
                         div(v-else).f-col
32
-                            input(type="range" min="-3" max="3" list="ticks" v-model="questionaire[q.response_key_category]").w-full
33
-                            label {{ questionaireResponses[parseInt(questionaire[q.response_key_category]) + 3] }} 
34
-                    nav.f-row
35
-                        button(:disabled="step == 0" @click="step--") back
36
-                        p {{step + 1}} of {{profileQuestions.length}}
37
-                        button(@click="step++") next
38
-                
31
+                            input(type="range" min="-3" max="3" list="ticks" @input="storeResponseLike(step, q.response_key_id, q.response_key_prompt, aspects[q.response_key_category])" v-model="aspects[q.response_key_category]").w-full
32
+                            label {{ aspectResponses[parseInt(aspects[q.response_key_category]) + 3] }} 
33
+                    
34
+                        nav.f-row
35
+                            button(:disabled="step == 0" @click="step--") back
36
+                            p {{step}} of {{profile.length}}
37
+                            button(
38
+                                v-if="(q.response_key_category === 'profile')"
39
+                                @click="storeResponseLike(step, q.response_key_id, q.response_key_prompt, profile[q.response_key_prompt]); step++"
40
+                            ) next
41
+                            button(
42
+                                v-else
43
+                                @click="storeResponseLike(step, q.response_key_id, q.response_key_prompt, aspects[q.response_key_category]); step++"
44
+                            ) next
45
+                    
39
             //- Confirmation
46
             //- Confirmation
40
-            li(v-if="step == profileQuestions.length")
47
+            li(v-if="step == profileQuestions.length + 1")
41
                 p Does this look correct?
48
                 p Does this look correct?
42
                 h4 {{ profile }}
49
                 h4 {{ profile }}
43
-                h4 {{questionaire}}
50
+                h4 {{aspects}}
44
                 nav.f-row
51
                 nav.f-row
45
                     button(@click="step--") back
52
                     button(@click="step--") back
46
-                    p(@click="step = 0").p-1 start over
47
-                    button(@click="$router.push({ name: 'HomeView' })") save
48
-
53
+                    p(@click="step = 1").p-1 start over
54
+                    button(@click="onSave") save
55
+                    //- button(@click="$router.push({ name: 'HomeView' })") save
56
+    footer
57
+        button(@click="bypass") +30 user profiles
49
 </template>
58
 </template>
50
 
59
 
51
 <script>
60
 <script>
52
-import { surveyFactory } from '@/utils'
53
-import { allSteps, allResponses, possible, stepToComponentMap } from '@/utils/lang'
54
-import { currentProfile } from '@/services'
61
+import { surveyFactory, randomSurveyResponses } from '@/utils'
62
+import { allSteps, aspectResponses, possible } from '@/utils/lang'
55
 
63
 
56
-import SurveyForm from '@/components/form.vue'
57
-import ButtonMulti from '@/components/form/button-multi.vue'
58
-import ButtonChoice from '@/components/form/button-choice.vue'
59
-import InputString from '@/components/form/input-string.vue'
64
+import { currentProfile, createProfileForUserId, fetchProfileByProfileId, scoreSurveyByProfileId, signupUser } from '@/services'
65
+
66
+import { maxDistanceKey } from '../../../backend/db/data-generator/config.json'
60
 
67
 
61
 export default {
68
 export default {
62
-    components: { SurveyForm, ButtonMulti, ButtonChoice, InputString },
63
     props: {
69
     props: {
64
         pid: {
70
         pid: {
65
             type: Number,
71
             type: Number,
68
     },
74
     },
69
     data() {
75
     data() {
70
         return {
76
         return {
77
+            bypassCount: 0,
71
             validSurvey: null,
78
             validSurvey: null,
72
-            componentMap: stepToComponentMap,
73
-            slideDuration: 1200,
74
-            step: 0,
75
-            profile: {
76
-                email: '',
77
-                name: '',
78
-                seeking: '',
79
-                urgency: '',
80
-                experience: '',
81
-                role: '',
82
-                duration: '',
83
-                distance: '',
84
-                position: '',
85
-                language: '',
86
-                pronouns: '',
87
-                zipcode: '',
88
-                image: '',
89
-                blurb: ''
90
-            },
91
-            questionaire: {
79
+            step: 1,
80
+            responseLikes: {},
81
+            profile: {},
82
+            aspects: {
92
                 grit: 0,
83
                 grit: 0,
93
                 openness: 0,
84
                 openness: 0,
94
                 bravery: 0,
85
                 bravery: 0,
96
                 honesty: 0,
87
                 honesty: 0,
97
                 respect: 0,
88
                 respect: 0,
98
             },
89
             },
99
-            questionaireResponses: Object.values(allResponses.usa.questionaire)
90
+            aspectResponses: Object.values(aspectResponses.usa)
100
         }
91
         }
101
     },
92
     },
102
     computed: {
93
     computed: {
119
         )
110
         )
120
     },
111
     },
121
     methods: {
112
     methods: {
113
+        // Just for testing quicker
114
+        bypass() {
115
+            const n = 30
116
+            const toGen = [...Array(n).keys()]
117
+            toGen.forEach(async () => {
118
+                const randRes = randomSurveyResponses(this.bypassCount)
119
+                randRes.forEach((tr, i) => {
120
+                    this.storeResponseLike(i, tr.idOrPrompt, tr.idOrPrompt, tr.val)
121
+                })
122
+                this.bypassCount++
123
+                await this.onSave()
124
+            })
125
+            this.step = 21
126
+            console.warn(`Auto-filling survey ${n} times...`)
127
+        },
128
+        storeResponseLike(step, id, prompt, val) {
129
+            // Also update the form model state for now
130
+            // We probably can refactor this out
131
+            this.profile[prompt] = val
132
+            
133
+            /**
134
+             * If NO response id is present, that means the answer
135
+             * is required and associated with a User and NOT a Profile
136
+             **/
137
+            this.responseLikes[`step-${step}`] = {
138
+                idOrPrompt: id ? id : prompt,
139
+                val: val.toString()
140
+            }
141
+        },
142
+        _formatIntoResponses(survey) {
143
+            return survey.map(resLike => {
144
+                resLike.response_key_id = resLike.idOrPrompt
145
+                delete resLike.idOrPrompt
146
+                return resLike
147
+            })
148
+        },
122
         /**
149
         /**
123
-         * Set state from promptVal key:value object
124
-         * from button-coice or button-multi
150
+         * Survey responses have User information
151
+         * mixed in with scoring information so we 
152
+         * need to separate them
153
+         * @param {array} responseLikes answered by the survey
125
          */
154
          */
126
-        onButtonSelect(promptVal) {
127
-            for (const [prompt, val] of Object.entries(promptVal)) {
128
-                this[prompt] = val
129
-                if (prompt == 'seeking') {
130
-                    this.validSurvey.setRoleResponses(val)
155
+        _separateUserInfoFromResponses(responseLikes) {
156
+            let survey = []
157
+            const user = {}
158
+            responseLikes.forEach(resLike => {
159
+                if(resLike.idOrPrompt && Number.isFinite(parseInt(resLike.idOrPrompt))) {
160
+                    survey.push(resLike)
161
+                } else {
162
+                    user[resLike.idOrPrompt] = resLike.val
131
                 }
163
                 }
164
+            })
165
+            return [user, this._formatIntoResponses(survey)]
166
+        },
167
+        async _createUserProfileRel(user, survey) {
168
+            /** A User is required before creating a profile */
169
+            const createdUser = await signupUser(user)
170
+            if(!createdUser) return
171
+
172
+            const userProfileRel = await createProfileForUserId(createdUser.user_id, survey)
173
+            if(!userProfileRel) return 
174
+
175
+            return userProfileRel
176
+        },
177
+        async _getProfileWithScore(createdProfileId, maxDistance) {
178
+            /** A Profile is associated with n:1 user and referenced by profile id */
179
+            const fetchedProfile = await fetchProfileByProfileId(createdProfileId)
180
+            if(!fetchedProfile) {
181
+                console.error(`Could not fetch Profile ${createdProfileId}. Please check that a User and Profile were created.`)
182
+                return
183
+            }
184
+
185
+            /** Use profile answers to compare against other profile answers */
186
+            const scored = await scoreSurveyByProfileId(createdProfileId, maxDistance)
187
+            if(!scored) {
188
+                console.error(`Could not score Profile ${createdProfileId}.`)
189
+                return
132
             }
190
             }
133
-            this.step++
191
+            return fetchedProfile
134
         },
192
         },
135
-        onInputChange(prompt, val) {
136
-            this[prompt] = val
193
+        async _setLoginForProfile(profile) {
194
+            const currentId = currentProfile.login(profile.profile_id)
195
+            if(currentId && profile.responses.length) {
196
+                // Stores responses without fetching again
197
+                currentProfile.setResponses(profile.responses)
198
+            }
199
+            if(!currentProfile.isComplete) {
200
+                console.error(`Profile ${currentProfile.id} is incomplete. Please make sure all survey questions have been answered.`)
201
+                return
202
+            }
203
+        },
204
+        async onSave() {
205
+            const [ user, survey] = this._separateUserInfoFromResponses(Object.values(this.responseLikes))
206
+            const maxDistanceRes = survey.find(res => res.response_key_id == maxDistanceKey)
207
+
208
+            /**
209
+             * Creating a profile only returns the created
210
+             * user id and profile id
211
+             **/
212
+            const userProfileRel = await this._createUserProfileRel(user, survey)
213
+            const createdProfileId = userProfileRel.profile_id
214
+
215
+            /** A Profile is associated with n:1 user and referenced by profile id */
216
+            const fetchedProfile = await this._getProfileWithScore(createdProfileId, maxDistanceRes.val)
217
+            
218
+            /** 
219
+             * Login only after there is a user and
220
+             * that user has a profile and all responses
221
+            */
222
+            this._setLoginForProfile(fetchedProfile)
223
+            
224
+            this.$router.push({ name: 'HomeView' })
137
         },
225
         },
138
         back(prompt) {
226
         back(prompt) {
139
             this[prompt] = ''
227
             this[prompt] = ''

Laddar…
Avbryt
Spara