You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
881 lines
29 KiB
881 lines
29 KiB
<template>
|
|
<QuickNav />
|
|
<TopMessage />
|
|
<!-- 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>
|
|
<span
|
|
v-if="
|
|
libsUtil.isGiveRecordTheUserCanConfirm(
|
|
isRegistered,
|
|
veriClaim,
|
|
activeDid,
|
|
confirmerIdList,
|
|
)
|
|
"
|
|
>
|
|
Do you agree?
|
|
</span>
|
|
<span v-else> Confirmation Details </span>
|
|
</h1>
|
|
</div>
|
|
|
|
<div v-if="giveDetails && !isLoading">
|
|
<div class="flex justify-center">
|
|
<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 px-4 py-2 rounded-md"
|
|
@click="confirmConfirmClaim()"
|
|
>
|
|
Confirm
|
|
<font-awesome
|
|
icon="circle-check"
|
|
class="ml-2 text-white cursor-pointer"
|
|
/>
|
|
</button>
|
|
<button
|
|
v-else
|
|
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"
|
|
@click="notifyWhyCannotConfirm()"
|
|
>
|
|
Confirm
|
|
<font-awesome
|
|
icon="circle-check"
|
|
class="ml-2 text-white cursor-pointer"
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Details -->
|
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4">
|
|
<div class="flex gap-4 overflow-hidden">
|
|
<div class="overflow-hidden">
|
|
<div class="text-sm">
|
|
<div>
|
|
<font-awesome icon="arrow-left" class="fa-fw text-slate-400" />
|
|
{{ giverName }}
|
|
</div>
|
|
<div class="ml-6">gave</div>
|
|
<div v-if="giveDetails.amount">
|
|
<font-awesome
|
|
icon="hand-holding-dollar"
|
|
class="fa-fw text-slate-400"
|
|
/>
|
|
{{ displayAmount(giveDetails.unit, giveDetails.amount) }}
|
|
</div>
|
|
<div v-if="giveDetails.description">
|
|
<font-awesome icon="message" class="fa-fw text-slate-400" />
|
|
{{ giveDetails.amount ? "and:" : "" }}
|
|
{{ giveDetails.description }}
|
|
</div>
|
|
<div class="ml-6">to</div>
|
|
<div>
|
|
<font-awesome icon="arrow-right" class="fa-fw text-slate-400" />
|
|
{{ recipientName }}
|
|
</div>
|
|
<div>
|
|
<font-awesome icon="calendar" class="fa-fw text-slate-400" />
|
|
on
|
|
{{ giveDetails.issuedAt.substring(0, 10) }}
|
|
</div>
|
|
|
|
<!-- Fullfills Links -->
|
|
|
|
<!-- fullfills links for a give -->
|
|
<div v-if="giveDetails?.fulfillsPlanHandleId" class="mt-2">
|
|
<router-link
|
|
:to="
|
|
'/project/' +
|
|
encodeURIComponent(giveDetails?.fulfillsPlanHandleId || '')
|
|
"
|
|
class="text-blue-500 mt-2 cursor-pointer"
|
|
target="_blank"
|
|
>
|
|
This fulfills a bigger plan
|
|
<font-awesome
|
|
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="
|
|
giveDetails?.fulfillsType &&
|
|
giveDetails?.fulfillsType !== 'PlanAction' &&
|
|
giveDetails?.fulfillsHandleId
|
|
"
|
|
>
|
|
<!-- router-link to /claim/ only changes URL path -->
|
|
<router-link
|
|
:to="
|
|
'/claim/' +
|
|
encodeURIComponent(giveDetails?.fulfillsHandleId || '')
|
|
"
|
|
class="text-blue-500 mt-2 cursor-pointer"
|
|
target="_blank"
|
|
>
|
|
This fulfills
|
|
{{
|
|
capitalizeAndInsertSpacesBeforeCapsWithAPrefix(
|
|
giveDetails?.fulfillsType || "",
|
|
)
|
|
}}
|
|
<font-awesome
|
|
icon="arrow-up-right-from-square"
|
|
class="fa-fw"
|
|
/>
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2">
|
|
<font-awesome icon="comment" class="text-slate-400" />
|
|
{{ issuerName }} posted that.
|
|
</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 confirmed this.
|
|
</span>
|
|
<span v-else> {{ totalConfirmers() }} people 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 issued or confirmed this claim.
|
|
</div>
|
|
<div v-if="confirmerIdList.length > 0">
|
|
The following people 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,
|
|
)
|
|
"
|
|
>
|
|
<font-awesome
|
|
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 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 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,
|
|
)
|
|
"
|
|
>
|
|
<font-awesome
|
|
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="!isRegistered">
|
|
You cannot confirm this because you are not registered. Find someone
|
|
to register you, maybe on the Help page.
|
|
</div>
|
|
<div v-else-if="giveDetails.issuerDid == 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 some people are hidden.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Note that a similar section is found in ClaimView.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)">
|
|
<button
|
|
@click="
|
|
copyToClipboard('The DID of ' + visDid, visDid)
|
|
"
|
|
>
|
|
<font-awesome
|
|
icon="copy"
|
|
class="text-slate-400 fa-fw"
|
|
/>
|
|
</button>
|
|
</span>
|
|
<span v-if="veriClaim.publicUrls?.[visDid]"
|
|
>, found at
|
|
<font-awesome
|
|
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 class="mt-2 ml-2">
|
|
<a
|
|
class="text-blue-500 cursor-pointer"
|
|
@click="showClaimPage(veriClaim.id)"
|
|
>
|
|
<font-awesome icon="file-lines" />
|
|
See All Generic Info
|
|
</a>
|
|
</div>
|
|
<div class="mt-2 ml-2">
|
|
<a
|
|
v-if="isRegistered"
|
|
class="text-blue-500 cursor-pointer"
|
|
:href="urlForNewGive"
|
|
>
|
|
<font-awesome icon="file-lines" />
|
|
Record a Give Similar to the Original
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="!isLoading">This does not have details to confirm.</div>
|
|
|
|
<div
|
|
v-if="isLoading"
|
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
|
>
|
|
<font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
|
|
</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 { useClipboard } from "@vueuse/core";
|
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
|
import { GenericVerifiableCredential } from "../interfaces";
|
|
import QuickNav from "../components/QuickNav.vue";
|
|
import { NotificationIface } from "../constants/app";
|
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|
import { Contact } from "../db/tables/contacts";
|
|
import * as serverUtil from "../libs/endorserServer";
|
|
import { GiveSummaryRecord } from "../interfaces";
|
|
import { displayAmount } from "../libs/endorserServer";
|
|
import * as libsUtil from "../libs/util";
|
|
import { isGiveAction, retrieveAccountDids } from "../libs/util";
|
|
import TopMessage from "../components/TopMessage.vue";
|
|
|
|
/**
|
|
* ConfirmGiftView Component
|
|
*
|
|
* Displays details about a gift claim and allows users to confirm it if eligible.
|
|
* Shows gift details including giver, recipient, amount, description, and confirmation status.
|
|
* Handles visibility of hidden DIDs and provides access to detailed claim information.
|
|
*
|
|
* Key features:
|
|
* - Gift confirmation workflow
|
|
* - Detailed gift information display
|
|
* - Confirmation status tracking
|
|
* - Hidden DID handling
|
|
* - Claim details expansion
|
|
*/
|
|
@Component({
|
|
components: {
|
|
QuickNav,
|
|
TopMessage,
|
|
},
|
|
})
|
|
export default class ConfirmGiftView 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
|
|
giveDetails?: GiveSummaryRecord;
|
|
giverName = "";
|
|
issuerName = "";
|
|
isLoading = false;
|
|
isRegistered = false;
|
|
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
|
|
recipientName = "";
|
|
showVeriClaimDump = false;
|
|
urlForNewGive = "";
|
|
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
|
veriClaimDump = "";
|
|
veriClaimDidsVisible: { [key: string]: string[] } = {};
|
|
windowLocation = window.location.href;
|
|
|
|
R = R;
|
|
yaml = yaml;
|
|
libsUtil = libsUtil;
|
|
serverUtil = serverUtil;
|
|
displayAmount = displayAmount;
|
|
|
|
/**
|
|
* Initializes the view with gift claim information
|
|
*
|
|
* Workflow:
|
|
* 1. Retrieves active account settings
|
|
* 2. Loads gift claim details from ID in URL
|
|
* 3. Processes claim information for display
|
|
* 4. Checks user's ability to confirm the gift
|
|
*/
|
|
async mounted() {
|
|
this.isLoading = true;
|
|
try {
|
|
await this.initializeSettings();
|
|
await this.loadClaimFromUrl();
|
|
} catch (error) {
|
|
console.error("Error in mounted:", error);
|
|
this.handleMountError(error);
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes component settings and user data
|
|
*/
|
|
private async initializeSettings() {
|
|
const settings = await retrieveSettingsForActiveAccount();
|
|
this.activeDid = settings.activeDid || "";
|
|
this.apiServer = settings.apiServer || "";
|
|
this.allContacts = await db.contacts.toArray();
|
|
this.isRegistered = settings.isRegistered || false;
|
|
this.allMyDids = await retrieveAccountDids();
|
|
|
|
// Check share capability
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* Loads and processes claim from URL parameters
|
|
*/
|
|
private async loadClaimFromUrl() {
|
|
const pathParam = window.location.pathname.substring("/confirm-gift/".length);
|
|
if (!pathParam) {
|
|
throw new Error("No claim ID was provided.");
|
|
}
|
|
|
|
const claimId = decodeURIComponent(pathParam);
|
|
await this.loadClaim(claimId, this.activeDid);
|
|
}
|
|
|
|
/**
|
|
* Handles errors during component mounting
|
|
*/
|
|
private handleMountError(error: unknown) {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Error",
|
|
text: error instanceof Error ? error.message : "No claim ID was provided.",
|
|
},
|
|
3000,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Loads claim details and associated give information
|
|
*
|
|
* @param claimId - ID of claim to load
|
|
* @param userDid - User's DID
|
|
*/
|
|
private async loadClaim(claimId: string, userDid: string) {
|
|
await this.fetchClaimDetails(claimId, userDid);
|
|
if (this.veriClaim.claimType === "GiveAction") {
|
|
await this.fetchGiveDetails(claimId, userDid);
|
|
await this.processGiveDetails();
|
|
await this.fetchConfirmerInfo(claimId, userDid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches basic claim details from server
|
|
*/
|
|
private async fetchClaimDetails(claimId: string, userDid: string) {
|
|
const urlPath = libsUtil.isGlobalUri(claimId)
|
|
? "/api/claim/byHandle/"
|
|
: "/api/claim/";
|
|
const url = this.apiServer + urlPath + encodeURIComponent(claimId);
|
|
|
|
try {
|
|
const headers = await serverUtil.getHeaders(userDid);
|
|
const resp = await this.axios.get(url, { headers });
|
|
|
|
if (resp.status === 200) {
|
|
this.veriClaim = resp.data;
|
|
this.veriClaimDump = yaml.dump(this.veriClaim);
|
|
this.veriClaimDidsVisible = libsUtil.findAllVisibleToDids(
|
|
this.veriClaim,
|
|
true,
|
|
);
|
|
this.issuerName = this.didInfo(this.veriClaim.issuer);
|
|
} else {
|
|
throw new Error("Error getting claim: " + resp.status);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error getting claim:", error);
|
|
throw new Error("There was a problem retrieving that claim.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches detailed give information
|
|
*/
|
|
private async fetchGiveDetails(claimId: string, userDid: string) {
|
|
const giveUrl = `${this.apiServer}/api/v2/report/gives?handleId=${encodeURIComponent(claimId)}`;
|
|
|
|
try {
|
|
const headers = await serverUtil.getHeaders(userDid);
|
|
const resp = await this.axios.get(giveUrl, { headers });
|
|
|
|
if (resp.status === 200) {
|
|
this.giveDetails = resp.data.data[0];
|
|
} else {
|
|
throw new Error("Error getting detailed give info: " + resp.status);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error getting detailed give info:", error);
|
|
throw new Error("Something went wrong retrieving gift data.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes give details and builds URL for new give
|
|
*/
|
|
private async processGiveDetails() {
|
|
if (!this.giveDetails) return;
|
|
|
|
this.urlForNewGive = "/gifted-details?";
|
|
this.addGiveDetailsToUrl();
|
|
this.processParticipantInfo();
|
|
this.processAdditionalDetails();
|
|
}
|
|
|
|
/**
|
|
* Adds basic give details to URL
|
|
*/
|
|
private addGiveDetailsToUrl() {
|
|
if (this.giveDetails?.amount) {
|
|
this.urlForNewGive += `&amountInput=${encodeURIComponent(String(this.giveDetails.amount))}`;
|
|
}
|
|
if (this.giveDetails?.unit) {
|
|
this.urlForNewGive += `&unitCode=${encodeURIComponent(this.giveDetails.unit)}`;
|
|
}
|
|
if (this.giveDetails?.description) {
|
|
this.urlForNewGive += `&description=${encodeURIComponent(this.giveDetails.description)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes participant (giver/recipient) information
|
|
*/
|
|
private processParticipantInfo() {
|
|
if (this.giveDetails?.agentDid) {
|
|
this.giverName = this.didInfo(this.giveDetails.agentDid);
|
|
this.urlForNewGive += `&giverDid=${encodeURIComponent(this.giveDetails.agentDid)}&giverName=${encodeURIComponent(this.giverName)}`;
|
|
}
|
|
if (this.giveDetails?.recipientDid) {
|
|
this.recipientName = this.didInfo(this.giveDetails.recipientDid);
|
|
this.urlForNewGive += `&recipientDid=${encodeURIComponent(this.giveDetails.recipientDid)}&recipientName=${encodeURIComponent(this.recipientName)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes additional give details (image, offer, plan)
|
|
*/
|
|
private processAdditionalDetails() {
|
|
if (this.giveDetails?.fullClaim.image) {
|
|
this.urlForNewGive += `&image=${encodeURIComponent(this.giveDetails.fullClaim.image)}`;
|
|
}
|
|
if (this.giveDetails?.type === "Offer" && this.giveDetails?.fulfillsHandleId) {
|
|
this.urlForNewGive += `&offerId=${encodeURIComponent(this.giveDetails.fulfillsHandleId)}`;
|
|
}
|
|
if (this.giveDetails?.fulfillsPlanHandleId) {
|
|
this.urlForNewGive += `&fulfillsProjectId=${encodeURIComponent(this.giveDetails.fulfillsPlanHandleId)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches confirmer information for the claim
|
|
*/
|
|
private async fetchConfirmerInfo(claimId: string, userDid: string) {
|
|
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.";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates total number of confirmers for the gift
|
|
* Includes both direct confirmers and those visible through network
|
|
*
|
|
* @returns Total number of confirmers
|
|
*/
|
|
totalConfirmers(): number {
|
|
return (
|
|
this.numConfsNotVisible +
|
|
this.confirmerIdList.length +
|
|
this.confsVisibleToIdList.length
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Formats display amount with proper unit
|
|
*
|
|
* @param unit - Currency or unit code
|
|
* @param amount - Numeric amount
|
|
* @returns Formatted amount string
|
|
*/
|
|
displayAmount(unit: string, amount: number): string {
|
|
return displayAmount(unit, amount);
|
|
}
|
|
|
|
/**
|
|
* Retrieves human-readable name for a DID
|
|
* Falls back to DID if no name available
|
|
*
|
|
* @param did - DID to get name for
|
|
* @returns Human-readable name
|
|
*/
|
|
didInfo(did: string): string {
|
|
return serverUtil.didInfo(
|
|
did,
|
|
this.activeDid,
|
|
this.allMyDids,
|
|
this.allContacts,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Copies text to clipboard and shows notification
|
|
*
|
|
* @param description - Description of copied content
|
|
* @param text - Text to copy
|
|
*/
|
|
copyToClipboard(description: string, text: string): void {
|
|
useClipboard()
|
|
.copy(text)
|
|
.then(() => {
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "toast",
|
|
title: "Copied",
|
|
text: (description || "That") + " was copied to the clipboard.",
|
|
},
|
|
2000,
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Navigates to claim page for detailed view
|
|
*
|
|
* @param claimId - ID of claim to view
|
|
*/
|
|
showClaimPage(claimId: string): void {
|
|
const route = {
|
|
path: "/claim/" + encodeURIComponent(claimId),
|
|
};
|
|
(this.$router as Router).push(route).then(async () => {
|
|
this.resetThisValues();
|
|
await this.loadClaim(claimId, this.activeDid);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initiates claim confirmation process
|
|
* Verifies user eligibility and handles confirmation workflow
|
|
*/
|
|
async confirmConfirmClaim(): Promise<void> {
|
|
this.$notify(
|
|
{
|
|
group: "modal",
|
|
type: "confirm",
|
|
title: "Confirm",
|
|
text: "Do you personally confirm that this is true?",
|
|
onYes: async () => {
|
|
await this.confirmClaim();
|
|
},
|
|
},
|
|
-1,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Notifies user why they cannot confirm the gift
|
|
* Explains requirements or restrictions preventing confirmation
|
|
*/
|
|
notifyWhyCannotConfirm(): void {
|
|
libsUtil.notifyWhyCannotConfirm(
|
|
this.$notify,
|
|
this.isRegistered,
|
|
this.veriClaim.claimType,
|
|
this.giveDetails,
|
|
this.activeDid,
|
|
this.confirmerIdList,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Formats type string for display by adding spaces before capitals
|
|
* Optionally adds a prefix
|
|
*
|
|
* @param text - Text to format
|
|
* @param prefix - Optional prefix to add
|
|
* @returns Formatted string
|
|
*/
|
|
capitalizeAndInsertSpacesBeforeCapsWithAPrefix(
|
|
text: string,
|
|
prefix?: string
|
|
): 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 "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initiates sharing of claim information
|
|
* Handles share functionality based on platform capabilities
|
|
*/
|
|
async onClickShareClaim(): Promise<void> {
|
|
this.copyToClipboard("A link to this page", this.windowLocation);
|
|
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,
|
|
});
|
|
}
|
|
|
|
resetThisValues() {
|
|
this.confirmerIdList = [];
|
|
this.confsVisibleErrorMessage = "";
|
|
this.confsVisibleToIdList = [];
|
|
this.giveDetails = undefined;
|
|
this.isRegistered = false;
|
|
this.numConfsNotVisible = 0;
|
|
this.urlForNewGive = "";
|
|
this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
|
this.veriClaimDump = "";
|
|
}
|
|
|
|
capitalizeAndInsertSpacesBeforeCaps(text: string) {
|
|
return !text
|
|
? ""
|
|
: text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
|
|
}
|
|
}
|
|
</script>
|
|
|