|  |  | @ -9,7 +9,7 @@ | 
			
		
	
		
			
				
					|  |  |  |         ></router-link> | 
			
		
	
		
			
				
					|  |  |  |       </li> | 
			
		
	
		
			
				
					|  |  |  |       <!-- Search --> | 
			
		
	
		
			
				
					|  |  |  |       <li class="basis-1/5 rounded-md bg-slate-400 text-white"> | 
			
		
	
		
			
				
					|  |  |  |       <li class="basis-1/5 rounded-md text-slate-500"> | 
			
		
	
		
			
				
					|  |  |  |         <router-link | 
			
		
	
		
			
				
					|  |  |  |           :to="{ name: 'discover' }" | 
			
		
	
		
			
				
					|  |  |  |           class="block text-center py-3 px-1" | 
			
		
	
	
		
			
				
					|  |  | @ -17,7 +17,7 @@ | 
			
		
	
		
			
				
					|  |  |  |         ></router-link> | 
			
		
	
		
			
				
					|  |  |  |       </li> | 
			
		
	
		
			
				
					|  |  |  |       <!-- Projects --> | 
			
		
	
		
			
				
					|  |  |  |       <li class="basis-1/5 rounded-md text-slate-500"> | 
			
		
	
		
			
				
					|  |  |  |       <li class="basis-1/5 rounded-md bg-slate-400 text-white"> | 
			
		
	
		
			
				
					|  |  |  |         <router-link | 
			
		
	
		
			
				
					|  |  |  |           :to="{ name: 'projects' }" | 
			
		
	
		
			
				
					|  |  |  |           class="block text-center py-3 px-1" | 
			
		
	
	
		
			
				
					|  |  | @ -108,14 +108,50 @@ | 
			
		
	
		
			
				
					|  |  |  |       </button> | 
			
		
	
		
			
				
					|  |  |  |     </div> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     <button | 
			
		
	
		
			
				
					|  |  |  |       @click="openDialog({ name: 'you', did: activeDid })" | 
			
		
	
		
			
				
					|  |  |  |       class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8" | 
			
		
	
		
			
				
					|  |  |  |     > | 
			
		
	
		
			
				
					|  |  |  |       I gave... | 
			
		
	
		
			
				
					|  |  |  |     </button> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     <div> | 
			
		
	
		
			
				
					|  |  |  |       <p>... or choose a contact who gave:</p> | 
			
		
	
		
			
				
					|  |  |  |       <!-- similar contact selection code is in multiple places --> | 
			
		
	
		
			
				
					|  |  |  |       <div class="px-4"> | 
			
		
	
		
			
				
					|  |  |  |         <button | 
			
		
	
		
			
				
					|  |  |  |           v-for="contact in allContacts" | 
			
		
	
		
			
				
					|  |  |  |           :key="contact.did" | 
			
		
	
		
			
				
					|  |  |  |           @click="openDialog(contact)" | 
			
		
	
		
			
				
					|  |  |  |           class="text-blue-500" | 
			
		
	
		
			
				
					|  |  |  |         > | 
			
		
	
		
			
				
					|  |  |  |            {{ contact.name }}, | 
			
		
	
		
			
				
					|  |  |  |         </button> | 
			
		
	
		
			
				
					|  |  |  |         <span v-if="allContacts.length > 0"> or </span> | 
			
		
	
		
			
				
					|  |  |  |         <button @click="openDialog()" class="text-blue-500"> | 
			
		
	
		
			
				
					|  |  |  |           someone not specified | 
			
		
	
		
			
				
					|  |  |  |         </button> | 
			
		
	
		
			
				
					|  |  |  |       </div> | 
			
		
	
		
			
				
					|  |  |  |     </div> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     <GiftedDialog | 
			
		
	
		
			
				
					|  |  |  |       ref="customDialog" | 
			
		
	
		
			
				
					|  |  |  |       @dialog-result="handleDialogResult" | 
			
		
	
		
			
				
					|  |  |  |       message="Received from" | 
			
		
	
		
			
				
					|  |  |  |     > | 
			
		
	
		
			
				
					|  |  |  |     </GiftedDialog> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     <!-- Commit --> | 
			
		
	
		
			
				
					|  |  |  |     <!-- | 
			
		
	
		
			
				
					|  |  |  |     <router-link | 
			
		
	
		
			
				
					|  |  |  |       :to="{ name: 'new-edit-commitment' }" | 
			
		
	
		
			
				
					|  |  |  |       class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8" | 
			
		
	
		
			
				
					|  |  |  |       >Make Commitment</router-link | 
			
		
	
		
			
				
					|  |  |  |     > | 
			
		
	
		
			
				
					|  |  |  |     --> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     <!-- Commitments --> | 
			
		
	
		
			
				
					|  |  |  |     <!-- | 
			
		
	
		
			
				
					|  |  |  |     <div class="bg-slate-100 px-4 py-3 rounded-md"> | 
			
		
	
		
			
				
					|  |  |  |       <h3 class="text-sm uppercase font-semibold mb-3">Commitments</h3> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  | @ -142,6 +178,19 @@ | 
			
		
	
		
			
				
					|  |  |  |         </li> | 
			
		
	
		
			
				
					|  |  |  |       </ul> | 
			
		
	
		
			
				
					|  |  |  |     </div> | 
			
		
	
		
			
				
					|  |  |  |     --> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     <!-- This same popup code is in many files. --> | 
			
		
	
		
			
				
					|  |  |  |     <div v-bind:class="computedAlertClassNames()"> | 
			
		
	
		
			
				
					|  |  |  |       <button | 
			
		
	
		
			
				
					|  |  |  |         class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2" | 
			
		
	
		
			
				
					|  |  |  |         @click="onClickClose()" | 
			
		
	
		
			
				
					|  |  |  |       > | 
			
		
	
		
			
				
					|  |  |  |         <fa icon="xmark"></fa> | 
			
		
	
		
			
				
					|  |  |  |       </button> | 
			
		
	
		
			
				
					|  |  |  |       <h4 class="font-bold pr-5">{{ alertTitle }}</h4> | 
			
		
	
		
			
				
					|  |  |  |       <p>{{ alertMessage }}</p> | 
			
		
	
		
			
				
					|  |  |  |     </div> | 
			
		
	
		
			
				
					|  |  |  |   </section> | 
			
		
	
		
			
				
					|  |  |  | </template> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  | @ -151,15 +200,20 @@ import * as moment from "moment"; | 
			
		
	
		
			
				
					|  |  |  | import * as R from "ramda"; | 
			
		
	
		
			
				
					|  |  |  | import { Options, Vue } from "vue-class-component"; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | import GiftedDialog from "@/components/GiftedDialog.vue"; | 
			
		
	
		
			
				
					|  |  |  | import { accountsDB, db } from "@/db"; | 
			
		
	
		
			
				
					|  |  |  | import { Contact } from "@/db/tables/contacts"; | 
			
		
	
		
			
				
					|  |  |  | import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; | 
			
		
	
		
			
				
					|  |  |  | import { createAndSubmitGive } from "@/libs/endorserServer"; | 
			
		
	
		
			
				
					|  |  |  | import { accessToken } from "@/libs/crypto"; | 
			
		
	
		
			
				
					|  |  |  | import { IIdentifier } from "@veramo/core"; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | @Options({ | 
			
		
	
		
			
				
					|  |  |  |   components: {}, | 
			
		
	
		
			
				
					|  |  |  |   components: { GiftedDialog }, | 
			
		
	
		
			
				
					|  |  |  | }) | 
			
		
	
		
			
				
					|  |  |  | export default class ProjectViewView extends Vue { | 
			
		
	
		
			
				
					|  |  |  |   activeDid = ""; | 
			
		
	
		
			
				
					|  |  |  |   allContacts: Array<Contact> = []; | 
			
		
	
		
			
				
					|  |  |  |   apiServer = ""; | 
			
		
	
		
			
				
					|  |  |  |   expanded = false; | 
			
		
	
		
			
				
					|  |  |  |   name = ""; | 
			
		
	
	
		
			
				
					|  |  | @ -167,7 +221,7 @@ export default class ProjectViewView extends Vue { | 
			
		
	
		
			
				
					|  |  |  |   truncatedDesc = ""; | 
			
		
	
		
			
				
					|  |  |  |   truncateLength = 40; | 
			
		
	
		
			
				
					|  |  |  |   timeSince = ""; | 
			
		
	
		
			
				
					|  |  |  |   projectId = localStorage.getItem("projectId") || ""; | 
			
		
	
		
			
				
					|  |  |  |   projectId = localStorage.getItem("projectId") || ""; // handle ID | 
			
		
	
		
			
				
					|  |  |  |   errorMessage = ""; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   onEditClick() { | 
			
		
	
	
		
			
				
					|  |  | @ -230,8 +284,9 @@ export default class ProjectViewView extends Vue { | 
			
		
	
		
			
				
					|  |  |  |   async created() { | 
			
		
	
		
			
				
					|  |  |  |     await db.open(); | 
			
		
	
		
			
				
					|  |  |  |     const settings = await db.settings.get(MASTER_SETTINGS_KEY); | 
			
		
	
		
			
				
					|  |  |  |     const activeDid = settings?.activeDid || ""; | 
			
		
	
		
			
				
					|  |  |  |     this.activeDid = settings?.activeDid || ""; | 
			
		
	
		
			
				
					|  |  |  |     this.apiServer = settings?.apiServer || ""; | 
			
		
	
		
			
				
					|  |  |  |     this.allContacts = await db.contacts.toArray(); | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     await accountsDB.open(); | 
			
		
	
		
			
				
					|  |  |  |     const num_accounts = await accountsDB.accounts.count(); | 
			
		
	
	
		
			
				
					|  |  | @ -239,7 +294,7 @@ export default class ProjectViewView extends Vue { | 
			
		
	
		
			
				
					|  |  |  |       console.error("Problem!  Should have a profile!"); | 
			
		
	
		
			
				
					|  |  |  |     } else { | 
			
		
	
		
			
				
					|  |  |  |       const accounts = await accountsDB.accounts.toArray(); | 
			
		
	
		
			
				
					|  |  |  |       const account = R.find((acc) => acc.did === activeDid, accounts); | 
			
		
	
		
			
				
					|  |  |  |       const account = R.find((acc) => acc.did === this.activeDid, accounts); | 
			
		
	
		
			
				
					|  |  |  |       const identity = JSON.parse(account?.identity || "null"); | 
			
		
	
		
			
				
					|  |  |  |       if (!identity) { | 
			
		
	
		
			
				
					|  |  |  |         throw new Error("No identity found."); | 
			
		
	
	
		
			
				
					|  |  | @ -247,5 +302,102 @@ export default class ProjectViewView extends Vue { | 
			
		
	
		
			
				
					|  |  |  |       this.LoadProject(identity); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   openDialog(contact) { | 
			
		
	
		
			
				
					|  |  |  |     this.$refs.customDialog.open(contact); | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |   handleDialogResult(result) { | 
			
		
	
		
			
				
					|  |  |  |     if (result.action === "confirm") { | 
			
		
	
		
			
				
					|  |  |  |       return new Promise((resolve) => { | 
			
		
	
		
			
				
					|  |  |  |         this.recordGive(result.contact?.did, result.description, result.hours); | 
			
		
	
		
			
				
					|  |  |  |         resolve(); | 
			
		
	
		
			
				
					|  |  |  |       }); | 
			
		
	
		
			
				
					|  |  |  |     } else { | 
			
		
	
		
			
				
					|  |  |  |       // action was not "confirm" so do nothing | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   /** | 
			
		
	
		
			
				
					|  |  |  |    * | 
			
		
	
		
			
				
					|  |  |  |    * @param giverDid may be null | 
			
		
	
		
			
				
					|  |  |  |    * @param description may be an empty string | 
			
		
	
		
			
				
					|  |  |  |    * @param hours may be 0 | 
			
		
	
		
			
				
					|  |  |  |    */ | 
			
		
	
		
			
				
					|  |  |  |   async recordGive(giverDid, description, hours) { | 
			
		
	
		
			
				
					|  |  |  |     if (this.activeDid == null) { | 
			
		
	
		
			
				
					|  |  |  |       this.alertTitle = "Error"; | 
			
		
	
		
			
				
					|  |  |  |       this.alertMessage = | 
			
		
	
		
			
				
					|  |  |  |         "You must select an identity before you can record a give."; | 
			
		
	
		
			
				
					|  |  |  |       return; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |     if (!description && !hours) { | 
			
		
	
		
			
				
					|  |  |  |       this.alertTitle = "Error"; | 
			
		
	
		
			
				
					|  |  |  |       this.alertMessage = | 
			
		
	
		
			
				
					|  |  |  |         "You must enter a description or some number of hours."; | 
			
		
	
		
			
				
					|  |  |  |       return; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |     const accounts = await accountsDB.accounts.toArray(); | 
			
		
	
		
			
				
					|  |  |  |     const account = R.find((acc) => acc.did === this.activeDid, accounts); | 
			
		
	
		
			
				
					|  |  |  |     const identity = JSON.parse(account?.identity || "null"); | 
			
		
	
		
			
				
					|  |  |  |     if (!identity) { | 
			
		
	
		
			
				
					|  |  |  |       throw new Error("No identity found."); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |     createAndSubmitGive( | 
			
		
	
		
			
				
					|  |  |  |       this.axios, | 
			
		
	
		
			
				
					|  |  |  |       this.apiServer, | 
			
		
	
		
			
				
					|  |  |  |       identity, | 
			
		
	
		
			
				
					|  |  |  |       giverDid, | 
			
		
	
		
			
				
					|  |  |  |       this.activeDid, | 
			
		
	
		
			
				
					|  |  |  |       description, | 
			
		
	
		
			
				
					|  |  |  |       hours, | 
			
		
	
		
			
				
					|  |  |  |       this.projectId | 
			
		
	
		
			
				
					|  |  |  |     ) | 
			
		
	
		
			
				
					|  |  |  |       .then((result) => { | 
			
		
	
		
			
				
					|  |  |  |         if (result.status != 201 || result.data?.error) { | 
			
		
	
		
			
				
					|  |  |  |           console.log("Error with give result:", result); | 
			
		
	
		
			
				
					|  |  |  |           this.alertTitle = "Error"; | 
			
		
	
		
			
				
					|  |  |  |           this.alertMessage = | 
			
		
	
		
			
				
					|  |  |  |             result.data?.error?.message || | 
			
		
	
		
			
				
					|  |  |  |             "There was an error recording the give."; | 
			
		
	
		
			
				
					|  |  |  |         } else { | 
			
		
	
		
			
				
					|  |  |  |           this.alertTitle = "Success"; | 
			
		
	
		
			
				
					|  |  |  |           this.alertMessage = "That gift was recorded."; | 
			
		
	
		
			
				
					|  |  |  |           //this.updateAllFeed(); // full update is overkill but we should show something | 
			
		
	
		
			
				
					|  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |       }) | 
			
		
	
		
			
				
					|  |  |  |       .catch((e) => { | 
			
		
	
		
			
				
					|  |  |  |         // axios throws errors on 400 responses | 
			
		
	
		
			
				
					|  |  |  |         console.log("Error with give caught:", e); | 
			
		
	
		
			
				
					|  |  |  |         this.alertTitle = "Error"; | 
			
		
	
		
			
				
					|  |  |  |         this.alertMessage = | 
			
		
	
		
			
				
					|  |  |  |           e.userMessage || | 
			
		
	
		
			
				
					|  |  |  |           e.response?.data?.error?.message || | 
			
		
	
		
			
				
					|  |  |  |           "There was an error recording the give."; | 
			
		
	
		
			
				
					|  |  |  |       }); | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |   // This same popup code is in many files. | 
			
		
	
		
			
				
					|  |  |  |   alertMessage = ""; | 
			
		
	
		
			
				
					|  |  |  |   alertTitle = ""; | 
			
		
	
		
			
				
					|  |  |  |   public onClickClose() { | 
			
		
	
		
			
				
					|  |  |  |     this.alertTitle = ""; | 
			
		
	
		
			
				
					|  |  |  |     this.alertMessage = ""; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |   public computedAlertClassNames() { | 
			
		
	
		
			
				
					|  |  |  |     return { | 
			
		
	
		
			
				
					|  |  |  |       hidden: !this.alertMessage, | 
			
		
	
		
			
				
					|  |  |  |       "dismissable-alert": true, | 
			
		
	
		
			
				
					|  |  |  |       "bg-slate-100": true, | 
			
		
	
		
			
				
					|  |  |  |       "p-5": true, | 
			
		
	
		
			
				
					|  |  |  |       rounded: true, | 
			
		
	
		
			
				
					|  |  |  |       "drop-shadow-lg": true, | 
			
		
	
		
			
				
					|  |  |  |       fixed: true, | 
			
		
	
		
			
				
					|  |  |  |       "top-3": true, | 
			
		
	
		
			
				
					|  |  |  |       "inset-x-3": true, | 
			
		
	
		
			
				
					|  |  |  |       "transition-transform": true, | 
			
		
	
		
			
				
					|  |  |  |       "ease-in": true, | 
			
		
	
		
			
				
					|  |  |  |       "duration-300": true, | 
			
		
	
		
			
				
					|  |  |  |     }; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | </script> | 
			
		
	
	
		
			
				
					|  |  | 
 |