diff --git a/src/components/OfferDialog.vue b/src/components/OfferDialog.vue new file mode 100644 index 00000000..0ab37004 --- /dev/null +++ b/src/components/OfferDialog.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 628d5bac..8db51eb8 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -59,17 +59,40 @@ export interface GiveServerRecord { unit: string; } +export interface OfferServerRecord { + amount: number; + amountGiven: number; + offeredByDid: string; + recipientDid: string; + requirementsMet: boolean; + unit: string; + validThrough: string; +} + 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 }; + fulfills?: { "@type": string; identifier?: string; lastClaimId?: string }; identifier?: string; object?: { amountOfThisGood: number; unitCode: string }; 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?: { identifier?: string; lastClaimId?: string; "@type"?: string }; + }; + offeredBy?: { identifier: string }; + validThrough?: string; +} + export interface PlanVerifiableCredential { "@context": "https://schema.org"; "@type": "PlanAction"; @@ -152,7 +175,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 +195,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", + identifier: 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 +310,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 5f42993a..3a2f4d51 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -80,10 +80,21 @@ +
+
+ +
+
+