added a lot of auth code and resolved merge conflicht
This commit is contained in:
21
package-lock.json
generated
21
package-lock.json
generated
@@ -2392,6 +2392,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||||
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
||||||
},
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||||
|
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.3.0",
|
||||||
|
"is-buffer": "^1.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-code-frame": {
|
"babel-code-frame": {
|
||||||
"version": "6.26.0",
|
"version": "6.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||||
@@ -5515,7 +5524,6 @@
|
|||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
|
||||||
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
|
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^3.2.6"
|
"debug": "^3.2.6"
|
||||||
},
|
},
|
||||||
@@ -5524,7 +5532,6 @@
|
|||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
@@ -12830,6 +12837,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.8.tgz",
|
||||||
"integrity": "sha512-+vp9lEC2Kt3yom673pzg1J7T1NVGuGzO9j8Wxno+rQN2WYVBX2pyo/RGQ3fXCLh2Pk76Skw/laAPCuBuEQ4diw=="
|
"integrity": "sha512-+vp9lEC2Kt3yom673pzg1J7T1NVGuGzO9j8Wxno+rQN2WYVBX2pyo/RGQ3fXCLh2Pk76Skw/laAPCuBuEQ4diw=="
|
||||||
},
|
},
|
||||||
|
"vue-axios": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-DS8Q+WFT3i7nS0aZ/NMmTPf2yhbtlXhj4QEZmY69au/BshsGzGjC6dXaniZaPQlErP3J3Sv1HtQ4RVrXaUTkxA=="
|
||||||
|
},
|
||||||
"vue-class-component": {
|
"vue-class-component": {
|
||||||
"version": "6.3.2",
|
"version": "6.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-6.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-6.3.2.tgz",
|
||||||
@@ -12950,6 +12962,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.0.tgz",
|
||||||
"integrity": "sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg=="
|
"integrity": "sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg=="
|
||||||
},
|
},
|
||||||
|
"vuex-typex": {
|
||||||
|
"version": "3.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vuex-typex/-/vuex-typex-3.1.4.tgz",
|
||||||
|
"integrity": "sha512-MXW5QBk8Cq01zP5/5cKLp8QQmC5Zp2m/mSZlWLWmmeF6gGT7d1RpQ2zxc0rT4JQkLOYLdZees+yvgjivtBZDLg=="
|
||||||
|
},
|
||||||
"w3c-hr-time": {
|
"w3c-hr-time": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
||||||
|
|||||||
@@ -10,14 +10,17 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/cli": "^3.4.1",
|
"@vue/cli": "^3.4.1",
|
||||||
|
"axios": "^0.18.0",
|
||||||
"bootstrap-vue": "^2.0.0-rc.13",
|
"bootstrap-vue": "^2.0.0-rc.13",
|
||||||
"vue": "^2.6.6",
|
"vue": "^2.6.6",
|
||||||
|
"vue-axios": "^2.1.4",
|
||||||
"vue-class-component": "^6.0.0",
|
"vue-class-component": "^6.0.0",
|
||||||
"vue-flag-icon": "^1.0.6",
|
"vue-flag-icon": "^1.0.6",
|
||||||
"vue-i18n": "^8.9.0",
|
"vue-i18n": "^8.9.0",
|
||||||
"vue-property-decorator": "^7.0.0",
|
"vue-property-decorator": "^7.0.0",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vuex": "^3.0.1"
|
"vuex": "^3.0.1",
|
||||||
|
"vuex-typex": "^3.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.1.0",
|
"@types/chai": "^4.1.0",
|
||||||
|
|||||||
31
src/api/index.ts
Normal file
31
src/api/index.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = 'http://localhost:5443/api';
|
||||||
|
|
||||||
|
export function fetchSurveys () {
|
||||||
|
return axios.get(`${API_URL}/surveys/`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchSurvey (surveyId: string) {
|
||||||
|
return axios.get(`${API_URL}/surveys/${surveyId}/`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveSurveyResponse (surveyResponse: any) {
|
||||||
|
return axios.put(`${API_URL}/surveys/${surveyResponse.id}/`, surveyResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postNewSurvey (survey: any, jwt: any) {
|
||||||
|
return axios.post(`${API_URL}/surveys/`, survey, { headers: { Authorization: `Bearer: ${jwt}` } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authenticate (userData: any) {
|
||||||
|
return axios.post(`${API_URL}/auth/login`, userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register (userData: any) {
|
||||||
|
return axios.post(`${API_URL}/auth/register`, userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProviders (providers: any) {
|
||||||
|
return axios.get(`${API_URL}/auth/providers`, providers)
|
||||||
|
}
|
||||||
79
src/components/Login.vue
Normal file
79
src/components/Login.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<!-- components/Login.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Login or Register</h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="email">Email:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="email" class="input is-large" id="email" v-model="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="password">Password:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="password" class="input is-large" id="password" v-model="password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-large is-primary" @click="authenticate">Login</a>
|
||||||
|
<a class="button is-large is-success" @click="register">Register</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { EventBus } from '@/utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
errorMsg: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate () {
|
||||||
|
this.$store.dispatch('login', { email: this.email, password: this.password })
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
},
|
||||||
|
register () {
|
||||||
|
this.$store.dispatch('register', { email: this.email, password: this.password })
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
EventBus.$on('failedRegistering', (msg) => {
|
||||||
|
this.errorMsg = msg
|
||||||
|
})
|
||||||
|
EventBus.$on('failedAuthentication', (msg) => {
|
||||||
|
this.errorMsg = msg
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
EventBus.$off('failedRegistering')
|
||||||
|
EventBus.$off('failedAuthentication')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
75
src/components/NewQuestion.vue
Normal file
75
src/components/NewQuestion.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large">Question</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="text" class="input is-large" v-model="question">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-large is-info" @click="addChoice">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-plus-square-o fa-align-left" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
<span>Add choice</span>
|
||||||
|
</a>
|
||||||
|
<a class="button is-large is-primary" @click="saveQuestion">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
<span>Save</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="label is-large" v-show="choices.length > 0">Question Choices</h2>
|
||||||
|
<div class="field has-addons" v-for="(choice, idx) in choices" v-bind:key="idx">
|
||||||
|
<div class="control choice">
|
||||||
|
<input type="text" class="input is-large" v-model="choices[idx]">
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-large">
|
||||||
|
<span class="icon is-small" @click.stop="removeChoice(choice)">
|
||||||
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
question: '',
|
||||||
|
choices: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeChoice (choice) {
|
||||||
|
const idx = this.choices.findIndex(c => c === choice)
|
||||||
|
this.choices.splice(idx, 1)
|
||||||
|
},
|
||||||
|
saveQuestion () {
|
||||||
|
this.$emit('questionComplete', {
|
||||||
|
question: this.question,
|
||||||
|
choices: this.choices.filter(c => !!c)
|
||||||
|
})
|
||||||
|
this.question = ''
|
||||||
|
this.choices = ['']
|
||||||
|
},
|
||||||
|
addChoice () {
|
||||||
|
this.choices.push('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.choice {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
122
src/components/NewSurvey.vue
Normal file
122
src/components/NewSurvey.vue
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">{{ name }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="tabs is-centered is-fullwidth is-large">
|
||||||
|
<ul>
|
||||||
|
<li :class="{'is-active': step == 'name'}" @click="step = 'name'">
|
||||||
|
<a>Name</a>
|
||||||
|
</li>
|
||||||
|
<li :class="{'is-active': step == 'questions'}" @click="step = 'questions'">
|
||||||
|
<a>Questions</a>
|
||||||
|
</li>
|
||||||
|
<li :class="{'is-active': step == 'review'}" @click="step = 'review'">
|
||||||
|
<a>Review</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-half is-offset-one-quarter">
|
||||||
|
|
||||||
|
<div class="name" v-show="step === 'name'">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="name">Survey name:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="text" class="input is-large" id="name" v-model="name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="questions" v-show="step === 'questions'">
|
||||||
|
<new-question v-on:questionComplete="appendQuestion"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="review" v-show="step === 'review'">
|
||||||
|
<ul>
|
||||||
|
<li class="question" v-for="(question, qIdx) in questions" :key="`question-${qIdx}`">
|
||||||
|
<div class="title">
|
||||||
|
{{ question.question }}
|
||||||
|
<span class="icon is-medium is-pulled-right delete-question"
|
||||||
|
@click.stop="removeQuestion(question)">
|
||||||
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(choice , cIdx) in question.choices" :key="`choice-${cIdx}`">
|
||||||
|
{{ cIdx + 1 }}. {{ choice }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-large is-primary" @click="submitSurvey">Submit</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NewQuestion from '@/components/NewQuestion'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { NewQuestion },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
step: 'name',
|
||||||
|
name: '',
|
||||||
|
questions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
appendQuestion (newQuestion) {
|
||||||
|
this.questions.push(newQuestion)
|
||||||
|
},
|
||||||
|
removeQuestion (question) {
|
||||||
|
const idx = this.questions.findIndex(q => q.question === question.question)
|
||||||
|
this.questions.splice(idx, 1)
|
||||||
|
},
|
||||||
|
submitSurvey () {
|
||||||
|
this.$store.dispatch('submitNewSurvey', {
|
||||||
|
name: this.name,
|
||||||
|
questions: this.questions
|
||||||
|
})
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('Error creating survey', error)
|
||||||
|
this.$router.push('/')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.question {
|
||||||
|
margin: 10px 20px 25px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-question {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-question:hover {
|
||||||
|
background-color: lightgray;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
115
src/components/Survey.vue
Normal file
115
src/components/Survey.vue
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">{{ survey.name }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-10 is-offset-1">
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(question, idx) in survey.questions"
|
||||||
|
v-bind:key="question.id"
|
||||||
|
v-show="currentQuestion === idx">
|
||||||
|
|
||||||
|
<div class="column is-offset-3 is-6">
|
||||||
|
<h4 class='title has-text-centered'>{{ question.text }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="column is-offset-4 is-4">
|
||||||
|
<div class="control">
|
||||||
|
<div v-for="choice in question.choices" v-bind:key="choice.id">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" v-model="question.choice" name="choice" :value="choice.id">
|
||||||
|
{{ choice.text }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-offset-one-quarter is-half">
|
||||||
|
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
|
||||||
|
<a class="pagination-previous" @click.stop="goToPreviousQuestion"><i class="fa fa-chevron-left" aria-hidden="true"></i> Back</a>
|
||||||
|
<a class="pagination-next" @click.stop="goToNextQuestion">Next <i class="fa fa-chevron-right" aria-hidden="true"></i></a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<a v-show="surveyComplete" class='button is-large is-focused is-primary' @click="handleSubmit">Submit</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
currentQuestion: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount () {
|
||||||
|
this.$store.dispatch('loadSurvey', { id: parseInt(this.$route.params.id) })
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToNextQuestion () {
|
||||||
|
if (this.currentQuestion === this.survey.questions.length - 1) {
|
||||||
|
this.currentQuestion = 0
|
||||||
|
} else {
|
||||||
|
this.currentQuestion++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goToPreviousQuestion () {
|
||||||
|
if (this.currentQuestion === 0) {
|
||||||
|
this.currentQuestion = this.survey.questions.lenth - 1
|
||||||
|
} else {
|
||||||
|
this.currentQuestion--
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit () {
|
||||||
|
this.$store.dispatch('addSurveyResponse')
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
surveyComplete () {
|
||||||
|
if (this.survey.questions) {
|
||||||
|
const numQuestions = this.survey.questions.length
|
||||||
|
const numCompleted = this.survey.questions.filter(q => q.choice).length
|
||||||
|
return numQuestions === numCompleted
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
survey () {
|
||||||
|
return this.$store.state.currentSurvey
|
||||||
|
},
|
||||||
|
selectedChoice: {
|
||||||
|
get () {
|
||||||
|
const question = this.survey.questions[this.currentQuestion]
|
||||||
|
return question.choice
|
||||||
|
},
|
||||||
|
set (value) {
|
||||||
|
const question = this.survey.questions[this.currentQuestion]
|
||||||
|
this.$store.commit('setChoice', { questionId: question.id, choice: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import axios from 'axios'
|
||||||
|
import VueAxios from 'vue-axios'
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
@@ -8,6 +10,7 @@ import FlagIcon from 'vue-flag-icon';
|
|||||||
// following is to avoid missing type definitions
|
// following is to avoid missing type definitions
|
||||||
// const FlagIcon = require('vue-flag-icon');
|
// const FlagIcon = require('vue-flag-icon');
|
||||||
|
|
||||||
|
Vue.use(VueAxios, axios)
|
||||||
Vue.use(FlagIcon);
|
Vue.use(FlagIcon);
|
||||||
|
|
||||||
// setup fake backend
|
// setup fake backend
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import NotFound from './views/NotFound.vue';
|
|||||||
import LoginPage from './views/LoginPage.vue';
|
import LoginPage from './views/LoginPage.vue';
|
||||||
|
|
||||||
|
|
||||||
|
import Survey from '@/components/Survey.vue';
|
||||||
|
import NewSurvey from '@/components/NewSurvey.vue';
|
||||||
|
import Login from '@/components/Login.vue';
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
export const router = new Router({
|
export const router = new Router({
|
||||||
@@ -28,6 +33,25 @@ export const router = new Router({
|
|||||||
// this generates a separate chunk (about.[hash].js) for this route
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
|
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
|
||||||
|
}, {
|
||||||
|
path: '/surveys/:id',
|
||||||
|
name: 'Survey',
|
||||||
|
component: Survey
|
||||||
|
}, {
|
||||||
|
path: '/surveys',
|
||||||
|
name: 'NewSurvey',
|
||||||
|
component: NewSurvey,
|
||||||
|
beforeEnter (to, from, next) {
|
||||||
|
if (!store.getters.isAuthenticated) {
|
||||||
|
next('/login')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: Login
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
|
|||||||
111
src/store.ts
111
src/store.ts
@@ -1,25 +1,110 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
|
||||||
import { alert } from './store/alert.module';
|
|
||||||
import { account } from './store/account.module';
|
// imports of AJAX functions will go here
|
||||||
import { users } from './store/users.module';
|
import { fetchSurveys, fetchSurvey, saveSurveyResponse, postNewSurvey, authenticate, register } from '@/api'
|
||||||
|
import { isValidJwt, EventBus } from '@/utils'
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
export default new Vuex.Store({
|
const state = {
|
||||||
modules: {
|
// single source of data
|
||||||
alert,
|
surveys: [],
|
||||||
account,
|
currentSurvey: {},
|
||||||
users,
|
user: {},
|
||||||
},
|
jwt: ''
|
||||||
state: {
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
// asynchronous operations
|
||||||
|
loadSurveys (context: any) {
|
||||||
|
return fetchSurveys()
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setSurveys', { surveys: response.data })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
loadSurvey (context: any, { id }) {
|
||||||
|
return fetchSurvey(id)
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setSurvey', { survey: response.data })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addSurveyResponse (context: any) {
|
||||||
|
return saveSurveyResponse(context.state.currentSurvey)
|
||||||
},
|
},
|
||||||
mutations: {
|
|
||||||
|
|
||||||
|
login (context: any, userData: any) {
|
||||||
|
context.commit('setUserData', { userData })
|
||||||
|
return authenticate(userData)
|
||||||
|
.then(response => context.commit('setJwtToken', { jwt: response.data }))
|
||||||
|
.catch(error => {
|
||||||
|
console.log('Error Authenticating: ', error)
|
||||||
|
// @ts-ignore
|
||||||
|
EventBus.emit('failedAuthentication', error)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
actions: {
|
register (context: any, userData: any) {
|
||||||
|
context.commit('setUserData', { userData })
|
||||||
|
return register(userData)
|
||||||
|
.then(context.dispatch('login', userData))
|
||||||
|
.catch(error => {
|
||||||
|
console.log('Error Registering: ', error)
|
||||||
|
// @ts-ignore
|
||||||
|
EventBus.emit('failedRegistering: ', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitNewSurvey (context: any, survey: any) {
|
||||||
|
return postNewSurvey(survey, context.state.jwt.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
// isolated data mutations
|
||||||
|
setSurveys (state: any, payload: any) {
|
||||||
|
state.surveys = payload.surveys
|
||||||
},
|
},
|
||||||
});
|
setSurvey (state: any, payload: any) {
|
||||||
|
const nQuestions = payload.survey.questions.length
|
||||||
|
for (let i = 0; i < nQuestions; i++) {
|
||||||
|
payload.survey.questions[i].choice = null
|
||||||
|
}
|
||||||
|
state.currentSurvey = payload.survey
|
||||||
|
},
|
||||||
|
setChoice (state: any, payload: any) {
|
||||||
|
const { questionId, choice } = payload
|
||||||
|
const nQuestions = state.currentSurvey.questions.length
|
||||||
|
for (let i = 0; i < nQuestions; i++) {
|
||||||
|
if (state.currentSurvey.questions[i].id === questionId) {
|
||||||
|
state.currentSurvey.questions[i].choice = choice
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setUserData (state: any, payload: any) {
|
||||||
|
console.log('setUserData payload = ', payload)
|
||||||
|
state.userData = payload.userData
|
||||||
|
},
|
||||||
|
setJwtToken (state: any, payload: any) {
|
||||||
|
console.log('setJwtToken payload = ', payload)
|
||||||
|
localStorage.token = payload.jwt.token
|
||||||
|
state.jwt = payload.jwt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
// reusable data accessors
|
||||||
|
isAuthenticated (state: any) {
|
||||||
|
return isValidJwt(state.jwt.token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
getters
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
||||||
|
|||||||
15
src/utils/index.ts
Normal file
15
src/utils/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// utils/index.js
|
||||||
|
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
export const EventBus = new Vue();
|
||||||
|
|
||||||
|
export function isValidJwt (jwt: any) {
|
||||||
|
if (!jwt || jwt.split('.').length < 3) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const data = JSON.parse(atob(jwt.split('.')[1]));
|
||||||
|
const exp = new Date(data.exp * 1000);
|
||||||
|
const now = new Date();
|
||||||
|
return now < exp;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user