Compare commits

..

46 Commits

Author SHA1 Message Date
f09684d7cd Merge branch 'master' into ui-fix 2023-08-07 02:41:30 -04:00
1767a48a7f Merge pull request 'New notification system + test' (#48) from notiwind-alert into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#48
2023-08-07 02:41:05 -04:00
d2e2fc707e Merge branch 'master' into notiwind-alert 2023-08-07 02:34:22 -04:00
Jose Olarte III
f55e50067f Replaced all alertMessage calls with notiwind 2023-07-21 20:26:00 +08:00
4866416aae fix didInfo logic, and add to project lists 2023-07-20 19:02:18 -06:00
e48a4ed05b fix to use the real project name, and add creator 2023-07-20 18:35:27 -06:00
87cfead094 fix display of gives on contact screen; adjust give UI for project 2023-07-20 18:22:45 -06:00
179a5cd9f8 add task for publicly-accessible project 2023-07-20 08:32:43 -06:00
Jose Olarte III
eff67c2a4a Replaced alertMessage calls with Notiwind 2023-07-20 21:51:23 +08:00
Jose Olarte III
db22d559b7 User-friendly error messages 2023-07-20 18:04:08 +08:00
Matthew Raymer
c4443f2ed1 Added error handling and new alerts in DiscoverView 2023-07-20 16:36:33 +08:00
Jose Olarte III
05a7758c65 New notification system + test
Set of buttons added to home view for preview. Comes in toast (self-dismiss) and context alert (info, warning, danger) variants.
2023-07-19 19:48:22 +08:00
d0ec7930e1 Merge pull request 'UI improvements' (#46) from contacts-view-improvements into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#46
2023-07-19 02:18:01 -04:00
3e2cd1291c Merge branch 'master' into contacts-view-improvements 2023-07-19 02:17:55 -04:00
8d42fe905d Merge pull request 'Fixes to search bar and list top' (#45) from projects-view-improvements into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#45
2023-07-19 02:17:44 -04:00
a7e98c8f1a Merge branch 'master' into projects-view-improvements 2023-07-18 06:56:01 -04:00
f07c804b24 Merge branch 'master' into contacts-view-improvements 2023-07-18 06:55:02 -04:00
Matthew Raymer
e8eae544f3 Merge remote-tracking branch 'origin/no-accounts-in-memory' 2023-07-18 18:50:55 +08:00
Jose Olarte III
7c77578f79 Fixes to search bar and list top 2023-07-18 17:33:49 +08:00
Jose Olarte III
34636d6047 Fixed alert message visibility 2023-07-18 17:14:03 +08:00
Jose Olarte III
5134e2f562 UI improvements
- Consistent button styling for interactive elements
- Tooltips relegated to title attribute to avoid blocking other buttons
2023-07-18 16:40:16 +08:00
91b46eaaee Update 'project.task.yaml' 2023-07-18 02:15:04 -04:00
31d1a449ae Update 'project.task.yaml' 2023-07-18 02:12:40 -04:00
1248132076 Update 'project.task.yaml' 2023-07-18 02:12:29 -04:00
015704c94e Merge pull request 'home-gifting-improvements' (#43) from home-gifting-improvements into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#43
2023-07-18 01:35:40 -04:00
540ef916c2 Merge pull request 'Remove form tags' (#44) from form-tag-cleanup into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#44
2023-07-18 01:35:22 -04:00
bee7c87a8f Merge branch 'master' into no-accounts-in-memory 2023-07-17 16:16:44 -04:00
6bbc88f86c avoid console errors when no identity exists, and add to tasks 2023-07-14 20:54:26 -06:00
624abbb179 correct an error with unseen internal methods 2023-07-14 20:53:41 -06:00
110ed009b2 remove unused reference, call out another verbiage fix to do 2023-07-14 20:30:40 -06:00
a5892238d5 on tasks: prioritize notifications, add fixes 2023-07-14 20:25:15 -06:00
8eb80a9ede remove unnecessary async (that also happens to break display) 2023-07-14 20:11:04 -06:00
Jose Olarte III
32125133f0 No-contacts state 2023-07-14 21:33:56 +08:00
Matthew Raymer
47ade49e31 Cleanup after some testing 2023-07-14 20:29:38 +08:00
Jose Olarte III
47ce91cca1 Implemented design of Contact Gifting List view 2023-07-14 19:42:13 +08:00
Jose Olarte III
3e52b504b0 Polished gifted dialog UI 2023-07-14 18:27:43 +08:00
Matthew Raymer
4ecea1ab0e Add a special page for seeing all contacts to gift 2023-07-14 18:12:39 +08:00
Matthew Raymer
b9fdc920ea Remove the old identity management. Update project tasks 2023-07-13 18:13:41 +08:00
Matthew Aaron Raymer
0907d59a6a Remove form tags 2023-07-13 16:06:09 +08:00
59ce15c744 Merge pull request 'Adding Identity Management stubs' (#42) from identity-switcher into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#42
2023-07-12 22:49:58 -04:00
Jose Olarte III
9960a96a20 Design tweaks to Latest Activity 2023-07-12 23:17:28 +08:00
Jose Olarte III
098c6c0fa0 Compacted Quick Action section 2023-07-12 22:52:58 +08:00
d7a9fb6d54 Merge branch 'master' into no-accounts-in-memory 2023-07-11 22:57:20 -04:00
cf2b80b1f5 Merge branch 'master' into no-accounts-in-memory 2023-07-11 21:28:51 -04:00
b86323ec83 adjust didInfo so we can use DIDs and not identities, removing last of identities in memory 2023-07-11 15:30:51 -06:00
8add6448fb remove code that keeps the private key (account) data in memory 2023-07-11 09:10:25 -06:00
27 changed files with 1546 additions and 555 deletions

17
package-lock.json generated
View File

@@ -38,6 +38,7 @@
"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",
@@ -20928,6 +20929,11 @@
"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",
@@ -21275,6 +21281,17 @@
"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

@@ -38,6 +38,7 @@
"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,23 +1,14 @@
tasks: tasks:
- 01 design ideas for simple gives on the Home page
- 02 Discover page - add infinite search
- 01 add a location for a project via map pin - 01 add a location for a project via map pin
- 04 search by a bounding box for local projects (see API by clicking on "Nearby") - 04 search by a bounding box for local projects (see API by clicking on "Nearby")
- 01 remove all the "form" fields (or at least investigate to see if that page refresh is desired) - 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:jose
- 01 Replace Gifted/Give in ContactsView with GiftedDialog - 02 Fix images on projectview - allow choice of image from a pallete of images or a url image.
- 02 Fix images on projectview: allow choice of image from a pallete of images or a url image.
- 08 Scan QR code to import into contacts. - 08 Scan QR code to import into contacts.
- contacts v1 : - 40 notifications :
- 01 Import contact info a la QR code. - push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data
- .2 move all "identity" references to temporary account access assignee:trent
- contacts v+ :
- 01 Import all the non-sensitive data (ie. contacts & settings).
- .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 if you've scrolled down on the page, eg. account data download
@@ -31,18 +22,24 @@ tasks:
- 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
- 40 notifications : - .5 add link to further project / people when a project pays ahead
- push - .5 add project ID to the URL, to make a project publicly-accessible
- .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
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.
- 01 fix images on project page, on discovery page - contacts v+ :
- .2 fix static icon to the right on project page (Matthew: I've made "Rotary" into issuer?) - 01 Import all the non-sensitive data (ie. contacts & settings).
- .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
@@ -51,7 +48,11 @@ 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)
- Do we want split first name & last name? - .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages
- .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
@@ -64,9 +65,6 @@ 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:
@@ -95,6 +93,10 @@ 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,5 +1,132 @@
<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-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2" class="close-button bg-amber-400 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-slate-100": true, "bg-amber-200": true,
"p-5": true, "p-5": true,
rounded: true, rounded: true,
"drop-shadow-lg": true, "drop-shadow-lg": true,

View File

@@ -1,41 +1,51 @@
<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-lg text-center"> <h1 class="text-xl font-bold text-center mb-4">
{{ 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-4 px-3 py-2" class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
placeholder="What was received" placeholder="What was received"
v-model="description" v-model="description"
/> />
<div class="flex flex-row"> <div class="flex flex-row mb-6">
<span class="py-4">Hours</span> <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="block w-8 rounded border border-slate-400 ml-4 text-center" class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
v-model="hours" v-model="hours"
/> />
<div class="flex flex-col px-1"> <div
<div> class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
<fa icon="square-caret-up" size="2xl" @click="increment()" /> @click="increment()"
</div> >
<div> <fa icon="chevron-right" />
<fa icon="square-caret-down" size="2xl" @click="decrement()" />
</div>
</div> </div>
</div> </div>
<p class="text-right">Sign & Send to publish to the world</p> <p class="text-center mb-2 italic">Sign & Send to publish to the world</p>
<div class="text-right"> <button
<button class="rounded border border-slate-400" @click="confirm"> class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
<span class="m-2">Sign & Send</span> @click="confirm"
</button> >
&nbsp; Sign &amp; Send
<button class="rounded border border-slate-400" @click="cancel"> </button>
<span class="m-2">Cancel</span> <button
</button> class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
</div> @click="cancel"
>
Cancel
</button>
</div> </div>
</div> </div>
</template> </template>
@@ -106,12 +116,14 @@ 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: 50%; width: 100%;
max-width: 500px;
} }
</style> </style>

View File

@@ -82,13 +82,15 @@ 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(did, activeDid, identifiers, contacts) { export function didInfo(
const myId: IIdentifier | undefined = R.find( did: string,
(i) => i.did === did, activeDid: string,
identifiers, allMyDids: Array<string>,
); contacts: Array<Contact>,
): string {
const myId: string | undefined = R.find(R.equals(did), allMyDids, did);
if (myId) { if (myId) {
return "You" + (myId.did !== activeDid ? " (Alt ID)" : ""); return "You" + (myId !== 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) {

View File

@@ -5,6 +5,7 @@ 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";
@@ -13,8 +14,10 @@ import {
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo,
faCircleQuestion, faCircleQuestion,
faCircleUser, faCircleUser,
faClock, faClock,
@@ -44,6 +47,7 @@ import {
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faTrashCan, faTrashCan,
faTriangleExclamation,
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
@@ -53,8 +57,10 @@ library.add(
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo,
faCircleQuestion, faCircleQuestion,
faCircleUser, faCircleUser,
faClock, faClock,
@@ -84,6 +90,7 @@ library.add(
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faTrashCan, faTrashCan,
faTriangleExclamation,
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
@@ -96,4 +103,5 @@ createApp(App)
.use(createPinia()) .use(createPinia())
.use(VueAxios, axios) .use(VueAxios, axios)
.use(router) .use(router)
.use(Notifications)
.mount("#app"); .mount("#app");

View File

@@ -166,6 +166,14 @@ 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 {*} */
@@ -174,4 +182,14 @@ 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

@@ -143,25 +143,23 @@
<!-- 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">
<form method="dialog"> <div class="text-slate-500 text-center">
<div class="text-slate-500 text-center"> <b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code>
<b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code> </div>
</div> <img src="/img/sample-qr-code.png" class="w-full mb-3" />
<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
@@ -260,20 +258,6 @@
</button> </button>
</div> </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> <div>
<button class="text-blue-500"> <button class="text-blue-500">
<router-link <router-link
@@ -299,12 +283,12 @@ 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;
@@ -326,7 +310,6 @@ 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;
@@ -344,12 +327,6 @@ 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;
} }
@@ -380,9 +357,8 @@ export default class AccountViewView extends Vue {
} }
async beforeCreate() { async beforeCreate() {
accountsDB.open(); await accountsDB.open();
this.accounts = accountsDB.accounts; this.numAccounts = await accountsDB.accounts.count();
this.numAccounts = await this.accounts.count();
} }
async created() { async created() {
@@ -404,14 +380,18 @@ export default class AccountViewView extends Vue {
const identity = await this.getIdentity(this.activeDid); const identity = await this.getIdentity(this.activeDid);
this.publicHex = identity.keys[0].publicKeyHex; if (identity) {
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64"); this.publicHex = identity.keys[0].publicKeyHex;
this.derivationPath = identity.keys[0].meta.derivationPath; this.publicBase64 = Buffer.from(this.publicHex, "hex").toString(
"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.checkLimits(); this.checkLimitsFor(identity);
}
} catch (err) { } catch (err) {
if ( if (
err.message === err.message ===
@@ -420,13 +400,19 @@ export default class AccountViewView extends Vue {
this.limitsMessage = "No identity."; this.limitsMessage = "No identity.";
this.loadingLimits = false; this.loadingLimits = false;
} else { } else {
this.alertMessage = this.$notify(
"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";
} }
} }
} }
@@ -438,13 +424,19 @@ export default class AccountViewView extends Vue {
showContactGivesInline: this.showContactGives, showContactGivesInline: this.showContactGives,
}); });
} catch (err) { } catch (err) {
this.alertMessage = this.$notify(
"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";
} }
} }
@@ -460,22 +452,42 @@ export default class AccountViewView extends Vue {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
this.alertTitle = "Download Started"; this.$notify(
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.alertTitle = "Export Error"; this.$notify(
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 });

View File

@@ -15,35 +15,33 @@
</h1> </h1>
</div> </div>
<form> <p class="text-center text-xl mb-4 font-light">
<p class="text-center text-xl mb-4 font-light"> Would you like to add <i>Firstname</i> to your network?
Would you like to add <i>Firstname</i> to your network? </p>
</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

@@ -78,7 +78,6 @@ 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";
@@ -101,13 +100,11 @@ 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() {
accountsDB.open(); await accountsDB.open();
this.accounts = accountsDB.accounts; this.numAccounts = await accountsDB.accounts.count();
this.numAccounts = await this.accounts.count();
} }
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
@@ -149,10 +146,17 @@ export default class ContactsView extends Vue {
this.loadGives(this.activeDid, this.contact); this.loadGives(this.activeDid, this.contact);
} }
} catch (err) { } catch (err) {
this.alertTitle = "Error"; this.$notify(
this.alertMessage = {
err.userMessage || group: "alert",
"There was an error retrieving the latest sweet, sweet action."; type: "danger",
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
},
-1,
);
} }
} }
@@ -176,9 +180,15 @@ export default class ContactsView extends Vue {
resp.status, resp.status,
resp.data, resp.data,
); );
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = {
"Got an error retrieving your given time from the server."; group: "alert",
type: "danger",
title: "Error With Server",
text: "Got an error retrieving your given time from the server.",
},
-1,
);
} }
const url2 = const url2 =
@@ -197,9 +207,15 @@ export default class ContactsView extends Vue {
resp2.status, resp2.status,
resp2.data, resp2.data,
); );
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = {
"Got an error retrieving your given time from the server."; group: "alert",
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(
@@ -209,8 +225,15 @@ export default class ContactsView extends Vue {
); );
this.giveRecords = sortedResult; this.giveRecords = sortedResult;
} catch (error) { } catch (error) {
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = error as string; {
group: "alert",
type: "danger",
title: "Error With Server",
text: error as string,
},
-1,
);
} }
} }
@@ -279,15 +302,29 @@ 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.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = userMessage; {
group: "alert",
type: "danger",
title: "Error With Server",
text: userMessage,
},
-1,
);
} }
} }
} }
cannotConfirmMessage() { cannotConfirmMessage() {
this.alertTitle = "Not Allowed"; this.$notify(
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

@@ -0,0 +1,302 @@
<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"
><fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa>
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"
><fa icon="user" class="fa-fw fa-xl text-slate-400"></fa>
{{ 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";
@Component({
components: { GiftedDialog, AlertMessage, QuickNav },
})
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

@@ -85,7 +85,15 @@ 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.alertMessage = "You have no identity yet."; this.$notify(
{
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;

View File

@@ -14,75 +14,69 @@
</h1> </h1>
</div> </div>
<form> <h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3>
<h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3> <div class="bg-black rounded overflow-hidden relative mb-4">
<div class="bg-black rounded overflow-hidden relative mb-4"> <img src="https://picsum.photos/400/400?random=1" class="w-full" />
<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 <div class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"></div>
class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4" <!-- Bottom -->
></div> <div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></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"> <h3 class="text-sm uppercase font-semibold mb-2">or Enter Contact Data</h3>
or Enter Contact Data <input
</h3> type="text"
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="text" type="submit"
placeholder="Name (optional)" 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 rounded border border-slate-400 mb-2 px-3 py-2" value="Look Up Contact"
/> />
<input <button
type="text" type="button"
placeholder="ID" 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 rounded border border-slate-400 mb-2 px-3 py-2" >
/> Cancel
<input </button>
type="text" </div>
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

@@ -70,9 +70,9 @@
</div> </div>
<!-- Results List --> <!-- Results List -->
<ul v-if="contacts.length > 0"> <ul v-if="contacts.length > 0" class="border-t border-slate-300">
<li <li
class="border-b border-slate-300" class="border-b border-slate-300 py-4"
v-for="contact in contacts" v-for="contact in contacts"
:key="contact.did" :key="contact.did"
> >
@@ -85,70 +85,82 @@
Public Key (base 64): {{ contact.publicKeyBase64 }} Public Key (base 64): {{ contact.publicKeyBase64 }}
</div> </div>
<button <div id="ContactActions" class="flex gap-1.5 mt-2">
v-if="contact.seesMe" <button
class="tooltip" v-if="contact.seesMe"
@click="setVisibility(contact, false)" class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
> @click="setVisibility(contact, false)"
<fa icon="eye" class="text-slate-900 fa-fw ml-1" /> title="They can see you"
<span class="tooltiptext">They can see you</span> >
</button> <fa icon="eye" class="fa-fw" />
<button v-else class="tooltip" @click="setVisibility(contact, true)"> </button>
<span class="tooltiptext">They cannot see you</span> <button
<fa icon="eye-slash" class="text-slate-900 fa-fw ml-1" /> v-else
</button> class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
@click="setVisibility(contact, true)"
title="They cannot see you"
>
<fa icon="eye-slash" class="fa-fw" />
</button>
<button class="tooltip" @click="checkVisibility(contact)"> <button
<span class="tooltiptext">Check Visibility</span> class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
<fa icon="rotate" class="text-slate-900 fa-fw ml-1" /> @click="checkVisibility(contact)"
</button> title="Check Visibility"
>
<fa icon="rotate" class="fa-fw" />
</button>
<button v-if="contact.registered" class="tooltip"> <button
<span class="tooltiptext">Registered</span> v-if="contact.registered"
<fa icon="person-circle-check" 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> title="Registered"
<button v-else @click="register(contact)" class="tooltip"> >
<span class="tooltiptext">Registration Unknown</span> <fa icon="person-circle-check" class="fa-fw" />
<fa </button>
icon="person-circle-question" <button
class="text-slate-900 fa-fw ml-1" v-else
/> @click="register(contact)"
</button> class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
title="Registration unknown"
>
<fa icon="person-circle-question" class="fa-fw" />
</button>
<button @click="deleteContact(contact)" class="px-9 tooltip"> <button
<span class="tooltiptext">Delete!</span> @click="deleteContact(contact)"
<fa icon="trash-can" class="text-red-600 fa-fw ml-1" /> class="text-sm uppercase bg-red-600 text-white px-2 py-1.5 rounded-md"
</button> title="Delete"
>
<fa icon="trash-can" class="fa-fw" />
</button>
<div v-if="showGiveNumbers" class="float-right"> <div v-if="showGiveNumbers" class="ml-auto flex gap-1.5">
<div class="float-right"> <button
<div class="tooltip"> class="text-sm uppercase bg-blue-600 text-white px-2 py-1.5 rounded-md"
to: @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 */
}} }}
<span <fa icon="plus" class="fa-fw" />
v-if="givenByMeDescriptions[contact.did]" </button>
class="tooltiptext-left"
> <button
{{ givenByMeDescriptions[contact.did] }} class="text-sm uppercase bg-blue-600 text-white px-2 py-1.5 rounded-md"
</span> @click="onClickAddGive(contact.did, activeDid)"
<button title="givenToMeDescriptions[contact.did]"
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" >
@click="onClickAddGive(activeDid, contact.did)" From:
>
+
</button>
</div>
<div class="tooltip px-2">
from:
{{ {{
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
this.showGiveTotals this.showGiveTotals
@@ -159,28 +171,18 @@
: (givenToMeUnconfirmed[contact.did] || 0) : (givenToMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<span <fa icon="plus" class="fa-fw" />
v-if="givenToMeDescriptions[contact.did]" </button>
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="tooltip" class="text-sm uppercase bg-slate-500 text-white px-2 py-1.5 rounded-md"
title="See all given activity"
> >
<fa icon="file-lines" class="text-slate-600 fa-fw ml-1" /> <fa icon="file-lines" class="fa-fw" />
<span class="tooltiptext-left">See All Given Activity</span>
</router-link> </router-link>
</div> </div>
</div> </div>
@@ -315,12 +317,19 @@ export default class ContactsView extends Vue {
resp.status, resp.status,
resp.data, resp.data,
); );
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = {
"Got an error retrieving your " + group: "alert",
resp.config.url.includes("recipientDid") type: "danger",
? "received" title: "Error With Server",
: "given" + " time from the server."; text:
"Got an error retrieving your " +
resp.config.url.includes("recipientDid")
? "received"
: "given" + " time from the server.",
},
-1,
);
} }
}; };
@@ -368,8 +377,15 @@ export default class ContactsView extends Vue {
this.givenToMeConfirmed = givenToMeConfirmed; this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = givenToMeUnconfirmed; this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} catch (error) { } catch (error) {
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = error as string; {
group: "alert",
type: "danger",
title: "Error With Server",
text: error as string,
},
-1,
);
} }
} }
@@ -462,18 +478,32 @@ 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 });
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.alertMessage = message; this.$notify(
{
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.alertTitle = "Registration Success"; this.$notify(
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.";
@@ -488,8 +518,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.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = userMessage; {
group: "alert",
type: "danger",
title: "Error With Server",
text: userMessage,
},
-1,
);
} }
} }
} }
@@ -510,17 +547,39 @@ 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.alertMessage = resp.data.error?.message; this.$notify(
{
group: "alert",
type: "danger",
title: "Error With Server",
text: resp.data.error?.message,
},
-1,
);
} else { } else {
this.alertMessage = "Bad server response of " + resp.status; this.$notify(
{
group: "alert",
type: "danger",
title: "Error With Server",
text: "Bad server response of " + resp.status,
},
-1,
);
} }
} }
} catch (err) { } catch (err) {
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = err as string; {
group: "alert",
type: "danger",
title: "Error With Server",
text: err as string,
},
-1,
);
} }
} }
@@ -537,23 +596,52 @@ 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.alertTitle = "Refreshed"; this.$notify(
this.alertMessage = {
this.nameForContact(contact, true) + group: "alert",
" can " + type: "toast",
(visibility ? "" : "not ") + title: "Refreshed",
"see your activity."; text:
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.alertMessage = resp.data.error?.message; this.$notify(
{
group: "alert",
type: "danger",
title: "Error With Server",
text: resp.data.error?.message,
},
-1,
);
} else { } else {
this.alertMessage = "Bad server response of " + resp.status; this.$notify(
{
group: "alert",
type: "danger",
title: "Error With Server",
text: "Bad server response of " + resp.status,
},
-1,
);
} }
} }
} catch (err) { } catch (err) {
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = err as string; {
group: "alert",
type: "danger",
title: "Error With Server",
text: err as string,
},
-1,
);
} }
} }
@@ -592,15 +680,35 @@ export default class ContactsView extends Vue {
} }
} }
if (!this.isNumeric(this.hourInput)) { if (!this.isNumeric(this.hourInput)) {
this.alertTitle = "Input Error"; this.$notify(
this.alertMessage = {
"This is not a valid number of hours: " + this.hourInput; group: "alert",
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.alertTitle = "Input Error"; this.$notify(
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.alertTitle = "Status Error"; this.$notify(
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;
@@ -684,8 +792,15 @@ 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 });
if (resp.data?.success?.handleId) { if (resp.data?.success?.handleId) {
this.alertTitle = "Done"; this.$notify(
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);
@@ -710,8 +825,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.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = userMessage; {
group: "alert",
type: "danger",
title: "Error With Server",
text: userMessage,
},
-1,
);
} }
} }
} }

View File

@@ -90,10 +90,12 @@
</div> </div>
<div class="grow"> <div class="grow">
<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> <fa icon="user" class="fa-fw text-slate-400"></fa>
{{ project.name }} {{
didInfo(project.issuerDid, activeDid, allMyDids, allContacts)
}}
</div> </div>
</div> </div>
</a> </a>
@@ -111,8 +113,10 @@
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 InfiniteScroll from "@/components/InfiniteScroll";
@@ -122,6 +126,8 @@ import InfiniteScroll from "@/components/InfiniteScroll";
}) })
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 = "";
@@ -133,11 +139,20 @@ export default class DiscoverView extends Vue {
remoteCount = 0; remoteCount = 0;
isLoading = false; 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(); this.searchLocal();
} }
@@ -166,7 +181,6 @@ export default class DiscoverView extends Vue {
public async search(beforeId?: string) { public async search(beforeId?: string) {
let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms); let queryParams = "claimContents=" + encodeURIComponent(this.searchTerms);
console.log(beforeId);
if (beforeId) { if (beforeId) {
queryParams = queryParams + `&beforeId=${beforeId}`; queryParams = queryParams + `&beforeId=${beforeId}`;
} }
@@ -186,6 +200,16 @@ 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;
} }
@@ -194,9 +218,8 @@ export default class DiscoverView extends Vue {
const plans: ProjectData[] = results.data; const plans: ProjectData[] = results.data;
if (plans) { if (plans) {
for (const plan of plans) { for (const plan of plans) {
const { name, description, handleId = plan.handleId, rowid } = plan; const { name, description, handleId, rowid, issuerDid } = plan;
console.log("here"); this.projects.push({ name, description, handleId, rowid, issuerDid });
this.projects.push({ name, description, handleId, rowid });
} }
this.remoteCount = this.projects.length; this.remoteCount = this.projects.length;
} else { } else {
@@ -204,9 +227,15 @@ export default class DiscoverView extends Vue {
} }
} catch (e) { } catch (e) {
console.log("Error with feed load:", e); console.log("Error with feed load:", e);
this.alertMessage = this.$notify(
e.userMessage || "There was an error retrieving projects."; {
this.alertTitle = "Error"; group: "alert",
type: "danger",
title: "Error",
text: e.userMessage || "There was a problem retrieving projects.",
},
-1,
);
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }
@@ -240,6 +269,16 @@ 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();
} }
@@ -263,9 +302,15 @@ export default class DiscoverView extends Vue {
} }
} catch (e) { } catch (e) {
console.log("Error with feed load:", e); console.log("Error with feed load:", e);
this.alertMessage = this.$notify(
e.userMessage || "There was an error retrieving projects."; {
this.alertTitle = "Error"; group: "alert",
type: "danger",
title: "Error",
text: e.userMessage || "There was a problem retrieving projects.",
},
-1,
);
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }
@@ -278,8 +323,6 @@ export default class DiscoverView extends Vue {
async loadMoreData(payload: boolean) { async loadMoreData(payload: boolean) {
if (this.projects.length > 0 && payload) { if (this.projects.length > 0 && payload) {
const latestProject = this.projects[this.projects.length - 1]; const latestProject = this.projects[this.projects.length - 1];
console.log("rowid", latestProject, payload);
console.log(Object.keys(latestProject));
if (this.isLocalActive) { if (this.isLocalActive) {
this.searchLocal(latestProject["rowid"]); this.searchLocal(latestProject["rowid"]);
} else if (this.isRemoteActive) { } else if (this.isRemoteActive) {

View File

@@ -7,22 +7,128 @@
</h1> </h1>
<div class="mb-8"> <div class="mb-8">
<h1 class="text-2xl">Quick Action</h1> <h2 class="text-xl font-bold mb-4">Notiwind Alert Test Suite</h2>
<p>Choose a contact to whom to show appreciation:</p>
<!-- similar contact selection code is in multiple places --> <button
<div class="px-4"> @click="
<button this.$notify(
{
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"
> >
{{ contact.name || "(no name)" }} <div class="mb-1">
</button> <fa icon="user" class="fa-fw fa-xl text-slate-400"></fa>
<span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span> </div>
<button @click="openDialog()" class="text-blue-500"> <h3
someone not specified class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
</button> >
{{ 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>
@@ -33,29 +139,27 @@
> >
</GiftedDialog> </GiftedDialog>
<div> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h1 class="text-2xl">Latest Activity</h1> <h2 class="text-xl font-bold mb-4">Latest Activity</h2>
<span :class="{ hidden: isHiddenSpinner }"> <div :class="{ hidden: isHiddenSpinner }">
<fa icon="spinner" class="fa-spin-pulse"></fa> <p class="text-slate-500 text-center italic mt-4 mb-4">
Loading&hellip; <fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</span> </p>
<ul> </div>
<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 py-2 mb-2 font-bold uppercase text-sm" class="border-b border-dashed border-slate-400 text-orange-400 pb-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 <fa icon="gift" class="pt-1 pr-2 text-slate-500"></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>
@@ -73,11 +177,9 @@
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";
@@ -87,8 +189,8 @@ import QuickNav from "@/components/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 = [];
@@ -97,13 +199,11 @@ export default class HomeView extends Vue {
isHiddenSpinner = true; isHiddenSpinner = true;
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
async beforeCreate() { async beforeCreate() {
accountsDB.open(); await accountsDB.open();
this.accounts = accountsDB.accounts; this.numAccounts = await accountsDB.accounts.count();
this.numAccounts = await this.accounts.count();
} }
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
@@ -134,7 +234,9 @@ export default class HomeView extends Vue {
async created() { async created() {
try { try {
await accountsDB.open(); await accountsDB.open();
this.allAccounts = await accountsDB.accounts.toArray(); const 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 || "";
@@ -143,10 +245,17 @@ export default class HomeView extends Vue {
this.feedLastViewedId = settings?.lastViewedClaimId; this.feedLastViewedId = settings?.lastViewedClaimId;
this.updateAllFeed(); this.updateAllFeed();
} catch (err) { } catch (err) {
this.alertTitle = "Error"; this.$notify(
this.alertMessage = {
err.userMessage || group: "alert",
"There was an error retrieving the latest sweet, sweet action."; type: "danger",
title: "Error",
text:
err.userMessage ||
"There was an error retrieving the latest sweet, sweet action.",
},
-1,
);
} }
} }
@@ -195,9 +304,15 @@ 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.alertMessage = this.$notify(
e.userMessage || "There was an error retrieving feed data."; {
this.alertTitle = "Error"; group: "alert",
type: "danger",
title: "Export Error",
text: e.userMessage || "There was an error retrieving feed data.",
},
-1,
);
}); });
this.isHiddenSpinner = true; this.isHiddenSpinner = true;
@@ -231,14 +346,13 @@ 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 || giveRecord.issuer; claim.agent?.identifier || claim.agent?.did || giveRecord.issuer;
const giverInfo = didInfo( const giverInfo = didInfo(
giverDid, giverDid,
this.activeDid, this.activeDid,
this.allAccounts, this.allMyDids,
this.allContacts, this.allContacts,
); );
const gaveAmount = claim.object?.amountOfThisGood const gaveAmount = claim.object?.amountOfThisGood
@@ -251,7 +365,7 @@ export default class HomeView extends Vue {
didInfo( didInfo(
gaveRecipientId, gaveRecipientId,
this.activeDid, this.activeDid,
this.allAccounts, this.allMyDids,
this.allContacts, this.allContacts,
) )
: ""; : "";
@@ -289,17 +403,27 @@ 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.setAlert( this.$notify(
"Error", {
"You must select an identity before you can record a give.", group: "alert",
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.setAlert( this.$notify(
"Error", {
"You must enter a description or some number of hours.", group: "alert",
type: "danger",
title: "Error",
text: "You must enter a description or some number of hours.",
},
-1,
); );
return; return;
} }
@@ -316,21 +440,41 @@ export default class HomeView extends Vue {
hours, hours,
); );
if (isGiveCreationError(result)) { if (this.isGiveCreationError(result)) {
const errorMessage = getGiveCreationErrorMessage(result); const errorMessage = this.getGiveCreationErrorMessage(result);
console.log("Error with give result:", result); console.log("Error with give result:", result);
this.setAlert( this.$notify(
"Error", {
errorMessage || "There was an error recording the give.", group: "alert",
type: "danger",
title: "Error",
text: errorMessage || "There was an error recording the give.",
},
-1,
); );
} else { } else {
this.setAlert("Success", "That gift was recorded."); this.$notify(
{
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.setAlert( this.$notify(
"Error", {
getGiveErrorMessage(error) || "There was an error recording the give.", group: "alert",
type: "danger",
title: "Error",
text:
this.getGiveErrorMessage(error) ||
"There was an error recording the give.",
},
-1,
); );
} }
} }

View File

@@ -133,13 +133,19 @@ export default class IdentitySwitcherView extends Vue {
this.limitsMessage = "No identity."; this.limitsMessage = "No identity.";
this.loadingLimits = false; this.loadingLimits = false;
} else { } else {
this.alertMessage = this.$notify(
"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";
} }
} }
} }

View File

@@ -13,38 +13,37 @@
[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"
/>
<div class="mt-8"> <input
<button type="text"
type="button" placeholder="First Name"
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 rounded border border-slate-400 mb-4 px-3 py-2"
@click="onClickSaveChanges()" v-model="firstName"
> />
Save Changes <input
</button> type="text"
<!-- SHOW ME instead while processing saving changes --> placeholder="Last Name"
<button class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
type="button" v-model="lastName"
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">
Cancel <button
</button> type="button"
</div> class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
</form> @click="onClickSaveChanges()"
>
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,47 +16,44 @@
</div> </div>
<!-- Project Details --> <!-- Project Details -->
<form>
<select <select class="block w-full rounded border border-slate-400 mb-4 px-3 py-2">
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" <option disabled>Choose a commitment type</option>
<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
> >
<option disabled>Choose a commitment type</option> </div>
<option selected>Time</option>
<option>Cryptocurrency</option>
<option>Money</option>
</select>
<!-- Time amount --> <!-- Crypto 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>
<!-- Crypto amount --> <!-- Money amount -->
<!-- Money amount --> <div class="mt-8">
<input
<div class="mt-8"> type="submit"
<input class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
type="submit" value="Commit"
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="Commit" <button
/> type="button"
<button class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
type="button" >
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" Maybe Later
> </button>
Maybe Later </div>
</button>
</div>
</form>
</section> </section>
</template> </template>

View File

@@ -76,7 +76,6 @@ 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";
@@ -92,15 +91,13 @@ export default class NewEditProjectView extends Vue {
projectName = ""; projectName = "";
description = ""; description = "";
errorMessage = ""; errorMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
async beforeCreate() { async beforeCreate() {
accountsDB.open(); await accountsDB.open();
this.accounts = accountsDB.accounts; this.numAccounts = await accountsDB.accounts.count();
this.numAccounts = await this.accounts.count();
} }
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
@@ -248,20 +245,41 @@ 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.alertMessage = userMessage; this.$notify(
{
group: "alert",
type: "danger",
title: "User Message",
text: userMessage,
},
-1,
);
} else { } else {
this.alertTitle = "Server Message"; this.$notify(
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.alertTitle = "Claim Error"; this.$notify(
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;

View File

@@ -50,7 +50,6 @@ 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] =
@@ -58,6 +57,8 @@ 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

@@ -93,7 +93,7 @@
<h1 class="text-xl">Given to this Project</h1> <h1 class="text-xl">Given to this Project</h1>
</div> </div>
<div> <div>
<h1 class="text-xl">... and from this Project</h1> <h1 class="text-xl">... and paid forward from this Project</h1>
</div> </div>
</div> </div>
<div class="flex justify-around"> <div class="flex justify-around">
@@ -104,11 +104,16 @@
<div class="flex gap-2"> <div class="flex gap-2">
<fa icon="user" class="fa-fw text-slate-400"></fa> <fa icon="user" class="fa-fw text-slate-400"></fa>
<span>{{ <span>{{
didInfo(give.agentDid, activeDid, accounts, allContacts) didInfo(give.agentDid, activeDid, allMyDids, allContacts)
}}</span> }}</span>
</div> </div>
<div class="flex gap-2" v-if="give.amount"> <div class="flex gap-2" v-if="give.amount">
<fa icon="coins" class="fa-fw text-slate-400"></fa> <fa
icon="clock"
v-if="give.unit === 'HUR'"
class="fa-fw text-slate-400"
></fa>
<fa icon="coins" v-else class="fa-fw text-slate-400"></fa>
<span>{{ give.amount }}</span> <span>{{ give.amount }}</span>
</div> </div>
<div class="flex gap-2" v-if="give.description"> <div class="flex gap-2" v-if="give.description">
@@ -126,11 +131,16 @@
<div class="flex gap-2"> <div class="flex gap-2">
<fa icon="user" class="fa-fw text-slate-400"></fa> <fa icon="user" class="fa-fw text-slate-400"></fa>
<span>{{ <span>{{
didInfo(give.agentDid, activeDid, accounts, allContacts) didInfo(give.agentDid, activeDid, allMyDids, allContacts)
}}</span> }}</span>
</div> </div>
<div class="flex gap-2" v-if="give.amount"> <div class="flex gap-2" v-if="give.amount">
<fa icon="coins" class="fa-fw text-slate-400"></fa> <fa
icon="clock"
v-if="give.unit === 'HUR'"
class="fa-fw text-slate-400"
></fa>
<fa icon="coins" v-else class="fa-fw text-slate-400"></fa>
<span>{{ give.amount }}</span> <span>{{ give.amount }}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -163,7 +173,6 @@ 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";
@@ -179,10 +188,10 @@ import QuickNav from "@/components/QuickNav";
components: { GiftedDialog, AlertMessage, QuickNav }, 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 = "";
@@ -191,18 +200,11 @@ export default class ProjectViewView extends Vue {
givesByThis: Array<GiveServerRecord> = []; givesByThis: Array<GiveServerRecord> = [];
name = ""; name = "";
issuer = ""; 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);
@@ -210,9 +212,11 @@ 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();
this.accounts = accountsDB.accounts; await accountsDB.open();
const accountsArr = await this.accounts?.toArray(); const accounts = accountsDB.accounts;
const account = accountsArr.find((acc) => acc.did === this.activeDid); const accountsArr = await accounts?.toArray();
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);
} }
@@ -251,8 +255,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, identities, contacts) { didInfo(did, activeDid, dids, contacts) {
return didInfo(did, activeDid, identities, contacts); return didInfo(did, activeDid, dids, contacts);
} }
expandText() { expandText() {
@@ -291,16 +295,38 @@ export default class ProjectViewView extends Vue {
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.alertMessage = "That project does not exist."; this.$notify(
{
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.alertMessage = "That project does not exist."; this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "That project does not exist.",
},
-1,
);
} else { } else {
this.alertMessage = this.$notify(
"Something went wrong retrieving that project." + {
" See logs for more info."; group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong retrieving that project. See logs for more info.",
},
-1,
);
console.error("Error retrieving project:", serverError.message); console.error("Error retrieving project:", serverError.message);
} }
} }
@@ -314,12 +340,27 @@ 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.alertMessage = "Failed to retrieve gives to this project."; this.$notify(
{
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; const serverError = error as AxiosError;
this.alertMessage = this.$notify(
"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( console.error(
"Error retrieving gives to this project:", "Error retrieving gives to this project:",
serverError.message, serverError.message,
@@ -335,11 +376,27 @@ 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.alertMessage = "Failed to retrieve gives by this project."; this.$notify(
{
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; const serverError = error as AxiosError;
this.alertMessage = "Something went wrong retrieving gives by project."; this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong retrieving gives by project.",
},
-1,
);
console.error( console.error(
"Error retrieving gives by this project:", "Error retrieving gives by this project:",
serverError.message, serverError.message,
@@ -370,16 +427,28 @@ export default class ProjectViewView extends Vue {
*/ */
async recordGive(giverDid, description, hours) { async recordGive(giverDid, description, hours) {
if (!this.activeDid) { if (!this.activeDid) {
this.alertTitle = "Error"; this.$notify(
this.alertMessage = {
"You must select an identity before you can record a give."; group: "alert",
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.alertTitle = "Error"; this.$notify(
this.alertMessage = {
"You must enter a description or some number of hours."; group: "alert",
type: "danger",
title: "Error",
text: "You must enter a description or some number of hours.",
},
-1,
);
return; return;
} }
@@ -398,21 +467,42 @@ 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.alertTitle = "Error"; this.$notify(
this.alertMessage = {
result.data?.error?.message || group: "alert",
"There was an error recording the give."; type: "danger",
title: "Error",
text:
result.data?.error?.message ||
"There was an error recording the give.",
},
-1,
);
} else { } else {
this.alertTitle = "Success"; this.$notify(
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.alertTitle = "Error"; this.$notify(
this.alertMessage = {
e.userMessage || group: "alert",
e.response?.data?.error?.message || type: "danger",
"There was an error recording the give."; title: "Error",
text:
e.userMessage ||
e.response?.data?.error?.message ||
"There was an error recording the give.",
},
-1,
);
} }
} }
} }

View File

@@ -7,7 +7,8 @@
</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…"
@@ -18,7 +19,7 @@
> >
<fa icon="magnifying-glass" class="fa-fw"></fa> <fa icon="magnifying-glass" class="fa-fw"></fa>
</button> </button>
</form> </div>
<!-- New Project --> <!-- New Project -->
<button <button
@@ -38,7 +39,7 @@
<!-- Results List --> <!-- Results List -->
<InfiniteScroll @reached-bottom="loadMoreData"> <InfiniteScroll @reached-bottom="loadMoreData">
<ul> <ul class="border-t border-slate-300">
<li <li
class="border-b border-slate-300" class="border-b border-slate-300"
v-for="project in projects" v-for="project in projects"
@@ -75,7 +76,6 @@
<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";
@@ -93,13 +93,11 @@ export default class ProjectsView extends Vue {
isLoading = false; isLoading = false;
alertTitle = ""; alertTitle = "";
alertMessage = ""; alertMessage = "";
accounts: AccountsSchema;
numAccounts = 0; numAccounts = 0;
async beforeCreate() { async beforeCreate() {
accountsDB.open(); await accountsDB.open();
this.accounts = accountsDB.accounts; this.numAccounts = await accountsDB.accounts.count();
this.numAccounts = await this.accounts.count();
} }
/** /**
@@ -128,8 +126,15 @@ 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.alertTitle = "Error"; this.$notify(
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;
} }
@@ -198,8 +203,15 @@ 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.alertTitle = "Error"; this.$notify(
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;
@@ -207,8 +219,15 @@ export default class ProjectsView extends Vue {
} }
} catch (err) { } catch (err) {
console.log("Error initializing:", err); console.log("Error initializing:", err);
this.alertTitle = "Error"; this.$notify(
this.alertMessage = "Something went wrong loading your projects."; {
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong loading your projects.",
},
-1,
);
} }
} }

View File

@@ -72,8 +72,15 @@ 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.alertTitle = "Error Loading Account"; this.$notify(
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

@@ -62,8 +62,15 @@ export default class StatisticsView extends Vue {
this.world = newWorld; this.world = newWorld;
} catch (err) { } catch (err) {
console.log(err); console.log(err);
this.alertTitle = "Mounting error"; this.$notify(
this.alertMessage = err.message; {
group: "alert",
type: "danger",
title: "Mounting Error",
text: err.message,
},
-1,
);
} }
} }