Browse Source

add ability to confirm give directly from a project

kb/add-usage-guide
Trent Larson 8 months ago
parent
commit
9dea4066c9
  1. 14
      README.md
  2. 5
      project.task.yaml
  3. 1
      src/libs/endorserServer.ts
  4. 18
      src/libs/util.ts
  5. 24
      src/views/ClaimView.vue
  6. 4
      src/views/ContactsView.vue
  7. 117
      src/views/ProjectViewView.vue

14
README.md

@ -81,19 +81,21 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
- Check the feed without names. - Check the feed without names.
- Copy the contact URL. - Copy the contact URL.
- On the discovery page, check that they can see projects, and set a search area to see projects nearby. - On the discovery page, check that they can see projects, and set a search area to see projects nearby.
- Switch to "no identifier" to see that things look OK without any ID.
- As User #0 in another browser on the test API, add a give & a project. (See User #0 details above.) - As User #0 in another browser on the test API, add a give & a project. (See User #0 details above.)
- With the new user on the home page, see the feed that shows User #0 in network but without the name. - With the new user on the home page, see the feed that shows User #0 in network but without the name.
- As the new user on the contacts page, add User #0 as a contact. - As the new user on the contacts page, add User #0 as a contact.
- On the home page, see the feed that shows User #0 with a name. - On the home page, see the feed that shows User #0 with a name.
- Generate an ID. - Switch back to the generated identifier.
- On the home page, check that it now prompts them to get registered.
- On the account page, check that they see messages on limits. - On the account page, check that they see messages on limits.
- Register the ID from User #0. - As User #0, register the ID.
- As the new user on the home page, check that they can now record a gift. - As the new user on the home page, check that they can now record a gift.
- On the contacts page, check that they cannot register someone else yet. - On the contacts page, check that they cannot register someone else yet.
- Walk through the functions on each page. - Walk through the functions on each page.
- Test the mobile view. - Test the notifications.
- Test export & import. - Test export & import.
- Offer, deliver a give, and confirm. Create a third user and test connections.
- Test the mobile view. (Note that Firefox mobile doesn't always show bottom icons.)

5
project.task.yaml

@ -1,11 +1,11 @@
tasks: tasks:
- 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 give action executable right from an offer - make give action executable right from an offer
- make a confirmation action executable right from a give - 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
- 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)
- 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
- on ClaimView, the "ask someone" should refer to "visible" IDs, or to confirmations only if confirmations are visible - on ClaimView, the "ask someone" should refer to "visible" IDs, or to confirmations only if confirmations are visible
- give feedback when import is completed - give feedback when import is completed
@ -36,7 +36,6 @@ tasks:
- bug (that is hard to reproduce) - back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity - bug (that is hard to reproduce) - back-and-forth on discovery & project pages led to "You need an identity to load your projects." error on product page when I had an identity
- bug (that is hard to reproduce) - in Chrome, install app then delete app and try from Chrome browser and see log errors "Uncaught TypeError: self.appendDailyLog is not a function" - bug (that is hard to reproduce) - in Chrome, install app then delete app and try from Chrome browser and see log errors "Uncaught TypeError: self.appendDailyLog is not a function"
- bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent - bug (that is hard to reproduce) - on the second 'give' recorded on prod it showed me as the agent
- 01 send visibility signal as a VC and store it
- 04 remove 'rowid' references (that are sqlite-specific); may involve server - 04 remove 'rowid' references (that are sqlite-specific); may involve server
- 04 look at other examples for better UI, eg friend.tech - 04 look at other examples for better UI, eg friend.tech
- .5 Add inactive flag / end date, start date to project - .5 Add inactive flag / end date, start date to project

1
src/libs/endorserServer.ts

@ -68,6 +68,7 @@ export interface GiveServerRecord {
fullClaim: GiveVerifiableCredential; fullClaim: GiveVerifiableCredential;
handleId: string; handleId: string;
issuedAt: string; issuedAt: string;
jwtId: string;
recipientDid: string; recipientDid: string;
unit: string; unit: string;
} }

18
src/libs/util.ts

@ -6,6 +6,7 @@ import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { accountsDB, db } from "@/db/index"; 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";
// 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;
@ -19,6 +20,23 @@ 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) => {
return veriClaim.claimType === "GiveAction";
};
export const userCanConfirm = (
veriClaim: GenericServerRecord,
activeDid: string,
confirmerIdList: string[] = [],
) => {
return (
isConfirmable(veriClaim) &&
!confirmerIdList.includes(activeDid) &&
veriClaim.issuer !== activeDid &&
!containsHiddenDid(veriClaim.claim)
);
};
/** /**
* 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

24
src/views/ClaimView.vue

@ -47,7 +47,7 @@
<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()" v-if="userCanConfirm(veriClaim, activeDid, confirmerIdList)"
@click="confirmClaim(veriClaim.id)" @click="confirmClaim(veriClaim.id)"
> >
Confirm Confirm
@ -67,7 +67,7 @@
:offerId="veriClaim.handleId" :offerId="veriClaim.handleId"
/> />
<div v-if="isConfirmable()"> <div v-if="isConfirmable(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>
@ -197,7 +197,6 @@ import { AxiosError, RawAxiosRequestHeaders } 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 { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import * as util from "util";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue"; import GiftedDialog from "@/components/GiftedDialog.vue";
@ -207,6 +206,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 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";
@ -239,9 +239,10 @@ export default class ClaimView extends Vue {
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
veriClaimDump = ""; veriClaimDump = "";
util = util;
yaml = yaml; yaml = yaml;
containsHiddenDid = serverUtil.containsHiddenDid; containsHiddenDid = serverUtil.containsHiddenDid;
isConfirmable = isConfirmable;
userCanConfirm = userCanConfirm;
async created() { async created() {
await db.open(); await db.open();
@ -283,20 +284,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");
} }
isConfirmable() {
return this.veriClaim.claimType === "GiveAction";
}
userCanConfirm() {
// Note that this logic is mirrored in the template. Look for "userCanConfirm"
return (
this.isConfirmable() &&
!this.confirmerIdList.includes(this.activeDid) &&
this.veriClaim.issuer !== this.activeDid &&
!this.containsHiddenDid(this.veriClaim.claim)
);
}
offerGiverDid(): string | undefined { offerGiverDid(): string | undefined {
let giver; let giver;
if ( if (
@ -492,6 +479,7 @@ export default class ClaimView extends Vue {
} }
} }
// similar code is found in ProjectViewView
async confirmClaim() { async confirmClaim() {
if (confirm("Do you personally confirm that this is true?")) { if (confirm("Do you personally confirm that this is true?")) {
// similar logic is found in endorser-mobile // similar logic is found in endorser-mobile

4
src/views/ContactsView.vue

@ -743,7 +743,9 @@ export default class ContactsView extends Vue {
group: "alert", group: "alert",
type: "info", type: "info",
title: "Registration Success", title: "Registration Success",
text: contact.name + " has been registered.", text:
(contact.name || "That unnamed person") +
" has been registered.",
}, },
-1, -1,
); );

117
src/views/ProjectViewView.vue

@ -182,7 +182,12 @@
<span> <span>
<fa icon="user" class="fa-fw text-slate-400"></fa> <fa icon="user" class="fa-fw text-slate-400"></fa>
{{ {{
didInfo(offer.offeredByDid, activeDid, allMyDids, allContacts) serverUtil.didInfo(
offer.offeredByDid,
activeDid,
allMyDids,
allContacts,
)
}} }}
</span> </span>
<a @click="onClickLoadClaim(offer.jwtId)"> <a @click="onClickLoadClaim(offer.jwtId)">
@ -219,11 +224,15 @@
<div class="flex justify-between gap-4"> <div class="flex justify-between gap-4">
<span <span
><fa icon="user" class="fa-fw text-slate-400"></fa> ><fa icon="user" class="fa-fw text-slate-400"></fa>
{{ didInfo(give.agentDid, activeDid, allMyDids, allContacts) }} {{
serverUtil.didInfo(
give.agentDid,
activeDid,
allMyDids,
allContacts,
)
}}
</span> </span>
<a @click="onClickLoadClaim(give.jwtId)">
<fa icon="circle-info" class="pl-2 pt-1 text-slate-500"></fa>
</a>
<span v-if="give.amount"> <span v-if="give.amount">
<fa <fa
:icon="iconForUnitCode(give.unit)" :icon="iconForUnitCode(give.unit)"
@ -235,6 +244,14 @@
<fa icon="comment" class="fa-fw text-slate-400"></fa> <fa icon="comment" class="fa-fw text-slate-400"></fa>
{{ give.description }} {{ give.description }}
</div> </div>
<div class="flex justify-between">
<a @click="onClickLoadClaim(give.jwtId)">
<fa icon="circle-info" class="text-blue-500"></fa>
</a>
<a v-if="checkIsConfirmable(give)" @click="confirmClaim(give)">
<fa icon="circle-check" class="text-blue-500"></fa>
</a>
</div>
</li> </li>
</ul> </ul>
</div> </div>
@ -292,14 +309,16 @@ import { accountsDB, db } from "@/db/index";
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 { accessToken } from "@/libs/crypto"; import { accessToken } from "@/libs/crypto";
import { isGlobalUri } from "@/libs/util"; import * as libsUtil from "@/libs/util";
import { import {
didInfo, BLANK_GENERIC_SERVER_RECORD,
GenericServerRecord,
GiverInputInfo, GiverInputInfo,
GiveServerRecord, GiveServerRecord,
OfferServerRecord, OfferServerRecord,
PlanServerRecord, PlanServerRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
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";
@ -338,6 +357,8 @@ export default class ProjectViewView extends Vue {
truncateLength = 40; truncateLength = 40;
url = ""; url = "";
serverUtil = serverUtil;
async created() { async created() {
await db.open(); await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
@ -356,7 +377,7 @@ export default class ProjectViewView extends Vue {
if (pathParam) { if (pathParam) {
this.projectId = decodeURIComponent(pathParam); this.projectId = decodeURIComponent(pathParam);
} }
this.LoadProject(this.projectId, identity); this.loadProject(this.projectId, identity);
} }
public async getIdentity(activeDid: string) { public async getIdentity(activeDid: string) {
@ -387,15 +408,6 @@ export default class ProjectViewView extends Vue {
} }
// Isn't there a better way to make this available to the template? // Isn't there a better way to make this available to the template?
didInfo(
did: string,
activeDid: string,
dids: Array<string>,
contacts: Array<Contact>,
) {
return didInfo(did, activeDid, dids, contacts);
}
expandText() { expandText() {
this.expanded = true; this.expanded = true;
} }
@ -404,7 +416,7 @@ export default class ProjectViewView extends Vue {
this.expanded = false; this.expanded = false;
} }
async LoadProject(projectId: string, identity: IIdentifier) { async loadProject(projectId: string, identity: IIdentifier) {
this.projectId = projectId; this.projectId = projectId;
const url = const url =
@ -628,7 +640,7 @@ export default class ProjectViewView extends Vue {
path: "/project/" + encodeURIComponent(projectId), path: "/project/" + encodeURIComponent(projectId),
}; };
this.$router.push(route); this.$router.push(route);
this.LoadProject(projectId, await this.getIdentity(this.activeDid)); this.loadProject(projectId, await this.getIdentity(this.activeDid));
} }
getOpenStreetMapUrl() { getOpenStreetMapUrl() {
@ -681,7 +693,7 @@ export default class ProjectViewView extends Vue {
// return an HTTPS URL if it's not a global URL // return an HTTPS URL if it's not a global URL
addScheme(url: string) { addScheme(url: string) {
if (!isGlobalUri(url)) { if (!libsUtil.isGlobalUri(url)) {
return "https://" + url; return "https://" + url;
} }
return url; return url;
@ -706,5 +718,70 @@ export default class ProjectViewView extends Vue {
return url; return url;
} }
} }
checkIsConfirmable(give: GiveServerRecord) {
const giveDetails: GenericServerRecord = {
...BLANK_GENERIC_SERVER_RECORD,
claim: give.fullClaim,
claimType: "GiveAction",
issuer: give.agentDid,
};
return libsUtil.userCanConfirm(giveDetails, this.activeDid);
}
// similar code is found in ClaimView
async confirmClaim(give: GiveServerRecord) {
if (confirm("Do you personally confirm that this is true?")) {
// similar logic is found in endorser-mobile
const goodClaim = serverUtil.removeSchemaContext(
serverUtil.removeVisibleToDids(
serverUtil.addLastClaimOrHandleAsIdIfMissing(
give.fullClaim,
give.jwtId,
give.handleId,
),
),
);
const confirmationClaim: serverUtil.GenericVerifiableCredential & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
object: any;
} = {
"@context": "https://schema.org",
"@type": "AgreeAction",
object: goodClaim,
};
const result = await serverUtil.createAndSubmitClaim(
confirmationClaim,
await this.getIdentity(this.activeDid),
this.apiServer,
this.axios,
);
if (result.type === "success") {
this.$notify(
{
group: "alert",
type: "success",
title: "Success",
text: "Confirmation submitted.",
},
5000,
);
} else {
console.error("Got error submitting the confirmation:", result);
const message =
(result.error?.error as string) ||
"There was a problem submitting the confirmation. See logs for more info.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: message,
},
-1,
);
}
}
}
} }
</script> </script>

Loading…
Cancel
Save