forked from trent_larson/crowd-funder-for-time-pwa
start with offer-edit
This commit is contained in:
@@ -48,7 +48,7 @@ export interface ClaimResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GenericVerifiableCredential {
|
export interface GenericVerifiableCredential {
|
||||||
"@context"?: string;
|
"@context"?: string; // optional when embedded, eg. in an Agree
|
||||||
"@type": string;
|
"@type": string;
|
||||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
@@ -139,13 +139,13 @@ export interface GiveVerifiableCredential extends GenericVerifiableCredential {
|
|||||||
|
|
||||||
// Note that previous VCs may have additional fields.
|
// Note that previous VCs may have additional fields.
|
||||||
// https://endorser.ch/doc/html/transactions.html#id8
|
// https://endorser.ch/doc/html/transactions.html#id8
|
||||||
export interface OfferVerifiableCredential {
|
export interface OfferVerifiableCredential extends GenericVerifiableCredential {
|
||||||
"@context"?: string; // optional when embedded, eg. in an Agree
|
"@context"?: string; // optional when embedded... though it doesn't make sense to agree to an offer
|
||||||
"@type": "Offer";
|
"@type": "Offer";
|
||||||
description?: string;
|
description?: string; // conditions for the offer
|
||||||
includesObject?: { amountOfThisGood: number; unitCode: string };
|
includesObject?: { amountOfThisGood: number; unitCode: string };
|
||||||
itemOffered?: {
|
itemOffered?: {
|
||||||
description?: string;
|
description?: string; // description of the item
|
||||||
isPartOf?: { identifier?: string; lastClaimId?: string; "@type"?: string };
|
isPartOf?: { identifier?: string; lastClaimId?: string; "@type"?: string };
|
||||||
};
|
};
|
||||||
offeredBy?: { identifier: string };
|
offeredBy?: { identifier: string };
|
||||||
@@ -155,7 +155,7 @@ export interface OfferVerifiableCredential {
|
|||||||
|
|
||||||
// Note that previous VCs may have additional fields.
|
// Note that previous VCs may have additional fields.
|
||||||
// https://endorser.ch/doc/html/transactions.html#id7
|
// https://endorser.ch/doc/html/transactions.html#id7
|
||||||
export interface PlanVerifiableCredential {
|
export interface PlanVerifiableCredential extends GenericVerifiableCredential {
|
||||||
"@context": "https://schema.org";
|
"@context": "https://schema.org";
|
||||||
"@type": "PlanAction";
|
"@type": "PlanAction";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -563,6 +563,8 @@ export async function setPlanInCache(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct GiveAction VC for submission to server
|
* Construct GiveAction VC for submission to server
|
||||||
|
*
|
||||||
|
* @param lastClaimId supplied when editing a previous claim
|
||||||
*/
|
*/
|
||||||
export function hydrateGive(
|
export function hydrateGive(
|
||||||
vcClaimOrig?: GiveVerifiableCredential,
|
vcClaimOrig?: GiveVerifiableCredential,
|
||||||
@@ -587,6 +589,7 @@ export function hydrateGive(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (lastClaimId) {
|
if (lastClaimId) {
|
||||||
|
// this is an edit
|
||||||
vcClaim.lastClaimId = lastClaimId;
|
vcClaim.lastClaimId = lastClaimId;
|
||||||
delete vcClaim.identifier;
|
delete vcClaim.identifier;
|
||||||
}
|
}
|
||||||
@@ -594,16 +597,17 @@ export function hydrateGive(
|
|||||||
vcClaim.agent = fromDid ? { identifier: fromDid } : undefined;
|
vcClaim.agent = fromDid ? { identifier: fromDid } : undefined;
|
||||||
vcClaim.recipient = toDid ? { identifier: toDid } : undefined;
|
vcClaim.recipient = toDid ? { identifier: toDid } : undefined;
|
||||||
vcClaim.description = description || undefined;
|
vcClaim.description = description || undefined;
|
||||||
vcClaim.object = amount
|
vcClaim.object =
|
||||||
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" }
|
amount && !isNaN(amount)
|
||||||
: undefined;
|
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// ensure fulfills is an array
|
// ensure fulfills is an array
|
||||||
if (!Array.isArray(vcClaim.fulfills)) {
|
if (!Array.isArray(vcClaim.fulfills)) {
|
||||||
vcClaim.fulfills = vcClaim.fulfills ? [vcClaim.fulfills] : [];
|
vcClaim.fulfills = vcClaim.fulfills ? [vcClaim.fulfills] : [];
|
||||||
}
|
}
|
||||||
// ... and replace or add each element, ending with Trade or Donate
|
// ... and replace or add each element, ending with Trade or Donate
|
||||||
// I realize this doesn't change any elements that are not PlanAction or Offer or Trade/Action.
|
// I realize the following doesn't change any elements that are not PlanAction or Offer or Trade/Action.
|
||||||
vcClaim.fulfills = vcClaim.fulfills.filter(
|
vcClaim.fulfills = vcClaim.fulfills.filter(
|
||||||
(elem) => elem["@type"] !== "PlanAction",
|
(elem) => elem["@type"] !== "PlanAction",
|
||||||
);
|
);
|
||||||
@@ -639,8 +643,8 @@ export function hydrateGive(
|
|||||||
*
|
*
|
||||||
* @param fromDid may be null
|
* @param fromDid may be null
|
||||||
* @param toDid
|
* @param toDid
|
||||||
* @param description may be null; should have this or amount
|
* @param description may be null
|
||||||
* @param amount may be null; should have this or description
|
* @param amount may be null
|
||||||
*/
|
*/
|
||||||
export async function createAndSubmitGive(
|
export async function createAndSubmitGive(
|
||||||
axios: Axios,
|
axios: Axios,
|
||||||
@@ -667,6 +671,7 @@ export async function createAndSubmitGive(
|
|||||||
fulfillsOfferHandleId,
|
fulfillsOfferHandleId,
|
||||||
isTrade,
|
isTrade,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
return createAndSubmitClaim(
|
return createAndSubmitClaim(
|
||||||
vcClaim as GenericVerifiableCredential,
|
vcClaim as GenericVerifiableCredential,
|
||||||
@@ -680,9 +685,9 @@ export async function createAndSubmitGive(
|
|||||||
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
||||||
*
|
*
|
||||||
* @param fromDid may be null
|
* @param fromDid may be null
|
||||||
* @param toDid
|
* @param toDid may be null if project is provided
|
||||||
* @param description may be null; should have this or amount
|
* @param description may be null
|
||||||
* @param amount may be null; should have this or description
|
* @param amount may be null
|
||||||
*/
|
*/
|
||||||
export async function editAndSubmitGive(
|
export async function editAndSubmitGive(
|
||||||
axios: Axios,
|
axios: Axios,
|
||||||
@@ -720,13 +725,69 @@ export async function editAndSubmitGive(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct Offer VC for submission to server
|
||||||
|
*
|
||||||
|
* @param lastClaimId supplied when editing a previous claim
|
||||||
|
*/
|
||||||
|
export function hydrateOffer(
|
||||||
|
vcClaimOrig?: OfferVerifiableCredential,
|
||||||
|
fromDid?: string,
|
||||||
|
toDid?: string,
|
||||||
|
conditionDescription?: string,
|
||||||
|
amount?: number,
|
||||||
|
unitCode?: string,
|
||||||
|
offeringDescription?: string,
|
||||||
|
fulfillsProjectHandleId?: string,
|
||||||
|
validThrough?: string,
|
||||||
|
lastClaimId?: string,
|
||||||
|
): OfferVerifiableCredential {
|
||||||
|
// Remember: replace values or erase if it's null
|
||||||
|
|
||||||
|
const vcClaim: OfferVerifiableCredential = vcClaimOrig
|
||||||
|
? R.clone(vcClaimOrig)
|
||||||
|
: {
|
||||||
|
"@context": SCHEMA_ORG_CONTEXT,
|
||||||
|
"@type": "Offer",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastClaimId) {
|
||||||
|
// this is an edit
|
||||||
|
vcClaim.lastClaimId = lastClaimId;
|
||||||
|
delete vcClaim.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
vcClaim.offeredBy = fromDid ? { identifier: fromDid } : undefined;
|
||||||
|
vcClaim.recipient = toDid ? { identifier: toDid } : undefined;
|
||||||
|
vcClaim.description = conditionDescription || undefined;
|
||||||
|
|
||||||
|
vcClaim.includesObject =
|
||||||
|
amount && !isNaN(amount)
|
||||||
|
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (offeringDescription || fulfillsProjectHandleId) {
|
||||||
|
vcClaim.itemOffered = vcClaim.itemOffered || {};
|
||||||
|
vcClaim.itemOffered.description = offeringDescription || undefined;
|
||||||
|
if (fulfillsProjectHandleId) {
|
||||||
|
vcClaim.itemOffered.isPartOf = {
|
||||||
|
"@type": "PlanAction",
|
||||||
|
identifier: fulfillsProjectHandleId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vcClaim.validThrough = validThrough || undefined;
|
||||||
|
|
||||||
|
return vcClaim;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
|
||||||
*
|
*
|
||||||
* @param identity
|
* @param identity
|
||||||
* @param description may be null; should have this or amount
|
* @param description may be null
|
||||||
* @param amount may be null; should have this or description
|
* @param amount may be null
|
||||||
* @param expirationDate ISO 8601 date string YYYY-MM-DD (may be null)
|
* @param validThrough ISO 8601 date string YYYY-MM-DD (may be null)
|
||||||
* @param fulfillsProjectHandleId ID of project to which this contributes (may be null)
|
* @param fulfillsProjectHandleId ID of project to which this contributes (may be null)
|
||||||
*/
|
*/
|
||||||
export async function createAndSubmitOffer(
|
export async function createAndSubmitOffer(
|
||||||
@@ -736,35 +797,54 @@ export async function createAndSubmitOffer(
|
|||||||
description?: string,
|
description?: string,
|
||||||
amount?: number,
|
amount?: number,
|
||||||
unitCode?: string,
|
unitCode?: string,
|
||||||
expirationDate?: string,
|
validThrough?: string,
|
||||||
recipientDid?: string,
|
recipientDid?: string,
|
||||||
fulfillsProjectHandleId?: string,
|
fulfillsProjectHandleId?: string,
|
||||||
): Promise<CreateAndSubmitClaimResult> {
|
): Promise<CreateAndSubmitClaimResult> {
|
||||||
const vcClaim: OfferVerifiableCredential = {
|
const vcClaim = hydrateOffer(
|
||||||
"@context": SCHEMA_ORG_CONTEXT,
|
undefined,
|
||||||
"@type": "Offer",
|
issuerDid,
|
||||||
offeredBy: { identifier: issuerDid },
|
recipientDid,
|
||||||
validThrough: expirationDate || undefined,
|
description,
|
||||||
};
|
amount,
|
||||||
if (amount) {
|
unitCode,
|
||||||
vcClaim.includesObject = {
|
description,
|
||||||
amountOfThisGood: amount,
|
fulfillsProjectHandleId,
|
||||||
unitCode: unitCode || "HUR",
|
validThrough,
|
||||||
};
|
undefined,
|
||||||
}
|
);
|
||||||
if (description) {
|
return createAndSubmitClaim(
|
||||||
vcClaim.itemOffered = { description };
|
vcClaim as OfferVerifiableCredential,
|
||||||
}
|
issuerDid,
|
||||||
if (recipientDid) {
|
apiServer,
|
||||||
vcClaim.recipient = { identifier: recipientDid };
|
axios,
|
||||||
}
|
);
|
||||||
if (fulfillsProjectHandleId) {
|
}
|
||||||
vcClaim.itemOffered = vcClaim.itemOffered || {};
|
|
||||||
vcClaim.itemOffered.isPartOf = {
|
export async function editAndSubmitOffer(
|
||||||
"@type": "PlanAction",
|
axios: Axios,
|
||||||
identifier: fulfillsProjectHandleId,
|
apiServer: string,
|
||||||
};
|
fullClaim: GenericCredWrapper<OfferVerifiableCredential>,
|
||||||
}
|
issuerDid: string,
|
||||||
|
description?: string,
|
||||||
|
amount?: number,
|
||||||
|
unitCode?: string,
|
||||||
|
validThrough?: string,
|
||||||
|
recipientDid?: string,
|
||||||
|
fulfillsProjectHandleId?: string,
|
||||||
|
): Promise<CreateAndSubmitClaimResult> {
|
||||||
|
const vcClaim = hydrateOffer(
|
||||||
|
fullClaim.claim,
|
||||||
|
issuerDid,
|
||||||
|
recipientDid,
|
||||||
|
description,
|
||||||
|
amount,
|
||||||
|
unitCode,
|
||||||
|
description,
|
||||||
|
fulfillsProjectHandleId,
|
||||||
|
validThrough,
|
||||||
|
fullClaim.id,
|
||||||
|
);
|
||||||
return createAndSubmitClaim(
|
return createAndSubmitClaim(
|
||||||
vcClaim as OfferVerifiableCredential,
|
vcClaim as OfferVerifiableCredential,
|
||||||
issuerDid,
|
issuerDid,
|
||||||
|
|||||||
@@ -694,7 +694,6 @@ export default class GiftedDetails extends Vue {
|
|||||||
constructGiveParam() {
|
constructGiveParam() {
|
||||||
const recipientDid = this.givenToRecipient ? this.recipientDid : undefined;
|
const recipientDid = this.givenToRecipient ? this.recipientDid : undefined;
|
||||||
const projectId = this.givenToProject ? this.projectId : undefined;
|
const projectId = this.givenToProject ? this.projectId : undefined;
|
||||||
// const giveClaim = constructGive(
|
|
||||||
const giveClaim = hydrateGive(
|
const giveClaim = hydrateGive(
|
||||||
this.prevCredToEdit?.claim as GiveVerifiableCredential,
|
this.prevCredToEdit?.claim as GiveVerifiableCredential,
|
||||||
this.giverDid,
|
this.giverDid,
|
||||||
|
|||||||
624
src/views/OfferDetails.vue
Normal file
624
src/views/OfferDetails.vue
Normal file
@@ -0,0 +1,624 @@
|
|||||||
|
<template>
|
||||||
|
<QuickNav />
|
||||||
|
<TopMessage />
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
|
<!-- Back -->
|
||||||
|
<div
|
||||||
|
v-if="!hideBackButton"
|
||||||
|
class="text-lg text-center font-light relative px-7"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="cancelBack()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 class="text-4xl text-center font-light px-4 mb-4">What Was Offered</h1>
|
||||||
|
|
||||||
|
<h1 class="text-xl font-bold text-center mb-4">
|
||||||
|
<span>From {{ giverName }}</span>
|
||||||
|
<span>
|
||||||
|
to
|
||||||
|
{{
|
||||||
|
offeredToProject
|
||||||
|
? projectName
|
||||||
|
: offeredToRecipient
|
||||||
|
? recipientName
|
||||||
|
: "someone unidentified"
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
|
<textarea
|
||||||
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
|
placeholder="What was offered"
|
||||||
|
v-model="description"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-row justify-center">
|
||||||
|
<span
|
||||||
|
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
||||||
|
@click="changeUnitCode()"
|
||||||
|
>
|
||||||
|
{{ libsUtil.UNIT_SHORT[unitCode] || unitCode }}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
|
@click="amountInput === '0' ? null : decrement()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||||
|
v-model="amountInput"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
|
@click="increment()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-7 mt-4 flex">
|
||||||
|
<input
|
||||||
|
v-if="projectId && !offeredToRecipient"
|
||||||
|
type="checkbox"
|
||||||
|
class="h-6 w-6 mr-2"
|
||||||
|
v-model="offeredToProject"
|
||||||
|
/>
|
||||||
|
<fa
|
||||||
|
v-else
|
||||||
|
icon="square"
|
||||||
|
class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
||||||
|
@click="notifyUserOfProject()"
|
||||||
|
/>
|
||||||
|
<label class="text-sm mt-1">
|
||||||
|
{{
|
||||||
|
projectId
|
||||||
|
? "This was given to " + projectName
|
||||||
|
: "No project was chosen"
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-7 mt-4 flex">
|
||||||
|
<input
|
||||||
|
v-if="recipientDid && !offeredToProject"
|
||||||
|
type="checkbox"
|
||||||
|
class="h-6 w-6 mr-2"
|
||||||
|
v-model="offeredToRecipient"
|
||||||
|
/>
|
||||||
|
<fa
|
||||||
|
v-else
|
||||||
|
icon="square"
|
||||||
|
class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
||||||
|
@click="notifyUserOfRecipient()"
|
||||||
|
/>
|
||||||
|
<label class="text-sm mt-1">
|
||||||
|
{{
|
||||||
|
recipientDid
|
||||||
|
? "This was given to " + recipientName
|
||||||
|
: "No recipient was chosen."
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 flex">
|
||||||
|
<router-link
|
||||||
|
:to="{
|
||||||
|
name: 'claim-add-raw',
|
||||||
|
query: {
|
||||||
|
claim: constructOfferParam(),
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
class="text-blue-500"
|
||||||
|
>
|
||||||
|
Edit & Submit Raw
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-center mb-2 mt-6 italic">
|
||||||
|
Sign & Send to publish to the world
|
||||||
|
<fa
|
||||||
|
icon="circle-info"
|
||||||
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
|
@click="explainData()"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
import { accountsDB, db } from "@/db/index";
|
||||||
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
|
import {
|
||||||
|
createAndSubmitOffer,
|
||||||
|
didInfo,
|
||||||
|
editAndSubmitOffer,
|
||||||
|
GenericCredWrapper,
|
||||||
|
getPlanFromCache,
|
||||||
|
hydrateOffer,
|
||||||
|
OfferVerifiableCredential,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
|
import * as libsUtil from "@/libs/util";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
QuickNav,
|
||||||
|
TopMessage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class OfferDetails extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
activeDid = "";
|
||||||
|
apiServer = "";
|
||||||
|
|
||||||
|
amountInput = "0";
|
||||||
|
description = "";
|
||||||
|
destinationPathAfter = "";
|
||||||
|
offeredToProject = false;
|
||||||
|
offeredToRecipient = false;
|
||||||
|
giverDid: string | undefined;
|
||||||
|
giverName = "";
|
||||||
|
hideBackButton = false;
|
||||||
|
isTrade = false;
|
||||||
|
message = "";
|
||||||
|
offerId = "";
|
||||||
|
prevCredToEdit?: GenericCredWrapper<OfferVerifiableCredential>;
|
||||||
|
projectId = "";
|
||||||
|
projectName = "a project";
|
||||||
|
recipientDid = "";
|
||||||
|
recipientName = "";
|
||||||
|
unitCode = "HUR";
|
||||||
|
validThroughDate = null;
|
||||||
|
|
||||||
|
libsUtil = libsUtil;
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
try {
|
||||||
|
this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"]
|
||||||
|
? (JSON.parse(
|
||||||
|
(this.$route as Router).query["prevCredToEdit"],
|
||||||
|
) as GenericCredWrapper<OfferVerifiableCredential>)
|
||||||
|
: undefined;
|
||||||
|
} catch (error) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Retrieval Error",
|
||||||
|
text: "The previous record isn't available for editing. If you submit, you'll create a new record.",
|
||||||
|
},
|
||||||
|
6000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevAmount = this.prevCredToEdit?.claim?.object?.amountOfThisGood;
|
||||||
|
this.amountInput =
|
||||||
|
(this.$route as Router).query["amountInput"] ||
|
||||||
|
(prevAmount ? String(prevAmount) : "") ||
|
||||||
|
this.amountInput;
|
||||||
|
this.description =
|
||||||
|
(this.$route as Router).query["description"] ||
|
||||||
|
this.prevCredToEdit?.claim?.description ||
|
||||||
|
this.description;
|
||||||
|
this.destinationPathAfter = (this.$route as Router).query[
|
||||||
|
"destinationPathAfter"
|
||||||
|
];
|
||||||
|
this.giverDid = ((this.$route as Router).query["giverDid"] ||
|
||||||
|
this.prevCredToEdit?.claim?.agent?.identifier ||
|
||||||
|
this.giverDid) as string;
|
||||||
|
this.giverName =
|
||||||
|
((this.$route as Router).query["giverName"] as string) || "";
|
||||||
|
this.hideBackButton =
|
||||||
|
(this.$route as Router).query["hideBackButton"] === "true";
|
||||||
|
this.message = ((this.$route as Router).query["message"] as string) || "";
|
||||||
|
// find any offer ID
|
||||||
|
const fulfills = this.prevCredToEdit?.claim?.fulfills;
|
||||||
|
const fulfillsArray = Array.isArray(fulfills)
|
||||||
|
? fulfills
|
||||||
|
: fulfills
|
||||||
|
? [fulfills]
|
||||||
|
: [];
|
||||||
|
const offer = fulfillsArray.find((rec) => rec["@type"] === "Offer");
|
||||||
|
this.offerId = ((this.$route as Router).query["offerId"] ||
|
||||||
|
offer?.identifier ||
|
||||||
|
this.offerId) as string;
|
||||||
|
|
||||||
|
// find any project ID
|
||||||
|
const project = fulfillsArray.find((rec) => rec["@type"] === "PlanAction");
|
||||||
|
this.projectId = ((this.$route as Router).query["projectId"] ||
|
||||||
|
project?.identifier ||
|
||||||
|
this.projectId) as string;
|
||||||
|
|
||||||
|
this.recipientDid = ((this.$route as Router).query["recipientDid"] ||
|
||||||
|
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
|
||||||
|
this.recipientName =
|
||||||
|
((this.$route as Router).query["recipientName"] as string) || "";
|
||||||
|
this.unitCode = ((this.$route as Router).query["unitCode"] ||
|
||||||
|
this.prevCredToEdit?.claim?.object?.unitCode ||
|
||||||
|
this.unitCode) as string;
|
||||||
|
|
||||||
|
// this is an endpoint for sharing project info to highlight something given
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
|
||||||
|
if ((this.$route as Router).query["shareTitle"]) {
|
||||||
|
this.description =
|
||||||
|
((this.$route as Router).query["shareTitle"] as string) +
|
||||||
|
(this.description ? "\n" + this.description : "");
|
||||||
|
}
|
||||||
|
if ((this.$route as Router).query["shareText"]) {
|
||||||
|
this.description =
|
||||||
|
(this.description ? this.description + "\n" : "") +
|
||||||
|
((this.$route as Router).query["shareText"] as string);
|
||||||
|
}
|
||||||
|
if ((this.$route as Router).query["shareUrl"]) {
|
||||||
|
this.imageUrl = (this.$route as Router).query["shareUrl"] as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.open();
|
||||||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||||
|
this.apiServer = settings?.apiServer || "";
|
||||||
|
this.activeDid = settings?.activeDid || "";
|
||||||
|
|
||||||
|
let allContacts: Contact[] = [];
|
||||||
|
let allMyDids: string[] = [];
|
||||||
|
if (
|
||||||
|
(this.giverDid && !this.giverName) ||
|
||||||
|
(this.recipientDid && !this.recipientName)
|
||||||
|
) {
|
||||||
|
allContacts = await db.contacts.toArray();
|
||||||
|
|
||||||
|
await accountsDB.open();
|
||||||
|
const allAccounts = await accountsDB.accounts.toArray();
|
||||||
|
allMyDids = allAccounts.map((acc) => acc.did);
|
||||||
|
if (this.giverDid && !this.giverName) {
|
||||||
|
this.giverName = didInfo(
|
||||||
|
this.giverDid,
|
||||||
|
this.activeDid,
|
||||||
|
allMyDids,
|
||||||
|
allContacts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.recipientDid && !this.recipientName) {
|
||||||
|
this.recipientName = didInfo(
|
||||||
|
this.recipientDid,
|
||||||
|
this.activeDid,
|
||||||
|
allMyDids,
|
||||||
|
allContacts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.offeredToProject = !!this.projectId;
|
||||||
|
this.offeredToRecipient = !this.offeredToProject && !!this.recipientDid;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error retrieving settings from database:", err);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: err.message || "There was an error retrieving your settings.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.projectId) {
|
||||||
|
// console.log("Getting project name from cache", this.projectId);
|
||||||
|
const project = await getPlanFromCache(
|
||||||
|
this.projectId,
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.activeDid,
|
||||||
|
);
|
||||||
|
this.projectName = project?.name
|
||||||
|
? "the project: " + project.name
|
||||||
|
: "a project";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeUnitCode() {
|
||||||
|
const units = Object.keys(this.libsUtil.UNIT_SHORT);
|
||||||
|
const index = units.indexOf(this.unitCode);
|
||||||
|
this.unitCode = units[(index + 1) % units.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
decrement() {
|
||||||
|
this.amountInput = `${Math.max(
|
||||||
|
0,
|
||||||
|
(parseFloat(this.amountInput) || 1) - 1,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
if (this.destinationPathAfter) {
|
||||||
|
(this.$router as Router).push({ path: this.destinationPathAfter });
|
||||||
|
} else {
|
||||||
|
(this.$router as Router).back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelBack() {
|
||||||
|
(this.$router as Router).back();
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirm() {
|
||||||
|
if (!this.activeDid) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must select an identifier before you can record a offer.",
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parseFloat(this.amountInput) < 0) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
text: "You may not send a negative number.",
|
||||||
|
title: "",
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.description && !parseFloat(this.amountInput)) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: `You must enter a description or some number of ${
|
||||||
|
this.libsUtil.UNIT_LONG[this.unitCode]
|
||||||
|
}.`,
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "toast",
|
||||||
|
text: "Recording the give...",
|
||||||
|
title: "",
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
// this is asynchronous, but we don't need to wait for it to complete
|
||||||
|
await this.recordOffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUserOfProject() {
|
||||||
|
if (!this.projectId) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Error",
|
||||||
|
text: "To assign to a project, you must open this dialog through a project.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// must be because offeredToRecipient is true
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Error",
|
||||||
|
text: "You cannot assign both to a project and to a recipient.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUserOfRecipient() {
|
||||||
|
if (!this.recipientDid) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Error",
|
||||||
|
text: "To assign to a recipient, you must open this dialog from a contact.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// must be because offeredToProject is true
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Error",
|
||||||
|
text: "You cannot assign both to a recipient and to a project.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param giverDid may be null
|
||||||
|
* @param description may be an empty string
|
||||||
|
* @param amountInput may be 0
|
||||||
|
* @param unitCode may be omitted, defaults to "HUR"
|
||||||
|
*/
|
||||||
|
public async recordOffer() {
|
||||||
|
try {
|
||||||
|
const recipientDid = this.offeredToRecipient
|
||||||
|
? this.recipientDid
|
||||||
|
: undefined;
|
||||||
|
const projectId = this.offeredToProject ? this.projectId : undefined;
|
||||||
|
let result;
|
||||||
|
if (this.prevCredToEdit) {
|
||||||
|
// don't create from a blank one in case some properties were set from a different interface
|
||||||
|
result = await editAndSubmitOffer(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.prevCredToEdit,
|
||||||
|
this.activeDid,
|
||||||
|
this.description,
|
||||||
|
parseFloat(this.amountInput),
|
||||||
|
this.unitCode,
|
||||||
|
this.validThroughDate,
|
||||||
|
recipientDid,
|
||||||
|
projectId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result = await createAndSubmitOffer(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.activeDid,
|
||||||
|
this.description,
|
||||||
|
parseFloat(this.amountInput),
|
||||||
|
this.unitCode,
|
||||||
|
this.validThroughDate,
|
||||||
|
recipientDid,
|
||||||
|
projectId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === "error" || this.isCreationError(result.response)) {
|
||||||
|
const errorMessage = this.getCreationErrorMessage(result);
|
||||||
|
console.error("Error with give creation result:", result);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: errorMessage || "There was an error creating the give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Success",
|
||||||
|
text: `That ${this.isTrade ? "trade" : "gift"} was recorded.`,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
localStorage.removeItem("imageUrl");
|
||||||
|
if (this.destinationPathAfter) {
|
||||||
|
(this.$router as Router).push({ path: this.destinationPathAfter });
|
||||||
|
} else {
|
||||||
|
(this.$router as Router).back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error with give recordation caught:", error);
|
||||||
|
const errorMessage =
|
||||||
|
error.userMessage ||
|
||||||
|
error.response?.data?.error?.message ||
|
||||||
|
"There was an error recording the give.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: errorMessage,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructOfferParam() {
|
||||||
|
const recipientDid = this.offeredToRecipient
|
||||||
|
? this.recipientDid
|
||||||
|
: undefined;
|
||||||
|
const projectId = this.offeredToProject ? this.projectId : undefined;
|
||||||
|
const giveClaim = hydrateOffer(
|
||||||
|
this.prevCredToEdit?.claim as OfferVerifiableCredential,
|
||||||
|
this.activeDid,
|
||||||
|
recipientDid,
|
||||||
|
this.description,
|
||||||
|
parseFloat(this.amountInput),
|
||||||
|
this.unitCode,
|
||||||
|
"",
|
||||||
|
projectId,
|
||||||
|
this.validThroughDate,
|
||||||
|
this.prevCredToEdit?.id as string,
|
||||||
|
);
|
||||||
|
const claimStr = JSON.stringify(giveClaim);
|
||||||
|
return claimStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for readability
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param result response "data" from the server
|
||||||
|
* @returns true if the result indicates an error
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
isCreationError(result: any) {
|
||||||
|
return result.status !== 201 || result.data?.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
||||||
|
* @returns best guess at an error message
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
getCreationErrorMessage(result: any) {
|
||||||
|
return (
|
||||||
|
result.error?.userMessage ||
|
||||||
|
result.error?.error ||
|
||||||
|
result.response?.data?.error?.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
explainData() {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Data Sharing",
|
||||||
|
text: libsUtil.PRIVACY_MESSAGE,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user