From 643f777d1078c6bd81945dbdb4730dd1f1fb9a0e Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sat, 1 Jul 2023 07:07:46 -0600 Subject: [PATCH] start linking gives to projects --- src/libs/endorserServer.ts | 12 +++- src/views/ProjectViewView.vue | 120 ++++++++++++++++++++++++++++++++-- src/views/ProjectsView.vue | 12 ++-- 3 files changed, 130 insertions(+), 14 deletions(-) diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 6766ecd0a..d220f5dd9 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -46,6 +46,7 @@ export interface GiveVerifiableCredential { "@type": string; agent?: { identifier: string }; description?: string; + fulfills?: { "@type": string; identifier: string }; identifier?: string; object?: { amountOfThisGood: number; unitCode: string }; recipient: { identifier: string }; @@ -95,7 +96,7 @@ export function didInfo(did, identifiers, contacts) { /** * For result, see https://endorser.ch:3000/api-docs/#/claims/post_api_v2_claim - + * * @param identity * @param fromDid may be null * @param toDid @@ -109,7 +110,8 @@ export async function createAndSubmitGive( fromDid: string, toDid: string, description: string, - hours: number + hours: number, + fulfillsProjectHandleId: string ): Promise | InternalError> { // Make a claim const vcClaim: GiveVerifiableCredential = { @@ -126,6 +128,12 @@ export async function createAndSubmitGive( if (hours) { vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" }; } + if (fulfillsProjectHandleId) { + vcClaim.fulfills = { + "@type": "PlanAction", + identifier: fulfillsProjectHandleId, + }; + } // Make a payload for the claim const vcPayload = { vc: { diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index 37fd059a9..93d87859f 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -9,7 +9,7 @@ > -
  • +
  • -
  • +
  • + + + + + + + @@ -151,15 +169,18 @@ import * as moment from "moment"; import * as R from "ramda"; import { Options, Vue } from "vue-class-component"; +import GiftedDialog from "@/components/GiftedDialog.vue"; import { accountsDB, db } from "@/db"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; +import { createAndSubmitGive } from "@/libs/endorserServer"; import { accessToken } from "@/libs/crypto"; import { IIdentifier } from "@veramo/core"; @Options({ - components: {}, + components: { GiftedDialog }, }) export default class ProjectViewView extends Vue { + activeDid = ""; apiServer = ""; expanded = false; name = ""; @@ -167,7 +188,7 @@ export default class ProjectViewView extends Vue { truncatedDesc = ""; truncateLength = 40; timeSince = ""; - projectId = localStorage.getItem("projectId") || ""; + projectId = localStorage.getItem("projectId") || ""; // handle ID errorMessage = ""; onEditClick() { @@ -230,7 +251,7 @@ export default class ProjectViewView extends Vue { async created() { await db.open(); const settings = await db.settings.get(MASTER_SETTINGS_KEY); - const activeDid = settings?.activeDid || ""; + this.activeDid = settings?.activeDid || ""; this.apiServer = settings?.apiServer || ""; await accountsDB.open(); @@ -239,7 +260,7 @@ export default class ProjectViewView extends Vue { console.error("Problem! Should have a profile!"); } else { const accounts = await accountsDB.accounts.toArray(); - const account = R.find((acc) => acc.did === activeDid, accounts); + const account = R.find((acc) => acc.did === this.activeDid, accounts); const identity = JSON.parse(account?.identity || "null"); if (!identity) { throw new Error("No identity found."); @@ -247,5 +268,92 @@ export default class ProjectViewView extends Vue { this.LoadProject(identity); } } + + openDialog(contact) { + this.$refs.customDialog.open(contact); + } + handleDialogResult(result) { + if (result.action === "confirm") { + return new Promise((resolve) => { + this.recordGive(result.contact, result.description, result.hours); + resolve(); + }); + } else { + // action was "cancel" so do nothing + } + } + + /** + * + * @param contact may be null + * @param description may be an empty string + * @param hours may be 0 + */ + async recordGive(contact, description, hours) { + if (this.activeDid == null) { + this.alertTitle = "Error"; + this.alertMessage = + "You must select an identity before you can record a give."; + return; + } + const accounts = await accountsDB.accounts.toArray(); + const account = R.find((acc) => acc.did === this.activeDid, accounts); + const identity = JSON.parse(account?.identity || "null"); + if (!identity) { + throw new Error("No identity found."); + } + createAndSubmitGive( + this.axios, + this.apiServer, + identity, + contact?.did, + this.activeDid, + description, + hours, + this.projectId + ) + .then((result) => { + if (result.status != 201 || result.data?.error) { + console.log("Error with give result:", result); + this.alertTitle = "Error"; + this.alertMessage = + result.data?.message || "There was an error recording the give."; + } else { + this.alertTitle = "Success"; + this.alertMessage = "That gift was recorded."; + //this.updateAllFeed(); // full update is overkill but we should show something + } + }) + .catch((e) => { + console.log("Error with give caught:", e); + this.alertTitle = "Error"; + this.alertMessage = + e.userMessage || "There was an error recording the give."; + }); + } + + // This same popup code is in many files. + alertMessage = ""; + alertTitle = ""; + public onClickClose() { + this.alertTitle = ""; + this.alertMessage = ""; + } + public computedAlertClassNames() { + return { + hidden: !this.alertMessage, + "dismissable-alert": true, + "bg-slate-100": true, + "p-5": true, + rounded: true, + "drop-shadow-lg": true, + fixed: true, + "top-3": true, + "inset-x-3": true, + "transition-transform": true, + "ease-in": true, + "duration-300": true, + }; + } } diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 107158a5e..8ef4311ed 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -185,9 +185,9 @@ export default class ProjectsView extends Vue { } /** - * Handle clicking on a project entry found in the list - * @param id of the project - **/ + * Handle clicking on a project entry found in the list + * @param id of the project + **/ onClickLoadProject(id: string) { localStorage.setItem("projectId", id); const route = { @@ -197,9 +197,9 @@ export default class ProjectsView extends Vue { } /** - * Load projects initially - * @param identity of the user - **/ + * Load projects initially + * @param identity of the user + **/ async LoadProjects(identity: IIdentifier) { const url = `${this.apiServer}/api/v2/report/plansByIssuer`; const token: string = await accessToken(identity);