|
|
|
<template>
|
|
|
|
<QuickNav selected="Contacts" />
|
|
|
|
<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
|
|
|
|
@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"></fa>
|
|
|
|
</button>
|
|
|
|
Identifier Details
|
|
|
|
</h1>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Identity Details -->
|
|
|
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
|
|
|
<div>
|
|
|
|
<h2 class="text-xl font-semibold">
|
|
|
|
{{
|
|
|
|
didInfoForContact(viewingDid, activeDid, contact, allMyDids)
|
|
|
|
.displayName
|
|
|
|
}}
|
|
|
|
</h2>
|
|
|
|
<span class="mt-2 text-xl font-semibold break-words">
|
|
|
|
{{ viewingDid }}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div class="flex justify-center mt-4">
|
|
|
|
<span v-if="contact?.profileImageUrl" class="flex justify-between">
|
|
|
|
<EntityIcon
|
|
|
|
:icon-size="96"
|
|
|
|
:profileImageUrl="contact?.profileImageUrl"
|
|
|
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
|
|
|
@click="showLargeIdenticonUrl = contact?.profileImageUrl"
|
|
|
|
/>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
|
|
<div class="flex justify-center">Auto-Generated Icon:</div>
|
|
|
|
<div class="flex justify-center">
|
|
|
|
<EntityIcon
|
|
|
|
:entityId="viewingDid"
|
|
|
|
:iconSize="64"
|
|
|
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
|
|
|
@click="showLargeIdenticonId = viewingDid"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
v-if="showLargeIdenticonId || showLargeIdenticonUrl"
|
|
|
|
class="fixed z-[100] top-0 inset-x-0 w-full"
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
|
|
>
|
|
|
|
<EntityIcon
|
|
|
|
:entityId="showLargeIdenticonId"
|
|
|
|
:iconSize="512"
|
|
|
|
:profileImageUrl="showLargeIdenticonUrl"
|
|
|
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
|
|
@click="
|
|
|
|
showLargeIdenticonId = undefined;
|
|
|
|
showLargeIdenticonUrl = undefined;
|
|
|
|
"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Loading Animation -->
|
|
|
|
<div
|
|
|
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
|
|
|
v-if="isLoading"
|
|
|
|
>
|
|
|
|
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
|
|
|
</div>
|
|
|
|
<!-- Results List -->
|
|
|
|
<div v-if="claims.length > 0" class="mt-4">
|
|
|
|
<div class="text-l font-bold text-center">Claims That Involve Them</div>
|
|
|
|
</div>
|
|
|
|
<InfiniteScroll @reached-bottom="loadMoreData">
|
|
|
|
<ul>
|
|
|
|
<li
|
|
|
|
class="border-b border-slate-300"
|
|
|
|
v-for="claim in claims"
|
|
|
|
:key="claim.handleId"
|
|
|
|
>
|
|
|
|
<div class="grid grid-cols-12 gap-4">
|
|
|
|
<span class="col-span-2">
|
|
|
|
{{ claim.issuedAt.substring(0, 10) }}
|
|
|
|
</span>
|
|
|
|
<span class="col-span-2">
|
|
|
|
{{ capitalizeAndInsertSpacesBeforeCaps(claim.claimType) }}
|
|
|
|
</span>
|
|
|
|
<span class="col-span-2">
|
|
|
|
{{ claimAmount(claim) }}
|
|
|
|
</span>
|
|
|
|
<span class="col-span-5">
|
|
|
|
{{ claimDescription(claim) }}
|
|
|
|
</span>
|
|
|
|
<span class="col-span-1">
|
|
|
|
<a @click="onClickLoadClaim(claim.id)" class="cursor-pointer">
|
|
|
|
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
|
|
|
</a>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</InfiniteScroll>
|
|
|
|
|
|
|
|
<div
|
|
|
|
v-if="!isLoading && claims.length === 0"
|
|
|
|
class="flex justify-center mt-4"
|
|
|
|
>
|
|
|
|
<span>They Are in No Claims Visible to You</span>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import { Component, Vue } from "vue-facing-decorator";
|
|
|
|
import { Router } from "vue-router";
|
|
|
|
|
|
|
|
import QuickNav from "@/components/QuickNav.vue";
|
|
|
|
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
|
|
|
import TopMessage from "@/components/TopMessage.vue";
|
|
|
|
import { NotificationIface } from "@/constants/app";
|
|
|
|
import { accountsDB, db } from "@/db/index";
|
|
|
|
import { Contact } from "@/db/tables/contacts";
|
|
|
|
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
|
|
import {
|
|
|
|
capitalizeAndInsertSpacesBeforeCaps,
|
|
|
|
didInfoForContact,
|
|
|
|
displayAmount,
|
|
|
|
getHeaders,
|
|
|
|
GenericCredWrapper,
|
|
|
|
GenericVerifiableCredential,
|
|
|
|
GiveVerifiableCredential,
|
|
|
|
OfferVerifiableCredential,
|
|
|
|
} from "@/libs/endorserServer";
|
|
|
|
import EntityIcon from "@/components/EntityIcon.vue";
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
components: {
|
|
|
|
EntityIcon,
|
|
|
|
InfiniteScroll,
|
|
|
|
QuickNav,
|
|
|
|
TopMessage,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
export default class DIDView extends Vue {
|
|
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
|
|
|
|
activeDid = "";
|
|
|
|
allMyDids: Array<string> = [];
|
|
|
|
apiServer = "";
|
|
|
|
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
|
|
|
|
contact?: Contact;
|
|
|
|
hitEnd = false;
|
|
|
|
isLoading = false;
|
|
|
|
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
|
|
|
showLargeIdenticonId?: string;
|
|
|
|
showLargeIdenticonUrl?: string;
|
|
|
|
viewingDid?: string;
|
|
|
|
|
|
|
|
capitalizeAndInsertSpacesBeforeCaps = capitalizeAndInsertSpacesBeforeCaps;
|
|
|
|
didInfoForContact = didInfoForContact;
|
|
|
|
displayAmount = displayAmount;
|
|
|
|
|
|
|
|
async mounted() {
|
|
|
|
await db.open();
|
|
|
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
|
|
|
this.activeDid = (settings?.activeDid as string) || "";
|
|
|
|
this.apiServer = (settings?.apiServer as string) || "";
|
|
|
|
|
|
|
|
const pathParam = window.location.pathname.substring("/did/".length);
|
|
|
|
if (pathParam) {
|
|
|
|
this.viewingDid = decodeURIComponent(pathParam);
|
|
|
|
this.contact = await db.contacts.get(this.viewingDid);
|
|
|
|
await this.loadClaimsAbout();
|
|
|
|
} else {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: "No claim ID was provided.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
await accountsDB.open();
|
|
|
|
const allAccounts = await accountsDB.accounts.toArray();
|
|
|
|
this.allMyDids = allAccounts.map((acc) => acc.did);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Data loader used by infinite scroller
|
|
|
|
* @param payload is the flag from the InfiniteScroll indicating if it should load
|
|
|
|
**/
|
|
|
|
async loadMoreData(payload: boolean) {
|
|
|
|
if (this.claims.length > 0 && !this.hitEnd && payload) {
|
|
|
|
this.loadClaimsAbout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async loadClaimsAbout() {
|
|
|
|
if (!this.viewingDid) {
|
|
|
|
console.error("This should never be called without a DID.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const queryParams = "claimContents=" + encodeURIComponent(this.viewingDid);
|
|
|
|
let postfix = "";
|
|
|
|
if (this.claims.length > 0) {
|
|
|
|
postfix = "&beforeId=" + this.claims[this.claims.length - 1].id;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
this.isLoading = true;
|
|
|
|
const response = await fetch(
|
|
|
|
this.apiServer + "/api/v2/report/claims?" + queryParams + postfix,
|
|
|
|
{
|
|
|
|
method: "GET",
|
|
|
|
headers: await getHeaders(this.activeDid),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (response.status !== 200) {
|
|
|
|
const details = await response.text();
|
|
|
|
console.error("Problem with full search:", details);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: `There was a problem accessing the server. Try again later.`,
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const results = await response.json();
|
|
|
|
this.claims = this.claims.concat(results.data);
|
|
|
|
this.hitEnd = !results.hitLimit;
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
} catch (e: any) {
|
|
|
|
console.error("Error with feed load:", e);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: e.userMessage || "There was a problem retrieving claims.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
this.isLoading = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onClickLoadClaim(jwtId: string) {
|
|
|
|
const route = {
|
|
|
|
path: "/claim/" + encodeURIComponent(jwtId),
|
|
|
|
};
|
|
|
|
(this.$router as Router).push(route);
|
|
|
|
}
|
|
|
|
|
|
|
|
public claimAmount(claim: GenericVerifiableCredential) {
|
|
|
|
if (claim.claimType === "GiveAction") {
|
|
|
|
const giveClaim = claim.claim as GiveVerifiableCredential;
|
|
|
|
if (giveClaim.object?.unitCode && giveClaim.object?.amountOfThisGood) {
|
|
|
|
return displayAmount(
|
|
|
|
giveClaim.object.unitCode,
|
|
|
|
giveClaim.object.amountOfThisGood,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
} else if (claim.claimType === "Offer") {
|
|
|
|
const offerClaim = claim.claim as OfferVerifiableCredential;
|
|
|
|
if (
|
|
|
|
offerClaim.includesObject?.unitCode &&
|
|
|
|
offerClaim.includesObject?.amountOfThisGood
|
|
|
|
) {
|
|
|
|
return displayAmount(
|
|
|
|
offerClaim.includesObject.unitCode,
|
|
|
|
offerClaim.includesObject.amountOfThisGood,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
claimDescription(claim: GenericVerifiableCredential) {
|
|
|
|
return claim.claim.name || claim.claim.description || "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|