Browse Source

replace many of the javascript "confirm" calls with the nicer UX version

kb/add-usage-guide
Trent Larson 5 months ago
parent
commit
421b4c1719
  1. 21
      src/App.vue
  2. 1
      src/constants/app.ts
  3. 412
      src/views/ContactsView.vue

21
src/App.vue

@ -129,7 +129,10 @@
</div> </div>
</NotificationGroup> </NotificationGroup>
<!-- These are general-purpose messages - except there are some for turning app notifications on and off. --> <!--
This "group" of "modal" is the prompt for an answer.
Set "type" as follows: "confirm" for yes/no, and "notification" ones: "-permission", "-mute", "-off"
-->
<NotificationGroup group="modal"> <NotificationGroup group="modal">
<div class="fixed z-[100] top-0 inset-x-0 w-full"> <div class="fixed z-[100] top-0 inset-x-0 w-full">
<Notification <Notification
@ -158,9 +161,8 @@
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg" class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
> >
<div class="w-full px-6 py-6 text-slate-900 text-center"> <div class="w-full px-6 py-6 text-slate-900 text-center">
<p class="text-lg mb-4"> <span class="font-semibold text-lg">{{ notification.title }}</span>
{{ notification.title }} <p class="text-sm mb-2">{{ notification.text }}</p>
</p>
<button <button
v-if="notification.onYes" v-if="notification.onYes"
@ -173,6 +175,17 @@
Yes Yes
</button> </button>
<button
v-if="notification.onNo"
@click="
notification.onNo();
close(notification.id);
"
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
>
No
</button>
<button <button
@click="close(notification.id)" @click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md" class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"

1
src/constants/app.ts

@ -39,5 +39,6 @@ export interface NotificationIface {
type: string; // "toast" | "info" | "success" | "warning" | "danger" type: string; // "toast" | "info" | "success" | "warning" | "danger"
title: string; title: string;
text: string; text: string;
onNo?: () => Promise<void>;
onYes?: () => Promise<void>; onYes?: () => Promise<void>;
} }

412
src/views/ContactsView.vue

@ -139,7 +139,7 @@
<button <button
v-if="contact.seesMe" v-if="contact.seesMe"
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" 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="setVisibility(contact, false, true)" @click="promptSetVisibility(contact, false)"
title="They can see you" title="They can see you"
> >
<fa icon="eye" class="fa-fw" /> <fa icon="eye" class="fa-fw" />
@ -147,7 +147,7 @@
<button <button
v-else v-else
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" 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="setVisibility(contact, true, true)" @click="promptSetVisibility(contact, true)"
title="They cannot see you" title="They cannot see you"
> >
<fa icon="eye-slash" class="fa-fw" /> <fa icon="eye-slash" class="fa-fw" />
@ -161,7 +161,7 @@
<fa icon="rotate" class="fa-fw" /> <fa icon="rotate" class="fa-fw" />
</button> </button>
<button <button
@click="register(contact)" @click="promptRegister(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" 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="activeDid" v-if="activeDid"
title="Registration" title="Registration"
@ -176,7 +176,7 @@
</div> </div>
<button <button
@click="deleteContact(contact)" @click="promptDeleteContact(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" 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" title="Delete"
> >
@ -189,7 +189,7 @@
> >
<button <button
class="text-sm 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-2 py-1.5 rounded-l-md" class="text-sm 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-2 py-1.5 rounded-l-md"
@click="showGiftedDialog(activeDid, contact.did)" @click="promptShowGiftedDialog(activeDid, contact.did)"
:title="givenByMeDescriptions[contact.did] || ''" :title="givenByMeDescriptions[contact.did] || ''"
> >
To: To:
@ -210,7 +210,7 @@
<button <button
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l" class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
@click="showGiftedDialog(contact.did, this.activeDid)" @click="promptShowGiftedDialog(contact.did, this.activeDid)"
:title="givenToMeDescriptions[contact.did] || ''" :title="givenToMeDescriptions[contact.did] || ''"
> >
From: From:
@ -781,211 +781,242 @@ export default class ContactsView extends Vue {
}); });
} }
async deleteContact(contact: Contact) { // prompt with confirmation if they want to delete a contact
if ( promptDeleteContact(contact: Contact) {
confirm( this.$notify(
"You should first make sure that your activity is no longer visible to them." + {
" Note that this only deletes them from your contacts on this device." + group: "modal",
" \n\nAre you sure you want to remove " + type: "confirm",
title: "Delete",
text:
"Are you sure you want to remove " +
this.nameForDid(this.contacts, contact.did) + this.nameForDid(this.contacts, contact.did) +
" with DID " + " with DID " +
contact.did + contact.did +
" from your contact list?", " from your contact list?",
) onYes: async () => {
) { await this.deleteContact(contact);
await db.open(); },
await db.contacts.delete(contact.did); },
this.contacts = R.without([contact], this.contacts); -1,
} );
} }
async register(contact: Contact) { async deleteContact(contact: Contact) {
if ( await db.open();
confirm( await db.contacts.delete(contact.did);
"Are you sure you want to register " + this.contacts = R.without([contact], this.contacts);
}
// prompt to register a new contact
async promptRegister(contact: Contact) {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Register",
text:
"Are you sure you want to register " +
this.nameForDid(this.contacts, contact.did) + this.nameForDid(this.contacts, contact.did) +
(contact.registered (contact.registered
? " -- especially since they are already marked as registered" ? " -- especially since they are already marked as registered"
: "") + : "") +
"?", "?",
) onYes: async () => {
) { await this.register(contact);
this.$notify(
{
group: "alert",
type: "toast",
text: "",
title: "Registration submitted...",
}, },
1000, },
); -1,
);
}
const identity = await this.getIdentity(this.activeDid); async register(contact: Contact) {
this.$notify(
{
group: "alert",
type: "toast",
text: "",
title: "Registration submitted...",
},
1000,
);
const vcClaim: RegisterVerifiableCredential = { const identity = await this.getIdentity(this.activeDid);
"@context": "https://schema.org",
"@type": "RegisterAction", const vcClaim: RegisterVerifiableCredential = {
agent: { identifier: identity.did }, "@context": "https://schema.org",
object: SERVICE_ID, "@type": "RegisterAction",
participant: { identifier: contact.did }, agent: { identifier: identity.did },
}; object: SERVICE_ID,
// Make a payload for the claim participant: { identifier: contact.did },
const vcPayload = { };
vc: { // Make a payload for the claim
"@context": ["https://www.w3.org/2018/credentials/v1"], const vcPayload = {
type: ["VerifiableCredential"], vc: {
credentialSubject: vcClaim, "@context": ["https://www.w3.org/2018/credentials/v1"],
}, type: ["VerifiableCredential"],
}; credentialSubject: vcClaim,
// Create a signature using private key of identity },
if (identity.keys[0].privateKeyHex !== null) { };
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // Create a signature using private key of identity
const privateKeyHex: string = identity.keys[0].privateKeyHex!; if (identity.keys[0].privateKeyHex !== null) {
const signer = await SimpleSigner(privateKeyHex); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const alg = undefined; const privateKeyHex: string = identity.keys[0].privateKeyHex!;
// Create a JWT for the request const signer = await SimpleSigner(privateKeyHex);
const vcJwt: string = await didJwt.createJWT(vcPayload, { const alg = undefined;
alg: alg, // Create a JWT for the request
issuer: identity.did, const vcJwt: string = await didJwt.createJWT(vcPayload, {
signer: signer, alg: alg,
}); issuer: identity.did,
signer: signer,
// Make the xhr request payload });
const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = this.apiServer + "/api/v2/claim"; // Make the xhr request payload
const headers = await this.getHeaders(identity); const payload = JSON.stringify({ jwtEncoded: vcJwt });
const url = this.apiServer + "/api/v2/claim";
try { const headers = await this.getHeaders(identity);
const resp = await this.axios.post(url, payload, { headers });
if (resp.data?.success?.embeddedRecordError) { try {
let message = "There was some problem with the registration."; const resp = await this.axios.post(url, payload, { headers });
if (typeof resp.data.success.embeddedRecordError == "string") { if (resp.data?.success?.embeddedRecordError) {
message += " " + resp.data.success.embeddedRecordError; let message = "There was some problem with the registration.";
} if (typeof resp.data.success.embeddedRecordError == "string") {
this.$notify( message += " " + resp.data.success.embeddedRecordError;
{
group: "alert",
type: "danger",
title: "Registration Still Unknown",
text: message,
},
5000,
);
} else if (resp.data?.success?.handleId) {
contact.registered = true;
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,
);
}
} 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( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Registration Error", title: "Registration Still Unknown",
text: userMessage, text: message,
},
5000,
);
} else if (resp.data?.success?.handleId) {
contact.registered = true;
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, 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,
);
} }
} }
} }
async promptSetVisibility(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 () => {
await this.setVisibility(contact, visibility, true);
},
},
-1,
);
}
async setVisibility( async setVisibility(
contact: Contact, contact: Contact,
visibility: boolean, visibility: boolean,
showSuccessAlert: boolean, showSuccessAlert: boolean,
) { ) {
const visibilityPrompt = const url =
showSuccessAlert && this.apiServer +
(visibility "/api/report/" +
? "Are you sure you want to make your activity visible to them?" (visibility ? "canSeeMe" : "cannotSeeMe");
: "Are you sure you want to hide all your activity from them?"); const identity = await this.getIdentity(this.activeDid);
if (!visibilityPrompt || confirm(visibilityPrompt)) { const headers = await this.getHeaders(identity);
const url = const payload = JSON.stringify({ did: contact.did });
this.apiServer +
"/api/report/" +
(visibility ? "canSeeMe" : "cannotSeeMe");
const identity = await this.getIdentity(this.activeDid);
const headers = await this.getHeaders(identity);
const payload = JSON.stringify({ did: contact.did });
try { try {
const resp = await this.axios.post(url, payload, { headers }); const resp = await this.axios.post(url, payload, { headers });
if (resp.status === 200) { if (resp.status === 200) {
if (showSuccessAlert) { if (showSuccessAlert) {
this.$notify(
{
group: "alert",
type: "success",
title: "Visibility Set",
text:
this.nameForDid(this.contacts, contact.did) +
" can " +
(visibility ? "" : "not ") +
"see your activity.",
},
3000,
);
}
contact.seesMe = visibility;
db.contacts.update(contact.did, { seesMe: visibility });
} else {
console.error(
"Got some bad server response when setting visibility: ",
resp.status,
resp,
);
const message =
resp.data.error?.message || "Got some error setting visibility.";
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "success",
title: "Error Setting Visibility", title: "Visibility Set",
text: message, text:
this.nameForDid(this.contacts, contact.did) +
" can " +
(visibility ? "" : "not ") +
"see your activity.",
}, },
5000, 3000,
); );
} }
} catch (err) { contact.seesMe = visibility;
console.error("Got some error when setting visibility:", err); db.contacts.update(contact.did, { seesMe: visibility });
} else {
console.error(
"Got some bad server response when setting visibility: ",
resp.status,
resp,
);
const message =
resp.data.error?.message || "Got some error setting visibility.";
this.$notify( this.$notify(
{ {
group: "alert", group: "alert",
type: "danger", type: "danger",
title: "Error Setting Visibility", title: "Error Setting Visibility",
text: "Check connectivity and try again.", text: message,
}, },
5000, 5000,
); );
} }
} catch (err) {
console.error("Got some error when setting visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Visibility",
text: "Check connectivity and try again.",
},
5000,
);
} }
} }
@ -1058,34 +1089,47 @@ export default class ContactsView extends Vue {
); );
} }
private showGiftedDialog(giverDid: string, recipientDid: string) { promptShowGiftedDialog(giverDid: string, recipientDid: string) {
// if they have unconfirmed amounts, ask to confirm those first // if they have unconfirmed amounts, ask to confirm those
if ( if (
recipientDid == this.activeDid && recipientDid === this.activeDid &&
this.givenToMeUnconfirmed[giverDid] > 0 this.givenToMeUnconfirmed[giverDid] > 0
) { ) {
const isAre = this.givenToMeUnconfirmed[giverDid] == 1 ? "is" : "are"; const isAre = this.givenToMeUnconfirmed[giverDid] == 1 ? "is" : "are";
const hours = this.givenToMeUnconfirmed[giverDid] == 1 ? "hour" : "hours"; const hours = this.givenToMeUnconfirmed[giverDid] == 1 ? "hour" : "hours";
if ( const message =
confirm( "There " +
"There " + isAre +
isAre + " " +
" " + this.givenToMeUnconfirmed[giverDid] +
this.givenToMeUnconfirmed[giverDid] + " unconfirmed " +
" unconfirmed " + hours +
hours + " from them." +
" from them." + " Would you like to confirm some of those hours?";
" Would you like to confirm some of those hours?", this.$notify(
) {
) { group: "modal",
this.$router.push({ type: "confirm",
name: "contact-amounts", title: "Delete",
query: { contactDid: giverDid }, text: message,
}); onNo: async () => {
return; this.showGiftedDialog(giverDid, recipientDid);
} },
onYes: async () => {
this.$router.push({
name: "contact-amounts",
query: { contactDid: giverDid },
});
},
},
-1,
);
} else {
this.showGiftedDialog(giverDid, recipientDid);
} }
}
private showGiftedDialog(giverDid: string, recipientDid: string) {
let giver: GiverReceiverInputInfo, receiver: GiverReceiverInputInfo; let giver: GiverReceiverInputInfo, receiver: GiverReceiverInputInfo;
if (giverDid) { if (giverDid) {
giver = { giver = {

Loading…
Cancel
Save