You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
7.6 KiB

<template>
<QuickNav selected="Home"></QuickNav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Back -->
<router-link
:to="{ name: 'home' }"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><fa icon="chevron-left" class="fa-fw"></fa
></router-link>
Give to Contacts
</h1>
</div>
<!-- Quick Search -->
<!-- Initial Loading Animation -->
<!-- Results List -->
<ul class="border-t border-slate-300">
<li class="border-b border-slate-300 py-3">
<h2 class="text-base flex gap-4 items-center">
<span class="grow italic text-slate-500"
><EntityIcon
:entityId="null"
:iconSize="32"
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
></EntityIcon>
Anonymous
</span>
<span class="text-right">
<button
type="button"
@click="openDialog()"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
</span>
</h2>
</li>
<li
v-for="contact in allContacts"
:key="contact.did"
class="border-b border-slate-300 py-3"
>
<h2 class="text-base flex gap-4 items-center">
<span class="grow font-semibold"
><EntityIcon
:entityId="contact.did"
:iconSize="32"
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
></EntityIcon>
{{ contact.name || "(no name)" }}
</span>
<span class="text-right">
<button
type="button"
@click="openDialog(contact)"
class="block w-full text-center text-sm uppercase bg-blue-600 text-white px-3 py-1.5 rounded-md"
>
<fa icon="gift" class="fa-fw"></fa>
</button>
</span>
</h2>
</li>
</ul>
<GiftedDialog
ref="customDialog"
@dialog-result="handleDialogResult"
message="Received from"
>
</GiftedDialog>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import GiftedDialog from "@/components/GiftedDialog.vue";
import { db, accountsDB } from "@/db/index";
import { AccountsSchema } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
createAndSubmitGive,
CreateAndSubmitGiveResult,
ErrorResult,
GiverInputInfo,
GiverOutputInfo,
} from "@/libs/endorserServer";
import { Contact } from "@/db/tables/contacts";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { IIdentifier } from "@veramo/core";
interface Notification {
group: string;
type: string;
title: string;
text: string;
}
@Component({
components: { GiftedDialog, QuickNav, EntityIcon },
})
export default class ContactGiftingView extends Vue {
$notify!: (notification: Notification, timeout?: number) => void;
activeDid = "";
allContacts: Array<Contact> = [];
apiServer = "";
accounts: typeof AccountsSchema;
numAccounts = 0;
async beforeCreate() {
accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
public async getIdentity(activeDid: string) {
await accountsDB.open();
const account = await accountsDB.accounts
.where("did")
.equals(activeDid)
.first();
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load Give records with no identity available.",
);
}
return identity;
}
public async getHeaders(identity: IIdentifier) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
this.allContacts = await db.contacts.toArray();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text:
err.message ||
"There was an error retrieving the latest sweet, sweet action.",
},
-1,
);
}
}
openDialog(giver: GiverInputInfo) {
(this.$refs.customDialog as GiftedDialog).open(giver);
}
handleDialogResult(result: GiverOutputInfo) {
if (result.action === "confirm") {
return new Promise((resolve) => {
this.recordGive(
result.giver?.did,
result.description,
result.hours,
).then(() => {
resolve(null);
});
});
} else {
// action was "cancel" so do nothing
}
}
/**
*
* @param giverDid may be null
* @param description may be an empty string
* @param hours may be 0
*/
public async recordGive(
giverDid?: string,
description?: string,
hours?: number,
) {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identity before you can record a give.",
},
-1,
);
return;
}
if (!description && !hours) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must enter a description or some number of hours.",
},
-1,
);
return;
}
try {
const identity = await this.getIdentity(this.activeDid);
const result = await createAndSubmitGive(
this.axios,
this.apiServer,
identity,
giverDid,
this.activeDid,
description,
hours,
);
if (this.isGiveCreationError(result)) {
const errorMessage = this.getGiveCreationErrorMessage(result);
console.log("Error with give result:", result);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: errorMessage || "There was an error recording the give.",
},
-1,
);
} else {
this.$notify(
{
group: "alert",
type: "success",
title: "Success",
text: "That gift was recorded.",
},
-1,
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
console.log("Error with give caught:", error);
const message =
error.userMessage ||
error.response?.data?.error?.message ||
"There was an error recording the Give.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: message,
},
-1,
);
}
}
// Helper functions for readability
isGiveCreationError(result: CreateAndSubmitGiveResult) {
return result.type == "error";
}
getGiveCreationErrorMessage(result: CreateAndSubmitGiveResult) {
return (result as ErrorResult).error?.userMessage;
}
}
</script>