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