Compare commits

..

1 Commits

Author SHA1 Message Date
69ae5fe2ff fix syntax & unused code warnings 2023-07-10 19:19:53 -06:00
31 changed files with 1140 additions and 2315 deletions

40
package-lock.json generated
View File

@@ -33,13 +33,11 @@
"ethereum-cryptography": "^2.0.0", "ethereum-cryptography": "^2.0.0",
"ethereumjs-util": "^7.1.5", "ethereumjs-util": "^7.1.5",
"ethr-did-resolver": "^8.0.0", "ethr-did-resolver": "^8.0.0",
"jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9", "js-generate-password": "^0.1.9",
"localstorage-slim": "^2.4.0", "localstorage-slim": "^2.4.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"merkletreejs": "^0.3.10", "merkletreejs": "^0.3.10",
"moment": "^2.29.4", "moment": "^2.29.4",
"notiwind": "^2.0.2",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"pina": "^0.20.2204228", "pina": "^0.20.2204228",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
@@ -12050,14 +12048,6 @@
"resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz", "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz",
"integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==" "integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w=="
}, },
"node_modules/canvas-renderer": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz",
"integrity": "sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/case-sensitive-paths-webpack-plugin": { "node_modules/case-sensitive-paths-webpack-plugin": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", "resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
@@ -17807,20 +17797,6 @@
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true "dev": true
}, },
"node_modules/jdenticon": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.2.0.tgz",
"integrity": "sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==",
"dependencies": {
"canvas-renderer": "~2.2.0"
},
"bin": {
"jdenticon": "bin/jdenticon.js"
},
"engines": {
"node": ">=6.4.0"
}
},
"node_modules/jest-environment-node": { "node_modules/jest-environment-node": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz",
@@ -20952,11 +20928,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "0.5.6", "version": "0.5.6",
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -21304,17 +21275,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/notiwind": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/notiwind/-/notiwind-2.0.2.tgz",
"integrity": "sha512-wMCf+07E093d0Q78C5UHroT9GQHm4mIGerhg7dGLJ0GN6zONqKj8nTR3clkq/Y44On9k28/0DtDNwOX7FT5p/A==",
"dependencies": {
"mitt": "^3.0.1"
},
"peerDependencies": {
"vue": "^3.3.4"
}
},
"node_modules/npm-package-arg": { "node_modules/npm-package-arg": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz",

View File

@@ -33,13 +33,11 @@
"ethereum-cryptography": "^2.0.0", "ethereum-cryptography": "^2.0.0",
"ethereumjs-util": "^7.1.5", "ethereumjs-util": "^7.1.5",
"ethr-did-resolver": "^8.0.0", "ethr-did-resolver": "^8.0.0",
"jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9", "js-generate-password": "^0.1.9",
"localstorage-slim": "^2.4.0", "localstorage-slim": "^2.4.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"merkletreejs": "^0.3.10", "merkletreejs": "^0.3.10",
"moment": "^2.29.4", "moment": "^2.29.4",
"notiwind": "^2.0.2",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"pina": "^0.20.2204228", "pina": "^0.20.2204228",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",

View File

@@ -1,49 +1,50 @@
tasks: tasks:
- .2 bug - on contacts view, click on "to" & "from" and nothing happens - .5 audit all console.log calls
- 01 add a location for a project via map pin : - 01 design ideas for simple gives on the Home page
- add with a "location" field containing this: { "geo":{ "@type":"GeoCoordinates", "latitude":40.883944, "longitude":-111.884787 } } - 01 add list of 'give' records for a project on ProjectView UI
- 04 search by a bounding box for local projects (see API by clicking on "Nearby") - 02 Discover page - display results (currently in console.log) & link, spin when searching
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:jose - 08 search by location, endpoint, etc assignee:trent
- 02 Fix images on projectview - allow choice of image from a pallete of images or a url image. - 01 add a location for a project via map pin (see API by clicking on "Nearby")
- 01 remove all the "form" fields (or at least investigate to see if that page refresh is desired)
- .2 change "errorMessage" to "alertMessage" on ProjectViewView.vue
- 08 Scan QR code to import into contacts. - 08 Scan QR code to import into contacts.
- 40 notifications : - contacts v1 :
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data - 01 Import contact info a la QR code.
- 01 Import all the non-sensitive data (ie. contacts & settings).
- .2 move all "identity" references to temporary account access assignee:trent
- contacts v+ :
- .2 show error to user when adding a duplicate contact
- 01 parse input more robustly (with CSV lib and not commas)
- refactor UI : - refactor UI :
- .5 Alerts show at the top and can be missed if you've scrolled down on the page, eg. account data download - .5 Alerts show at the top and can be missed, eg. account data download
- .2 Make alerts at the top more visible (because they're currently a similar color and sometimes aren't seen) - .5 Fix how icons show on top of bottom bar on ContactAmounts page
- .2 Hide "Advanced" section in Account page by default
- Show pop-up or some message confirming that settings & contacts download has been initiated/finished - show pop-up confirming that settings & contacts have been downloaded
- Ensure each action sent to the server has a confirmation - eg registration - Ensure each action sent to the server has a confirmation - registration
- Home Feed & Quick Give screen : - Home Feed & Quick Give screen :
- 01 save the feed-viewed status in settings storage ("afterQuery") - 01 save the feed-viewed status in settings storage ("afterQuery")
- 01 quick action - send action, maybe choose via canvas tool https://github.com/konvajs/vue-konva - 01 quick action - send action, maybe choose via canvas tool https://github.com/konvajs/vue-konva
- .5 customize favicon
- 04 allow user to download claims, mine + ones I can see about me from others
- 24 Move to Vite - 24 Move to Vite
- .5 add link to further project / people when a project pays ahead - 40 notifications :
- .5 add project ID to the URL, to make a project publicly-accessible - push
- .5 remove edit from project page for projects owned by others
- .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
- .2 there are three dots at the top of ProjectViewView that refreshes the page but doesn't do anything else
- 01 fix images on project page, on discovery page
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist
- .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?)
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
- .2 move 'switch identity' to the advanced section
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.
- contacts v+ : - 01 fix images on project page, on discovery page
- 01 Import all the non-sensitive data (ie. contacts & settings). - .2 fix "Rotary" and static icon to the right on project page
- .2 show error to user when adding a duplicate contact
- 01 parse input more robustly (with CSV lib and not commas)
- stats v1 : - stats v1 :
- 01 show numeric stats - 01 show numeric stats
@@ -52,11 +53,7 @@ tasks:
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version") - maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages - Do we want split first name & last name?
- .5 customize favicon
- 04 allow user to download claims, mine + ones I can see about me from others
- Do we want to combine first name & last name?
- Show a warning if both giver and recipient are the same (but still allow?)
- Release Minimum Viable Product : - Release Minimum Viable Product :
- 08 thorough testing for errors & edge cases - 08 thorough testing for errors & edge cases
@@ -69,6 +66,9 @@ tasks:
- Test PWA features on Android and iOS. - Test PWA features on Android and iOS.
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
- 40 notifications v+ :
- pull, w/ scheduled runs
- linking between projects or plans : - linking between projects or plans :
- show total time given to & from a project - show total time given to & from a project
- terminology: - terminology:
@@ -97,10 +97,6 @@ tasks:
- Write to or read from a different ledger (eg. private ACDC, attest.sh) - Write to or read from a different ledger (eg. private ACDC, attest.sh)
- Do we want split first name & last name?
- 40 notifications v+ :
- pull, w/ scheduled runs
log: log:
- videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29 - videos for multiple identities https://youtu.be/p8L87AeD76w and for adding time to contacts https://youtu.be/7Yylczevp10 done:2023-03-29

View File

@@ -1,132 +1,5 @@
<template> <template>
<router-view /> <router-view />
<NotificationGroup group="alert">
<div
class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end"
>
<Notification
v-slot="{ notifications, close }"
enter="transform ease-out duration-300 transition"
enter-from="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4"
enter-to="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-500"
leave-from="opacity-100"
leave-to="opacity-0"
move="transition duration-500"
move-delay="delay-300"
>
<div
v-for="notification in notifications"
:key="notification.id"
class="w-full"
role="alert"
>
<div
v-if="notification.type === 'toast'"
class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md"
>
<div class="w-full px-4 py-3">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ notification.text }}</p>
</div>
</div>
<div
v-if="notification.type === 'info'"
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-100 rounded-lg shadow-md"
>
<div
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
>
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ notification.text }}</p>
<button
@click="close(notification.id)"
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
>
<fa icon="xmark" class="fa-fw"></fa>
</button>
</div>
</div>
<div
v-if="notification.type === 'success'"
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-emerald-100 rounded-lg shadow-md"
>
<div
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
>
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ notification.text }}</p>
<button
@click="close(notification.id)"
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
>
<fa icon="xmark" class="fa-fw"></fa>
</button>
</div>
</div>
<div
v-if="notification.type === 'warning'"
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-amber-100 rounded-lg shadow-md"
>
<div
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
>
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ notification.text }}</p>
<button
@click="close(notification.id)"
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
>
<fa icon="xmark" class="fa-fw"></fa>
</button>
</div>
</div>
<div
v-if="notification.type === 'danger'"
class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-rose-100 rounded-lg shadow-md"
>
<div
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
>
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ notification.text }}</p>
<button
@click="close(notification.id)"
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
>
<fa icon="xmark" class="fa-fw"></fa>
</button>
</div>
</div>
</div>
</Notification>
</div>
</NotificationGroup>
</template> </template>
<style></style> <style></style>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div v-bind:class="computedAlertClassNames()"> <div v-bind:class="computedAlertClassNames()">
<button <button
class="close-button bg-amber-400 w-8 leading-loose rounded-full absolute top-2 right-2" class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@click="onClickClose()" @click="onClickClose()"
> >
<fa icon="xmark"></fa> <fa icon="xmark"></fa>
@@ -28,7 +28,7 @@ export default class AlertMessage extends Vue {
return { return {
hidden: !this.isAlertVisible, hidden: !this.isAlertVisible,
"dismissable-alert": true, "dismissable-alert": true,
"bg-amber-200": true, "bg-slate-100": true,
"p-5": true, "p-5": true,
rounded: true, rounded: true,
"drop-shadow-lg": true, "drop-shadow-lg": true,

View File

@@ -1,19 +0,0 @@
<template>
<div v-html="generateIdenticon()" class="w-fit"></div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-facing-decorator";
import { toSvg } from "jdenticon";
@Component
export default class EntityIcon extends Vue {
@Prop entityId = "";
@Prop iconSize = "";
generateIdenticon() {
const svgString = toSvg(this.entityId, this.iconSize);
return svgString;
}
}
</script>
<style scoped></style>

View File

@@ -1,51 +1,41 @@
<template> <template>
<div v-if="visible" class="dialog-overlay"> <div v-if="visible" class="dialog-overlay">
<div class="dialog"> <div class="dialog">
<h1 class="text-xl font-bold text-center mb-4"> <h1 class="text-lg text-center">
{{ message }} {{ giver?.name || "somebody not specified" }} {{ message }} {{ giver?.name || "somebody not specified" }}
</h1> </h1>
<input <input
type="text" type="text"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2" class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
placeholder="What was received" placeholder="What was received"
v-model="description" v-model="description"
/> />
<div class="flex flex-row mb-6"> <div class="flex flex-row">
<span <span class="py-4">Hours</span>
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center px-2 py-2"
>Hours</span
>
<div
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
@click="decrement()"
>
<fa icon="chevron-left" />
</div>
<input <input
type="text" type="text"
class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center" class="block w-8 rounded border border-slate-400 ml-4 text-center"
v-model="hours" v-model="hours"
/> />
<div <div class="flex flex-col px-1">
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2" <div>
@click="increment()" <fa icon="square-caret-up" size="2xl" @click="increment()" />
> </div>
<fa icon="chevron-right" /> <div>
<fa icon="square-caret-down" size="2xl" @click="decrement()" />
</div>
</div> </div>
</div> </div>
<p class="text-center mb-2 italic">Sign & Send to publish to the world</p> <p class="text-right">Sign & Send to publish to the world</p>
<button <div class="text-right">
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" <button class="rounded border border-slate-400" @click="confirm">
@click="confirm" <span class="m-2">Sign & Send</span>
> </button>
Sign &amp; Send &nbsp;
</button> <button class="rounded border border-slate-400" @click="cancel">
<button <span class="m-2">Cancel</span>
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" </button>
@click="cancel" </div>
>
Cancel
</button>
</div> </div>
</div> </div>
</template> </template>
@@ -116,14 +106,12 @@ export default class GiftedDialog extends Vue {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 1.5rem;
} }
.dialog { .dialog {
background-color: white; background-color: white;
padding: 1rem; padding: 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
width: 100%; width: 50%;
max-width: 500px;
} }
</style> </style>

View File

@@ -0,0 +1,146 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
target="_blank"
rel="noopener"
>babel</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa"
target="_blank"
rel="noopener"
>pwa</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
target="_blank"
rel="noopener"
>router</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
target="_blank"
rel="noopener"
>vuex</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
target="_blank"
rel="noopener"
>eslint</a
>
</li>
<li>
<a
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
target="_blank"
rel="noopener"
>typescript</a
>
</li>
</ul>
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
</li>
<li>
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
>Forum</a
>
</li>
<li>
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
>Community Chat</a
>
</li>
<li>
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
>Twitter</a
>
</li>
<li>
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
</li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li>
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
>vue-router</a
>
</li>
<li>
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
</li>
<li>
<a
href="https://github.com/vuejs/vue-devtools#vue-devtools"
target="_blank"
rel="noopener"
>vue-devtools</a
>
</li>
<li>
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
>vue-loader</a
>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>awesome-vue</a
>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-facing-decorator";
@Component
export default class HelloWorld extends Vue {
@Prop msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -54,6 +54,7 @@ if (localStorage.getItem("secret") == null) {
localStorage.setItem("secret", secret); localStorage.setItem("secret", secret);
} }
//console.log("IndexedDB Encryption Secret:", secret);
encrypted(accountsDB, { secretKey: secret }); encrypted(accountsDB, { secretKey: secret });
accountsDB.version(1).stores(SensitiveSchemas); accountsDB.version(1).stores(SensitiveSchemas);

View File

@@ -82,15 +82,13 @@ export function isHiddenDid(did) {
/** /**
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
**/ **/
export function didInfo( export function didInfo(did, activeDid, identifiers, contacts) {
did: string, const myId: IIdentifier | undefined = R.find(
activeDid: string, (i) => i.did === did,
allMyDids: Array<string>, identifiers,
contacts: Array<Contact>, );
): string {
const myId: string | undefined = R.find(R.equals(did), allMyDids, did);
if (myId) { if (myId) {
return "You" + (myId !== activeDid ? " (Alt ID)" : ""); return "You" + (myId.did !== activeDid ? " (Alt ID)" : "");
} else { } else {
const contact: Contact | undefined = R.find((c) => c.did === did, contacts); const contact: Contact | undefined = R.find((c) => c.did === did, contacts);
if (contact) { if (contact) {
@@ -199,53 +197,3 @@ export function isNumeric(str: string): boolean {
export function numberOrZero(str: string): number { export function numberOrZero(str: string): number {
return isNumeric(str) ? +str : 0; return isNumeric(str) ? +str : 0;
} }
export interface ErrorResponse {
error?: {
message?: string;
};
}
export interface RateLimits {
doneClaimsThisWeek: string;
doneRegistrationsThisMonth: string;
maxClaimsPerWeek: string;
maxRegistrationsPerMonth: string;
nextMonthBeginDateTime: string;
nextWeekBeginDateTime: string;
}
/**
* Represents data about a project
**/
export interface ProjectData {
/**
* Name of the project
**/
name: string;
/**
* Description of the project
**/
description: string;
/**
* URL referencing information about the project
**/
handleId: string;
/**
* The Identier of the project
**/
rowid: string;
}
export interface VerifiableCredential {
"@context": string;
"@type": string;
name: string;
description: string;
identifier?: string;
}
export interface WorldProperties {
startTime?: string;
endTime?: string;
}

View File

@@ -5,7 +5,6 @@ import "./registerServiceWorker";
import router from "./router"; import router from "./router";
import axios from "axios"; import axios from "axios";
import VueAxios from "vue-axios"; import VueAxios from "vue-axios";
import Notifications from "notiwind";
import "./assets/styles/tailwind.css"; import "./assets/styles/tailwind.css";
@@ -14,10 +13,8 @@ import {
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo,
faCircleQuestion, faCircleQuestion,
faCircleUser, faCircleUser,
faClock, faClock,
@@ -47,7 +44,6 @@ import {
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faTrashCan, faTrashCan,
faTriangleExclamation,
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
@@ -57,10 +53,8 @@ library.add(
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo,
faCircleQuestion, faCircleQuestion,
faCircleUser, faCircleUser,
faClock, faClock,
@@ -90,7 +84,6 @@ library.add(
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faTrashCan, faTrashCan,
faTriangleExclamation,
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
@@ -103,5 +96,4 @@ createApp(App)
.use(createPinia()) .use(createPinia())
.use(VueAxios, axios) .use(VueAxios, axios)
.use(router) .use(router)
.use(Notifications)
.mount("#app"); .mount("#app");

View File

@@ -123,14 +123,6 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "new-identifier" */ "../views/NewIdentifierView.vue" /* webpackChunkName: "new-identifier" */ "../views/NewIdentifierView.vue"
), ),
}, },
{
path: "/identity-switcher",
name: "identity-switcher",
component: () =>
import(
/* webpackChunkName: "identity-switcher" */ "../views/IdentitySwitcherView.vue"
),
},
{ {
path: "/project", path: "/project",
name: "project", name: "project",
@@ -166,14 +158,6 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "statistics" */ "../views/StatisticsView.vue" /* webpackChunkName: "statistics" */ "../views/StatisticsView.vue"
), ),
}, },
{
path: "/contact-gives",
name: "contact-gives",
component: () =>
import(
/* webpackChunkName: "statistics" */ "../views/ContactGiftingView.vue"
),
},
]; ];
/** @type {*} */ /** @type {*} */
@@ -182,14 +166,4 @@ const router = createRouter({
routes, routes,
}); });
const errorHandler = (error, to, from) => {
// Handle the error here
console.error(error, to, from);
console.log("XXXXX");
// You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page
};
router.onError(errorHandler); // Assign the error handler to the router instance
export default router; export default router;

View File

@@ -3,23 +3,10 @@
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Identity Your Identity
</h1> </h1>
<div class="flex justify-between">
<span />
<span class="whitespace-nowrap">
<router-link
:to="{ name: 'contact-qr' }"
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw"></fa>
</router-link>
</span>
<span />
</div>
<div class="flex justify-between py-2"> <div class="flex justify-between py-2">
<span /> <span />
<span> <span>
@@ -66,6 +53,14 @@
<fa icon="copy" class="text-slate-400 fa-fw"></fa> <fa icon="copy" class="text-slate-400 fa-fw"></fa>
</button> </button>
<span v-show="showDidCopy">Copied!</span> <span v-show="showDidCopy">Copied!</span>
<span class="whitespace-nowrap ml-4">
<router-link
:to="{ name: 'contact-qr' }"
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md ml-1"
>
<fa icon="qrcode" class="fa-fw"></fa>
</router-link>
</span>
</div> </div>
<div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div> <div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div>
@@ -113,16 +108,10 @@
<router-link <router-link
:to="{ name: 'new-edit-account' }" :to="{ name: 'new-edit-account' }"
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
> >
Edit Identity Edit Identity
</router-link> </router-link>
<router-link
:to="{ name: 'identity-switcher' }"
class="block text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-8"
>
Switch Identity / No Identity
</router-link>
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3> <h3 class="text-sm uppercase font-semibold mb-3">Data</h3>
@@ -143,131 +132,138 @@
<!-- QR code popup --> <!-- QR code popup -->
<dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md"> <dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md">
<div class="text-slate-500 text-center"> <form method="dialog">
<b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code> <div class="text-slate-500 text-center">
</div> <b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code>
<img src="/img/sample-qr-code.png" class="w-full mb-3" /> </div>
<img src="/img/sample-qr-code.png" class="w-full mb-3" />
<button <button
value="cancel" value="cancel"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
> >
Copy to Clipboard Copy to Clipboard
</button> </button>
<button <button
value="cancel" value="cancel"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
> >
Close Close
</button> </button>
</form>
</dialog> </dialog>
<h3 <h3 class="text-sm uppercase font-semibold mb-3">Advanced</h3>
class="text-sm uppercase font-semibold mb-3"
@click="showAdvanced = !showAdvanced" <label
for="toggleShowAmounts"
class="flex items-center cursor-pointer mb-6"
@click="handleChange"
> >
Advanced <!-- toggle -->
</h3> <div class="relative">
<div v-if="showAdvanced"> <!-- input -->
<label
for="toggleShowAmounts"
class="flex items-center cursor-pointer mb-6"
@click="handleChange"
>
<!-- toggle -->
<div class="relative">
<!-- input -->
<input
type="checkbox"
v-model="showContactGives"
name="showContactGives"
class="sr-only"
/>
<!-- line -->
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
<!-- dot -->
<div
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
></div>
</div>
<!-- label -->
<div class="ml-2">Show amounts given with contacts</div>
</label>
<div class="flex py-2">
<button
class="text-center text-md text-blue-500"
@click="checkLimits()"
>
Check Limits
</button>
<!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="ml-2">
Checking... <fa icon="spinner" class="fa-spin"></fa>
</div>
<div class="ml-2">
{{ limitsMessage }}
</div>
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
<span class="font-bold">Rate Limits</span>
<p>
You have done {{ limits.doneClaimsThisWeek }} claims out of
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter
resets at {{ readableTime(limits.nextWeekBeginDateTime) }}
</p>
<p>
You have done {{ limits.doneRegistrationsThisMonth }} registrations
out of {{ limits.maxRegistrationsPerMonth }} for this month. Your
registrations counter resets at
{{ readableTime(limits.nextMonthBeginDateTime) }}
</p>
</div>
</div>
<div class="flex py-2">
Claim Server
<input <input
type="text" type="checkbox"
class="block w-full rounded border border-slate-400 px-3 py-2" v-model="showContactGives"
v-model="apiServerInput" name="showContactGives"
class="sr-only"
/> />
<button <!-- line -->
v-if="apiServerInput != apiServer" <div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
class="px-4 rounded bg-red-500 border border-slate-400" <!-- dot -->
@click="onClickSaveApiServer()" <div
> class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
<fa icon="floppy-disk" class="fa-fw" color="white"></fa> ></div>
</button>
<button
class="px-4 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.PROD_ENDORSER_API_SERVER)"
>
Use Prod
</button>
<button
class="px-4 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.TEST_ENDORSER_API_SERVER)"
>
Use Test
</button>
<button
class="px-4 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.LOCAL_ENDORSER_API_SERVER)"
>
Use Local
</button>
</div> </div>
<!-- label -->
<div class="ml-2">Show amounts given with contacts</div>
</label>
<div> <div class="flex py-2">
<button class="text-blue-500"> <button class="text-center text-md text-blue-500" @click="checkLimits()">
<router-link Check Limits
:to="{ name: 'statistics' }" </button>
class="block text-center py-3" <!-- show spinner if loading limits -->
> <div v-if="loadingLimits" class="ml-2">
See Achievements & Statistics Checking... <fa icon="spinner" class="fa-spin"></fa>
</router-link>
</button>
</div> </div>
<div class="ml-2">
{{ limitsMessage }}
</div>
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
<span class="font-bold">Rate Limits</span>
<p>
You have done {{ limits.doneClaimsThisWeek }} claims out of
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter
resets at {{ readableTime(limits.nextWeekBeginDateTime) }}
</p>
<p>
You have done {{ limits.doneRegistrationsThisMonth }} registrations
out of {{ limits.maxRegistrationsPerMonth }} for this month. Your
registrations counter resets at
{{ readableTime(limits.nextMonthBeginDateTime) }}
</p>
</div>
</div>
<div class="flex py-2">
Claim Server
<input
type="text"
class="block w-full rounded border border-slate-400 px-3 py-2"
v-model="apiServerInput"
/>
<button
v-if="apiServerInput != apiServer"
class="px-4 rounded bg-red-500 border border-slate-400"
@click="onClickSaveApiServer()"
>
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
</button>
<button
class="px-4 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.PROD_ENDORSER_API_SERVER)"
>
Use Prod
</button>
<button
class="px-4 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.TEST_ENDORSER_API_SERVER)"
>
Use Test
</button>
<button
class="px-4 rounded bg-slate-200 border border-slate-400"
@click="setApiServerInput(Constants.LOCAL_ENDORSER_API_SERVER)"
>
Use Local
</button>
</div>
<div v-if="numAccounts > 0" class="flex py-2">
Switch Identifier
<span>
<button class="text-blue-500 px-2" @click="switchAccount(0)">
None
</button>
</span>
<span v-for="accountNum in numAccounts" :key="accountNum">
<button class="text-blue-500 px-2" @click="switchAccount(accountNum)">
#{{ accountNum }}
</button>
</span>
</div>
<div>
<button class="text-blue-500">
<router-link
:to="{ name: 'statistics' }"
class="block text-center py-3"
>
See Achievements & Statistics
</router-link>
</button>
</div> </div>
<AlertMessage <AlertMessage
:alertTitle="alertTitle" :alertTitle="alertTitle"
@@ -283,16 +279,25 @@ import { useClipboard } from "@vueuse/core";
import { AppString } from "@/constants/app"; import { AppString } from "@/constants/app";
import { db, accountsDB } from "@/db"; import { db, accountsDB } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { AxiosError } from "axios/index"; import { AxiosError } from "axios/index";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import { IIdentifier } from "@veramo/core";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer; const Buffer = require("buffer/").Buffer;
interface RateLimits {
doneClaimsThisWeek: string;
doneRegistrationsThisMonth: string;
maxClaimsPerWeek: string;
maxRegistrationsPerMonth: string;
nextMonthBeginDateTime: string;
nextWeekBeginDateTime: string;
}
@Component({ components: { AlertMessage, QuickNav } }) @Component({ components: { AlertMessage, QuickNav } })
export default class AccountViewView extends Vue { export default class AccountViewView extends Vue {
Constants = AppString; Constants = AppString;
@@ -310,16 +315,13 @@ export default class AccountViewView extends Vue {
limitsMessage = ""; limitsMessage = "";
loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message loadingLimits = true; // might as well now that we do it on mount, to avoid flashing the registration message
showContactGives = false; showContactGives = false;
private accounts: AccountsSchema;
showDidCopy = false; showDidCopy = false;
showDerCopy = false; showDerCopy = false;
showB64Copy = false; showB64Copy = false;
showPubCopy = false; showPubCopy = false;
showAdvanced = false;
alertMessage = "";
alertTitle = "";
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
await accountsDB.open(); await accountsDB.open();
const account = await accountsDB.accounts const account = await accountsDB.accounts
@@ -327,6 +329,12 @@ export default class AccountViewView extends Vue {
.equals(activeDid) .equals(activeDid)
.first(); .first();
const identity = JSON.parse(account?.identity || "null"); const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
);
}
return identity; return identity;
} }
@@ -357,8 +365,9 @@ export default class AccountViewView extends Vue {
} }
async beforeCreate() { async beforeCreate() {
await accountsDB.open(); accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
} }
async created() { async created() {
@@ -380,18 +389,14 @@ export default class AccountViewView extends Vue {
const identity = await this.getIdentity(this.activeDid); const identity = await this.getIdentity(this.activeDid);
if (identity) { this.publicHex = identity.keys[0].publicKeyHex;
this.publicHex = identity.keys[0].publicKeyHex; this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString( this.derivationPath = identity.keys[0].meta.derivationPath;
"base64",
);
this.derivationPath = identity.keys[0].meta.derivationPath;
db.settings.update(MASTER_SETTINGS_KEY, { db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: identity.did, activeDid: identity.did,
}); });
this.checkLimitsFor(identity); this.checkLimits();
}
} catch (err) { } catch (err) {
if ( if (
err.message === err.message ===
@@ -400,19 +405,13 @@ export default class AccountViewView extends Vue {
this.limitsMessage = "No identity."; this.limitsMessage = "No identity.";
this.loadingLimits = false; this.loadingLimits = false;
} else { } else {
this.$notify( this.alertMessage =
{ "Clear your cache and start over (after data backup).";
group: "alert",
type: "danger",
title: "Error Creating Account",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error( console.error(
"Telling user to clear cache at page create because:", "Telling user to clear cache at page create because:",
err, err,
); );
this.alertTitle = "Error Creating Account";
} }
} }
} }
@@ -424,19 +423,13 @@ export default class AccountViewView extends Vue {
showContactGivesInline: this.showContactGives, showContactGivesInline: this.showContactGives,
}); });
} catch (err) { } catch (err) {
this.$notify( this.alertMessage =
{ "Clear your cache and start over (after data backup).";
group: "alert",
type: "danger",
title: "Error Updating Contact Setting",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error( console.error(
"Telling user to clear cache after contact setting update because:", "Telling user to clear cache after contact setting update because:",
err, err,
); );
this.alertTitle = "Error Updating Contact Setting";
} }
} }
@@ -452,42 +445,22 @@ export default class AccountViewView extends Vue {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
this.$notify( this.alertTitle = "Download Started";
{ this.alertMessage = "See your downloads directory for the backup.";
group: "alert",
type: "toast",
title: "Download Started",
text: "See your downloads directory for the backup.",
},
5000,
);
} catch (error) { } catch (error) {
this.$notify( this.alertTitle = "Export Error";
{ this.alertMessage = "See console logs for more info.";
group: "alert",
type: "danger",
title: "Export Error",
text: "See console logs for more info.",
},
-1,
);
console.error("Export Error:", error); console.error("Export Error:", error);
} }
} }
async checkLimits() { async checkLimits() {
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.checkLimitsFor(identity);
}
}
async checkLimitsFor(identity: IIdentifier) {
this.loadingLimits = true; this.loadingLimits = true;
this.limitsMessage = ""; this.limitsMessage = "";
try { try {
const url = this.apiServer + "/api/report/rateLimits"; const url = this.apiServer + "/api/report/rateLimits";
const identity = await this.getIdentity(this.activeDid);
const headers = await this.getHeaders(identity); const headers = await this.getHeaders(identity);
const resp = await this.axios.get(url, { headers }); const resp = await this.axios.get(url, { headers });
@@ -505,14 +478,10 @@ export default class AccountViewView extends Vue {
} else { } else {
const serverError = error as AxiosError; const serverError = error as AxiosError;
console.error("Bad response retrieving limits: ", serverError); console.error("Bad response retrieving limits: ", serverError);
// Anybody know how to access items inside "response.data" without this?
const data: ErrorResponse | undefined = // eslint-disable-next-line @typescript-eslint/no-explicit-any
serverError.response && serverError.response.data; const data: any = serverError.response?.data;
if (data && data.error && data.error.message) { this.limitsMessage = data?.error?.message || "Bad server response.";
this.limitsMessage = data.error.message;
} else {
this.limitsMessage = "Bad server response.";
}
} }
} }
@@ -565,5 +534,9 @@ export default class AccountViewView extends Vue {
setApiServerInput(value) { setApiServerInput(value) {
this.apiServerInput = value; this.apiServerInput = value;
} }
// This same popup code is in many files.
alertMessage = "";
alertTitle = "";
} }
</script> </script>

View File

@@ -15,33 +15,35 @@
</h1> </h1>
</div> </div>
<p class="text-center text-xl mb-4 font-light"> <form>
Would you like to add <i>Firstname</i> to your network? <p class="text-center text-xl mb-4 font-light">
</p> Would you like to add <i>Firstname</i> to your network?
</p>
<!-- Account Details --> <!-- Account Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h2 class="text-xl font-semibold mb-2">Firstname Lastname</h2> <h2 class="text-xl font-semibold mb-2">Firstname Lastname</h2>
<div class="text-slate-500 text-sm font-bold">ID</div> <div class="text-slate-500 text-sm font-bold">ID</div>
<div class="text-sm text-slate-500 mb-1"> <div class="text-sm text-slate-500 mb-1">
<span><code>did:peer:kl45kj41lk451kl3</code></span> <span><code>did:peer:kl45kj41lk451kl3</code></span>
</div>
</div> </div>
</div>
<div class="mt-8"> <div class="mt-8">
<input <input
type="submit" type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
value="Add Contact" value="Add Contact"
/> />
<button <button
type="button" type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
> >
Cancel Cancel
</button> </button>
</div> </div>
</form>
</section> </section>
</template> </template>

View File

@@ -1,14 +1,52 @@
<template> <template>
<QuickNav selected="Contacts"></QuickNav> <!-- QUICK NAV -->
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
<ul class="flex text-2xl p-2 gap-2">
<!-- Home Feed -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1"
><fa icon="house-chimney" class="fa-fw"></fa
></router-link>
</li>
<!-- Search -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'discover' }"
class="block text-center py-3 px-1"
><fa icon="magnifying-glass" class="fa-fw"></fa
></router-link>
</li>
<!-- Contacts -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'projects' }"
class="block text-center py-3 px-1"
><fa icon="folder-open" class="fa-fw"></fa
></router-link>
</li>
<!-- Contacts -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'contacts' }"
class="block text-center py-3 px-1"
><fa icon="users" class="fa-fw"></fa
></router-link>
</li>
<!-- Profile -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'account' }"
class="block text-center py-3 px-1"
><fa icon="circle-user" class="fa-fw"></fa
></router-link>
</li>
</ul>
</nav>
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Given with {{ contact?.name }} Given with {{ contact?.name }}
</h1> </h1>
<div class="flex justify-around">
<span />
<span class="justify-around">(Only 50 most recent)</span>
<span />
</div>
<!-- Results List --> <!-- Results List -->
<div> <div>
@@ -83,6 +121,7 @@ import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto"; import { accessToken, SimpleSigner } from "@/libs/crypto";
@@ -95,9 +134,8 @@ import {
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav";
@Component({ components: { AlertMessage, QuickNav } }) @Component({ components: { AlertMessage } })
export default class ContactsView extends Vue { export default class ContactsView extends Vue {
activeDid = ""; activeDid = "";
apiServer = ""; apiServer = "";
@@ -105,11 +143,13 @@ export default class ContactsView extends Vue {
giveRecords: Array<GiveServerRecord> = []; giveRecords: Array<GiveServerRecord> = [];
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
async beforeCreate() { async beforeCreate() {
await accountsDB.open(); accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
} }
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
@@ -151,17 +191,10 @@ export default class ContactsView extends Vue {
this.loadGives(this.activeDid, this.contact); this.loadGives(this.activeDid, this.contact);
} }
} catch (err) { } catch (err) {
this.$notify( this.alertTitle = "Error";
{ this.alertMessage =
group: "alert", err.userMessage ||
type: "danger", "There was an error retrieving the latest sweet, sweet action.";
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
},
-1,
);
} }
} }
@@ -175,7 +208,11 @@ export default class ContactsView extends Vue {
encodeURIComponent(identity.did) + encodeURIComponent(identity.did) +
"&recipientDid=" + "&recipientDid=" +
encodeURIComponent(contact.did); encodeURIComponent(contact.did);
const headers = await this.getHeaders(identity); const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
const resp = await this.axios.get(url, { headers }); const resp = await this.axios.get(url, { headers });
if (resp.status === 200) { if (resp.status === 200) {
result = resp.data.data; result = resp.data.data;
@@ -185,15 +222,9 @@ export default class ContactsView extends Vue {
resp.status, resp.status,
resp.data, resp.data,
); );
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage =
group: "alert", "Got an error retrieving your given time from the server.";
type: "danger",
title: "Error With Server",
text: "Got an error retrieving your given time from the server.",
},
-1,
);
} }
const url2 = const url2 =
@@ -202,7 +233,11 @@ export default class ContactsView extends Vue {
encodeURIComponent(contact.did) + encodeURIComponent(contact.did) +
"&recipientDid=" + "&recipientDid=" +
encodeURIComponent(identity.did); encodeURIComponent(identity.did);
const headers2 = await this.getHeaders(identity); const token2 = await accessToken(identity);
const headers2 = {
"Content-Type": "application/json",
Authorization: "Bearer " + token2,
};
const resp2 = await this.axios.get(url2, { headers: headers2 }); const resp2 = await this.axios.get(url2, { headers: headers2 });
if (resp2.status === 200) { if (resp2.status === 200) {
result = R.concat(result, resp2.data.data); result = R.concat(result, resp2.data.data);
@@ -212,15 +247,9 @@ export default class ContactsView extends Vue {
resp2.status, resp2.status,
resp2.data, resp2.data,
); );
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage =
group: "alert", "Got an error retrieving your given time from the server.";
type: "danger",
title: "Error With Server",
text: "Got an error retrieving your given time from the server.",
},
-1,
);
} }
const sortedResult: Array<GiveServerRecord> = R.sort( const sortedResult: Array<GiveServerRecord> = R.sort(
@@ -230,15 +259,8 @@ export default class ContactsView extends Vue {
); );
this.giveRecords = sortedResult; this.giveRecords = sortedResult;
} catch (error) { } catch (error) {
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = error as string;
group: "alert",
type: "danger",
title: "Error With Server",
text: error as string,
},
-1,
);
} }
} }
@@ -291,6 +313,7 @@ export default class ContactsView extends Vue {
try { try {
const resp = await this.axios.post(url, payload, { headers }); const resp = await this.axios.post(url, payload, { headers });
//console.log("Got resp data:", resp.data);
if (resp.data?.success) { if (resp.data?.success) {
record.amountConfirmed = origClaim.object?.amountOfThisGood || 1; record.amountConfirmed = origClaim.object?.amountOfThisGood || 1;
} }
@@ -307,29 +330,15 @@ export default class ContactsView extends Vue {
userMessage = error as string; userMessage = error as string;
} }
// Now set that error for the user to see. // Now set that error for the user to see.
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = userMessage;
group: "alert",
type: "danger",
title: "Error With Server",
text: userMessage,
},
-1,
);
} }
} }
} }
cannotConfirmMessage() { cannotConfirmMessage() {
this.$notify( this.alertTitle = "Not Allowed";
{ this.alertMessage = "Only the recipient can confirm final receipt.";
group: "alert",
type: "danger",
title: "Not Allowed",
text: "Only the recipient can confirm final receipt.",
},
-1,
);
} }
} }
</script> </script>

View File

@@ -1,311 +0,0 @@
<template>
<QuickNav selected="Home"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Back -->
<router-link
:to="{ name: 'home' }"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><fa icon="chevron-left" class="fa-fw"></fa
></router-link>
Give to Contacts
</h1>
</div>
<!-- Quick Search -->
<!-- Initial Loading Animation -->
<!-- Results List -->
<ul class="border-t border-slate-300">
<li class="border-b border-slate-300 py-3">
<h2 class="text-base flex gap-4 items-center">
<span class="grow italic text-slate-500"
><EntityIcon
entityId="Anonymous"
:iconSize="32"
class="opacity-50 inline-block align-middle border border-dashed border-slate-400 bg-slate-200 rounded-md mr-1"
></EntityIcon>
Anonymous
</span>
<span class="text-right">
<button
type="button"
@click="openDialog()"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
</span>
</h2>
</li>
<li
v-for="contact in allContacts"
:key="contact.did"
class="border-b border-slate-300 py-3"
>
<h2 class="text-base flex gap-4 items-center">
<span class="grow font-semibold"
><EntityIcon
:entityId="contact.did"
:iconSize="32"
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
></EntityIcon>
{{ contact.name || "(no name)" }}
</span>
<span class="text-right">
<button
type="button"
@click="openDialog(contact)"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
</span>
</h2>
</li>
</ul>
<GiftedDialog
ref="customDialog"
@dialog-result="handleDialogResult"
message="Received from"
>
</GiftedDialog>
<AlertMessage
:alertTitle="alertTitle"
:alertMessage="alertMessage"
></AlertMessage>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue";
import { db, accountsDB } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { createAndSubmitGive } from "@/libs/endorserServer";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({
components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon },
})
export default class HomeView extends Vue {
activeDid = "";
allAccounts: Array<Account> = [];
allContacts: Array<Contact> = [];
apiServer = "";
isHiddenSpinner = true;
alertTitle = "";
alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0;
async beforeCreate() {
accountsDB.open();
this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
}
public async getIdentity(activeDid) {
await accountsDB.open();
const account = await accountsDB.accounts
.where("did")
.equals(activeDid)
.first();
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
);
}
return identity;
}
public async getHeaders(identity) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
async created() {
try {
await accountsDB.open();
this.allAccounts = await accountsDB.accounts.toArray();
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedId = settings?.lastViewedClaimId;
this.updateAllFeed();
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
},
-1,
);
}
}
public async buildHeaders() {
const headers = { "Content-Type": "application/json" };
if (this.activeDid) {
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
const account = allAccounts.find((acc) => acc.did === this.activeDid);
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.",
);
}
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
// it's OK without auth... we just won't get any identifiers
}
return headers;
}
openDialog(giver) {
this.$refs.customDialog.open(giver);
}
handleDialogResult(result) {
if (result.action === "confirm") {
return new Promise((resolve) => {
this.recordGive(result.contact?.did, result.description, result.hours);
resolve();
});
} else {
// action was "cancel" so do nothing
}
}
/**
*
* @param giverDid may be null
* @param description may be an empty string
* @param hours may be 0
*/
public async recordGive(giverDid, description, hours) {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identity before you can record a give.",
},
-1,
);
return;
}
if (!description && !hours) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must enter a description or some number of hours.",
},
-1,
);
return;
}
try {
const identity = await this.getIdentity(this.activeDid);
const result = await createAndSubmitGive(
this.axios,
this.apiServer,
identity,
giverDid,
this.activeDid,
description,
hours,
);
if (isGiveCreationError(result)) {
const errorMessage = getGiveCreationErrorMessage(result);
console.log("Error with give result:", result);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: errorMessage || "There was an error recording the give.",
},
-1,
);
} else {
this.$notify(
{
group: "alert",
type: "success",
title: "Success",
text: "That gift was recorded.",
},
-1,
);
}
} catch (error) {
console.log("Error with give caught:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text:
getGiveErrorMessage(error) ||
"There was an error recording the give.",
},
-1,
);
}
}
private setAlert(title, message) {
this.alertTitle = title;
this.alertMessage = message;
}
// Helper functions for readability
isGiveCreationError(result) {
return result.status !== 201 || result.data?.error;
}
getGiveCreationErrorMessage(result) {
return result.data?.error?.message;
}
getGiveErrorMessage(error) {
return error.userMessage || error.response?.data?.error?.message;
}
}
</script>

View File

@@ -37,8 +37,6 @@ import QuickNav from "@/components/QuickNav";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer; const Buffer = require("buffer/").Buffer;
alertTitle = "";
alertMessage = "";
@Component({ @Component({
components: { components: {
@@ -85,15 +83,7 @@ export default class ContactQRScanShow extends Vue {
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === this.activeDid, accounts); const account = R.find((acc) => acc.did === this.activeDid, accounts);
if (!account) { if (!account) {
this.$notify( this.alertMessage = "You have no identity yet.";
{
group: "alert",
type: "warning",
title: "",
text: "You have no identity yet.",
},
-1,
);
} else { } else {
const identity = await this.getIdentity(this.activeDid); const identity = await this.getIdentity(this.activeDid);
const publicKeyHex = identity.keys[0].publicKeyHex; const publicKeyHex = identity.keys[0].publicKeyHex;
@@ -120,5 +110,9 @@ export default class ContactQRScanShow extends Vue {
this.qrValue = viewPrefix + vcJwt; this.qrValue = viewPrefix + vcJwt;
} }
} }
// This same popup code is in many files.
alertTitle = "";
alertMessage = "";
} }
</script> </script>

View File

@@ -14,69 +14,75 @@
</h1> </h1>
</div> </div>
<h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3> <form>
<div class="bg-black rounded overflow-hidden relative mb-4"> <h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3>
<img src="https://picsum.photos/400/400?random=1" class="w-full" /> <div class="bg-black rounded overflow-hidden relative mb-4">
<img src="https://picsum.photos/400/400?random=1" class="w-full" />
<!-- Darken overlay --> <!-- Darken overlay -->
<!-- Top --> <!-- Top -->
<div class="absolute top-0 left-0 right-0 bg-black/50 h-1/4"></div> <div class="absolute top-0 left-0 right-0 bg-black/50 h-1/4"></div>
<!-- Reft --> <!-- Reft -->
<div class="absolute top-1/4 bottom-1/4 left-0 bg-black/50 w-1/4"></div> <div class="absolute top-1/4 bottom-1/4 left-0 bg-black/50 w-1/4"></div>
<!-- Right --> <!-- Right -->
<div class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"></div> <div
<!-- Bottom --> class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"
<div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></div> ></div>
<!-- Bottom -->
<div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></div>
<!-- Reticle overlay --> <!-- Reticle overlay -->
<!-- Top-left --> <!-- Top-left -->
<div <div
class="absolute top-1/4 left-1/4 h-6 w-6 border-white border-t-4 border-l-4 drop-shadow" class="absolute top-1/4 left-1/4 h-6 w-6 border-white border-t-4 border-l-4 drop-shadow"
></div> ></div>
<!-- Top-right --> <!-- Top-right -->
<div <div
class="absolute top-1/4 right-1/4 h-6 w-6 border-white border-t-4 border-r-4 drop-shadow" class="absolute top-1/4 right-1/4 h-6 w-6 border-white border-t-4 border-r-4 drop-shadow"
></div> ></div>
<!-- Bottom-left --> <!-- Bottom-left -->
<div <div
class="absolute bottom-1/4 left-1/4 h-6 w-6 border-white border-b-4 border-l-4 drop-shadow" class="absolute bottom-1/4 left-1/4 h-6 w-6 border-white border-b-4 border-l-4 drop-shadow"
></div> ></div>
<!-- Bottom-right --> <!-- Bottom-right -->
<div <div
class="absolute bottom-1/4 right-1/4 h-6 w-6 border-white border-b-4 border-r-4 drop-shadow" class="absolute bottom-1/4 right-1/4 h-6 w-6 border-white border-b-4 border-r-4 drop-shadow"
></div> ></div>
</div> </div>
<h3 class="text-sm uppercase font-semibold mb-2">or Enter Contact Data</h3> <h3 class="text-sm uppercase font-semibold mb-2">
<input or Enter Contact Data
type="text" </h3>
placeholder="Name (optional)"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
/>
<input
type="text"
placeholder="ID"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
/>
<input
type="text"
placeholder="Public Key (optional)"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
/>
<div class="mt-8">
<input <input
type="submit" type="text"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" placeholder="Name (optional)"
value="Look Up Contact" class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
/> />
<button <input
type="button" type="text"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" placeholder="ID"
> class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
Cancel />
</button> <input
</div> type="text"
placeholder="Public Key (optional)"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
/>
<div class="mt-8">
<input
type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
value="Look Up Contact"
/>
<button
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</form>
</section> </section>
</template> </template>

View File

@@ -66,27 +66,18 @@
: "Unconfirmed" : "Unconfirmed"
}} }}
</button> </button>
<br />
(Only hours shown)
<br />
(Only recent shown)
</div> </div>
</div> </div>
<!-- Results List --> <!-- Results List -->
<ul v-if="contacts.length > 0" class="border-t border-slate-300"> <ul v-if="contacts.length > 0">
<li <li
class="border-b border-slate-300 pt-2.5 pb-4" class="border-b border-slate-300"
v-for="contact in contacts" v-for="contact in contacts"
:key="contact.did" :key="contact.did"
> >
<div class="grow overflow-hidden"> <div class="grow overflow-hidden">
<h2 class="text-base font-semibold"> <h2 class="text-base font-semibold">
<EntityIcon
:entityId="contact.did"
:iconSize="24"
class="inline-block align-text-bottom border border-slate-300 rounded"
></EntityIcon>
{{ contact.name || "(no name)" }} {{ contact.name || "(no name)" }}
</h2> </h2>
<div class="text-sm truncate">{{ contact.did }}</div> <div class="text-sm truncate">{{ contact.did }}</div>
@@ -94,85 +85,70 @@
Public Key (base 64): {{ contact.publicKeyBase64 }} Public Key (base 64): {{ contact.publicKeyBase64 }}
</div> </div>
<div id="ContactActions" class="flex gap-1.5 mt-2"> <button
<button v-if="contact.seesMe"
v-if="contact.seesMe" class="tooltip"
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" @click="setVisibility(contact, false)"
@click="setVisibility(contact, false)" >
title="They can see you" <fa icon="eye" class="text-slate-900 fa-fw ml-1" />
> <span class="tooltiptext">They can see you</span>
<fa icon="eye" class="fa-fw" /> </button>
</button> <button v-else class="tooltip" @click="setVisibility(contact, true)">
<button <span class="tooltiptext">They cannot see you</span>
v-else <fa icon="eye-slash" class="text-slate-900 fa-fw ml-1" />
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" </button>
@click="setVisibility(contact, true)"
title="They cannot see you"
>
<fa icon="eye-slash" class="fa-fw" />
</button>
<button <button class="tooltip" @click="checkVisibility(contact)">
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" <span class="tooltiptext">Check Visibility</span>
@click="checkVisibility(contact)" <fa icon="rotate" class="text-slate-900 fa-fw ml-1" />
title="Check Visibility" </button>
>
<fa icon="rotate" class="fa-fw" />
</button>
<button <button v-if="contact.registered" class="tooltip">
v-if="contact.registered" <span class="tooltiptext">Registered</span>
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" <fa icon="person-circle-check" class="text-slate-900 fa-fw ml-1" />
title="Registered" </button>
> <button v-else @click="register(contact)" class="tooltip">
<fa icon="person-circle-check" class="fa-fw" /> <span class="tooltiptext">Registration Unknown</span>
</button> <fa
<button icon="person-circle-question"
v-else class="text-slate-900 fa-fw ml-1"
@click="register(contact)" />
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" </button>
title="Registration unknown"
>
<fa icon="person-circle-question" class="fa-fw" />
</button>
<button <button @click="deleteContact(contact)" class="px-9 tooltip">
@click="deleteContact(contact)" <span class="tooltiptext">Delete!</span>
class="text-sm uppercase bg-red-600 text-white px-2 py-1.5 rounded-md" <fa icon="trash-can" class="text-red-600 fa-fw ml-1" />
title="Delete" </button>
>
<fa icon="trash-can" class="fa-fw" />
</button>
<div <div v-if="showGiveNumbers" class="float-right">
v-if="showGiveNumbers && contact.did != activeDid" <div class="float-right">
class="ml-auto flex gap-1.5" <div class="tooltip">
> to:
<button
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)"
title="givenByMeDescriptions[contact.did]"
>
To:
{{ {{
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
this.showGiveTotals this.showGiveTotals
? ((givenByMeConfirmed[contact.did] || 0) ? ((givenByMeConfirmed[contact.did] || 0)
+ (givenByMeUnconfirmed[contact.did] || 0)) + (givenByMeUnconfirmed[contact.did] || 0))
: this.showGiveConfirmed : this.showGiveConfirmed
? (givenByMeConfirmed[contact.did] || 0) ? (givenByMeConfirmed[contact.did] || 0)
: (givenByMeUnconfirmed[contact.did] || 0) : (givenByMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<fa icon="plus" /> <span
</button> v-if="givenByMeDescriptions[contact.did]"
class="tooltiptext-left"
<button >
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400" {{ givenByMeDescriptions[contact.did] }}
@click="onClickAddGive(contact.did, activeDid)" </span>
title="givenToMeDescriptions[contact.did]" <button
> class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
From: @click="onClickAddGive(activeDid, contact.did)"
>
+
</button>
</div>
<div class="tooltip px-2">
from:
{{ {{
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
this.showGiveTotals this.showGiveTotals
@@ -183,18 +159,28 @@
: (givenToMeUnconfirmed[contact.did] || 0) : (givenToMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<fa icon="plus" /> <span
</button> v-if="givenToMeDescriptions[contact.did]"
class="tooltiptext-left"
>
{{ givenToMeDescriptions[contact.did] }}
</span>
<button
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@click="onClickAddGive(contact.did, activeDid)"
>
+
</button>
</div>
<router-link <router-link
:to="{ :to="{
name: 'contact-amounts', name: 'contact-amounts',
query: { contactDid: contact.did }, query: { contactDid: contact.did },
}" }"
class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md" class="tooltip"
title="See all given activity"
> >
<fa icon="file-lines" class="fa-fw" /> <fa icon="file-lines" class="text-slate-600 fa-fw ml-1" />
<span class="tooltiptext-left">See All Given Activity</span>
</router-link> </router-link>
</div> </div>
</div> </div>
@@ -226,13 +212,12 @@ import {
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer; const Buffer = require("buffer/").Buffer;
@Component({ @Component({
components: { AlertMessage, QuickNav, EntityIcon }, components: { AlertMessage, QuickNav },
}) })
export default class ContactsView extends Vue { export default class ContactsView extends Vue {
activeDid = ""; activeDid = "";
@@ -307,27 +292,20 @@ export default class ContactsView extends Vue {
} }
async loadGives() { async loadGives() {
const handleResponse = ( const handleResponse = (resp, descriptions, confirmed, unconfirmed) => {
resp,
descriptions,
confirmed,
unconfirmed,
useRecipient,
) => {
if (resp.status === 200) { if (resp.status === 200) {
const allData = resp.data.data; const allData = resp.data.data;
for (const give of allData) { for (const give of allData) {
const otherDid = useRecipient ? give.recipientDid : give.agentDid;
if (give.unit === "HUR") { if (give.unit === "HUR") {
if (give.amountConfirmed) { if (give.amountConfirmed) {
const prevAmount = confirmed[otherDid] || 0; const prevAmount = confirmed[give.agentDid] || 0;
confirmed[otherDid] = prevAmount + give.amount; confirmed[give.agentDid] = prevAmount + give.amount;
} else { } else {
const prevAmount = unconfirmed[otherDid] || 0; const prevAmount = unconfirmed[give.agentDid] || 0;
unconfirmed[otherDid] = prevAmount + give.amount; unconfirmed[give.agentDid] = prevAmount + give.amount;
} }
if (!descriptions[otherDid] && give.description) { if (!descriptions[give.agentDid] && give.description) {
descriptions[otherDid] = give.description; descriptions[give.agentDid] = give.description;
} }
} }
} }
@@ -337,32 +315,27 @@ export default class ContactsView extends Vue {
resp.status, resp.status,
resp.data, resp.data,
); );
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage =
group: "alert", "Got an error retrieving your " +
type: "danger", resp.config.url.includes("recipientDid")
title: "Error With Server", ? "received"
text: : "given" + " time from the server.";
"Got an error retrieving your " +
resp.config.url.includes("recipientDid")
? "received"
: "given" + " time from the server.",
},
-1,
);
} }
}; };
try { try {
const { headers } = await this.getHeadersAndIdentity(this.activeDid); const { headers, identity } = await this.getHeadersAndIdentity(
this.activeDid,
);
const givenByUrl = const givenByUrl =
this.apiServer + this.apiServer +
"/api/v2/report/gives?agentDid=" + "/api/v2/report/gives?agentDid=" +
encodeURIComponent(this.activeDid); encodeURIComponent(identity.did);
const givenToUrl = const givenToUrl =
this.apiServer + this.apiServer +
"/api/v2/report/gives?recipientDid=" + "/api/v2/report/gives?recipientDid=" +
encodeURIComponent(this.activeDid); encodeURIComponent(identity.did);
const [givenByMeResp, givenToMeResp] = await Promise.all([ const [givenByMeResp, givenToMeResp] = await Promise.all([
this.axios.get(givenByUrl, { headers }), this.axios.get(givenByUrl, { headers }),
@@ -377,7 +350,6 @@ export default class ContactsView extends Vue {
givenByMeDescriptions, givenByMeDescriptions,
givenByMeConfirmed, givenByMeConfirmed,
givenByMeUnconfirmed, givenByMeUnconfirmed,
true,
); );
this.givenByMeDescriptions = givenByMeDescriptions; this.givenByMeDescriptions = givenByMeDescriptions;
this.givenByMeConfirmed = givenByMeConfirmed; this.givenByMeConfirmed = givenByMeConfirmed;
@@ -391,21 +363,13 @@ export default class ContactsView extends Vue {
givenToMeDescriptions, givenToMeDescriptions,
givenToMeConfirmed, givenToMeConfirmed,
givenToMeUnconfirmed, givenToMeUnconfirmed,
false,
); );
this.givenToMeDescriptions = givenToMeDescriptions; this.givenToMeDescriptions = givenToMeDescriptions;
this.givenToMeConfirmed = givenToMeConfirmed; this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = givenToMeUnconfirmed; this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} catch (error) { } catch (error) {
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = error as string;
group: "alert",
type: "danger",
title: "Error With Server",
text: error as string,
},
-1,
);
} }
} }
@@ -493,37 +457,28 @@ export default class ContactsView extends Vue {
// Make the xhr request payload // Make the xhr request payload
const payload = JSON.stringify({ jwtEncoded: vcJwt }); const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = this.apiServer + "/api/v2/claim"; const url = this.apiServer + "/api/v2/claim";
const headers = await this.getHeaders(identity); const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
try { try {
const resp = await this.axios.post(url, payload, { headers }); const resp = await this.axios.post(url, payload, { headers });
//console.log("Got resp data:", resp.data);
if (resp.data?.success?.embeddedRecordError) { if (resp.data?.success?.embeddedRecordError) {
this.alertTitle = "Registration Still Unknown";
let message = "There was some problem with the registration."; let message = "There was some problem with the registration.";
if (typeof resp.data.success.embeddedRecordError == "string") { if (typeof resp.data.success.embeddedRecordError == "string") {
message += " " + resp.data.success.embeddedRecordError; message += " " + resp.data.success.embeddedRecordError;
} }
this.$notify( this.alertMessage = message;
{
group: "alert",
type: "danger",
title: "Registration Still Unknown",
text: message,
},
-1,
);
} else if (resp.data?.success?.handleId) { } else if (resp.data?.success?.handleId) {
contact.registered = true; contact.registered = true;
db.contacts.update(contact.did, { registered: true }); db.contacts.update(contact.did, { registered: true });
this.$notify( this.alertTitle = "Registration Success";
{ this.alertMessage = contact.name + " has been registered.";
group: "alert",
type: "info",
title: "Registration Success",
text: contact.name + " has been registered.",
},
-1,
);
} }
} catch (error) { } catch (error) {
let userMessage = "There was an error. See logs for more info."; let userMessage = "There was an error. See logs for more info.";
@@ -538,15 +493,8 @@ export default class ContactsView extends Vue {
userMessage = error as string; userMessage = error as string;
} }
// Now set that error for the user to see. // Now set that error for the user to see.
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = userMessage;
group: "alert",
type: "danger",
title: "Error With Server",
text: userMessage,
},
-1,
);
} }
} }
} }
@@ -567,39 +515,17 @@ export default class ContactsView extends Vue {
contact.seesMe = visibility; contact.seesMe = visibility;
db.contacts.update(contact.did, { seesMe: visibility }); db.contacts.update(contact.did, { seesMe: visibility });
} else { } else {
this.alertTitle = "Error With Server";
console.error("Bad response setting visibility: ", resp.data); console.error("Bad response setting visibility: ", resp.data);
if (resp.data.error?.message) { if (resp.data.error?.message) {
this.$notify( this.alertMessage = resp.data.error?.message;
{
group: "alert",
type: "danger",
title: "Error With Server",
text: resp.data.error?.message,
},
-1,
);
} else { } else {
this.$notify( this.alertMessage = "Bad server response of " + resp.status;
{
group: "alert",
type: "danger",
title: "Error With Server",
text: "Bad server response of " + resp.status,
},
-1,
);
} }
} }
} catch (err) { } catch (err) {
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = err as string;
group: "alert",
type: "danger",
title: "Error With Server",
text: err as string,
},
-1,
);
} }
} }
@@ -616,52 +542,23 @@ export default class ContactsView extends Vue {
contact.seesMe = visibility; contact.seesMe = visibility;
db.contacts.update(contact.did, { seesMe: visibility }); db.contacts.update(contact.did, { seesMe: visibility });
this.$notify( this.alertTitle = "Refreshed";
{ this.alertMessage =
group: "alert", this.nameForContact(contact, true) +
type: "toast", " can " +
title: "Refreshed", (visibility ? "" : "not ") +
text: "see your activity.";
this.nameForContact(contact, true) +
" can " +
(visibility ? "" : "not ") +
"see your activity.",
},
5000,
);
} else { } else {
this.alertTitle = "Error With Server";
if (resp.data.error?.message) { if (resp.data.error?.message) {
this.$notify( this.alertMessage = resp.data.error?.message;
{
group: "alert",
type: "danger",
title: "Error With Server",
text: resp.data.error?.message,
},
-1,
);
} else { } else {
this.$notify( this.alertMessage = "Bad server response of " + resp.status;
{
group: "alert",
type: "danger",
title: "Error With Server",
text: "Bad server response of " + resp.status,
},
-1,
);
} }
} }
} catch (err) { } catch (err) {
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = err as string;
group: "alert",
type: "danger",
title: "Error With Server",
text: err as string,
},
-1,
);
} }
} }
@@ -700,35 +597,15 @@ export default class ContactsView extends Vue {
} }
} }
if (!this.isNumeric(this.hourInput)) { if (!this.isNumeric(this.hourInput)) {
this.$notify( this.alertTitle = "Input Error";
{ this.alertMessage =
group: "alert", "This is not a valid number of hours: " + this.hourInput;
type: "danger",
title: "Input Error",
text: "This is not a valid number of hours: " + this.hourInput,
},
-1,
);
} else if (!parseFloat(this.hourInput)) { } else if (!parseFloat(this.hourInput)) {
this.$notify( this.alertTitle = "Input Error";
{ this.alertMessage = "Giving 0 hours does nothing.";
group: "alert",
type: "danger",
title: "Input Error",
text: "Giving 0 hours does nothing.",
},
-1,
);
} else if (!identity) { } else if (!identity) {
this.$notify( this.alertTitle = "Status Error";
{ this.alertMessage = "No identity is available.";
group: "alert",
type: "danger",
title: "Status Error",
text: "No identity is available.",
},
-1,
);
} else { } else {
// ask to confirm amount // ask to confirm amount
let toFrom; let toFrom;
@@ -807,20 +684,18 @@ export default class ContactsView extends Vue {
const payload = JSON.stringify({ jwtEncoded: vcJwt }); const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = this.apiServer + "/api/v2/claim"; const url = this.apiServer + "/api/v2/claim";
const headers = await this.getHeaders(identity); const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
try { try {
const resp = await this.axios.post(url, payload, { headers }); const resp = await this.axios.post(url, payload, { headers });
//console.log("Got resp data:", resp.data);
if (resp.data?.success?.handleId) { if (resp.data?.success?.handleId) {
this.$notify( this.alertTitle = "Done";
{ this.alertMessage = "Successfully logged time to the server.";
group: "alert",
type: "success",
title: "Done",
text: "Successfully logged time to the server.",
},
-1,
);
if (fromDid === identity.did) { if (fromDid === identity.did) {
const newList = R.clone(this.givenByMeUnconfirmed); const newList = R.clone(this.givenByMeUnconfirmed);
@@ -845,15 +720,8 @@ export default class ContactsView extends Vue {
userMessage = error as string; userMessage = error as string;
} }
// Now set that error for the user to see. // Now set that error for the user to see.
this.$notify( this.alertTitle = "Error With Server";
{ this.alertMessage = userMessage;
group: "alert",
type: "danger",
title: "Error With Server",
text: userMessage,
},
-1,
);
} }
} }
} }

View File

@@ -30,80 +30,109 @@
<li> <li>
<a <a
href="#" href="#"
@click=" @click="searchLocal()"
projects = []; class="inline-block py-3 rounded-t-lg border-b-2 active text-blue-600 border-blue-600 font-semibold"
searchLocal();
"
v-bind:class="computedLocalTabClassNames()"
> >
Nearby Nearby
<span <span
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md" class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
>{{ localCount }}</span >20+</span
> >
</a> </a>
</li> </li>
<li> <li>
<a <a
href="#" href="#"
v-bind:class="computedRemoteTabClassNames()" class="inline-block py-3 rounded-t-lg border-b-2 border-transparent hover:text-slate-600 hover:border-slate-300"
@click="
projects = [];
search();
"
> >
Remote Remote
<span <span
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md" class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
>{{ remoteCount }}</span >13</span
> >
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<!-- Loading Animation -->
<div
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
v-if="isLoading"
>
<fa icon="spinner" class="fa-spin-pulse"></fa>
</div>
<!-- Results List --> <!-- Results List -->
<InfiniteScroll @reached-bottom="loadMoreData"> <ul class="">
<ul> <li class="border-b border-slate-300">
<li <a
class="border-b border-slate-300" @click="
v-for="project in projects" onClickLoadProject(
:key="project.handleId" 'https://endorser.ch/entity/01H3HER4DTNCR6ZC4VXA3VPWVQ',
)
"
class="block py-4 flex gap-4"
> >
<a <div class="w-12">
@click="onClickLoadProject(project.handleId)" <img
class="block py-4 flex gap-4" src="https://picsum.photos/200/200?random=1"
> class="w-full rounded"
<div class="w-12"> />
<EntityIcon </div>
:entityId="project.handleId"
:iconSize="48"
class="block border border-slate-300 rounded-md"
></EntityIcon>
</div>
<div class="grow"> <div class="grow">
<h2 class="text-base font-semibold">{{ project.name }}</h2> <h2 class="text-base font-semibold">Canyon cleanup</h2>
<h2 class="text-base font-semibold">{{ project.name }}</h2> <div class="text-sm">
<div class="text-sm"> <fa icon="user" class="fa-fw text-slate-400"></fa> Rotary
<fa icon="user" class="fa-fw text-slate-400"></fa>
{{
didInfo(project.issuerDid, activeDid, allMyDids, allContacts)
}}
</div>
</div> </div>
</a> </div>
</li> </a>
</ul> </li>
</InfiniteScroll>
<li class="border-b border-slate-300">
<a
@click="
onClickLoadProject(
'https://endorser.ch/entity/01H3HER4DTNCR6ZC4VXA3VPWVQ',
)
"
class="block py-4 flex gap-4"
>
<div class="w-12">
<img
src="https://picsum.photos/200/200?random=2"
class="w-full rounded"
/>
</div>
<div class="grow">
<h2 class="text-base font-semibold">Potluck with neighbors</h2>
<div class="text-sm">
<fa icon="user" class="fa-fw text-slate-400"></fa> Andrew A.
</div>
</div>
</a>
</li>
<li class="border-b border-slate-300">
<a
@click="
onClickLoadProject(
'https://endorser.ch/entity/01H3HER4DTNCR6ZC4VXA3VPWVQ',
)
"
class="block py-4 flex gap-4"
>
<div class="w-12">
<img
src="https://picsum.photos/200/200?random=3"
class="w-full rounded"
/>
</div>
<div class="grow">
<h2 class="text-base font-semibold">Historical site</h2>
<div class="text-sm">
<fa icon="user" class="fa-fw text-slate-400 mr-1"></fa>
<em>Unknown</em>
</div>
</div>
</a>
</li>
</ul>
<AlertMessage <AlertMessage
:alertTitle="alertTitle" :alertTitle="alertTitle"
:alertMessage="alertMessage" :alertMessage="alertMessage"
@@ -115,48 +144,26 @@
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { didInfo } from "@/libs/endorserServer";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import InfiniteScroll from "@/components/InfiniteScroll";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { AlertMessage, QuickNav, InfiniteScroll, EntityIcon }, components: { AlertMessage, QuickNav },
}) })
export default class DiscoverView extends Vue { export default class DiscoverView extends Vue {
activeDid = ""; activeDid = "";
allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
apiServer = ""; apiServer = "";
searchTerms = ""; searchTerms = "";
alertMessage = ""; alertMessage = "";
alertTitle = ""; alertTitle = "";
projects: ProjectData[] = [];
isLocalActive = true;
isRemoteActive = false;
localCount = 0;
remoteCount = 0;
isLoading = false;
// make this function available to the Vue template
didInfo = didInfo;
async mounted() { async mounted() {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || ""; this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
this.allContacts = await db.contacts.toArray();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did);
this.searchLocal();
} }
public async buildHeaders() { public async buildHeaders() {
@@ -181,20 +188,15 @@ export default class DiscoverView extends Vue {
return headers; return headers;
} }
public async search(beforeId?: string) { public async search() {
let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms); const claimContents =
"claimContents=" + encodeURIComponent(this.searchTerms);
if (beforeId) { const claimType = "claimType=PlanAction";
queryParams = queryParams + `&beforeId=${beforeId}`; const queryParams = [claimContents, claimType].join("&");
}
this.isRemoteActive = true;
this.isLocalActive = false;
try { try {
this.isLoading = true;
const response = await fetch( const response = await fetch(
this.apiServer + "/api/v2/report/plans?" + queryParams, this.apiServer + "/api/v2/report/claims?" + queryParams,
{ {
method: "GET", method: "GET",
headers: await this.buildHeaders(), headers: await this.buildHeaders(),
@@ -203,51 +205,28 @@ export default class DiscoverView extends Vue {
if (response.status !== 200) { if (response.status !== 200) {
const details = await response.text(); const details = await response.text();
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `There was a problem accessing the server. Please try again later. (${details})`,
},
-1,
);
throw details; throw details;
} }
const results = await response.json(); const results = await response.json();
const plans: ProjectData[] = results.data; if (results.data) {
if (plans) { console.log("Plans found in that search:", results.data);
for (const plan of plans) {
const { name, description, handleId, rowid, issuerDid } = plan;
this.projects.push({ name, description, handleId, rowid, issuerDid });
}
this.remoteCount = this.projects.length;
} else { } else {
throw JSON.stringify(results); throw JSON.stringify(results);
} }
} catch (e) { } catch (e) {
console.log("Error with feed load:", e); console.log("Error with feed load:", e);
this.$notify( this.alertMessage =
{ e.userMessage || "There was an error retrieving projects.";
group: "alert", this.alertTitle = "Error";
type: "danger",
title: "Error",
text: e.userMessage || "There was a problem retrieving projects.",
},
-1,
);
} finally {
this.isLoading = false;
} }
} }
public async searchLocal(beforeId?: string) { public async searchLocal() {
const claimContents = const claimContents =
"claimContents=" + encodeURIComponent(this.searchTerms); "claimContents=" + encodeURIComponent(this.searchTerms);
let queryParams = [ const queryParams = [
claimContents, claimContents,
"minLocLat=40.901000", "minLocLat=40.901000",
"maxLocLat=40.904000", "maxLocLat=40.904000",
@@ -255,14 +234,7 @@ export default class DiscoverView extends Vue {
"eastLocLon=-111.909000", "eastLocLon=-111.909000",
].join("&"); ].join("&");
if (beforeId) {
queryParams = queryParams + `&beforeId=${beforeId}`;
}
try { try {
this.isLoading = true;
this.isLocalActive = true;
this.isRemoteActive = false;
const response = await fetch( const response = await fetch(
this.apiServer + "/api/v2/report/plansByLocation?" + queryParams, this.apiServer + "/api/v2/report/plansByLocation?" + queryParams,
{ {
@@ -272,65 +244,21 @@ export default class DiscoverView extends Vue {
); );
if (response.status !== 200) { if (response.status !== 200) {
const details = await response.text();
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `There was a problem accessing the server. Please try again later. (${details})`,
},
-1,
);
throw await response.text(); throw await response.text();
} }
const results = await response.json(); const results = await response.json();
if (results.data) { if (results.data) {
if (beforeId) { console.log("Plans found in that location:", results.data);
const plans: ProjectData[] = results.data;
for (const plan of plans) {
const { name, description, handleId = plan.handleId, rowid } = plan;
if (beforeId !== plan["rowid"]) {
this.projects.push({ name, description, handleId, rowid });
}
}
} else {
this.projects = results.data;
}
this.localCount = this.projects.length;
} else { } else {
throw JSON.stringify(results); throw JSON.stringify(results);
} }
} catch (e) { } catch (e) {
console.log("Error with feed load:", e); console.log("Error with feed load:", e);
this.$notify( this.alertMessage =
{ e.userMessage || "There was an error retrieving projects.";
group: "alert", this.alertTitle = "Error";
type: "danger",
title: "Error",
text: e.userMessage || "There was a problem retrieving projects.",
},
-1,
);
} finally {
this.isLoading = false;
}
}
/**
* Data loader used by infinite scroller
* @param payload is the flag from the InfiniteScroll indicating if it should load
**/
async loadMoreData(payload: boolean) {
if (this.projects.length > 0 && payload) {
const latestProject = this.projects[this.projects.length - 1];
if (this.isLocalActive) {
this.searchLocal(latestProject["rowid"]);
} else if (this.isRemoteActive) {
this.search(latestProject["rowid"]);
}
} }
} }
@@ -345,37 +273,5 @@ export default class DiscoverView extends Vue {
}; };
this.$router.push(route); this.$router.push(route);
} }
public computedLocalTabClassNames() {
return {
"inline-block": true,
"py-3": true,
"rounded-t-lg": true,
"border-b-2": true,
active: this.isLocalActive,
"text-blue-600": this.isLocalActive,
"border-blue-600": this.isLocalActive,
"font-semibold": this.isLocalActive,
"border-transparent": !this.isLocalActive,
"hover:text-slate-600": !this.isLocalActive,
"hover:border-slate-300": !this.isLocalActive,
};
}
public computedRemoteTabClassNames() {
return {
"inline-block": true,
"py-3": true,
"rounded-t-lg": true,
"border-b-2": true,
active: this.isRemoteActive,
"text-blue-600": this.isRemoteActive,
"border-blue-600": this.isRemoteActive,
"font-semibold": this.isRemoteActive,
"border-transparent": !this.isRemoteActive,
"hover:text-slate-600": !this.isRemoteActive,
"hover:border-slate-300": !this.isRemoteActive,
};
}
} }
</script> </script>

View File

@@ -128,14 +128,6 @@
key. key.
</p> </p>
<h2 class="text-xl font-semibold">How do I create another identity?</h2>
<p>
Go
<router-link to="start" class="text-blue-500">
create another identity here.
</router-link>
</p>
<h2 class="text-xl font-semibold"> <h2 class="text-xl font-semibold">
I know there is a record from someone, so why can't I see that info? I know there is a record from someone, so why can't I see that info?
</h2> </h2>
@@ -154,6 +146,14 @@
page. page.
</p> </p>
<h2 class="text-xl font-semibold">How do I create another identity?</h2>
<p>
Go
<router-link to="start" class="text-blue-500">
create another identity here.
</router-link>
</p>
<h2 class="text-xl font-semibold">What is your privacy policy?</h2> <h2 class="text-xl font-semibold">What is your privacy policy?</h2>
<p> <p>
See See

View File

@@ -7,130 +7,22 @@
</h1> </h1>
<div class="mb-8"> <div class="mb-8">
<h2 class="text-xl font-bold mb-4">Notiwind Alert Test Suite</h2> <h1 class="text-2xl">Quick Action</h1>
<p>Choose a contact to whom to show appreciation:</p>
<button <!-- similar contact selection code is in multiple places -->
@click=" <div class="px-4">
this.$notify( <button
{
group: 'alert',
type: 'toast',
text: 'I\'m a toast. Don\'t mind me.',
},
5000,
)
"
class="font-bold uppercase bg-slate-400 text-white px-3 py-2 rounded-md mr-2"
>
Toast (self-dismiss)
</button>
<button
@click="
this.$notify(
{
group: 'alert',
type: 'info',
title: 'Information Alert',
text: 'Just wanted you to know.',
},
-1,
)
"
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
>
Info
</button>
<button
@click="
this.$notify(
{
group: 'alert',
type: 'success',
title: 'Success Alert',
text: 'Congratulations!',
},
-1,
)
"
class="font-bold uppercase bg-emerald-600 text-white px-3 py-2 rounded-md mr-2"
>
Success
</button>
<button
@click="
this.$notify(
{
group: 'alert',
type: 'warning',
title: 'Warning Alert',
text: 'You might wanna look at this.',
},
-1,
)
"
class="font-bold uppercase bg-amber-600 text-white px-3 py-2 rounded-md mr-2"
>
Warning
</button>
<button
@click="
this.$notify(
{
group: 'alert',
type: 'danger',
title: 'Danger Alert',
text: 'Something terrible has happened!',
},
-1,
)
"
class="font-bold uppercase bg-rose-600 text-white px-3 py-2 rounded-md mr-2"
>
Danger
</button>
</div>
<div class="mb-8">
<h2 class="text-xl font-bold">Quick Action</h2>
<p class="mb-4">Show appreciation to a contact:</p>
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<li
v-for="contact in allContacts" v-for="contact in allContacts"
:key="contact.did" :key="contact.did"
@click="openDialog(contact)" @click="openDialog(contact)"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
> >
<EntityIcon {{ contact.name || "(no name)" }}
:entityId="contact.did" </button>
:iconSize="64" <span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span>
class="mx-auto border border-slate-300 rounded-md mb-1" <button @click="openDialog()" class="text-blue-500">
></EntityIcon> someone not specified
<h3 </button>
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
>
{{ contact.name || "(no name)" }}
</h3>
</li>
</ul>
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
<router-link
v-if="allContacts.length > 7"
:to="{ name: 'contact-gives' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
>
Show More Contacts&hellip;
</router-link>
<!-- If there are no contacts, show this instead: -->
<div
class="rounded border border-dashed border-slate-300 bg-slate-100 px-4 py-3 text-center italic text-slate-500"
>
(No contacts to show.)
</div> </div>
</div> </div>
@@ -141,27 +33,29 @@
> >
</GiftedDialog> </GiftedDialog>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div>
<h2 class="text-xl font-bold mb-4">Latest Activity</h2> <h1 class="text-2xl">Latest Activity</h1>
<div :class="{ hidden: isHiddenSpinner }"> <span :class="{ hidden: isHiddenSpinner }">
<p class="text-slate-500 text-center italic mt-4 mb-4"> <fa icon="spinner" class="fa-spin-pulse"></fa>
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip; Loading&hellip;
</p> </span>
</div> <ul>
<ul class="border-t border-slate-300">
<li <li
class="border-b border-slate-300 py-2" class="border-b border-slate-300 py-2"
v-for="record in feedData" v-for="record in feedData"
:key="record.jwtId" :key="record.jwtId"
> >
<div <div
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm" class="border-b border-dashed border-slate-400 text-orange-400 py-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedId" v-if="record.jwtId == feedLastViewedId"
> >
You've seen all claims below: You've seen all claims below:
</div> </div>
<div class="flex"> <div class="flex">
<fa icon="gift" class="pt-1 pr-2 text-slate-500"></fa> <fa
icon="gift"
class="fa-fw flex-none pt-1 pr-2 text-slate-500"
></fa>
<!-- icon values: "coins" = money; "clock" = time; "gift" = others --> <!-- icon values: "coins" = money; "clock" = time; "gift" = others -->
<span class="">{{ this.giveDescription(record) }}</span> <span class="">{{ this.giveDescription(record) }}</span>
</div> </div>
@@ -179,21 +73,22 @@
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import { db, accountsDB } from "@/db"; import { db, accountsDB } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer"; import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon }, components: { GiftedDialog, AlertMessage, QuickNav },
}) })
export default class HomeView extends Vue { export default class HomeView extends Vue {
activeDid = ""; activeDid = "";
allAccounts: Array<Account> = [];
allContacts: Array<Contact> = []; allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
apiServer = ""; apiServer = "";
feedAllLoaded = false; feedAllLoaded = false;
feedData = []; feedData = [];
@@ -202,11 +97,13 @@ export default class HomeView extends Vue {
isHiddenSpinner = true; isHiddenSpinner = true;
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
async beforeCreate() { async beforeCreate() {
await accountsDB.open(); accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
} }
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
@@ -237,9 +134,7 @@ export default class HomeView extends Vue {
async created() { async created() {
try { try {
await accountsDB.open(); await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray(); this.allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did);
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
@@ -248,17 +143,10 @@ export default class HomeView extends Vue {
this.feedLastViewedId = settings?.lastViewedClaimId; this.feedLastViewedId = settings?.lastViewedClaimId;
this.updateAllFeed(); this.updateAllFeed();
} catch (err) { } catch (err) {
this.$notify( this.alertTitle = "Error";
{ this.alertMessage =
group: "alert", err.userMessage ||
type: "danger", "There was an error retrieving the latest sweet, sweet action.";
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
},
-1,
);
} }
} }
@@ -307,15 +195,9 @@ export default class HomeView extends Vue {
}) })
.catch((e) => { .catch((e) => {
console.log("Error with feed load:", e); console.log("Error with feed load:", e);
this.$notify( this.alertMessage =
{ e.userMessage || "There was an error retrieving feed data.";
group: "alert", this.alertTitle = "Error";
type: "danger",
title: "Export Error",
text: e.userMessage || "There was an error retrieving feed data.",
},
-1,
);
}); });
this.isHiddenSpinner = true; this.isHiddenSpinner = true;
@@ -349,13 +231,14 @@ export default class HomeView extends Vue {
if (claim.claim) { if (claim.claim) {
claim = claim.claim; claim = claim.claim;
} }
// agent.did is for legacy data, before March 2023 // agent.did is for legacy data, before March 2023
const giverDid = const giverDid =
claim.agent?.identifier || claim.agent?.did; claim.agent?.identifier || claim.agent?.did || giveRecord.issuer;
const giverInfo = didInfo( const giverInfo = didInfo(
giverDid, giverDid,
this.activeDid, this.activeDid,
this.allMyDids, this.allAccounts,
this.allContacts, this.allContacts,
); );
const gaveAmount = claim.object?.amountOfThisGood const gaveAmount = claim.object?.amountOfThisGood
@@ -368,7 +251,7 @@ export default class HomeView extends Vue {
didInfo( didInfo(
gaveRecipientId, gaveRecipientId,
this.activeDid, this.activeDid,
this.allMyDids, this.allAccounts,
this.allContacts, this.allContacts,
) )
: ""; : "";
@@ -406,27 +289,17 @@ export default class HomeView extends Vue {
*/ */
public async recordGive(giverDid, description, hours) { public async recordGive(giverDid, description, hours) {
if (!this.activeDid) { if (!this.activeDid) {
this.$notify( this.setAlert(
{ "Error",
group: "alert", "You must select an identity before you can record a give.",
type: "danger",
title: "Error",
text: "You must select an identity before you can record a give.",
},
-1,
); );
return; return;
} }
if (!description && !hours) { if (!description && !hours) {
this.$notify( this.setAlert(
{ "Error",
group: "alert", "You must enter a description or some number of hours.",
type: "danger",
title: "Error",
text: "You must enter a description or some number of hours.",
},
-1,
); );
return; return;
} }
@@ -443,41 +316,21 @@ export default class HomeView extends Vue {
hours, hours,
); );
if (this.isGiveCreationError(result)) { if (isGiveCreationError(result)) {
const errorMessage = this.getGiveCreationErrorMessage(result); const errorMessage = getGiveCreationErrorMessage(result);
console.log("Error with give result:", result); console.log("Error with give result:", result);
this.$notify( this.setAlert(
{ "Error",
group: "alert", errorMessage || "There was an error recording the give.",
type: "danger",
title: "Error",
text: errorMessage || "There was an error recording the give.",
},
-1,
); );
} else { } else {
this.$notify( this.setAlert("Success", "That gift was recorded.");
{
group: "alert",
type: "success",
title: "Success",
text: "That gift was recorded.",
},
-1,
);
} }
} catch (error) { } catch (error) {
console.log("Error with give caught:", error); console.log("Error with give caught:", error);
this.$notify( this.setAlert(
{ "Error",
group: "alert", getGiveErrorMessage(error) || "There was an error recording the give.",
type: "danger",
title: "Error",
text:
this.getGiveErrorMessage(error) ||
"There was an error recording the give.",
},
-1,
); );
} }
} }

View File

@@ -1,175 +0,0 @@
<template>
<QuickNav selected="Profile"></QuickNav>
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Cancel -->
<router-link
:to="{ name: 'account' }"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><fa icon="chevron-left" class="fa-fw"></fa>
</router-link>
Switch Identity
</h1>
</div>
<!-- Identity List -->
<!-- Current Identity - Display First! -->
<div class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-4">
<fa icon="circle-check" class="fa-fw text-blue-600 text-xl mr-3"></fa>
<span class="overflow-hidden">
<h2 class="text-xl font-semibold mb-0">
{{ firstName }} {{ lastName }}
</h2>
<div class="text-sm text-slate-500 truncate">
<b>ID:</b> <code>{{ activeDid }}</code>
</div>
</span>
</div>
<!-- Other Identity/ies -->
<ul class="mb-4">
<li
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-2"
v-for="ident in otherIdentities"
:key="ident.did"
@click="switchAccount(ident.did)"
>
<fa icon="circle" class="fa-fw text-slate-400 text-xl mr-3"></fa>
<span class="overflow-hidden">
<h2 class="text-xl font-semibold mb-0"></h2>
<div class="text-sm text-slate-500 truncate">
<b>ID:</b> <code>{{ ident.did }}</code>
</div>
</span>
</li>
</ul>
<!-- Actions -->
<router-link
:to="{ name: 'start' }"
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
>
Add Another Identity&hellip;
</router-link>
<a
href="#"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-8"
@click="switchAccount('0')"
>
No Identity
</a>
<AlertMessage
:alertTitle="alertTitle"
:alertMessage="alertMessage"
></AlertMessage>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { AppString } from "@/constants/app";
import { db, accountsDB } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav";
@Component({ components: { AlertMessage, QuickNav } })
export default class IdentitySwitcherView extends Vue {
Constants = AppString;
public accounts: AccountsSchema;
public activeDid;
public firstName;
public lastName;
public alertTitle;
public alertMessage;
public otherIdentities = [];
public async getIdentity(activeDid) {
await accountsDB.open();
const account = await accountsDB.accounts
.where("did")
.equals(activeDid)
.first();
const identity = JSON.parse(account?.identity || "null");
return identity;
}
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.apiServerInput = settings?.apiServer || "";
this.firstName = settings?.firstName || "No";
this.lastName = settings?.lastName || "Name";
this.showContactGives = !!settings?.showContactGivesInline;
const identity = await this.getIdentity(this.activeDid);
if (identity) {
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: identity.did,
});
}
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
for (let n = 0; n < accounts.length; n++) {
const did = JSON.parse(accounts[n].identity)["did"];
if (did && this.activeDid !== did) {
this.otherIdentities.push({ did: did });
}
}
} catch (err) {
if (
err.message ===
"Attempted to load account records with no identity available."
) {
this.limitsMessage = "No identity.";
this.loadingLimits = false;
} else {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Creating Account",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
console.error(
"Telling user to clear cache at page create because:",
err,
);
}
}
}
async switchAccount(did: string) {
// 0 means none
if (did === "0") {
did = undefined;
}
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: did,
});
this.activeDid = did;
this.otherIdentities = [];
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
for (let n = 0; n < accounts.length; n++) {
const did = JSON.parse(accounts[n].identity)["did"];
if (did && this.activeDid !== did) {
this.otherIdentities.push({ did: did });
}
}
this.$router.push({ name: "account" });
}
}
</script>

View File

@@ -13,37 +13,38 @@
[New/Edit] Identity [New/Edit] Identity
</h1> </h1>
</div> </div>
<form>
<input
type="text"
placeholder="First Name"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
v-model="firstName"
/>
<input
type="text"
placeholder="Last Name"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
v-model="lastName"
/>
<input <div class="mt-8">
type="text" <button
placeholder="First Name" type="button"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
v-model="firstName" @click="onClickSaveChanges()"
/> >
<input Save Changes
type="text" </button>
placeholder="Last Name" <!-- SHOW ME instead while processing saving changes -->
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" <button
v-model="lastName" type="button"
/> class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="onClickCancel()"
<div class="mt-8"> >
<button Cancel
type="button" </button>
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" </div>
@click="onClickSaveChanges()" </form>
>
Save Changes
</button>
<!-- SHOW ME instead while processing saving changes -->
<button
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="onClickCancel()"
>
Cancel
</button>
</div>
</section> </section>
</template> </template>

View File

@@ -16,44 +16,47 @@
</div> </div>
<!-- Project Details --> <!-- Project Details -->
<form>
<select class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"> <select
<option disabled>Choose a commitment type</option> class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
<option selected>Time</option>
<option>Cryptocurrency</option>
<option>Money</option>
</select>
<!-- Time amount -->
<div class="mb-4 flex items-stretch">
<input
type="number"
placeholder="0.0"
class="block w-full rounded-l border border-slate-400 px-3 py-2"
/>
<span
class="px-4 py-2 rounded-r bg-slate-200 border border-l-0 border-slate-400"
>hours</span
> >
</div> <option disabled>Choose a commitment type</option>
<option selected>Time</option>
<option>Cryptocurrency</option>
<option>Money</option>
</select>
<!-- Crypto amount --> <!-- Time amount -->
<div class="mb-4 flex items-stretch">
<input
type="number"
placeholder="0.0"
class="block w-full rounded-l border border-slate-400 px-3 py-2"
/>
<span
class="px-4 py-2 rounded-r bg-slate-200 border border-l-0 border-slate-400"
>hours</span
>
</div>
<!-- Money amount --> <!-- Crypto amount -->
<div class="mt-8"> <!-- Money amount -->
<input
type="submit" <div class="mt-8">
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" <input
value="Commit" type="submit"
/> class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
<button value="Commit"
type="button" />
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" <button
> type="button"
Maybe Later class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
</button> >
</div> Maybe Later
</button>
</div>
</form>
</section> </section>
</template> </template>

View File

@@ -76,12 +76,21 @@ import * as didJwt from "did-jwt";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken, SimpleSigner } from "@/libs/crypto"; import { accessToken, SimpleSigner } from "@/libs/crypto";
import { useAppStore } from "@/store/app"; import { useAppStore } from "@/store/app";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
interface VerifiableCredential {
"@context": string;
"@type": string;
name: string;
description: string;
identifier?: string;
}
@Component({ @Component({
components: { AlertMessage }, components: { AlertMessage },
}) })
@@ -91,13 +100,13 @@ export default class NewEditProjectView extends Vue {
projectName = ""; projectName = "";
description = ""; description = "";
errorMessage = ""; errorMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
alertTitle = "";
alertMessage = "";
async beforeCreate() { async beforeCreate() {
await accountsDB.open(); accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
} }
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
@@ -245,41 +254,20 @@ export default class NewEditProjectView extends Vue {
if (serverError) { if (serverError) {
if (Object.prototype.hasOwnProperty.call(serverError, "message")) { if (Object.prototype.hasOwnProperty.call(serverError, "message")) {
console.log(serverError); console.log(serverError);
this.alertTitle = "User Message";
userMessage = serverError.response.data.error.message; // This is info for the user. userMessage = serverError.response.data.error.message; // This is info for the user.
this.$notify( this.alertMessage = userMessage;
{
group: "alert",
type: "danger",
title: "User Message",
text: userMessage,
},
-1,
);
} else { } else {
this.$notify( this.alertTitle = "Server Message";
{ this.alertMessage = JSON.stringify(serverError.toJSON());
group: "alert",
type: "danger",
title: "Server Message",
text: JSON.stringify(serverError.toJSON()),
},
-1,
);
} }
} else { } else {
console.error( console.error(
"Here's the full error trying to save the claim:", "Here's the full error trying to save the claim:",
error, error,
); );
this.$notify( this.alertTitle = "Claim Error";
{ this.alertMessage = error as string;
group: "alert",
type: "danger",
title: "Claim Error",
text: error as string,
},
-1,
);
} }
// Now set that error for the user to see. // Now set that error for the user to see.
this.errorMessage = userMessage; this.errorMessage = userMessage;
@@ -302,5 +290,9 @@ export default class NewEditProjectView extends Vue {
public onCancelClick() { public onCancelClick() {
this.$router.back(); this.$router.back();
} }
// This same popup code is in many files.
alertTitle = "";
alertMessage = "";
} }
</script> </script>

View File

@@ -50,6 +50,7 @@ export default class AccountViewView extends Vue {
loading = true; loading = true;
async mounted() { async mounted() {
await accountsDB.open();
const mnemonic = generateSeed(); const mnemonic = generateSeed();
// address is 0x... ETH address, without "did:eth:" // address is 0x... ETH address, without "did:eth:"
const [address, privateHex, publicHex, derivationPath] = const [address, privateHex, publicHex, derivationPath] =
@@ -57,8 +58,6 @@ export default class AccountViewView extends Vue {
const newId = newIdentifier(address, publicHex, privateHex, derivationPath); const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
const identity = JSON.stringify(newId); const identity = JSON.stringify(newId);
await accountsDB.open();
await accountsDB.accounts.add({ await accountsDB.accounts.add({
dateCreated: new Date().toISOString(), dateCreated: new Date().toISOString(),
derivationPath: derivationPath, derivationPath: derivationPath,

View File

@@ -23,31 +23,20 @@
</h1> </h1>
</div> </div>
<div class="text-red-500">
{{ errorMessage }}
</div>
<!-- Project Details --> <!-- Project Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<div> <div>
<div class="block pb-4 flex gap-4"> <h2 class="text-xl font-semibold">{{ name }}</h2>
<div class="flex-none w-16 pt-1"> <div class="flex justify-between gap-4 text-sm mb-3">
<EntityIcon <span><fa icon="user" class="fa-fw text-slate-400"></fa> Rotary</span>
:entityId="projectId" <span
:iconSize="64" ><fa icon="calendar" class="fa-fw text-slate-400"></fa
class="block border border-slate-300 rounded-md" >{{ timeSince }}
></EntityIcon> </span>
</div>
<div class="overflow-hidden">
<h2 class="text-xl font-semibold">{{ name }}</h2>
<div class="text-sm mb-3">
<div class="truncate"
><fa icon="user" class="fa-fw text-slate-400"></fa>
{{ issuer }}</div
>
<div
><fa icon="calendar" class="fa-fw text-slate-400"></fa
>{{ timeSince }}
</div>
</div>
</div>
</div> </div>
<div class="text-sm text-slate-500"> <div class="text-sm text-slate-500">
@@ -77,115 +66,83 @@
</div> </div>
<div> <div>
<div v-if="activeDid" class="text-center"> <div v-if="activeDid">
<button <button
@click="openDialog({ name: 'you', did: activeDid })" @click="openDialog({ name: 'you', did: activeDid })"
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md" class="text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
> >
I gave&hellip; I gave...
</button> </button>
<p class="mt-2 mb-4 text-center">Or, record a gift from:</p> &horbar; or:
</div> </div>
<p v-if="!activeDid" class="mt-2 mb-4">Record a gift from:</p> <!-- similar contact selection code is in multiple places -->
Record a gift from
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5"> <span v-for="contact in allContacts" :key="contact.did">
<li @click="openDialog()"> <button @click="openDialog(contact)" class="text-blue-500">
<div class="mb-1"> &nbsp;{{ contact.name }}</button
<fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa> >,
</div> </span>
<h3 <span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span>
class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden" <button @click="openDialog()" class="text-blue-500">
> someone not specified
Anonymous </button>
</h3>
</li>
<li
v-for="contact in allContacts"
:key="contact.did"
@click="openDialog(contact)"
>
<div class="mb-1">
<fa icon="user" class="fa-fw fa-xl text-slate-400"></fa>
</div>
<h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
>
{{ contact.name || "(no name)" }}
</h3>
</li>
</ul>
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
<router-link
v-if="allContacts.length > 7"
:to="{ name: 'contact-gives' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
>
Show More Contacts&hellip;
</router-link>
</div> </div>
<!-- Gifts to & from this --> <!-- Gifts to & from this -->
<div class="grid items-start grid-cols-1 sm:grid-cols-2 gap-4"> <div class="mt-8 flex justify-around">
<div class="bg-slate-100 px-4 py-3 rounded-md"> <div>
<h3 class="text-sm uppercase font-semibold mb-3"> <h1 class="text-xl">Given to this Project</h1>
Given to this Project
</h3>
<ul class="text-sm border-t border-slate-300">
<li
v-for="give in givesToThis"
:key="give.id"
class="py-1.5 border-b border-slate-300"
>
<div class="flex justify-between gap-4">
<span
><fa icon="user" class="fa-fw text-slate-400"></fa>
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
</span>
<span v-if="give.amount"
><fa icon="coins" class="fa-fw text-slate-400"></fa>
{{ give.amount }}
</span>
</div>
<div v-if="give.description" class="text-slate-500">
<fa icon="comment" class="fa-fw text-slate-400"></fa>
{{ give.description }}
</div>
</li>
</ul>
</div> </div>
<div>
<div class="bg-slate-100 px-4 py-3 rounded-md"> <h1 class="text-xl">... and from this Project</h1>
<h3 class="text-sm uppercase font-semibold mb-3"> </div>
&hellip;and from this Project </div>
</h3> <div class="flex justify-around">
<div class="w-1/2">
<ul class="text-sm border-t border-slate-300"> <div v-for="give in givesToThis" :key="give.id">
<li <div class="flex justify-between">
v-for="give in givesByThis" <div class="flex gap-3">
:key="give.id" <div class="flex gap-2">
class="py-1.5 border-b border-slate-300" <fa icon="user" class="fa-fw text-slate-400"></fa>
> <span>{{
<div class="flex justify-between gap-4"> didInfo(give.agentDid, activeDid, accounts, allContacts)
<span }}</span>
><fa icon="user" class="fa-fw text-slate-400"></fa> </div>
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }} <div class="flex gap-2" v-if="give.amount">
</span> <fa icon="coins" class="fa-fw text-slate-400"></fa>
<span v-if="give.amount" <span>{{ give.amount }}</span>
><fa icon="coins" class="fa-fw text-slate-400"></fa> </div>
{{ give.amount }} <div class="flex gap-2" v-if="give.description">
</span> <fa icon="comment" class="fa-fw text-slate-400"></fa>
</div> <span>{{ give.description }}</span>
<div v-if="give.description" class="text-slate-500"> </div>
<fa icon="comment" class="fa-fw text-slate-400"></fa> </div>
{{ give.description }} </div>
</div> </div>
</li> </div>
</ul> <div class="w-1/2">
<div v-for="give in givesByThis" :key="give.id">
<div class="flex justify-between">
<div class="flex gap-3">
<div class="flex gap-2">
<fa icon="user" class="fa-fw text-slate-400"></fa>
<span>{{
didInfo(give.agentDid, activeDid, accounts, allContacts)
}}</span>
</div>
<div class="flex gap-2" v-if="give.amount">
<fa icon="coins" class="fa-fw text-slate-400"></fa>
<span>{{ give.amount }}</span>
</div>
<div class="flex gap-2">
<fa icon="comment" class="fa-fw text-slate-400"></fa>
<span>{{ give.description }}</span>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<GiftedDialog <GiftedDialog
ref="customDialog" ref="customDialog"
@dialog-result="handleDialogResult" @dialog-result="handleDialogResult"
@@ -207,6 +164,7 @@ import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
@@ -217,29 +175,35 @@ import {
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon }, components: { GiftedDialog, AlertMessage, QuickNav },
}) })
export default class ProjectViewView extends Vue { export default class ProjectViewView extends Vue {
accounts: AccountsSchema;
activeDid = ""; activeDid = "";
alertMessage = ""; alertMessage = "";
alertTitle = ""; alertTitle = "";
allMyDids: Array<string> = [];
allContacts: Array<Contact> = []; allContacts: Array<Contact> = [];
apiServer = ""; apiServer = "";
description = ""; description = "";
errorMessage = "";
expanded = false; expanded = false;
givesToThis: Array<GiveServerRecord> = []; givesToThis: Array<GiveServerRecord> = [];
givesByThis: Array<GiveServerRecord> = []; givesByThis: Array<GiveServerRecord> = [];
name = ""; name = "";
issuer = ""; numAccounts = 0;
projectId = localStorage.getItem("projectId") || ""; // handle ID projectId = localStorage.getItem("projectId") || ""; // handle ID
timeSince = ""; timeSince = "";
truncatedDesc = ""; truncatedDesc = "";
truncateLength = 40; truncateLength = 40;
async beforeCreate() {
accountsDB.open();
this.accounts = accountsDB.accounts;
this.numAccounts = (await this.accounts?.count()) || 0;
}
async created() { async created() {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);
@@ -247,11 +211,9 @@ export default class ProjectViewView extends Vue {
this.apiServer = settings?.apiServer || ""; this.apiServer = settings?.apiServer || "";
this.allContacts = await db.contacts.toArray(); this.allContacts = await db.contacts.toArray();
await accountsDB.open(); this.accounts = accountsDB.accounts;
const accounts = accountsDB.accounts; const accountsArr = await this.accounts?.toArray();
const accountsArr = await accounts?.toArray(); const account = accountsArr.find((acc) => acc.did === this.activeDid);
this.allMyDids = accountsArr.map((acc) => acc.did);
const account = accountsArr?.find((acc) => acc.did === this.activeDid);
const identity = JSON.parse(account?.identity || "null"); const identity = JSON.parse(account?.identity || "null");
this.LoadProject(identity); this.LoadProject(identity);
} }
@@ -290,8 +252,8 @@ export default class ProjectViewView extends Vue {
} }
// Isn't there a better way to make this available to the template? // Isn't there a better way to make this available to the template?
didInfo(did, activeDid, dids, contacts) { didInfo(did, activeDid, identities, contacts) {
return didInfo(did, activeDid, dids, contacts); return didInfo(did, activeDid, identities, contacts);
} }
expandText() { expandText() {
@@ -318,51 +280,30 @@ export default class ProjectViewView extends Vue {
try { try {
const resp = await this.axios.get(url, { headers }); const resp = await this.axios.get(url, { headers });
if (resp.status === 200) { if (resp.status === 200) {
// feel free to remove this; I haven't yet because it's helpful
console.log("Loaded project: ", resp.data);
const startTime = resp.data.startTime; const startTime = resp.data.startTime;
if (startTime != null) { if (startTime != null) {
const eventDate = new Date(startTime); const eventDate = new Date(startTime);
const now = moment.now(); const now = moment.now();
this.timeSince = moment.utc(now).to(eventDate); this.timeSince = moment.utc(now).to(eventDate);
} }
this.issuer = resp.data.issuer;
this.name = resp.data.claim?.name || "(no name)"; this.name = resp.data.claim?.name || "(no name)";
this.description = resp.data.claim?.description || "(no description)"; this.description = resp.data.claim?.description || "(no description)";
this.truncatedDesc = this.description.slice(0, this.truncateLength); this.truncatedDesc = this.description.slice(0, this.truncateLength);
} else if (resp.status === 404) { } else if (resp.status === 404) {
// actually, axios throws an error so we never get here // actually, axios throws an error so we never get here
this.$notify( this.errorMessage = "That project does not exist.";
{
group: "alert",
type: "danger",
title: "Error",
text: "That project does not exist.",
},
-1,
);
} }
} catch (error: unknown) { } catch (error: unknown) {
const serverError = error as AxiosError; const serverError = error as AxiosError;
if (serverError.response?.status === 404) { if (serverError.response?.status === 404) {
this.$notify( this.errorMessage = "That project does not exist.";
{
group: "alert",
type: "danger",
title: "Error",
text: "That project does not exist.",
},
-1,
);
} else { } else {
this.$notify( this.errorMessage =
{ "Something went wrong retrieving that project." +
group: "alert", " See logs for more info.";
type: "danger", console.error("Error retrieving project:", error);
title: "Error",
text: "Something went wrong retrieving that project. See logs for more info.",
},
-1,
);
console.error("Error retrieving project:", serverError.message);
} }
} }
@@ -375,31 +316,12 @@ export default class ProjectViewView extends Vue {
if (resp.status === 200 && resp.data.data) { if (resp.status === 200 && resp.data.data) {
this.givesToThis = resp.data.data; this.givesToThis = resp.data.data;
} else { } else {
this.$notify( this.errorMessage = "Failed to retrieve gives to this project.";
{
group: "alert",
type: "danger",
title: "Error",
text: "Failed to retrieve gives to this project.",
},
-1,
);
} }
} catch (error: unknown) { } catch (error: unknown) {
const serverError = error as AxiosError; console.error("Error retrieving gives to this project:", error);
this.$notify( this.errorMessage =
{ "Something went wrong retrieving gives to this project.";
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong retrieving gives to this project.",
},
-1,
);
console.error(
"Error retrieving gives to this project:",
serverError.message,
);
} }
const givesOutUrl = const givesOutUrl =
@@ -411,31 +333,11 @@ export default class ProjectViewView extends Vue {
if (resp.status === 200 && resp.data.data) { if (resp.status === 200 && resp.data.data) {
this.givesByThis = resp.data.data; this.givesByThis = resp.data.data;
} else { } else {
this.$notify( this.errorMessage = "Failed to retrieve gives by this project.";
{
group: "alert",
type: "danger",
title: "Error",
text: "Failed to retrieve gives by this project.",
},
-1,
);
} }
} catch (error: unknown) { } catch (error: unknown) {
const serverError = error as AxiosError; console.error("Error retrieving gives by this project:", error);
this.$notify( this.errorMessage = "Something went wrong retrieving gives by project.";
{
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong retrieving gives by project.",
},
-1,
);
console.error(
"Error retrieving gives by this project:",
serverError.message,
);
} }
} }
@@ -462,28 +364,16 @@ export default class ProjectViewView extends Vue {
*/ */
async recordGive(giverDid, description, hours) { async recordGive(giverDid, description, hours) {
if (!this.activeDid) { if (!this.activeDid) {
this.$notify( this.alertTitle = "Error";
{ this.alertMessage =
group: "alert", "You must select an identity before you can record a give.";
type: "danger",
title: "Error",
text: "You must select an identity before you can record a give.",
},
-1,
);
return; return;
} }
if (!description && !hours) { if (!description && !hours) {
this.$notify( this.alertTitle = "Error";
{ this.alertMessage =
group: "alert", "You must enter a description or some number of hours.";
type: "danger",
title: "Error",
text: "You must enter a description or some number of hours.",
},
-1,
);
return; return;
} }
@@ -502,42 +392,21 @@ export default class ProjectViewView extends Vue {
if (result.status !== 201 || result.data?.error) { if (result.status !== 201 || result.data?.error) {
console.log("Error with give result:", result); console.log("Error with give result:", result);
this.$notify( this.alertTitle = "Error";
{ this.alertMessage =
group: "alert", result.data?.error?.message ||
type: "danger", "There was an error recording the give.";
title: "Error",
text:
result.data?.error?.message ||
"There was an error recording the give.",
},
-1,
);
} else { } else {
this.$notify( this.alertTitle = "Success";
{ this.alertMessage = "That gift was recorded.";
group: "alert",
type: "success",
title: "Success",
text: "That gift was recorded.",
},
-1,
);
} }
} catch (e) { } catch (e) {
console.log("Error with give caught:", e); console.log("Error with give caught:", e);
this.$notify( this.alertTitle = "Error";
{ this.alertMessage =
group: "alert", e.userMessage ||
type: "danger", e.response?.data?.error?.message ||
title: "Error", "There was an error recording the give.";
text:
e.userMessage ||
e.response?.data?.error?.message ||
"There was an error recording the give.",
},
-1,
);
} }
} }
} }

View File

@@ -7,8 +7,7 @@
</h1> </h1>
<!-- Quick Search --> <!-- Quick Search -->
<form id="QuickSearch" class="mb-4 flex">
<div id="QuickSearch" class="mb-4 flex">
<input <input
type="text" type="text"
placeholder="Search…" placeholder="Search…"
@@ -19,7 +18,7 @@
> >
<fa icon="magnifying-glass" class="fa-fw"></fa> <fa icon="magnifying-glass" class="fa-fw"></fa>
</button> </button>
</div> </form>
<!-- New Project --> <!-- New Project -->
<button <button
@@ -39,7 +38,7 @@
<!-- Results List --> <!-- Results List -->
<InfiniteScroll @reached-bottom="loadMoreData"> <InfiniteScroll @reached-bottom="loadMoreData">
<ul class="border-t border-slate-300"> <ul>
<li <li
class="border-b border-slate-300" class="border-b border-slate-300"
v-for="project in projects" v-for="project in projects"
@@ -50,11 +49,10 @@
class="block py-4 flex gap-4" class="block py-4 flex gap-4"
> >
<div class="flex-none w-12"> <div class="flex-none w-12">
<EntityIcon <img
:entityId="project.handleId" src="https://picsum.photos/200/200?random=1"
:iconSize="48" class="w-full rounded"
class="inline-block align-middle border border-slate-300 rounded-md" />
></EntityIcon>
</div> </div>
<div class="grow overflow-hidden"> <div class="grow overflow-hidden">
@@ -77,16 +75,38 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import InfiniteScroll from "@/components/InfiniteScroll"; import InfiniteScroll from "@/components/InfiniteScroll";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
/**
* Represents data about a project
**/
interface ProjectData {
/**
* Name of the project
**/
name: string;
/**
* Description of the project
**/
description: string;
/**
* URL referencing information about the project
**/
handleId: string;
/**
* The Identier of the project
**/
rowid: string;
}
@Component({ @Component({
components: { InfiniteScroll, AlertMessage, QuickNav, EntityIcon }, components: { InfiniteScroll, AlertMessage, QuickNav },
}) })
export default class ProjectsView extends Vue { export default class ProjectsView extends Vue {
apiServer = ""; apiServer = "";
@@ -95,11 +115,13 @@ export default class ProjectsView extends Vue {
isLoading = false; isLoading = false;
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
async beforeCreate() { async beforeCreate() {
await accountsDB.open(); accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); this.accounts = accountsDB.accounts;
this.numAccounts = await this.accounts.count();
} }
/** /**
@@ -128,15 +150,8 @@ export default class ProjectsView extends Vue {
} }
} catch (error) { } catch (error) {
console.error("Got error loading projects:", error.message); console.error("Got error loading projects:", error.message);
this.$notify( this.alertTitle = "Error";
{ this.alertMessage = "Got an error loading projects:" + error.message;
group: "alert",
type: "danger",
title: "Error",
text: "Got an error loading projects: " + error.message,
},
-1,
);
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }
@@ -205,15 +220,8 @@ export default class ProjectsView extends Vue {
if (this.numAccounts === 0) { if (this.numAccounts === 0) {
console.error("No accounts found."); console.error("No accounts found.");
this.$notify( this.alertTitle = "Error";
{ this.alertMessage = "You need an identity to load your projects.";
group: "alert",
type: "danger",
title: "Error",
text: "You need an identity to load your projects.",
},
-1,
);
} else { } else {
const identity = await this.getIdentity(activeDid); const identity = await this.getIdentity(activeDid);
this.current = identity; this.current = identity;
@@ -221,15 +229,8 @@ export default class ProjectsView extends Vue {
} }
} catch (err) { } catch (err) {
console.log("Error initializing:", err); console.log("Error initializing:", err);
this.$notify( this.alertTitle = "Error";
{ this.alertMessage = "Something went wrong loading your projects.";
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong loading your projects.",
},
-1,
);
} }
} }

View File

@@ -20,26 +20,22 @@
</div> </div>
<div v-if="activeAccount"> <div v-if="activeAccount">
<p class="text-center mb-4"> <p>
<b class="text-red-600">BEWARE!</b> Anyone who has this seed phrase will BEWARE: Anyone who gets hold of this mnemonic seed phrase will be able
be able impersonate you and take over any digital holdings based on it. impersonate you and take over any digital holdings based on it. So only
Reveal it when you are somewhere only you can see your screen, and reveal it when you are in a private place out of sight of other eyes,
record it somewhere only you have access. and only record it in something private -- don't take a screenshot or
<i>Don't take a screenshot or send it to any online service.</i> send it to any online service.
</p> </p>
<div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4"> <button
<button class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" @click="showSeedPhrase"
@click="showSeedPhrase" >
> Click here when you're ready to see it.
Reveal my Seed Phrase </button>
</button>
<p v-if="showSeed" class="text-center text-slate-700 mt-2"> <p v-if="showSeed">{{ activeAccount.mnemonic }}</p>
{{ activeAccount.mnemonic }}
</p>
</div>
</div> </div>
<div v-else>You do not have an active identity.</div> <div v-else>You do not have an active identity.</div>
<AlertMessage <AlertMessage
@@ -76,15 +72,8 @@ export default class SeedBackupView extends Vue {
this.activeAccount = R.find((acc) => acc.did === activeDid, accounts); this.activeAccount = R.find((acc) => acc.did === activeDid, accounts);
} catch (err) { } catch (err) {
console.error("Got an error loading an identity:", err); console.error("Got an error loading an identity:", err);
this.$notify( this.alertTitle = "Error Loading Account";
{ this.alertMessage = "Got an error loading your seed data.";
group: "alert",
type: "danger",
title: "Error Loading Account",
text: "Got an error loading your seed data.",
},
-1,
);
} }
} }

View File

@@ -47,6 +47,11 @@ import { World } from "@/components/World/World.js";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
interface WorldProperties {
startTime?: string;
endTime?: string;
}
@Component({ components: { AlertMessage, World, QuickNav } }) @Component({ components: { AlertMessage, World, QuickNav } })
export default class StatisticsView extends Vue { export default class StatisticsView extends Vue {
world: World; world: World;
@@ -57,20 +62,14 @@ export default class StatisticsView extends Vue {
mounted() { mounted() {
try { try {
const container = document.querySelector("#scene-container"); const container = document.querySelector("#scene-container");
console.log(container);
const newWorld = new World(container, this); const newWorld = new World(container, this);
newWorld.start(); newWorld.start();
this.world = newWorld; this.world = newWorld;
} catch (err) { } catch (err) {
console.log(err); console.log(err);
this.$notify( this.alertTitle = "Mounting error";
{ this.alertMessage = err.message;
group: "alert",
type: "danger",
title: "Mounting Error",
text: err.message,
},
-1,
);
} }
} }