Browse Source

allow details on a give for a providing project (so we can attach a picture)

master
Trent Larson 1 month ago
parent
commit
625acb66f0
  1. 2
      src/components/GiftedDialog.vue
  2. 17
      src/libs/endorserServer.ts
  3. 20
      src/views/ClaimView.vue
  4. 2
      src/views/ConfirmGiftView.vue
  5. 181
      src/views/GiftedDetailsView.vue
  6. 80
      src/views/HomeView.vue
  7. 2
      src/views/QuickActionBvcBeginView.vue
  8. 50
      src/views/QuickActionBvcEndView.vue

2
src/components/GiftedDialog.vue

@ -47,7 +47,7 @@
giverDid: giver?.did,
giverName: giver?.name,
offerId,
projectId,
fulfillsProjectId: projectId,
recipientDid: receiver?.did,
recipientName: receiver?.name,
unitCode,

17
src/libs/endorserServer.ts

@ -91,6 +91,7 @@ export interface GiveSummaryRecord {
issuedAt: string;
issuerDid: string;
jwtId: string;
providerPlanHandleId?: string;
recipientDid: string;
unit: string;
}
@ -530,7 +531,7 @@ const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
* @param apiServer
*/
export async function getPlanFromCache(
handleId: string,
handleId: string | undefined,
axios: Axios,
apiServer: string,
requesterDid?: string,
@ -593,7 +594,7 @@ export function hydrateGive(
fulfillsOfferHandleId?: string,
isTrade: boolean = false,
imageUrl?: string,
providers?: Array<GenericVerifiableCredential>, // typically @type & identifier
providerPlanHandleId?: string,
lastClaimId?: string,
): GiveVerifiableCredential {
// Remember: replace values or erase if it's null
@ -652,7 +653,9 @@ export function hydrateGive(
vcClaim.image = imageUrl || undefined;
vcClaim.provider = providers || undefined;
vcClaim.provider = providerPlanHandleId
? [{ "@type": "PlanAction", identifier: providerPlanHandleId }]
: undefined;
return vcClaim;
}
@ -678,7 +681,7 @@ export async function createAndSubmitGive(
fulfillsOfferHandleId?: string,
isTrade: boolean = false,
imageUrl?: string,
providers?: Array<GenericVerifiableCredential>,
providerPlanHandleId?: string,
): Promise<CreateAndSubmitClaimResult> {
const vcClaim = hydrateGive(
undefined,
@ -691,7 +694,7 @@ export async function createAndSubmitGive(
fulfillsOfferHandleId,
isTrade,
imageUrl,
providers,
providerPlanHandleId,
undefined,
);
return createAndSubmitClaim(
@ -724,7 +727,7 @@ export async function editAndSubmitGive(
fulfillsOfferHandleId?: string,
isTrade: boolean = false,
imageUrl?: string,
providers?: Array<GenericVerifiableCredential>,
providerPlanHandleId?: string,
): Promise<CreateAndSubmitClaimResult> {
const vcClaim = hydrateGive(
fullClaim.claim,
@ -737,7 +740,7 @@ export async function editAndSubmitGive(
fulfillsOfferHandleId,
isTrade,
imageUrl,
providers,
providerPlanHandleId,
fullClaim.id,
);
return createAndSubmitClaim(

20
src/views/ClaimView.vue

@ -86,6 +86,15 @@
</a>
</div>
<div v-if="veriClaim.claimType === 'PlanAction'" class="mt-4">
<router-link
:to="'/project/' + encodeURIComponent(veriClaim.id)"
class="text-blue-500 mt-2"
>
Go to Project page
</router-link>
</div>
<!-- Fullfills Links -->
<!-- fullfills links for a give -->
@ -159,18 +168,13 @@
"
class="text-blue-500 mt-4 cursor-pointer"
>
{{
provider.identifier.startsWith("did:")
? "a person..."
: "an activity..."
}}
an activity...
</a>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
@ -552,10 +556,11 @@ export default class ClaimView extends Vue {
this.fullClaimDump = "";
this.fullClaimMessage = "";
this.isEditedGlobalId = false;
this.isRegistered = false;
this.numConfsNotVisible = 0;
this.providersForGive = [];
this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
this.veriClaimDump = "";
this.veriClaimDidsVisible = {};
}
async created() {
@ -672,7 +677,6 @@ export default class ClaimView extends Vue {
this.apiServer +
"/api/v2/report/providersToGive?handleId=" +
encodeURIComponent(this.veriClaim.handleId as string);
console.log("providerUrl:", providerUrl);
const providerHeaders = await serverUtil.getHeaders(userDid);
const providerResp = await this.axios.get(providerUrl, {
headers: providerHeaders,

2
src/views/ConfirmGiftView.vue

@ -655,7 +655,7 @@ export default class ClaimView extends Vue {
}
if (this.giveDetails.fulfillsPlanHandleId) {
this.urlForNewGive +=
"&projectId=" +
"&fulfillsProjectId=" +
encodeURIComponent(this.giveDetails.fulfillsPlanHandleId);
}

181
src/views/GiftedDetailsView.vue

@ -21,12 +21,22 @@
<h1 class="text-4xl text-center font-light px-4 mb-4">What Was Given</h1>
<h1 class="text-xl font-bold text-center mb-4">
<span>From {{ giverName || "someone not named" }}</span>
<span>
From
{{
providedByProject
? providerProjectName
: providedByGiver
? giverName
: "someone not named"
}}
</span>
<br />
<span>
to
{{
givenToProject
? projectName
? fulfillsProjectName
: givenToRecipient
? recipientName
: "someone unidentified"
@ -87,7 +97,29 @@
<div class="h-7 mt-4 flex">
<input
v-if="projectId && !givenToRecipient"
v-if="providerProjectId && !providedByGiver"
type="checkbox"
class="h-6 w-6 mr-2"
v-model="providedByProject"
/>
<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="notifyUserOfProvidingProject()"
/>
<label class="text-sm mt-1">
{{
providerProjectId
? "This was provided by " + providerProjectName
: "This was not provided by a project"
}}
</label>
</div>
<div class="h-7 mt-4 flex">
<input
v-if="fulfillsProjectId && !givenToRecipient"
type="checkbox"
class="h-6 w-6 mr-2"
v-model="givenToProject"
@ -96,13 +128,13 @@
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()"
@click="notifyUserFulfillsProject()"
/>
<label class="text-sm mt-1">
{{
projectId
? "This was given to " + projectName
: "No project was chosen"
fulfillsProjectId
? "This was given to " + fulfillsProjectName
: "No recipient project was chosen"
}}
</label>
</div>
@ -211,8 +243,10 @@ export default class GiftedDetails extends Vue {
amountInput = "0";
description = "";
destinationPathAfter = "";
givenToProject = false;
givenToRecipient = false;
fulfillsProjectId = "";
fulfillsProjectName = "a project";
givenToProject = false; // basically static, based on input; if we allow changing then let's fix things (see below)
givenToRecipient = false; // basically static, based on input; if we allow changing then let's fix things (see below)
giverDid: string | undefined;
giverName = "";
hideBackButton = false;
@ -221,8 +255,10 @@ export default class GiftedDetails extends Vue {
message = "";
offerId = "";
prevCredToEdit?: GenericCredWrapper<GiveVerifiableCredential>;
projectId = "";
projectName = "a project";
providerProjectId = "";
providerProjectName = "a project";
providedByProject = false; // basically static, based on input; if we allow changing then let's fix things (see below)
providedByGiver = false; // basically static, based on input; if we allow changing then let's fix things (see below)
recipientDid = "";
recipientName = "";
showGeneralAdvanced = false;
@ -282,11 +318,31 @@ export default class GiftedDetails extends Vue {
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;
// find any fulfills project ID
const fulfillsProject = fulfillsArray.find(
(rec) => rec["@type"] === "PlanAction",
);
// eslint-disable-next-line prettier/prettier
this.fulfillsProjectId =
((this.$route as Router).query["fulfillsProjectId"] ||
fulfillsProject?.identifier ||
this.fulfillsProjectId) as string;
// find any provider project ID
const provider = this.prevCredToEdit?.claim?.provider;
const providerArray = Array.isArray(provider)
? provider
: provider
? [provider]
: [];
const providerProject = providerArray.find(
(rec) => rec["@type"] === "PlanAction",
);
this.providerProjectId = ((this.$route as Router).query[
"providerProjectId"
] ||
providerProject?.identifier ||
this.providerProjectId) as string;
this.recipientDid = ((this.$route as Router).query["recipientDid"] ||
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
@ -318,7 +374,6 @@ export default class GiftedDetails extends Vue {
this.imageUrl = (this.$route as Router).query["shareUrl"] as string;
}
try {
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
@ -352,35 +407,37 @@ export default class GiftedDetails extends Vue {
}
}
// these should be functions but something's wrong with the syntax in the <> conditional
this.givenToProject = !!this.projectId;
this.givenToProject = !!this.fulfillsProjectId;
this.givenToRecipient = !this.givenToProject && !!this.recipientDid;
// these should be functions but something's wrong with the syntax in the <> conditional
this.providedByProject = !!this.providerProjectId;
this.providedByGiver = !this.providedByProject && !!this.giverDid;
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
// 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.fulfillsProjectId) {
// console.log("Getting project name from cache", this.fulfillsProjectId);
const fulfillsProject = await getPlanFromCache(
this.fulfillsProjectId,
this.axios,
this.apiServer,
this.activeDid,
);
this.fulfillsProjectName = fulfillsProject?.name
? `the project "${fulfillsProject.name}"`
: "a project";
}
if (this.projectId) {
// console.log("Getting project name from cache", this.projectId);
const project = await getPlanFromCache(
this.projectId,
if (this.providerProjectId) {
// console.log("Getting project name from cache", this.providerProjectId);
const providerProject = await getPlanFromCache(
this.providerProjectId,
this.axios,
this.apiServer,
this.activeDid,
);
this.projectName = project?.name
? "the project: " + project.name
this.providerProjectName = providerProject?.name
? `the project "${providerProject.name}"`
: "a project";
}
}
@ -545,8 +602,35 @@ export default class GiftedDetails extends Vue {
await this.recordGive();
}
notifyUserOfProject() {
if (!this.projectId) {
notifyUserOfProvidingProject() {
// we're here because they clicked and either there is no provider project or there is a giver chosen
if (!this.providerProjectId) {
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "To select a project as a provider, you must open this page through a project.",
},
3000,
);
} else {
// no providing project was chosen
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "You cannot select both a giving project and person.",
},
3000,
);
}
}
notifyUserFulfillsProject() {
// we're here because they clicked and either there is no fulfills project or there is a recipient chosen
if (!this.fulfillsProjectId) {
this.$notify(
{
group: "alert",
@ -557,7 +641,7 @@ export default class GiftedDetails extends Vue {
3000,
);
} else {
// must be because givenToRecipient is true
// no fulfills project was chosen
this.$notify(
{
group: "alert",
@ -607,7 +691,9 @@ export default class GiftedDetails extends Vue {
const recipientDid = this.givenToRecipient
? this.recipientDid
: undefined;
const projectId = this.givenToProject ? this.projectId : undefined;
const fulfillsProjectId = this.givenToProject
? this.fulfillsProjectId
: undefined;
let result;
if (this.prevCredToEdit) {
// don't create from a blank one in case some properties were set from a different interface
@ -621,10 +707,11 @@ export default class GiftedDetails extends Vue {
this.description,
parseFloat(this.amountInput),
this.unitCode,
projectId,
fulfillsProjectId,
this.offerId,
this.isTrade,
this.imageUrl,
this.providerProjectId,
);
} else {
result = await createAndSubmitGive(
@ -636,11 +723,11 @@ export default class GiftedDetails extends Vue {
this.description,
parseFloat(this.amountInput),
this.unitCode,
projectId,
fulfillsProjectId,
this.offerId,
this.isTrade,
this.imageUrl,
[],
this.providerProjectId,
);
}
@ -697,7 +784,9 @@ export default class GiftedDetails extends Vue {
constructGiveParam() {
const recipientDid = this.givenToRecipient ? this.recipientDid : undefined;
const projectId = this.givenToProject ? this.projectId : undefined;
const fulfillsProjectId = this.givenToProject
? this.fulfillsProjectId
: undefined;
const giveClaim = hydrateGive(
this.prevCredToEdit?.claim as GiveVerifiableCredential,
this.giverDid,
@ -705,11 +794,11 @@ export default class GiftedDetails extends Vue {
this.description,
parseFloat(this.amountInput),
this.unitCode,
projectId,
fulfillsProjectId,
this.offerId,
this.isTrade,
this.imageUrl,
[],
this.providerProjectId,
this.prevCredToEdit?.id as string,
);
const claimStr = JSON.stringify(giveClaim);

80
src/views/HomeView.vue

@ -282,6 +282,15 @@
>
<fa icon="hammer" class="text-blue-500" />
</router-link>
<router-link
v-if="record.providerPlanHandleId"
:to="
'/project/' +
encodeURIComponent(record.providerPlanHandleId)
"
>
<fa icon="hammer" class="text-blue-500" />
</router-link>
</span>
</div>
<div v-if="record.image" class="flex justify-center">
@ -359,6 +368,7 @@ interface GiveRecordWithContactInfo extends GiveSummaryRecord {
profileImageUrl?: string;
};
image?: string;
providerPlanName?: string;
recipientProjectName?: string;
receiver: {
displayName: string;
@ -554,8 +564,8 @@ export default class HomeView extends Vue {
// This has indeed proven problematic. See loadMoreGives
// We should display it immediately and then get the plan later.
const plan = await getPlanFromCache(
record.fulfillsPlanHandleId || "",
const fulfillsPlan = await getPlanFromCache(
record.fulfillsPlanHandleId,
this.axios,
this.apiServer,
this.activeDid,
@ -570,8 +580,13 @@ export default class HomeView extends Vue {
if (!anyMatch && this.isFeedFilteredByNearby) {
// check if the associated project has a location inside user's search box
if (record.fulfillsPlanHandleId) {
if (plan?.locLat && plan?.locLon) {
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
if (fulfillsPlan?.locLat && fulfillsPlan?.locLon) {
if (
this.latLongInAnySearchBox(
fulfillsPlan.locLat,
fulfillsPlan.locLon,
)
) {
anyMatch = true;
}
}
@ -581,6 +596,17 @@ export default class HomeView extends Vue {
continue;
}
// checking for arrays due to legacy data
const provider = Array.isArray(claim.provider)
? claim.provider[0]
: claim.provider;
const providedByPlan = await getPlanFromCache(
provider?.identifier as string,
this.axios,
this.apiServer,
this.activeDid,
);
const newRecord: GiveRecordWithContactInfo = {
...record,
giver: didInfoForContact(
@ -590,7 +616,9 @@ export default class HomeView extends Vue {
this.allMyDids,
),
image: claim.image,
recipientProjectName: plan?.name as string,
providerPlanHandleId: provider?.identifier as string,
providerPlanName: providedByPlan?.name as string,
recipientProjectName: fulfillsPlan?.name as string,
receiver: didInfoForContact(
recipientDid,
this.activeDid,
@ -684,46 +712,62 @@ export default class HomeView extends Vue {
}
/**
* Only show giver and/or receiver info first if they're named.
* Only show giver and/or receiver info first if they're named in your contacts.
* - If only giver is named, show "... gave"
* - If only receiver is named, show "... received"
*/
const giverInfo = giveRecord.giver;
const recipientInfo = giveRecord.receiver;
// any specific names should be shown first
if (giverInfo.known && recipientInfo.known) {
// both giver and recipient are named
return `${giverInfo.displayName} gave to ${recipientInfo.displayName}: ${gaveAmount}`;
} else if (giverInfo.known) {
// giver is named but recipient is not
// giver is known but recipient is not
// show the project name if to one
if (giveRecord.recipientProjectName) {
// retrieve the project name
return `${giverInfo.displayName} gave: ${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
}
return `${giverInfo.displayName} gave: ${gaveAmount} (to the project "${giveRecord.recipientProjectName}")`;
} else {
// it's not to a project
return `${giverInfo.displayName} gave: ${gaveAmount} (to ${recipientInfo.displayName})`;
}
} else if (recipientInfo.known) {
// recipient is named but giver is not
// recipient is known but giver is not
// show the project name if from one
if (giveRecord.providerPlanName) {
return `${giverInfo.displayName} received: ${gaveAmount} (from the project "${giveRecord.providerPlanName}")`;
} else {
// it's not from a project
return `${recipientInfo.displayName} received: ${gaveAmount} (from ${giverInfo.displayName})`;
}
} else {
// neither giver nor recipient are named
// show the project name if to one
// create the part in parens
let peopleInfo = "";
if (giveRecord.providerPlanName || giveRecord.recipientProjectName) {
if (giveRecord.providerPlanName) {
peopleInfo = `from the project "${giveRecord.providerPlanName}"`;
} else {
peopleInfo = `from ${giverInfo.displayName}`;
}
if (giveRecord.recipientProjectName) {
// retrieve the project name
return `${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
peopleInfo += ` to the project "${giveRecord.recipientProjectName}"`;
} else {
peopleInfo += ` to ${recipientInfo.displayName}`;
}
// it's not to a project
let peopleInfo;
} else {
if (giverInfo.displayName === recipientInfo.displayName) {
peopleInfo = `between two who are ${giverInfo.displayName}`;
} else {
peopleInfo = `from ${giverInfo.displayName} to ${recipientInfo.displayName}`;
}
}
return gaveAmount + " (" + peopleInfo + ")";
}
}

2
src/views/QuickActionBvcBeginView.vue

@ -67,6 +67,7 @@
<script lang="ts">
import axios from "axios";
import { DateTime } from "luxon";
import { Router } from "vue-router";
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
@ -200,6 +201,7 @@ export default class QuickActionBvcBeginView extends Vue {
},
3000,
);
(this.$router as Router).push({ path: "/quick-action-bvc" });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any

50
src/views/QuickActionBvcEndView.vue

@ -106,7 +106,8 @@
class="border border-slate-400 h-6 px-2"
/>
<br />
(Everyone likes personalized messages! 😁)
(Everyone likes personalized messages! 😁 ... and for a pic:
<input type="checkbox" v-model="supplyGiftDetails" />)
</span>
<!-- This is to match input height to avoid shifting when hiding & showing. -->
<span v-else class="h-6">...</span>
@ -179,6 +180,7 @@ export default class QuickActionBvcBeginView extends Vue {
description = "breakfast";
loadingConfirms = true;
someoneGave = false;
supplyGiftDetails = false;
async created() {
this.loadingConfirms = true;
@ -262,7 +264,9 @@ export default class QuickActionBvcBeginView extends Vue {
async record() {
try {
if (this.claimsToConfirmSelected.length > 0) {
this.$notify({ group: "alert", type: "toast", title: "Sent..." }, 1000);
}
// in parallel, make a confirmation for each selected claim and send them all to the server
const confirmResults = await Promise.allSettled(
@ -304,13 +308,7 @@ export default class QuickActionBvcBeginView extends Vue {
// now send the give for the description
let giveSucceeded = false;
if (this.someoneGave) {
const providers: Array<GenericVerifiableCredential> = [
{
"@type": "PlanAction",
identifier: BVC_MEETUPS_PROJECT_CLAIM_ID,
},
];
if (this.someoneGave && !this.supplyGiftDetails) {
const giveResult = await createAndSubmitGive(
axios,
this.apiServer,
@ -324,7 +322,7 @@ export default class QuickActionBvcBeginView extends Vue {
undefined,
false,
undefined,
providers,
BVC_MEETUPS_PROJECT_CLAIM_ID,
);
giveSucceeded = giveResult.type === "success";
if (!giveSucceeded) {
@ -342,7 +340,35 @@ export default class QuickActionBvcBeginView extends Vue {
);
}
}
if (this.someoneGave && this.supplyGiftDetails) {
// we'll give a success message for the confirmations and go to the gifted details page
if (confirmsSucceeded.length > 0) {
const actions =
confirmsSucceeded.length === 1
? `Your confirmation has been recorded.`
: `Your confirmations have been recorded.`;
this.$notify(
{
group: "alert",
type: "success",
title: "Success",
text: actions,
},
3000,
);
}
(this.$router as Router).push({
name: "gifted-details",
query: {
description: this.description,
destinationPathAfter: "/",
providerProjectId:
"https://endorser.ch/entity/01J8XW5SE5NJZ34TQCC10FC18B", //BVC_MEETUPS_PROJECT_CLAIM_ID,
recipientDid: this.activeDid,
},
});
} else {
// just go ahead and print a message for all the activity
if (confirmsSucceeded.length > 0 || giveSucceeded) {
const confirms =
confirmsSucceeded.length === 1 ? "confirmation" : "confirmations";
@ -365,6 +391,10 @@ export default class QuickActionBvcBeginView extends Vue {
},
3000,
);
(this.$router as Router).push({ path: "/" });
} else {
// errors should have already shown
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any

Loading…
Cancel
Save