Remove v2 Vue app

This commit is contained in:
Daniel J. Summers 2021-10-26 11:26:08 -04:00
parent 619c94f5ed
commit 5f47d15877
48 changed files with 23 additions and 16583 deletions

View File

@ -131,8 +131,8 @@ with
/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains
/// properties that may be filled for history and notes.
[<NoComparison; NoEquality>]
type JournalRequest =
{ /// The ID of the request (just the CUID part)
type JournalRequest = {
/// The ID of the request (just the CUID part)
requestId : RequestId
/// The ID of the user to whom the request belongs
userId : UserId

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not ie <= 8

View File

@ -1,18 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: '@typescript-eslint/parser'
}
}

24
src/app/.gitignore vendored
View File

@ -1,24 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
# Auth0 settings
src/auth/auth0-variables.*

View File

@ -1,5 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

View File

@ -1,21 +0,0 @@
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

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

13749
src/app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
{
"name": "my-prayer-journal",
"version": "3.0.0",
"private": true,
"description": "myPrayerJournal - Front End",
"author": "Daniel J. Summers <daniel@bitbadger.solutions>",
"scripts": {
"serve": "vue-cli-service serve --port 8081",
"build": "vue-cli-service build --modern",
"lint": "vue-cli-service lint",
"apistart": "cd ../MyPrayerJournal.Api && dotnet run",
"publish": "vue-cli-service build --modern && cd ../MyPrayerJournal.Api && dotnet publish -c Release -r linux-x64 --self-contained false",
"vue": "vue-cli-service build --modern && cd ../MyPrayerJournal.Api && dotnet run"
},
"dependencies": {
"@types/events": "^3.0.0",
"@vue/composition-api": "^0.5.0",
"auth0-js": "^9.7.3",
"axios": "^0.19.0",
"core-js": "^3.3.2",
"date-fns": "^2.14.0",
"vue": "^2.5.15",
"vue-material": "^1.0.0-beta-14",
"vue-router": "^3.3.2",
"vuex": "^3.4.0"
},
"devDependencies": {
"@types/auth0-js": "^9.13.1",
"@types/node": "^13.13.10",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"@vue/cli-plugin-babel": "^4.4.1",
"@vue/cli-plugin-eslint": "^4.4.1",
"@vue/cli-plugin-typescript": "^4.4.1",
"@vue/cli-service": "^4.4.1",
"@vue/eslint-config-standard": "^5.0.0",
"@vue/eslint-config-typescript": "^5.0.0",
"eslint": "^6.6.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.0.1",
"node-sass": "^4.14.1",
"pug": "^2.0.1",
"pug-plain-loader": "^1.0.0",
"sass-loader": "^8.0.0",
"typescript": "^3.9.5",
"vue-template-compiler": "^2.5.17",
"webpack-bundle-analyzer": "^3.8.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="preload" as="style">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<title>myPrayerJournal</title>
</head>
<body>
<noscript>
<strong>We're sorry but newapp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,218 +0,0 @@
<template lang="pug">
#app.page-container
md-app(md-waterfall md-mode='fixed-last' role='application')
md-app-toolbar.md-large.md-dense.md-primary
md-progress-bar(v-if='progress.visible'
:md-mode='progress.mode')
.md-no-progress-bar(v-if='!progress.visible')
.md-toolbar-row
.md-toolbar-section-start
router-link(to='/').md-title
span(style='font-weight:100;') my
span(style='font-weight:400;') Prayer
span(style='font-weight:700;') Journal
navigation
md-app-content
md-progress-bar(v-if='progress.visible.value'
:md-mode='progress.mode.value')
router-view
md-snackbar(:md-active.sync='snackbar.visible.value'
md-position='center'
:md-duration='snackbar.interval.value'
ref='snackbar') {{ snackbar.message.value }}
footer
p.mpj-muted-text.mpj-text-right
| myPrayerJournal v{{ version }}
br
em: small.
#[router-link(to='/legal/privacy-policy') Privacy Policy] &bull;
#[router-link(to='/legal/terms-of-service') Terms of Service] &bull;
#[a(href='https://github.com/bit-badger/myprayerjournal' target='_blank') Developed] and hosted by
#[a(href='https://bitbadger.solutions' target='_blank') Bit Badger Solutions]
</template>
<script lang="ts">
import Vue from 'vue'
import { computed, createComponent, inject, onMounted, provide, ref } from '@vue/composition-api'
import Navigation from '@/components/common/Navigation.vue'
import auth from './auth'
import router from './router'
import store from './store'
import { Actions } from './store/types'
import { SnackbarProps, ProgressProps } from './types' // eslint-disable-line no-unused-vars
import { provideAuth } from './plugins/auth'
import { provideRouter } from './plugins/router'
import { provideStore } from './plugins/store'
function setupSnackbar (): SnackbarProps {
const events = new Vue()
const visible = ref(false)
const message = ref('')
const interval = ref(4000)
const showSnackbar = (msg: string) => {
message.value = msg
visible.value = true
}
const showInfo = (msg: string) => {
interval.value = 4000
showSnackbar(msg)
}
const showError = (msg: string) => {
interval.value = Infinity
showSnackbar(msg)
}
onMounted(() => {
events.$on('info', showInfo)
events.$on('error', showError)
})
return {
events,
visible,
message,
interval,
showSnackbar,
showInfo,
showError
}
}
function setupProgress (): ProgressProps {
const events = new Vue()
const visible = ref(false)
const mode = ref('query')
const showProgress = (mod: string) => {
mode.value = mod
visible.value = true
}
const hideProgress = () => { visible.value = false }
onMounted(() => {
events.$on('show', showProgress)
events.$on('done', hideProgress)
})
return {
events,
visible,
mode,
showProgress,
hideProgress
}
}
const SnackbarSymbol = Symbol('Snackbar events')
const ProgressSymbol = Symbol('Progress events')
export default createComponent({
name: 'app',
components: { Navigation },
setup () {
const pkg = require('../package.json')
provideAuth(auth)
provideRouter(router)
provideStore(store)
const version = computed(() =>
pkg.version.endsWith('.0')
? pkg.version.endsWith('.0.0')
? pkg.version.substr(0, pkg.version.length - 4)
: pkg.version.substr(0, pkg.version.length - 2)
: pkg.version)
const progress = setupProgress()
const snackbar = setupSnackbar()
onMounted(async () => store.dispatch(Actions.CheckAuthentication))
provide(SnackbarSymbol, snackbar.events)
provide(ProgressSymbol, progress.events)
return {
version,
progress,
snackbar
}
}
})
export function useSnackbar () {
const snackbar = inject(SnackbarSymbol)
if (!snackbar) {
throw new Error('Snackbar not configured')
}
return snackbar as SnackbarProps
}
export function useProgress () {
const progress = inject(ProgressSymbol)
if (!progress) {
throw new Error('Progress not configured')
}
return progress as ProgressProps
}
</script>
<style lang="sass">
@import "~vue-material/dist/theme/engine"
@include md-register-theme("default", (primary: md-get-palette-color(green, 800), accent: md-get-palette-color(gray, 700)))
@import "~vue-material/dist/theme/all"
html, body
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
font-size: 1rem
p
margin-bottom: 0
footer
border-top: solid 1px lightgray
margin: 1rem -1rem 0
padding: 0 1rem
footer p
margin: 0
.mpj-full-page-card
font-size: 1rem
line-height: 1.25rem
.mpj-main-content
max-width: 60rem
margin: auto
.mpj-request-text
white-space: pre-line
p.mpj-request-text
margin-top: 0
.mpj-text-center
text-align: center
.mpj-text-nowrap
white-space: nowrap
.mpj-text-right
text-align: right
.mpj-muted-text
color: rgba(0, 0, 0, .6)
.mpj-valign-top
vertical-align: top
.mpj-narrow
max-width: 40rem
margin: auto
.mpj-skinny
max-width: 20rem
margin: auto
.mpj-full-width
width: 100%
.md-toolbar > .md-progress-bar
height: 2px
width: 100%
background-color: rgba(255, 255, 255, .8) !important
margin: 0
.md-toolbar > .md-no-progress-bar
height: 2px
width: 100%
</style>

View File

@ -1,100 +0,0 @@
'use strict'
import axios from 'axios'
const http = axios.create({
baseURL: `${location.protocol}//${location.host}/api/`
})
/**
* API access for myPrayerJournal
*/
export default {
/**
* Set the bearer token for all future requests
* @param token The token to use to identify the user to the server
*/
setBearer: (token: string) => { http.defaults.headers.common.Authorization = `Bearer ${token}` },
/**
* Remove the bearer token
*/
removeBearer: () => delete http.defaults.headers.common.Authorization,
/**
* Add a note for a prayer request
* @param requestId The Id of the request to which the note applies
* @param notes The notes to be added
*/
addNote: (requestId: string, notes: string) => http.post(`request/${requestId}/note`, { notes }),
/**
* Add a new prayer request
* @param requestText The text of the request to be added
* @param recurType The type of recurrence for this request
* @param recurCount The number of intervals of recurrence
*/
addRequest: (requestText: string, recurType: string, recurCount: number) =>
http.post('request', { requestText, recurType, recurCount }),
/**
* Get all answered requests, along with the text they had when it was answered
*/
getAnsweredRequests: () => http.get('requests/answered'),
/**
* Get a prayer request (full; includes all history and notes)
* @param requestId The Id of the request to retrieve
*/
getFullRequest: (requestId: string) => http.get(`request/${requestId}/full`),
/**
* Get past notes for a prayer request
* @param requestId The Id of the request for which notes should be retrieved
*/
getNotes: (requestId: string) => http.get(`request/${requestId}/notes`),
/**
* Get a prayer request (journal-style; only latest update)
* @param requestId The Id of the request to retrieve
*/
getRequest: (requestId: string) => http.get(`request/${requestId}`),
/**
* Get all prayer requests and their most recent updates
*/
journal: () => http.get('journal'),
/**
* Show a request after the given date (used for "show now")
* @param requestId The ID of the request which should be shown
* @param showAfter The ticks after which the request should be shown
*/
showRequest: (requestId: string, showAfter: number) => http.patch(`request/${requestId}/show`, { showAfter }),
/**
* Snooze a request until the given time
* @param requestId The ID of the prayer request to be snoozed
* @param until The ticks until which the request should be snoozed
*/
snoozeRequest: (requestId: string, until: number) => http.patch(`request/${requestId}/snooze`, { until }),
/**
* Update recurrence for a prayer request
* @param requestId The ID of the prayer request for which recurrence is being updated
* @param recurType The type of recurrence to set
* @param recurCount The number of recurrence intervals to set
*/
updateRecurrence: (requestId: string, recurType: string, recurCount: number) =>
http.patch(`request/${requestId}/recurrence`, { recurType, recurCount }),
/**
* Update a prayer request
* @param requestId The ID of the request to be updated
* @param status The status of the update
* @param updateText The text of the update (optional)
*/
updateRequest: (requestId: string, status: string, updateText: string) =>
http.post(`request/${requestId}/history`, { status, updateText })
}

View File

@ -1,184 +0,0 @@
import { Store } from 'vuex'
import auth0, { Auth0DecodedHash } from 'auth0-js'
import { EventEmitter } from 'events'
import { Mutations, AppState } from '@/store/types'
import Auth0Config from './auth0-variables'
import { Session, Token } from './types'
// Auth0 web authentication instance to use for our calls
const webAuth = new auth0.WebAuth({
domain: Auth0Config.domain,
clientID: Auth0Config.clientId,
redirectUri: Auth0Config.appDomain + Auth0Config.callbackUrl,
audience: `https://${Auth0Config.domain}/userinfo`,
responseType: 'token id_token',
scope: 'openid profile email'
})
/**
* A class to handle all authentication calls and determinations
*/
export class AuthService extends EventEmitter {
// Local storage key for our session data
AUTH_SESSION = 'auth-session'
// Received and calculated values for our ssesion (initially loaded from local storage if present)
session = new Session()
constructor () {
super()
this.refreshSession()
}
/**
* Starts the user log in flow
* @param customState Application state to be returned after user has authenticated
*/
login (customState?: any) {
webAuth.authorize({
appState: customState
})
}
/**
* Promisified parseHash function
* @returns A promise that resolves with the parsed hash returned from Auth0
*/
parseHash (): Promise<Auth0DecodedHash> {
return new Promise((resolve, reject) => {
webAuth.parseHash((err, authResult) => {
if (err || authResult === null) {
reject(err)
} else {
resolve(authResult)
}
})
})
}
/**
* Handle authentication replies from Auth0
* @param store The Vuex store
*/
async handleAuthentication (store: Store<AppState>) {
try {
const authResult = await this.parseHash()
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult)
store.commit(Mutations.UserLoggedOn, this.session.profile)
}
} catch (err) {
console.error(err) // eslint-disable-line no-console
alert(`Error: ${err.error}. Check the console for further details.`)
}
}
/**
* Set up the session and commit it to local storage
* @param authResult The authorization result
*/
setSession (authResult: Auth0DecodedHash) {
this.session.profile = authResult.idTokenPayload
this.session.id = new Token(authResult.idToken!, this.session.profile.exp * 1000)
this.session.access = new Token(authResult.accessToken!, authResult.expiresIn! * 1000 + Date.now())
localStorage.setItem(this.AUTH_SESSION, JSON.stringify(this.session))
this.emit('loginEvent', {
loggedIn: true,
profile: authResult.idTokenPayload,
state: authResult.appState || {}
})
}
/**
* Refresh this instance's session from the one in local storage
*/
refreshSession () {
this.session =
localStorage.getItem(this.AUTH_SESSION)
? JSON.parse(localStorage.getItem(this.AUTH_SESSION) || '{}')
: new Session()
}
/**
* Renew authorzation tokens with Auth0
* @returns A promise with the parsed hash from the Auth0 response
*/
renewTokens (): Promise<Auth0DecodedHash> {
return new Promise((resolve, reject) => {
this.refreshSession()
if (this.session.id.token !== null) {
webAuth.checkSession({}, (err, authResult) => {
if (err) {
reject(err)
} else {
const result = authResult as Auth0DecodedHash
this.setSession(result)
resolve(result)
}
})
} else {
reject(new Error('Not logged in'))
}
})
}
/**
* Log out of myPrayerJournal
* @param store The Vuex store
*/
logout (store: Store<AppState>) {
// Clear access token and ID token from local storage
localStorage.removeItem(this.AUTH_SESSION)
this.refreshSession()
store.commit(Mutations.UserLoggedOff)
webAuth.logout({
returnTo: `${Auth0Config.appDomain}/`,
clientID: Auth0Config.clientId
})
this.emit('loginEvent', { loggedIn: false })
}
/**
* Whether the given token is currently valid
* @param t The token to be validated
* @returns True if the token is valid
*/
isTokenValid = (t: Token) => t.token !== '' && t.expiry !== 0 && Date.now() < t.expiry
/**
* Is there a user authenticated?
* @returns True if a user is authenticated
*/
isAuthenticated () {
return this.session && this.session.id && this.isTokenValid(this.session.id)
}
/**
* Is the current access token valid?
* @returns True if the user's access token is valid
*/
isAccessTokenValid () {
return this.session && this.session.access && this.isTokenValid(this.session.access)
}
/**
* Get the user's access token, renewing it if required
* @returns A promise that resolves to the user's access token
*/
async getAccessToken (): Promise<string> {
if (this.isAccessTokenValid()) {
return this.session.access.token
} else {
const authResult = await this.renewTokens()
return authResult.accessToken!
}
}
}
export default new AuthService()

View File

@ -1,21 +0,0 @@
/** A token and expiration set */
export class Token {
/**
* Create a new token
* @param token The actual token
* @param expiry The expiration for the token
*/
constructor (public token: string, public expiry: number) { } // eslint-disable-line no-useless-constructor
}
/** A user's current session */
export class Session {
/** The user's profile from Auth0 */
profile: any = {}
/** The user's ID token */
id = new Token('', 0)
/** The user's access token */
access = new Token('', 0)
}

View File

@ -1,14 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content
page-title(title='Welcome!'
hideOnPage=true)
p &nbsp;
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. It also allows
individuals to review their answered prayers.
p.
This site is open and available to the general public. To get started, simply click the &ldquo;Log On&rdquo; link
above, and log on with either a Microsoft or Google account. You can also learn more about the site at the
&ldquo;Docs&rdquo; link, also above.
</template>

View File

@ -1,94 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content-wide
page-title(:title='title')
p(v-if='isLoadingJournal') Loading your prayer journal...
template(v-else)
md-empty-state(v-if='journal.length === 0'
md-icon='done_all'
md-label='No Requests to Show'
md-description='You have no requests to be shown; see the “Active” link above for snoozed/deferred requests, and the “Answered” link for answered requests')
md-button(:to="{ name: 'EditRequest', params: { id: 'new' } }").md-primary.md-raised Add a New Request
template(v-else)
.mpj-text-center
md-button(:to="{ name: 'EditRequest', params: { id: 'new' } }"
role='button').md-raised.md-accent #[md-icon add_box] Add a New Request
br
.mpj-journal
request-card(v-for='request in journal'
:key='request.requestId'
:request='request')
notes-edit
snooze-request
</template>
<script lang="ts">
import Vue from 'vue'
import { computed, createComponent, inject, onBeforeMount, provide } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import NotesEdit from './request/NotesEdit.vue'
import RequestCard from './request/RequestCard.vue'
import SnoozeRequest from './request/SnoozeRequest.vue'
import { Actions, AppState } from '../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../plugins/store'
import { useSnackbar, useProgress } from '../App.vue'
const EventSymbol = Symbol('Journal events')
export default createComponent({
components: {
NotesEdit,
RequestCard,
SnoozeRequest
},
setup () {
/** The Vuex store */
const store = useStore() as Store<AppState>
/** The title of the page */
const title = computed(() => `${store.state.user.given_name}&rsquo;s Prayer Journal`)
/** Events to which the journal will respond */
const eventBus = new Vue()
/** Reference to the application's snackbar component */
const snackbar = useSnackbar()
/** Reference to the application's progress bar component */
const progress = useProgress()
/** Provide the event bus for child components */
provide(EventSymbol, eventBus)
onBeforeMount(async () => {
await store.dispatch(Actions.LoadJournal, progress)
snackbar.events.$emit('info', `Loaded ${store.state.journal.length} prayer requests`)
})
return {
title,
journal: store.state.journal,
isLoadingJournal: store.state.isLoadingJournal
}
}
})
export function useEvents () {
const events = inject(EventSymbol)
if (!events) {
throw new Error('Event bus not configured')
}
return events as Vue
}
</script>
<style lang="sass">
.mpj-journal
display: flex
flex-flow: row wrap
justify-content: center
align-items: flex-start
.mpj-dialog-content
padding: 0 1rem
</style>

View File

@ -1,50 +0,0 @@
<script lang="ts">
import { computed, createComponent, createElement, onBeforeUnmount, onMounted, ref } from '@vue/composition-api'
import { format, formatDistance } from 'date-fns'
export default createComponent({
props: {
tag: {
type: String,
default: 'span'
},
value: {
type: Number,
default: 0
},
interval: {
type: Number,
default: 10000
}
},
setup (props) {
/** Interval ID for updating relative time */
let intervalId: number = 0
/** The relative time string */
const fromNow = ref(formatDistance(props.value, Date.now(), { addSuffix: true }))
/** The actual date/time (used as the title for the relative time) */
const actual = computed(() => format(props.value, 'PPPPp'))
/** Update the relative time string if it is different */
const updateFromNow = () => {
const newFromNow = formatDistance(props.value, Date.now(), { addSuffix: true })
if (newFromNow !== fromNow.value) fromNow.value = newFromNow
}
/** Refresh the relative time string to keep it accurate */
onMounted(() => { intervalId = setInterval(updateFromNow, props.interval) })
/** Cancel refreshing the time string represented with this component */
onBeforeUnmount(() => clearInterval(intervalId))
return () => createElement(props.tag, {
domProps: {
title: actual.value,
innerText: fromNow.value
}
})
}
})
</script>

View File

@ -1,76 +0,0 @@
<template lang="pug">
.md-toolbar-row
md-tabs(md-sync-route).md-primary
template(v-if='isAuthenticated')
md-tab(md-label='Journal'
to='/journal')
md-tab(md-label='Active'
to='/requests/active')
md-tab(v-if='hasSnoozed.value'
md-label='Snoozed'
to='/requests/snoozed')
md-tab(md-label='Answered'
to='/requests/answered')
md-tab(md-label='Log Off'
href='/user/log-off'
@click.prevent='logOff()')
md-tab(md-label='Docs'
href='https://docs.prayerjournal.me'
@click.prevent='showHelp()')
template(v-else)
md-tab(md-label='Log On'
href='/user/log-on'
@click.prevent='logOn()')
md-tab(md-label='Docs'
href='https://docs.prayerjournal.me'
@click.prevent='showHelp()')
</template>
<script lang="ts">
import { computed, createComponent } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { AppState } from '../../store/types' // eslint-disable-line no-unused-vars
import { AuthService } from '../../auth' // eslint-disable-line no-unused-vars
import { useAuth } from '../../plugins/auth'
import { useRouter } from '../../plugins/router'
import { useStore } from '../../plugins/store'
export default createComponent({
setup () {
/** The Vuex store */
const store = useStore() as Store<AppState>
/** The auth service */
const auth = useAuth() as AuthService
/** The router for myPrayerJournal */
const router = useRouter()
/** Whether the user has any snoozed requests */
const hasSnoozed = computed(() =>
store.state.isAuthenticated &&
Array.isArray(store.state.journal) &&
store.state.journal.filter(req => req.snoozedUntil > Date.now()).length > 0)
/** Log a user on using Auth0 */
const logOn = () => auth.login()
/** Log a user off using Auth0 */
const logOff = () => {
auth.logout(store)
router.push('/')
}
/** Open a new window/tab with help */
const showHelp = () => { window.open('https://docs.prayerjournal.me', '_blank') }
return {
hasSnoozed,
logOn,
logOff,
showHelp
}
}
})
</script>

View File

@ -1,27 +0,0 @@
<template lang="pug">
h1(v-if='!hideOnPage'
v-html='title').md-title
</template>
<script lang="ts">
import { createComponent, watch, ref } from '@vue/composition-api'
export default createComponent({
props: {
title: {
type: String,
required: true
},
hideOnPage: {
type: Boolean,
default: false
}
},
setup (props) {
watch(ref(props.title), (title: string, prevTitle: string) => {
document.title = `${props.title.replace('&rsquo;', "'")} « myPrayerJournal`
})
return { }
}
})
</script>

View File

@ -1,59 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content
page-title(title='Privacy Policy'
hide-on-page=true)
md-card
md-card-header
.md-title Privacy Policy
.md-subhead as of May 21, 2018
md-card-content.mpj-full-page-card
p.
The nature of the service is one where privacy is a must. The items below will help you understand the data we
collect, access, and store on your behalf as you use this service.
hr
h3 Third Party Services
p.
myPrayerJournal utilizes a third-party authentication and identity provider. You should familiarize yourself
with the privacy policy for #[a(href='https://auth0.com/privacy' target='_blank') Auth0], as well as your
chosen provider (#[a(href='https://privacy.microsoft.com/en-us/privacystatement' target='_blank') Microsoft] or
#[a(href='https://policies.google.com/privacy' target='_blank') Google]).
hr
h3 What We Collect
h4 Identifying Data
ul
li.
The only identifying data myPrayerJournal stores is the subscriber (&ldquo;sub&rdquo;) field from the token we
receive from Auth0, once you have signed in through their hosted service. All information is associated with
you via this field.
li.
While you are signed in, within your browser, the service has access to your first and last names, along with
a URL to the profile picture (provided by your selected identity provider). This information is not
transmitted to the server, and is removed when &ldquo;Log Off&rdquo; is clicked.
h4 User Provided Data
ul
li.
myPrayerJournal stores the information you provide, including the text of prayer requests, updates, and notes;
and the date/time when certain actions are taken.
hr
h3 How Your Data Is Accessed / Secured
ul
li.
Your provided data is returned to you, as required, to display your journal or your answered requests. On the
server, it is stored in a controlled-access database.
li.
Your data is backed up, along with other Bit Badger Solutions hosted systems, in a rolling manner; backups are
preserved for the prior 7 days, and backups from the 1st and 15th are preserved for 3 months. These backups
are stored in a private cloud data repository.
li.
The data collected and stored is the absolute minimum necessary for the functionality of the service. There
are no plans to &ldquo;monetize&rdquo; this service, and storing the minimum amount of information means that
the data we have is not interesting to purchasers (or those who may have more nefarious purposes).
li Access to servers and backups is strictly controlled and monitored for unauthorized access attempts.
hr
h3 Removing Your Data
p.
At any time, you may choose to discontinue using this service. Both Microsoft and Google provide ways to revoke
access from this application. However, if you want your data removed from the database, please contact daniel at
bitbadger.solutions (via e-mail, replacing at with @) prior to doing so, to ensure we can determine which
subscriber ID belongs to you.
</template>

View File

@ -1,40 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content
page-title(title='Terms of Service'
hide-on-page=true)
md-card
md-card-header
.md-title Terms of Service
.md-subhead as of May 21, 2018
md-card-content.mpj-full-page-card
h3 1. Acceptance of Terms
p.
By accessing this web site, you are agreeing to be bound by these Terms and Conditions, and that you are
responsible to ensure that your use of this site complies with all applicable laws. Your continued use of this
site implies your acceptance of these terms.
h3 2. Description of Service and Registration
p.
myPrayerJournal is a service that allows individuals to enter and amend their prayer requests. It requires no
registration by itself, but access is granted based on a successful login with an external identity provider.
See #[router-link(:to="{ name: 'PrivacyPolicy' }") our privacy policy] for details on how that information is
accessed and stored.
h3 3. Third Party Services
p.
This service utilizes a third-party service provider for identity management. Review the terms of service for
#[a(href='https://auth0.com/terms' target='_blank') Auth0], as well as those for the selected authorization
provider (#[a(href='https://www.microsoft.com/en-us/servicesagreement' target='_blank') Microsoft] or
#[a(href='https://policies.google.com/terms' target='_blank') Google]).
h3 4. Liability
p.
This service is provided "as is", and no warranty (express or implied) exists. The service and its developers
may not be held liable for any damages that may arise through the use of this service.
h3 5. Updates to Terms
p.
These terms and conditions may be updated at any time, and this service does not have the capability to notify
users when these change. The date at the top of the page will be updated when any of the text of these terms is
updated.
hr
p.
You may also wish to review our #[router-link(:to="{ name: 'PrivacyPolicy' }") privacy policy] to learn how we
handle your data.
</template>

View File

@ -1,65 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content
page-title(title='Active Requests'
hide-on-page=true)
template(v-if='isLoaded.value')
md-empty-state(v-if='requests.length === 0'
md-icon='sentiment_dissatisfied'
md-label='No Active Requests'
md-description='Your prayer journal has no active requests')
md-button(to='/journal').md-primary.md-raised Return to your journal
request-list(v-if='requests.length !== 0'
title='Active Requests'
:requests='requests')
p(v-else) Loading journal...
</template>
<script lang="ts">
import { onBeforeMount, createComponent, ref } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import RequestList from './RequestList.vue'
import { useProgress } from '../../App.vue'
import { Actions, AppState, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../../plugins/store'
export default createComponent({
components: {
RequestList
},
setup () {
/** The Vuex store */
const store = useStore() as Store<AppState>
/** The progress bar component instance */
const progress = useProgress()
/** The requests, sorted by the date they will be next shown */
let requests: JournalRequest[] = []
/** Whether all requests have been loaded */
const isLoaded = ref(false)
const ensureJournal = async () => {
if (!Array.isArray(store.state.journal)) {
isLoaded.value = false
await store.dispatch(Actions.LoadJournal, progress)
}
requests = store.state.journal.sort((a, b) => a.showAfter - b.showAfter)
isLoaded.value = true
}
onBeforeMount(async () => { await ensureJournal() })
// TODO: how do we do this?
// this.$on('requestUnsnoozed', ensureJournal)
// this.$on('requestNowShown', ensureJournal)
return {
requests,
isLoaded
}
}
})
</script>

View File

@ -1,63 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content
page-title(title='Answered Requests'
hide-on-page=true)
template(v-if='loaded')
md-empty-state(v-if='requests.length === 0'
md-icon='sentiment_dissatisfied'
md-label='No Answered Requests'
md-description='Your prayer journal has no answered requests; once you have marked one as “Answered”, it will appear here')
request-list(v-if='requests.length !== 0'
title='Answered Requests'
:requests='requests')
p(v-else) Loading answered requests...
</template>
<script lang="ts">
import { createComponent, onMounted, ref } from '@vue/composition-api'
import RequestList from './RequestList.vue'
import api from '../../api'
import { JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue'
export default createComponent({
components: {
RequestList
},
setup () {
/** The answered requests */
let requests: JournalRequest[] = []
/** Whether the requests have been loaded */
const isLoaded = ref(false)
/** The snackbar component instance */
const snackbar = useSnackbar()
/** The progress bar instance */
const progress = useProgress()
onMounted(async () => {
progress.events.$emit('show', 'query')
try {
const reqs = await api.getAnsweredRequests()
requests = reqs.data
progress.events.$emit('done')
} catch (err) {
console.error(err) // eslint-disable-line no-console
snackbar.events.$emit('error', 'Error loading requests; check console for details')
progress.events.$emit('done')
} finally {
isLoaded.value = true
}
})
return {
requests,
isLoaded
}
}
})
</script>

View File

@ -1,224 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-narrow
page-title(:title='title.value')
md-field
label(for='request_text') Prayer Request
md-textarea(v-model='form.requestText'
@blur='trimText()'
md-autogrow
autofocus).mpj-full-width
br
template(v-if='!isNew.value')
label Also Mark As
br
md-radio(v-model='form.status'
value='Updated') Updated
md-radio(v-model='form.status'
value='Prayed') Prayed
md-radio(v-model='form.status'
value='Answered') Answered
br
label Recurrence
| &nbsp; &nbsp;
em.mpj-muted-text After prayer, request reappears...
br
.md-layout
.md-layout-item.md-size-30
md-radio(v-model='form.recur.typ'
value='Immediate') Immediately
.md-layout-item.md-size-20
md-radio(v-model='form.recur.typ'
value='other') Every...
.md-layout-item.md-size-10
md-field(md-inline)
label Count
md-input(v-model='form.recur.count'
type='number'
:disabled='!showRecurrence.value')
.md-layout-item.md-size-20
md-field
label Interval
md-select(v-model='form.recur.other'
:disabled='!showRecurrence.value')
md-option(value='Hours') hours
md-option(value='Days') days
md-option(value='Weeks') weeks
.mpj-text-right
md-button(:disabled='!isValidRecurrence.value'
@click.stop='saveRequest()').md-primary.md-raised #[md-icon save] Save
md-button(@click.stop='goBack()').md-raised #[md-icon arrow_back] Cancel
</template>
<script lang="ts">
import { createComponent, ref, computed, onMounted } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { Actions, AppState, AddRequestAction, UpdateRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue'
import { useStore } from '../../plugins/store'
import { useRouter } from '../../plugins/router'
/** The recurrence settings for the request */
class RecurrenceForm {
/** The type of recurrence */
typ = 'Immediate'
/** The type of recurrence (other than immediate) */
other = ''
/** The count of non-immediate intervals */
count = ''
/**
* The recurrence represented by the given form
* @param x The recurrence form
*/
static recurrence = (x: RecurrenceForm) => x.typ === 'Immediate' ? 'Immediate' : x.other
/**
* The interval represented by the given form
* @param x The recurrence form
*/
static interval = (x: RecurrenceForm) => x.typ === 'Immediate' ? 0 : Number.parseInt(x.count)
}
/** The form for editing the request */
class EditForm {
/** The ID of the request */
requestId = ''
/** The text of the request */
requestText = ''
/** The status associated with this update */
status = 'Updated'
/** The recurrence for the request */
recur = new RecurrenceForm()
}
export default createComponent({
props: {
id: {
type: String,
required: true
}
},
setup (props) {
/** The Vuex store */
const store = useStore() as Store<AppState>
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The progress bar component properties */
const progress = useProgress()
/** The application router */
const router = useRouter()
/** The page title */
const title = ref('Edit Prayer Request')
/** Whether this is a new request */
const isNew = ref(false)
/** The input form */
const form = new EditForm()
/** Is the selected recurrence a valid recurrence? */
const isValidRecurrence = computed(() => {
if (form.recur.typ === 'Immediate') return true
const count = Number.parseInt(form.recur.count)
if (isNaN(count) || form.recur.other === '') return false
if (form.recur.other === 'Hours' && count > (365 * 24)) return false
if (form.recur.other === 'Days' && count > 365) return false
if (form.recur.other === 'Weeks' && count > 52) return false
return true
})
/** Whether the recurrence should be shown */
const showRecurrence = computed(() => form.recur.typ !== 'Immediate')
/** Go back 1 in browser history */
const goBack = () => { router.go(-1) }
/** Trim the request text */
const trimText = () => { form.requestText = form.requestText.trim() }
/** Save the edited request */
const saveRequest = async () => {
if (isNew.value) {
const opts: AddRequestAction = {
progress,
requestText: form.requestText,
recurType: RecurrenceForm.recurrence(form.recur),
recurCount: RecurrenceForm.interval(form.recur)
}
await store.dispatch(Actions.AddRequest, opts)
snackbar.events.$emit('info', 'New prayer request added')
} else {
const opts: UpdateRequestAction = {
progress,
requestId: form.requestId,
updateText: form.requestText,
status: form.status,
recurType: RecurrenceForm.recurrence(form.recur),
recurCount: RecurrenceForm.interval(form.recur)
}
await store.dispatch(Actions.UpdateRequest, opts)
if (form.status === 'Answered') {
snackbar.events.$emit('info', 'Request updated and removed from active journal')
} else {
snackbar.events.$emit('info', 'Request updated')
}
}
goBack()
}
onMounted(async () => {
if (!Array.isArray(store.state.journal)) {
await store.dispatch(Actions.LoadJournal, progress)
}
if (props.id === 'new') {
title.value = 'Add Prayer Request'
isNew.value = true
form.requestId = ''
form.requestText = ''
form.status = 'Created'
form.recur.typ = 'Immediate'
form.recur.other = ''
form.recur.count = ''
} else {
title.value = 'Edit Prayer Request'
isNew.value = false
const req = store.state.journal.filter(r => r.requestId === props.id)[0]
form.requestId = props.id
form.requestText = req.text
form.status = 'Updated'
if (req.recurType === 'Immediate') {
form.recur.typ = 'Immediate'
form.recur.other = ''
form.recur.count = ''
} else {
form.recur.typ = 'other'
form.recur.other = req.recurType
form.recur.count = req.recurCount.toString()
}
}
})
return {
form,
goBack,
isNew,
isValidRecurrence,
journal: store.state.journal,
saveRequest,
showRecurrence,
title,
trimText
}
}
})
</script>

View File

@ -1,108 +0,0 @@
<template lang="pug">
md-content(role='main').mpj-main-content
page-title(title='Full Prayer Request'
hide-on-page=true)
md-card(v-if='request')
md-card-header
.md-title Full Prayer Request
.md-subhead
span(v-if='isAnswered.value') Answered {{ formatDate(answered) }} (#[date-from-now(:value='answered')]) !{' &bull; '}
| Prayed {{ prayedCount }} times &bull; Open {{ openDays }} days
md-card-content.mpj-full-page-card
p.mpj-request-text {{ lastText }}
md-table
md-table-row
md-table-head Action
md-table-head Update / Notes
md-table-row(v-for='item in log'
:key='item.asOf')
md-table-cell.mpj-valign-top {{ item.status }} on #[span.mpj-text-nowrap {{ formatDate(item.asOf) }}]
md-table-cell(v-if='item.text').mpj-request-text.mpj-valign-top {{ item.text }}
md-table-cell(v-else) &nbsp;
p(v-else) Loading request...
</template>
<script lang="ts">
import { computed, createComponent, onMounted } from '@vue/composition-api'
import { format } from 'date-fns'
import api from '../../api'
import { useProgress } from '../../App.vue'
import { HistoryEntry, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
/** Sort history entries in descending order */
const asOfDesc = (a: HistoryEntry, b: HistoryEntry) => b.asOf - a.asOf
export default createComponent({
props: {
id: {
type: String,
required: true
}
},
setup (props) {
/** The progress bar component instance */
const progress = useProgress()
/** The request being displayed */
let request = new JournalRequest()
/** The history entry where the request was marked as answered */
const answer = computed(() => request.history.find(hist => hist.status === 'Answered'))
/** Whether this request is answered */
const isAnswered = computed(() => answer.value !== undefined)
/** The date/time this request was answered */
const answered = computed(() => answer.value ? answer.value.asOf : undefined)
/** The last recorded text for the request */
const lastText = computed(() => request.history.filter(hist => hist.text).sort(asOfDesc)[0].text)
/** The history log including notes (and excluding the final entry for answered requests) */
const log = computed(() => {
const allHistory = (request.notes || [])
.map(note => ({ asOf: note.asOf, text: note.notes, status: 'Notes' } as HistoryEntry))
.concat(request.history)
.sort(asOfDesc)
// Skip the first entry for answered requests; that info is already displayed
return isAnswered.value ? allHistory.slice(1) : allHistory
})
/** The number of days this request [was|has been] open */
const openDays = computed(() => {
const asOf = answered.value ? answered.value : Date.now()
return Math.floor(
(asOf - request.history.filter(hist => hist.status === 'Created')[0].asOf) / 1000 / 60 / 60 / 24)
})
/** How many times this request has been prayed for */
const prayedCount = computed(() => request.history.filter(hist => hist.status === 'Prayed').length)
/** Format a date */
const formatDate = (asOf: number) => format(asOf, 'PPP')
onMounted(async () => {
progress.events.$emit('show', 'indeterminate')
try {
const req = await api.getFullRequest(props.id)
request = req.data as JournalRequest
} catch (e) {
console.log(e) // eslint-disable-line no-console
} finally {
progress.events.$emit('done')
}
})
return {
answered,
formatDate,
isAnswered,
lastText,
log,
openDays,
prayedCount,
request
}
}
})
</script>

View File

@ -1,155 +0,0 @@
<template lang="pug">
md-dialog(:md-active.sync='notesVisible').mpj-note-dialog
md-dialog-title Add Notes to Prayer Request
md-content.mpj-dialog-content
md-field
label Notes
md-textarea(v-model='form.notes'
md-autogrow
@blur='trimText()')
md-dialog-actions
md-button(@click='saveNotes()').md-primary #[md-icon save] Save
md-button(@click='closeDialog()') #[md-icon undo] Cancel
md-dialog-content(md-scrollbar='true').mpj-dialog-content
div(v-if='hasPriorNotes.value')
p.mpj-text-center: strong Prior Notes for This Request
.mpj-note-list
p(v-for='note in priorNotes'
:key='note.asOf')
small.mpj-muted-text: date-from-now(:value='note.asOf')
br
span.mpj-request-text {{ note.notes }}
div(v-else-if='noPriorNotes.value').mpj-text-center.mpj-muted-text There are no prior notes for this request
div(v-else).mpj-text-center
hr
md-button(@click='loadNotes()') #[md-icon cloud_download] Load Prior Notes
</template>
<script lang="ts">
import { computed, createComponent, ref } from '@vue/composition-api'
import api from '../../api'
import { useEvents } from '../Journal.vue'
import { NotesEntry, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue'
/** The input form for the notes dialog */
class NotesForm {
/** The ID of the request */
requestId = ''
/** The actual notes */
notes = ''
}
/** The prior notes for this request */
class PriorNotes {
/** The prior notes */
notes: NotesEntry[] = []
/** Have the prior notes been loaded? */
isLoaded = false
}
export default createComponent({
setup () {
/** The event bus for the journal page */
const events = useEvents()
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The progress bar component properties */
const progress = useProgress()
/** Is this dialog visible? */
const notesVisible = ref(false)
/** The input form */
const form = new NotesForm()
/** The prior notes */
const prior = new PriorNotes()
/** Are there prior notes? */
const hasPriorNotes = computed(() => prior.isLoaded && prior.notes.length > 0)
/** Are there no prior notes? */
const noPriorNotes = computed(() => prior.isLoaded && prior.notes.length === 0)
/** Close this dialog */
const closeDialog = () => {
form.requestId = ''
form.notes = ''
prior.notes = []
prior.isLoaded = false
notesVisible.value = false
}
/** Load the notes for this request */
const loadNotes = async () => {
progress.events.$emit('show', 'indeterminate')
try {
const notes = await api.getNotes(form.requestId)
prior.notes = (notes.data as NotesEntry[]).sort((a, b) => b.asOf - a.asOf)
} catch (e) {
console.error(e) // eslint-disable-line no-console
} finally {
progress.events.$emit('done')
prior.isLoaded = true
}
}
/** Open this dialog */
const openDialog = (request: JournalRequest) => {
form.requestId = request.requestId
notesVisible.value = true
}
/** Save the notes entered on this dialog */
const saveNotes = async () => {
progress.events.$emit('show', 'indeterminate')
try {
await api.addNote(form.requestId, form.notes)
snackbar.events.$emit('info', 'Added notes')
closeDialog()
} catch (e) {
console.error(e) // eslint-disable-line no-console
} finally {
progress.events.$emit('done')
}
}
/** Trim the note text */
const trimText = () => { form.notes = form.notes.trim() }
events.$on('notes', openDialog)
return {
closeDialog,
form,
hasPriorNotes,
loadNotes,
noPriorNotes,
notesVisible,
openDialog,
prior,
saveNotes,
trimText
}
}
})
</script>
<style lang="sass">
.mpj-note-dialog
width: 40rem
padding-bottom: 1.5rem
@media screen and (max-width: 40rem)
@media screen and (max-width: 20rem)
.mpj-note-dialog
width: 100%
.mpj-note-dialog
width: 20rem
.mpj-note-list p
border-top: dotted 1px lightgray
</style>

View File

@ -1,108 +0,0 @@
<template lang="pug">
md-card(v-if='shouldDisplay'
md-with-hover).mpj-request-card
md-card-actions(md-alignment='space-between')
md-button(@click='markPrayed()').md-icon-button.md-raised.md-primary
md-icon done
md-tooltip(md-direction='top'
md-delay=1000) Mark as Prayed
span
md-button(@click.stop='showEdit()').md-icon-button.md-raised
md-icon edit
md-tooltip(md-direction='top'
md-delay=1000) Edit Request
md-button(@click.stop='showNotes()').md-icon-button.md-raised
md-icon comment
md-tooltip(md-direction='top'
md-delay=1000) Add Notes
md-button(@click.stop='snooze()').md-icon-button.md-raised
md-icon schedule
md-tooltip(md-direction='top'
md-delay=1000) Snooze Request
md-card-content
p.mpj-request-text {{ request.text }}
p.mpj-text-right: small.mpj-muted-text: em (last activity #[date-from-now(:value='request.asOf')])
</template>
<script lang="ts">
import { createComponent, computed } from '@vue/composition-api'
import { Actions, JournalRequest, UpdateRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
import { useEvents } from '../Journal.vue'
import { useStore } from '../../plugins/store'
import { useProgress, useSnackbar } from '../../App.vue'
import { useRouter } from '../../plugins/router'
export default createComponent({
props: {
request: {
type: JournalRequest,
required: true
}
},
setup (props) {
/** The Vuex store */
const store = useStore()
/** The application router */
const router = useRouter()
/** The progress bar component properties */
const progress = useProgress()
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The journal event bus */
const events = useEvents()
/** Should this request be displayed? */
const shouldDisplay = computed(() => {
const now = Date.now()
return Math.max(now, props.request.showAfter, props.request.snoozedUntil) === now
})
/** Mark the request as prayed */
const markPrayed = async () => {
const opts: UpdateRequestAction = {
progress,
requestId: props.request.requestId,
status: 'Prayed',
updateText: '',
recurType: '',
recurCount: 0
}
await store.dispatch(Actions.UpdateRequest, opts)
snackbar.events.$emit('info', 'Request marked as prayed')
}
/** Show the edit page for this request */
const showEdit = () => { router.push({ name: 'EditRequest', params: { id: props.request.requestId } }) }
/** Show the request notes dialog */
const showNotes = () => events.$emit('notes', props.request)
/** Show the snooze request dialog */
const snooze = () => events.$emit('snooze', props.request.requestId)
return {
markPrayed,
request: props.request,
shouldDisplay,
showEdit,
showNotes,
snooze
}
}
})
</script>
<style lang="sass">
.mpj-request-card
width: 20rem
margin-bottom: 1rem
@media screen and (max-width: 20rem)
.mpj-request-card
width: 100%
</style>

View File

@ -1,44 +0,0 @@
<template lang="pug">
md-table(md-card)
md-table-toolbar
h1.md-title {{ title }}
md-table-row
md-table-head Actions
md-table-head Request
request-list-item(v-for='req in requests'
:key='req.requestId'
:request='req')
</template>
<script lang="ts">
import { createComponent, onMounted } from '@vue/composition-api'
import RequestListItem from './RequestListItem.vue'
import { JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
export default createComponent({
components: { RequestListItem },
props: {
title: {
type: String,
required: true
},
requests: {
type: Array,
required: true
}
},
setup (props, { parent }) {
// TODO: custom events; does this work?
onMounted(function () {
this.$on('requestUnsnoozed', parent?.$emit('requestUnsnoozed'))
this.$on('requestNowShown', parent?.$emit('requestNowShown'))
})
return {
title: props.title,
requests: props.requests as JournalRequest[]
}
}
})
</script>

View File

@ -1,118 +0,0 @@
<template lang="pug">
md-table-row
md-table-cell.mpj-action-cell.mpj-valign-top
md-button(@click='viewFull').md-icon-button.md-raised
md-icon description
md-tooltip(md-direction='top'
md-delay=250) View Full Request
template(v-if='!isAnswered.value')
md-button(@click='editRequest').md-icon-button.md-raised
md-icon edit
md-tooltip(md-direction='top'
md-delay=250) Edit Request
template(v-if='isSnoozed.value')
md-button(@click='cancelSnooze()').md-icon-button.md-raised
md-icon restore
md-tooltip(md-direction='top'
md-delay=250) Cancel Snooze
template(v-if='isPending.value')
md-button(@click='showNow()').md-icon-button.md-raised
md-icon restore
md-tooltip(md-direction='top'
md-delay=250) Show Now
md-table-cell.mpj-valign-top
p.mpj-request-text {{ request.text }}
br(v-if='isSnoozed.value || isPending.value || isAnswered.value')
small(v-if='isSnoozed.value').mpj-muted-text: em Snooze expires #[date-from-now(:value='request.snoozedUntil')]
small(v-if='isPending.value').mpj-muted-text: em Request appears next #[date-from-now(:value='request.showAfter')]
small(v-if='isAnswered.value').mpj-muted-text: em Answered #[date-from-now(:value='request.asOf')]
</template>
<script lang="ts">
import { computed, createComponent } from '@vue/composition-api'
import { Actions, JournalRequest, SnoozeRequestAction, ShowRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../../plugins/store'
import { useRouter } from '../../plugins/router'
import { useProgress, useSnackbar } from '../../App.vue'
export default createComponent({
props: {
request: {
type: JournalRequest,
required: true
}
},
setup (props, { parent }) {
/** Shorthand for props.request */
const r = props.request
/** The Vuex store */
const store = useStore()
/** The application router */
const router = useRouter()
/** The snackbar instance */
const snackbar = useSnackbar()
/** The progress bar component instance */
const progress = useProgress()
/** Whether the request has been answered */
const isAnswered = computed(() => r.lastStatus === 'Answered')
/** Whether the request is snoozed */
const isSnoozed = computed(() => r.snoozedUntil > Date.now())
/** Whether the request is not shown because of an interval */
const isPending = computed(() => !isSnoozed.value && r.showAfter > Date.now())
/** Cancel the snooze period for this request */
const cancelSnooze = async () => {
const opts: SnoozeRequestAction = {
progress,
requestId: r.requestId,
until: 0
}
await store.dispatch(Actions.SnoozeRequest, opts)
snackbar.events.$emit('info', 'Request un-snoozed')
if (parent) parent.$emit('requestUnsnoozed')
}
/** Edit the given request */
const editRequest = () => { router.push({ name: 'EditRequest', params: { id: r.requestId } }) }
/** Show the request now */
const showNow = async () => {
const opts: ShowRequestAction = {
progress,
requestId: r.requestId,
showAfter: 0
}
await store.dispatch(Actions.ShowRequestNow, opts)
snackbar.events.$emit('info', 'Recurrence skipped; request now shows in journal')
if (parent) parent.$emit('requestNowShown')
}
/** View the full request */
const viewFull = () => { router.push({ name: 'FullRequest', params: { id: r.requestId } }) }
return {
cancelSnooze,
editRequest,
isAnswered,
isPending,
isSnoozed,
showNow,
viewFull
}
}
})
</script>
<style lang="sass">
.mpj-action-cell
width: 1%
white-space: nowrap
</style>

View File

@ -1,96 +0,0 @@
<template lang="pug">
md-dialog(:md-active.sync='isVisible.value').mpj-skinny
md-dialog-title Snooze Prayer Request
md-content.mpj-dialog-content
span.mpj-text-muted Until
md-datepicker(v-model='form.snoozedUntil'
:md-disabled-dates='datesInPast'
md-immediately)
md-dialog-actions
md-button(:disabled='!isValid.value'
@click='snoozeRequest()').md-primary #[md-icon snooze] Snooze
md-button(@click='closeDialog()') #[md-icon undo] Cancel
</template>
<script lang="ts">
import { createComponent, ref, computed } from '@vue/composition-api'
import { Actions, SnoozeRequestAction } from '../../store/types' // eslint-disable-line no-unused-vars
import { useProgress, useSnackbar } from '../../App.vue'
import { useEvents } from '../Journal.vue'
import { useStore } from '../../plugins/store'
/** The input form */
class SnoozeForm {
/** The ID of the request */
requestId = ''
/** The date until which the request will be snoozed */
snoozedUntil = ''
}
export default createComponent({
setup () {
/** The Vuex store */
const store = useStore()
/** The progress bar component properties */
const progress = useProgress()
/** The snackbar component properties */
const snackbar = useSnackbar()
/** The journal event bus */
const events = useEvents()
/** Whether this dialog is visible */
const isVisible = ref(false)
/** The input form */
const form = new SnoozeForm()
/** Is the input date valid? */
const isValid = computed(() => !isNaN(Date.parse(form.snoozedUntil)))
/** Close the dialog */
const closeDialog = () => {
form.requestId = ''
form.snoozedUntil = ''
isVisible.value = false
}
/**
* Open the dialog
* @param requestId The ID of the request to be snoozed
*/
const openDialog = (requestId: string) => {
form.requestId = requestId
isVisible.value = true
}
const snoozeRequest = async () => {
const opts: SnoozeRequestAction = {
progress,
requestId: form.requestId,
until: Date.parse(form.snoozedUntil)
}
await store.dispatch(Actions.SnoozeRequest, opts)
snackbar.events.$emit('info', `Request snoozed until ${form.snoozedUntil}`)
closeDialog()
}
events.$on('snooze', openDialog)
return {
closeDialog,
datesInPast: (date: Date) => date < new Date(),
form,
isValid,
isVisible,
openDialog,
snoozeRequest
}
}
})
</script>

View File

@ -1,64 +0,0 @@
<template lang="pug">
article.mpj-main-content(role='main')
page-title(title='Snoozed Requests'
hide-on-page=true)
template(v-if='isLoaded.value')
md-empty-state(v-if='requests.length === 0'
md-icon='sentiment_dissatisfied'
md-label='No Snoozed Requests'
md-description='Your prayer journal has no snoozed requests')
md-button(to='/journal').md-primary.md-raised Return to your journal
request-list(v-if='requests.length !== 0'
title='Snoozed Requests'
:requests='requests')
p(v-else) Loading journal...
</template>
<script lang="ts">
import { createComponent, ref, onMounted } from '@vue/composition-api'
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import RequestList from './RequestList.vue'
import { Actions, AppState, JournalRequest } from '../../store/types' // eslint-disable-line no-unused-vars
import { useStore } from '../../plugins/store'
import { useProgress } from '../../App.vue'
export default createComponent({
components: { RequestList },
setup () {
/** The Vuex store */
const store = useStore() as Store<AppState>
/** The progress bar component properties */
const progress = useProgress()
/** The snoozed requests */
let requests: JournalRequest[] = []
/** Have snoozed requests been loaded? */
const isLoaded = ref(false)
/** Ensure the latest journal is loaded, and filter it to snoozed requests */
const ensureJournal = async () => {
if (!Array.isArray(store.state.journal)) {
isLoaded.value = false
await store.dispatch(Actions.LoadJournal, progress)
}
requests = store.state.journal
.filter(req => req.snoozedUntil > Date.now())
.sort((a, b) => a.snoozedUntil - b.snoozedUntil)
isLoaded.value = true
}
onMounted(ensureJournal)
// this.$on('requestUnsnoozed', ensureJournal)
return {
requests,
isLoaded
}
}
})
</script>

View File

@ -1,41 +0,0 @@
<template lang="pug">
article.mpj-main-content(role='main')
pageTitle(title='Logging On')
p Logging you on...
</template>
<script lang="ts">
import { createComponent, onBeforeMount } from '@vue/composition-api'
import VueRouter from 'vue-router' // eslint-disable-line no-unused-vars
import { Store } from 'vuex' // eslint-disable-line no-unused-vars
import { AppState } from '../../store/types' // eslint-disable-line no-unused-vars
import { AuthService } from '../../auth' // eslint-disable-line no-unused-vars
import { useAuth } from '../../plugins/auth'
import { useRouter } from '../../plugins/router'
import { useStore } from '../../plugins/store'
export default createComponent({
setup () {
/** Auth service instance */
const auth = useAuth() as AuthService
/** Store instance */
const store = useStore() as Store<AppState>
/** Router instance */
const router = useRouter() as VueRouter
/** Navigate on auth completion */
auth.on('loginEvent', (data: any) => {
router.push(data.state.target || '/journal')
})
// this.progress.$emit('show', 'indeterminate')
onBeforeMount(async () => { await auth.handleAuthentication(store) })
return { }
}
})
</script>

View File

@ -1,64 +0,0 @@
// Vue packages and components
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
import {
MdApp,
MdButton,
MdCard,
MdContent,
MdDatepicker,
MdDialog,
MdEmptyState,
MdField,
MdIcon,
MdLayout,
MdProgress,
MdRadio,
MdSnackbar,
MdTable,
MdTabs,
MdToolbar,
MdTooltip
} from 'vue-material/dist/components'
// myPrayerJournal components
import App from './App.vue'
import router from './router'
import store from './store'
import DateFromNow from './components/common/DateFromNow.vue'
import PageTitle from './components/common/PageTitle.vue'
import AuthPlugin from './plugins/auth'
// Styles
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'
Vue.config.productionTip = false
Vue.use(MdApp)
Vue.use(MdButton)
Vue.use(MdCard)
Vue.use(MdContent)
Vue.use(MdDatepicker)
Vue.use(MdDialog)
Vue.use(MdEmptyState)
Vue.use(MdField)
Vue.use(MdIcon)
Vue.use(MdLayout)
Vue.use(MdProgress)
Vue.use(MdRadio)
Vue.use(MdSnackbar)
Vue.use(MdTable)
Vue.use(MdTabs)
Vue.use(MdToolbar)
Vue.use(MdTooltip)
Vue.use(AuthPlugin)
Vue.use(VueCompositionApi)
Vue.component('date-from-now', DateFromNow)
Vue.component('page-title', PageTitle)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

View File

@ -1,36 +0,0 @@
import { inject, provide } from '@vue/composition-api'
import authService, { AuthService } from '@/auth'
export default {
install (Vue: any) {
Vue.prototype.$auth = authService
Vue.mixin({
created () {
if (this.handleLoginEvent) {
authService.addListener('loginEvent', this.handleLoginEvent)
}
},
destroyed () {
if (this.handleLoginEvent) {
authService.removeListener('loginEvent', this.handleLoginEvent)
}
}
})
}
}
const AuthSymbol = Symbol('Auth service')
export function provideAuth (auth: AuthService) {
provide(AuthSymbol, auth)
}
/** Use the auth service */
export function useAuth (): AuthService {
const auth = inject(AuthSymbol)
if (!auth) {
throw new Error('Auth not configured!')
}
return auth as AuthService
}

View File

@ -1,16 +0,0 @@
import VueRouter from 'vue-router'
import { inject, provide } from '@vue/composition-api'
const RouterSymbol = Symbol('Vue router')
export function provideRouter (router: VueRouter) {
provide(RouterSymbol, router)
}
export function useRouter (): VueRouter {
const router = inject(RouterSymbol)
if (!router) {
throw new Error('Router not configured!')
}
return router as VueRouter
}

View File

@ -1,20 +0,0 @@
import { provide, inject } from '@vue/composition-api'
import { Store } from 'vuex'
import { AppState } from '@/store/types'
const StoreSymbol = Symbol('Vuex store')
/** Configure the store provided by this plugin */
export function provideStore (store: Store<AppState>) {
provide(StoreSymbol, store)
}
/** Use the provided store */
export function useStore (): Store<AppState> {
const store = inject(StoreSymbol)
if (!store) {
throw new Error('No store configured!')
}
return store as Store<AppState>
}

View File

@ -1,82 +0,0 @@
import Vue from 'vue'
import Router from 'vue-router'
import auth from '@/auth'
import Home from '@/components/Home.vue'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
},
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/journal',
name: 'Journal',
component: () => import('@/components/Journal.vue')
},
{
path: '/legal/privacy-policy',
name: 'PrivacyPolicy',
component: () => import('@/components/legal/PrivacyPolicy.vue')
},
{
path: '/legal/terms-of-service',
name: 'TermsOfService',
component: () => import('@/components/legal/TermsOfService.vue')
},
{
path: '/request/:id/edit',
name: 'EditRequest',
component: () => import('@/components/request/EditRequest.vue'),
props: true
},
{
path: '/request/:id/full',
name: 'FullRequest',
component: () => import('@/components/request/FullRequest.vue'),
props: true
},
{
path: '/requests/active',
name: 'ActiveRequests',
component: () => import('@/components/request/ActiveRequests.vue')
},
{
path: '/requests/answered',
name: 'AnsweredRequests',
component: () => import('@/components/request/AnsweredRequests.vue')
},
{
path: '/requests/snoozed',
name: 'SnoozedRequests',
component: () => import('@/components/request/SnoozedRequests.vue')
},
{
path: '/user/log-on',
name: 'LogOn',
component: () => import('@/components/user/LogOn.vue')
}
]
})
router.beforeEach((to, from, next) => {
if (to.path === '/' || to.path === '/user/log-on' || auth.isAuthenticated()) {
return next()
}
auth.login({ target: to.path })
})
export default router

View File

@ -1,13 +0,0 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}

View File

@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

View File

@ -1,197 +0,0 @@
import Vue from 'vue'
import Vuex, { StoreOptions } from 'vuex'
import api from '@/api'
import auth from '@/auth'
import {
AppState,
Actions,
JournalRequest,
Mutations,
SnoozeRequestAction,
ShowRequestAction,
AddRequestAction,
UpdateRequestAction
} from './types'
import { ProgressProps } from '@/types'
Vue.use(Vuex)
/* eslint-disable no-console */
const logError = function (error: any) { // TODO: can we do better on this type?
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error(error.response.data)
console.error(error.response.status)
console.error(error.response.headers)
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request)
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message)
}
console.error(`config: ${error.config}`)
}
/**
* Set the "Bearer" authorization header with the current access token
*/
const setBearer = async function () {
try {
await auth.getAccessToken()
api.setBearer(auth.session.id.token)
} catch (err) {
if (err.message === 'Not logged in') {
console.warn('API request attempted when user was not logged in')
} else {
console.error(err)
}
}
}
/* eslint-enable no-console */
/**
* Get the sort value for a prayer request
* @param x The prayer request
*/
const sortValue = (x: JournalRequest) => x.showAfter === 0 ? x.asOf : x.showAfter
/**
* Sort journal requests either by asOf or showAfter
*/
const journalSort = (a: JournalRequest, b: JournalRequest) => sortValue(a) - sortValue(b)
/** The initial state of the store */
const store : StoreOptions<AppState> = {
state: {
user: auth.session.profile,
isAuthenticated: auth.isAuthenticated(),
journal: [],
isLoadingJournal: false
},
mutations: {
[Mutations.LoadingJournal] (state, flag: boolean) {
state.isLoadingJournal = flag
},
[Mutations.LoadedJournal] (state, journal: JournalRequest[]) {
state.journal = journal.sort(journalSort)
},
[Mutations.RequestAdded] (state, newRequest: JournalRequest) {
state.journal.push(newRequest)
},
[Mutations.RequestUpdated] (state, request: JournalRequest) {
const jrnl = state.journal.filter(it => it.requestId !== request.requestId)
if (request.lastStatus !== 'Answered') jrnl.push(request)
state.journal = jrnl
},
[Mutations.SetAuthentication] (state, value: boolean) {
state.isAuthenticated = value
},
[Mutations.UserLoggedOff] (state) {
state.user = {}
api.removeBearer()
state.isAuthenticated = false
},
[Mutations.UserLoggedOn] (state, user) {
state.user = user
state.isAuthenticated = true
}
},
actions: {
async [Actions.AddRequest] ({ commit }, p: AddRequestAction) {
const { progress, requestText, recurType, recurCount } = p
progress.events.$emit('show', 'indeterminate')
try {
await setBearer()
const newRequest = await api.addRequest(requestText, recurType, recurCount)
commit(Mutations.RequestAdded, newRequest.data)
} catch (err) {
logError(err)
} finally {
progress.events.$emit('done')
}
},
async [Actions.CheckAuthentication] ({ commit }) {
try {
await auth.getAccessToken()
commit(Mutations.SetAuthentication, auth.isAuthenticated())
} catch (_) {
commit(Mutations.SetAuthentication, false)
}
},
async [Actions.LoadJournal] ({ commit }, progress: ProgressProps) {
commit(Mutations.LoadedJournal, [])
progress.events.$emit('show', 'query')
commit(Mutations.LoadingJournal, true)
await setBearer()
try {
const jrnl = await api.journal()
commit(Mutations.LoadedJournal, jrnl.data)
} catch (err) {
logError(err)
} finally {
progress.events.$emit('done')
commit(Mutations.LoadingJournal, false)
}
},
async [Actions.UpdateRequest] ({ commit, state }, p: UpdateRequestAction) {
const { progress, requestId, status, updateText, recurType, recurCount } = p
progress.events.$emit('show', 'indeterminate')
try {
await setBearer()
const oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {}
if (!(status === 'Prayed' && updateText === '')) {
if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) {
await api.updateRecurrence(requestId, recurType, recurCount)
}
}
if (status !== 'Updated' || oldReq.text !== updateText) {
await api.updateRequest(requestId, status, oldReq.text !== updateText ? updateText : '')
}
const request = await api.getRequest(requestId)
commit(Mutations.RequestUpdated, request.data)
} catch (err) {
logError(err)
} finally {
progress.events.$emit('done')
}
},
async [Actions.ShowRequestNow] ({ commit }, p: ShowRequestAction) {
const { progress, requestId, showAfter } = p
progress.events.$emit('show', 'indeterminate')
try {
await setBearer()
await api.showRequest(requestId, showAfter)
const request = await api.getRequest(requestId)
commit(Mutations.RequestUpdated, request.data)
} catch (err) {
logError(err)
} finally {
progress.events.$emit('done')
}
},
async [Actions.SnoozeRequest] ({ commit }, p: SnoozeRequestAction) {
const { progress, requestId, until } = p
progress.events.$emit('show', 'indeterminate')
try {
await setBearer()
await api.snoozeRequest(requestId, until)
const request = await api.getRequest(requestId)
commit(Mutations.RequestUpdated, request.data)
} catch (err) {
logError(err)
} finally {
progress.events.$emit('done')
}
}
},
getters: {},
modules: {}
}
export default new Vuex.Store<AppState>(store)

View File

@ -1,166 +0,0 @@
import { ProgressProps } from '@/types'
/** A history entry for a prayer request */
export class HistoryEntry {
/** The status for this history entry */
status = '' // TODO string union?
/** The date/time for this history entry */
asOf = 0
/** The text of this history entry */
text?: string = undefined
}
/** An entry with notes for a request */
export class NotesEntry {
/** The date/time the notes were recorded */
asOf = 0
/** The notes */
notes = ''
}
/** A prayer request that is part of the user's journal */
export class JournalRequest {
/** The ID of the request (just the CUID part) */
requestId = ''
/** The ID of the user to whom the request belongs */
userId = ''
/** The current text of the request */
text = ''
/** The last time action was taken on the request */
asOf = 0
/** The last status for the request */
lastStatus = '' // TODO string union?
/** The time that this request should reappear in the user's journal */
snoozedUntil = 0
/** The time after which this request should reappear in the user's journal by configured recurrence */
showAfter = 0
/** The type of recurrence for this request */
recurType = '' // TODO Recurrence union?
/** How many of the recurrence intervals should occur between appearances in the journal */
recurCount = 0
/** History entries for the request */
history: HistoryEntry[] = []
/** Note entries for the request */
notes: NotesEntry[] = []
}
/** The state of myPrayerJournal */
export interface AppState {
/** The user's profile */
user: any,
/** Whether there is a user signed in */
isAuthenticated: boolean,
/** The current set of prayer requests */
journal: JournalRequest[],
/** Whether the journal is currently being loaded */
isLoadingJournal: boolean
}
const actions = {
/** Action to add a prayer request (pass request text) */
AddRequest: 'add-request',
/** Action to check if a user is authenticated, refreshing the session first if it exists */
CheckAuthentication: 'check-authentication',
/** Action to load the user's prayer journal */
LoadJournal: 'load-journal',
/** Action to update a request */
UpdateRequest: 'update-request',
/** Action to skip the remaining recurrence period */
ShowRequestNow: 'show-request-now',
/** Action to snooze a request */
SnoozeRequest: 'snooze-request'
}
export { actions as Actions }
const mutations = {
/** Mutation for when the user's prayer journal is being loaded */
LoadingJournal: 'loading-journal',
/** Mutation for when the user's prayer journal has been loaded */
LoadedJournal: 'journal-loaded',
/** Mutation for adding a new prayer request (pass text) */
RequestAdded: 'request-added',
/** Mutation to replace a prayer request at the top of the current journal */
RequestUpdated: 'request-updated',
/** Mutation for setting the authentication state */
SetAuthentication: 'set-authentication',
/** Mutation for logging a user off */
UserLoggedOff: 'user-logged-off',
/** Mutation for logging a user on (pass user) */
UserLoggedOn: 'user-logged-on'
}
export { mutations as Mutations }
/** The shape of the parameter to the add request action */
export interface AddRequestAction {
/** The progress bar component properties */
progress: ProgressProps
/** The text of the request */
requestText: string
/** The recurrence type */
recurType: string
/** The number of intervals for non-immediate recurrence */
recurCount: number
}
/** The shape of the parameter to the show request action */
export interface ShowRequestAction {
/** The progress bar component properties */
progress: ProgressProps
/** The ID of the prayer request being shown */
requestId: string
/** The date/time after which the request will be once again shown */
showAfter: number
}
/** The shape of the parameter to the snooze request action */
export interface SnoozeRequestAction {
/** The progress bar component properties */
progress: ProgressProps
/** The ID of the prayer request being snoozed/unsnoozed */
requestId: string
/** The date/time after which the request will be once again shown */
until: number
}
/** The shape of the parameter to the update request action */
export interface UpdateRequestAction {
/** The progress bar component properties */
progress: ProgressProps
/** The ID of the prayer request */
requestId: string
/** The text of the update */
updateText: string
/** The status associated with the update */
status: string
/** The type of recurrence for the request */
recurType: string
/** The number of intervals for non-immediate recurrence */
recurCount: number
}

View File

@ -1,20 +0,0 @@
import Vue from 'vue'
import { Ref } from '@vue/composition-api';
export interface SnackbarProps {
events: Vue
visible: Ref<boolean>
message: Ref<string>
interval: Ref<number>
showSnackbar: (msg: string) => void
showInfo: (msg: string) => void
showError: (msg: string) => void
}
export interface ProgressProps {
events: Vue
visible: Ref<boolean>
mode: Ref<string>
showProgress: (mod: string) => void
hideProgress: () => void
}

View File

@ -1 +0,0 @@
declare module 'vue-material/dist/components'

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.auth0.com/js/auth0/8.9/auth0.min.js"></script>
<script>
var webAuth = new auth0.WebAuth({
domain: 'djs-consulting.auth0.com',
clientID: 'Of2s0RQCQ3mt3dwIkOBY5h85J9sXbF2n',
scope: 'openid profile email',
responseType: 'token id_token',
redirectUri: location.protocol + '//' + location.host + '/static/silent.html'
})
</script>
<script>
webAuth.parseHash(window.location.hash, function (err, response) {
parent.postMessage(err || response, location.protocol + '//' + location.host);
})
</script>
</head>
<body></body>
</html>

View File

@ -1,39 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@ -1,15 +0,0 @@
const webpack = require('webpack')
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
outputDir: '../MyPrayerJournal.Api/wwwroot',
configureWebpack: {
plugins: [
// new BundleAnalyzerPlugin(),
],
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
}