From 4fdfe2f824cd30911b4a41656bc55b891a5a06c5 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 12 Mar 2023 18:30:18 -0600 Subject: [PATCH 1/7] feat: add contacts DB & page --- project.yaml | 3 +- src/db/index.ts | 18 +++-- src/db/tables/accounts.ts | 6 -- src/db/tables/contacts.ts | 12 +++ src/router/index.ts | 6 ++ src/views/ContactsView.vue | 121 +++++++++++++++++++++++++++++++ src/views/NewEditProjectView.vue | 1 + src/views/ProjectViewView.vue | 1 + src/views/ProjectsView.vue | 1 + 9 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 src/db/tables/contacts.ts create mode 100644 src/views/ContactsView.vue diff --git a/project.yaml b/project.yaml index c5abb19f..00d5c593 100644 --- a/project.yaml +++ b/project.yaml @@ -14,7 +14,8 @@ - replace user-affecting console.logs with error messages (eg. catches) -- contacts +- contacts v1: + - parse input correctly (with CSV lib and not commas) - commit screen diff --git a/src/db/index.ts b/src/db/index.ts index be9d9f4e..67b30619 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,6 +1,12 @@ -import BaseDexie from "dexie"; +import BaseDexie, { Table } from "dexie"; import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon"; -import { accountsSchema, AccountsTable } from "./tables/accounts"; +import { accountsSchema, Account } from "./tables/accounts"; +import { contactsSchema, Contact } from "./tables/contacts"; + +type AllTables = { + accounts: Table; + contacts: Table; +}; /** * In order to make the next line be acceptable, the program needs to have its linter suppress a rule: @@ -10,10 +16,9 @@ import { accountsSchema, AccountsTable } from "./tables/accounts"; * * https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any */ -type DexieTables = AccountsTable; +type DexieTables = AllTables; export type Dexie = BaseDexie & T; export const db = new BaseDexie("kickStarter") as Dexie; -const schema = Object.assign({}, accountsSchema); /** * Needed to enable a special webpack setting to allow *await* below: @@ -27,6 +32,7 @@ const secret = if (localStorage.getItem("secret") == null) { localStorage.setItem("secret", secret); } -console.log(secret); +console.log("DB encryption secretKey:", secret); encrypted(db, { secretKey: secret }); -db.version(1).stores(schema); +db.version(1).stores(accountsSchema); +db.version(2).stores(contactsSchema); diff --git a/src/db/tables/accounts.ts b/src/db/tables/accounts.ts index 8ed1bd2b..b78982f2 100644 --- a/src/db/tables/accounts.ts +++ b/src/db/tables/accounts.ts @@ -1,5 +1,3 @@ -import { Table } from "dexie"; - export type Account = { id?: number; publicKey: string; @@ -8,10 +6,6 @@ export type Account = { dateCreated: number; }; -export type AccountsTable = { - accounts: Table; -}; - // mark encrypted field by starting with a $ character export const accountsSchema = { accounts: "++id, publicKey, $mnemonic, $identity, dateCreated", diff --git a/src/db/tables/contacts.ts b/src/db/tables/contacts.ts new file mode 100644 index 00000000..a1979023 --- /dev/null +++ b/src/db/tables/contacts.ts @@ -0,0 +1,12 @@ +export interface Contact { + did: string; + name?: string; + publicKeyBase64?: string; + seesMe?: boolean; + registered?: boolean; +} + +// mark encrypted field by starting with a $ character +export const contactsSchema = { + contacts: "++did, name, publicKeyBase64, seesMe, registered", +}; diff --git a/src/router/index.ts b/src/router/index.ts index 38611c98..3147ab42 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -43,6 +43,12 @@ const routes: Array = [ /* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue" ), }, + { + path: "/contacts", + name: "contacts", + component: () => + import(/* webpackChunkName: "contacts" */ "../views/ContactsView.vue"), + }, { path: "/scan-contact", name: "scan-contact", diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue new file mode 100644 index 00000000..ccd5fa39 --- /dev/null +++ b/src/views/ContactsView.vue @@ -0,0 +1,121 @@ + + + diff --git a/src/views/NewEditProjectView.vue b/src/views/NewEditProjectView.vue index 54c020f0..bb153aaf 100644 --- a/src/views/NewEditProjectView.vue +++ b/src/views/NewEditProjectView.vue @@ -107,6 +107,7 @@ export default class NewEditProjectView extends Vue { isHiddenSave = false; isHiddenSpinner = true; + // 'created' hook runs when the Vue instance is first created async created() { if (this.projectId === "") { console.log("This is a new project"); diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index f0a2b4ee..e8577ff4 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -222,6 +222,7 @@ export default class ProjectViewView extends Vue { } } + // 'created' hook runs when the Vue instance is first created async created() { await db.open(); const num_accounts = await db.accounts.count(); diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 06b8fdc5..ae3abbe6 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -151,6 +151,7 @@ export default class ProjectsView extends Vue { } } + // 'created' hook runs when the Vue instance is first created async created() { await db.open(); const num_accounts = await db.accounts.count(); From 72148825239d7dca378e2224df99e68a3aeb07e1 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Mon, 13 Mar 2023 20:36:33 -0600 Subject: [PATCH 2/7] feat: show hours given to and received from contacts --- src/views/ContactsView.vue | 109 ++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index ccd5fa39..eb204f13 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -40,12 +40,17 @@ +

My Contacts

+
+ {{ errorMessage }} +
+
+ +
+ + +
+
  • {{ contact.did }}
    {{ contact.publicKeyBase64 }}
    +
    +
    + to: {{ contactGivenTotals[contact.did] || 0 }} +
    +
    +
    + from: {{ contactReceivedTotals[contact.did] || 0 }} +
    +
@@ -83,6 +112,10 @@ From 392728fd4a7d735ddcacbdca6d512dec99e46b18 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 14 Mar 2023 17:03:32 -0600 Subject: [PATCH 3/7] feat: allow entry and send of a Give to/from addresses in contact list --- src/views/ContactsView.vue | 177 ++++++++++++++++++++++++++++++++----- 1 file changed, 157 insertions(+), 20 deletions(-) diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index eb204f13..f8c62060 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -69,17 +69,28 @@
- +
+ +
+
+ Hours to Add: + +
@@ -97,11 +108,20 @@
{{ contact.publicKeyBase64 }}
- to: {{ contactGivenTotals[contact.did] || 0 }} -
-
-
- from: {{ contactReceivedTotals[contact.did] || 0 }} + to: {{ givenByMeTotals[contact.did] || 0 }} + + by: {{ givenToMeTotals[contact.did] || 0 }} +
@@ -111,14 +131,24 @@ From d5abfb0265b3e18d31377d3cb51f3d7016bcf561 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 14 Mar 2023 18:42:30 -0600 Subject: [PATCH 4/7] feat: load totals immediately, and prompt to verify giving amount --- src/views/ContactsView.vue | 51 ++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index f8c62060..0ecb8d27 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -67,22 +67,8 @@ - -
- -
- -
-
+
+
Hours to Add: import { AxiosError } from "axios"; import * as didJwt from "did-jwt"; +import * as R from "ramda"; import { Options, Vue } from "vue-class-component"; import { AppString } from "@/constants/app"; @@ -153,7 +140,7 @@ export interface GiveVerifiableCredential { components: {}, }) export default class ContactsView extends Vue { - contacts: Contact[] = []; + contacts: Array = []; contactInput = ""; // { "did:...": amount } entry for each contact givenByMeTotals = {}; @@ -167,13 +154,15 @@ export default class ContactsView extends Vue { // 'created' hook runs when the Vue instance is first created async created() { await db.open(); + const accounts = await db.accounts.toArray(); + this.identity = JSON.parse(accounts[0].identity); this.contacts = await db.contacts.toArray(); const params = new URLSearchParams(window.location.search); this.showGiveTotals = params.get("showGiveTotals") == "true"; - - const accounts = await db.accounts.toArray(); - this.identity = JSON.parse(accounts[0].identity); + if (this.showGiveTotals) { + this.loadGives(); + } } async onClickNewContact(): void { @@ -259,6 +248,10 @@ export default class ContactsView extends Vue { return !isNaN(str) && !isNaN(parseFloat(str)); } + private contactForDid(contacts: Array, did: string): Contact { + return R.find((con) => con.did == did, contacts); + } + async onClickAddGive(fromDid: string, toDid: string): void { if (!this.hourInput) { this.errorMessage = "Giving 0 hours does nothing."; @@ -267,7 +260,23 @@ export default class ContactsView extends Vue { "This is not a valid number of hours: " + this.hourInput; } else { this.errorMessage = ""; - this.createAndSubmitGive(fromDid, toDid, parseFloat(this.hourInput)); + let toFrom; + if (fromDid == this.identity.did) { + toFrom = "to " + this.contactForDid(this.contacts, toDid).name; + } else { + toFrom = "from " + this.contactForDid(this.contacts, fromDid).name; + } + if ( + confirm( + "Are you sure you want to record " + + this.hourInput + + " hours " + + toFrom + + "?" + ) + ) { + this.createAndSubmitGive(fromDid, toDid, parseFloat(this.hourInput)); + } } } From d6a5bd02f3d10eb31ea5059138e3018f3768b8e5 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 14 Mar 2023 20:31:26 -0600 Subject: [PATCH 5/7] fix: type-checking --- src/views/ContactsView.vue | 110 +++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 28 deletions(-) diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 0ecb8d27..c6647bad 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -114,6 +114,16 @@
+
+ +

{{ alertTitle }}

+

{{ alertMessage }}

+
From 9cb10b856196bf810db3fb554ef06b86fd74a64d Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 15 Mar 2023 18:49:05 -0600 Subject: [PATCH 6/7] ui: change item on bottom row to 'contacts' --- src/main.ts | 2 ++ src/views/AccountViewView.vue | 4 ++-- src/views/ContactsView.vue | 8 +++++--- src/views/DiscoverView.vue | 6 ++++-- src/views/ProjectsView.vue | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main.ts b/src/main.ts index 234967b8..8fc26a4e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,7 @@ import { faShareNodes, faQrcode, faUser, + faUsers, faPen, faPlus, faTrashCan, @@ -41,6 +42,7 @@ library.add( faShareNodes, faQrcode, faUser, + faUsers, faPen, faPlus, faTrashCan, diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index 0bc56faf..d67e3a25 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -29,10 +29,10 @@
  • - +
  • diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index c6647bad..6147f5dc 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -19,15 +19,17 @@
  • -
  • diff --git a/src/views/DiscoverView.vue b/src/views/DiscoverView.vue index c3514810..7eadab71 100644 --- a/src/views/DiscoverView.vue +++ b/src/views/DiscoverView.vue @@ -26,8 +26,10 @@
  • -
  • diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index ae3abbe6..46660cd3 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -26,8 +26,10 @@
  • -
  • From f6a7677bdc0ed0e95590d2d7c24d7a040df7628d Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 15 Mar 2023 21:04:10 -0600 Subject: [PATCH 7/7] feat: add a description for time gifts (and refactor errors) --- src/views/ContactsView.vue | 76 ++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 6147f5dc..34fcb2b8 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -49,10 +49,6 @@ My Contacts -
    - {{ errorMessage }} -
    -
    +
    +
    @@ -144,8 +147,9 @@ export interface GiveVerifiableCredential { "@context": string; "@type": string; agent: { identifier: string }; - recipient: { identifier: string }; + description?: string; object: { amountOfThisGood: number; unitCode: string }; + recipient: { identifier: string }; } @Options({ @@ -158,9 +162,9 @@ export default class ContactsView extends Vue { givenByMeTotals: Record = {}; // { "did:...": amount } entry for each contact givenToMeTotals: Record = {}; + hourDescriptionInput = ""; hourInput = "0"; identity: IIdentifier | null = null; - errorMessage = ""; showGiveTotals = false; // 'created' hook runs when the Vue instance is first created @@ -231,7 +235,9 @@ export default class ContactsView extends Vue { this.givenByMeTotals = contactTotals; } } catch (error) { - this.errorMessage = "" + error; + this.alertTitle = "Error from Server"; + this.alertMessage = error as string; + this.isAlertVisible = true; } // load all the time I have received @@ -259,7 +265,9 @@ export default class ContactsView extends Vue { this.givenToMeTotals = contactTotals; } } catch (error) { - this.errorMessage = "" + error; + this.alertTitle = "Error from Server"; + this.alertMessage = error as string; + this.isAlertVisible = true; } } @@ -275,20 +283,25 @@ export default class ContactsView extends Vue { } async onClickAddGive(fromDid: string, toDid: string): Promise { - if (!this.hourInput) { - this.errorMessage = "Giving 0 hours does nothing."; - } else if (!this.isNumeric(this.hourInput)) { - this.errorMessage = + if (!this.isNumeric(this.hourInput)) { + this.alertTitle = "Input Error"; + this.alertMessage = "This is not a valid number of hours: " + this.hourInput; + this.isAlertVisible = true; + } else if (!parseFloat(this.hourInput)) { + this.alertTitle = "Input Error"; + this.alertMessage = "Giving 0 hours does nothing."; + this.isAlertVisible = true; } else if (!this.identity) { - this.errorMessage = "No identity is available."; + this.alertTitle = "Status Error"; + this.alertMessage = "No identity is available."; + this.isAlertVisible = true; } else { - this.errorMessage = ""; let toFrom; if (fromDid == this.identity?.did) { - toFrom = "to " + this.nameForDid(this.contacts, toDid); + toFrom = "from you to " + this.nameForDid(this.contacts, toDid); } else { - toFrom = "from " + this.nameForDid(this.contacts, fromDid); + toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you"; } if ( confirm( @@ -303,7 +316,8 @@ export default class ContactsView extends Vue { this.identity, fromDid, toDid, - parseFloat(this.hourInput) + parseFloat(this.hourInput), + this.hourDescriptionInput ); } } @@ -313,16 +327,20 @@ export default class ContactsView extends Vue { identity: IIdentifier, fromDid: string, toDid: string, - amount: number + amount: number, + description: string ): Promise { // Make a claim const vcClaim: GiveVerifiableCredential = { "@context": "https://schema.org", "@type": "GiveAction", agent: { identifier: fromDid }, - recipient: { identifier: toDid }, object: { amountOfThisGood: amount, unitCode: "HUR" }, + recipient: { identifier: toDid }, }; + if (description) { + vcClaim.description = description; + } // Make a payload for the claim const vcPayload = { vc: { @@ -359,18 +377,17 @@ export default class ContactsView extends Vue { const resp = await this.axios.post(url, payload, { headers }); //console.log("Got resp data:", resp.data); if (resp.data?.success?.handleId) { - this.errorMessage = ""; this.alertTitle = ""; this.alertMessage = ""; if (fromDid === identity.did) { this.givenByMeTotals[toDid] = this.givenByMeTotals[toDid] + amount; - // do this to update the UI + // do this to update the UI (is there a better way?) // eslint-disable-next-line no-self-assign this.givenByMeTotals = this.givenByMeTotals; } else { this.givenToMeTotals[fromDid] = this.givenToMeTotals[fromDid] + amount; - // do this to update the UI + // do this to update the UI (is there a better way?) // eslint-disable-next-line no-self-assign this.givenToMeTotals = this.givenToMeTotals; } @@ -379,21 +396,18 @@ export default class ContactsView extends Vue { let userMessage = "There was an error. See logs for more info."; const serverError = error as AxiosError; if (serverError) { - this.isAlertVisible = true; if (serverError.message) { - this.alertTitle = "User Message"; - userMessage = serverError.message; // This is info for the user. - this.alertMessage = userMessage; + userMessage = serverError.message; // Info for the user } else { - this.alertTitle = "Server Message"; - this.alertMessage = JSON.stringify(serverError.toJSON()); + userMessage = JSON.stringify(serverError.toJSON()); } } else { - this.alertTitle = "Claim Error"; - this.alertMessage = error as string; + userMessage = error as string; } // Now set that error for the user to see. - this.errorMessage = userMessage; + this.alertTitle = "Error with Server"; + this.alertMessage = userMessage; + this.isAlertVisible = true; } } }