Vue it is

I know, the 18th front-end change; I have a feeling there won't be a
19th.
This commit is contained in:
Daniel J. Summers 2017-07-29 16:28:27 -05:00
parent 632f06ac5f
commit 338f11d1ab
31 changed files with 12054 additions and 60 deletions

View File

@ -1,12 +1,9 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true root = true
# Unix-style newlines with a newline ending every file
[*] [*]
end_of_line = lf charset = utf-8
insert_final_newline = true
# 2 space indentation
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

2
src/app/.eslintignore Normal file
View File

@ -0,0 +1,2 @@
build/*.js
config/*.js

27
src/app/.eslintrc.js Normal file
View File

@ -0,0 +1,27 @@
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}

19
src/app/.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
test/unit/coverage
test/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
# Auth0 settings
src/auth/auth0-variables.js

8
src/app/.postcssrc.js Normal file
View File

@ -0,0 +1,8 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}

21
src/app/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Daniel J. Summers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

38
src/app/config/index.js Normal file
View File

@ -0,0 +1,38 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}

View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}

View File

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})

View File

@ -2,12 +2,13 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<base href="/"> <title>myPrayerJournal</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<!-- link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css" -->
</head> </head>
<body>
<body aurelia-app="main"> <div id="app"></div>
<script src="scripts/vendor-bundle.js" data-main="aurelia-bootstrapper"></script> <!-- built files will be auto injected -->
</body> </body>
</html> </html>

11350
src/app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +1,98 @@
{ {
"name": "contact-manager", "name": "my-prayer-journal",
"description": "An Aurelia client application.", "version": "0.8.0",
"version": "0.1.0", "description": "myPrayerJournal - Front End",
"repository": { "author": "Daniel J. Summers <daniel@djs-consulting.com>",
"type": "???", "private": true,
"url": "???" "scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
}, },
"license": "MIT",
"dependencies": { "dependencies": {
"aurelia-animator-css": "^1.0.1", "EventEmitter": "^1.0.0",
"aurelia-bootstrapper": "^2.1.0", "auth0-js": "^8.8.0",
"aurelia-fetch-client": "^1.1.2", "bootstrap-vue": "^0.18.0",
"auth0-lock": "^10.16.0", "vue": "^2.3.3",
"bluebird": "^3.4.1", "vue-router": "^2.6.0"
"bootstrap": "^3.3.7",
"jquery": "^2.2.4",
"nprogress": "^0.2.0",
"requirejs": "^2.3.2",
"text": "github:requirejs/text#latest"
}, },
"peerDependencies": {},
"devDependencies": { "devDependencies": {
"aurelia-cli": "^0.27.0", "autoprefixer": "^7.1.2",
"aurelia-testing": "^1.0.0-beta.2.0.1", "babel-core": "^6.22.1",
"aurelia-tools": "^1.0.0", "babel-eslint": "^7.1.1",
"browser-sync": "^2.13.0", "babel-loader": "^7.1.1",
"connect-history-api-fallback": "^1.2.0", "babel-plugin-transform-runtime": "^6.22.0",
"gulp": "github:gulpjs/gulp#4.0", "babel-preset-env": "^1.3.2",
"gulp-changed-in-place": "^2.0.3", "babel-preset-stage-2": "^6.22.0",
"gulp-plumber": "^1.1.0", "babel-register": "^6.22.0",
"gulp-rename": "^1.2.2", "chalk": "^2.0.1",
"gulp-sourcemaps": "^2.0.0-alpha", "connect-history-api-fallback": "^1.3.0",
"gulp-notify": "^2.2.0", "copy-webpack-plugin": "^4.0.1",
"minimatch": "^3.0.2", "css-loader": "^0.28.0",
"through2": "^2.0.1", "cssnano": "^3.10.0",
"uglify-js": "^2.6.3", "eslint": "^3.19.0",
"vinyl-fs": "^2.4.3", "eslint-friendly-formatter": "^3.0.0",
"event-stream": "^3.3.3", "eslint-loader": "^1.7.1",
"gulp-typescript": "^3.1.4", "eslint-plugin-html": "^3.0.0",
"gulp-tslint": "^5.0.0", "eslint-config-standard": "^6.2.1",
"tslint": "^3.11.0", "eslint-plugin-promise": "^3.4.0",
"typescript": ">=1.9.0-dev || ^2.0.0", "eslint-plugin-standard": "^2.0.1",
"@types/node": "^6.0.45", "eventsource-polyfill": "^0.9.6",
"gulp-htmlmin": "^3.0.0", "express": "^4.14.1",
"html-minifier": "^3.2.3", "extract-text-webpack-plugin": "^2.0.0",
"jasmine-core": "^2.4.1", "file-loader": "^0.11.1",
"karma": "^0.13.22", "friendly-errors-webpack-plugin": "^1.1.3",
"karma-chrome-launcher": "^1.0.1", "html-webpack-plugin": "^2.28.0",
"karma-jasmine": "^1.0.2", "http-proxy-middleware": "^0.17.3",
"karma-typescript-preprocessor": "^0.2.1", "webpack-bundle-analyzer": "^2.2.1",
"@types/jasmine": "^2.2.0" "cross-env": "^5.0.1",
} "karma": "^1.4.1",
"karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.2",
"karma-phantomjs-shim": "^1.4.0",
"karma-sinon-chai": "^1.3.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.31",
"karma-webpack": "^2.0.2",
"lolex": "^1.5.2",
"mocha": "^3.2.0",
"chai": "^3.5.0",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0",
"inject-loader": "^3.0.0",
"babel-plugin-istanbul": "^4.1.1",
"phantomjs-prebuilt": "^2.1.14",
"chromedriver": "^2.27.2",
"cross-spawn": "^5.0.1",
"nightwatch": "^0.9.12",
"selenium-server": "^3.0.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"opn": "^5.1.0",
"optimize-css-assets-webpack-plugin": "^2.0.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"url-loader": "^0.5.8",
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
} }

73
src/app/src/App.vue Normal file
View File

@ -0,0 +1,73 @@
<template>
<div id="app">
<navigation :auth="auth" />
<div id="content" class="container">
<router-view :auth="auth"></router-view>
</div>
<footer>
<p class="text-right"><i>myPrayerJournal v0.8.0</i></p>
</footer>
</div>
</template>
<script>
import AuthService from './auth/AuthService'
import Navigation from './components/Navigation.vue'
const auth = new AuthService()
const { login, logout, authenticated, authNotifier } = auth
export default {
name: 'app',
data: function () {
authNotifier.on('authChange', authState => {
this.authenticated = authState.authenticated
})
return {
auth,
authenticated
}
},
methods: {
login,
logout
},
components: {
Navigation
}
}
</script>
<style>
@import url('../node_modules/bootstrap/dist/css/bootstrap.css');
@import url('../node_modules/bootstrap-vue/dist/bootstrap-vue.css');
body {
padding-top: 60px;
}
footer {
border-top: solid 1px lightgray;
margin-top: 1rem;
padding: 0 1rem;
}
.material-icons.md-18 {
font-size: 18px;
}
.material-icons.md-24 {
font-size: 24px;
}
.material-icons.md-36 {
font-size: 36px;
}
.material-icons.md-48 {
font-size: 48px;
}
.material-icons {
vertical-align: middle;
}
.mpj-page-title {
border-bottom: solid 1px lightgray;
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,70 @@
import auth0 from 'auth0-js'
import { AUTH_CONFIG } from './auth0-variables'
import EventEmitter from 'EventEmitter'
import router from './../router'
export default class AuthService {
authenticated = this.isAuthenticated()
authNotifier = new EventEmitter()
constructor () {
this.login = this.login.bind(this)
this.setSession = this.setSession.bind(this)
this.logout = this.logout.bind(this)
this.isAuthenticated = this.isAuthenticated.bind(this)
}
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token',
scope: 'openid'
})
login () {
this.auth0.authorize()
}
handleAuthentication () {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult)
router.replace('/dashboard')
} else if (err) {
router.replace('/')
console.log(err)
alert(`Error: ${err.error}. Check the console for further details.`)
}
})
}
setSession (authResult) {
// Set the time that the access token will expire at
let expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
)
localStorage.setItem('access_token', authResult.accessToken)
localStorage.setItem('id_token', authResult.idToken)
localStorage.setItem('expires_at', expiresAt)
this.authNotifier.emit('authChange', { authenticated: true })
}
logout () {
// Clear access token and ID token from local storage
localStorage.removeItem('access_token')
localStorage.removeItem('id_token')
localStorage.removeItem('expires_at')
this.userProfile = null
this.authNotifier.emit('authChange', false)
// navigate to the home route
router.replace('/')
}
isAuthenticated () {
// Check whether the current time is past the access token's expiry time
let expiresAt = JSON.parse(localStorage.getItem('expires_at'))
return new Date().getTime() < expiresAt
}
}

View File

@ -0,0 +1,21 @@
<template>
<article>
<page-title title="Your Dashboard" />
<p>here you are! {{ JSON.stringify(this.user) }}</p>
</article>
</template>
<script>
import PageTitle from './PageTitle'
export default {
name: 'dashboard',
props: ['user'],
data () {
return {}
},
components: {
PageTitle
}
}
</script>

View File

@ -0,0 +1,34 @@
<template>
<article>
<page-title title="Welcome!" hideOnPage="true" />
<div class="row">
<div class="col">
<p>&nbsp;</p>
<p>
myPrayerJournal is a place where individuals can record their prayer requests, record that they prayed for
them, update them as God moves in the situation, and record a final answer received on that request.&nbsp; It
will also allow individuals to review their answered prayers.
</p>
<p>
This site is currently in very limited alpha, as it is being developed with a core group of test users.&nbsp;
If this is something in which you are interested, check back around mid-November 2017 for an update on the
development progress.
</p>
</div>
</div>
</article>
</template>
<script>
import PageTitle from './PageTitle.vue'
export default {
name: 'home',
data () {
return {}
},
components: {
PageTitle
}
}
</script>

View File

@ -0,0 +1,25 @@
<template>
<b-navbar toggleable type="inverse" variant="inverse" fixed="top">
<b-nav-toggle target="navCollapse"></b-nav-toggle>
<b-link class="navbar-brand" :to="{ name: 'Home' }">
<span style="font-weight:100;">my</span><span style="font-weight:600;">Prayer</span><span style="font-weight:700;">Journal</span>
</b-link>
<b-collapse is-nav id="navCollapse">
<b-nav is-nav-bar>
<b-nav-item v-if="auth.authenticated" :to="{ name: 'Dashboard' }">Dashboard</b-nav-item>
<b-nav-item v-if="auth.authenticated" @click="auth.logout()">Log Off</b-nav-item>
<b-nav-item v-if="!auth.authenticated" @click="auth.login()">Log On</b-nav-item>
</b-nav>
</b-collapse>
</b-navbar>
</template>
<script>
export default {
name: 'navigation',
props: ['auth'],
data: function () {
return { }
}
}
</script>

View File

@ -0,0 +1,21 @@
<template>
<h2 v-if="!hideOnPage" class="mpj-page-title" v-html="title"></h2>
</template>
<script>
export default {
name: 'page-title',
props: ['title', 'hideOnPage'],
data () {
return {}
},
created () {
document.title = `${this.title} « myPrayerJournal`
},
watch: {
title () {
document.title = `${this.title} « myPrayerJournal`
}
}
}
</script>

View File

@ -0,0 +1,14 @@
<template>
<p>hang tight...</p>
</template>
<script>
export default {
name: 'log-on',
props: ['auth'],
data () {
this.auth.handleAuthentication()
return {}
}
}
</script>

18
src/app/src/main.js Normal file
View File

@ -0,0 +1,18 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
Vue.use(BootstrapVue)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})

View File

@ -0,0 +1,17 @@
import Vue from 'vue'
import Router from 'vue-router'
import Dashboard from '@/components/Dashboard'
import Home from '@/components/Home'
import LogOn from '@/components/user/LogOn'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{ path: '/', name: 'Home', component: Home },
{ path: '/dashboard', name: 'Dashboard', component: Dashboard },
{ path: '/user/log-on', name: 'LogOn', component: LogOn }
]
})

0
src/app/static/.gitkeep Normal file
View File

View File

@ -0,0 +1,26 @@
// A custom Nightwatch assertion.
// the name of the method is the filename.
// can be used in tests like this:
//
// browser.assert.elementCount(selector, count)
//
// for how to write custom assertions see
// http://nightwatchjs.org/guide#writing-custom-assertions
exports.assertion = function (selector, count) {
this.message = 'Testing if element <' + selector + '> has count: ' + count
this.expected = count
this.pass = function (val) {
return val === this.expected
}
this.value = function (res) {
return res.value
}
this.command = function (cb) {
var self = this
return this.api.execute(function (selector) {
return document.querySelectorAll(selector).length
}, [selector], function (res) {
cb.call(self, res)
})
}
}

View File

@ -0,0 +1,46 @@
require('babel-register')
var config = require('../../config')
// http://nightwatchjs.org/gettingstarted#settings-file
module.exports = {
src_folders: ['test/e2e/specs'],
output_folder: 'test/e2e/reports',
custom_assertions_path: ['test/e2e/custom-assertions'],
selenium: {
start_process: true,
server_path: require('selenium-server').path,
host: '127.0.0.1',
port: 4444,
cli_args: {
'webdriver.chrome.driver': require('chromedriver').path
}
},
test_settings: {
default: {
selenium_port: 4444,
selenium_host: 'localhost',
silent: true,
globals: {
devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
}
},
chrome: {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true
}
},
firefox: {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true
}
}
}
}

View File

@ -0,0 +1,33 @@
// 1. start the dev server using production config
process.env.NODE_ENV = 'testing'
var server = require('../../build/dev-server.js')
server.ready.then(() => {
// 2. run the nightwatch test suite against it
// to run in additional browsers:
// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
// 2. add it to the --env flag below
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
// For more information on Nightwatch's config file, see
// http://nightwatchjs.org/guide#settings-file
var opts = process.argv.slice(2)
if (opts.indexOf('--config') === -1) {
opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
}
if (opts.indexOf('--env') === -1) {
opts = opts.concat(['--env', 'chrome'])
}
var spawn = require('cross-spawn')
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
runner.on('exit', function (code) {
server.close()
process.exit(code)
})
runner.on('error', function (err) {
server.close()
throw err
})
})

View File

@ -0,0 +1,19 @@
// For authoring Nightwatch tests, see
// http://nightwatchjs.org/guide#usage
module.exports = {
'default e2e tests': function (browser) {
// automatically uses dev Server port from /config.index.js
// default: http://localhost:8080
// see nightwatch.conf.js
const devServer = browser.globals.devServerURL
browser
.url(devServer)
.waitForElementVisible('#app', 5000)
.assert.elementPresent('.hello')
.assert.containsText('h1', 'Welcome to Your Vue.js App')
.assert.elementCount('img', 1)
.end()
}
}

View File

@ -0,0 +1,9 @@
{
"env": {
"mocha": true
},
"globals": {
"expect": true,
"sinon": true
}
}

View File

@ -0,0 +1,13 @@
import Vue from 'vue'
Vue.config.productionTip = false
// require all test files (files that ends with .spec.js)
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
// require all src files except main.js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)

View File

@ -0,0 +1,33 @@
// This is a karma config file. For more details see
// http://karma-runner.github.io/0.13/config/configuration-file.html
// we are also using it with karma-webpack
// https://github.com/webpack/karma-webpack
var webpackConfig = require('../../build/webpack.test.conf')
module.exports = function (config) {
config.set({
// to run in additional browsers:
// 1. install corresponding karma launcher
// http://karma-runner.github.io/0.13/config/browsers.html
// 2. add it to the `browsers` array below.
browsers: ['PhantomJS'],
frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
reporters: ['spec', 'coverage'],
files: ['./index.js'],
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
},
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
}
})
}

View File