Browse Source

show a 'give' button directly on offers in the ProjectView

starred-projects
Trent Larson 8 months ago
parent
commit
be6ec6745a
  1. 4
      project.task.yaml
  2. 9
      src/components/GiftedDialog.vue
  3. 2
      src/libs/endorserServer.ts
  4. 38
      src/libs/util.ts
  5. 2
      src/main.ts
  6. 62
      src/views/ClaimView.vue
  7. 60
      src/views/ProjectViewView.vue

4
project.task.yaml

@ -1,9 +1,9 @@
tasks: tasks:
- make give action executable right from an offer
- make a confirmation action executable right from a give
- link to the project claim from the project screen - link to the project claim from the project screen
- supply the projectId to the OfferDialog just like we do with the offerId
- the confirm button on each give on the ProjectViewView page doesn't have all the context of the ClaimView page, so it can show sometimes inappropriately; consider consolidation
- choose an agent via a contact chooser (not just copy-paste a DID) - choose an agent via a contact chooser (not just copy-paste a DID)
- make the "give" on contact screen work like other give (allowing donation vs current blank) - make the "give" on contact screen work like other give (allowing donation vs current blank)
- check that 'show more contacts' from the contact-give-list on the project screen includes project ID - check that 'show more contacts' from the contact-give-list on the project screen includes project ID

9
src/components/GiftedDialog.vue

@ -90,7 +90,6 @@ export default class GiftedDialog extends Vue {
@Prop message = ""; @Prop message = "";
@Prop projectId = ""; @Prop projectId = "";
@Prop offerId = "";
@Prop showGivenToUser = false; @Prop showGivenToUser = false;
activeDid = ""; activeDid = "";
@ -103,6 +102,7 @@ export default class GiftedDialog extends Vue {
description = ""; description = "";
givenToUser = false; givenToUser = false;
isTrade = false; isTrade = false;
offerId = "";
unitCode = "HUR"; unitCode = "HUR";
visible = false; visible = false;
@ -117,8 +117,8 @@ export default class GiftedDialog extends Vue {
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
UNIT_LONG: Record<string, string> = { UNIT_LONG: Record<string, string> = {
"BTC": "BTC", "BTC": "Bitcoin",
"ETH": "ETH", "ETH": "Ethereum",
"HUR": "hours", "HUR": "hours",
"USD": "dollars", "USD": "dollars",
}; };
@ -152,7 +152,7 @@ export default class GiftedDialog extends Vue {
} }
} }
open(giver: GiverInputInfo) { open(giver?: GiverInputInfo, offerId?: string) {
this.description = ""; this.description = "";
this.giver = giver || {}; this.giver = giver || {};
if (!this.giver.name) { if (!this.giver.name) {
@ -166,6 +166,7 @@ export default class GiftedDialog extends Vue {
// if we show "given to user" selection, default checkbox to true // if we show "given to user" selection, default checkbox to true
this.givenToUser = this.showGivenToUser; this.givenToUser = this.showGivenToUser;
this.amountInput = "0"; this.amountInput = "0";
this.offerId = offerId || "";
this.visible = true; this.visible = true;
} }

2
src/libs/endorserServer.ts

@ -76,6 +76,8 @@ export interface GiveServerRecord {
export interface OfferServerRecord { export interface OfferServerRecord {
amount: number; amount: number;
amountGiven: number; amountGiven: number;
fullClaim: OfferVerifiableCredential;
handleId: string;
offeredByDid: string; offeredByDid: string;
recipientDid: string; recipientDid: string;
requirementsMet: boolean; requirementsMet: boolean;

38
src/libs/util.ts

@ -7,6 +7,7 @@ import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer"; import { GenericServerRecord, containsHiddenDid } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer; const Buffer = require("buffer/").Buffer;
@ -20,23 +21,54 @@ export const isGlobalUri = (uri: string) => {
export const ONBOARD_MESSAGE = export const ONBOARD_MESSAGE =
"1) Check that they have entered their name on the profile page in their device. 2) Add them to your Contacts by scanning with the QR icon that is by the input box. 3) Click the person icon to register them. 4) Have them go to their Contact page and scan your QR to add you to their list."; "1) Check that they have entered their name on the profile page in their device. 2) Add them to your Contacts by scanning with the QR icon that is by the input box. 3) Click the person icon to register them. 4) Have them go to their Contact page and scan your QR to add you to their list.";
export const isConfirmable = (veriClaim: GenericServerRecord) => { export const giveIsConfirmable = (veriClaim: GenericServerRecord) => {
return veriClaim.claimType === "GiveAction"; return veriClaim.claimType === "GiveAction";
}; };
export const userCanConfirm = ( /**
* @returns true if the user can confirm the claim
* @param veriClaim is expected to have fields: claim, claimType, and issuer
*/
export const giveRecordTheUserCanConfirm = (
veriClaim: GenericServerRecord, veriClaim: GenericServerRecord,
activeDid: string, activeDid: string,
confirmerIdList: string[] = [], confirmerIdList: string[] = [],
) => { ) => {
return ( return (
isConfirmable(veriClaim) && giveIsConfirmable(veriClaim) &&
!confirmerIdList.includes(activeDid) && !confirmerIdList.includes(activeDid) &&
veriClaim.issuer !== activeDid && veriClaim.issuer !== activeDid &&
!containsHiddenDid(veriClaim.claim) !containsHiddenDid(veriClaim.claim)
); );
}; };
/**
* @returns the DID of the person who offered, or undefined if hidden
* @param veriClaim is expected to have fields: claim and issuer
*/
export const offerGiverDid: (
arg0: GenericServerRecord,
) => string | undefined = (veriClaim) => {
let giver;
if (
veriClaim.claim.offeredBy?.identifier &&
!serverUtil.isHiddenDid(veriClaim.claim.offeredBy.identifier as string)
) {
giver = veriClaim.claim.offeredBy.identifier;
} else if (veriClaim.issuer && !serverUtil.isHiddenDid(veriClaim.issuer)) {
giver = veriClaim.issuer;
}
return giver;
};
/**
* @returns true if the user can fulfill the offer
* @param veriClaim is expected to have fields: claim, claimType, and issuer
*/
export const canFulfillOffer = (veriClaim: GenericServerRecord) => {
return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim));
};
/** /**
* Generates a new identity, saves it to the database, and sets it as the active identity. * Generates a new identity, saves it to the database, and sets it as the active identity.
* @return {Promise<string>} with the DID of the new identity * @return {Promise<string>} with the DID of the new identity

2
src/main.ts

@ -39,6 +39,7 @@ import {
faGift, faGift,
faGlobe, faGlobe,
faHand, faHand,
faHandHoldingHeart,
faHouseChimney, faHouseChimney,
faLocationDot, faLocationDot,
faLongArrowAltLeft, faLongArrowAltLeft,
@ -93,6 +94,7 @@ library.add(
faGift, faGift,
faGlobe, faGlobe,
faHand, faHand,
faHandHoldingHeart,
faHouseChimney, faHouseChimney,
faLocationDot, faLocationDot,
faLongArrowAltLeft, faLongArrowAltLeft,

62
src/views/ClaimView.vue

@ -47,27 +47,31 @@
<div class="columns-3"> <div class="columns-3">
<button <button
class="col-span-1 bg-blue-600 text-white px-4 py-2 rounded-md" class="col-span-1 bg-blue-600 text-white px-4 py-2 rounded-md"
v-if="userCanConfirm(veriClaim, activeDid, confirmerIdList)" v-if="
libsUtil.giveRecordTheUserCanConfirm(
veriClaim,
activeDid,
confirmerIdList,
)
"
@click="confirmClaim(veriClaim.id)" @click="confirmClaim(veriClaim.id)"
> >
Confirm Confirm
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" />
</button> </button>
<button <button
v-if="canFulfillOffer()" v-if="libsUtil.canFulfillOffer(veriClaim)"
@click="openGiftDialog()" @click="openFulfillGiftDialog()"
class="col-span-1 block w-fit text-center text-md bg-blue-600 text-white px-1.5 py-2 rounded-md" class="col-span-1 block w-fit text-center text-md bg-blue-600 text-white px-1.5 py-2 rounded-md"
> >
Affirm Delivery Affirm Delivery
<fa icon="hand-holding-heart" class="ml-2 text-white cursor-pointer" />
</button> </button>
</div> </div>
<GiftedDialog <GiftedDialog ref="customGiveDialog" message="Offer fulfilled by" />
ref="customGiveDialog"
message="Offer fulfilled by"
:offerId="veriClaim.handleId"
/>
<div v-if="isConfirmable(veriClaim)"> <div v-if="libsUtil.giveIsConfirmable(veriClaim)">
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2> <h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2>
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span> <span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
@ -148,7 +152,7 @@
You cannot confirm this because you issued this claim, so you already You cannot confirm this because you issued this claim, so you already
count as confirming it. count as confirming it.
</div> </div>
<div v-else-if="containsHiddenDid(veriClaim.claim)"> <div v-else-if="serverUtil.containsHiddenDid(veriClaim.claim)">
You cannot confirm this because it contains hidden identifiers. You cannot confirm this because it contains hidden identifiers.
</div> </div>
</div> </div>
@ -206,7 +210,7 @@ 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 { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import * as serverUtil from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer";
import { isConfirmable, userCanConfirm } from "@/libs/util"; import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import { Account } from "@/db/tables/accounts"; import { Account } from "@/db/tables/accounts";
@ -240,9 +244,8 @@ export default class ClaimView extends Vue {
veriClaimDump = ""; veriClaimDump = "";
yaml = yaml; yaml = yaml;
containsHiddenDid = serverUtil.containsHiddenDid; libsUtil = libsUtil;
isConfirmable = isConfirmable; serverUtil = serverUtil;
userCanConfirm = userCanConfirm;
async created() { async created() {
await db.open(); await db.open();
@ -284,28 +287,6 @@ export default class ClaimView extends Vue {
: text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1"); : text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
} }
offerGiverDid(): string | undefined {
let giver;
if (
this.veriClaim.claim.offeredBy?.identifier &&
!serverUtil.isHiddenDid(
this.veriClaim.claim.offeredBy.identifier as string,
)
) {
giver = this.veriClaim.claim.offeredBy.identifier;
} else if (
this.veriClaim.issuer &&
!serverUtil.isHiddenDid(this.veriClaim.issuer)
) {
giver = this.veriClaim.issuer;
}
return giver;
}
canFulfillOffer() {
return this.veriClaim.claimType === "Offer" && this.offerGiverDid();
}
totalConfirmers() { totalConfirmers() {
return ( return (
this.numConfsNotVisible + this.numConfsNotVisible +
@ -531,11 +512,14 @@ export default class ClaimView extends Vue {
} }
} }
openGiftDialog() { openFulfillGiftDialog() {
const giver: GiverInputInfo = { const giver: GiverInputInfo = {
did: this.offerGiverDid(), did: libsUtil.offerGiverDid(this.veriClaim),
}; };
(this.$refs.customGiveDialog as GiftedDialog).open(giver); (this.$refs.customGiveDialog as GiftedDialog).open(
giver,
this.veriClaim.handleId,
);
} }
} }
</script> </script>

60
src/views/ProjectViewView.vue

@ -123,7 +123,7 @@
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5"> <ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
<li @click="openGiftDialog()"> <li @click="openGiftDialog()">
<EntityIcon <EntityIcon
:entityId="null" :entityId="undefined"
:iconSize="64" :iconSize="64"
class="mx-auto border border-slate-300 rounded-md mb-1" class="mx-auto border border-slate-300 rounded-md mb-1"
></EntityIcon> ></EntityIcon>
@ -198,12 +198,24 @@
</span> </span>
</div> </div>
<div v-if="offer.objectDescription" class="text-slate-500"> <div v-if="offer.objectDescription" class="text-slate-500">
<fa icon="comment" class="fa-fw text-slate-400"></fa> <fa icon="comment" class="fa-fw text-slate-400" />
{{ offer.objectDescription }} {{ offer.objectDescription }}
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<a @click="onClickLoadClaim(offer.jwtId)" class="cursor-pointer"> <a
<fa icon="circle-info" class="pl-2 pt-1 text-blue-500"></fa> @click="onClickLoadClaim(offer.jwtId as string)"
class="cursor-pointer"
>
<fa icon="circle-info" class="pl-2 pt-1 text-blue-500" />
</a>
<a
v-if="checkIsFulfillable(offer)"
@click="onClickFulfillGiveToOffer(offer)"
>
<fa
icon="hand-holding-heart"
class="text-blue-500 cursor-pointer"
/>
</a> </a>
</div> </div>
</li> </li>
@ -243,15 +255,15 @@
</span> </span>
</div> </div>
<div v-if="give.description" class="text-slate-500"> <div v-if="give.description" class="text-slate-500">
<fa icon="comment" class="fa-fw text-slate-400"></fa> <fa icon="comment" class="fa-fw text-slate-400" />
{{ give.description }} {{ give.description }}
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<a @click="onClickLoadClaim(give.jwtId)"> <a @click="onClickLoadClaim(give.jwtId)">
<fa icon="circle-info" class="text-blue-500 cursor-pointer"></fa> <fa icon="circle-info" class="text-blue-500 cursor-pointer" />
</a> </a>
<a v-if="checkIsConfirmable(give)" @click="confirmClaim(give)"> <a v-if="checkIsConfirmable(give)" @click="confirmClaim(give)">
<fa icon="circle-check" class="text-blue-500 cursor-pointer"></fa> <fa icon="circle-check" class="text-blue-500 cursor-pointer" />
</a> </a>
</div> </div>
</li> </li>
@ -359,6 +371,7 @@ export default class ProjectViewView extends Vue {
truncateLength = 40; truncateLength = 40;
url = ""; url = "";
libsUtil = libsUtil;
serverUtil = serverUtil; serverUtil = serverUtil;
async created() { async created() {
@ -370,7 +383,7 @@ export default class ProjectViewView extends Vue {
await accountsDB.open(); await accountsDB.open();
const accounts = accountsDB.accounts; const accounts = accountsDB.accounts;
const accountsArr = await accounts?.toArray(); const accountsArr: Account[] = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did); this.allMyDids = accountsArr.map((acc) => acc.did);
const account = accountsArr.find((acc) => acc.did === this.activeDid); const account = accountsArr.find((acc) => acc.did === this.activeDid);
const identity = JSON.parse(account?.identity || "null"); const identity = JSON.parse(account?.identity || "null");
@ -659,7 +672,7 @@ export default class ProjectViewView extends Vue {
); );
} }
openGiftDialog(contact: GiverInputInfo) { openGiftDialog(contact?: GiverInputInfo) {
(this.$refs.customGiveDialog as GiftedDialog).open(contact); (this.$refs.customGiveDialog as GiftedDialog).open(contact);
} }
@ -674,6 +687,33 @@ export default class ProjectViewView extends Vue {
this.$router.push(route); this.$router.push(route);
} }
checkIsFulfillable(offer: OfferServerRecord) {
const offerRecord: GenericServerRecord = {
...BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim,
claimType: "Offer",
issuer: offer.offeredByDid,
};
console.log(
"checking for can fulfill ",
libsUtil.canFulfillOffer(offerRecord),
offerRecord,
);
return libsUtil.canFulfillOffer(offerRecord);
}
onClickFulfillGiveToOffer(offer: OfferServerRecord) {
const offerRecord: GenericServerRecord = {
...BLANK_GENERIC_SERVER_RECORD,
claim: offer.fullClaim,
issuer: offer.offeredByDid,
};
const giver: GiverInputInfo = {
did: libsUtil.offerGiverDid(offerRecord),
};
(this.$refs.customGiveDialog as GiftedDialog).open(giver, offer.handleId);
}
UNIT_CODES: Record<string, Record<string, string>> = { UNIT_CODES: Record<string, Record<string, string>> = {
BTC: { BTC: {
name: "Bitcoin", name: "Bitcoin",
@ -728,7 +768,7 @@ export default class ProjectViewView extends Vue {
claimType: "GiveAction", claimType: "GiveAction",
issuer: give.agentDid, issuer: give.agentDid,
}; };
return libsUtil.userCanConfirm(giveDetails, this.activeDid); return libsUtil.giveRecordTheUserCanConfirm(giveDetails, this.activeDid);
} }
// similar code is found in ClaimView // similar code is found in ClaimView

Loading…
Cancel
Save