Browse Source

Merge branch 'master' into update-api-server

kb/add-usage-guide
anomalist 1 year ago
parent
commit
ac4c92d8e8
  1. 40
      package-lock.json
  2. 2
      package.json
  3. 7
      project.task.yaml
  4. 127
      src/App.vue
  5. 19
      src/components/EntityIcon.vue
  6. 11
      src/libs/endorserServer.ts
  7. 6
      src/main.ts
  8. 10
      src/router/index.ts
  9. 46
      src/views/AccountViewView.vue
  10. 79
      src/views/ContactAmountsView.vue
  11. 88
      src/views/ContactGiftingView.vue
  12. 10
      src/views/ContactQRScanShowView.vue
  13. 260
      src/views/ContactsView.vue
  14. 84
      src/views/DiscoverView.vue
  15. 16
      src/views/HelpView.vue
  16. 185
      src/views/HomeView.vue
  17. 12
      src/views/IdentitySwitcherView.vue
  18. 33
      src/views/NewEditProjectView.vue
  19. 335
      src/views/ProjectViewView.vue
  20. 45
      src/views/ProjectsView.vue
  21. 41
      src/views/SeedBackupView.vue
  22. 11
      src/views/StatisticsView.vue

40
package-lock.json

@ -33,11 +33,13 @@
"ethereum-cryptography": "^2.0.0", "ethereum-cryptography": "^2.0.0",
"ethereumjs-util": "^7.1.5", "ethereumjs-util": "^7.1.5",
"ethr-did-resolver": "^8.0.0", "ethr-did-resolver": "^8.0.0",
"jdenticon": "^3.2.0",
"js-generate-password": "^0.1.9", "js-generate-password": "^0.1.9",
"localstorage-slim": "^2.4.0", "localstorage-slim": "^2.4.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"merkletreejs": "^0.3.10", "merkletreejs": "^0.3.10",
"moment": "^2.29.4", "moment": "^2.29.4",
"notiwind": "^2.0.2",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"pina": "^0.20.2204228", "pina": "^0.20.2204228",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
@ -12048,6 +12050,14 @@
"resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz", "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz",
"integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==" "integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w=="
}, },
"node_modules/canvas-renderer": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/canvas-renderer/-/canvas-renderer-2.2.1.tgz",
"integrity": "sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/case-sensitive-paths-webpack-plugin": { "node_modules/case-sensitive-paths-webpack-plugin": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", "resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
@ -17797,6 +17807,20 @@
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true "dev": true
}, },
"node_modules/jdenticon": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jdenticon/-/jdenticon-3.2.0.tgz",
"integrity": "sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==",
"dependencies": {
"canvas-renderer": "~2.2.0"
},
"bin": {
"jdenticon": "bin/jdenticon.js"
},
"engines": {
"node": ">=6.4.0"
}
},
"node_modules/jest-environment-node": { "node_modules/jest-environment-node": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz",
@ -20928,6 +20952,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 +21304,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",

2
package.json

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

7
project.task.yaml

@ -1,6 +1,8 @@
tasks: tasks:
- 01 add a location for a project via map pin - .2 bug - on contacts view, click on "to" & "from" and nothing happens
- 01 add a location for a project via map pin :
- add with a "location" field containing this: { "geo":{ "@type":"GeoCoordinates", "latitude":40.883944, "longitude":-111.884787 } }
- 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 Replace Gifted/Give in ContactsView with GiftedDialog assignee:jose - 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:jose
- 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.
@ -24,6 +26,7 @@ tasks:
- 24 Move to Vite - 24 Move to Vite
- .5 add link to further project / people when a project pays ahead
- .5 add project ID to the URL, to make a project publicly-accessible - .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 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 - .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
@ -32,6 +35,8 @@ tasks:
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist - .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 static icon to the right on project page (Matthew - I've made "Rotary" into issuer?)
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent - .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
- .2 move 'switch identity' to the advanced section
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.

127
src/App.vue

@ -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>

19
src/components/EntityIcon.vue

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

11
src/libs/endorserServer.ts

@ -82,10 +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, allMyDids, contacts) { export function didInfo(
const myId: string | undefined = R.find(R.identity, allMyDids); did: string,
activeDid: string,
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) {

6
src/main.ts

@ -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";
@ -16,6 +17,7 @@ import {
faChevronRight, faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo,
faCircleQuestion, faCircleQuestion,
faCircleUser, faCircleUser,
faClock, faClock,
@ -45,6 +47,7 @@ import {
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faTrashCan, faTrashCan,
faTriangleExclamation,
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
@ -57,6 +60,7 @@ library.add(
faChevronRight, faChevronRight,
faCircle, faCircle,
faCircleCheck, faCircleCheck,
faCircleInfo,
faCircleQuestion, faCircleQuestion,
faCircleUser, faCircleUser,
faClock, faClock,
@ -86,6 +90,7 @@ library.add(
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faTrashCan, faTrashCan,
faTriangleExclamation,
faUser, faUser,
faUsers, faUsers,
faXmark, faXmark,
@ -98,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");

10
src/router/index.ts

@ -182,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;

46
src/views/AccountViewView.vue

@ -400,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";
} }
} }
} }
@ -418,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";
} }
} }
@ -440,11 +452,25 @@ 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);
} }
} }

79
src/views/ContactAmountsView.vue

@ -4,6 +4,11 @@
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Given with {{ contact?.name }} Given with {{ contact?.name }}
</h1> </h1>
<div class="flex justify-around">
<span />
<span class="justify-around">(Only 50 most recent)</span>
<span />
</div>
<!-- Results List --> <!-- Results List -->
<div> <div>
@ -146,10 +151,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,
);
} }
} }
@ -163,7 +175,7 @@ export default class ContactsView extends Vue {
encodeURIComponent(identity.did) + encodeURIComponent(identity.did) +
"&recipientDid=" + "&recipientDid=" +
encodeURIComponent(contact.did); encodeURIComponent(contact.did);
const headers = 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 });
if (resp.status === 200) { if (resp.status === 200) {
result = resp.data.data; result = resp.data.data;
@ -173,9 +185,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 =
@ -194,9 +212,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(
@ -206,8 +230,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,
);
} }
} }
@ -276,15 +307,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>

88
src/views/ContactGiftingView.vue

@ -24,8 +24,12 @@
<ul class="border-t border-slate-300"> <ul class="border-t border-slate-300">
<li class="border-b border-slate-300 py-3"> <li class="border-b border-slate-300 py-3">
<h2 class="text-base flex gap-4 items-center"> <h2 class="text-base flex gap-4 items-center">
<span class="grow italic" <span class="grow italic text-slate-500"
><fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa> ><EntityIcon
entityId="Anonymous"
:iconSize="32"
class="opacity-50 inline-block align-middle border border-dashed border-slate-400 bg-slate-200 rounded-md mr-1"
></EntityIcon>
Anonymous Anonymous
</span> </span>
<span class="text-right"> <span class="text-right">
@ -46,7 +50,11 @@
> >
<h2 class="text-base flex gap-4 items-center"> <h2 class="text-base flex gap-4 items-center">
<span class="grow font-semibold" <span class="grow font-semibold"
><fa icon="user" class="fa-fw fa-xl text-slate-400"></fa> ><EntityIcon
:entityId="contact.did"
:iconSize="32"
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
></EntityIcon>
{{ contact.name || "(no name)" }} {{ contact.name || "(no name)" }}
</span> </span>
<span class="text-right"> <span class="text-right">
@ -87,9 +95,10 @@ import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { GiftedDialog, AlertMessage, QuickNav }, components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon },
}) })
export default class HomeView extends Vue { export default class HomeView extends Vue {
activeDid = ""; activeDid = "";
@ -145,10 +154,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,
);
} }
} }
@ -197,17 +213,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;
} }
@ -227,18 +253,38 @@ export default class HomeView extends Vue {
if (isGiveCreationError(result)) { if (isGiveCreationError(result)) {
const errorMessage = getGiveCreationErrorMessage(result); const errorMessage = 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:
getGiveErrorMessage(error) ||
"There was an error recording the give.",
},
-1,
); );
} }
} }

10
src/views/ContactQRScanShowView.vue

@ -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;

260
src/views/ContactsView.vue

@ -66,18 +66,27 @@
: "Unconfirmed" : "Unconfirmed"
}} }}
</button> </button>
<br />
(Only hours shown)
<br />
(Only recent shown)
</div> </div>
</div> </div>
<!-- Results List --> <!-- Results List -->
<ul v-if="contacts.length > 0" class="border-t border-slate-300"> <ul v-if="contacts.length > 0" class="border-t border-slate-300">
<li <li
class="border-b border-slate-300 py-4" class="border-b border-slate-300 pt-2.5 pb-4"
v-for="contact in contacts" v-for="contact in contacts"
:key="contact.did" :key="contact.did"
> >
<div class="grow overflow-hidden"> <div class="grow overflow-hidden">
<h2 class="text-base font-semibold"> <h2 class="text-base font-semibold">
<EntityIcon
:entityId="contact.did"
:iconSize="24"
class="inline-block align-text-bottom border border-slate-300 rounded"
></EntityIcon>
{{ contact.name || "(no name)" }} {{ contact.name || "(no name)" }}
</h2> </h2>
<div class="text-sm truncate">{{ contact.did }}</div> <div class="text-sm truncate">{{ contact.did }}</div>
@ -135,9 +144,12 @@
<fa icon="trash-can" class="fa-fw" /> <fa icon="trash-can" class="fa-fw" />
</button> </button>
<div v-if="!showGiveNumbers" class="ml-auto flex gap-1.5"> <div
v-if="showGiveNumbers && contact.did != activeDid"
class="ml-auto flex gap-1.5"
>
<button <button
class="text-sm uppercase bg-blue-600 text-white px-2 py-1.5 rounded-md" class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-l-md"
@click="onClickAddGive(activeDid, contact.did)" @click="onClickAddGive(activeDid, contact.did)"
title="givenByMeDescriptions[contact.did]" title="givenByMeDescriptions[contact.did]"
> >
@ -152,11 +164,11 @@
: (givenByMeUnconfirmed[contact.did] || 0) : (givenByMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<fa icon="plus" class="fa-fw" /> <fa icon="plus" />
</button> </button>
<button <button
class="text-sm uppercase bg-blue-600 text-white px-2 py-1.5 rounded-md" class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded-r-md -ml-1.5 border-l border-blue-400"
@click="onClickAddGive(contact.did, activeDid)" @click="onClickAddGive(contact.did, activeDid)"
title="givenToMeDescriptions[contact.did]" title="givenToMeDescriptions[contact.did]"
> >
@ -171,7 +183,7 @@
: (givenToMeUnconfirmed[contact.did] || 0) : (givenToMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<fa icon="plus" class="fa-fw" /> <fa icon="plus" />
</button> </button>
<router-link <router-link
@ -214,12 +226,13 @@ import {
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer; const Buffer = require("buffer/").Buffer;
@Component({ @Component({
components: { AlertMessage, QuickNav }, components: { AlertMessage, QuickNav, EntityIcon },
}) })
export default class ContactsView extends Vue { export default class ContactsView extends Vue {
activeDid = ""; activeDid = "";
@ -294,20 +307,27 @@ export default class ContactsView extends Vue {
} }
async loadGives() { async loadGives() {
const handleResponse = (resp, descriptions, confirmed, unconfirmed) => { const handleResponse = (
resp,
descriptions,
confirmed,
unconfirmed,
useRecipient,
) => {
if (resp.status === 200) { if (resp.status === 200) {
const allData = resp.data.data; const allData = resp.data.data;
for (const give of allData) { for (const give of allData) {
const otherDid = useRecipient ? give.recipientDid : give.agentDid;
if (give.unit === "HUR") { if (give.unit === "HUR") {
if (give.amountConfirmed) { if (give.amountConfirmed) {
const prevAmount = confirmed[give.agentDid] || 0; const prevAmount = confirmed[otherDid] || 0;
confirmed[give.agentDid] = prevAmount + give.amount; confirmed[otherDid] = prevAmount + give.amount;
} else { } else {
const prevAmount = unconfirmed[give.agentDid] || 0; const prevAmount = unconfirmed[otherDid] || 0;
unconfirmed[give.agentDid] = prevAmount + give.amount; unconfirmed[otherDid] = prevAmount + give.amount;
} }
if (!descriptions[give.agentDid] && give.description) { if (!descriptions[otherDid] && give.description) {
descriptions[give.agentDid] = give.description; descriptions[otherDid] = give.description;
} }
} }
} }
@ -317,27 +337,32 @@ 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,
);
} }
}; };
try { try {
const { headers, identity } = await this.getHeadersAndIdentity( const { headers } = await this.getHeadersAndIdentity(this.activeDid);
this.activeDid,
);
const givenByUrl = const givenByUrl =
this.apiServer + this.apiServer +
"/api/v2/report/gives?agentDid=" + "/api/v2/report/gives?agentDid=" +
encodeURIComponent(identity.did); encodeURIComponent(this.activeDid);
const givenToUrl = const givenToUrl =
this.apiServer + this.apiServer +
"/api/v2/report/gives?recipientDid=" + "/api/v2/report/gives?recipientDid=" +
encodeURIComponent(identity.did); encodeURIComponent(this.activeDid);
const [givenByMeResp, givenToMeResp] = await Promise.all([ const [givenByMeResp, givenToMeResp] = await Promise.all([
this.axios.get(givenByUrl, { headers }), this.axios.get(givenByUrl, { headers }),
@ -352,6 +377,7 @@ export default class ContactsView extends Vue {
givenByMeDescriptions, givenByMeDescriptions,
givenByMeConfirmed, givenByMeConfirmed,
givenByMeUnconfirmed, givenByMeUnconfirmed,
true,
); );
this.givenByMeDescriptions = givenByMeDescriptions; this.givenByMeDescriptions = givenByMeDescriptions;
this.givenByMeConfirmed = givenByMeConfirmed; this.givenByMeConfirmed = givenByMeConfirmed;
@ -365,13 +391,21 @@ export default class ContactsView extends Vue {
givenToMeDescriptions, givenToMeDescriptions,
givenToMeConfirmed, givenToMeConfirmed,
givenToMeUnconfirmed, givenToMeUnconfirmed,
false,
); );
this.givenToMeDescriptions = givenToMeDescriptions; this.givenToMeDescriptions = givenToMeDescriptions;
this.givenToMeConfirmed = givenToMeConfirmed; this.givenToMeConfirmed = givenToMeConfirmed;
this.givenToMeUnconfirmed = givenToMeUnconfirmed; this.givenToMeUnconfirmed = givenToMeUnconfirmed;
} catch (error) { } catch (error) {
this.alertTitle = "Error With Server"; this.$notify(
this.alertMessage = error as string; {
group: "alert",
type: "danger",
title: "Error With Server",
text: error as string,
},
-1,
);
} }
} }
@ -464,18 +498,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.";
@ -490,8 +538,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,
);
} }
} }
} }
@ -512,17 +567,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,
);
} }
} }
@ -539,23 +616,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,
);
} }
} }
@ -594,15 +700,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;
@ -686,8 +812,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);
@ -712,8 +845,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,
);
} }
} }
} }

84
src/views/DiscoverView.vue

@ -83,17 +83,21 @@
class="block py-4 flex gap-4" class="block py-4 flex gap-4"
> >
<div class="w-12"> <div class="w-12">
<img <EntityIcon
src="https://picsum.photos/200/200?random=1" :entityId="project.handleId"
class="w-full rounded" :iconSize="48"
/> class="block border border-slate-300 rounded-md"
></EntityIcon>
</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>
<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,17 +115,22 @@
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";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { AlertMessage, QuickNav, InfiniteScroll }, components: { AlertMessage, QuickNav, InfiniteScroll, EntityIcon },
}) })
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 +142,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 +184,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 +203,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 +221,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 +230,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 +272,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 +305,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 +326,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) {

16
src/views/HelpView.vue

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

185
src/views/HomeView.vue

@ -6,6 +6,94 @@
Time Safari Time Safari
</h1> </h1>
<div class="mb-8">
<h2 class="text-xl font-bold mb-4">Notiwind Alert Test Suite</h2>
<button
@click="
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"> <div class="mb-8">
<h2 class="text-xl font-bold">Quick Action</h2> <h2 class="text-xl font-bold">Quick Action</h2>
<p class="mb-4">Show appreciation to a contact:</p> <p class="mb-4">Show appreciation to a contact:</p>
@ -16,9 +104,11 @@
:key="contact.did" :key="contact.did"
@click="openDialog(contact)" @click="openDialog(contact)"
> >
<div class="mb-1"> <EntityIcon
<fa icon="user" class="fa-fw fa-xl text-slate-400"></fa> :entityId="contact.did"
</div> :iconSize="64"
class="mx-auto border border-slate-300 rounded-md mb-1"
></EntityIcon>
<h3 <h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden" class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
> >
@ -95,9 +185,10 @@ import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { GiftedDialog, AlertMessage, QuickNav }, components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon },
}) })
export default class HomeView extends Vue { export default class HomeView extends Vue {
activeDid = ""; activeDid = "";
@ -157,10 +248,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,
);
} }
} }
@ -209,9 +307,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;
@ -247,7 +351,7 @@ export default class HomeView extends Vue {
} }
// 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;
const giverInfo = didInfo( const giverInfo = didInfo(
giverDid, giverDid,
this.activeDid, this.activeDid,
@ -302,17 +406,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;
} }
@ -332,19 +446,38 @@ export default class HomeView extends Vue {
if (this.isGiveCreationError(result)) { if (this.isGiveCreationError(result)) {
const errorMessage = this.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", {
this.getGiveErrorMessage(error) || group: "alert",
"There was an error recording the give.", type: "danger",
title: "Error",
text:
this.getGiveErrorMessage(error) ||
"There was an error recording the give.",
},
-1,
); );
} }
} }

12
src/views/IdentitySwitcherView.vue

@ -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";
} }
} }
} }

33
src/views/NewEditProjectView.vue

@ -245,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;

335
src/views/ProjectViewView.vue

@ -26,16 +26,28 @@
<!-- Project Details --> <!-- Project Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<div> <div>
<h2 class="text-xl font-semibold">{{ name }}</h2> <div class="block pb-4 flex gap-4">
<div class="flex justify-between gap-4 text-sm mb-3"> <div class="flex-none w-16 pt-1">
<span <EntityIcon
><fa icon="user" class="fa-fw text-slate-400"></fa> :entityId="projectId"
{{ issuer }}</span :iconSize="64"
> class="block border border-slate-300 rounded-md"
<span ></EntityIcon>
><fa icon="calendar" class="fa-fw text-slate-400"></fa </div>
>{{ timeSince }}
</span> <div class="overflow-hidden">
<h2 class="text-xl font-semibold">{{ name }}</h2>
<div class="text-sm mb-3">
<div class="truncate"
><fa icon="user" class="fa-fw text-slate-400"></fa>
{{ issuer }}</div
>
<div
><fa icon="calendar" class="fa-fw text-slate-400"></fa
>{{ timeSince }}
</div>
</div>
</div>
</div> </div>
<div class="text-sm text-slate-500"> <div class="text-sm text-slate-500">
@ -65,83 +77,115 @@
</div> </div>
<div> <div>
<div v-if="activeDid"> <div v-if="activeDid" class="text-center">
<button <button
@click="openDialog({ name: 'you', did: activeDid })" @click="openDialog({ name: 'you', did: activeDid })"
class="text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md" class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
> >
I gave... I gave&hellip;
</button> </button>
&horbar; or: <p class="mt-2 mb-4 text-center">Or, record a gift from:</p>
</div> </div>
<!-- similar contact selection code is in multiple places --> <p v-if="!activeDid" class="mt-2 mb-4">Record a gift from:</p>
Record a gift from
<span v-for="contact in allContacts" :key="contact.did"> <ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<button @click="openDialog(contact)" class="text-blue-500"> <li @click="openDialog()">
&nbsp;{{ contact.name }}</button <div class="mb-1">
>, <fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa>
</span> </div>
<span v-if="allContacts.length > 0">&nbsp;or&nbsp;</span> <h3
<button @click="openDialog()" class="text-blue-500"> class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
someone not specified >
</button> Anonymous
</h3>
</li>
<li
v-for="contact in allContacts"
:key="contact.did"
@click="openDialog(contact)"
>
<div class="mb-1">
<fa icon="user" class="fa-fw fa-xl text-slate-400"></fa>
</div>
<h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
>
{{ contact.name || "(no name)" }}
</h3>
</li>
</ul>
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
<router-link
v-if="allContacts.length > 7"
:to="{ name: 'contact-gives' }"
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
>
Show More Contacts&hellip;
</router-link>
</div> </div>
<!-- Gifts to & from this --> <!-- Gifts to & from this -->
<div class="mt-8 flex justify-around"> <div class="grid items-start grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div class="bg-slate-100 px-4 py-3 rounded-md">
<h1 class="text-xl">Given to this Project</h1> <h3 class="text-sm uppercase font-semibold mb-3">
</div> Given to this Project
<div> </h3>
<h1 class="text-xl">... and from this Project</h1>
</div> <ul class="text-sm border-t border-slate-300">
</div> <li
<div class="flex justify-around"> v-for="give in givesToThis"
<div class="w-1/2"> :key="give.id"
<div v-for="give in givesToThis" :key="give.id"> class="py-1.5 border-b border-slate-300"
<div class="flex justify-between"> >
<div class="flex gap-3"> <div class="flex justify-between gap-4">
<div class="flex gap-2"> <span
<fa icon="user" class="fa-fw text-slate-400"></fa> ><fa icon="user" class="fa-fw text-slate-400"></fa>
<span>{{ {{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
didInfo(give.agentDid, activeDid, allMyDids, allContacts) </span>
}}</span> <span v-if="give.amount"
</div> ><fa icon="coins" class="fa-fw text-slate-400"></fa>
<div class="flex gap-2" v-if="give.amount"> {{ give.amount }}
<fa icon="coins" class="fa-fw text-slate-400"></fa> </span>
<span>{{ give.amount }}</span>
</div>
<div class="flex gap-2" v-if="give.description">
<fa icon="comment" class="fa-fw text-slate-400"></fa>
<span>{{ give.description }}</span>
</div>
</div> </div>
</div> <div v-if="give.description" class="text-slate-500">
</div> <fa icon="comment" class="fa-fw text-slate-400"></fa>
{{ give.description }}
</div>
</li>
</ul>
</div> </div>
<div class="w-1/2">
<div v-for="give in givesByThis" :key="give.id"> <div class="bg-slate-100 px-4 py-3 rounded-md">
<div class="flex justify-between"> <h3 class="text-sm uppercase font-semibold mb-3">
<div class="flex gap-3"> &hellip;and from this Project
<div class="flex gap-2"> </h3>
<fa icon="user" class="fa-fw text-slate-400"></fa>
<span>{{ <ul class="text-sm border-t border-slate-300">
didInfo(give.agentDid, activeDid, allMyDids, allContacts) <li
}}</span> v-for="give in givesByThis"
</div> :key="give.id"
<div class="flex gap-2" v-if="give.amount"> class="py-1.5 border-b border-slate-300"
<fa icon="coins" class="fa-fw text-slate-400"></fa> >
<span>{{ give.amount }}</span> <div class="flex justify-between gap-4">
</div> <span
<div class="flex gap-2"> ><fa icon="user" class="fa-fw text-slate-400"></fa>
<fa icon="comment" class="fa-fw text-slate-400"></fa> {{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }}
<span>{{ give.description }}</span> </span>
</div> <span v-if="give.amount"
><fa icon="coins" class="fa-fw text-slate-400"></fa>
{{ give.amount }}
</span>
</div> </div>
</div> <div v-if="give.description" class="text-slate-500">
</div> <fa icon="comment" class="fa-fw text-slate-400"></fa>
{{ give.description }}
</div>
</li>
</ul>
</div> </div>
</div> </div>
<GiftedDialog <GiftedDialog
ref="customDialog" ref="customDialog"
@dialog-result="handleDialogResult" @dialog-result="handleDialogResult"
@ -173,9 +217,10 @@ import {
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { GiftedDialog, AlertMessage, QuickNav }, components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon },
}) })
export default class ProjectViewView extends Vue { export default class ProjectViewView extends Vue {
activeDid = ""; activeDid = "";
@ -285,16 +330,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);
} }
} }
@ -308,12 +375,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,
@ -329,11 +411,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,
@ -364,16 +462,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;
} }
@ -392,21 +502,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,
);
} }
} }
} }

45
src/views/ProjectsView.vue

@ -50,10 +50,11 @@
class="block py-4 flex gap-4" class="block py-4 flex gap-4"
> >
<div class="flex-none w-12"> <div class="flex-none w-12">
<img <EntityIcon
src="https://picsum.photos/200/200?random=1" :entityId="project.handleId"
class="w-full rounded" :iconSize="48"
/> class="inline-block align-middle border border-slate-300 rounded-md"
></EntityIcon>
</div> </div>
<div class="grow overflow-hidden"> <div class="grow overflow-hidden">
@ -82,9 +83,10 @@ import { IIdentifier } from "@veramo/core";
import InfiniteScroll from "@/components/InfiniteScroll"; import InfiniteScroll from "@/components/InfiniteScroll";
import AlertMessage from "@/components/AlertMessage"; import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
import EntityIcon from "@/components/EntityIcon";
@Component({ @Component({
components: { InfiniteScroll, AlertMessage, QuickNav }, components: { InfiniteScroll, AlertMessage, QuickNav, EntityIcon },
}) })
export default class ProjectsView extends Vue { export default class ProjectsView extends Vue {
apiServer = ""; apiServer = "";
@ -126,8 +128,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;
} }
@ -196,8 +205,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;
@ -205,8 +221,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,
);
} }
} }

41
src/views/SeedBackupView.vue

@ -20,22 +20,26 @@
</div> </div>
<div v-if="activeAccount"> <div v-if="activeAccount">
<p> <p class="text-center mb-4">
BEWARE: Anyone who gets hold of this mnemonic seed phrase will be able <b class="text-red-600">BEWARE!</b> Anyone who has this seed phrase will
impersonate you and take over any digital holdings based on it. So only be able impersonate you and take over any digital holdings based on it.
reveal it when you are in a private place out of sight of other eyes, Reveal it when you are somewhere only you can see your screen, and
and only record it in something private -- don't take a screenshot or record it somewhere only you have access.
send it to any online service. <i>Don't take a screenshot or send it to any online service.</i>
</p> </p>
<button <div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4">
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" <button
@click="showSeedPhrase" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
> @click="showSeedPhrase"
Click here when you're ready to see it. >
</button> Reveal my Seed Phrase
</button>
<p v-if="showSeed">{{ activeAccount.mnemonic }}</p> <p v-if="showSeed" class="text-center text-slate-700 mt-2">
{{ activeAccount.mnemonic }}
</p>
</div>
</div> </div>
<div v-else>You do not have an active identity.</div> <div v-else>You do not have an active identity.</div>
<AlertMessage <AlertMessage
@ -72,8 +76,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,
);
} }
} }

11
src/views/StatisticsView.vue

@ -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,
);
} }
} }

Loading…
Cancel
Save