├── src ├── features │ ├── login │ │ ├── store.js │ │ ├── service.js │ │ └── main.vue │ ├── account │ │ ├── store.js │ │ ├── service.js │ │ ├── components │ │ │ ├── address-edit.vue │ │ │ └── countries.js │ │ └── main.vue │ ├── dashboard │ │ ├── service.js │ │ ├── test.css │ │ ├── store.js │ │ ├── components │ │ │ ├── line-chart.vue │ │ │ └── chart.vue │ │ └── main.vue │ ├── wip │ │ └── main.vue │ ├── tutorial │ │ └── main.vue │ ├── billing │ │ └── main.vue │ └── premium │ │ └── main.vue ├── styles │ ├── scss │ │ ├── _vendor.scss │ │ ├── _variables.scss │ │ └── main.scss │ └── stylus │ │ ├── 2-tools │ │ └── 2-tools.styl │ │ ├── 7-utils │ │ └── 7-utils.styl │ │ ├── 3-generic │ │ └── 3-generic.styl │ │ ├── 5-objects │ │ └── 5-objects.styl │ │ ├── 4-elements │ │ └── 4-elements.styl │ │ ├── 6-components │ │ ├── 6-components.styl │ │ └── vendor.styl │ │ ├── 1-settings │ │ ├── vendor.styl │ │ ├── 1-settings.styl │ │ └── variables.styl │ │ └── main.styl ├── assets │ ├── logo.png │ └── images │ │ ├── logo.png │ │ ├── backdrop.jpg │ │ ├── profile.jpg │ │ ├── traffic-cone.svg │ │ ├── oops.svg │ │ ├── logo-name.svg │ │ └── logo.svg ├── http │ ├── index.js │ ├── router.js │ └── routes.js ├── layouts │ ├── public │ │ └── main.vue │ └── default │ │ └── main.vue ├── store │ ├── index.js │ ├── plugins.js │ └── common.js ├── constants │ └── index.js ├── components │ ├── app-footer.vue │ ├── app-snackbar.vue │ ├── app-dialog.vue │ ├── Hello.vue │ ├── app-bar.vue │ ├── loading.vue │ └── app-sidebar.vue ├── auth │ ├── store.js │ ├── index.js │ └── helpers.js ├── app.vue └── main.js ├── .eslintignore ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── static ├── images │ └── mountains.png ├── img │ └── icons │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── msapplication-icon-144x144.png │ │ └── safari-pinned-tab.svg └── manifest.json ├── .editorconfig ├── .postcssrc.js ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── .babelrc ├── test └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── runner.js │ └── nightwatch.conf.js ├── .eslintrc.js ├── README.md ├── index.html └── package.json /src/features/login/store.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/scss/_vendor.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/account/store.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/dashboard/service.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/features/login/service.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/stylus/2-tools/2-tools.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/stylus/7-utils/7-utils.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/stylus/3-generic/3-generic.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/stylus/5-objects/5-objects.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/stylus/4-elements/4-elements.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /src/styles/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | @import '_vendor'; -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /src/features/dashboard/test.css: -------------------------------------------------------------------------------- 1 | .test { 2 | background-color: red; 3 | } -------------------------------------------------------------------------------- /src/styles/stylus/6-components/6-components.styl: -------------------------------------------------------------------------------- 1 | @import 'vendor.styl' 2 | -------------------------------------------------------------------------------- /src/styles/stylus/1-settings/vendor.styl: -------------------------------------------------------------------------------- 1 | $color-pack = false // Let's save 30k 2 | -------------------------------------------------------------------------------- /src/styles/stylus/1-settings/1-settings.styl: -------------------------------------------------------------------------------- 1 | @import 'vendor' 2 | @import 'variables' 3 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /static/images/mountains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/images/mountains.png -------------------------------------------------------------------------------- /src/assets/images/backdrop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/src/assets/images/backdrop.jpg -------------------------------------------------------------------------------- /src/assets/images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/src/assets/images/profile.jpg -------------------------------------------------------------------------------- /static/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/favicon.ico -------------------------------------------------------------------------------- /static/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /static/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /static/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/styles/stylus/6-components/vendor.styl: -------------------------------------------------------------------------------- 1 | @import '~vuetify/src/stylus/main' 2 | 3 | .application.theme--light 4 | background: #FFFFFF -------------------------------------------------------------------------------- /static/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /static/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /static/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prograhammer/vue-pizza/HEAD/static/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const merge = require('webpack-merge') 4 | const devEnv = require('./dev.env') 5 | 6 | module.exports = merge(devEnv, { 7 | NODE_ENV: '"testing"' 8 | }) 9 | -------------------------------------------------------------------------------- /src/styles/stylus/1-settings/variables.styl: -------------------------------------------------------------------------------- 1 | $app-primary = #21CE99 2 | $app-secondary = #D81B60 3 | $app-accent = #805441 4 | $app-success = #61B865 5 | $app-light-grey = #E1E2E1 6 | $app-error = #FF6666 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const merge = require('webpack-merge') 4 | const prodEnv = require('./prod.env') 5 | 6 | module.exports = merge(prodEnv, { 7 | NODE_ENV: '"development"' 8 | }) 9 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/http/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import router from './router' 3 | 4 | export const http = { 5 | install (Vue, options) { 6 | Vue.prototype.$http = Vue.http = axios.create() 7 | } 8 | } 9 | 10 | export { router } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/e2e/reports 8 | selenium-debug.log 9 | package-lock.json 10 | 11 | # Editor directories and files 12 | .idea 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /src/layouts/public/main.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import auth from '@/auth/store' 4 | import common from './common' 5 | import { localStoragePlugin } from './plugins' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | modules: { common, auth }, 11 | plugins: [localStoragePlugin] 12 | }) 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 6 | "dbaeumer.vscode-eslint", 7 | "sysoev.language-stylus", 8 | "octref.vetur" 9 | ] 10 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": [ "istanbul" ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/stylus/main.styl: -------------------------------------------------------------------------------- 1 | // Follows ITCSS: https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/ 2 | // Note: You may need to add a /vendor folder to some of these folders for working 3 | // with vendor styles. Be careful to add them in the order you need. 4 | @import '1-settings' 5 | @import '2-tools' 6 | @import '3-generic' 7 | @import '4-elements' 8 | @import '5-objects' 9 | @import '6-components' 10 | @import '7-utils' -------------------------------------------------------------------------------- /src/features/dashboard/store.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | store.registerModule('dashboard', { 4 | namespaced: true, 5 | 6 | // State loaded when this component is first loaded. 7 | state: { 8 | test: 0 9 | }, 10 | 11 | mutations: { 12 | updateTest (state, newVal) { 13 | state.test = newVal 14 | } 15 | }, 16 | 17 | actions: { 18 | updateTest ({ state, commit, rootState, dispatch }, newVal) { 19 | console.log(newVal) 20 | commit('updateTest', newVal) 21 | } 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | // Testing 2 | export const DEBUG = true 3 | 4 | // Backend API endpoints 5 | export const API_BASE_URL = '/api' 6 | export const REFRESH_TOKEN_URL = '/auth' 7 | 8 | /** 9 | * Key for local storage. 10 | * 11 | * Set the key to use in local storage to hold persistant data. If logged in, 12 | * you can see this key by going to Chrome > dev tools > application tab, 13 | * then choosing "Local Storage" and "http://localhost:8080". 14 | * 15 | * @type {string} 16 | */ 17 | export const STORAGE_KEY = 'vue-pizza' 18 | -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-pizza", 3 | "short_name": "Vue Pizza", 4 | "icons": [ 5 | { 6 | "src": "/app/static/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/app/static/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/app/index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /src/components/app-footer.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /src/features/dashboard/components/line-chart.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js PWA') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/features/dashboard/components/chart.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/features/wip/main.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 28 | 29 | 39 | -------------------------------------------------------------------------------- /src/auth/store.js: -------------------------------------------------------------------------------- 1 | const defaults = { 2 | isLoggedIn: false, 3 | accessToken: null, 4 | refreshToken: null, 5 | user: { 6 | name: '', 7 | id: '' 8 | } 9 | } 10 | 11 | const auth = { 12 | namespaced: true, 13 | 14 | state: Object.assign({}, defaults), 15 | 16 | mutations: { 17 | update (state, data) { 18 | state = Object.assign({}, defaults, data) 19 | }, 20 | clear (state) { 21 | state = Object.assign(state, defaults) 22 | } 23 | }, 24 | 25 | actions: { 26 | clear ({ state, commit, rootState, dispatch }) { 27 | commit('clear') 28 | }, 29 | update ({ state, commit, rootState }, data) { 30 | commit('update', data) 31 | } 32 | } 33 | } 34 | 35 | export default auth 36 | -------------------------------------------------------------------------------- /src/store/plugins.js: -------------------------------------------------------------------------------- 1 | import auth from '@/auth/store' 2 | import * as constants from '@/constants' 3 | 4 | // Sync with local storage. 5 | if (localStorage.getItem(constants.STORAGE_KEY)) { 6 | const syncedState = JSON.parse(localStorage.getItem(constants.STORAGE_KEY)) 7 | auth.state = Object.assign(auth.state, syncedState.auth) 8 | } 9 | 10 | // LocalStorage plugin. 11 | const localStoragePlugin = store => { 12 | store.subscribe((mutation, state) => { 13 | const syncedData = { auth: state.auth } 14 | 15 | localStorage.setItem(constants.STORAGE_KEY, JSON.stringify(syncedData)) 16 | 17 | if (mutation.type === 'common/clear') { 18 | localStorage.removeItem(constants.STORAGE_KEY) 19 | } 20 | }) 21 | } 22 | 23 | export { localStoragePlugin } 24 | -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 38 | -------------------------------------------------------------------------------- /src/layouts/default/main.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/components/app-snackbar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/components/app-dialog.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /src/features/tutorial/main.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | 36 | 46 | -------------------------------------------------------------------------------- /src/assets/images/traffic-cone.svg: -------------------------------------------------------------------------------- 1 | Created by Hea Poh Linfrom the Noun Project -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint', 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | jest: true 12 | }, 13 | globals: { 14 | utils: true 15 | }, 16 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 17 | // https://github.com/vuejs/eslint-plugin-vue 18 | extends: [ 19 | 'standard', 20 | 'plugin:vue/recommended' 21 | ], 22 | // required to lint *.vue files 23 | plugins: [ 24 | 'vue' 25 | ], 26 | // add your custom rules here 27 | 'rules': { 28 | // allow paren-less arrow functions 29 | 'arrow-parens': 0, 30 | // allow async-await 31 | 'generator-star-spacing': 0, 32 | // allow debugger during development 33 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :pizza: Vue Pizza 2 | 3 | > A tasty Vue.js starter project. Clone, remove `vue-pizza` everywhere, and add your own features. Also, read through the wiki. 4 | 5 | ## Demo 6 | 7 | [https://vue.pizza/app](https://vue.pizza/app) 8 | 9 | ## Why? 10 | 11 | A lot of starter projects or examples of Vue.js in the wild were: 12 | 13 | - Outdated. 14 | - No tutorial or wiki. 15 | - Involved unneccesary, complex, server-side-rendering/NUXT setups (this project is for authenticated, no SEO needed, static/cacheable apps that communicate to a backend server API). 16 | - Not comprehensive and didn't cover most of the elements necessary for real world apps. 17 | - This project was started from the latest official Vue-cli PWA template and built up from there. 18 | 19 | ## Wiki 20 | 21 | Visit the Github wiki tab [here](https://github.com/prograhammer/vue-pizza/wiki) to learn everything you need to know about 22 | how this starter project was built (which allows you to learn and rebuild this project from scratch if you want). 23 | 24 | ## Installation 25 | 26 | Very simple. See the Installation section in [wiki](https://github.com/prograhammer/vue-pizza/wiki). 27 | 28 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from './store' 3 | import { sync } from 'vuex-router-sync' 4 | import { http, router } from './http' 5 | import auth from './auth' 6 | import Vuetify from 'vuetify' 7 | import URLSearchParams from 'url-search-params' 8 | import App from './app' 9 | import Loading from './components/loading' 10 | import Appbar from './components/app-bar' 11 | import Appfooter from './components/app-footer' 12 | 13 | Vue.config.productionTip = false 14 | 15 | // Polyfills 16 | global.URLSearchParams = URLSearchParams 17 | 18 | // Sync router to store, as `store.state.route`. 19 | sync(store, router) 20 | 21 | // Http and Auth plugins 22 | Vue.use(http) 23 | Vue.use(auth) 24 | 25 | // Vuetify 26 | Vue.use(Vuetify, { 27 | theme: { 28 | primary: '#21CE99', 29 | secondary: '#D81B60', 30 | accent: '#805441' 31 | } 32 | }) 33 | 34 | // Styles 35 | require('./styles/scss/main.scss') 36 | require('./styles/stylus/main.styl') 37 | 38 | // Global Components 39 | Vue.component('loading', Loading) 40 | Vue.component('Appbar', Appbar) 41 | Vue.component('Appfooter', Appfooter) 42 | 43 | /* eslint-disable no-new */ 44 | new Vue({ 45 | el: '#app', 46 | router, 47 | store, 48 | render: h => h(App) 49 | }) 50 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to override the default and user settings 2 | { 3 | // Settings for .js, .vue, .styl files. 4 | "[javascript]": { 5 | "editor.tabSize": 2, 6 | "editor.insertSpaces": true 7 | }, 8 | "[vue]": { 9 | "editor.tabSize": 2, 10 | "editor.insertSpaces": true 11 | }, 12 | "[stylus]": { 13 | "editor.tabSize": 2, 14 | "editor.insertSpaces": true 15 | }, 16 | 17 | // This is required for ESLint to work in Vue in VS Code. 18 | "eslint.options": { 19 | "extensions": [".html", ".js", ".vue", ".jsx"] 20 | }, 21 | "eslint.validate": [ 22 | { 23 | "language": "html", 24 | "autoFix": true 25 | }, 26 | { 27 | "language": "vue", 28 | "autoFix": true 29 | }, 30 | { 31 | "language": "javascript", 32 | "autoFix": true 33 | }, 34 | { 35 | "language": "javascriptreact", 36 | "autoFix": true 37 | } 38 | ], 39 | 40 | // When you hit ctrl+e to search, you don't want node_modules to be included. 41 | "search.exclude": { 42 | "**/.git": true, 43 | "**/node_modules": true, 44 | "**/tmp": true 45 | } 46 | } -------------------------------------------------------------------------------- /src/features/account/service.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | // import auth from '@/auth/helpers' 3 | 4 | export default class Service { 5 | constructor (options) { 6 | this.id = store.state.auth.user.id 7 | } 8 | 9 | getProfile () { 10 | // Mock data. 11 | // Replace this with actual call to backend server below. 12 | const parsed = { 13 | email: 'prograhammer@gmail.com', 14 | name: 'David Graham', 15 | country: 'USA', 16 | addressLine1: '1234 Some St.', 17 | addressLine2: '', 18 | state: 'Texas', 19 | zipcode: '78789' 20 | } 21 | 22 | // Simulate loading time. 23 | return new Promise((resolve) => { 24 | setTimeout(() => { resolve(parsed) }, 500) 25 | }) 26 | 27 | /* 28 | return auth.get('/account') 29 | .then((response) => { 30 | const parsed = { 31 | email: response.email, 32 | name: response.name, 33 | country: response.country, 34 | addressLine1: response.address_line1, 35 | addressLine2: response.address_line2, 36 | state: response.state, 37 | zipcode: response.zipcode 38 | } 39 | 40 | return new Promise((resolve) => { resolve(parsed) }) 41 | }) 42 | .catch((error) => { 43 | return new Promise((resolve, reject) => { reject(error) }) 44 | }) 45 | */ 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/features/billing/main.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 40 | 41 | 56 | -------------------------------------------------------------------------------- /src/features/premium/main.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 40 | 41 | 56 | -------------------------------------------------------------------------------- /src/http/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import routes from './routes' 4 | import store from '@/store' 5 | 6 | Vue.use(Router) 7 | 8 | /** 9 | * Guard the route from unauthorized users. 10 | * 11 | * @param {Route} to The route we want to access. 12 | * @param {Route} from The route from which we are coming from. 13 | * @param {Function} next Callback for passing a route to be called next. 14 | * @return {void} 15 | */ 16 | function guardRoute (to, from, next) { 17 | // work-around to get to the Vuex store (as of Vue 2.0) 18 | const auth = router.app.$options.store.state.auth 19 | 20 | if (!auth.isLoggedIn) { 21 | next({path: '/login', query: { redirect: to.fullPath }}) 22 | } else { 23 | next() 24 | } 25 | } 26 | 27 | /** 28 | * The Router instance containing all the routes for the application. 29 | */ 30 | const router = new Router({ 31 | base: '/app', 32 | // mode: 'history', // <-- uncomment to turn on history mode (preferred) 33 | routes: routes.map(route => ({ 34 | name: route.name, 35 | path: route.path, 36 | component: route.component, 37 | beforeEnter: (to, from, next) => { 38 | // Setup some per-page stuff. 39 | document.title = route.title 40 | store.dispatch('common/updateTitle', route.title) 41 | store.dispatch('common/updateLayout', route.layout) 42 | 43 | // Auth navigation guard. 44 | if (!route.isPublic) return guardRoute(to, from, next) 45 | 46 | next() 47 | } 48 | })) 49 | }) 50 | 51 | export default router 52 | -------------------------------------------------------------------------------- /src/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | 33 | 34 | 53 | -------------------------------------------------------------------------------- /src/http/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Every route becomes a chunk, loaded only when used. 3 | * Reduces size of initial App load. 4 | */ 5 | const routes = [ 6 | { 7 | name: 'login', 8 | path: '/login', 9 | component: () => import(/* webpackChunkName: "login" */ '@/features/login/main.vue'), 10 | title: 'Login', 11 | layout: 'PublicLayout', 12 | isPublic: true 13 | }, 14 | { 15 | name: 'home', 16 | path: '/', 17 | component: () => import(/* webpackChunkName: "dashboard" */ '@/features/dashboard/main.vue'), 18 | title: 'Dashboard', 19 | layout: 'DefaultLayout', 20 | isPublic: false 21 | }, 22 | { 23 | name: 'dashboard', 24 | path: '/dashboard', 25 | component: () => import(/* webpackChunkName: "dashboard" */ '@/features/dashboard/main.vue'), 26 | title: 'Dashboard', 27 | layout: 'DefaultLayout', 28 | isPublic: false 29 | }, 30 | { 31 | name: 'account', 32 | path: '/account', 33 | component: () => import(/* webpackChunkName: "account" */ '@/features/account/main.vue'), 34 | title: 'Account', 35 | layout: 'DefaultLayout', 36 | isPublic: false 37 | }, 38 | { 39 | name: 'tutorial', 40 | path: '/examples/tutorial', 41 | component: () => import(/* webpackChunkName: "tutorial" */ '@/features/tutorial/main.vue'), 42 | title: 'Tutorial', 43 | layout: 'DefaultLayout', 44 | isPublic: false 45 | }, 46 | { 47 | name: 'wip', 48 | path: '/examples/wip', 49 | component: () => import(/* webpackChunkName: "wip" */ '@/features/wip/main.vue'), 50 | title: 'Wip', 51 | layout: 'DefaultLayout', 52 | isPublic: false 53 | } 54 | ] 55 | 56 | export default routes 57 | -------------------------------------------------------------------------------- /src/components/app-bar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 43 | 44 | 66 | -------------------------------------------------------------------------------- /src/store/common.js: -------------------------------------------------------------------------------- 1 | // Common State. 2 | const defaults = { 3 | sidebar: { 4 | visible: true 5 | }, 6 | title: '', 7 | layout: 'DefaultLayout', 8 | dialog: { 9 | visible: false, 10 | text: '' 11 | }, 12 | snackbar: { 13 | visible: false, 14 | text: '', 15 | timeout: 6000, 16 | color: '' 17 | }, 18 | error: { 19 | code: null, 20 | level: null, 21 | message: '' 22 | } 23 | } 24 | 25 | // Global module loaded on first app load. 26 | export default { 27 | namespaced: true, 28 | 29 | state: Object.assign({}, defaults), 30 | 31 | mutations: { 32 | updateSidebar (state, options) { 33 | state.sidebar = Object.assign({}, defaults.sidebar, options) 34 | }, 35 | 36 | updateTitle (state, title) { 37 | state.title = title 38 | }, 39 | 40 | updateLayout (state, layout) { 41 | state.layout = layout 42 | }, 43 | 44 | updateDialog (state, options) { 45 | state.dialog = Object.assign({}, defaults.dialog, options) 46 | }, 47 | 48 | updateSnackbar (state, options) { 49 | state.snackbar = Object.assign({}, defaults.snackbar, options) 50 | }, 51 | 52 | error (state, options) { 53 | state.error = Object.assign({}, defaults.error, options) 54 | }, 55 | 56 | clear (state) { 57 | state = Object.assign({}, defaults) 58 | } 59 | }, 60 | 61 | actions: { 62 | clear ({ state, commit, rootState, dispatch }) { 63 | commit('clear') 64 | dispatch('auth/clear', {}, { root: true }) 65 | }, 66 | 67 | updateSidebar ({ commit }, options) { 68 | commit('updateSidebar', options) 69 | }, 70 | 71 | updateTitle ({ commit }, title) { 72 | commit('updateTitle', title) 73 | }, 74 | 75 | updateLayout ({ commit }, layout) { 76 | commit('updateLayout', layout) 77 | }, 78 | 79 | updateDialog ({ commit }, options) { 80 | commit('updateDialog', options) 81 | }, 82 | 83 | updateSnackbar ({ commit }, options) { 84 | commit('updateSnackbar', options) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | vue-pizza 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | <% for (var chunk of webpack.chunks) { 26 | for (var file of chunk.files) { 27 | if (file.match(/\.(js|css)$/)) { %> 28 | <% }}} %> 29 | 30 | 31 | 34 |
35 | 36 | <%= htmlWebpackPlugin.options.serviceWorkerLoader %> 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | const path = require('path') 5 | 6 | module.exports = { 7 | build: { 8 | env: require('./prod.env'), 9 | index: path.resolve(__dirname, '../dist/index.html'), 10 | assetsRoot: path.resolve(__dirname, '../dist'), 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/app/', 13 | productionSourceMap: true, 14 | // Gzip off by default as many popular static hosts such as 15 | // Surge or Netlify already gzip all static assets for you. 16 | // Before setting to `true`, make sure to: 17 | // npm install --save-dev compression-webpack-plugin 18 | productionGzip: false, 19 | productionGzipExtensions: ['js', 'css'], 20 | // Run the build command with an extra argument to 21 | // View the bundle analyzer report after build finishes: 22 | // `npm run build --report` 23 | // Set to `true` or `false` to always turn it on or off 24 | bundleAnalyzerReport: process.env.npm_config_report 25 | }, 26 | dev: { 27 | env: require('./dev.env'), 28 | port: 8080, 29 | autoOpenBrowser: true, 30 | assetsSubDirectory: 'static', 31 | assetsPublicPath: '/', 32 | proxyTable: { 33 | '/auth': { 34 | // @TODO: You need to replace this with your own backend API. 35 | // Demo OAuth2 server https://github.com/bshaffer/oauth2-demo-php. 36 | // Username: demouser Password: demopass 37 | //target: 'http://brentertainment.com/oauth2/lockdin/token', 38 | target: 'http://localhost:8081', 39 | changeOrigin: true, 40 | ws: true, 41 | pathRewrite: { 42 | '^/auth': '' 43 | }, 44 | router: { 45 | } 46 | }, 47 | '/api': { 48 | // target: 'http://brentertainment.com/oauth2', // <-- Api server. 49 | target: 'http://localhost:8081/experience', 50 | changeOrigin: true, // <-- For virtual hosted sites. 51 | ws: true, // <-- Proxy websockets. 52 | pathRewrite: { 53 | // Rewrite path localhost:8080/api to http://brentertainment.com/oauth2/lockdin. 54 | '^/api': '' 55 | }, 56 | router: { 57 | // when request.headers.host == 'dev.localhost:3000', 58 | // override target 'http://www.example.org' to 'http://localhost:8000' 59 | // 'dev.localhost:3000': 'http://localhost:8000' 60 | } 61 | } 62 | }, 63 | // CSS Sourcemaps off by default because relative paths are "buggy" 64 | // with this option, according to the CSS-Loader README 65 | // (https://github.com/webpack/css-loader#sourcemaps) 66 | // In our experience, they generally work as expected, 67 | // just be aware of this issue when enabling this option. 68 | cssSourceMap: false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/assets/images/oops.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /src/auth/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | import axios from 'axios' 4 | import * as constants from '@/constants' 5 | 6 | // const CLIENT_SECRET = 'demopass' // Base64(client_id:client_secret) "demoapp:demopass" 7 | 8 | export default { 9 | 10 | install (Vue, options) { 11 | Vue.prototype.$auth = Vue.auth = axios.create() 12 | 13 | this.setDefaults() 14 | this.addInterceptors() 15 | }, 16 | 17 | setDefaults () { 18 | Vue.auth.defaults.baseURL = constants.API_BASE_URL 19 | }, 20 | 21 | addInterceptors () { 22 | // Watch for accessToken changes and update our common Auth header. 23 | store.watch((state) => { 24 | return state.auth.accessToken 25 | }, (accessToken) => { 26 | if (!constants.DEBUG) { 27 | Vue.auth.defaults.headers.common['Authorization'] = 'Bearer ' + accessToken 28 | Vue.auth.defaults.transformRequest = [(data, headers) => { 29 | data.access_token = accessToken 30 | return data 31 | }] 32 | } 33 | 34 | if (constants.DEBUG) { 35 | console.log('token set') 36 | } 37 | }, { 38 | deep: true 39 | }) 40 | 41 | // Intercept the response and refresh (one retry) if invalid token. 42 | Vue.auth.interceptors.response.use(function (response) { 43 | if (constants.DEBUG) return Promise.resolve(response) 44 | 45 | if (this.isInvalidToken(response)) { 46 | return this.refreshToken(response.request) 47 | } 48 | }, function (error) { 49 | return Promise.reject(error) 50 | }) 51 | }, 52 | 53 | isInvalidToken (response) { 54 | const status = response.status 55 | const error = response.data.error 56 | 57 | // Customize this to your Oauth server. 58 | return (status === 401 && (error === 'invalid_token' || error === 'expired_token')) 59 | }, 60 | 61 | refreshToken (request) { 62 | return axios({ 63 | method: 'post', 64 | url: constants.REFRESH_TOKEN_URL, 65 | // headers: {'Authorization': 'Basic ' + CLIENT_SECRET}, 66 | data: { 67 | grant_type: 'refresh_token', 68 | refresh_token: store.state.auth.refreshToken 69 | } 70 | }) 71 | .then((response) => { 72 | this.storeToken(response) 73 | return this.retry(request) 74 | }) 75 | .catch((errorResponse) => { 76 | if (this.isInvalidToken(errorResponse)) { this.logout() } 77 | return errorResponse 78 | }) 79 | }, 80 | 81 | storeToken (response) { 82 | const auth = store.state.auth 83 | 84 | auth.isLoggedIn = true 85 | auth.accessToken = response.data.accessToken 86 | auth.refreshToken = response.data.refreshToken 87 | // @TODO: get user's name from response from Oauth server. 88 | auth.user.name = 'David Graham' 89 | auth.user.id = 'e3f657cb80354820b657cb8035c8208e' 90 | 91 | store.dispatch('auth/update', auth) 92 | }, 93 | 94 | retry (request) { 95 | return Vue.auth(request) 96 | .then((response) => { return response }) 97 | .catch((response) => { return response }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-pizza", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "David Graham ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "e2e": "node test/e2e/runner.js", 12 | "test": "npm run e2e", 13 | "lint": "eslint --ext .js,.vue src test/e2e/specs", 14 | "deploy": "gh-pages-deploy" 15 | }, 16 | "gh-pages-deploy": { 17 | "staticpath": "dist" 18 | }, 19 | "dependencies": { 20 | "vue": "^2.5.2", 21 | "vue-router": "^3.0.1", 22 | "vuex-router-sync": "^5.0.0" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^7.1.5", 26 | "axios": "^0.18.0", 27 | "babel-core": "^6.26.0", 28 | "babel-eslint": "^8.0.1", 29 | "babel-loader": "^7.1.2", 30 | "babel-plugin-transform-runtime": "^6.23.0", 31 | "babel-preset-env": "^1.6.0", 32 | "babel-preset-stage-2": "^6.24.1", 33 | "babel-register": "^6.26.0", 34 | "chalk": "^2.1.0", 35 | "chart.js": "^2.7.2", 36 | "chromedriver": "^2.33.1", 37 | "connect-history-api-fallback": "^1.4.0", 38 | "copy-webpack-plugin": "^4.1.1", 39 | "cross-spawn": "^5.1.0", 40 | "css-loader": "^0.28.7", 41 | "cssnano": "^3.10.0", 42 | "eslint": "^4.9.0", 43 | "eslint-config-standard": "^10.2.1", 44 | "eslint-friendly-formatter": "^3.0.0", 45 | "eslint-loader": "^1.9.0", 46 | "eslint-plugin-html": "^3.2.2", 47 | "eslint-plugin-import": "^2.7.0", 48 | "eslint-plugin-node": "^5.2.0", 49 | "eslint-plugin-promise": "^3.6.0", 50 | "eslint-plugin-standard": "^3.0.1", 51 | "eslint-plugin-vue": "^4.5.0", 52 | "eventsource-polyfill": "^0.9.6", 53 | "express": "^4.16.2", 54 | "extract-text-webpack-plugin": "^3.0.0", 55 | "file-loader": "^1.1.5", 56 | "friendly-errors-webpack-plugin": "^1.6.1", 57 | "gh-pages-deploy": "^0.4.2", 58 | "html-webpack-plugin": "^2.30.1", 59 | "http-proxy-middleware": "^0.17.4", 60 | "nightwatch": "^0.9.16", 61 | "node-sass": "^4.9.0", 62 | "opn": "^5.1.0", 63 | "optimize-css-assets-webpack-plugin": "^3.2.0", 64 | "ora": "^1.3.0", 65 | "pug": "^2.0.3", 66 | "pug-loader": "^2.3.0", 67 | "rimraf": "^2.6.2", 68 | "sass-loader": "^7.0.1", 69 | "selenium-server": "^3.6.0", 70 | "semver": "^5.4.1", 71 | "shelljs": "^0.7.8", 72 | "stylus": "^0.54.5", 73 | "stylus-loader": "^3.0.2", 74 | "sw-precache-webpack-plugin": "^0.11.4", 75 | "uglify-es": "^3.1.3", 76 | "url-loader": "^0.6.2", 77 | "url-search-params": "^0.10.0", 78 | "vue-chartjs": "^3.3.1", 79 | "vue-gravatar": "^1.2.1", 80 | "vue-loader": "^13.3.0", 81 | "vue-style-loader": "^3.0.3", 82 | "vue-template-compiler": "^2.5.2", 83 | "vuetify": "^1.0.17", 84 | "vuex": "^3.0.1", 85 | "webpack": "^3.7.1", 86 | "webpack-bundle-analyzer": "^2.9.0", 87 | "webpack-dev-middleware": "^1.12.0", 88 | "webpack-hot-middleware": "^2.19.1", 89 | "webpack-merge": "^4.1.0" 90 | }, 91 | "engines": { 92 | "node": ">= 4.0.0", 93 | "npm": ">= 3.0.0" 94 | }, 95 | "browserslist": [ 96 | "> 1%", 97 | "last 2 versions", 98 | "not ie <= 8" 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /src/features/login/main.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 97 | 98 | 129 | -------------------------------------------------------------------------------- /src/features/dashboard/main.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 126 | 127 | 138 | -------------------------------------------------------------------------------- /src/features/account/components/address-edit.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 137 | 138 | 142 | -------------------------------------------------------------------------------- /src/auth/helpers.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { router } from '@/http' 3 | import store from '@/store' 4 | import auth from './' 5 | 6 | const LOGIN_URL = '/auth' 7 | 8 | // const CLIENT_SECRET = 'ZGVtb2FwcDpkZW1vcGFzcw==' // Base64(client_id:client_secret) "demoapp:demopass" 9 | 10 | export default { 11 | URLSearchParams (obj) { 12 | var params = new URLSearchParams() 13 | 14 | for (var [key, value] of Object.entries(obj)) params.append(key, value) 15 | 16 | return params 17 | }, 18 | 19 | login (creds, redirect, callback) { 20 | return Vue.http({ 21 | method: 'post', 22 | url: LOGIN_URL, 23 | // headers: { 24 | // 'Authorization': 'Basic ' + CLIENT_SECRET, 25 | // 'Content-Type': 'application/x-www-form-urlencoded' 26 | // }, 27 | data: this.URLSearchParams({ 28 | grant_type: 'password', 29 | client_id: 'demoapp', 30 | client_secret: 'demopass', 31 | username: creds.username, 32 | password: creds.password 33 | }) 34 | }) 35 | .then((response) => { 36 | auth.storeToken(response) 37 | 38 | if (redirect) router.push({ name: redirect }) 39 | return response 40 | }) 41 | .catch((error) => { 42 | let errorMessage = null 43 | 44 | if (error.response) errorMessage = error.response.status 45 | else if (error.request) errorMessage = 'no response from server' 46 | else errorMessage = error.message 47 | 48 | return errorMessage 49 | }) 50 | }, 51 | 52 | logout () { 53 | store.dispatch('common/clear') 54 | router.push({ name: 'login' }) 55 | }, 56 | 57 | fakeLogin (creds, redirect) { 58 | return new Promise((resolve, reject) => { 59 | setTimeout(() => { 60 | auth.storeToken({data: { accessToken: '123456789', refreshToken: '77777777' }}) 61 | if (redirect) router.push({ name: redirect }) 62 | resolve({}) 63 | }, 500) 64 | }) 65 | }, 66 | 67 | // Standardizes errors. A place to add logging if needed. 68 | get (url, params = {}) { 69 | const config = { 70 | params: { 71 | username: store.state.auth.user.id, 72 | orgId: store.state.auth.user.orgId 73 | } 74 | } 75 | 76 | config.params = Object.assign(config.params, params) 77 | 78 | return Vue.auth.get(url, config) 79 | .then((response) => { 80 | return new Promise((resolve) => { 81 | // @TODO check for no response.data.data? 82 | resolve(response.data.data) 83 | }) 84 | }) 85 | .catch((error) => { 86 | // Standardize errors. 87 | let errorMessage = null 88 | 89 | if (error.response) { 90 | errorMessage = error.response.statusText || error.response.status 91 | } else if (error.request) { 92 | errorMessage = 'no response from server' 93 | } else { 94 | errorMessage = error.message 95 | } 96 | 97 | return new Promise((resolve, reject) => { 98 | reject(new Error(errorMessage)) 99 | }) 100 | }) 101 | }, 102 | 103 | put (url, data = {}) { 104 | const config = {} 105 | 106 | const defaultData = { 107 | username: store.state.auth.user.id, 108 | orgId: store.state.auth.user.orgId 109 | } 110 | 111 | data = Object.assign(defaultData, data) 112 | 113 | // console.log(settings.data) 114 | 115 | return Vue.auth.put(url, data, config) 116 | .then((response) => { 117 | if (response.data.errors) { 118 | return new Promise((resolve, reject) => { 119 | reject(new Error(response.data.errors[0].user_message)) 120 | }) 121 | } 122 | 123 | return new Promise((resolve) => { 124 | // @TODO check for no response.data.data? 125 | resolve(response.data.data) 126 | }) 127 | }) 128 | .catch((error) => { 129 | // Standardize errors. 130 | let errorMessage = null 131 | 132 | if (error.response) { 133 | errorMessage = error.response.statusText || error.response.status 134 | } else if (error.request) { 135 | errorMessage = 'no response from server' 136 | } else { 137 | errorMessage = error.message 138 | } 139 | 140 | return new Promise((resolve, reject) => { 141 | reject(new Error(errorMessage)) 142 | }) 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/components/loading.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 169 | 170 | 186 | -------------------------------------------------------------------------------- /src/features/account/main.vue: -------------------------------------------------------------------------------- 1 | 126 | 127 | 198 | 199 | 216 | -------------------------------------------------------------------------------- /static/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/features/account/components/countries.js: -------------------------------------------------------------------------------- 1 | const countries = [ 2 | { 'name': 'United States', 'code': 'US' }, 3 | { 'name': 'Afghanistan', 'code': 'AF' }, 4 | { 'name': 'Åland Islands', 'code': 'AX' }, 5 | { 'name': 'Albania', 'code': 'AL' }, 6 | { 'name': 'Algeria', 'code': 'DZ' }, 7 | { 'name': 'American Samoa', 'code': 'AS' }, 8 | { 'name': 'Andorra', 'code': 'AD' }, 9 | { 'name': 'Angola', 'code': 'AO' }, 10 | { 'name': 'Anguilla', 'code': 'AI' }, 11 | { 'name': 'Antarctica', 'code': 'AQ' }, 12 | { 'name': 'Antigua and Barbuda', 'code': 'AG' }, 13 | { 'name': 'Argentina', 'code': 'AR' }, 14 | { 'name': 'Armenia', 'code': 'AM' }, 15 | { 'name': 'Aruba', 'code': 'AW' }, 16 | { 'name': 'Australia', 'code': 'AU' }, 17 | { 'name': 'Austria', 'code': 'AT' }, 18 | { 'name': 'Azerbaijan', 'code': 'AZ' }, 19 | { 'name': 'Bahamas', 'code': 'BS' }, 20 | { 'name': 'Bahrain', 'code': 'BH' }, 21 | { 'name': 'Bangladesh', 'code': 'BD' }, 22 | { 'name': 'Barbados', 'code': 'BB' }, 23 | { 'name': 'Belarus', 'code': 'BY' }, 24 | { 'name': 'Belgium', 'code': 'BE' }, 25 | { 'name': 'Belize', 'code': 'BZ' }, 26 | { 'name': 'Benin', 'code': 'BJ' }, 27 | { 'name': 'Bermuda', 'code': 'BM' }, 28 | { 'name': 'Bhutan', 'code': 'BT' }, 29 | { 'name': 'Bolivia', 'code': 'BO' }, 30 | { 'name': 'Bosnia and Herzegovina', 'code': 'BA' }, 31 | { 'name': 'Botswana', 'code': 'BW' }, 32 | { 'name': 'Bouvet Island', 'code': 'BV' }, 33 | { 'name': 'Brazil', 'code': 'BR' }, 34 | { 'name': 'British Indian Ocean Territory', 'code': 'IO' }, 35 | { 'name': 'Brunei Darussalam', 'code': 'BN' }, 36 | { 'name': 'Bulgaria', 'code': 'BG' }, 37 | { 'name': 'Burkina Faso', 'code': 'BF' }, 38 | { 'name': 'Burundi', 'code': 'BI' }, 39 | { 'name': 'Cambodia', 'code': 'KH' }, 40 | { 'name': 'Cameroon', 'code': 'CM' }, 41 | { 'name': 'Canada', 'code': 'CA' }, 42 | { 'name': 'Cape Verde', 'code': 'CV' }, 43 | { 'name': 'Cayman Islands', 'code': 'KY' }, 44 | { 'name': 'Central African Republic', 'code': 'CF' }, 45 | { 'name': 'Chad', 'code': 'TD' }, 46 | { 'name': 'Chile', 'code': 'CL' }, 47 | { 'name': 'China', 'code': 'CN' }, 48 | { 'name': 'Christmas Island', 'code': 'CX' }, 49 | { 'name': 'Cocos (Keeling) Islands', 'code': 'CC' }, 50 | { 'name': 'Colombia', 'code': 'CO' }, 51 | { 'name': 'Comoros', 'code': 'KM' }, 52 | { 'name': 'Congo', 'code': 'CG' }, 53 | { 'name': 'Congo, The Democratic Republic of the', 'code': 'CD' }, 54 | { 'name': 'Cook Islands', 'code': 'CK' }, 55 | { 'name': 'Costa Rica', 'code': 'CR' }, 56 | { 'name': 'Cote D\'Ivoire', 'code': 'CI' }, 57 | { 'name': 'Croatia', 'code': 'HR' }, 58 | { 'name': 'Cuba', 'code': 'CU' }, 59 | { 'name': 'Cyprus', 'code': 'CY' }, 60 | { 'name': 'Czech Republic', 'code': 'CZ' }, 61 | { 'name': 'Denmark', 'code': 'DK' }, 62 | { 'name': 'Djibouti', 'code': 'DJ' }, 63 | { 'name': 'Dominica', 'code': 'DM' }, 64 | { 'name': 'Dominican Republic', 'code': 'DO' }, 65 | { 'name': 'Ecuador', 'code': 'EC' }, 66 | { 'name': 'Egypt', 'code': 'EG' }, 67 | { 'name': 'El Salvador', 'code': 'SV' }, 68 | { 'name': 'Equatorial Guinea', 'code': 'GQ' }, 69 | { 'name': 'Eritrea', 'code': 'ER' }, 70 | { 'name': 'Estonia', 'code': 'EE' }, 71 | { 'name': 'Ethiopia', 'code': 'ET' }, 72 | { 'name': 'Falkland Islands (Malvinas)', 'code': 'FK' }, 73 | { 'name': 'Faroe Islands', 'code': 'FO' }, 74 | { 'name': 'Fiji', 'code': 'FJ' }, 75 | { 'name': 'Finland', 'code': 'FI' }, 76 | { 'name': 'France', 'code': 'FR' }, 77 | { 'name': 'French Guiana', 'code': 'GF' }, 78 | { 'name': 'French Polynesia', 'code': 'PF' }, 79 | { 'name': 'French Southern Territories', 'code': 'TF' }, 80 | { 'name': 'Gabon', 'code': 'GA' }, 81 | { 'name': 'Gambia', 'code': 'GM' }, 82 | { 'name': 'Georgia', 'code': 'GE' }, 83 | { 'name': 'Germany', 'code': 'DE' }, 84 | { 'name': 'Ghana', 'code': 'GH' }, 85 | { 'name': 'Gibraltar', 'code': 'GI' }, 86 | { 'name': 'Greece', 'code': 'GR' }, 87 | { 'name': 'Greenland', 'code': 'GL' }, 88 | { 'name': 'Grenada', 'code': 'GD' }, 89 | { 'name': 'Guadeloupe', 'code': 'GP' }, 90 | { 'name': 'Guam', 'code': 'GU' }, 91 | { 'name': 'Guatemala', 'code': 'GT' }, 92 | { 'name': 'Guernsey', 'code': 'GG' }, 93 | { 'name': 'Guinea', 'code': 'GN' }, 94 | { 'name': 'Guinea-Bissau', 'code': 'GW' }, 95 | { 'name': 'Guyana', 'code': 'GY' }, 96 | { 'name': 'Haiti', 'code': 'HT' }, 97 | { 'name': 'Heard Island and Mcdonald Islands', 'code': 'HM' }, 98 | { 'name': 'Holy See (Vatican City State)', 'code': 'VA' }, 99 | { 'name': 'Honduras', 'code': 'HN' }, 100 | { 'name': 'Hong Kong', 'code': 'HK' }, 101 | { 'name': 'Hungary', 'code': 'HU' }, 102 | { 'name': 'Iceland', 'code': 'IS' }, 103 | { 'name': 'India', 'code': 'IN' }, 104 | { 'name': 'Indonesia', 'code': 'ID' }, 105 | { 'name': 'Iran, Islamic Republic Of', 'code': 'IR' }, 106 | { 'name': 'Iraq', 'code': 'IQ' }, 107 | { 'name': 'Ireland', 'code': 'IE' }, 108 | { 'name': 'Isle of Man', 'code': 'IM' }, 109 | { 'name': 'Israel', 'code': 'IL' }, 110 | { 'name': 'Italy', 'code': 'IT' }, 111 | { 'name': 'Jamaica', 'code': 'JM' }, 112 | { 'name': 'Japan', 'code': 'JP' }, 113 | { 'name': 'Jersey', 'code': 'JE' }, 114 | { 'name': 'Jordan', 'code': 'JO' }, 115 | { 'name': 'Kazakhstan', 'code': 'KZ' }, 116 | { 'name': 'Kenya', 'code': 'KE' }, 117 | { 'name': 'Kiribati', 'code': 'KI' }, 118 | { 'name': 'Democratic People\'s Republic of Korea', 'code': 'KP' }, 119 | { 'name': 'Korea, Republic of', 'code': 'KR' }, 120 | { 'name': 'Kosovo', 'code': 'XK' }, 121 | { 'name': 'Kuwait', 'code': 'KW' }, 122 | { 'name': 'Kyrgyzstan', 'code': 'KG' }, 123 | { 'name': 'Lao People\'s Democratic Republic', 'code': 'LA' }, 124 | { 'name': 'Latvia', 'code': 'LV' }, 125 | { 'name': 'Lebanon', 'code': 'LB' }, 126 | { 'name': 'Lesotho', 'code': 'LS' }, 127 | { 'name': 'Liberia', 'code': 'LR' }, 128 | { 'name': 'Libyan Arab Jamahiriya', 'code': 'LY' }, 129 | { 'name': 'Liechtenstein', 'code': 'LI' }, 130 | { 'name': 'Lithuania', 'code': 'LT' }, 131 | { 'name': 'Luxembourg', 'code': 'LU' }, 132 | { 'name': 'Macao', 'code': 'MO' }, 133 | { 'name': 'Macedonia, The Former Yugoslav Republic of', 'code': 'MK' }, 134 | { 'name': 'Madagascar', 'code': 'MG' }, 135 | { 'name': 'Malawi', 'code': 'MW' }, 136 | { 'name': 'Malaysia', 'code': 'MY' }, 137 | { 'name': 'Maldives', 'code': 'MV' }, 138 | { 'name': 'Mali', 'code': 'ML' }, 139 | { 'name': 'Malta', 'code': 'MT' }, 140 | { 'name': 'Marshall Islands', 'code': 'MH' }, 141 | { 'name': 'Martinique', 'code': 'MQ' }, 142 | { 'name': 'Mauritania', 'code': 'MR' }, 143 | { 'name': 'Mauritius', 'code': 'MU' }, 144 | { 'name': 'Mayotte', 'code': 'YT' }, 145 | { 'name': 'Mexico', 'code': 'MX' }, 146 | { 'name': 'Micronesia, Federated States of', 'code': 'FM' }, 147 | { 'name': 'Moldova, Republic of', 'code': 'MD' }, 148 | { 'name': 'Monaco', 'code': 'MC' }, 149 | { 'name': 'Mongolia', 'code': 'MN' }, 150 | { 'name': 'Montenegro', 'code': 'ME' }, 151 | { 'name': 'Montserrat', 'code': 'MS' }, 152 | { 'name': 'Morocco', 'code': 'MA' }, 153 | { 'name': 'Mozambique', 'code': 'MZ' }, 154 | { 'name': 'Myanmar', 'code': 'MM' }, 155 | { 'name': 'Namibia', 'code': 'NA' }, 156 | { 'name': 'Nauru', 'code': 'NR' }, 157 | { 'name': 'Nepal', 'code': 'NP' }, 158 | { 'name': 'Netherlands', 'code': 'NL' }, 159 | { 'name': 'Netherlands Antilles', 'code': 'AN' }, 160 | { 'name': 'New Caledonia', 'code': 'NC' }, 161 | { 'name': 'New Zealand', 'code': 'NZ' }, 162 | { 'name': 'Nicaragua', 'code': 'NI' }, 163 | { 'name': 'Niger', 'code': 'NE' }, 164 | { 'name': 'Nigeria', 'code': 'NG' }, 165 | { 'name': 'Niue', 'code': 'NU' }, 166 | { 'name': 'Norfolk Island', 'code': 'NF' }, 167 | { 'name': 'Northern Mariana Islands', 'code': 'MP' }, 168 | { 'name': 'Norway', 'code': 'NO' }, 169 | { 'name': 'Oman', 'code': 'OM' }, 170 | { 'name': 'Pakistan', 'code': 'PK' }, 171 | { 'name': 'Palau', 'code': 'PW' }, 172 | { 'name': 'Palestinian Territory, Occupied', 'code': 'PS' }, 173 | { 'name': 'Panama', 'code': 'PA' }, 174 | { 'name': 'Papua New Guinea', 'code': 'PG' }, 175 | { 'name': 'Paraguay', 'code': 'PY' }, 176 | { 'name': 'Peru', 'code': 'PE' }, 177 | { 'name': 'Philippines', 'code': 'PH' }, 178 | { 'name': 'Pitcairn', 'code': 'PN' }, 179 | { 'name': 'Poland', 'code': 'PL' }, 180 | { 'name': 'Portugal', 'code': 'PT' }, 181 | { 'name': 'Puerto Rico', 'code': 'PR' }, 182 | { 'name': 'Qatar', 'code': 'QA' }, 183 | { 'name': 'Reunion', 'code': 'RE' }, 184 | { 'name': 'Romania', 'code': 'RO' }, 185 | { 'name': 'Russian Federation', 'code': 'RU' }, 186 | { 'name': 'Rwanda', 'code': 'RW' }, 187 | { 'name': 'Saint Helena', 'code': 'SH' }, 188 | { 'name': 'Saint Kitts and Nevis', 'code': 'KN' }, 189 | { 'name': 'Saint Lucia', 'code': 'LC' }, 190 | { 'name': 'Saint Pierre and Miquelon', 'code': 'PM' }, 191 | { 'name': 'Saint Vincent and the Grenadines', 'code': 'VC' }, 192 | { 'name': 'Samoa', 'code': 'WS' }, 193 | { 'name': 'San Marino', 'code': 'SM' }, 194 | { 'name': 'Sao Tome and Principe', 'code': 'ST' }, 195 | { 'name': 'Saudi Arabia', 'code': 'SA' }, 196 | { 'name': 'Senegal', 'code': 'SN' }, 197 | { 'name': 'Serbia', 'code': 'RS' }, 198 | { 'name': 'Seychelles', 'code': 'SC' }, 199 | { 'name': 'Sierra Leone', 'code': 'SL' }, 200 | { 'name': 'Singapore', 'code': 'SG' }, 201 | { 'name': 'Slovakia', 'code': 'SK' }, 202 | { 'name': 'Slovenia', 'code': 'SI' }, 203 | { 'name': 'Solomon Islands', 'code': 'SB' }, 204 | { 'name': 'Somalia', 'code': 'SO' }, 205 | { 'name': 'South Africa', 'code': 'ZA' }, 206 | { 'name': 'South Georgia and the South Sandwich Islands', 'code': 'GS' }, 207 | { 'name': 'Spain', 'code': 'ES' }, 208 | { 'name': 'Sri Lanka', 'code': 'LK' }, 209 | { 'name': 'Sudan', 'code': 'SD' }, 210 | { 'name': 'Suriname', 'code': 'SR' }, 211 | { 'name': 'Svalbard and Jan Mayen', 'code': 'SJ' }, 212 | { 'name': 'Swaziland', 'code': 'SZ' }, 213 | { 'name': 'Sweden', 'code': 'SE' }, 214 | { 'name': 'Switzerland', 'code': 'CH' }, 215 | { 'name': 'Syrian Arab Republic', 'code': 'SY' }, 216 | { 'name': 'Taiwan', 'code': 'TW' }, 217 | { 'name': 'Tajikistan', 'code': 'TJ' }, 218 | { 'name': 'Tanzania, United Republic of', 'code': 'TZ' }, 219 | { 'name': 'Thailand', 'code': 'TH' }, 220 | { 'name': 'Timor-Leste', 'code': 'TL' }, 221 | { 'name': 'Togo', 'code': 'TG' }, 222 | { 'name': 'Tokelau', 'code': 'TK' }, 223 | { 'name': 'Tonga', 'code': 'TO' }, 224 | { 'name': 'Trinidad and Tobago', 'code': 'TT' }, 225 | { 'name': 'Tunisia', 'code': 'TN' }, 226 | { 'name': 'Turkey', 'code': 'TR' }, 227 | { 'name': 'Turkmenistan', 'code': 'TM' }, 228 | { 'name': 'Turks and Caicos Islands', 'code': 'TC' }, 229 | { 'name': 'Tuvalu', 'code': 'TV' }, 230 | { 'name': 'Uganda', 'code': 'UG' }, 231 | { 'name': 'Ukraine', 'code': 'UA' }, 232 | { 'name': 'United Arab Emirates', 'code': 'AE' }, 233 | { 'name': 'United Kingdom', 'code': 'GB' }, 234 | { 'name': 'United States', 'code': 'US' }, 235 | { 'name': 'United States Minor Outlying Islands', 'code': 'UM' }, 236 | { 'name': 'Uruguay', 'code': 'UY' }, 237 | { 'name': 'Uzbekistan', 'code': 'UZ' }, 238 | { 'name': 'Vanuatu', 'code': 'VU' }, 239 | { 'name': 'Venezuela', 'code': 'VE' }, 240 | { 'name': 'Viet Nam', 'code': 'VN' }, 241 | { 'name': 'Virgin Islands, British', 'code': 'VG' }, 242 | { 'name': 'Virgin Islands, U.S.', 'code': 'VI' }, 243 | { 'name': 'Wallis and Futuna', 'code': 'WF' }, 244 | { 'name': 'Western Sahara', 'code': 'EH' }, 245 | { 'name': 'Yemen', 'code': 'YE' }, 246 | { 'name': 'Zambia', 'code': 'ZM' }, 247 | { 'name': 'Zimbabwe', 'code': 'ZW' } 248 | ] 249 | 250 | export default countries 251 | -------------------------------------------------------------------------------- /src/components/app-sidebar.vue: -------------------------------------------------------------------------------- 1 | 93 | 94 | 118 | 119 | 140 | -------------------------------------------------------------------------------- /src/assets/images/logo-name.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 73 | 125 | 165 | 171 | PriceMatchers 190 | 195 | 200 | 205 | 210 | 215 | 220 | 225 | 230 | 231 | 232 | 237 | 238 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 73 | 92 | 111 | 289 | 358 | 364 | 369 | 372 | 377 | 383 | 384 | 387 | 392 | 397 | 398 | 401 | 406 | 411 | 412 | 415 | 420 | 425 | 426 | 431 | 432 | 437 | 438 | --------------------------------------------------------------------------------