diff --git a/package-lock.json b/package-lock.json index df6f9ba..21d2dcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,11 +33,13 @@ "ethereum-cryptography": "^2.0.0", "ethereumjs-util": "^7.1.5", "ethr-did-resolver": "^8.0.0", + "jdenticon": "^3.2.0", "js-generate-password": "^0.1.9", "localstorage-slim": "^2.4.0", "luxon": "^3.3.0", "merkletreejs": "^0.3.10", "moment": "^2.29.4", + "notiwind": "^2.0.2", "papaparse": "^5.4.1", "pina": "^0.20.2204228", "pinia-plugin-persistedstate": "^3.1.0", @@ -12048,6 +12050,14 @@ "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz", "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": { "version": "2.4.0", "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==", "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": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", @@ -20928,6 +20952,11 @@ "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": { "version": "0.5.6", "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", @@ -21275,6 +21304,17 @@ "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": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", diff --git a/package.json b/package.json index 062adb0..f2eb5a6 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,13 @@ "ethereum-cryptography": "^2.0.0", "ethereumjs-util": "^7.1.5", "ethr-did-resolver": "^8.0.0", + "jdenticon": "^3.2.0", "js-generate-password": "^0.1.9", "localstorage-slim": "^2.4.0", "luxon": "^3.3.0", "merkletreejs": "^0.3.10", "moment": "^2.29.4", + "notiwind": "^2.0.2", "papaparse": "^5.4.1", "pina": "^0.20.2204228", "pinia-plugin-persistedstate": "^3.1.0", diff --git a/project.task.yaml b/project.task.yaml index c13535c..e50f441 100644 --- a/project.task.yaml +++ b/project.task.yaml @@ -1,6 +1,8 @@ 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") - 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. @@ -24,6 +26,7 @@ tasks: - 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 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 @@ -32,6 +35,8 @@ tasks: - .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist - .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?) - .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent +- .2 move 'switch identity' to the advanced section +- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164 - Discuss whether the remaining tasks are worthwhile before MVP release. diff --git a/src/App.vue b/src/App.vue index 8b2af92..8a01476 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,132 @@ diff --git a/src/components/EntityIcon.vue b/src/components/EntityIcon.vue new file mode 100644 index 0000000..0029849 --- /dev/null +++ b/src/components/EntityIcon.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index b3c44ec..d5f4f86 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -82,10 +82,15 @@ export function isHiddenDid(did) { /** always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY **/ -export function didInfo(did, activeDid, allMyDids, contacts) { - const myId: string | undefined = R.find(R.identity, allMyDids); +export function didInfo( + did: string, + activeDid: string, + allMyDids: Array, + contacts: Array, +): string { + const myId: string | undefined = R.find(R.equals(did), allMyDids, did); if (myId) { - return "You" + (myId.did !== activeDid ? " (Alt ID)" : ""); + return "You" + (myId !== activeDid ? " (Alt ID)" : ""); } else { const contact: Contact | undefined = R.find((c) => c.did === did, contacts); if (contact) { diff --git a/src/main.ts b/src/main.ts index bc3f7d5..14eb8d5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import "./registerServiceWorker"; import router from "./router"; import axios from "axios"; import VueAxios from "vue-axios"; +import Notifications from "notiwind"; import "./assets/styles/tailwind.css"; @@ -16,6 +17,7 @@ import { faChevronRight, faCircle, faCircleCheck, + faCircleInfo, faCircleQuestion, faCircleUser, faClock, @@ -45,6 +47,7 @@ import { faSquareCaretDown, faSquareCaretUp, faTrashCan, + faTriangleExclamation, faUser, faUsers, faXmark, @@ -57,6 +60,7 @@ library.add( faChevronRight, faCircle, faCircleCheck, + faCircleInfo, faCircleQuestion, faCircleUser, faClock, @@ -86,6 +90,7 @@ library.add( faSquareCaretDown, faSquareCaretUp, faTrashCan, + faTriangleExclamation, faUser, faUsers, faXmark, @@ -98,4 +103,5 @@ createApp(App) .use(createPinia()) .use(VueAxios, axios) .use(router) + .use(Notifications) .mount("#app"); diff --git a/src/router/index.ts b/src/router/index.ts index 501feda..c524c1b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -182,4 +182,14 @@ const router = createRouter({ 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; diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 23e730e..3edda70 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -400,13 +400,19 @@ export default class AccountViewView extends Vue { this.limitsMessage = "No identity."; this.loadingLimits = false; } else { - this.alertMessage = - "Clear your cache and start over (after data backup)."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Creating Account", + text: "Clear your cache and start over (after data backup).", + }, + -1, + ); console.error( "Telling user to clear cache at page create because:", err, ); - this.alertTitle = "Error Creating Account"; } } } @@ -418,13 +424,19 @@ export default class AccountViewView extends Vue { showContactGivesInline: this.showContactGives, }); } catch (err) { - this.alertMessage = - "Clear your cache and start over (after data backup)."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Updating Contact Setting", + text: "Clear your cache and start over (after data backup).", + }, + -1, + ); console.error( "Telling user to clear cache after contact setting update because:", err, ); - this.alertTitle = "Error Updating Contact Setting"; } } @@ -440,11 +452,25 @@ export default class AccountViewView extends Vue { URL.revokeObjectURL(url); - this.alertTitle = "Download Started"; - this.alertMessage = "See your downloads directory for the backup."; + this.$notify( + { + group: "alert", + type: "toast", + title: "Download Started", + text: "See your downloads directory for the backup.", + }, + 5000, + ); } catch (error) { - this.alertTitle = "Export Error"; - this.alertMessage = "See console logs for more info."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Export Error", + text: "See console logs for more info.", + }, + -1, + ); console.error("Export Error:", error); } } diff --git a/src/views/ContactAmountsView.vue b/src/views/ContactAmountsView.vue index fa02391..2f3770a 100644 --- a/src/views/ContactAmountsView.vue +++ b/src/views/ContactAmountsView.vue @@ -4,6 +4,11 @@

Given with {{ contact?.name }}

+
+ + (Only 50 most recent) + +
@@ -146,10 +151,17 @@ export default class ContactsView extends Vue { this.loadGives(this.activeDid, this.contact); } } catch (err) { - this.alertTitle = "Error"; - this.alertMessage = - err.userMessage || - "There was an error retrieving the latest sweet, sweet action."; + this.$notify( + { + group: "alert", + 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) + "&recipientDid=" + encodeURIComponent(contact.did); - const headers = this.getHeaders(identity); + const headers = await this.getHeaders(identity); const resp = await this.axios.get(url, { headers }); if (resp.status === 200) { result = resp.data.data; @@ -173,9 +185,15 @@ export default class ContactsView extends Vue { resp.status, resp.data, ); - this.alertTitle = "Error With Server"; - this.alertMessage = - "Got an error retrieving your given time from the server."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error With Server", + text: "Got an error retrieving your given time from the server.", + }, + -1, + ); } const url2 = @@ -194,9 +212,15 @@ export default class ContactsView extends Vue { resp2.status, resp2.data, ); - this.alertTitle = "Error With Server"; - this.alertMessage = - "Got an error retrieving your given time from the server."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error With Server", + text: "Got an error retrieving your given time from the server.", + }, + -1, + ); } const sortedResult: Array = R.sort( @@ -206,8 +230,15 @@ export default class ContactsView extends Vue { ); this.giveRecords = sortedResult; } catch (error) { - this.alertTitle = "Error With Server"; - this.alertMessage = error as string; + this.$notify( + { + 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; } // Now set that error for the user to see. - this.alertTitle = "Error With Server"; - this.alertMessage = userMessage; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error With Server", + text: userMessage, + }, + -1, + ); } } } cannotConfirmMessage() { - this.alertTitle = "Not Allowed"; - this.alertMessage = "Only the recipient can confirm final receipt."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Not Allowed", + text: "Only the recipient can confirm final receipt.", + }, + -1, + ); } } diff --git a/src/views/ContactGiftingView.vue b/src/views/ContactGiftingView.vue index 11b7821..0f2a5e3 100644 --- a/src/views/ContactGiftingView.vue +++ b/src/views/ContactGiftingView.vue @@ -24,8 +24,12 @@
  • - + Anonymous @@ -46,7 +50,11 @@ >

    + > {{ contact.name || "(no name)" }} @@ -87,9 +95,10 @@ import { Account } from "@/db/tables/accounts"; import { Contact } from "@/db/tables/contacts"; import AlertMessage from "@/components/AlertMessage"; import QuickNav from "@/components/QuickNav"; +import EntityIcon from "@/components/EntityIcon"; @Component({ - components: { GiftedDialog, AlertMessage, QuickNav }, + components: { GiftedDialog, AlertMessage, QuickNav, EntityIcon }, }) export default class HomeView extends Vue { activeDid = ""; @@ -145,10 +154,17 @@ export default class HomeView extends Vue { this.feedLastViewedId = settings?.lastViewedClaimId; this.updateAllFeed(); } catch (err) { - this.alertTitle = "Error"; - this.alertMessage = - err.userMessage || - "There was an error retrieving the latest sweet, sweet action."; + this.$notify( + { + group: "alert", + 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) { if (!this.activeDid) { - this.setAlert( - "Error", - "You must select an identity before you can record a give.", + 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.setAlert( - "Error", - "You must enter a description or some number of hours.", + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "You must enter a description or some number of hours.", + }, + -1, ); return; } @@ -227,18 +253,38 @@ export default class HomeView extends Vue { if (isGiveCreationError(result)) { const errorMessage = getGiveCreationErrorMessage(result); console.log("Error with give result:", result); - this.setAlert( - "Error", - errorMessage || "There was an error recording the give.", + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: errorMessage || "There was an error recording the give.", + }, + -1, ); } else { - this.setAlert("Success", "That gift was recorded."); + this.$notify( + { + group: "alert", + type: "success", + title: "Success", + text: "That gift was recorded.", + }, + -1, + ); } } catch (error) { console.log("Error with give caught:", error); - this.setAlert( - "Error", - getGiveErrorMessage(error) || "There was an error recording the give.", + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: + getGiveErrorMessage(error) || + "There was an error recording the give.", + }, + -1, ); } } diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue index acef71f..08a6b91 100644 --- a/src/views/ContactQRScanShowView.vue +++ b/src/views/ContactQRScanShowView.vue @@ -85,7 +85,15 @@ export default class ContactQRScanShow extends Vue { const accounts = await accountsDB.accounts.toArray(); const account = R.find((acc) => acc.did === this.activeDid, accounts); if (!account) { - this.alertMessage = "You have no identity yet."; + this.$notify( + { + group: "alert", + type: "warning", + title: "", + text: "You have no identity yet.", + }, + -1, + ); } else { const identity = await this.getIdentity(this.activeDid); const publicKeyHex = identity.keys[0].publicKeyHex; diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index e3f3c6c..740ad88 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -66,18 +66,27 @@ : "Unconfirmed" }} +
    + (Only hours shown) +
    + (Only recent shown)

-
-
-
-
-
- - {{ - didInfo(give.agentDid, activeDid, allMyDids, allContacts) - }} -
-
- - {{ give.amount }} -
-
- - {{ give.description }} -
+ +
+

+ …and from this Project +

+ +
    +
  • +
    + + {{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }} + + + {{ give.amount }} +
    -
-
+
+ + {{ give.description }} +
+ +
+
- +
@@ -82,9 +83,10 @@ import { IIdentifier } from "@veramo/core"; import InfiniteScroll from "@/components/InfiniteScroll"; import AlertMessage from "@/components/AlertMessage"; import QuickNav from "@/components/QuickNav"; +import EntityIcon from "@/components/EntityIcon"; @Component({ - components: { InfiniteScroll, AlertMessage, QuickNav }, + components: { InfiniteScroll, AlertMessage, QuickNav, EntityIcon }, }) export default class ProjectsView extends Vue { apiServer = ""; @@ -126,8 +128,15 @@ export default class ProjectsView extends Vue { } } catch (error) { console.error("Got error loading projects:", error.message); - this.alertTitle = "Error"; - this.alertMessage = "Got an error loading projects:" + error.message; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "Got an error loading projects: " + error.message, + }, + -1, + ); } finally { this.isLoading = false; } @@ -196,8 +205,15 @@ export default class ProjectsView extends Vue { if (this.numAccounts === 0) { console.error("No accounts found."); - this.alertTitle = "Error"; - this.alertMessage = "You need an identity to load your projects."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "You need an identity to load your projects.", + }, + -1, + ); } else { const identity = await this.getIdentity(activeDid); this.current = identity; @@ -205,8 +221,15 @@ export default class ProjectsView extends Vue { } } catch (err) { console.log("Error initializing:", err); - this.alertTitle = "Error"; - this.alertMessage = "Something went wrong loading your projects."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "Something went wrong loading your projects.", + }, + -1, + ); } } diff --git a/src/views/SeedBackupView.vue b/src/views/SeedBackupView.vue index 0812904..7935e86 100644 --- a/src/views/SeedBackupView.vue +++ b/src/views/SeedBackupView.vue @@ -20,22 +20,26 @@
-

- BEWARE: Anyone who gets hold of this mnemonic seed phrase will be able - impersonate you and take over any digital holdings based on it. So only - reveal it when you are in a private place out of sight of other eyes, - and only record it in something private -- don't take a screenshot or - send it to any online service. +

+ BEWARE! Anyone who has this seed phrase will + be able impersonate you and take over any digital holdings based on it. + Reveal it when you are somewhere only you can see your screen, and + record it somewhere only you have access. + Don't take a screenshot or send it to any online service.

- +
+ -

{{ activeAccount.mnemonic }}

+

+ {{ activeAccount.mnemonic }} +

+
You do not have an active identity.
acc.did === activeDid, accounts); } catch (err) { console.error("Got an error loading an identity:", err); - this.alertTitle = "Error Loading Account"; - this.alertMessage = "Got an error loading your seed data."; + this.$notify( + { + group: "alert", + type: "danger", + title: "Error Loading Account", + text: "Got an error loading your seed data.", + }, + -1, + ); } } diff --git a/src/views/StatisticsView.vue b/src/views/StatisticsView.vue index d987553..0c2c7d6 100644 --- a/src/views/StatisticsView.vue +++ b/src/views/StatisticsView.vue @@ -62,8 +62,15 @@ export default class StatisticsView extends Vue { this.world = newWorld; } catch (err) { console.log(err); - this.alertTitle = "Mounting error"; - this.alertMessage = err.message; + this.$notify( + { + group: "alert", + type: "danger", + title: "Mounting Error", + text: err.message, + }, + -1, + ); } }