├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .stylintrc ├── README.md ├── build ├── css-utils.js ├── env-utils.js ├── hot-reload.js ├── script.build.js ├── script.clean.js ├── script.dev.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── package-lock.json ├── package.json ├── src ├── App.vue ├── api │ └── routes.js ├── assets │ └── quasar-logo-full.svg ├── bootstrap │ └── index.js ├── components │ ├── Error404.vue │ └── Hello.vue ├── config │ └── auth.js ├── index.html ├── interceptors │ ├── axios.js │ └── v-router.js ├── main.js ├── oauth │ ├── index.js │ └── service.js ├── router.js ├── statics │ └── quasar-logo.png ├── themes │ ├── app.ios.styl │ ├── app.mat.styl │ ├── app.variables.styl │ └── quasar.variables.styl └── views │ └── Login.vue └── templates ├── component.vue ├── layout.vue └── page.vue /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015", {"modules": false}], "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | dist/*.js 4 | src/**/*.js 5 | src/**/*.vue -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true 9 | }, 10 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 11 | extends: [ 12 | 'standard' 13 | ], 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html', 17 | 'import' 18 | ], 19 | globals: { 20 | 'cordova': true, 21 | 'DEV': true, 22 | 'PROD': true, 23 | '__THEME': true 24 | }, 25 | // add your custom rules here 26 | 'rules': { 27 | // allow paren-less arrow functions 28 | 'arrow-parens': 0, 29 | 'one-var': 0, 30 | 'import/first': 0, 31 | 'import/named': 2, 32 | 'import/namespace': 2, 33 | 'import/default': 2, 34 | 'import/export': 2, 35 | // allow debugger during development 36 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 37 | 'brace-style': [2, 'stroustrup', { 'allowSingleLine': true }] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules/ 4 | dist/ 5 | npm-debug.log* 6 | cordova/platforms 7 | cordova/plugins 8 | -------------------------------------------------------------------------------- /.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": "never", 3 | "brackets": "never", 4 | "colons": "never", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "depthLimit": false, 10 | "duplicates": true, 11 | "efficient": "always", 12 | "extendPref": false, 13 | "globalDupe": true, 14 | "indentPref": 2, 15 | "leadingZero": "never", 16 | "maxErrors": false, 17 | "maxWarnings": false, 18 | "mixed": false, 19 | "namingConvention": false, 20 | "namingConventionStrict": false, 21 | "none": "never", 22 | "noImportant": false, 23 | "parenSpace": "never", 24 | "placeholder": false, 25 | "prefixVarsWithDollar": "always", 26 | "quotePref": "single", 27 | "semicolons": "never", 28 | "sortOrder": false, 29 | "stackedProperties": "never", 30 | "trailingWhitespace": "never", 31 | "universal": "never", 32 | "valid": true, 33 | "zeroUnits": "never", 34 | "zIndexNormalize": false 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quasar App 2 | 3 | > A Quasar project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | $ quasar dev 13 | 14 | # build for production with minification 15 | $ quasar build 16 | 17 | # lint code 18 | $ quasar lint 19 | ``` 20 | -------------------------------------------------------------------------------- /build/css-utils.js: -------------------------------------------------------------------------------- 1 | var 2 | ExtractTextPlugin = require('extract-text-webpack-plugin'), 3 | autoprefixer = require('autoprefixer'), 4 | purify = require('purify-css'), 5 | glob = require('glob'), 6 | path = require('path'), 7 | fs = require('fs') 8 | 9 | module.exports.postcss = [autoprefixer()] 10 | 11 | module.exports.styleLoaders = function (options) { 12 | options = options || {} 13 | 14 | function generateLoaders (loaders) { 15 | if (options.postcss) { 16 | loaders.splice(1, 0, 'postcss') 17 | } 18 | 19 | var sourceLoader = loaders.map(function (loader) { 20 | var extraParamChar 21 | if (/\?/.test(loader)) { 22 | loader = loader.replace(/\?/, '-loader?') 23 | extraParamChar = '&' 24 | } 25 | else { 26 | loader = loader + '-loader' 27 | extraParamChar = '?' 28 | } 29 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 30 | }).join('!') 31 | 32 | if (options.extract) { 33 | return ExtractTextPlugin.extract({ 34 | use: sourceLoader, 35 | fallback: 'vue-style-loader' 36 | }) 37 | } 38 | else { 39 | return ['vue-style-loader', sourceLoader].join('!') 40 | } 41 | } 42 | 43 | return { 44 | css: generateLoaders(['css']), 45 | less: generateLoaders(['css', 'less']), 46 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 47 | scss: generateLoaders(['css', 'sass']), 48 | styl: generateLoaders(['css', 'stylus']), 49 | stylus: generateLoaders(['css', 'stylus']) 50 | } 51 | } 52 | 53 | module.exports.styleRules = function (options) { 54 | var output = [] 55 | var loaders = exports.styleLoaders(options) 56 | for (var extension in loaders) { 57 | var loader = loaders[extension] 58 | output.push({ 59 | test: new RegExp('\\.' + extension + '$'), 60 | loader: loader 61 | }) 62 | } 63 | return output 64 | } 65 | 66 | function getSize (size) { 67 | return (size / 1024).toFixed(2) + 'kb' 68 | } 69 | 70 | module.exports.purify = function(cb) { 71 | var css = glob.sync(path.join(__dirname, '../dist/**/*.css')) 72 | var js = glob.sync(path.join(__dirname, '../dist/**/*.js')) 73 | 74 | Promise.all(css.map(function (file) { 75 | return new Promise(function (resolve) { 76 | console.log('\n Purifying ' + path.relative(path.join(__dirname, '../dist'), file).bold + '...') 77 | purify(js, [file], {minify: true}, function (purified) { 78 | var oldSize = fs.statSync(file).size 79 | fs.writeFileSync(file, purified) 80 | var newSize = fs.statSync(file).size 81 | 82 | console.log( 83 | ' * Reduced size by ' + ((1 - newSize / oldSize) * 100).toFixed(2) + '%, from ' + 84 | getSize(oldSize) + ' to ' + getSize(newSize) + '.' 85 | ) 86 | resolve() 87 | }) 88 | }) 89 | })) 90 | .then(cb) 91 | } 92 | -------------------------------------------------------------------------------- /build/env-utils.js: -------------------------------------------------------------------------------- 1 | var 2 | config = require('../config'), 3 | theme = process.argv[2] || config.defaultTheme 4 | 5 | module.exports = { 6 | dev: process.env.NODE_ENV === 'development', 7 | prod: process.env.NODE_ENV === 'production', 8 | 9 | platform: { 10 | theme: theme, 11 | cordovaAssets: './cordova/platforms/' + (theme === 'mat' ? 'android' : 'ios') + '/platform_www' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /build/hot-reload.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | -------------------------------------------------------------------------------- /build/script.build.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'production' 2 | 3 | require('colors') 4 | 5 | var 6 | shell = require('shelljs'), 7 | path = require('path'), 8 | env = require('./env-utils'), 9 | css = require('./css-utils'), 10 | config = require('../config'), 11 | webpack = require('webpack'), 12 | webpackConfig = require('./webpack.prod.conf'), 13 | targetPath = path.join(__dirname, '../dist') 14 | 15 | console.log(' WARNING!'.bold) 16 | console.log(' Do NOT use VueRouter\'s "history" mode if') 17 | console.log(' building for Cordova or Electron.\n') 18 | 19 | require('./script.clean.js') 20 | console.log((' Building Quasar App with "' + env.platform.theme + '" theme...\n').bold) 21 | 22 | shell.mkdir('-p', targetPath) 23 | shell.cp('-R', 'src/statics', targetPath) 24 | 25 | function finalize () { 26 | console.log(( 27 | '\n Build complete with "' + env.platform.theme.bold + '" theme in ' + 28 | '"/dist"'.bold + ' folder.\n').cyan) 29 | 30 | console.log(' Built files are meant to be served over an HTTP server.'.bold) 31 | console.log(' Opening index.html over file:// won\'t work.'.bold) 32 | } 33 | 34 | webpack(webpackConfig, function (err, stats) { 35 | if (err) throw err 36 | 37 | process.stdout.write(stats.toString({ 38 | colors: true, 39 | modules: false, 40 | children: false, 41 | chunks: false, 42 | chunkModules: false 43 | }) + '\n') 44 | 45 | if (stats.hasErrors()) { 46 | process.exit(1) 47 | } 48 | 49 | if (config.build.purifyCSS) { 50 | css.purify(finalize) 51 | } 52 | else { 53 | finalize() 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /build/script.clean.js: -------------------------------------------------------------------------------- 1 | var 2 | shell = require('shelljs'), 3 | path = require('path') 4 | 5 | shell.rm('-rf', path.resolve(__dirname, '../dist/*')) 6 | shell.rm('-rf', path.resolve(__dirname, '../dist/.*')) 7 | console.log(' Cleaned build artifacts.\n') 8 | -------------------------------------------------------------------------------- /build/script.dev.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'development' 2 | 3 | require('colors') 4 | 5 | var 6 | path = require('path'), 7 | express = require('express'), 8 | webpack = require('webpack'), 9 | env = require('./env-utils'), 10 | config = require('../config'), 11 | opn = require('opn'), 12 | proxyMiddleware = require('http-proxy-middleware'), 13 | webpackConfig = require('./webpack.dev.conf'), 14 | app = express(), 15 | port = process.env.PORT || config.dev.port, 16 | uri = 'http://localhost:' + port 17 | 18 | console.log(' Starting dev server with "' + (process.argv[2] || env.platform.theme).bold + '" theme...') 19 | console.log(' Will listen at ' + uri.bold) 20 | if (config.dev.openBrowser) { 21 | console.log(' Browser will open when build is ready.\n') 22 | } 23 | 24 | var compiler = webpack(webpackConfig) 25 | 26 | // Define HTTP proxies to your custom API backend 27 | // https://github.com/chimurai/http-proxy-middleware 28 | var proxyTable = config.dev.proxyTable 29 | 30 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 31 | publicPath: webpackConfig.output.publicPath, 32 | quiet: true 33 | }) 34 | 35 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 36 | log: function () {} 37 | }) 38 | 39 | // force page reload when html-webpack-plugin template changes 40 | compiler.plugin('compilation', function (compilation) { 41 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 42 | hotMiddleware.publish({ action: 'reload' }) 43 | cb() 44 | }) 45 | }) 46 | 47 | // proxy requests like API. See /config/index.js -> dev.proxyTable 48 | // https://github.com/chimurai/http-proxy-middleware 49 | Object.keys(proxyTable).forEach(function (context) { 50 | var options = proxyTable[context] 51 | if (typeof options === 'string') { 52 | options = { target: options } 53 | } 54 | app.use(proxyMiddleware(context, options)) 55 | }) 56 | 57 | // handle fallback for HTML5 history API 58 | app.use(require('connect-history-api-fallback')()) 59 | 60 | // serve webpack bundle output 61 | app.use(devMiddleware) 62 | 63 | // enable hot-reload and state-preserving 64 | // compilation error display 65 | app.use(hotMiddleware) 66 | 67 | // serve pure static assets 68 | var staticsPath = path.posix.join(webpackConfig.output.publicPath, 'statics/') 69 | app.use(staticsPath, express.static('./src/statics')) 70 | 71 | // try to serve Cordova statics for Play App 72 | app.use(express.static(env.platform.cordovaAssets)) 73 | 74 | module.exports = app.listen(port, function (err) { 75 | if (err) { 76 | console.log(err) 77 | process.exit(1) 78 | } 79 | 80 | // open browser if set so in /config/index.js 81 | if (config.dev.openBrowser) { 82 | devMiddleware.waitUntilValid(function () { 83 | opn(uri) 84 | }) 85 | } 86 | }) 87 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var 2 | path = require('path'), 3 | webpack = require('webpack'), 4 | config = require('../config'), 5 | cssUtils = require('./css-utils'), 6 | env = require('./env-utils'), 7 | merge = require('webpack-merge'), 8 | projectRoot = path.resolve(__dirname, '../'), 9 | ProgressBarPlugin = require('progress-bar-webpack-plugin'), 10 | useCssSourceMap = 11 | (env.dev && config.dev.cssSourceMap) || 12 | (env.prod && config.build.productionSourceMap) 13 | 14 | function resolve (dir) { 15 | return path.join(__dirname, '..', dir) 16 | } 17 | 18 | module.exports = { 19 | entry: { 20 | app: './src/main.js' 21 | }, 22 | output: { 23 | path: path.resolve(__dirname, '../dist'), 24 | publicPath: config[env.prod ? 'build' : 'dev'].publicPath, 25 | filename: 'js/[name].js', 26 | chunkFilename: 'js/[id].[chunkhash].js' 27 | }, 28 | resolve: { 29 | extensions: ['.js', '.vue', '.json'], 30 | modules: [ 31 | resolve('src'), 32 | resolve('node_modules') 33 | ], 34 | alias: config.aliases 35 | }, 36 | module: { 37 | rules: [ 38 | { // eslint 39 | enforce: 'pre', 40 | test: /\.(vue|js)$/, 41 | loader: 'eslint-loader', 42 | include: projectRoot, 43 | exclude: /node_modules/, 44 | options: { 45 | formatter: require('eslint-friendly-formatter') 46 | } 47 | }, 48 | { 49 | test: /\.js$/, 50 | loader: 'babel-loader', 51 | include: projectRoot, 52 | exclude: /node_modules/ 53 | }, 54 | { 55 | test: /\.vue$/, 56 | loader: 'vue-loader', 57 | options: { 58 | postcss: cssUtils.postcss, 59 | loaders: merge({js: 'babel-loader'}, cssUtils.styleLoaders({ 60 | sourceMap: useCssSourceMap, 61 | extract: env.prod 62 | })) 63 | } 64 | }, 65 | { 66 | test: /\.json$/, 67 | loader: 'json-loader' 68 | }, 69 | { 70 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 71 | loader: 'url-loader', 72 | options: { 73 | limit: 10000, 74 | name: 'img/[name].[hash:7].[ext]' 75 | } 76 | }, 77 | { 78 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 79 | loader: 'url-loader', 80 | options: { 81 | limit: 10000, 82 | name: 'fonts/[name].[hash:7].[ext]' 83 | } 84 | } 85 | ] 86 | }, 87 | plugins: [ 88 | new webpack.DefinePlugin({ 89 | 'process.env': config[env.prod ? 'build' : 'dev'].env, 90 | 'DEV': env.dev, 91 | 'PROD': env.prod, 92 | '__THEME': '"' + env.platform.theme + '"' 93 | }), 94 | new webpack.LoaderOptionsPlugin({ 95 | minimize: env.prod, 96 | options: { 97 | context: path.resolve(__dirname, '../src'), 98 | postcss: cssUtils.postcss 99 | } 100 | }), 101 | new ProgressBarPlugin({ 102 | format: config.progressFormat 103 | }) 104 | ], 105 | performance: { 106 | hints: false 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var 2 | config = require('../config'), 3 | webpack = require('webpack'), 4 | merge = require('webpack-merge'), 5 | cssUtils = require('./css-utils'), 6 | baseWebpackConfig = require('./webpack.base.conf'), 7 | HtmlWebpackPlugin = require('html-webpack-plugin'), 8 | FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | 10 | // add hot-reload related code to entry chunks 11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 12 | baseWebpackConfig.entry[name] = ['./build/hot-reload.js', baseWebpackConfig.entry[name]] 13 | }) 14 | 15 | module.exports = merge(baseWebpackConfig, { 16 | // eval-source-map is faster for development 17 | devtool: '#cheap-module-eval-source-map', 18 | devServer: { 19 | historyApiFallback: true, 20 | noInfo: true 21 | }, 22 | module: { 23 | rules: cssUtils.styleRules({ 24 | sourceMap: config.dev.cssSourceMap, 25 | postcss: true 26 | }) 27 | }, 28 | plugins: [ 29 | new webpack.HotModuleReplacementPlugin(), 30 | new webpack.NoEmitOnErrorsPlugin(), 31 | new HtmlWebpackPlugin({ 32 | filename: 'index.html', 33 | template: 'src/index.html', 34 | inject: true 35 | }), 36 | new FriendlyErrorsPlugin({ 37 | clearConsole: config.dev.clearConsoleOnRebuild 38 | }) 39 | ], 40 | performance: { 41 | hints: false 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var 2 | path = require('path'), 3 | config = require('../config'), 4 | cssUtils = require('./css-utils'), 5 | webpack = require('webpack'), 6 | merge = require('webpack-merge'), 7 | baseWebpackConfig = require('./webpack.base.conf'), 8 | ExtractTextPlugin = require('extract-text-webpack-plugin'), 9 | HtmlWebpackPlugin = require('html-webpack-plugin'), 10 | OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | module.exports = merge(baseWebpackConfig, { 13 | module: { 14 | rules: cssUtils.styleRules({ 15 | sourceMap: config.build.productionSourceMap, 16 | extract: true, 17 | postcss: true 18 | }) 19 | }, 20 | devtool: config.build.productionSourceMap ? '#source-map' : false, 21 | plugins: [ 22 | new webpack.optimize.UglifyJsPlugin({ 23 | sourceMap: config.build.productionSourceMap, 24 | minimize: true, 25 | compress: { 26 | warnings: false 27 | } 28 | }), 29 | // Compress extracted CSS. We are using this plugin so that possible 30 | // duplicated CSS from different components can be deduped. 31 | new OptimizeCSSPlugin({ 32 | cssProcessorOptions: { 33 | safe: true 34 | } 35 | }), 36 | // extract css into its own file 37 | new ExtractTextPlugin({ 38 | filename: '[name].[contenthash].css' 39 | }), 40 | new HtmlWebpackPlugin({ 41 | filename: path.resolve(__dirname, '../dist/index.html'), 42 | template: 'src/index.html', 43 | inject: true, 44 | minify: { 45 | removeComments: true, 46 | collapseWhitespace: true, 47 | removeAttributeQuotes: true 48 | // more options: 49 | // https://github.com/kangax/html-minifier#options-quick-reference 50 | }, 51 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 52 | chunksSortMode: 'dependency' 53 | }), 54 | // split vendor js into its own file 55 | new webpack.optimize.CommonsChunkPlugin({ 56 | name: 'vendor', 57 | minChunks: function (module, count) { 58 | // any required modules inside node_modules are extracted to vendor 59 | return ( 60 | module.resource && 61 | /\.js$/.test(module.resource) && 62 | ( 63 | module.resource.indexOf('quasar') > -1 || 64 | module.resource.indexOf( 65 | path.join(__dirname, '../node_modules') 66 | ) === 0 67 | ) 68 | ) 69 | } 70 | }), 71 | // extract webpack runtime and module manifest to its own file in order to 72 | // prevent vendor hash from being updated whenever app bundle is updated 73 | new webpack.optimize.CommonsChunkPlugin({ 74 | name: 'manifest', 75 | chunks: ['vendor'] 76 | }) 77 | ] 78 | }) 79 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | // Webpack aliases 5 | aliases: { 6 | quasar: path.resolve(__dirname, '../node_modules/quasar-framework/'), 7 | src: path.resolve(__dirname, '../src'), 8 | assets: path.resolve(__dirname, '../src/assets'), 9 | '@': path.resolve(__dirname, '../src/components'), 10 | variables: path.resolve(__dirname, '../src/themes/quasar.variables.styl') 11 | }, 12 | 13 | // Progress Bar Webpack plugin format 14 | // https://github.com/clessg/progress-bar-webpack-plugin#options 15 | progressFormat: ' [:bar] ' + ':percent'.bold + ' (:msg)', 16 | 17 | // Default theme to build with ('ios' or 'mat') 18 | defaultTheme: 'mat', 19 | 20 | build: { 21 | env: require('./prod.env'), 22 | publicPath: '', 23 | productionSourceMap: false, 24 | 25 | // Remove unused CSS 26 | // Disable it if it has side-effects for your specific app 27 | purifyCSS: true 28 | }, 29 | dev: { 30 | env: require('./dev.env'), 31 | cssSourceMap: true, 32 | // auto open browser or not 33 | openBrowser: true, 34 | publicPath: '/', 35 | port: 8080, 36 | 37 | // If for example you are using Quasar Play 38 | // to generate a QR code then on each dev (re)compilation 39 | // you need to avoid clearing out the console, so set this 40 | // to "false", otherwise you can set it to "true" to always 41 | // have only the messages regarding your last (re)compilation. 42 | clearConsoleOnRebuild: false, 43 | 44 | // Proxy your API if using any. 45 | // Also see /build/script.dev.js and search for "proxy api requests" 46 | // https://github.com/chimurai/http-proxy-middleware 47 | proxyTable: {} 48 | } 49 | } 50 | 51 | /* 52 | * proxyTable example: 53 | * 54 | proxyTable: { 55 | // proxy all requests starting with /api 56 | '/api': { 57 | target: 'https://some.address.com/api', 58 | changeOrigin: true, 59 | pathRewrite: { 60 | '^/api': '' 61 | } 62 | } 63 | } 64 | */ 65 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-app", 3 | "productName": "Quasar App", 4 | "version": "0.0.1", 5 | "description": "A Quasar App", 6 | "author": "Your Name ", 7 | "scripts": { 8 | "clean": "node build/script.clean.js", 9 | "dev": "node build/script.dev.js", 10 | "build": "node build/script.build.js", 11 | "lint": "eslint --ext .js,.vue src" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.16.2", 15 | "babel-runtime": "^6.25.0", 16 | "node-sass": "^4.5.3", 17 | "quasar-extras": "0.x", 18 | "quasar-framework": "^0.14.1", 19 | "sass-loader": "^6.0.6", 20 | "vue": "~2.3.4", 21 | "vue-router": "^2.7.0" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^6.4.0", 25 | "babel-core": "^6.0.0", 26 | "babel-eslint": "^7.0.0", 27 | "babel-loader": "^7.0.0", 28 | "babel-plugin-transform-runtime": "^6.0.0", 29 | "babel-preset-es2015": "^6.0.0", 30 | "babel-preset-stage-2": "^6.0.0", 31 | "colors": "^1.1.2", 32 | "connect-history-api-fallback": "^1.1.0", 33 | "css-loader": "^0.28.0", 34 | "es6-promise": "^4.1.1", 35 | "eslint": "^4.4.1", 36 | "eslint-config-standard": "^10.2.1", 37 | "eslint-friendly-formatter": "^3.0.0", 38 | "eslint-loader": "^1.9.0", 39 | "eslint-plugin-html": "^3.1.1", 40 | "eslint-plugin-import": "^2.7.0", 41 | "eslint-plugin-node": "^5.1.1", 42 | "eslint-plugin-promise": "^3.5.0", 43 | "eslint-plugin-standard": "^3.0.1", 44 | "eventsource-polyfill": "^0.9.6", 45 | "express": "^4.15.4", 46 | "extract-text-webpack-plugin": "^3.0.0", 47 | "file-loader": "^0.11.1", 48 | "friendly-errors-webpack-plugin": "^1.1.3", 49 | "glob": "^7.1.2", 50 | "html-webpack-plugin": "^2.30.1", 51 | "http-proxy-middleware": "^0.17.0", 52 | "json-loader": "^0.5.7", 53 | "opn": "^5.0.0", 54 | "optimize-css-assets-webpack-plugin": "^3.0.0", 55 | "postcss-loader": "^1.0.0", 56 | "progress-bar-webpack-plugin": "^1.10.0", 57 | "purify-css": "^1.2.6", 58 | "shelljs": "^0.7.0", 59 | "stylus": "^0.54.5", 60 | "stylus-loader": "^3.0.1", 61 | "url-loader": "^0.5.7", 62 | "vue-loader": "~12.2.2", 63 | "vue-style-loader": "^3.0.1", 64 | "vue-template-compiler": "~2.3.4", 65 | "webpack": "^3.5.2", 66 | "webpack-dev-middleware": "^1.12.0", 67 | "webpack-hot-middleware": "^2.18.2", 68 | "webpack-merge": "^4.1.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/api/routes.js: -------------------------------------------------------------------------------- 1 | import { 2 | BASE_TOKEN, 3 | API_URL, 4 | } from 'config/auth' 5 | 6 | 7 | export default { 8 | TOKEN_URL: BASE_TOKEN + 'oauth/token', 9 | CURRENT_USER_URL : API_URL + "users/user", 10 | endpoints: { 11 | USERS_URL: API_URL + "users", 12 | POSTS_URL: API_URL + "posts", 13 | // resource_url : API_URL + "resource" 14 | } 15 | } -------------------------------------------------------------------------------- /src/assets/quasar-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 66 | 69 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 104 | 105 | 106 | 107 | 113 | 118 | 126 | 133 | 142 | 151 | 160 | 169 | 178 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/bootstrap/index.js: -------------------------------------------------------------------------------- 1 | import OAuth from 'oauth' 2 | 3 | import AxiosInterceptor from 'src/interceptors/axios' 4 | import RouterInterceptor from 'src/interceptors/v-router' 5 | 6 | export default 7 | { 8 | install(Vue, options){ 9 | //In this way we'll expose our class in all Vue components 10 | Vue.prototype.$oauth = new OAuth(); 11 | 12 | Vue.mixin({ 13 | mounted(){ 14 | console.log("Bootstrapping") 15 | } 16 | }) 17 | } 18 | } -------------------------------------------------------------------------------- /src/components/Error404.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 54 | 55 | 73 | -------------------------------------------------------------------------------- /src/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/config/auth.js: -------------------------------------------------------------------------------- 1 | export const STATUS = 'local'; 2 | 3 | export const BASE_URL = { 4 | 'local' : 'http://localhost:8000/', 5 | 'dev' : 'https://api-dev.myapp.com/', 6 | 'production' : 'https://api.myapp.com/', 7 | } 8 | 9 | export const API_URL = BASE_URL[STATUS] + 'api/' 10 | export const BASE_TOKEN = BASE_URL[STATUS] 11 | 12 | function getSecret(value = ''){ 13 | return 'API_' + value.toUpperCase() 14 | } 15 | 16 | let secrets = { 17 | API_LOCAL : 'dPQPMwjpK3oAjIFwrJKOZxC58OoJiG3Bmc0crnWK', 18 | API_DEV : 'mysecretindev', 19 | API_PRODUCTION : 'mysecretinproduction' 20 | } 21 | 22 | let secret = getSecret(STATUS); 23 | const CLIENT_SECRET = secrets[ secret ] 24 | 25 | // src/config/auth.js 26 | export default { 27 | oauth : { 28 | grant_type : 'password', 29 | client_id : 2, 30 | client_secret : CLIENT_SECRET, 31 | scope : '*' 32 | }, 33 | default_storage : 'Cookies', //Supported Types 'Cookies', 'Localstorage', 34 | oauth_type : 'Bearer', 35 | } 36 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Quasar App 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/interceptors/axios.js: -------------------------------------------------------------------------------- 1 | import http from 'axios' 2 | import OAuth from 'src/oauth' 3 | let oAuth = new OAuth(); 4 | 5 | http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' 6 | 7 | // Add a request interceptor 8 | http.interceptors.request.use(function (config) { 9 | 10 | config.headers['Authorization'] = oAuth.getAuthHeader() //Example: Bearer asf3132dfsfddffd 11 | 12 | return config; 13 | }, function (error) { 14 | // Do something with request error 15 | return Promise.reject(error); 16 | }); 17 | 18 | // Add a response interceptor 19 | http.interceptors.response.use(function (response) { 20 | // Do something with response data 21 | return response; 22 | }, function (error) { 23 | // Do something with response error 24 | return Promise.reject(error); 25 | }); -------------------------------------------------------------------------------- /src/interceptors/v-router.js: -------------------------------------------------------------------------------- 1 | 2 | import OAuth from 'src/oauth' 3 | import router from 'src/router' 4 | 5 | let oAuth = new OAuth(); 6 | 7 | router.beforeEach((to,from,next) => { 8 | //If visiting login view but you already have logged in, you should not be able to see this view 9 | if (to.name == 'app.login' && oAuth.isAuthenticated()) { 10 | return next({ 11 | path: '/' 12 | }) 13 | } 14 | 15 | //If you are visiting '/' and you are a guest then, you must be redirected to login 16 | if(to.meta.requiresAuth && oAuth.guest()){ 17 | return next({ 18 | path: '/login', 19 | query: { 20 | redirect: to.fullPath 21 | } 22 | }) 23 | } 24 | 25 | return next() 26 | 27 | }) -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // === DEFAULT / CUSTOM STYLE === 2 | // WARNING! always comment out ONE of the two require() calls below. 3 | // 1. use next line to activate CUSTOM STYLE (./src/themes) 4 | // require(`./themes/app.${__THEME}.styl`) 5 | // 2. or, use next line to activate DEFAULT QUASAR STYLE 6 | require(`quasar/dist/quasar.${__THEME}.css`) 7 | // ============================== 8 | 9 | // Uncomment the following lines if you need IE11/Edge support 10 | // require(`quasar/dist/quasar.ie`) 11 | // require(`quasar/dist/quasar.ie.${__THEME}.css`) 12 | 13 | import Vue from 'vue' 14 | import Quasar from 'quasar' 15 | import router from './router' 16 | 17 | Vue.config.productionTip = false 18 | Vue.use(Quasar) // Install Quasar Framework 19 | 20 | if (__THEME === 'mat') { 21 | require('quasar-extras/roboto-font') 22 | } 23 | import 'quasar-extras/material-icons' 24 | // import 'quasar-extras/ionicons' 25 | // import 'quasar-extras/fontawesome' 26 | // import 'quasar-extras/animate' 27 | import Bootstrap from './bootstrap' 28 | Vue.use(Bootstrap) 29 | 30 | Quasar.start(() => { 31 | /* eslint-disable no-new */ 32 | new Vue({ 33 | el: '#q-app', 34 | router, 35 | render: h => h(require('./App')) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/oauth/index.js: -------------------------------------------------------------------------------- 1 | 2 | /*************************************************** 3 | * [Quasar Cookies] http://quasar-framework.org/components/cookies.html 4 | * [Quasar LocalStorage] http://quasar-framework.org/components/web-storage.html 5 | **************************************************/ 6 | 7 | import { Cookies, LocalStorage } from 'quasar' 8 | 9 | import HttpService from './service' 10 | 11 | import ConfigAuth from 'config/auth' 12 | 13 | import http from 'axios' 14 | 15 | 16 | class OAuth 17 | { 18 | constructor(){ 19 | this.storages = { 20 | Cookies, 21 | LocalStorage 22 | } 23 | this.Session = this.storages[ConfigAuth.default_storage] 24 | } 25 | 26 | logout(){ 27 | HttpService.destroySession() 28 | this.Session.remove('access_token'); 29 | this.Session.remove('refresh_token'); 30 | } 31 | 32 | guest(){ 33 | return this.Session.has('access_token') ? false : true; 34 | } 35 | 36 | isAuthenticated(){ 37 | return this.Session.has('access_token') ? true : false; 38 | } 39 | 40 | login(username, password){ 41 | let data = { 42 | username, 43 | password, 44 | } 45 | 46 | //We merge grant type and client secret stored in configuration 47 | Object.assign(data, ConfigAuth.oauth); 48 | return new Promise( (resolve, reject) => { 49 | HttpService.attemptLogin(data).then( response => { 50 | this.storeSession(response.data) 51 | this.addAuthHeaders() 52 | resolve(response) 53 | }).catch( error => { 54 | console.log("OAUTH Authentication error: ",error) 55 | reject(error) 56 | }) 57 | }) 58 | } 59 | 60 | getUser(){ 61 | if(this.Session.has('access_token')){ 62 | return new Promise( (resolve, reject) => { 63 | HttpService 64 | .currentUser() 65 | .then( response => { 66 | resolve(response) 67 | }).catch(error => { 68 | reject(error) 69 | }) 70 | }) 71 | } 72 | return new Promise( resolve => resolve(null) ) 73 | } 74 | 75 | getAuthHeader() { 76 | if(this.Session.has('access_token')){ 77 | let access_token = this.getItem('access_token') 78 | return ConfigAuth.oauth_type+" "+ access_token // Example : Bearer xk8dfwv8783dxddjk232xjshoqpx 79 | } 80 | return null 81 | } 82 | 83 | getItem(key){ 84 | if(ConfigAuth.default_storage == 'LocalStorage'){ 85 | return this.Session.get.item(key) 86 | } 87 | return this.Session.get(key) 88 | } 89 | 90 | addAuthHeaders() { 91 | let header = this.getAuthHeader(); 92 | HttpService.addAuthorizationHeader(header) 93 | } 94 | 95 | storeSession(data){ 96 | 97 | let hourInMilliSeconds = 86400; 98 | let time = data.expires_in / hourInMilliSeconds; 99 | 100 | if(ConfigAuth.default_storage == 'LocalStorage'){ 101 | this.Session.set('access_token', data.access_token) 102 | this.Session.set('refresh_token', data.access_token) 103 | }else{ 104 | 105 | /************************************* 106 | ** when the Storage is type Cookies 107 | ** we send the expires property given in days 108 | **************************************/ 109 | this.Session.set('access_token', data.access_token, { 110 | expires : time, 111 | }) 112 | /* 113 | ** We duplicate the time because, 114 | ** in theory it lasts the double of time access token duration 115 | */ 116 | this.Session.set('refresh_token', data.access_token, { 117 | expires : time * 2, 118 | }) 119 | } 120 | 121 | } 122 | 123 | } 124 | 125 | export default OAuth -------------------------------------------------------------------------------- /src/oauth/service.js: -------------------------------------------------------------------------------- 1 | import Http from 'axios' 2 | import API from 'api/routes' //It will changed to config OAuth 3 | export default { 4 | user : null, 5 | destroySession(){ 6 | this.user = null 7 | }, 8 | async currentUser(){ 9 | if(this.user) { 10 | return this.user 11 | } 12 | try{ 13 | let user = await Http.get(API.CURRENT_USER_URL) 14 | this.user = user; 15 | return user 16 | } 17 | catch(error){ 18 | return error 19 | } 20 | }, 21 | async attemptLogin(credentials){ 22 | try { 23 | let response = await Http.post(API.TOKEN_URL, credentials) 24 | return new Promise(resolve => resolve(response)) 25 | } catch(error){ 26 | return new Promise(reject => reject(error)) 27 | } 28 | }, 29 | addAuthorizationHeader(header){ 30 | Http.defaults.headers.common['Authorization'] = header 31 | } 32 | } -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter) 5 | 6 | function load (component) { 7 | // '@' is aliased to src/components 8 | return () => import(`@/${component}.vue`) 9 | } 10 | 11 | //We create this function to point to Views folder 12 | function loadView(component) { 13 | // '@' is aliased to src/components 14 | return () => import(`src/views/${component}.vue`) 15 | } 16 | 17 | export default new VueRouter({ 18 | /* 19 | * NOTE! VueRouter "history" mode DOESN'T works for Cordova builds, 20 | * it is only to be used only for websites. 21 | * 22 | * If you decide to go with "history" mode, please also open /config/index.js 23 | * and set "build.publicPath" to something other than an empty string. 24 | * Example: '/' instead of current '' 25 | * 26 | * If switching back to default "hash" mode, don't forget to set the 27 | * build publicPath back to '' so Cordova builds work again. 28 | */ 29 | 30 | routes: [ 31 | { 32 | path: '/', 33 | component: load('Hello'), 34 | name : 'app.hello', 35 | meta : { 36 | requiresAuth : true 37 | }, 38 | }, 39 | { 40 | path: '/login', 41 | component: loadView('Login'), 42 | name : 'app.login', 43 | meta : { 44 | requiresAuth : false 45 | } 46 | }, //Login Route 47 | 48 | // Always leave this last one 49 | { path: '*', component: load('Error404') } // Not found 50 | ] 51 | }) 52 | -------------------------------------------------------------------------------- /src/statics/quasar-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blogui91/vue-oauth/3f5d6eefc15f7285a8661e688ab6a51ba1f3e996/src/statics/quasar-logo.png -------------------------------------------------------------------------------- /src/themes/app.ios.styl: -------------------------------------------------------------------------------- 1 | // This file is included in the build if src/main.js imports it. 2 | // Otherwise the default iOS CSS file is bundled. 3 | // Check "DEFAULT / CUSTOM STYLE" in src/main.js 4 | 5 | // App Shared Variables 6 | // -------------------------------------------------- 7 | // Shared Stylus variables go in the app.variables.styl file 8 | @import 'app.variables' 9 | 10 | // Quasar iOS Design Stylus 11 | // -------------------------------------------------- 12 | // Custom App variables must be declared before importing Quasar. 13 | // Quasar will use its default values when a custom variable isn't provided. 14 | @import '~quasar-framework/dist/quasar.ios.styl' 15 | -------------------------------------------------------------------------------- /src/themes/app.mat.styl: -------------------------------------------------------------------------------- 1 | // This file is included in the build if src/main.js imports it. 2 | // Otherwise the default Material CSS file is bundled. 3 | // Check "DEFAULT / CUSTOM STYLE" in src/main.js 4 | 5 | // App Shared Variables 6 | // -------------------------------------------------- 7 | // Shared Stylus variables go in the app.variables.styl file 8 | @import 'app.variables' 9 | 10 | // Quasar Material Design Stylus 11 | // -------------------------------------------------- 12 | // Custom App variables must be declared before importing Quasar. 13 | // Quasar will use its default values when a custom variable isn't provided. 14 | @import '~quasar-framework/dist/quasar.mat.styl' 15 | -------------------------------------------------------------------------------- /src/themes/app.variables.styl: -------------------------------------------------------------------------------- 1 | // This file is included in the build if src/main.js imports 2 | // either app.mat.styl or app.ios.styl. 3 | // Check "DEFAULT / CUSTOM STYLE" in src/main.js 4 | 5 | // App Shared Variables 6 | // -------------------------------------------------- 7 | // To customize the look and feel of this app, you can override 8 | // the Stylus variables found in Quasar's source Stylus files. Setting 9 | // variables before Quasar's Stylus will use these variables rather than 10 | // Quasar's default Stylus variable values. Stylus variables specific 11 | // to the themes belong in either the app.ios.styl or app.mat.styl files. 12 | 13 | 14 | // App Shared Color Variables 15 | // -------------------------------------------------- 16 | // It's highly recommended to change the default colors 17 | // to match your app's branding. 18 | 19 | $primary = #027be3 20 | $secondary = #26A69A 21 | $tertiary = #555 22 | 23 | $neutral = #E0E1E2 24 | $positive = #21BA45 25 | $negative = #DB2828 26 | $info = #31CCEC 27 | $warning = #F2C037 28 | -------------------------------------------------------------------------------- /src/themes/quasar.variables.styl: -------------------------------------------------------------------------------- 1 | // 2 | // Webpack alias "variables" points to this file. 3 | // So you can import it in your app's *.vue files 4 | // inside the 12 | 13 | 14 | // First we load app's Stylus variables 15 | @import 'app.variables' 16 | 17 | // Then we load Quasar Stylus variables. 18 | // Any variables defined in "app.variables.styl" 19 | // will override Quasar's ones. 20 | // 21 | // NOTICE that we only import Core Quasar Variables 22 | // like colors, media breakpoints, and so. 23 | // No component variable will be included. 24 | @import '~quasar/dist/core.variables' 25 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 18 | 77 | 93 | 94 | -------------------------------------------------------------------------------- /templates/component.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /templates/layout.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 71 | 72 | 74 | -------------------------------------------------------------------------------- /templates/page.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 18 | --------------------------------------------------------------------------------