|
|
|
<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">
|
|
|
|
{{ contact?.name || "(no name)" }}
|
|
|
|
<button
|
|
|
|
@click="
|
|
|
|
contactEdit = true;
|
|
|
|
contactNewName = contact.name || '';
|
|
|
|
"
|
|
|
|
title="Edit"
|
|
|
|
>
|
|
|
|
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
|
|
|
</button>
|
|
|
|
</h2>
|
|
|
|
<button
|
|
|
|
@click="showDidDetails = !showDidDetails"
|
|
|
|
class="ml-2 mr-2 mt-4"
|
|
|
|
>
|
|
|
|
Details
|
|
|
|
<fa v-if="showDidDetails" icon="chevron-up" class="text-blue-400" />
|
|
|
|
<fa v-else icon="chevron-down" class="text-blue-400" />
|
|
|
|
</button>
|
|
|
|
<!-- Keep the dump contents directly between > and < to avoid weird spacing. -->
|
|
|
|
<pre
|
|
|
|
v-if="showDidDetails"
|
|
|
|
class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md"
|
|
|
|
>{{ contactYaml }}</pre
|
|
|
|
>
|
|
|
|
</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="flex justify-between mt-4">
|
|
|
|
<div class="flex items-center">
|
|
|
|
<div v-if="activeDid" class="flex justify-between">
|
|
|
|
<div>
|
|
|
|
<button
|
|
|
|
v-if="contact?.seesMe && contact.did !== activeDid"
|
|
|
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
|
|
@click="confirmSetVisibility(contact, false)"
|
|
|
|
title="They can see you"
|
|
|
|
>
|
|
|
|
<fa icon="eye" class="fa-fw" />
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
v-else-if="!contact?.seesMe && contact?.did !== activeDid"
|
|
|
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
|
|
@click="confirmSetVisibility(contact, true)"
|
|
|
|
title="They cannot see you"
|
|
|
|
>
|
|
|
|
<fa icon="eye-slash" class="fa-fw" />
|
|
|
|
</button>
|
|
|
|
<!-- otherwise it's this user so hide it -->
|
|
|
|
<fa v-else icon="eye" class="text-white mx-2.5" />
|
|
|
|
|
|
|
|
<button
|
|
|
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
|
|
@click="checkVisibility(contact)"
|
|
|
|
title="Check Visibility"
|
|
|
|
v-if="contact?.did !== activeDid"
|
|
|
|
>
|
|
|
|
<fa icon="rotate" class="fa-fw" />
|
|
|
|
</button>
|
|
|
|
<!-- otherwise it's this user so hide it -->
|
|
|
|
<fa v-else icon="rotate" class="text-white mx-2.5" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="confirmRegister(contact)"
|
|
|
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
|
|
v-if="contact?.did !== activeDid"
|
|
|
|
title="Registration"
|
|
|
|
>
|
|
|
|
<fa
|
|
|
|
v-if="contact?.registered"
|
|
|
|
icon="person-circle-check"
|
|
|
|
class="fa-fw"
|
|
|
|
/>
|
|
|
|
<fa v-else icon="person-circle-question" class="fa-fw" />
|
|
|
|
</button>
|
|
|
|
<!-- otherwise it's this user so hide it -->
|
|
|
|
<fa v-else icon="rotate" class="text-white ml-6 px-2.5" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<button
|
|
|
|
@click="confirmDeleteContact(contact)"
|
|
|
|
class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
|
|
title="Delete"
|
|
|
|
>
|
|
|
|
<fa icon="trash-can" class="fa-fw" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div v-if="!contact?.profileImageUrl">
|
|
|
|
<div>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>
|
|
|
|
<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>
|
|
|
|
<div v-if="contactEdit" class="dialog-overlay">
|
|
|
|
<div class="dialog">
|
|
|
|
<h1 class="text-xl font-bold text-center mb-4">Edit Name</h1>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
|
|
|
placeholder="Name"
|
|
|
|
v-model="contactNewName"
|
|
|
|
/>
|
|
|
|
<div class="flex justify-between">
|
|
|
|
<button
|
|
|
|
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded -ml-1.5 border-l border-blue-400"
|
|
|
|
@click="onClickSaveName(contactNewName)"
|
|
|
|
>
|
|
|
|
<fa icon="save" />
|
|
|
|
</button>
|
|
|
|
<span class="inline-block w-2" />
|
|
|
|
<button
|
|
|
|
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded -ml-1.5 border-l border-blue-400"
|
|
|
|
@click="onClickCancelName()"
|
|
|
|
>
|
|
|
|
<fa icon="ban" />
|
|
|
|
</button>
|
|
|
|
</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 { AxiosError } from "axios";
|
|
|
|
import * as yaml from "js-yaml";
|
|
|
|
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,
|
|
|
|
register,
|
|
|
|
setVisibilityUtil,
|
|
|
|
} from "@/libs/endorserServer";
|
|
|
|
import * as libsUtil from "@/libs/util";
|
|
|
|
import EntityIcon from "@/components/EntityIcon.vue";
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
components: {
|
|
|
|
EntityIcon,
|
|
|
|
InfiniteScroll,
|
|
|
|
QuickNav,
|
|
|
|
TopMessage,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
export default class DIDView extends Vue {
|
|
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
|
|
|
|
libsUtil = libsUtil;
|
|
|
|
yaml = yaml;
|
|
|
|
|
|
|
|
activeDid = "";
|
|
|
|
allMyDids: Array<string> = [];
|
|
|
|
apiServer = "";
|
|
|
|
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
|
|
|
|
contact: Contact;
|
|
|
|
contactEdit = false;
|
|
|
|
contactNewName?: string;
|
|
|
|
contactYaml = "";
|
|
|
|
hitEnd = false;
|
|
|
|
isLoading = false;
|
|
|
|
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
|
|
|
showDidDetails = false;
|
|
|
|
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);
|
|
|
|
let theContact: Contact | undefined;
|
|
|
|
if (pathParam) {
|
|
|
|
this.viewingDid = decodeURIComponent(pathParam);
|
|
|
|
theContact = await db.contacts.get(this.viewingDid);
|
|
|
|
}
|
|
|
|
if (theContact) {
|
|
|
|
this.contact = theContact;
|
|
|
|
} else {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: "No valid claim ID was provided.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.contactYaml = yaml.dump(this.contact);
|
|
|
|
await this.loadClaimsAbout();
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// prompt with confirmation if they want to delete a contact
|
|
|
|
confirmDeleteContact(contact: Contact) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "modal",
|
|
|
|
type: "confirm",
|
|
|
|
title: "Delete",
|
|
|
|
text:
|
|
|
|
"Are you sure you want to remove " +
|
|
|
|
libsUtil.nameForContact(contact, false) +
|
|
|
|
" from your contact list?",
|
|
|
|
onYes: async () => {
|
|
|
|
await this.deleteContact(contact);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteContact(contact: Contact) {
|
|
|
|
await db.open();
|
|
|
|
await db.contacts.delete(contact.did);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "success",
|
|
|
|
title: "Deleted",
|
|
|
|
text: "Contact has been removed.",
|
|
|
|
},
|
|
|
|
3000,
|
|
|
|
);
|
|
|
|
(this.$router as Router).push({ name: "contacts" });
|
|
|
|
}
|
|
|
|
|
|
|
|
// confirm to register a new contact
|
|
|
|
async confirmRegister(contact: Contact) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "modal",
|
|
|
|
type: "confirm",
|
|
|
|
title: "Register",
|
|
|
|
text:
|
|
|
|
"Are you sure you want to register " +
|
|
|
|
libsUtil.nameForContact(this.contact, false) +
|
|
|
|
(contact.registered
|
|
|
|
? " -- especially since they are already marked as registered"
|
|
|
|
: "") +
|
|
|
|
"?",
|
|
|
|
onYes: async () => {
|
|
|
|
await this.register(contact);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// note that this is also in ContactView.vue
|
|
|
|
async register(contact: Contact) {
|
|
|
|
this.$notify({ group: "alert", type: "toast", title: "Sent..." }, 1000);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const regResult = await register(
|
|
|
|
this.activeDid,
|
|
|
|
this.apiServer,
|
|
|
|
this.axios,
|
|
|
|
contact,
|
|
|
|
);
|
|
|
|
if (regResult.success) {
|
|
|
|
contact.registered = true;
|
|
|
|
await db.contacts.update(contact.did, { registered: true });
|
|
|
|
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "success",
|
|
|
|
title: "Registration Success",
|
|
|
|
text:
|
|
|
|
(contact.name || "That unnamed person") + " has been registered.",
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Registration Error",
|
|
|
|
text:
|
|
|
|
(regResult.error as string) ||
|
|
|
|
"Something went wrong during registration.",
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Error when registering:", error);
|
|
|
|
let userMessage = "There was an error. See logs for more info.";
|
|
|
|
const serverError = error as AxiosError;
|
|
|
|
if (serverError) {
|
|
|
|
if (serverError.response?.data?.error?.message) {
|
|
|
|
userMessage = serverError.response.data.error.message;
|
|
|
|
} else if (serverError.message) {
|
|
|
|
userMessage = serverError.message; // Info for the user
|
|
|
|
} else {
|
|
|
|
userMessage = JSON.stringify(serverError.toJSON());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
userMessage = error as string;
|
|
|
|
}
|
|
|
|
// Now set that error for the user to see.
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Registration Error",
|
|
|
|
text: userMessage,
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 || "";
|
|
|
|
}
|
|
|
|
|
|
|
|
private async onClickCancelName() {
|
|
|
|
this.contactEdit = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async onClickSaveName(newName: string) {
|
|
|
|
this.contact.name = newName;
|
|
|
|
return db.contacts
|
|
|
|
.update(this.contact.did, { name: newName })
|
|
|
|
.then(() => (this.contactEdit = false));
|
|
|
|
}
|
|
|
|
|
|
|
|
// note that this is also in ContactView.vue
|
|
|
|
async confirmSetVisibility(contact: Contact, visibility: boolean) {
|
|
|
|
const visibilityPrompt = visibility
|
|
|
|
? "Are you sure you want to make your activity visible to them?"
|
|
|
|
: "Are you sure you want to hide all your activity from them?";
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "modal",
|
|
|
|
type: "confirm",
|
|
|
|
title: "Set Visibility",
|
|
|
|
text: visibilityPrompt,
|
|
|
|
onYes: async () => {
|
|
|
|
const success = await this.setVisibility(contact, visibility, true);
|
|
|
|
if (success) {
|
|
|
|
contact.seesMe = visibility; // didn't work inside setVisibility
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// note that this is also in ContactView.vue
|
|
|
|
async setVisibility(
|
|
|
|
contact: Contact,
|
|
|
|
visibility: boolean,
|
|
|
|
showSuccessAlert: boolean,
|
|
|
|
) {
|
|
|
|
const result = await setVisibilityUtil(
|
|
|
|
this.activeDid,
|
|
|
|
this.apiServer,
|
|
|
|
this.axios,
|
|
|
|
db,
|
|
|
|
contact,
|
|
|
|
visibility,
|
|
|
|
);
|
|
|
|
if (result.success) {
|
|
|
|
//contact.seesMe = visibility; // why doesn't it affect the UI from here?
|
|
|
|
//console.log("Set result & seesMe", result, contact.seesMe, contact.did);
|
|
|
|
if (showSuccessAlert) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "success",
|
|
|
|
title: "Visibility Set",
|
|
|
|
text:
|
|
|
|
(contact.name || "That user") +
|
|
|
|
" can " +
|
|
|
|
(visibility ? "" : "not ") +
|
|
|
|
"see your activity.",
|
|
|
|
},
|
|
|
|
3000,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
console.error("Got strange result from setting visibility:", result);
|
|
|
|
const message =
|
|
|
|
(result.error as string) || "Could not set visibility on the server.";
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error Setting Visibility",
|
|
|
|
text: message,
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// note that this is also in ContactView.vue
|
|
|
|
async checkVisibility(contact: Contact) {
|
|
|
|
const url =
|
|
|
|
this.apiServer +
|
|
|
|
"/api/report/canDidExplicitlySeeMe?did=" +
|
|
|
|
encodeURIComponent(contact.did);
|
|
|
|
const headers = await getHeaders(this.activeDid);
|
|
|
|
if (!headers["Authorization"]) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "No Identity",
|
|
|
|
text: "There is no identity to use to check visibility.",
|
|
|
|
},
|
|
|
|
3000,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const resp = await this.axios.get(url, { headers });
|
|
|
|
if (resp.status === 200) {
|
|
|
|
const visibility = resp.data;
|
|
|
|
contact.seesMe = visibility;
|
|
|
|
//console.log("Visi check:", visibility, contact.seesMe, contact.did);
|
|
|
|
await db.contacts.update(contact.did, { seesMe: visibility });
|
|
|
|
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "info",
|
|
|
|
title: "Visibility Refreshed",
|
|
|
|
text:
|
|
|
|
libsUtil.nameForContact(contact, true) +
|
|
|
|
" can " +
|
|
|
|
(visibility ? "" : "not ") +
|
|
|
|
"see your activity.",
|
|
|
|
},
|
|
|
|
3000,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
console.error("Got bad server response checking visibility:", resp);
|
|
|
|
const message = resp.data.error?.message || "Got bad server response.";
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error Checking Visibility",
|
|
|
|
text: message,
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Caught error from request to check visibility:", err);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error Checking Visibility",
|
|
|
|
text: "Check connectivity and try again.",
|
|
|
|
},
|
|
|
|
3000,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
.dialog-overlay {
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
right: 0;
|
|
|
|
bottom: 0;
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
padding: 1.5rem;
|
|
|
|
}
|
|
|
|
.dialog {
|
|
|
|
background-color: white;
|
|
|
|
padding: 1rem;
|
|
|
|
border-radius: 0.5rem;
|
|
|
|
width: 100%;
|
|
|
|
max-width: 500px;
|
|
|
|
}
|
|
|
|
</style>
|