Browse Source
			
			
			
			
				
		Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/kick-starter-for-time-pwa/pulls/43projects-view-improvements
				 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