Version 3 #67
src/MyPrayerJournal/App
.browserslistrc.eslintrc.js.gitignoreREADME.mdbabel.config.jspackage-lock.jsonpackage.json
public
src
tsconfig.jsonvue.config.js@ -1,3 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
@ -1,18 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended',
|
|
||||||
'@vue/typescript/recommended'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2020
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
|
||||||
}
|
|
||||||
}
|
|
23
src/MyPrayerJournal/App/.gitignore
vendored
23
src/MyPrayerJournal/App/.gitignore
vendored
@ -1,23 +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*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
@ -1,24 +0,0 @@
|
|||||||
# my-prayer-journal-app
|
|
||||||
|
|
||||||
## Project setup
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
|
||||||
```
|
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and minifies for production
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lints and fixes files
|
|
||||||
```
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customize configuration
|
|
||||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
31395
src/MyPrayerJournal/App/package-lock.json
generated
31395
src/MyPrayerJournal/App/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,45 +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",
|
|
||||||
"build": "vue-cli-service build",
|
|
||||||
"lint": "vue-cli-service lint",
|
|
||||||
"apistart": "cd ../Server && dotnet run",
|
|
||||||
"publish": "vue-cli-service build --modern && cd ../Server && dotnet publish -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained false",
|
|
||||||
"vue": "vue-cli-service build --mode development && cd ../Server && dotnet run"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@vueuse/core": "^6.4.1",
|
|
||||||
"auth0-js": "^9.16.4",
|
|
||||||
"bootstrap": "^5.1.1",
|
|
||||||
"core-js": "^3.6.5",
|
|
||||||
"date-fns": "^2.24.0",
|
|
||||||
"vue": "^3.0.0",
|
|
||||||
"vue-router": "^4.0.0-0",
|
|
||||||
"vuex": "^4.0.0-0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/auth0-js": "^9.14.5",
|
|
||||||
"@types/events": "^3.0.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^4.29.3",
|
|
||||||
"@typescript-eslint/parser": "^4.29.3",
|
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
|
||||||
"@vue/cli-plugin-router": "~4.5.0",
|
|
||||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
|
||||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
|
||||||
"@vue/cli-service": "~4.5.0",
|
|
||||||
"@vue/compiler-sfc": "^3.0.0",
|
|
||||||
"@vue/eslint-config-typescript": "^7.0.0",
|
|
||||||
"eslint": "^6.8.0",
|
|
||||||
"eslint-plugin-vue": "^7.17.0",
|
|
||||||
"sass": "^1.37.0",
|
|
||||||
"sass-loader": "^10.0.0",
|
|
||||||
"typescript": "~4.3.5",
|
|
||||||
"vue-cli-plugin-pug": "~2.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="">
|
|
||||||
<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">
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> 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>
|
|
@ -1,27 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
.mpj-app
|
|
||||||
navigation-bar
|
|
||||||
router-view
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import "bootstrap/dist/css/bootstrap.min.css"
|
|
||||||
|
|
||||||
import NavigationBar from "@/components/NavigationBar.vue"
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
#app
|
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif
|
|
||||||
-webkit-font-smoothing: antialiased
|
|
||||||
-moz-osx-font-smoothing: grayscale
|
|
||||||
color: #2c3e50
|
|
||||||
#nav
|
|
||||||
padding: 30px
|
|
||||||
a
|
|
||||||
font-weight: bold
|
|
||||||
color: #2c3e50
|
|
||||||
&.router-link-exact-active
|
|
||||||
color: #42b983
|
|
||||||
</style>
|
|
@ -1,177 +0,0 @@
|
|||||||
import { JournalRequest, NotesEntry } from "./types"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a URL that will access the API
|
|
||||||
* @param url The partial URL for the API
|
|
||||||
* @returns A full URL for the API
|
|
||||||
*/
|
|
||||||
const apiUrl = (url : string) : string => `/api/${url}`
|
|
||||||
|
|
||||||
/** The bearer token */
|
|
||||||
let bearer : string | undefined
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create request init parameters
|
|
||||||
*
|
|
||||||
* @param method The method by which the request should be executed
|
|
||||||
* @param user The currently logged-on user
|
|
||||||
* @returns RequestInit parameters
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const reqInit = (method : string, body : any | undefined = undefined) : RequestInit => {
|
|
||||||
const headers = new Headers()
|
|
||||||
if (bearer) headers.append("Authorization", bearer)
|
|
||||||
if (body) {
|
|
||||||
headers.append("Content-Type", "application/json")
|
|
||||||
return {
|
|
||||||
headers,
|
|
||||||
method,
|
|
||||||
cache: "no-cache",
|
|
||||||
body: JSON.stringify(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
headers,
|
|
||||||
method
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a result for an API call
|
|
||||||
*
|
|
||||||
* @param resp The response received from the API
|
|
||||||
* @param action The action being performed (used in error messages)
|
|
||||||
* @returns The expected result (if found), undefined (if not found), or an error string
|
|
||||||
*/
|
|
||||||
async function apiResult<T> (resp : Response, action : string) : Promise<T | undefined | string> {
|
|
||||||
if (resp.status === 200) return await resp.json() as T
|
|
||||||
if (resp.status === 404) return undefined
|
|
||||||
return `Error ${action} - ${await resp.text()}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run an API action that does not return a result
|
|
||||||
*
|
|
||||||
* @param resp The response received from the API call
|
|
||||||
* @param action The action being performed (used in error messages)
|
|
||||||
* @returns Undefined (if successful), or an error string
|
|
||||||
*/
|
|
||||||
const apiAction = async (resp : Response, action : string) : Promise<string | undefined> => {
|
|
||||||
if (resp.status === 200) return undefined
|
|
||||||
return `Error ${action} - ${await resp.text()}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) => {
|
|
||||||
console.info(`Setting bearer token to ${token}`)
|
|
||||||
bearer = `Bearer ${token}`
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the bearer token
|
|
||||||
*/
|
|
||||||
removeBearer: () => { bearer = undefined },
|
|
||||||
|
|
||||||
notes: {
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
add: async (requestId : string, notes : string) =>
|
|
||||||
apiAction(await fetch(apiUrl(`request/${requestId}/note`), reqInit("POST", { notes })), "adding note"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get past notes for a prayer request
|
|
||||||
* @param requestId The Id of the request for which notes should be retrieved
|
|
||||||
*/
|
|
||||||
getForRequest: async (requestId : string) =>
|
|
||||||
apiResult<NotesEntry[]>(await fetch(apiUrl(`request/${requestId}/notes`), reqInit("GET")), "getting notes")
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
add: async (requestText : string, recurType : string, recurCount : number) =>
|
|
||||||
apiAction(await fetch(apiUrl("request"), reqInit("POST", { requestText, recurType, recurCount })),
|
|
||||||
"adding prayer request"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a prayer request (full; includes all history and notes)
|
|
||||||
* @param requestId The Id of the request to retrieve
|
|
||||||
*/
|
|
||||||
full: async (requestId : string) =>
|
|
||||||
apiResult<JournalRequest>(await fetch(apiUrl(`request/${requestId}/full`), reqInit("GET")),
|
|
||||||
"retrieving full request"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a prayer request (journal-style; only latest update)
|
|
||||||
* @param requestId The Id of the request to retrieve
|
|
||||||
*/
|
|
||||||
get: async (requestId : string) =>
|
|
||||||
apiResult<JournalRequest>(await fetch(apiUrl(`request/${requestId}`), reqInit("GET")), "retrieving request"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all answered requests, along with the text they had when it was answered
|
|
||||||
*/
|
|
||||||
getAnswered: async () =>
|
|
||||||
apiResult<JournalRequest[]>(await fetch(apiUrl("requests/answered"), reqInit("GET")),
|
|
||||||
"retrieving answered requests"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all prayer requests and their most recent updates
|
|
||||||
*/
|
|
||||||
journal: async () =>
|
|
||||||
apiResult<JournalRequest[]>(await fetch(apiUrl('journal'), reqInit("GET")), "retrieving 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
|
|
||||||
*/
|
|
||||||
show: async (requestId : string, showAfter : number) =>
|
|
||||||
apiAction(await fetch(apiUrl(`request/${requestId}/show`), reqInit("PATCH", { showAfter })), "showing request"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
snooze: async (requestId : string, until : number) =>
|
|
||||||
apiAction(await fetch(apiUrl(`request/${requestId}/snooze`), reqInit("PATCH", { until })), "snoozing request"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
*/
|
|
||||||
update: async (requestId : string, status : string, updateText : string) =>
|
|
||||||
apiAction(await fetch(apiUrl(`request/${requestId}/history`), reqInit("POST", { status, updateText })),
|
|
||||||
"updating request"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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: async (requestId : string, recurType : string, recurCount : number) =>
|
|
||||||
apiAction(await fetch(apiUrl(`request/${requestId}/recurrence`), reqInit("PATCH", { recurType, recurCount })),
|
|
||||||
"updating request recurrence")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from './types'
|
|
@ -1,43 +0,0 @@
|
|||||||
/** 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[] = []
|
|
||||||
}
|
|
Binary file not shown.
@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
export default {
|
|
||||||
clientId: 'Of2s0RQCQ3mt3dwIkOBY5h85J9sXbF2n',
|
|
||||||
domain: 'djs-consulting.auth0.com',
|
|
||||||
appDomain: `${location.protocol}//${location.host}`,
|
|
||||||
callbackUrl: '/user/log-on',
|
|
||||||
audience: 'https://prayerjournal.me'
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
import { Store } from "vuex"
|
|
||||||
import auth0, { Auth0DecodedHash } from "auth0-js"
|
|
||||||
import { EventEmitter } from "events"
|
|
||||||
|
|
||||||
import { Mutations, State } from "@/store"
|
|
||||||
|
|
||||||
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<State>) {
|
|
||||||
try {
|
|
||||||
const authResult = await this.parseHash()
|
|
||||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
|
||||||
this.setSession(authResult)
|
|
||||||
store.commit(Mutations.UserLoggedOn, this.session.profile)
|
|
||||||
}
|
|
||||||
} catch (err : any) {
|
|
||||||
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<State>) {
|
|
||||||
// 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?.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?.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()
|
|
@ -1,19 +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)
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, h, onBeforeUnmount, onMounted, ref } from "vue"
|
|
||||||
import { format, formatDistance } from "date-fns"
|
|
||||||
|
|
||||||
/** Properties for this component */
|
|
||||||
interface Props {
|
|
||||||
/** The tag name to be rendered (defaults to `span`) */
|
|
||||||
tag : string
|
|
||||||
/** The value of the date */
|
|
||||||
value : number
|
|
||||||
/** The interval at which the date should be refreshed (defaults to 10 seconds) */
|
|
||||||
interval : number
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
tag : "span",
|
|
||||||
value : 0,
|
|
||||||
interval : 10000
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 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))
|
|
||||||
|
|
||||||
/** Render the element */
|
|
||||||
h(props.tag, {
|
|
||||||
domProps: {
|
|
||||||
title: actual.value,
|
|
||||||
innerText: fromNow.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,72 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
nav.navbar.navbar-dark
|
|
||||||
.container-fluid
|
|
||||||
router-link.navbar-brand(to="/")
|
|
||||||
span.m my
|
|
||||||
span.p Prayer
|
|
||||||
span.j Journal
|
|
||||||
ul.navbar-nav.me-auto.d-flex.flex-row
|
|
||||||
template(v-if="isAuthenticated")
|
|
||||||
li.nav-item: router-link(to="/journal") Journal
|
|
||||||
li.nav-item: router-link(to="/requests/active") Active
|
|
||||||
li.nav-item(v-if="hasSnoozed"): router-link(to="/requests/snoozed") Snoozed
|
|
||||||
li.nav-item: router-link(to="/requests/answered") Answered
|
|
||||||
li.nav-item: a(href="/user/log-off" @click.prevent="logOff()") Log Off
|
|
||||||
template(v-else)
|
|
||||||
li.nav-item: a(href="/user/log-on" @click.prevent="logOn()") Log On
|
|
||||||
li.nav-item: a(href="https://docs.prayerjournal.me" target="_blank") Docs
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from "vue"
|
|
||||||
import { useRouter } from "vue-router"
|
|
||||||
|
|
||||||
import { useAuth } from "@/plugins/auth"
|
|
||||||
import { useStore } from "@/store"
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuth()
|
|
||||||
|
|
||||||
/** Whether a user is authenticated */
|
|
||||||
const isAuthenticated = computed(() => store?.state.isAuthenticated ?? false)
|
|
||||||
|
|
||||||
/** 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("/")
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
|
||||||
nav
|
|
||||||
background-color: green
|
|
||||||
.m
|
|
||||||
font-weight: 100
|
|
||||||
.p
|
|
||||||
font-weight: 400
|
|
||||||
.j
|
|
||||||
font-weight: 700
|
|
||||||
.nav-item
|
|
||||||
a:link,
|
|
||||||
a:visited
|
|
||||||
padding: .5rem 1rem
|
|
||||||
margin: 0 .5rem
|
|
||||||
border-radius: .5rem
|
|
||||||
color: white
|
|
||||||
text-decoration: none
|
|
||||||
a:hover
|
|
||||||
cursor: pointer
|
|
||||||
background-color: rgba(255, 255, 255, .2)
|
|
||||||
.navbar-nav .router-link-exact-active
|
|
||||||
background-color: rgba(255, 255, 255, .2)
|
|
||||||
</style>
|
|
@ -1,7 +0,0 @@
|
|||||||
import { createApp } from "vue"
|
|
||||||
import App from "./App.vue"
|
|
||||||
import auth, { key as authKey } from "./plugins/auth"
|
|
||||||
import router from "./router"
|
|
||||||
import store, { key as storeKey } from "./store"
|
|
||||||
|
|
||||||
createApp(App).use(store, storeKey).use(router).use(auth, authKey).mount('#app')
|
|
@ -1,34 +0,0 @@
|
|||||||
import { App, InjectionKey } from "vue"
|
|
||||||
import authService, { AuthService } from "@/auth"
|
|
||||||
|
|
||||||
/** The symbol to use for dependency injection */
|
|
||||||
export const key : InjectionKey<AuthService> = Symbol("Auth service")
|
|
||||||
|
|
||||||
/** The auth service instance */
|
|
||||||
const service = new AuthService()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install (app : App) {
|
|
||||||
Object.defineProperty(app, "authService", { get: () => service })
|
|
||||||
|
|
||||||
app.provide(key, service)
|
|
||||||
|
|
||||||
app.mixin({
|
|
||||||
created () {
|
|
||||||
if (this.handleLoginEvent) {
|
|
||||||
authService.addListener("loginEvent", this.handleLoginEvent)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroyed () {
|
|
||||||
if (this.handleLoginEvent) {
|
|
||||||
authService.removeListener("loginEvent", this.handleLoginEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Use the auth service */
|
|
||||||
export function useAuth () : AuthService {
|
|
||||||
return service
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
import {
|
|
||||||
createRouter,
|
|
||||||
createWebHistory,
|
|
||||||
NavigationGuardNext,
|
|
||||||
RouteLocationNormalized,
|
|
||||||
RouteRecordRaw
|
|
||||||
} from "vue-router"
|
|
||||||
|
|
||||||
import auth from "@/auth"
|
|
||||||
import store, { Mutations } from "@/store"
|
|
||||||
import Home from "@/views/Home.vue"
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
name: "Home",
|
|
||||||
component: Home,
|
|
||||||
meta: { title: "Welcome!" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/journal",
|
|
||||||
name: "Journal",
|
|
||||||
component: () => import(/* webpackChunkName: "journal" */ "@/views/Journal.vue"),
|
|
||||||
meta: { auth: true, title: "Loading Prayer Journal..." }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/legal/privacy-policy",
|
|
||||||
name: "PrivacyPolicy",
|
|
||||||
component: () => import(/* webpackChunkName: "legal" */ "@/views/legal/PrivacyPolicy.vue"),
|
|
||||||
meta: { title: "Privacy Policy" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/legal/terms-of-service",
|
|
||||||
name: "TermsOfService",
|
|
||||||
component: () => import(/* webpackChunkName: "legal" */ "@/views/legal/TermsOfService.vue"),
|
|
||||||
meta: { title: "Terms of Service" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/request/:id/edit",
|
|
||||||
name: "EditRequest",
|
|
||||||
component: () => import(/* webpackChunkName: "edit" */ "@/views/request/EditRequest.vue"),
|
|
||||||
meta: { auth: true, title: "Edit Prayer Request" }
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// path: "/request/:id/full",
|
|
||||||
// name: "FullRequest",
|
|
||||||
// component: () => import(/* webpackChunkName: "full" */ "@/views/request/FullRequest.vue"),
|
|
||||||
// meta: { auth: true, title: "View Full Prayer Request" }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: "/requests/active",
|
|
||||||
// name: "ActiveRequests",
|
|
||||||
// component: () => import(/* webpackChunkName: "list" */ "@/views/request/ActiveRequests.vue"),
|
|
||||||
// meta: { auth: true, title: "All Active Requests" }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: "/requests/answered",
|
|
||||||
// name: "AnsweredRequests",
|
|
||||||
// component: () => import(/* webpackChunkName: "list" */ "@/views/request/AnsweredRequests.vue"),
|
|
||||||
// meta: { auth: true, title: "Answered Requests" }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: "/requests/snoozed",
|
|
||||||
// name: "SnoozedRequests",
|
|
||||||
// component: () => import(/* webpackChunkName: "list" */ "@/views/request/SnoozedRequests.vue"),
|
|
||||||
// meta: { auth: true, title: "Snoozed Requests" }
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
path: "/user/log-on",
|
|
||||||
name: "LogOn",
|
|
||||||
component: () => import(/* webpackChunkName: "logon" */ "@/views/user/LogOn.vue"),
|
|
||||||
meta: { title: "Logging On..." }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
|
||||||
scrollBehavior: (to : RouteLocationNormalized, from : RouteLocationNormalized, savedPosition : any) => {
|
|
||||||
return savedPosition ?? { x: 0, y: 0 }
|
|
||||||
},
|
|
||||||
routes
|
|
||||||
})
|
|
||||||
|
|
||||||
router.beforeEach((to : RouteLocationNormalized, from : RouteLocationNormalized, next : NavigationGuardNext) => {
|
|
||||||
// Check for routes requiring authentication
|
|
||||||
if (!store.state.isAuthenticated && (to.meta.auth || false)) {
|
|
||||||
return auth.login({ target: to.path })
|
|
||||||
}
|
|
||||||
store.commit(Mutations.SetTitle, to.meta.title ?? "")
|
|
||||||
return next()
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
6
src/MyPrayerJournal/App/src/shims-vue.d.ts
vendored
6
src/MyPrayerJournal/App/src/shims-vue.d.ts
vendored
@ -1,6 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
declare module '*.vue' {
|
|
||||||
import type { DefineComponent } from 'vue'
|
|
||||||
const component: DefineComponent<{}, {}, any>
|
|
||||||
export default component
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
/** Action to add a prayer request (pass request text) */
|
|
||||||
export const AddRequest = "add-request"
|
|
||||||
|
|
||||||
/** Action to check if a user is authenticated, refreshing the session first if it exists */
|
|
||||||
export const CheckAuthentication = "check-authentication"
|
|
||||||
|
|
||||||
/** Action to load the user's prayer journal */
|
|
||||||
export const LoadJournal = "load-journal"
|
|
||||||
|
|
||||||
/** Action to update a request */
|
|
||||||
export const UpdateRequest = "update-request"
|
|
||||||
|
|
||||||
/** Action to skip the remaining recurrence period */
|
|
||||||
export const ShowRequestNow = "show-request-now"
|
|
||||||
|
|
||||||
/** Action to snooze a request */
|
|
||||||
export const SnoozeRequest = "snooze-request"
|
|
@ -1,201 +0,0 @@
|
|||||||
import { InjectionKey } from "vue"
|
|
||||||
import { createStore, Store, useStore as baseUseStore } from "vuex"
|
|
||||||
import { useTitle } from "@vueuse/core"
|
|
||||||
|
|
||||||
import api, { JournalRequest } from "@/api"
|
|
||||||
import { useAuth } from "@/plugins/auth"
|
|
||||||
|
|
||||||
import * as Actions from "./actions"
|
|
||||||
import * as Mutations from "./mutations"
|
|
||||||
|
|
||||||
/** The state of myPrayerJournal */
|
|
||||||
export interface State {
|
|
||||||
pageTitle : string,
|
|
||||||
/** 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An injection key to identify this state with Vue */
|
|
||||||
export const key : InjectionKey<Store<State>> = Symbol("VueX Store")
|
|
||||||
|
|
||||||
/** Use this store in component `setup` functions */
|
|
||||||
export function useStore () : Store<State> {
|
|
||||||
return baseUseStore(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The authentication service */
|
|
||||||
const auth = useAuth()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 : any) {
|
|
||||||
if (err.message === "Not logged in") {
|
|
||||||
console.warn("API request attempted when user was not logged in")
|
|
||||||
} else {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The name of the application */
|
|
||||||
const appName = "myPrayerJournal"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the sort value for a prayer request
|
|
||||||
* @param it The prayer request
|
|
||||||
*/
|
|
||||||
const sortValue = (it : JournalRequest) => it.showAfter === 0 ? it.asOf : it.showAfter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort journal requests either by asOf or showAfter
|
|
||||||
*/
|
|
||||||
const journalSort = (a : JournalRequest, b : JournalRequest) => sortValue(a) - sortValue(b)
|
|
||||||
|
|
||||||
export default createStore({
|
|
||||||
state: () : State => ({
|
|
||||||
pageTitle: appName,
|
|
||||||
user: auth?.session.profile ?? {},
|
|
||||||
isAuthenticated: auth?.isAuthenticated() ?? false,
|
|
||||||
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.SetTitle]: (state, title : string) => {
|
|
||||||
state.pageTitle = title === "" ? appName : `${title} | ${appName}`
|
|
||||||
useTitle(state.pageTitle)
|
|
||||||
},
|
|
||||||
[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.request.journal()
|
|
||||||
if (typeof jrnl === "string") {
|
|
||||||
throw new Error(`Unable to retrieve journal - ${jrnl}`)
|
|
||||||
}
|
|
||||||
if (typeof jrnl === "undefined") {
|
|
||||||
throw new Error(`HTTP 404 when retrieving journal`)
|
|
||||||
}
|
|
||||||
commit(Mutations.LoadedJournal, jrnl)
|
|
||||||
} 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")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export * as Actions from "./actions"
|
|
||||||
export * as Mutations from "./mutations"
|
|
@ -1,23 +0,0 @@
|
|||||||
/** Mutation for when the user's prayer journal is being loaded */
|
|
||||||
export const LoadingJournal = "loading-journal"
|
|
||||||
|
|
||||||
/** Mutation for when the user's prayer journal has been loaded */
|
|
||||||
export const LoadedJournal = "journal-loaded"
|
|
||||||
|
|
||||||
/** Mutation for adding a new prayer request (pass text) */
|
|
||||||
export const RequestAdded = "request-added"
|
|
||||||
|
|
||||||
/** Mutation to replace a prayer request at the top of the current journal */
|
|
||||||
export const RequestUpdated = "request-updated"
|
|
||||||
|
|
||||||
/** Mutation for setting the authentication state */
|
|
||||||
export const SetAuthentication = "set-authentication"
|
|
||||||
|
|
||||||
/** Mutation for setting the page title */
|
|
||||||
export const SetTitle = "set-title"
|
|
||||||
|
|
||||||
/** Mutation for logging a user off */
|
|
||||||
export const UserLoggedOff = "user-logged-off"
|
|
||||||
|
|
||||||
/** Mutation for logging a user on (pass user) */
|
|
||||||
export const UserLoggedOn = "user-logged-on"
|
|
@ -1,12 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
main.container
|
|
||||||
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. 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 “Log On” link
|
|
||||||
above, and log on with either a Microsoft or Google account. You can also learn more about the site at the
|
|
||||||
“Docs” link, also above.
|
|
||||||
</template>
|
|
@ -1,41 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
main
|
|
||||||
p(v-if="isLoadingJournal") Loading your prayer journal…
|
|
||||||
template(v-else)
|
|
||||||
.card(v-if="journal.length === 0").no-requests.mt-3
|
|
||||||
.card-body
|
|
||||||
h5.card-title No Active Requests
|
|
||||||
p.card-text.
|
|
||||||
You have no requests to be shown; see the “Active” link above for snoozed or deferred requests,
|
|
||||||
and the “Answered” link for answered requests
|
|
||||||
router-link(:to="{ name: 'EditRequest', params: { id: 'new' } }").btn.btn-primary Add a Request
|
|
||||||
p(v-else) There are requests
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted } from "vue"
|
|
||||||
import { Actions, Mutations, useStore } from "@/store"
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
/** The user's prayer journal */
|
|
||||||
const journal = computed(() => store.state.journal)
|
|
||||||
|
|
||||||
/** Whether the journal is loading */
|
|
||||||
const isLoadingJournal = computed(() => store.state.isLoadingJournal)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
await store.dispatch(Actions.LoadJournal)
|
|
||||||
} finally {
|
|
||||||
store.commit(Mutations.SetTitle, `${store.state.user.given_name}’s Prayer Journal`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.no-requests
|
|
||||||
max-width: 40rem
|
|
||||||
margin: auto
|
|
||||||
</style>
|
|
@ -1,57 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
md-content(role='main').mpj-main-content
|
|
||||||
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 (“sub”) 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 “Log Off” 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 “monetize” 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>
|
|
@ -1,38 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
md-content(role='main').mpj-main-content
|
|
||||||
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>
|
|
@ -1,11 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
main
|
|
||||||
p It works
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const props = defineProps({
|
|
||||||
id: String
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
@ -1,25 +0,0 @@
|
|||||||
<template lang="pug">
|
|
||||||
main.container
|
|
||||||
p Logging you on...
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onBeforeMount } from "vue"
|
|
||||||
import { useRouter } from "vue-router"
|
|
||||||
import { useAuth } from "@/plugins/auth"
|
|
||||||
import { useStore } from "@/store"
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
/** Auth service instance */
|
|
||||||
const auth = useAuth()
|
|
||||||
|
|
||||||
/** 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) })
|
|
||||||
</script>
|
|
@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"module": "esnext",
|
|
||||||
"strict": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"importHelpers": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": 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"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
lintOnSave: false,
|
|
||||||
outputDir: "../Server/wwwroot",
|
|
||||||
configureWebpack: {
|
|
||||||
module: {
|
|
||||||
rules: [{
|
|
||||||
test: /\.mjs$/,
|
|
||||||
include: /node_modules/,
|
|
||||||
type: "javascript/auto"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user