Browse Source
Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/kick-starter-for-time-pwa/pulls/43projects-view-improvements
anomalist
1 year ago
5 changed files with 350 additions and 47 deletions
@ -0,0 +1,265 @@ |
|||||
|
<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" |
||||
|
><fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa> |
||||
|
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" |
||||
|
><fa icon="user" class="fa-fw fa-xl text-slate-400"></fa> |
||||
|
{{ 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> |
||||
|
<AlertMessage |
||||
|
:alertTitle="alertTitle" |
||||
|
:alertMessage="alertMessage" |
||||
|
></AlertMessage> |
||||
|
</section> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { Component, Vue } from "vue-facing-decorator"; |
||||
|
import GiftedDialog from "@/components/GiftedDialog.vue"; |
||||
|
import { db, accountsDB } from "@/db"; |
||||
|
import { AccountsSchema } from "@/db/tables/accounts"; |
||||
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; |
||||
|
import { accessToken } from "@/libs/crypto"; |
||||
|
import { createAndSubmitGive } from "@/libs/endorserServer"; |
||||
|
import { Account } from "@/db/tables/accounts"; |
||||
|
import { Contact } from "@/db/tables/contacts"; |
||||
|
import AlertMessage from "@/components/AlertMessage"; |
||||
|
import QuickNav from "@/components/QuickNav"; |
||||
|
|
||||
|
@Component({ |
||||
|
components: { GiftedDialog, AlertMessage, QuickNav }, |
||||
|
}) |
||||
|
export default class HomeView extends Vue { |
||||
|
activeDid = ""; |
||||
|
allAccounts: Array<Account> = []; |
||||
|
allContacts: Array<Contact> = []; |
||||
|
apiServer = ""; |
||||
|
isHiddenSpinner = true; |
||||
|
alertTitle = ""; |
||||
|
alertMessage = ""; |
||||
|
accounts: AccountsSchema; |
||||
|
numAccounts = 0; |
||||
|
|
||||
|
async beforeCreate() { |
||||
|
accountsDB.open(); |
||||
|
this.accounts = accountsDB.accounts; |
||||
|
this.numAccounts = await this.accounts.count(); |
||||
|
} |
||||
|
|
||||
|
public async getIdentity(activeDid) { |
||||
|
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) { |
||||
|
const token = await accessToken(identity); |
||||
|
const headers = { |
||||
|
"Content-Type": "application/json", |
||||
|
Authorization: "Bearer " + token, |
||||
|
}; |
||||
|
return headers; |
||||
|
} |
||||
|
|
||||
|
async created() { |
||||
|
try { |
||||
|
await accountsDB.open(); |
||||
|
this.allAccounts = await accountsDB.accounts.toArray(); |
||||
|
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(); |
||||
|
this.feedLastViewedId = settings?.lastViewedClaimId; |
||||
|
this.updateAllFeed(); |
||||
|
} catch (err) { |
||||
|
this.alertTitle = "Error"; |
||||
|
this.alertMessage = |
||||
|
err.userMessage || |
||||
|
"There was an error retrieving the latest sweet, sweet action."; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async buildHeaders() { |
||||
|
const headers = { "Content-Type": "application/json" }; |
||||
|
|
||||
|
if (this.activeDid) { |
||||
|
await accountsDB.open(); |
||||
|
const allAccounts = await accountsDB.accounts.toArray(); |
||||
|
const account = allAccounts.find((acc) => acc.did === this.activeDid); |
||||
|
const identity = JSON.parse(account?.identity || "null"); |
||||
|
|
||||
|
if (!identity) { |
||||
|
throw new Error( |
||||
|
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service.", |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
headers["Authorization"] = "Bearer " + (await accessToken(identity)); |
||||
|
} else { |
||||
|
// it's OK without auth... we just won't get any identifiers |
||||
|
} |
||||
|
return headers; |
||||
|
} |
||||
|
|
||||
|
openDialog(giver) { |
||||
|
this.$refs.customDialog.open(giver); |
||||
|
} |
||||
|
|
||||
|
handleDialogResult(result) { |
||||
|
if (result.action === "confirm") { |
||||
|
return new Promise((resolve) => { |
||||
|
this.recordGive(result.contact?.did, result.description, result.hours); |
||||
|
resolve(); |
||||
|
}); |
||||
|
} 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, description, hours) { |
||||
|
if (!this.activeDid) { |
||||
|
this.setAlert( |
||||
|
"Error", |
||||
|
"You must select an identity before you can record a give.", |
||||
|
); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!description && !hours) { |
||||
|
this.setAlert( |
||||
|
"Error", |
||||
|
"You must enter a description or some number of hours.", |
||||
|
); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const identity = await this.getIdentity(this.activeDid); |
||||
|
const result = await createAndSubmitGive( |
||||
|
this.axios, |
||||
|
this.apiServer, |
||||
|
identity, |
||||
|
giverDid, |
||||
|
this.activeDid, |
||||
|
description, |
||||
|
hours, |
||||
|
); |
||||
|
|
||||
|
if (isGiveCreationError(result)) { |
||||
|
const errorMessage = getGiveCreationErrorMessage(result); |
||||
|
console.log("Error with give result:", result); |
||||
|
this.setAlert( |
||||
|
"Error", |
||||
|
errorMessage || "There was an error recording the give.", |
||||
|
); |
||||
|
} else { |
||||
|
this.setAlert("Success", "That gift was recorded."); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.log("Error with give caught:", error); |
||||
|
this.setAlert( |
||||
|
"Error", |
||||
|
getGiveErrorMessage(error) || "There was an error recording the give.", |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private setAlert(title, message) { |
||||
|
this.alertTitle = title; |
||||
|
this.alertMessage = message; |
||||
|
} |
||||
|
|
||||
|
// Helper functions for readability |
||||
|
|
||||
|
isGiveCreationError(result) { |
||||
|
return result.status !== 201 || result.data?.error; |
||||
|
} |
||||
|
|
||||
|
getGiveCreationErrorMessage(result) { |
||||
|
return result.data?.error?.message; |
||||
|
} |
||||
|
|
||||
|
getGiveErrorMessage(error) { |
||||
|
return error.userMessage || error.response?.data?.error?.message; |
||||
|
} |
||||
|
} |
||||
|
</script> |
Loading…
Reference in new issue