From 0a618cc4ffbdfa8f29fc90666651d0b84ccaef46 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 19 Nov 2023 17:08:42 -0700 Subject: [PATCH 1/2] add button to record an offer on a project --- src/components/OfferDialog.vue | 317 +++++++++++++++++++++++++++++++++ src/libs/endorserServer.ts | 114 +++++++++--- src/views/ProjectViewView.vue | 42 ++++- 3 files changed, 443 insertions(+), 30 deletions(-) create mode 100644 src/components/OfferDialog.vue diff --git a/src/components/OfferDialog.vue b/src/components/OfferDialog.vue new file mode 100644 index 000000000..0ab370041 --- /dev/null +++ b/src/components/OfferDialog.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 628d5bac0..e48aa1a69 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -61,7 +61,7 @@ export interface GiveServerRecord { export interface GiveVerifiableCredential { "@context"?: string; // optional when embedded, eg. in an Agree - "@type": string; + "@type": "GiveAction"; agent?: { identifier: string }; description?: string; fulfills?: { "@type": string; identifier: string }; @@ -70,6 +70,19 @@ export interface GiveVerifiableCredential { recipient?: { identifier: string }; } +export interface OfferVerifiableCredential { + "@context"?: string; // optional when embedded, eg. in an Agree + "@type": "Offer"; + description?: string; + includesObject?: { amountOfThisGood: number; unitCode: string }; + itemOffered?: { + description?: string; + isPartOf?: { handleId?: string; lastClaimId?: string; "@type"?: string }; + }; + offeredBy?: { identifier: string }; + validThrough?: string; +} + export interface PlanVerifiableCredential { "@context": "https://schema.org"; "@type": "PlanAction"; @@ -152,7 +165,7 @@ export interface ErrorResult { error: InternalError; } -export type CreateAndSubmitGiveResult = SuccessResult | ErrorResult; +export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult; /** * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim @@ -172,20 +185,81 @@ export async function createAndSubmitGive( description?: string, hours?: number, fulfillsProjectHandleId?: string, -): Promise { - try { - const vcClaim: GiveVerifiableCredential = { - "@context": "https://schema.org", - "@type": "GiveAction", - recipient: toDid ? { identifier: toDid } : undefined, - agent: fromDid ? { identifier: fromDid } : undefined, - description: description || undefined, - object: hours ? { amountOfThisGood: hours, unitCode: "HUR" } : undefined, - fulfills: fulfillsProjectHandleId - ? { "@type": "PlanAction", identifier: fulfillsProjectHandleId } - : undefined, +): Promise { + const vcClaim: GiveVerifiableCredential = { + "@context": "https://schema.org", + "@type": "GiveAction", + recipient: toDid ? { identifier: toDid } : undefined, + agent: fromDid ? { identifier: fromDid } : undefined, + description: description || undefined, + object: hours ? { amountOfThisGood: hours, unitCode: "HUR" } : undefined, + fulfills: fulfillsProjectHandleId + ? { "@type": "PlanAction", identifier: fulfillsProjectHandleId } + : undefined, + }; + return createAndSubmitClaim( + vcClaim as GenericClaim, + identity, + apiServer, + axios, + ); +} + +/** + * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim + * + * @param identity + * @param description may be null; should have this or hours + * @param hours may be null; should have this or description + * @param expirationDate ISO 8601 date string YYYY-MM-DD (may be null) + * @param fulfillsProjectHandleId ID of project to which this contributes (may be null) + */ +export async function createAndSubmitOffer( + axios: Axios, + apiServer: string, + identity: IIdentifier, + description?: string, + hours?: number, + expirationDate?: string, + fulfillsProjectHandleId?: string, +): Promise { + const vcClaim: OfferVerifiableCredential = { + "@context": "https://schema.org", + "@type": "Offer", + offeredBy: { identifier: identity.did }, + validThrough: expirationDate || undefined, + }; + if (hours) { + vcClaim.includesObject = { + amountOfThisGood: hours, + unitCode: "HUR", + }; + } + if (description) { + vcClaim.itemOffered = { description }; + } + if (fulfillsProjectHandleId) { + vcClaim.itemOffered = vcClaim.itemOffered || {}; + vcClaim.itemOffered.isPartOf = { + "@type": "PlanAction", + handleId: fulfillsProjectHandleId, }; + } + return createAndSubmitClaim( + vcClaim as GenericClaim, + identity, + apiServer, + axios, + ); +} +export async function createAndSubmitClaim( + vcClaim: GenericClaim, + identity: IIdentifier, + apiServer: string, + axios: Axios, +): Promise { + try { const vcPayload = { vc: { "@context": ["https://www.w3.org/2018/credentials/v1"], @@ -226,15 +300,11 @@ export async function createAndSubmitGive( }); return { type: "success", response }; - } catch (error: unknown) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + console.log("Error creating claim:", error); const errorMessage: string = - error === null - ? "Null error" - : error instanceof Error - ? error.message - : typeof error === "object" && error !== null && "message" in error - ? (error as { message: string }).message - : "Unknown error"; + error.response?.data?.error?.message || error.message || "Unknown error"; return { type: "error", diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index 5f42993a1..b270c3a7b 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -80,10 +80,21 @@ +
+
+ +
+
+