forked from trent_larson/crowd-funder-for-time-pwa
move contact actions into the details page (prepping for checkboxes)
This commit is contained in:
@@ -74,7 +74,7 @@ export default defineConfig({
|
|||||||
|
|
||||||
/* Configure global timeout; default is 30000 milliseconds */
|
/* Configure global timeout; default is 30000 milliseconds */
|
||||||
// the image upload will often not succeed at 5 seconds
|
// the image upload will often not succeed at 5 seconds
|
||||||
//timeout: 10000,
|
timeout: 15000,
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
// many of these are also found in endorser-mobile utility.ts
|
// many of these are also found in endorser-mobile utility.ts
|
||||||
|
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
import * as R from "ramda";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
|
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import {
|
import {
|
||||||
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
||||||
MASTER_SETTINGS_KEY,
|
MASTER_SETTINGS_KEY,
|
||||||
@@ -18,11 +21,9 @@ import {
|
|||||||
OfferVerifiableCredential,
|
OfferVerifiableCredential,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as serverUtil from "@/libs/endorserServer";
|
import * as serverUtil from "@/libs/endorserServer";
|
||||||
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
|
|
||||||
|
|
||||||
import { Buffer } from "buffer";
|
|
||||||
import { KeyMeta } from "@/libs/crypto/vc";
|
import { KeyMeta } from "@/libs/crypto/vc";
|
||||||
import { createPeerDid } from "@/libs/crypto/vc/didPeer";
|
import { createPeerDid } from "@/libs/crypto/vc/didPeer";
|
||||||
|
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
|
||||||
|
|
||||||
export const PRIVACY_MESSAGE =
|
export const PRIVACY_MESSAGE =
|
||||||
"The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow.";
|
"The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow.";
|
||||||
@@ -91,6 +92,28 @@ export const isGiveAction = (
|
|||||||
return veriClaim.claimType === "GiveAction";
|
return veriClaim.claimType === "GiveAction";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const nameForDid = (
|
||||||
|
activeDid: string,
|
||||||
|
contacts: Array<Contact>,
|
||||||
|
did: string,
|
||||||
|
): string => {
|
||||||
|
if (did === activeDid) {
|
||||||
|
return "you";
|
||||||
|
}
|
||||||
|
const contact = R.find((con) => con.did == did, contacts);
|
||||||
|
return nameForContact(contact);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nameForContact = (
|
||||||
|
contact?: Contact,
|
||||||
|
capitalize?: boolean,
|
||||||
|
): string => {
|
||||||
|
return (
|
||||||
|
(contact?.name as string) ||
|
||||||
|
(capitalize ? "This" : "this") + " unnamed user"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const doCopyTwoSecRedo = (text: string, fn: () => void) => {
|
export const doCopyTwoSecRedo = (text: string, fn: () => void) => {
|
||||||
fn();
|
fn();
|
||||||
useClipboard()
|
useClipboard()
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
@click="onClickNewContact()"
|
@click="onClickNewContact()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw"></fa>
|
<fa icon="plus" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -82,10 +82,10 @@
|
|||||||
<ul
|
<ul
|
||||||
id="listContacts"
|
id="listContacts"
|
||||||
v-if="contacts.length > 0"
|
v-if="contacts.length > 0"
|
||||||
class="border-t border-slate-300"
|
class="border-t border-slate-300 mt-1"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300 pt-2.5 pb-4"
|
class="border-b border-slate-300 pt-1 pb-1"
|
||||||
v-for="contact in contacts"
|
v-for="contact in contacts"
|
||||||
:key="contact.did"
|
:key="contact.did"
|
||||||
>
|
>
|
||||||
@@ -98,131 +98,16 @@
|
|||||||
@click="showLargeIdenticon = contact"
|
@click="showLargeIdenticon = contact"
|
||||||
/>
|
/>
|
||||||
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
contactEdit = contact;
|
|
||||||
contactNewName = contact.name || '';
|
|
||||||
"
|
|
||||||
title="Edit"
|
|
||||||
>
|
|
||||||
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1"></fa>
|
|
||||||
</button>
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
path: '/did/' + encodeURIComponent(contact.did),
|
path: '/did/' + encodeURIComponent(contact.did),
|
||||||
}"
|
}"
|
||||||
title="See more about this DID"
|
title="See more about this person"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="text-blue-500 ml-4" />
|
<fa icon="circle-info" class="text-blue-500 ml-4" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-sm truncate">
|
|
||||||
Identifier:
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
libsUtil.doCopyTwoSecRedo(
|
|
||||||
contact.did,
|
|
||||||
() => (showDidCopy = !showDidCopy),
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
<span v-show="showDidCopy" class="text-green-500">Copied DID</span>
|
|
||||||
{{ contact.did }}
|
|
||||||
</div>
|
|
||||||
<div class="text-sm truncate" v-if="contact.publicKeyBase64">
|
|
||||||
Public Key (base 64):
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
libsUtil.doCopyTwoSecRedo(
|
|
||||||
contact.publicKeyBase64,
|
|
||||||
() => (showPubKeyCopy = !showPubKeyCopy),
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
<span v-show="showPubKeyCopy" class="text-green-500"
|
|
||||||
>Copied Key</span
|
|
||||||
>
|
|
||||||
{{ contact.publicKeyBase64 }}
|
|
||||||
</div>
|
|
||||||
<div class="text-sm truncate" v-if="contact.nextPubKeyHashB64">
|
|
||||||
Next Public Key Hash (base 64):
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
libsUtil.doCopyTwoSecRedo(
|
|
||||||
contact.nextPubKeyHashB64,
|
|
||||||
() => (showPubKeyHashCopy = !showPubKeyHashCopy),
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
<span v-show="showPubKeyHashCopy" class="text-green-500"
|
|
||||||
>Copied Hash</span
|
|
||||||
>
|
|
||||||
{{ contact.nextPubKeyHashB64 }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="ContactActions" class="flex gap-1.5 mt-2">
|
<div id="ContactActions" class="flex gap-1.5 mt-2">
|
||||||
<div v-if="activeDid">
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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 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 px-2 py-1.5 rounded-md"
|
|
||||||
title="Delete"
|
|
||||||
>
|
|
||||||
<fa icon="trash-can" class="fa-fw" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showGiveNumbers && contact.did != activeDid"
|
v-if="showGiveNumbers && contact.did != activeDid"
|
||||||
class="ml-auto flex gap-1.5"
|
class="ml-auto flex gap-1.5"
|
||||||
@@ -308,33 +193,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="contactEdit !== null" 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(contactEdit, 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>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -782,55 +640,29 @@ export default class ContactsView extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// prompt with confirmation if they want to delete a contact
|
// note that this is also in DIDView.vue
|
||||||
confirmDeleteContact(contact: Contact) {
|
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(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "modal",
|
group: "modal",
|
||||||
type: "confirm",
|
type: "confirm",
|
||||||
title: "Delete",
|
title: "Set Visibility",
|
||||||
text:
|
text: visibilityPrompt,
|
||||||
"Are you sure you want to remove " +
|
|
||||||
this.nameForDid(this.contacts, contact.did) +
|
|
||||||
" with DID " +
|
|
||||||
contact.did +
|
|
||||||
" from your contact list?",
|
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
await this.deleteContact(contact);
|
const success = await this.setVisibility(contact, visibility, true);
|
||||||
},
|
if (success) {
|
||||||
},
|
contact.seesMe = visibility; // didn't work inside setVisibility
|
||||||
-1,
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteContact(contact: Contact) {
|
|
||||||
await db.open();
|
|
||||||
await db.contacts.delete(contact.did);
|
|
||||||
this.contacts = R.without([contact], this.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 " +
|
|
||||||
this.nameForDid(this.contacts, contact.did) +
|
|
||||||
(contact.registered
|
|
||||||
? " -- especially since they are already marked as registered"
|
|
||||||
: "") +
|
|
||||||
"?",
|
|
||||||
onYes: async () => {
|
|
||||||
await this.register(contact);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note that this is also in DIDView.vue
|
||||||
async register(contact: Contact) {
|
async register(contact: Contact) {
|
||||||
this.$notify({ group: "alert", type: "toast", title: "Sent..." }, 1000);
|
this.$notify({ group: "alert", type: "toast", title: "Sent..." }, 1000);
|
||||||
|
|
||||||
@@ -896,27 +728,7 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmSetVisibility(contact: Contact, visibility: boolean) {
|
// note that this is also in DIDView.vue
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setVisibility(
|
async setVisibility(
|
||||||
contact: Contact,
|
contact: Contact,
|
||||||
visibility: boolean,
|
visibility: boolean,
|
||||||
@@ -966,6 +778,7 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note that this is also in DIDView.vue
|
||||||
async checkVisibility(contact: Contact) {
|
async checkVisibility(contact: Contact) {
|
||||||
const url =
|
const url =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
@@ -999,7 +812,7 @@ export default class ContactsView extends Vue {
|
|||||||
type: "info",
|
type: "info",
|
||||||
title: "Visibility Refreshed",
|
title: "Visibility Refreshed",
|
||||||
text:
|
text:
|
||||||
this.nameForContact(contact, true) +
|
libsUtil.nameForContact(contact, true) +
|
||||||
" can " +
|
" can " +
|
||||||
(visibility ? "" : "not ") +
|
(visibility ? "" : "not ") +
|
||||||
"see your activity.",
|
"see your activity.",
|
||||||
@@ -1033,21 +846,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private nameForDid(contacts: Array<Contact>, did: string): string {
|
|
||||||
if (did === this.activeDid) {
|
|
||||||
return "you";
|
|
||||||
}
|
|
||||||
const contact = R.find((con) => con.did == did, contacts);
|
|
||||||
return this.nameForContact(contact);
|
|
||||||
}
|
|
||||||
|
|
||||||
private nameForContact(contact?: Contact, capitalize?: boolean): string {
|
|
||||||
return (
|
|
||||||
(contact?.name as string) ||
|
|
||||||
(capitalize ? "This" : "this") + " unnamed user"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmShowGiftedDialog(giverDid: string, recipientDid: string) {
|
confirmShowGiftedDialog(giverDid: string, recipientDid: string) {
|
||||||
// if they have unconfirmed amounts, ask to confirm those
|
// if they have unconfirmed amounts, ask to confirm those
|
||||||
if (
|
if (
|
||||||
@@ -1093,13 +891,13 @@ export default class ContactsView extends Vue {
|
|||||||
if (giverDid) {
|
if (giverDid) {
|
||||||
giver = {
|
giver = {
|
||||||
did: giverDid,
|
did: giverDid,
|
||||||
name: this.nameForDid(this.contacts, giverDid),
|
name: libsUtil.nameForDid(this.activeDid, this.contacts, giverDid),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (recipientDid) {
|
if (recipientDid) {
|
||||||
receiver = {
|
receiver = {
|
||||||
did: recipientDid,
|
did: recipientDid,
|
||||||
name: this.nameForDid(this.contacts, recipientDid),
|
name: libsUtil.nameForDid(this.activeDid, this.contacts, recipientDid),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1131,25 +929,13 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
openOfferDialog(recipientDid: string, recipientName: string) {
|
openOfferDialog(recipientDid: string, recipientName?: string) {
|
||||||
(this.$refs.customOfferDialog as OfferDialog).open(
|
(this.$refs.customOfferDialog as OfferDialog).open(
|
||||||
recipientDid,
|
recipientDid,
|
||||||
recipientName,
|
recipientName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onClickCancelName() {
|
|
||||||
this.contactEdit = null;
|
|
||||||
this.contactNewName = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async onClickSaveName(contact: Contact, newName: string) {
|
|
||||||
contact.name = newName;
|
|
||||||
return db.contacts
|
|
||||||
.update(contact.did, { name: newName })
|
|
||||||
.then(() => (this.contactEdit = null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async toggleShowContactAmounts() {
|
public async toggleShowContactAmounts() {
|
||||||
const newShowValue = !this.showGiveNumbers;
|
const newShowValue = !this.showGiveNumbers;
|
||||||
try {
|
try {
|
||||||
@@ -1211,74 +997,3 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
/* How do we share with the above so code isn't duplicated? */
|
|
||||||
.tooltip .tooltiptext-left {
|
|
||||||
visibility: hidden;
|
|
||||||
width: 200px;
|
|
||||||
background-color: black;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
padding: 5px 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
bottom: 0%;
|
|
||||||
right: 105%;
|
|
||||||
margin-left: -60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Show the tooltip text when you mouse over the tooltip container */
|
|
||||||
.tooltip:hover .tooltiptext {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.tooltip:hover .tooltiptext-left {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -26,8 +26,20 @@
|
|||||||
didInfoForContact(viewingDid, activeDid, contact, allMyDids)
|
didInfoForContact(viewingDid, activeDid, contact, allMyDids)
|
||||||
.displayName
|
.displayName
|
||||||
}}
|
}}
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
contactEdit = true;
|
||||||
|
contactNewName = contact.name || '';
|
||||||
|
"
|
||||||
|
title="Edit"
|
||||||
|
>
|
||||||
|
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
||||||
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<button @click="showDidDetails = !showDidDetails" class="ml-2 mr-2">
|
<button
|
||||||
|
@click="showDidDetails = !showDidDetails"
|
||||||
|
class="ml-2 mr-2 mt-4"
|
||||||
|
>
|
||||||
Details
|
Details
|
||||||
<fa v-if="showDidDetails" icon="chevron-up" class="text-blue-400" />
|
<fa v-if="showDidDetails" icon="chevron-up" class="text-blue-400" />
|
||||||
<fa v-else icon="chevron-down" class="text-blue-400" />
|
<fa v-else icon="chevron-down" class="text-blue-400" />
|
||||||
@@ -49,15 +61,76 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="flex justify-between mt-4">
|
||||||
<div class="flex justify-center">Auto-Generated Icon:</div>
|
<div class="flex items-center">
|
||||||
<div class="flex justify-center">
|
<div v-if="activeDid" class="flex justify-between">
|
||||||
<EntityIcon
|
<div>
|
||||||
:entityId="viewingDid"
|
<button
|
||||||
:iconSize="64"
|
v-if="contact?.seesMe && contact.did !== activeDid"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
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="showLargeIdenticonId = viewingDid"
|
@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>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -80,6 +153,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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 -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<div
|
||||||
@@ -126,15 +225,16 @@
|
|||||||
v-if="!isLoading && claims.length === 0"
|
v-if="!isLoading && claims.length === 0"
|
||||||
class="flex justify-center mt-4"
|
class="flex justify-center mt-4"
|
||||||
>
|
>
|
||||||
<span>They Are in No Claims Visible to You</span>
|
<span>They are in no claims visible to you.</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import * as yaml from "js-yaml";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
import * as yaml from "js-yaml";
|
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||||
@@ -152,6 +252,8 @@ import {
|
|||||||
GenericVerifiableCredential,
|
GenericVerifiableCredential,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
OfferVerifiableCredential,
|
OfferVerifiableCredential,
|
||||||
|
register,
|
||||||
|
setVisibilityUtil,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
@@ -174,7 +276,9 @@ export default class DIDView extends Vue {
|
|||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
|
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
|
||||||
contact?: Contact;
|
contact: Contact;
|
||||||
|
contactEdit = false;
|
||||||
|
contactNewName?: string;
|
||||||
contactYaml = "";
|
contactYaml = "";
|
||||||
hitEnd = false;
|
hitEnd = false;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@@ -195,23 +299,29 @@ export default class DIDView extends Vue {
|
|||||||
this.apiServer = (settings?.apiServer as string) || "";
|
this.apiServer = (settings?.apiServer as string) || "";
|
||||||
|
|
||||||
const pathParam = window.location.pathname.substring("/did/".length);
|
const pathParam = window.location.pathname.substring("/did/".length);
|
||||||
|
let theContact: Contact | undefined;
|
||||||
if (pathParam) {
|
if (pathParam) {
|
||||||
this.viewingDid = decodeURIComponent(pathParam);
|
this.viewingDid = decodeURIComponent(pathParam);
|
||||||
this.contact = await db.contacts.get(this.viewingDid);
|
theContact = await db.contacts.get(this.viewingDid);
|
||||||
this.contactYaml = yaml.dump(this.contact);
|
}
|
||||||
await this.loadClaimsAbout();
|
if (theContact) {
|
||||||
|
this.contact = theContact;
|
||||||
} else {
|
} else {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: "No claim ID was provided.",
|
text: "No valid claim ID was provided.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.contactYaml = yaml.dump(this.contact);
|
||||||
|
await this.loadClaimsAbout();
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
const allAccounts = await accountsDB.accounts.toArray();
|
||||||
this.allMyDids = allAccounts.map((acc) => acc.did);
|
this.allMyDids = allAccounts.map((acc) => acc.did);
|
||||||
@@ -227,6 +337,128 @@ export default class DIDView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
public async loadClaimsAbout() {
|
||||||
if (!this.viewingDid) {
|
if (!this.viewingDid) {
|
||||||
console.error("This should never be called without a DID.");
|
console.error("This should never be called without a DID.");
|
||||||
@@ -323,5 +555,178 @@ export default class DIDView extends Vue {
|
|||||||
claimDescription(claim: GenericVerifiableCredential) {
|
claimDescription(claim: GenericVerifiableCredential) {
|
||||||
return claim.claim.name || claim.claim.description || "";
|
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>
|
</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>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { test, expect } from '@playwright/test';
|
|||||||
import { importUser } from './testUtils';
|
import { importUser } from './testUtils';
|
||||||
|
|
||||||
test('Record something given', async ({ page }) => {
|
test('Record something given', async ({ page }) => {
|
||||||
// Generate a random string of 3 characters
|
// Generate a random string of a few characters
|
||||||
const randomString = Math.random().toString(36).substring(2, 5);
|
const randomString = Math.random().toString(36).substring(2, 6);
|
||||||
|
|
||||||
// Generate a random non-zero single-digit number
|
// Generate a random non-zero single-digit number
|
||||||
const randomNonZeroNumber = Math.floor(Math.random() * 99) + 1;
|
const randomNonZeroNumber = Math.floor(Math.random() * 99) + 1;
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
|
|||||||
const finalTitle = standardTitle + finalRandomString;
|
const finalTitle = standardTitle + finalRandomString;
|
||||||
|
|
||||||
// Contact name
|
// Contact name
|
||||||
const contactName = 'Contact 00';
|
const contactName = 'Contact #000';
|
||||||
|
|
||||||
// Import user 01
|
// Import user 01
|
||||||
await importUser(page, '01');
|
await importUser(page, '01');
|
||||||
|
|
||||||
// Add new contact 00
|
// Add new contact 00
|
||||||
await page.goto('./contacts');
|
await page.goto('./contacts');
|
||||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F');
|
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F, User #000');
|
||||||
await page.locator('button > svg.fa-plus').click();
|
await page.locator('button > svg.fa-plus').click();
|
||||||
await expect(page.locator('div[role="alert"]')).toBeVisible();
|
await expect(page.locator('div[role="alert"]')).toBeVisible();
|
||||||
|
|
||||||
@@ -36,10 +36,11 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
|
|||||||
// await page.locator('div[role="alert"] button:has-text("Yes")').click();
|
// await page.locator('div[role="alert"] button:has-text("Yes")').click();
|
||||||
|
|
||||||
// Verify added contact
|
// Verify added contact
|
||||||
await expect(page.locator('li.border-b')).toContainText('did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F');
|
await expect(page.locator('li.border-b')).toContainText('User #000');
|
||||||
|
|
||||||
// Rename contact
|
// Rename contact
|
||||||
await page.locator('li.border-b h2 > button[title="Edit"]').click();
|
await page.locator('li.border-b h2 > a[title="See more about this person"]').click();
|
||||||
|
await page.locator('h2 > button[title="Edit"]').click();
|
||||||
await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible();
|
await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible();
|
||||||
await page.getByPlaceholder('Name', { exact: true }).fill(contactName);
|
await page.getByPlaceholder('Name', { exact: true }).fill(contactName);
|
||||||
await page.locator('.dialog > .flex > button').first().click();
|
await page.locator('.dialog > .flex > button').first().click();
|
||||||
|
|||||||
Reference in New Issue
Block a user