diff --git a/package-lock.json b/package-lock.json index 218be7d..2cba43c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2392,6 +2392,15 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "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": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -5515,7 +5524,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", - "dev": true, "requires": { "debug": "^3.2.6" }, @@ -5524,7 +5532,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, "requires": { "ms": "^2.1.1" } @@ -12830,6 +12837,11 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.8.tgz", "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": { "version": "6.3.2", "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", "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": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", diff --git a/package.json b/package.json index 3856dce..b1f3027 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,17 @@ }, "dependencies": { "@vue/cli": "^3.4.1", + "axios": "^0.18.0", "bootstrap-vue": "^2.0.0-rc.13", "vue": "^2.6.6", + "vue-axios": "^2.1.4", "vue-class-component": "^6.0.0", "vue-flag-icon": "^1.0.6", "vue-i18n": "^8.9.0", "vue-property-decorator": "^7.0.0", "vue-router": "^3.0.1", - "vuex": "^3.0.1" + "vuex": "^3.0.1", + "vuex-typex": "^3.1.4" }, "devDependencies": { "@types/chai": "^4.1.0", diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..f8becc2 --- /dev/null +++ b/src/api/index.ts @@ -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) +} \ No newline at end of file diff --git a/src/components/Login.vue b/src/components/Login.vue new file mode 100644 index 0000000..5b91993 --- /dev/null +++ b/src/components/Login.vue @@ -0,0 +1,79 @@ + + + + + + + Login or Register + {{ errorMsg }} + + + + + + + Email: + + + + + + Password: + + + + + + + Login + Register + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/NewQuestion.vue b/src/components/NewQuestion.vue new file mode 100644 index 0000000..03031a9 --- /dev/null +++ b/src/components/NewQuestion.vue @@ -0,0 +1,75 @@ + + + + Question + + + + + + + + + + + + Add choice + + + + + + Save + + + + + Question Choices + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/NewSurvey.vue b/src/components/NewSurvey.vue new file mode 100644 index 0000000..e1a75f6 --- /dev/null +++ b/src/components/NewSurvey.vue @@ -0,0 +1,122 @@ + + + + + + {{ name }} + + + + + + + + + + Name + + + Questions + + + Review + + + + + + + + + Survey name: + + + + + + + + + + + + + + + {{ question.question }} + + + + + + + {{ cIdx + 1 }}. {{ choice }} + + + + + + + Submit + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Survey.vue b/src/components/Survey.vue new file mode 100644 index 0000000..58fd823 --- /dev/null +++ b/src/components/Survey.vue @@ -0,0 +1,115 @@ + + + + + + {{ survey.name }} + + + + + + + + + + + + + + {{ question.text }} + + + + + + + {{ choice.text }} + + + + + + + + + + Back + Next + + + + + Submit + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 1c3829c..f929369 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,6 @@ import Vue from 'vue'; +import axios from 'axios' +import VueAxios from 'vue-axios' import App from './App.vue'; import router from './router'; import store from './store'; @@ -8,6 +10,7 @@ import FlagIcon from 'vue-flag-icon'; // following is to avoid missing type definitions // const FlagIcon = require('vue-flag-icon'); +Vue.use(VueAxios, axios) Vue.use(FlagIcon); // setup fake backend diff --git a/src/router.ts b/src/router.ts index 25a570c..ca00a94 100644 --- a/src/router.ts +++ b/src/router.ts @@ -5,6 +5,11 @@ import NotFound from './views/NotFound.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); 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 // which is lazy-loaded when the route is visited. 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: '*', diff --git a/src/store.ts b/src/store.ts index 51b3506..0e35c08 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,25 +1,110 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import { alert } from './store/alert.module'; -import { account } from './store/account.module'; -import { users } from './store/users.module'; + +// imports of AJAX functions will go here +import { fetchSurveys, fetchSurvey, saveSurveyResponse, postNewSurvey, authenticate, register } from '@/api' +import { isValidJwt, EventBus } from '@/utils' Vue.use(Vuex); -export default new Vuex.Store({ - modules: { - alert, - account, - users, - }, - state: { +const state = { + // single source of data + surveys: [], + currentSurvey: {}, + user: {}, + jwt: '' +} +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 diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..33d555b --- /dev/null +++ b/src/utils/index.ts @@ -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; +} \ No newline at end of file
{{ errorMsg }}