<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 class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" @click="$router.go(-1)" > <font-awesome icon="chevron-left" class="fa-fw" /> </button> Verifiable Claim Details </h1> </div> <!-- Details --> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4 w-full"> <div class="block flex gap-4 overflow-hidden w-full"> <div class="w-full"> <div class="flex columns-3"> <h2 class="text-md font-bold w-full"> {{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType || "") }} <button v-if=" ['GiveAction', 'Offer', 'PlanAction'].includes( veriClaim.claimType as string, ) && veriClaim.issuer === activeDid // a PlanAction agent also could edit one of those, // but rather than add more Plan-specific logic to detect the agent // we'll let them click the Project link and edit from there " title="Edit" data-testId="editClaimButton" @click="onClickEditClaim" > <font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" /> </button> </h2> <div class="flex justify-center w-full"> <router-link :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)" class="text-blue-500 mt-2" title="Printable Certificate" > <font-awesome icon="square" class="text-white bg-yellow-500 p-1" /> </router-link> </div> <!-- show link icon to copy this URL to the clipboard --> <div class="flex justify-end w-full"> <button title="Copy Link" @click=" copyToClipboard('A link to this page', window.location.href) " > <font-awesome icon="link" class="text-slate-500" /> </button> </div> </div> <div class="text-sm"> <div data-testId="description"> <font-awesome icon="message" class="fa-fw text-slate-400" /> {{ (veriClaim.claim?.itemOffered as any)?.description || (veriClaim.claim as any)?.description || "" }} </div> <div> <font-awesome icon="user" class="fa-fw text-slate-400" /> {{ didInfo(veriClaim.issuer) }} </div> <div> <font-awesome icon="calendar" class="fa-fw text-slate-400" /> Recorded {{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }} </div> <div v-if="(veriClaim.claim as any).image" class="flex justify-center" > <a :href="(veriClaim.claim as any).image" target="_blank"> <img :src="(veriClaim.claim as any).image" class="h-24 rounded-xl" /> </a> </div> <div v-if="veriClaim.claimType === 'PlanAction'" class="mt-4"> <router-link :to="'/project/' + encodeURIComponent(veriClaim.handleId)" class="text-blue-500 mt-2" > Go to Project page </router-link> </div> <!-- Fullfills Links --> <!-- fullfills links for a give --> <div v-if="detailsForGive?.fulfillsPlanHandleId" class="mt-4"> <router-link :to=" '/project/' + encodeURIComponent(detailsForGive?.fulfillsPlanHandleId) " class="text-blue-500 mt-2" > Fulfills a bigger plan... </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 --> <a class="text-blue-500 mt-4 cursor-pointer" @click=" showDifferentClaimPage(detailsForGive?.fulfillsHandleId) " > Fulfills {{ capitalizeAndInsertSpacesBeforeCaps( detailsForGive.fulfillsType, ) }}... </a> </div> <!-- fullfills links for an offer --> <div v-if="detailsForOffer?.fulfillsPlanHandleId"> <router-link :to=" '/project/' + encodeURIComponent(detailsForOffer?.fulfillsPlanHandleId) " class="text-blue-500 mt-4" > Offered to a bigger plan... </router-link> </div> <!-- Providers --> <div v-if="providersForGive?.length > 0" class="mt-4"> <span>Other assistance provided by:</span> <ul class="ml-4"> <li v-for="provider of providersForGive" :key="provider.identifier" class="list-disc ml-4" > <div class="flex gap-4"> <div class="grow overflow-hidden"> <a class="text-blue-500 mt-4 cursor-pointer" @click=" provider.identifier.startsWith('did:') ? $router.push( '/did/' + encodeURIComponent(provider.identifier), ) : showDifferentClaimPage(provider.identifier) " > an activity... </a> </div> </div> </li> </ul> </div> </div> </div> </div> </div> <div class="mt-2"> <font-awesome icon="comment" class="text-slate-400" /> {{ issuerName }} posted that. </div> <!-- <div> <router-link :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)"> <font-awesome icon="file-contract" class="text-slate-400" /> <span class="ml-2 text-blue-500">Printable Certificate</span> </router-link> </div> --> <div class="mt-8"> <button v-if="libsUtil.canFulfillOffer(veriClaim)" class="col-span-1 block w-fit text-center text-md 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-1.5 py-2 rounded-md" @click="openFulfillGiftDialog()" > Affirm Delivery <font-awesome icon="hand-holding-heart" class="ml-2 text-white cursor-pointer" /> </button> </div> <GiftedDialog ref="customGiveDialog" /> <div v-if="libsUtil.isGiveAction(veriClaim)"> <div class="flex columns-3"> <button v-if=" libsUtil.isGiveRecordTheUserCanConfirm( isRegistered, veriClaim, activeDid, confirmerIdList, ) " 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 mt-2 px-4 py-2 rounded-md" @click="confirmConfirmClaim()" > Confirm <font-awesome icon="circle-check" class="ml-2 text-white cursor-pointer" /> </button> <h2 v-else class="font-bold uppercase text-xl mt-2">Confirmations</h2> <span class="mt-0.5 px-4 py-2"> <router-link v-if="libsUtil.isGiveAction(veriClaim)" :to="'/confirm-gift/' + encodeURIComponent(veriClaim.id)" class="col-span-1 text-blue-500" data-testId="confirmGiftLink" > Details... </router-link> </span> </div> <div class="mt-2"> <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> <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 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)"> <a :href="`/did/${confirmerId}`" target="_blank" class="text-blue-500" > <font-awesome icon="arrow-up-right-from-square" class="fa-fw" /> </a> </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)"> <a :href="`/did/${confsVisibleTo}`" target="_blank" class="text-blue-500" > <font-awesome icon="arrow-up-right-from-square" class="fa-fw" /> </a> </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> <!-- Note that a similar section is found in ConfirmGiftView.vue, and kinda in HiddenDidDialog.vue --> <h2 class="font-bold uppercase text-xl text-blue-500 mt-8 cursor-pointer" @click="showVeriClaimDump = !showVeriClaimDump" > Details <font-awesome v-if="showVeriClaimDump" icon="chevron-up" /> <font-awesome v-else icon="chevron-right" /> </h2> <div v-if="showVeriClaimDump"> <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"> You can ask one of your contacts to take a look and see if their contacts can see more details: <a class="text-blue-500" @click="onClickShareClaim()" >click to send them this page info</a > and see if they can make an introduction. Someone is connected to people closer to them; if you don't know who to ask, try the person who registered you. </span> <span v-else> You can ask one of your contacts to take a look and see if their contacts can see more details: <a class="text-blue-500" @click="copyToClipboard('A link to this page', windowLocation)" >click to copy this page info</a > and see if they can make an introduction. Someone is connected to people closer to them; if you don't know who to ask, try the person who registered you. </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 class="text-blue-500" @click="onClickShareClaim()" >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 class="text-blue-500" @click="copyToClipboard('A link to this page', windowLocation)" >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"> <font-awesome 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)"> <a :href="`/did/${visDid}`" target="_blank" class="text-blue-500" > <font-awesome icon="arrow-up-right-from-square" class="fa-fw" /> </a> </span> <span v-if="veriClaim.publicUrls?.[visDid]" >, found at <a :href="veriClaim.publicUrls?.[visDid]" target="_blank" class="text-blue-500" > <font-awesome icon="globe" class="fa-fw" /> {{ veriClaim.publicUrls[visDid].substring( veriClaim.publicUrls[visDid].indexOf("//") + 2, ) }} </a> </span> </span> </div> </li> </ul> </div> </div> </div> <span v-if="isEditedGlobalId" class="mt-2"> This record is an edited version. The latest version is shown. </span> <br /> <!-- Keep the dump contents directly between > and < to avoid weird spacing. --> <pre v-if="showVeriClaimDump" class="text-sm overflow-x-scroll bg-slate-100 px-4 py-3 rounded-md" >{{ veriClaimDump }}</pre > <h2 class="text-xl mt-8 mb-2">Full Claim</h2> <p class="mb-4"> The full claim includes the claim as it was originally issued, including the signature (ie. the proof of issuance by that person). </p> <div v-if="!fullClaim"> <p v-if="fullClaimMessage" class="mb-4"> {{ fullClaimMessage }} </p> <button v-else class="text-blue-500 cursor-pointer" @click="showFullClaim(veriClaim.id as string)" > <font-awesome icon="file-lines" class="fa-fw" /> Load Full Claim Details </button> </div> <div v-else> <pre class="text-sm overflow-x-scroll bg-slate-100 px-4 py-3 rounded-md" >{{ fullClaimDump }}</pre > </div> <a :href="apiServer + '/api/claim/' + veriClaim.id" target="_blank" class="text-blue-500 cursor-pointer" > <font-awesome icon="file-lines" class="fa-fw" /> <font-awesome icon="arrow-up-right-from-square" class="ml-1 fa-fw" /> View on the Public Server </a> </div> </section> </template> <script lang="ts"> import { AxiosError } from "axios"; import * as yaml from "js-yaml"; import * as R from "ramda"; import { Component, Vue } from "vue-facing-decorator"; import { Router, RouteLocationNormalizedLoaded } from "vue-router"; import { useClipboard } from "@vueuse/core"; import { GenericVerifiableCredential } from "../interfaces"; import GiftedDialog from "../components/GiftedDialog.vue"; import QuickNav from "../components/QuickNav.vue"; import { NotificationIface } from "../constants/app"; import { db, logConsoleAndDb, retrieveSettingsForActiveAccount, } from "../db/index"; import { Contact } from "../db/tables/contacts"; import * as serverUtil from "../libs/endorserServer"; import { GenericCredWrapper, OfferVerifiableCredential, ProviderInfo, } from "../interfaces"; import * as libsUtil from "../libs/util"; import { logger } from "../utils/logger"; @Component({ components: { GiftedDialog, QuickNav }, }) export default class ClaimView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; $route!: RouteLocationNormalizedLoaded; $router!: Router; 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: { fulfillsPlanHandleId?: string; fulfillsType?: string; fulfillsHandleId?: string; } | null = null; detailsForOffer: { fulfillsPlanHandleId?: string } | null = null; fullClaim = null; fullClaimDump = ""; fullClaimMessage = ""; isEditedGlobalId = false; isRegistered = false; issuerName = ""; numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible providersForGive: ProviderInfo[] = []; showIdCopy = false; showVeriClaimDump = false; veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaimDump = ""; veriClaimDidsVisible: { [key: string]: string[] } = {}; windowLocation = window.location.href; R = R; yaml = yaml; libsUtil = libsUtil; serverUtil = serverUtil; window = window; resetThisValues() { this.confirmerIdList = []; this.confsVisibleErrorMessage = ""; this.confsVisibleToIdList = []; this.detailsForGive = null; this.detailsForOffer = null; this.fullClaim = null; this.fullClaimDump = ""; this.fullClaimMessage = ""; this.isEditedGlobalId = false; this.numConfsNotVisible = 0; this.providersForGive = []; this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; this.veriClaimDump = ""; this.veriClaimDidsVisible = {}; } async created() { logger.log("ClaimView created"); const settings = await retrieveSettingsForActiveAccount(); this.activeDid = settings.activeDid || ""; this.apiServer = settings.apiServer || ""; this.allContacts = await db.contacts.toArray(); this.isRegistered = settings.isRegistered || false; try { this.allMyDids = await libsUtil.retrieveAccountDids(); } catch (error) { logConsoleAndDb( "Error retrieving all account DIDs on home page:" + error, true, ); this.$notify( { group: "alert", type: "danger", title: "Error Loading Profile", text: "See the Help page for problems with your personal data.", }, 5000, ); } const claimId = this.$route.params.id as string; if (claimId) { await this.loadClaim(claimId, this.activeDid); } else { this.$notify( { group: "alert", type: "danger", title: "Error", text: "No claim ID was provided.", }, 5000, ); } 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"); } totalConfirmers() { return ( this.numConfsNotVisible + this.confirmerIdList.length + this.confsVisibleToIdList.length ); } // Isn't there a better way to make this available to the template? didInfo(did: string) { return serverUtil.didInfo( did, this.activeDid, this.allMyDids, this.allContacts, ); } async loadClaim(claimId: string, userDid: string) { logger.log("[ClaimView] loadClaim called with claimId:", claimId); const urlPath = libsUtil.isGlobalUri(claimId) ? "/api/claim/byHandle/" : "/api/claim/"; const url = this.apiServer + urlPath + encodeURIComponent(claimId); const headers = await serverUtil.getHeaders(userDid); try { logger.log("[ClaimView] Making API request to:", url); const resp = await this.axios.get(url, { headers }); if (resp.status === 200) { this.veriClaim = resp.data; this.issuerName = this.didInfo(this.veriClaim.issuer); 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 logger.error("Error getting claim:", resp); this.$notify( { group: "alert", type: "danger", title: "Error", text: "There was a problem retrieving that claim.", }, 5000, ); return; } this.isEditedGlobalId = !this.veriClaim.handleId.endsWith(claimId); // retrieve more details on Give, Offer, or Plan if (this.veriClaim.claimType === "GiveAction") { const giveUrl = this.apiServer + "/api/v2/report/gives?handleId=" + encodeURIComponent(this.veriClaim.handleId as string); const giveHeaders = await serverUtil.getHeaders(userDid); const giveResp = await this.axios.get(giveUrl, { headers: giveHeaders, }); if (giveResp.status === 200 && giveResp.data.data?.length > 0) { this.detailsForGive = giveResp.data.data[0]; } else { logger.error("Error getting detailed give info:", giveResp); } // look for providers const providerUrl = this.apiServer + "/api/v2/report/providersToGive?handleId=" + encodeURIComponent(this.veriClaim.handleId as string); const providerHeaders = await serverUtil.getHeaders(userDid); const providerResp = await this.axios.get(providerUrl, { headers: providerHeaders, }); // should be at least an empty array if ( providerResp.status === 200 && Array.isArray(providerResp.data.data) ) { this.providersForGive = providerResp.data.data; } else { logger.error("Error getting give providers:", giveResp); this.$notify( { group: "alert", type: "warning", title: "Error", text: "Got error retrieving linked provider data.", }, 5000, ); } } else if (this.veriClaim.claimType === "Offer") { const offerUrl = this.apiServer + "/api/v2/report/offers?handleId=" + encodeURIComponent(this.veriClaim.handleId as string); const offerHeaders = await serverUtil.getHeaders(userDid); const offerResp = await this.axios.get(offerUrl, { headers: offerHeaders, }); if (offerResp.status === 200) { this.detailsForOffer = offerResp.data.data[0]; } else { logger.error("Error getting detailed offer info:", offerResp); this.$notify( { group: "alert", type: "warning", title: "Error", text: "Got error retrieving linked offer data.", }, 5000, ); } } // retrieve the list of confirmers const confirmerInfo = await libsUtil.retrieveConfirmerIdList( this.apiServer, claimId, this.veriClaim.issuer, userDid, ); if (confirmerInfo) { this.confirmerIdList = confirmerInfo.confirmerIdList; this.confsVisibleToIdList = confirmerInfo.confsVisibleToIdList; this.numConfsNotVisible = confirmerInfo.numConfsNotVisible; } else { this.confsVisibleErrorMessage = "Had problems retrieving confirmations."; } } catch (error: unknown) { const serverError = error as AxiosError; logger.error("Error retrieving claim:", serverError); this.$notify( { group: "alert", type: "danger", title: "Error", text: "Something went wrong retrieving claim data.", }, 3000, ); } } async showFullClaim(claimId: string) { const url = this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId); const headers = await serverUtil.getHeaders(this.activeDid); try { const resp = await this.axios.get(url, { headers }); if (resp.status === 200) { this.fullClaim = resp.data; this.fullClaimDump = yaml.dump(this.fullClaim); } else { // actually, axios typically throws an error so we never get here logger.error("Error getting full claim:", resp); this.$notify( { group: "alert", type: "danger", title: "Error", text: "There was a problem getting that claim.", }, 5000, ); } } catch (error: unknown) { logger.error("Error retrieving full claim:", error); const serverError = error as AxiosError; if (serverError.response?.status === 403) { let issuerPhrase = ""; const issuerContact = serverUtil.contactForDid( this.veriClaim.issuer, this.allContacts, ); if (issuerContact?.name) { issuerPhrase += "Ask " + issuerContact.name + " to show you the full claim details."; } if ( this.confirmerIdList.length > 0 || this.confsVisibleToIdList.length > 0 ) { if (issuerContact?.name) { issuerPhrase += "You could also ask someone in the Confirmations section to make an introduction."; } else { issuerPhrase += "Ask someone in the Confirmations section to make an introduction."; } } this.fullClaimMessage = "You are not authorized to view the full contents of this claim." + issuerPhrase + " You might ask someone in your network -- like the person who registered you --" + " if they can find out more and make an introduction: " + " send them this page and see if they can make a connection for you."; } else { this.$notify( { group: "alert", type: "danger", title: "Error", text: "Something went wrong retrieving that claim.", }, 5000, ); } } } 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: GenericVerifiableCredential = { "@context": "https://schema.org", "@type": "AgreeAction", object: goodClaim, }; const result = await serverUtil.createAndSubmitClaim( confirmationClaim, this.activeDid, this.apiServer, this.axios, ); if (result.type === "success") { this.$notify( { group: "alert", type: "success", title: "Success", text: "Confirmation submitted.", }, 5000, ); } else { logger.error("Got error submitting the confirmation:", result); this.$notify( { group: "alert", type: "danger", title: "Error", text: "There was a problem submitting the confirmation.", }, 5000, ); } } showDifferentClaimPage(claimId: string) { const route = { path: "/claim/" + encodeURIComponent(claimId), }; (this.$router as Router).push(route).then(async () => { this.resetThisValues(); await this.loadClaim(claimId, this.activeDid); }); } openFulfillGiftDialog() { const giver: libsUtil.GiverReceiverInputInfo = { did: libsUtil.offerGiverDid( this.veriClaim as GenericCredWrapper<OfferVerifiableCredential>, ), }; (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, ); }); } onClickShareClaim() { this.copyToClipboard("A link to this page", this.windowLocation); window.navigator.share({ title: "Help Connect Me", text: "I'm trying to find the people who recorded this. Can you help me?", url: this.windowLocation, }); } onClickEditClaim() { if (this.veriClaim.claimType === "GiveAction") { const route = { name: "gifted-details", query: { prevCredToEdit: JSON.stringify(this.veriClaim), destinationPathAfter: "/claim/" + encodeURIComponent(this.veriClaim.handleId), }, }; (this.$router as Router).push(route); } else if (this.veriClaim.claimType === "Offer") { const route = { name: "offer-details", query: { prevCredToEdit: JSON.stringify(this.veriClaim), destinationPathAfter: "/claim/" + encodeURIComponent(this.veriClaim.handleId), }, }; (this.$router as Router).push(route); } else if (this.veriClaim.claimType === "PlanAction") { const route = { name: "new-edit-project", query: { projectId: this.veriClaim.handleId }, }; (this.$router as Router).push(route); } else { logger.error( "Unrecognized claim type for edit:", this.veriClaim.claimType, ); this.$notify( { group: "alert", type: "danger", title: "Error", text: "This is an unrecognized claim type.", }, 3000, ); } } } </script> <style> /* Tooltip, generated on "title" attributes on "fa" icons Kudos to https://www.w3schools.com/css/css_tooltip.asp */ /* Tooltip container */ .tooltip { position: relative; display: inline-block; border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ } /* Tooltip text */ .tooltip .tooltiptext { visibility: hidden; width: 200px; background-color: black; color: #fff; text-align: center; padding: 5px 0; border-radius: 6px; position: absolute; z-index: 1; } /* Show the tooltip text when you mouse over the tooltip container */ .tooltip:hover .tooltiptext { visibility: visible; } .tooltip:hover .tooltiptext-left { visibility: visible; } </style>