Browse Source

add ability to edit a GiveAction

Trent Larson 4 months ago
parent
commit
c16c1689a3
  1. 4
      src/components/GiftedDialog.vue
  2. 2
      src/libs/crypto/vc/index.ts
  3. 159
      src/libs/endorserServer.ts
  4. 28
      src/libs/util.ts
  5. 42
      src/views/ClaimView.vue
  6. 11
      src/views/ConfirmGiftView.vue
  7. 119
      src/views/GiftedDetails.vue
  8. 3
      src/views/HomeView.vue
  9. 3
      src/views/NewEditProjectView.vue
  10. 2
      src/views/SharedPhotoView.vue
  11. 10
      src/vite-env.d.ts

4
src/components/GiftedDialog.vue

@ -291,8 +291,8 @@ export default class GiftedDialog extends Vue {
this.axios, this.axios,
this.apiServer, this.apiServer,
this.activeDid, this.activeDid,
giverDid, giverDid as string,
this.receiver?.did as string, recipientDid as string,
description, description,
amount, amount,
unitCode, unitCode,

2
src/libs/crypto/vc/index.ts

@ -67,7 +67,7 @@ export async function createEndorserJwtForKey(
* The SimpleSigner returns a configured function for signing data. * The SimpleSigner returns a configured function for signing data.
* *
* @example * @example
* const signer = SimpleSigner(import.meta.env.PRIVATE_KEY) * const signer = SimpleSigner(privateKeyHexString)
* signer(data, (err, signature) => { * signer(data, (err, signature) => {
* ... * ...
* }) * })

159
src/libs/endorserServer.ts

@ -48,29 +48,31 @@ export interface ClaimResult {
} }
export interface GenericVerifiableCredential { export interface GenericVerifiableCredential {
"@context": string; "@context"?: string;
"@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
} }
export interface GenericCredWrapper extends GenericVerifiableCredential { export interface GenericCredWrapper<T extends GenericVerifiableCredential> {
"@context": string;
"@type": string;
handleId: string; handleId: string;
id: string; id: string;
issuedAt: string; issuedAt: string;
issuer: string; issuer: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any claim: T;
claim: Record<string, any>;
claimType?: string; claimType?: string;
} }
export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper = { export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCredential> =
{
"@context": SCHEMA_ORG_CONTEXT, "@context": SCHEMA_ORG_CONTEXT,
"@type": "", "@type": "",
claim: {}, claim: { "@type": "" },
handleId: "", handleId: "",
id: "", id: "",
issuedAt: "", issuedAt: "",
issuer: "", issuer: "",
}; };
// a summary record; the VC is found the fullClaim field // a summary record; the VC is found the fullClaim field
export interface GiveSummaryRecord { export interface GiveSummaryRecord {
@ -123,7 +125,7 @@ export interface PlanSummaryRecord {
// Note that previous VCs may have additional fields. // Note that previous VCs may have additional fields.
// https://endorser.ch/doc/html/transactions.html#id4 // https://endorser.ch/doc/html/transactions.html#id4
export interface GiveVerifiableCredential { export interface GiveVerifiableCredential extends GenericVerifiableCredential {
"@context"?: string; // optional when embedded, eg. in an Agree "@context"?: string; // optional when embedded, eg. in an Agree
"@type": "GiveAction"; "@type": "GiveAction";
agent?: { identifier: string }; agent?: { identifier: string };
@ -191,7 +193,7 @@ export interface PlanData {
*/ */
issuerDid: string; issuerDid: string;
/** /**
* The Identier of the project -- different from jwtId, needs to be fixed * The identifier of the project -- different from jwtId, needs to be fixed
**/ **/
rowid?: string; rowid?: string;
} }
@ -562,8 +564,9 @@ export async function setPlanInCache(
/** /**
* Construct GiveAction VC for submission to server * Construct GiveAction VC for submission to server
*/ */
export function constructGive( export function hydrateGive(
fromDid?: string | null, vcClaimOrig?: GiveVerifiableCredential,
fromDid?: string,
toDid?: string, toDid?: string,
description?: string, description?: string,
amount?: number, amount?: number,
@ -572,42 +575,68 @@ export function constructGive(
fulfillsOfferHandleId?: string, fulfillsOfferHandleId?: string,
isTrade: boolean = false, isTrade: boolean = false,
imageUrl?: string, imageUrl?: string,
lastClaimId?: string,
): GiveVerifiableCredential { ): GiveVerifiableCredential {
const vcClaim: GiveVerifiableCredential = { // Remember: replace values or erase if it's null
const vcClaim: GiveVerifiableCredential = vcClaimOrig
? R.clone(vcClaimOrig)
: {
"@context": SCHEMA_ORG_CONTEXT, "@context": SCHEMA_ORG_CONTEXT,
"@type": "GiveAction", "@type": "GiveAction",
recipient: toDid ? { identifier: toDid } : undefined,
agent: fromDid ? { identifier: fromDid } : undefined,
description: description || undefined,
object: amount
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" }
: undefined,
fulfills: [{ "@type": isTrade ? "TradeAction" : "DonateAction" }],
}; };
if (lastClaimId) {
vcClaim.lastClaimId = lastClaimId;
delete vcClaim.identifier;
}
vcClaim.agent = fromDid ? { identifier: fromDid } : undefined;
vcClaim.recipient = toDid ? { identifier: toDid } : undefined;
vcClaim.description = description || undefined;
vcClaim.object = amount
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" }
: undefined;
// ensure fulfills is an array
if (!Array.isArray(vcClaim.fulfills)) {
vcClaim.fulfills = vcClaim.fulfills ? [vcClaim.fulfills] : [];
}
// ... 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.
if (fulfillsProjectHandleId) { if (fulfillsProjectHandleId) {
vcClaim.fulfills = vcClaim.fulfills || []; // weird that it won't typecheck without this vcClaim.fulfills = vcClaim.fulfills.filter(
(elem) => elem["@type"] !== "PlanAction",
);
vcClaim.fulfills.push({ vcClaim.fulfills.push({
"@type": "PlanAction", "@type": "PlanAction",
identifier: fulfillsProjectHandleId, identifier: fulfillsProjectHandleId,
}); });
} }
if (fulfillsOfferHandleId) { if (fulfillsOfferHandleId) {
vcClaim.fulfills = vcClaim.fulfills || []; // weird that it won't typecheck without this vcClaim.fulfills = vcClaim.fulfills.filter(
(elem) => elem["@type"] !== "Offer",
);
vcClaim.fulfills.push({ vcClaim.fulfills.push({
"@type": "Offer", "@type": "Offer",
identifier: fulfillsOfferHandleId, identifier: fulfillsOfferHandleId,
}); });
} }
if (imageUrl) { // do Trade/Donate last because current endorser.ch only looks at the first for plans & offers
vcClaim.image = imageUrl; vcClaim.fulfills = vcClaim.fulfills.filter(
} (elem) =>
elem["@type"] !== "DonateAction" && elem["@type"] !== "TradeAction",
);
vcClaim.fulfills.push({ "@type": isTrade ? "TradeAction" : "DonateAction" });
vcClaim.image = imageUrl || undefined;
return vcClaim; 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 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; should have this or amount
@ -617,7 +646,7 @@ export async function createAndSubmitGive(
axios: Axios, axios: Axios,
apiServer: string, apiServer: string,
issuerDid: string, issuerDid: string,
fromDid?: string | null, fromDid?: string,
toDid?: string, toDid?: string,
description?: string, description?: string,
amount?: number, amount?: number,
@ -627,7 +656,8 @@ export async function createAndSubmitGive(
isTrade: boolean = false, isTrade: boolean = false,
imageUrl?: string, imageUrl?: string,
): Promise<CreateAndSubmitClaimResult> { ): Promise<CreateAndSubmitClaimResult> {
const vcClaim = constructGive( const vcClaim = hydrateGive(
undefined,
fromDid, fromDid,
toDid, toDid,
description, description,
@ -639,7 +669,51 @@ export async function createAndSubmitGive(
imageUrl, imageUrl,
); );
return createAndSubmitClaim( return createAndSubmitClaim(
vcClaim as GenericCredWrapper, vcClaim as GenericVerifiableCredential,
issuerDid,
apiServer,
axios,
);
}
/**
* For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
*
* @param fromDid may be null
* @param toDid
* @param description may be null; should have this or amount
* @param amount may be null; should have this or description
*/
export async function editAndSubmitGive(
axios: Axios,
apiServer: string,
fullClaim: GenericCredWrapper<GiveVerifiableCredential>,
issuerDid: string,
fromDid?: string,
toDid?: string,
description?: string,
amount?: number,
unitCode?: string,
fulfillsProjectHandleId?: string,
fulfillsOfferHandleId?: string,
isTrade: boolean = false,
imageUrl?: string,
): Promise<CreateAndSubmitClaimResult> {
const vcClaim = hydrateGive(
fullClaim.claim,
fromDid,
toDid,
description,
amount,
unitCode,
fulfillsProjectHandleId,
fulfillsOfferHandleId,
isTrade,
imageUrl,
fullClaim.id,
);
return createAndSubmitClaim(
vcClaim as GenericVerifiableCredential,
issuerDid, issuerDid,
apiServer, apiServer,
axios, axios,
@ -692,7 +766,7 @@ export async function createAndSubmitOffer(
}; };
} }
return createAndSubmitClaim( return createAndSubmitClaim(
vcClaim as GenericCredWrapper, vcClaim as OfferVerifiableCredential,
issuerDid, issuerDid,
apiServer, apiServer,
axios, axios,
@ -751,7 +825,7 @@ export async function createAndSubmitClaim(
return { type: "success", response }; return { type: "success", response };
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) { } catch (error: any) {
console.error("Error creating claim:", error); console.error("Error submitting claim:", error);
const errorMessage: string = const errorMessage: string =
error.response?.data?.error?.message || error.response?.data?.error?.message ||
error.message || error.message ||
@ -820,24 +894,29 @@ export const capitalizeAndInsertSpacesBeforeCaps = (text: string) => {
similar code is also contained in endorser-mobile similar code is also contained in endorser-mobile
**/ **/
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const claimSummary = (claim: Record<string, any>) => { const claimSummary = (
claim: GenericCredWrapper<GenericVerifiableCredential>,
) => {
if (!claim) { if (!claim) {
// to differentiate from "something" above // to differentiate from "something" above
return "something"; return "something";
} }
let specificClaim:
| GenericVerifiableCredential
| GenericCredWrapper<GenericVerifiableCredential> = claim;
if (claim.claim) { if (claim.claim) {
// probably a Verified Credential // probably a Verified Credential
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
claim = claim.claim as Record<string, any>; specificClaim = claim.claim;
} }
if (Array.isArray(claim)) { if (Array.isArray(specificClaim)) {
if (claim.length === 1) { if (specificClaim.length === 1) {
claim = claim[0]; specificClaim = specificClaim[0];
} else { } else {
return "multiple claims"; return "multiple claims";
} }
} }
const type = claim["@type"]; const type = specificClaim["@type"];
if (!type) { if (!type) {
return "a claim"; return "a claim";
} else { } else {
@ -858,7 +937,7 @@ const claimSummary = (claim: Record<string, any>) => {
similar code is also contained in endorser-mobile similar code is also contained in endorser-mobile
**/ **/
export const claimSpecialDescription = ( export const claimSpecialDescription = (
record: GenericCredWrapper, record: GenericCredWrapper<GenericVerifiableCredential>,
activeDid: string, activeDid: string,
identifiers: Array<string>, identifiers: Array<string>,
contacts: Array<Contact>, contacts: Array<Contact>,
@ -952,7 +1031,11 @@ export const claimSpecialDescription = (
"...]" "...]"
); );
} else { } else {
return issuer + " declared " + claimSummary(claim as GenericCredWrapper); return (
issuer +
" declared " +
claimSummary(claim as GenericCredWrapper<GenericVerifiableCredential>)
);
} }
}; };

28
src/libs/util.ts

@ -11,7 +11,12 @@ import {
MASTER_SETTINGS_KEY, MASTER_SETTINGS_KEY,
} from "@/db/tables/settings"; } from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer"; import {
containsHiddenDid,
GenericCredWrapper,
GenericVerifiableCredential,
OfferVerifiableCredential,
} from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer"; import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
@ -79,7 +84,9 @@ export const isGlobalUri = (uri: string) => {
return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/)); return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/));
}; };
export const isGiveAction = (veriClaim: GenericCredWrapper) => { export const isGiveAction = (
veriClaim: GenericCredWrapper<GenericVerifiableCredential>,
) => {
return veriClaim.claimType === "GiveAction"; return veriClaim.claimType === "GiveAction";
}; };
@ -95,7 +102,7 @@ export const doCopyTwoSecRedo = (text: string, fn: () => void) => {
* @param veriClaim is expected to have fields: claim, claimType, and issuer * @param veriClaim is expected to have fields: claim, claimType, and issuer
*/ */
export const isGiveRecordTheUserCanConfirm = ( export const isGiveRecordTheUserCanConfirm = (
veriClaim: GenericCredWrapper, veriClaim: GenericCredWrapper<GenericVerifiableCredential>,
activeDid: string, activeDid: string,
confirmerIdList: string[] = [], confirmerIdList: string[] = [],
) => { ) => {
@ -111,9 +118,9 @@ export const isGiveRecordTheUserCanConfirm = (
* @returns the DID of the person who offered, or undefined if hidden * @returns the DID of the person who offered, or undefined if hidden
* @param veriClaim is expected to have fields: claim and issuer * @param veriClaim is expected to have fields: claim and issuer
*/ */
export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = ( export const offerGiverDid: (
veriClaim, arg0: GenericCredWrapper<OfferVerifiableCredential>,
) => { ) => string | undefined = (veriClaim) => {
let giver; let giver;
if ( if (
veriClaim.claim.offeredBy?.identifier && veriClaim.claim.offeredBy?.identifier &&
@ -130,8 +137,13 @@ export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = (
* @returns true if the user can fulfill the offer * @returns true if the user can fulfill the offer
* @param veriClaim is expected to have fields: claim, claimType, and issuer * @param veriClaim is expected to have fields: claim, claimType, and issuer
*/ */
export const canFulfillOffer = (veriClaim: GenericCredWrapper) => { export const canFulfillOffer = (
return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim)); veriClaim: GenericCredWrapper<GenericVerifiableCredential>,
) => {
return !!(
veriClaim.claimType === "Offer" &&
offerGiverDid(veriClaim as GenericCredWrapper<OfferVerifiableCredential>)
);
}; };
// return object with paths and arrays of DIDs for any keys ending in "VisibleToDid" // return object with paths and arrays of DIDs for any keys ending in "VisibleToDid"

42
src/views/ClaimView.vue

@ -22,6 +22,16 @@
<div class="overflow-hidden"> <div class="overflow-hidden">
<h2 class="text-md font-bold"> <h2 class="text-md font-bold">
{{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }} {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }}
<button
v-if="
veriClaim.claimType === 'GiveAction' &&
veriClaim.issuer === activeDid
"
@click="onClickEditClaim"
title="Edit"
>
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1"></fa>
</button>
</h2> </h2>
<div class="text-sm"> <div class="text-sm">
<div> <div>
@ -368,6 +378,9 @@
</div> </div>
</div> </div>
</div> </div>
<span v-if="isEditedGlobalId" class="mt-2">
This record is an edited version. The latest version is being shown.
</span>
<!-- Keep the dump contents directly between > and < to avoid weird spacing. --> <!-- Keep the dump contents directly between > and < to avoid weird spacing. -->
<pre <pre
class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md" class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md"
@ -411,6 +424,7 @@ import { AxiosError } from "axios";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import * as R from "ramda"; import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
@ -422,7 +436,11 @@ import * as serverUtil from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
import { GiverReceiverInputInfo } from "@/libs/endorserServer"; import {
GenericCredWrapper,
GiverReceiverInputInfo,
OfferVerifiableCredential,
} from "@/libs/endorserServer";
@Component({ @Component({
components: { GiftedDialog, QuickNav }, components: { GiftedDialog, QuickNav },
@ -444,6 +462,7 @@ export default class ClaimView extends Vue {
fullClaim = null; fullClaim = null;
fullClaimDump = ""; fullClaimDump = "";
fullClaimMessage = ""; fullClaimMessage = "";
isEditedGlobalId = false;
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
showDidCopy = false; showDidCopy = false;
showIdCopy = false; showIdCopy = false;
@ -466,6 +485,7 @@ export default class ClaimView extends Vue {
this.fullClaim = null; this.fullClaim = null;
this.fullClaimDump = ""; this.fullClaimDump = "";
this.fullClaimMessage = ""; this.fullClaimMessage = "";
this.isEditedGlobalId = false;
this.numConfsNotVisible = 0; this.numConfsNotVisible = 0;
this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
this.veriClaimDump = ""; this.veriClaimDump = "";
@ -562,6 +582,8 @@ export default class ClaimView extends Vue {
return; return;
} }
this.isEditedGlobalId = !this.veriClaim.handleId.endsWith(claimId);
// retrieve more details on Give, Offer, or Plan // retrieve more details on Give, Offer, or Plan
if (this.veriClaim.claimType === "GiveAction") { if (this.veriClaim.claimType === "GiveAction") {
const giveUrl = const giveUrl =
@ -753,7 +775,7 @@ export default class ClaimView extends Vue {
const route = { const route = {
path: "/claim/" + encodeURIComponent(claimId), path: "/claim/" + encodeURIComponent(claimId),
}; };
this.$router.push(route).then(async () => { (this.$router as Router).push(route).then(async () => {
this.resetThisValues(); this.resetThisValues();
await this.loadClaim(claimId, this.activeDid); await this.loadClaim(claimId, this.activeDid);
}); });
@ -761,7 +783,9 @@ export default class ClaimView extends Vue {
openFulfillGiftDialog() { openFulfillGiftDialog() {
const giver: GiverReceiverInputInfo = { const giver: GiverReceiverInputInfo = {
did: libsUtil.offerGiverDid(this.veriClaim), did: libsUtil.offerGiverDid(
this.veriClaim as GenericCredWrapper<OfferVerifiableCredential>,
),
}; };
(this.$refs.customGiveDialog as GiftedDialog).open( (this.$refs.customGiveDialog as GiftedDialog).open(
giver, giver,
@ -794,5 +818,17 @@ export default class ClaimView extends Vue {
url: this.windowLocation, url: this.windowLocation,
}); });
} }
onClickEditClaim() {
const route = {
name: "gifted-details",
query: {
prevCredToEdit: JSON.stringify(this.veriClaim),
destinationPathAfter:
"/claim/" + encodeURIComponent(this.veriClaim.handleId),
},
};
(this.$router as Router).push(route);
}
} }
</script> </script>

11
src/views/ConfirmGiftView.vue

@ -407,7 +407,12 @@ import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { displayAmount, GiverReceiverInputInfo } from "@/libs/endorserServer"; import {
displayAmount,
GenericCredWrapper,
GiverReceiverInputInfo,
OfferVerifiableCredential,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { isGiveAction } from "@/libs/util"; import { isGiveAction } from "@/libs/util";
@ -767,7 +772,9 @@ export default class ClaimView extends Vue {
openFulfillGiftDialog() { openFulfillGiftDialog() {
const giver: GiverReceiverInputInfo = { const giver: GiverReceiverInputInfo = {
did: libsUtil.offerGiverDid(this.veriClaim), did: libsUtil.offerGiverDid(
this.veriClaim as GenericCredWrapper<OfferVerifiableCredential>,
),
}; };
(this.$refs.customGiveDialog as GiftedDialog).open( (this.$refs.customGiveDialog as GiftedDialog).open(
giver, giver,

119
src/views/GiftedDetails.vue

@ -175,6 +175,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import ImageMethodDialog from "@/components/ImageMethodDialog.vue"; import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@ -183,11 +184,14 @@ import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index"; import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { import {
constructGive,
createAndSubmitGive, createAndSubmitGive,
didInfo, didInfo,
editAndSubmitGive,
GenericCredWrapper,
getHeaders, getHeaders,
getPlanFromCache, getPlanFromCache,
GiveVerifiableCredential,
hydrateGive,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
@ -207,7 +211,7 @@ export default class GiftedDetails extends Vue {
amountInput = "0"; amountInput = "0";
description = ""; description = "";
destinationNameAfter = ""; destinationPathAfter = "";
givenToProject = false; givenToProject = false;
givenToRecipient = false; givenToRecipient = false;
giverDid: string | undefined; giverDid: string | undefined;
@ -217,6 +221,7 @@ export default class GiftedDetails extends Vue {
isTrade = false; isTrade = false;
message = ""; message = "";
offerId = ""; offerId = "";
prevCredToEdit?: GenericCredWrapper<GiveVerifiableCredential>;
projectId = ""; projectId = "";
projectName = "a project"; projectName = "a project";
recipientDid = ""; recipientDid = "";
@ -226,34 +231,80 @@ export default class GiftedDetails extends Vue {
libsUtil = libsUtil; libsUtil = libsUtil;
async mounted() { async mounted() {
try {
this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"]
? (JSON.parse(
(this.$route as Router).query["prevCredToEdit"],
) as GenericCredWrapper<GiveVerifiableCredential>)
: 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,
);
}
this.amountInput = this.amountInput =
(this.$route.query.amountInput as string) || this.amountInput; this.$route.query.amountInput ||
this.description = (this.$route.query.description as string) || ""; String(this.prevCredToEdit?.claim?.object?.amountOfThisGood) ||
this.destinationNameAfter = this.$route.query this.amountInput;
.destinationNameAfter as string; this.description =
this.giverDid = this.$route.query.giverDid as string; this.$route.query.description ||
this.prevCredToEdit?.claim?.description ||
this.description;
this.destinationPathAfter = this.$route.query.destinationPathAfter;
this.giverDid = (this.$route.query.giverDid ||
this.prevCredToEdit?.claim?.agent?.identifier ||
this.giverDid) as string;
this.giverName = (this.$route.query.giverName as string) || ""; this.giverName = (this.$route.query.giverName as string) || "";
this.hideBackButton = this.$route.query.hideBackButton === "true"; this.hideBackButton = this.$route.query.hideBackButton === "true";
this.message = (this.$route.query.message as string) || ""; this.message = (this.$route.query.message as string) || "";
this.offerId = this.$route.query.offerId as string; // find any offer ID
this.projectId = this.$route.query.projectId as string; const fulfills = this.prevCredToEdit?.claim?.fulfills;
this.recipientDid = this.$route.query.recipientDid as string; const fulfillsArray = Array.isArray(fulfills)
? fulfills
: fulfills
? [fulfills]
: [];
const offer = fulfillsArray.find((rec) => rec.claimType === "Offer");
this.offerId = (this.$route.query.offerId ||
offer?.identifier ||
this.offerId) as string;
// find any project ID
const project = fulfillsArray.find((rec) => rec.claimType === "PlanAction");
this.projectId = (this.$route.query.projectId ||
project?.identifier ||
this.projectId) as string;
this.recipientDid = (this.$route.query.recipientDid ||
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
this.recipientName = (this.$route.query.recipientName as string) || ""; this.recipientName = (this.$route.query.recipientName as string) || "";
this.unitCode = (this.$route.query.unitCode as string) || this.unitCode; this.unitCode = (this.$route.query.unitCode ||
this.prevCredToEdit?.claim?.object?.unitCode ||
this.unitCode) as string;
this.imageUrl = this.imageUrl =
(this.$route.query.imageUrl as string) || (this.$route.query.imageUrl as string) ||
this.prevCredToEdit?.claim?.image ||
localStorage.getItem("imageUrl") || localStorage.getItem("imageUrl") ||
""; this.imageUrl;
// this is an endpoint for sharing project info to highlight something given // this is an endpoint for sharing project info to highlight something given
// https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target // https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
if (this.$route.query.shareTitle) { if (this.$route.query.shareTitle) {
this.description = this.$route.query.shareTitle as string; this.description =
(this.$route.query.shareTitle as string) +
(this.description ? "\n" + this.description : "");
} }
if (this.$route.query.shareText) { if (this.$route.query.shareText) {
this.description = this.description =
(this.description ? this.description + " " : "") + (this.description ? this.description + "\n" : "") +
(this.$route.query.shareText as string); (this.$route.query.shareText as string);
} }
if (this.$route.query.shareUrl) { if (this.$route.query.shareUrl) {
@ -344,16 +395,16 @@ export default class GiftedDetails extends Vue {
cancel() { cancel() {
this.deleteImage(); // not awaiting, so they'll go back immediately this.deleteImage(); // not awaiting, so they'll go back immediately
if (this.destinationNameAfter) { if (this.destinationPathAfter) {
this.$router.push({ name: this.destinationNameAfter }); (this.$router as Router).push({ path: this.destinationPathAfter });
} else { } else {
this.$router.back(); (this.$router as Router).back();
} }
} }
cancelBack() { cancelBack() {
this.deleteImage(); // not awaiting, so they'll go back immediately this.deleteImage(); // not awaiting, so they'll go back immediately
this.$router.back(); (this.$router as Router).back();
} }
openImageDialog() { openImageDialog() {
@ -548,7 +599,26 @@ export default class GiftedDetails extends Vue {
? this.recipientDid ? this.recipientDid
: undefined; : undefined;
const projectId = this.givenToProject ? this.projectId : undefined; const projectId = this.givenToProject ? this.projectId : undefined;
const result = await createAndSubmitGive( let result;
if (this.prevCredToEdit) {
// don't create from a blank one in case some properties were set from a different interface
result = await editAndSubmitGive(
this.axios,
this.apiServer,
this.prevCredToEdit,
this.activeDid,
this.giverDid,
recipientDid,
this.description,
parseFloat(this.amountInput),
this.unitCode,
projectId,
this.offerId,
this.isTrade,
this.imageUrl,
);
} else {
result = await createAndSubmitGive(
this.axios, this.axios,
this.apiServer, this.apiServer,
this.activeDid, this.activeDid,
@ -562,6 +632,7 @@ export default class GiftedDetails extends Vue {
this.isTrade, this.isTrade,
this.imageUrl, this.imageUrl,
); );
}
if ( if (
result.type === "error" || result.type === "error" ||
@ -589,10 +660,10 @@ export default class GiftedDetails extends Vue {
5000, 5000,
); );
localStorage.removeItem("imageUrl"); localStorage.removeItem("imageUrl");
if (this.destinationNameAfter) { if (this.destinationPathAfter) {
this.$router.push({ name: this.destinationNameAfter }); (this.$router as Router).push({ path: this.destinationPathAfter });
} else { } else {
this.$router.back(); (this.$router as Router).back();
} }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -617,7 +688,8 @@ 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(
this.prevCredToEdit?.claim as GiveVerifiableCredential,
this.giverDid, this.giverDid,
recipientDid, recipientDid,
this.description, this.description,
@ -627,6 +699,7 @@ export default class GiftedDetails extends Vue {
this.offerId, this.offerId,
this.isTrade, this.isTrade,
this.imageUrl, this.imageUrl,
this.prevCredToEdit?.id as string,
); );
const claimStr = JSON.stringify(giveClaim); const claimStr = JSON.stringify(giveClaim);
return claimStr; return claimStr;

3
src/views/HomeView.vue

@ -89,7 +89,8 @@
:to="{ name: 'contact-qr' }" :to="{ name: 'contact-qr' }"
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md" class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
> >
Show Them {{ PASSKEYS_ENABLED ? "Default" : "Your" }} Identifier Info Show Them {{ PASSKEYS_ENABLED ? "Default" : "Your" }} Identifier
Info
</router-link> </router-link>
<div v-if="PASSKEYS_ENABLED" class="flex justify-end w-full"> <div v-if="PASSKEYS_ENABLED" class="flex justify-end w-full">
<router-link <router-link

3
src/views/NewEditProjectView.vue

@ -74,6 +74,9 @@
v-model="fullClaim.description" v-model="fullClaim.description"
maxlength="5000" maxlength="5000"
></textarea> ></textarea>
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
If you want to be contacted, be sure to include your contact information.
</div>
<div class="text-xs text-slate-500 italic -mt-3 mb-4"> <div class="text-xs text-slate-500 italic -mt-3 mb-4">
{{ fullClaim.description?.length }}/5000 max. characters {{ fullClaim.description?.length }}/5000 max. characters
</div> </div>

2
src/views/SharedPhotoView.vue

@ -114,7 +114,7 @@ export default class SharedPhotoView extends Vue {
this.$router.push({ this.$router.push({
name: "gifted-details", name: "gifted-details",
query: { query: {
destinationNameAfter: "home", destinationPathAfter: "/home",
hideBackButton: true, hideBackButton: true,
imageUrl: url, imageUrl: url,
recipientDid: this.activeDid, recipientDid: this.activeDid,

10
src/vite-env.d.ts

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
Loading…
Cancel
Save