Trent Larson
5 months ago
11 changed files with 992 additions and 34 deletions
@ -0,0 +1,933 @@ |
|||||
|
<template> |
||||
|
<QuickNav /> |
||||
|
<!-- CONTENT --> |
||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> |
||||
|
<!-- Breadcrumb --> |
||||
|
<div id="ViewBreadcrumb" class="mb-8"> |
||||
|
<h1 class="text-lg text-center font-light relative px-7"> |
||||
|
<!-- Back --> |
||||
|
<button |
||||
|
@click="$router.go(-1)" |
||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" |
||||
|
> |
||||
|
<fa icon="chevron-left" class="fa-fw" /> |
||||
|
</button> |
||||
|
<span |
||||
|
v-if=" |
||||
|
libsUtil.isGiveRecordTheUserCanConfirm( |
||||
|
veriClaim, |
||||
|
activeDid, |
||||
|
confirmerIdList, |
||||
|
) |
||||
|
" |
||||
|
> |
||||
|
Do you agree? |
||||
|
</span> |
||||
|
<span v-else> Details </span> |
||||
|
</h1> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="detailsForGive"> |
||||
|
<div class="flex justify-center"> |
||||
|
<button |
||||
|
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md" |
||||
|
v-if=" |
||||
|
libsUtil.isGiveRecordTheUserCanConfirm( |
||||
|
veriClaim, |
||||
|
activeDid, |
||||
|
confirmerIdList, |
||||
|
) |
||||
|
" |
||||
|
@click="confirmConfirmClaim()" |
||||
|
> |
||||
|
Confirm |
||||
|
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" /> |
||||
|
</button> |
||||
|
<button |
||||
|
v-else |
||||
|
@click="notifyWhyCannotConfirm()" |
||||
|
class="col-span-1 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-4 py-2 rounded-md" |
||||
|
> |
||||
|
Confirm |
||||
|
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" /> |
||||
|
</button> |
||||
|
<a |
||||
|
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-4 py-2 rounded-md" |
||||
|
:href="urlForNewGive" |
||||
|
> |
||||
|
Record One of Your Own |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Details --> |
||||
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"> |
||||
|
<div class="block flex gap-4 overflow-hidden"> |
||||
|
<div class="overflow-hidden"> |
||||
|
<div class="text-sm"> |
||||
|
<div> |
||||
|
<fa icon="arrow-down" class="fa-fw text-slate-400" /> |
||||
|
{{ giverName }} |
||||
|
</div> |
||||
|
<div class="ml-6">gave</div> |
||||
|
<div v-if="veriClaim.claim?.object"> |
||||
|
<fa icon="hand-holding-dollar" class="fa-fw text-slate-400" /> |
||||
|
{{ |
||||
|
displayAmount( |
||||
|
veriClaim.claim.object.unitCode, |
||||
|
veriClaim.claim.object.amountOfThisGood, |
||||
|
) |
||||
|
}} |
||||
|
600 |
||||
|
</div> |
||||
|
<div> |
||||
|
<fa icon="message" class="fa-fw text-slate-400" /> |
||||
|
{{ veriClaim.claim?.object ? "and" : "" }} |
||||
|
{{ veriClaim.claim?.description }} |
||||
|
</div> |
||||
|
<div> |
||||
|
<fa icon="arrow-up" class="fa-fw text-slate-400" /> |
||||
|
to {{ recipientName }} |
||||
|
</div> |
||||
|
<div> |
||||
|
<fa icon="calendar" class="fa-fw text-slate-400" /> |
||||
|
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }} |
||||
|
</div> |
||||
|
|
||||
|
<!-- Fullfills Links --> |
||||
|
|
||||
|
<!-- fullfills links for a give --> |
||||
|
<div class="mt-2" v-if="detailsForGive?.fulfillsPlanHandleId"> |
||||
|
<router-link |
||||
|
:to=" |
||||
|
'/project/' + |
||||
|
encodeURIComponent(detailsForGive?.fulfillsPlanHandleId) |
||||
|
" |
||||
|
class="text-blue-500 mt-2 cursor-pointer" |
||||
|
target="_blank" |
||||
|
> |
||||
|
This fulfills a bigger plan |
||||
|
<fa icon="arrow-up-right-from-square" class="fa-fw" /> |
||||
|
</router-link> |
||||
|
</div> |
||||
|
<!-- if there's another, it's probably fulfilling an offer, too --> |
||||
|
<div |
||||
|
v-if=" |
||||
|
detailsForGive?.fulfillsType && |
||||
|
detailsForGive?.fulfillsType !== 'PlanAction' && |
||||
|
detailsForGive?.fulfillsHandleId |
||||
|
" |
||||
|
> |
||||
|
<!-- router-link to /claim/ only changes URL path --> |
||||
|
<router-link |
||||
|
:to=" |
||||
|
'/claim/' + |
||||
|
encodeURIComponent(detailsForGive?.fulfillsHandleId) |
||||
|
" |
||||
|
class="text-blue-500 mt-2 cursor-pointer" |
||||
|
target="_blank" |
||||
|
> |
||||
|
This fulfills |
||||
|
{{ |
||||
|
capitalizeAndInsertSpacesBeforeCapsWithAPrefix( |
||||
|
detailsForGive.fulfillsType, |
||||
|
) |
||||
|
}} |
||||
|
<fa icon="arrow-up-right-from-square" class="fa-fw" /> |
||||
|
</router-link> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="mt-2"> |
||||
|
<fa icon="comment" class="fa-fw text-slate-400" /> |
||||
|
{{ issuerName }} said that. |
||||
|
</div> |
||||
|
|
||||
|
<div class="flex justify-center"> |
||||
|
<button |
||||
|
v-if=" |
||||
|
libsUtil.isGiveRecordTheUserCanConfirm( |
||||
|
veriClaim, |
||||
|
activeDid, |
||||
|
confirmerIdList, |
||||
|
) |
||||
|
" |
||||
|
@click="confirmConfirmClaim()" |
||||
|
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md" |
||||
|
> |
||||
|
Confirm |
||||
|
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" /> |
||||
|
</button> |
||||
|
<button |
||||
|
v-else |
||||
|
@click="notifyWhyCannotConfirm()" |
||||
|
class="col-span-1 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-4 py-2 rounded-md" |
||||
|
> |
||||
|
Confirm |
||||
|
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" /> |
||||
|
</button> |
||||
|
<a |
||||
|
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-4 py-2 rounded-md" |
||||
|
:href="urlForNewGive" |
||||
|
> |
||||
|
Record One of Your Own |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="libsUtil.isGiveAction(veriClaim)" class="mt-4"> |
||||
|
<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-else-if="totalConfirmers() === 1"> |
||||
|
One person has confirmed this. |
||||
|
</span> |
||||
|
<span v-else> |
||||
|
{{ totalConfirmers() }} people have confirmed this. |
||||
|
</span> |
||||
|
|
||||
|
<div v-if="totalConfirmers() > 0"> |
||||
|
<div |
||||
|
v-if=" |
||||
|
confirmerIdList.length === 0 && confsVisibleToIdList.length === 0 |
||||
|
" |
||||
|
> |
||||
|
Nobody that you know confirmed this claim, nor do they have any |
||||
|
confirmers in their network. |
||||
|
</div> |
||||
|
|
||||
|
<div |
||||
|
v-if=" |
||||
|
confirmerIdList.length === 0 && confsVisibleToIdList.length > 0 |
||||
|
" |
||||
|
> |
||||
|
<!-- Only show if this person has links to confirmers (below). --> |
||||
|
Nobody that you know has issued or confirmed this claim. |
||||
|
</div> |
||||
|
<div v-if="confirmerIdList.length > 0"> |
||||
|
The following people have issued or confirmed this claim. |
||||
|
<ul class="ml-4"> |
||||
|
<li |
||||
|
v-for="confirmerId in confirmerIdList" |
||||
|
:key="confirmerId" |
||||
|
class="list-disc ml-4" |
||||
|
> |
||||
|
<div class="flex gap-4"> |
||||
|
<div class="grow overflow-hidden"> |
||||
|
<div class="text-sm"> |
||||
|
{{ didInfo(confirmerId) }} |
||||
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(confirmerId)"> |
||||
|
<button |
||||
|
@click=" |
||||
|
copyToClipboard( |
||||
|
'The DID of ' + confirmerId, |
||||
|
confirmerId, |
||||
|
) |
||||
|
" |
||||
|
> |
||||
|
<fa icon="copy" class="text-slate-400 fa-fw" /> |
||||
|
</button> |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
|
||||
|
<!-- |
||||
|
Never need to show this message: |
||||
|
"Nobody that you know can see someone who has confirmed this claim." |
||||
|
|
||||
|
If there is nobody in the confirmerIdList then we'll show the combined "nobody" message. |
||||
|
If there is somebody in the confirmerIdList then that's all they need to show. |
||||
|
--> |
||||
|
|
||||
|
<!-- Now show anyone linked to confirmers. --> |
||||
|
<div v-if="confsVisibleToIdList.length > 0"> |
||||
|
The following people can connect you with people who have issued or |
||||
|
confirmed this claim. |
||||
|
<ul class="ml-4"> |
||||
|
<li |
||||
|
v-for="confsVisibleTo in confsVisibleToIdList" |
||||
|
:key="confsVisibleTo" |
||||
|
class="list-disc ml-4" |
||||
|
> |
||||
|
<div class="flex gap-4"> |
||||
|
<div class="grow overflow-hidden"> |
||||
|
<div class="text-sm"> |
||||
|
{{ didInfo(confsVisibleTo) }} |
||||
|
<span |
||||
|
v-if="!serverUtil.isEmptyOrHiddenDid(confsVisibleTo)" |
||||
|
> |
||||
|
<button |
||||
|
@click=" |
||||
|
copyToClipboard( |
||||
|
'The DID of ' + confsVisibleTo, |
||||
|
confsVisibleTo, |
||||
|
) |
||||
|
" |
||||
|
> |
||||
|
<fa icon="copy" class="text-slate-400 fa-fw" /> |
||||
|
</button> |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- explain if user cannot confirm --> |
||||
|
<!-- Note that these conditions are mirrored in userCanConfirm(). --> |
||||
|
<div v-if="confirmerIdList.includes(activeDid)"> |
||||
|
You have confirmed this claim. |
||||
|
</div> |
||||
|
<div v-else-if="veriClaim.issuer == activeDid"> |
||||
|
You cannot confirm this because you issued this claim, so you already |
||||
|
count as confirming it. |
||||
|
</div> |
||||
|
<div v-else-if="serverUtil.containsHiddenDid(veriClaim.claim)"> |
||||
|
You cannot confirm this because it contains hidden identifiers. |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<h2 |
||||
|
class="font-bold uppercase text-xl text-blue-500 mt-8 mb-2 cursor-pointer" |
||||
|
@click="showDetails = !showDetails" |
||||
|
> |
||||
|
{{ serverUtil.containsHiddenDid(veriClaim) ? "Visible " : "" }}Details |
||||
|
<span v-if="!showDetails">...</span> |
||||
|
</h2> |
||||
|
<div v-if="showDetails"> |
||||
|
<div |
||||
|
v-if=" |
||||
|
serverUtil.containsHiddenDid(veriClaim) && |
||||
|
R.isEmpty(veriClaimDidsVisible) |
||||
|
" |
||||
|
class="mb-2" |
||||
|
> |
||||
|
Some of the details are not visible to you; they show as "HIDDEN". |
||||
|
They are not visible to any of your direct contacts, either. |
||||
|
<span v-if="canShare"> |
||||
|
If you'd like to ask any of your contacts to take a look and see if |
||||
|
their contacts can see more details, |
||||
|
<a @click="onClickShareClaim()" class="text-blue-500" |
||||
|
>click to send them this info</a |
||||
|
> |
||||
|
and see if they are willing to make an introduction. |
||||
|
</span> |
||||
|
<span v-else> |
||||
|
If you'd like to ask any of your contacts to take a look and see if |
||||
|
their contacts can see more details, |
||||
|
<a |
||||
|
@click="copyToClipboard('Location', windowLocation.href)" |
||||
|
class="text-blue-500" |
||||
|
>share this page with them</a |
||||
|
> |
||||
|
and see if they are willing to make an introduction. |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="!R.isEmpty(veriClaimDidsVisible)"> |
||||
|
Some of the details are not visible to you but they are visible to |
||||
|
some of your contacts. |
||||
|
<span v-if="canShare"> |
||||
|
If you'd like an introduction, |
||||
|
<a @click="onClickShareClaim()" class="text-blue-500" |
||||
|
>click to share the information with them and ask if they'll tell |
||||
|
you more about the participants.</a |
||||
|
> |
||||
|
</span> |
||||
|
<span v-else> |
||||
|
If you'd like an introduction, |
||||
|
<a |
||||
|
@click="copyToClipboard('Location', windowLocation.href)" |
||||
|
class="text-blue-500" |
||||
|
>share this page with them and ask if they'll tell you more about |
||||
|
about the participants.</a |
||||
|
> |
||||
|
</span> |
||||
|
|
||||
|
<div |
||||
|
v-for="(visibleDidPath, index) of Object.keys(veriClaimDidsVisible)" |
||||
|
:key="index" |
||||
|
class="list-disc p-4" |
||||
|
> |
||||
|
<div class="text-sm"> |
||||
|
<fa icon="minus" class="fa-fw" /> |
||||
|
The {{ visibleDidPath }} is visible to: |
||||
|
</div> |
||||
|
<div class="ml-12 p-1"> |
||||
|
<ul> |
||||
|
<li |
||||
|
v-for="(visDid, idx2) of veriClaimDidsVisible[visibleDidPath]" |
||||
|
:key="idx2" |
||||
|
class="list-disc" |
||||
|
> |
||||
|
<div class="text-sm mt-2"> |
||||
|
<span> |
||||
|
{{ didInfo(visDid) }} |
||||
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(visDid)"> |
||||
|
<button |
||||
|
@click=" |
||||
|
copyToClipboard('The DID of ' + visDid, visDid) |
||||
|
" |
||||
|
> |
||||
|
<fa icon="copy" class="text-slate-400 fa-fw" /> |
||||
|
</button> |
||||
|
</span> |
||||
|
<span v-if="veriClaim.publicUrls?.[visDid]" |
||||
|
>, found at |
||||
|
<fa icon="globe" class="fa-fw text-slate-400" /> |
||||
|
<a |
||||
|
:href="veriClaim.publicUrls?.[visDid]" |
||||
|
class="text-blue-500" |
||||
|
>{{ |
||||
|
veriClaim.publicUrls[visDid].substring( |
||||
|
veriClaim.publicUrls[visDid].indexOf("//") + 2, |
||||
|
) |
||||
|
}} |
||||
|
</a> |
||||
|
</span> |
||||
|
</span> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Keep the dump contents directly between > and < to avoid weird spacing. --> |
||||
|
<pre |
||||
|
class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md" |
||||
|
>{{ veriClaimDump }}</pre |
||||
|
> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div v-else>This does not have details to confirm.</div> |
||||
|
|
||||
|
<div class="mt-4"> |
||||
|
<a |
||||
|
@click="showClaimPage(veriClaim.id)" |
||||
|
class="text-blue-500 cursor-pointer" |
||||
|
> |
||||
|
<fa icon="file-lines" class="pl-2" /> |
||||
|
All Generic Info |
||||
|
</a> |
||||
|
</div> |
||||
|
</section> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { AxiosError, RawAxiosRequestHeaders } from "axios"; |
||||
|
import * as yaml from "js-yaml"; |
||||
|
import * as R from "ramda"; |
||||
|
import { IIdentifier } from "@veramo/core"; |
||||
|
import { Component, Vue } from "vue-facing-decorator"; |
||||
|
import { useClipboard } from "@vueuse/core"; |
||||
|
|
||||
|
import GiftedDialog from "@/components/GiftedDialog.vue"; |
||||
|
import QuickNav from "@/components/QuickNav.vue"; |
||||
|
import { NotificationIface } from "@/constants/app"; |
||||
|
import { accountsDB, db } from "@/db/index"; |
||||
|
import { Account } from "@/db/tables/accounts"; |
||||
|
import { Contact } from "@/db/tables/contacts"; |
||||
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; |
||||
|
import { accessToken } from "@/libs/crypto"; |
||||
|
import * as serverUtil from "@/libs/endorserServer"; |
||||
|
import { displayAmount, GiverReceiverInputInfo } from "@/libs/endorserServer"; |
||||
|
import * as libsUtil from "@/libs/util"; |
||||
|
import { isGiveAction } from "@/libs/util"; |
||||
|
|
||||
|
@Component({ |
||||
|
methods: { displayAmount }, |
||||
|
components: { GiftedDialog, QuickNav }, |
||||
|
}) |
||||
|
export default class ClaimView extends Vue { |
||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void; |
||||
|
|
||||
|
accountIdentityStr: string = "null"; |
||||
|
activeDid = ""; |
||||
|
allMyDids: Array<string> = []; |
||||
|
allContacts: Array<Contact> = []; |
||||
|
apiServer = ""; |
||||
|
|
||||
|
canShare = false; |
||||
|
confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer |
||||
|
confsVisibleErrorMessage = ""; |
||||
|
confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer |
||||
|
detailsForGive = null; |
||||
|
giverName = ""; |
||||
|
issuerName = ""; |
||||
|
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible |
||||
|
recipientName = ""; |
||||
|
showDetails = false; |
||||
|
urlForNewGive = ""; |
||||
|
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; |
||||
|
veriClaimDump = ""; |
||||
|
veriClaimDidsVisible = {}; |
||||
|
windowLocation = window.location; |
||||
|
|
||||
|
R = R; |
||||
|
yaml = yaml; |
||||
|
libsUtil = libsUtil; |
||||
|
serverUtil = serverUtil; |
||||
|
|
||||
|
resetThisValues() { |
||||
|
this.confirmerIdList = []; |
||||
|
this.confsVisibleErrorMessage = ""; |
||||
|
this.confsVisibleToIdList = []; |
||||
|
this.detailsForGive = null; |
||||
|
this.numConfsNotVisible = 0; |
||||
|
this.urlForNewGive = ""; |
||||
|
this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; |
||||
|
this.veriClaimDump = ""; |
||||
|
} |
||||
|
|
||||
|
async mounted() { |
||||
|
await db.open(); |
||||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; |
||||
|
this.activeDid = settings?.activeDid || ""; |
||||
|
this.apiServer = settings?.apiServer || ""; |
||||
|
this.allContacts = await db.contacts.toArray(); |
||||
|
|
||||
|
await accountsDB.open(); |
||||
|
const accounts = accountsDB.accounts; |
||||
|
const accountsArr: Array<Account> = await accounts?.toArray(); |
||||
|
this.allMyDids = accountsArr.map((acc) => acc.did); |
||||
|
const account = accountsArr.find((acc) => acc.did === this.activeDid); |
||||
|
this.accountIdentityStr = (account?.identity as string) || "null"; |
||||
|
const identity = JSON.parse(this.accountIdentityStr); |
||||
|
|
||||
|
const pathParam = window.location.pathname.substring( |
||||
|
"/confirm-gift/".length, |
||||
|
); |
||||
|
let claimId; |
||||
|
if (pathParam) { |
||||
|
claimId = decodeURIComponent(pathParam); |
||||
|
await this.loadClaim(claimId, identity); |
||||
|
} else { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: "No claim ID was provided.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare |
||||
|
// then use this truer check: navigator.canShare && navigator.canShare() |
||||
|
this.canShare = !!navigator.share; |
||||
|
} |
||||
|
|
||||
|
// insert a space before any capital letters except the initial letter |
||||
|
// (and capitalize initial letter, just in case) |
||||
|
capitalizeAndInsertSpacesBeforeCaps(text: string) { |
||||
|
return !text |
||||
|
? "" |
||||
|
: text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1"); |
||||
|
} |
||||
|
|
||||
|
capitalizeAndInsertSpacesBeforeCapsWithAPrefix(text: string) { |
||||
|
const word = this.capitalizeAndInsertSpacesBeforeCaps(text); |
||||
|
if (word) { |
||||
|
// if the word starts with a vowel, use "an" instead of "a" |
||||
|
const firstLetter = word[0].toLowerCase(); |
||||
|
const vowels = ["a", "e", "i", "o", "u"]; |
||||
|
const particle = vowels.includes(firstLetter) ? "an" : "a"; |
||||
|
return particle + " " + word; |
||||
|
} else { |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
totalConfirmers() { |
||||
|
return ( |
||||
|
this.numConfsNotVisible + |
||||
|
this.confirmerIdList.length + |
||||
|
this.confsVisibleToIdList.length |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public async getIdentity(activeDid: string): Promise<IIdentifier> { |
||||
|
await accountsDB.open(); |
||||
|
const account = (await accountsDB.accounts |
||||
|
.where("did") |
||||
|
.equals(activeDid) |
||||
|
.first()) as Account; |
||||
|
const identity = JSON.parse(account?.identity || "null"); |
||||
|
|
||||
|
if (!identity) { |
||||
|
throw new Error( |
||||
|
"Attempted to load project records with no identifier available.", |
||||
|
); |
||||
|
} |
||||
|
return identity; |
||||
|
} |
||||
|
|
||||
|
public async getHeaders(identity: IIdentifier) { |
||||
|
const headers: RawAxiosRequestHeaders = { |
||||
|
"Content-Type": "application/json", |
||||
|
}; |
||||
|
if (identity) { |
||||
|
const token = await accessToken(identity); |
||||
|
headers["Authorization"] = "Bearer " + token; |
||||
|
} |
||||
|
return headers; |
||||
|
} |
||||
|
|
||||
|
// Isn't there a better way to make this available to the template? |
||||
|
didInfo(did: string | undefined) { |
||||
|
return serverUtil.didInfo( |
||||
|
did, |
||||
|
this.activeDid, |
||||
|
this.allMyDids, |
||||
|
this.allContacts, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
async loadClaim(claimId: string, identity: IIdentifier) { |
||||
|
const urlPath = libsUtil.isGlobalUri(claimId) |
||||
|
? "/api/claim/byHandle/" |
||||
|
: "/api/claim/"; |
||||
|
const url = this.apiServer + urlPath + encodeURIComponent(claimId); |
||||
|
|
||||
|
try { |
||||
|
const headers = await this.getHeaders(identity); |
||||
|
const resp = await this.axios.get(url, { headers }); |
||||
|
// resp.data is: |
||||
|
// - a Jwt from https://api.endorser.ch/api-docs/ |
||||
|
// - with a Give from https://endorser.ch/doc/html/transactions.html#id3 |
||||
|
if (resp.status === 200) { |
||||
|
this.veriClaim = resp.data; |
||||
|
this.veriClaimDump = yaml.dump(this.veriClaim); |
||||
|
this.veriClaimDidsVisible = libsUtil.findAllVisibleToDids( |
||||
|
this.veriClaim, |
||||
|
true, |
||||
|
); |
||||
|
} else { |
||||
|
// actually, axios typically throws an error so we never get here |
||||
|
console.error("Error getting claim:", resp); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: "There was a problem retrieving that claim.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// retrieve more details on Give, Offer, or Plan |
||||
|
if (this.veriClaim.claimType !== "GiveAction") { |
||||
|
// no need to go further... this page is for gifts |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.issuerName = this.didInfo(this.veriClaim.issuer); |
||||
|
|
||||
|
this.urlForNewGive = "/gifted-details?"; |
||||
|
if (this.veriClaim.claim.object) { |
||||
|
if (this.veriClaim.claim.object.amountOfThisGood) { |
||||
|
this.urlForNewGive += |
||||
|
"&amountInput=" + |
||||
|
encodeURIComponent( |
||||
|
String(this.veriClaim.claim.object.amountOfThisGood), |
||||
|
); |
||||
|
} |
||||
|
if (this.veriClaim.claim.object.unitCode) { |
||||
|
this.urlForNewGive += |
||||
|
"&unitCode=" + |
||||
|
encodeURIComponent(this.veriClaim.claim.object.unitCode as string); |
||||
|
} |
||||
|
} |
||||
|
if (this.veriClaim.claim.description) { |
||||
|
this.urlForNewGive += |
||||
|
"&description=" + |
||||
|
encodeURIComponent(this.veriClaim.claim.description as string); |
||||
|
} |
||||
|
this.giverName = this.didInfo( |
||||
|
this.veriClaim.claim.agent?.identifier as string, |
||||
|
); |
||||
|
if (this.veriClaim.claim.agent) { |
||||
|
this.urlForNewGive += |
||||
|
"&giverDid=" + |
||||
|
encodeURIComponent(this.veriClaim.claim.agent.identifier as string) + |
||||
|
"&giverName=" + |
||||
|
encodeURIComponent(this.giverName); |
||||
|
} |
||||
|
this.recipientName = this.didInfo( |
||||
|
this.veriClaim.claim.recipient?.identifier as string, |
||||
|
); |
||||
|
if (this.veriClaim.claim.recipient) { |
||||
|
this.urlForNewGive += |
||||
|
"&recipientDid=" + |
||||
|
encodeURIComponent( |
||||
|
this.veriClaim.claim.recipient.identifier as string, |
||||
|
) + |
||||
|
"&recipientName=" + |
||||
|
encodeURIComponent(this.recipientName); |
||||
|
} |
||||
|
if (this.veriClaim.claim.image) { |
||||
|
this.urlForNewGive += |
||||
|
"&image=" + encodeURIComponent(this.veriClaim.claim.image as string); |
||||
|
} |
||||
|
if (this.veriClaim.claim.fulfills) { |
||||
|
this.urlForNewGive += this.fulfillsUrlSnippet( |
||||
|
this.veriClaim.claim.fulfills, |
||||
|
); |
||||
|
if (Array.isArray(this.veriClaim.claim.fulfills)) { |
||||
|
for (const fulfills of this.veriClaim.claim.fulfills) { |
||||
|
console.log("adding fulfills:", fulfills); |
||||
|
this.urlForNewGive += this.fulfillsUrlSnippet(fulfills); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const giveUrl = |
||||
|
this.apiServer + |
||||
|
"/api/v2/report/gives?handleId=" + |
||||
|
encodeURIComponent(this.veriClaim.handleId as string); |
||||
|
const giveHeaders = await this.getHeaders(identity); |
||||
|
const giveResp = await this.axios.get(giveUrl, { |
||||
|
headers: giveHeaders, |
||||
|
}); |
||||
|
// giveResp.data is a Give from https://api.endorser.ch/api-docs/ |
||||
|
if (giveResp.status === 200) { |
||||
|
this.detailsForGive = giveResp.data.data[0]; |
||||
|
} else { |
||||
|
console.error("Error getting detailed give info:", giveResp); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: "Something went wrong retrieving gift data.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// retrieve the list of confirmers |
||||
|
const confirmUrl = |
||||
|
this.apiServer + |
||||
|
"/api/report/issuersWhoClaimedOrConfirmed?claimId=" + |
||||
|
encodeURIComponent(serverUtil.stripEndorserPrefix(claimId)); |
||||
|
const confirmHeaders = await this.getHeaders(identity); |
||||
|
const response = await this.axios.get(confirmUrl, { |
||||
|
headers: confirmHeaders, |
||||
|
}); |
||||
|
if (response.status === 200) { |
||||
|
const resultList1 = response.data.result || []; |
||||
|
const resultList2 = R.reject(serverUtil.isHiddenDid, resultList1); |
||||
|
const resultList3 = R.reject( |
||||
|
(did: string) => did === this.veriClaim.issuer, |
||||
|
resultList2, |
||||
|
); |
||||
|
this.confirmerIdList = resultList3; |
||||
|
this.numConfsNotVisible = resultList1.length - resultList2.length; |
||||
|
if (resultList3.length === resultList2.length) { |
||||
|
// the issuer was not in the "visible" list so they must be hidden |
||||
|
// so subtract them from the non-visible confirmers count |
||||
|
this.numConfsNotVisible = this.numConfsNotVisible - 1; |
||||
|
} |
||||
|
this.confsVisibleToIdList = |
||||
|
response.data.result.resultVisibleToDids || []; |
||||
|
} else { |
||||
|
this.confsVisibleErrorMessage = |
||||
|
"Had problems retrieving confirmations."; |
||||
|
} |
||||
|
} catch (error: unknown) { |
||||
|
const serverError = error as AxiosError; |
||||
|
console.error("Error retrieving claim:", serverError); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: "Something went wrong retrieving claim data.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fulfillsUrlSnippet(object) { |
||||
|
if (object["@type"] === "PlanAction") { |
||||
|
return "&projectId=" + encodeURIComponent(object.identifier); |
||||
|
} else if (object["@type"] === "OfferAction") { |
||||
|
return "&offerId=" + encodeURIComponent(object.identifier); |
||||
|
} else { |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
confirmConfirmClaim() { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "modal", |
||||
|
type: "confirm", |
||||
|
title: "Confirm", |
||||
|
text: "Do you personally confirm that this is true?", |
||||
|
onYes: async () => { |
||||
|
await this.confirmClaim(); |
||||
|
}, |
||||
|
}, |
||||
|
-1, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// similar code is found in ProjectViewView |
||||
|
async confirmClaim() { |
||||
|
// similar logic is found in endorser-mobile |
||||
|
const goodClaim = serverUtil.removeSchemaContext( |
||||
|
serverUtil.removeVisibleToDids( |
||||
|
serverUtil.addLastClaimOrHandleAsIdIfMissing( |
||||
|
this.veriClaim.claim, |
||||
|
this.veriClaim.id, |
||||
|
this.veriClaim.handleId, |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
const confirmationClaim: serverUtil.GenericVerifiableCredential = { |
||||
|
"@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); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: "There was a problem submitting the confirmation. See logs for more info.", |
||||
|
}, |
||||
|
5000, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
showClaimPage(claimId: string) { |
||||
|
const route = { |
||||
|
path: "/claim/" + encodeURIComponent(claimId), |
||||
|
}; |
||||
|
this.$router.push(route).then(async () => { |
||||
|
this.resetThisValues(); |
||||
|
await this.loadClaim(claimId, JSON.parse(this.accountIdentityStr)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
openFulfillGiftDialog() { |
||||
|
const giver: GiverReceiverInputInfo = { |
||||
|
did: libsUtil.offerGiverDid(this.veriClaim), |
||||
|
}; |
||||
|
(this.$refs.customGiveDialog as GiftedDialog).open( |
||||
|
giver, |
||||
|
undefined, |
||||
|
this.veriClaim.handleId, |
||||
|
"Offer fulfilled by " + (giver?.name || "someone not named"), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
copyToClipboard(name: string, text: string) { |
||||
|
useClipboard() |
||||
|
.copy(text) |
||||
|
.then(() => { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "toast", |
||||
|
title: "Copied", |
||||
|
text: (name || "That") + " was copied to the clipboard.", |
||||
|
}, |
||||
|
2000, |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
notifyWhyCannotConfirm() { |
||||
|
if (!isGiveAction(this.veriClaim)) { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "info", |
||||
|
title: "Not A Give", |
||||
|
text: "This is not a giving action to confirm.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} else if (this.confirmerIdList.includes(this.activeDid)) { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "info", |
||||
|
title: "Already Confirmed", |
||||
|
text: "You have already confirmed this claim.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} else if (this.veriClaim.issuer == this.activeDid) { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "info", |
||||
|
title: "Cannot Confirm", |
||||
|
text: "You cannot confirm this because you issued this claim.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} else if (serverUtil.containsHiddenDid(this.veriClaim.claim)) { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "info", |
||||
|
title: "Cannot Confirm", |
||||
|
text: "You cannot confirm this because it contains hidden identifiers.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} else { |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "info", |
||||
|
title: "Cannot Confirm", |
||||
|
text: "You cannot confirm this claim.", |
||||
|
}, |
||||
|
3000, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onClickShareClaim() { |
||||
|
window.navigator.share({ |
||||
|
title: "Help Connect Me", |
||||
|
text: "I'm trying to find the full details of this claim. Can you help me?", |
||||
|
url: this.windowLocation.href, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
</script> |
Loading…
Reference in new issue