10 changed files with 667 additions and 14 deletions
			
			
		| @ -0,0 +1,99 @@ | |||
| <template> | |||
|   <QuickNav /> | |||
|   <TopMessage /> | |||
| 
 | |||
|   <!-- CONTENT --> | |||
|   <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> | |||
|     <!-- Back --> | |||
|     <div class="text-lg text-center font-light relative px-7"> | |||
|       <h1 | |||
|         class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" | |||
|         @click="$router.back()" | |||
|       > | |||
|         <fa icon="chevron-left" class="fa-fw"></fa> | |||
|       </h1> | |||
|     </div> | |||
| 
 | |||
|     <!-- Heading --> | |||
|     <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4"> | |||
|       Beginning of BVC Saturday Meeting | |||
|     </h1> | |||
| 
 | |||
|     <div> | |||
|       <h2 class="text-2xl m-4">You're Here</h2> | |||
|       <div class="m-4 flex"> | |||
|         <input type="checkbox" v-model="gaveTime" class="h-6 w-6" /> | |||
|         <span class="pb-2 pl-2 pr-2">Attended</span> | |||
|         <span v-if="gaveTime"> | |||
|           <input | |||
|             type="text" | |||
|             placeholder="How much time" | |||
|             v-model="hours" | |||
|             size="1" | |||
|             class="border border-slate-400 h-6 px-2" | |||
|           /> | |||
|           hour(s) | |||
|         </span> | |||
|         <!-- This is to match input height to avoid shifting when hiding & showing. --> | |||
|         <span v-else class="h-6" /> | |||
|       </div> | |||
|     </div> | |||
| 
 | |||
|     <div class="m-4" v-if="gaveTime && hours && hours != '0'"> | |||
|       <button | |||
|         @click="record()" | |||
|         class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md" | |||
|       > | |||
|         Sign & Send | |||
|       </button> | |||
|     </div> | |||
|   </section> | |||
| </template> | |||
| 
 | |||
| <script lang="ts"> | |||
| import { DateTime } from "luxon"; | |||
| import { Component, Vue } from "vue-facing-decorator"; | |||
| 
 | |||
| import QuickNav from "@/components/QuickNav.vue"; | |||
| import TopMessage from "@/components/TopMessage.vue"; | |||
| import { NotificationIface } from "@/constants/app"; | |||
| import { numberOrZero } from "@/libs/endorserServer"; | |||
| 
 | |||
| @Component({ | |||
|   components: { | |||
|     QuickNav, | |||
|     TopMessage, | |||
|   }, | |||
| }) | |||
| export default class QuickActionBvcBeginView extends Vue { | |||
|   $notify!: (notification: NotificationIface, timeout?: number) => void; | |||
| 
 | |||
|   gaveTime = false; | |||
|   hours = "1"; | |||
|   todayOrPreviousStartDate = ""; | |||
| 
 | |||
|   async mounted() { | |||
|     let currentOrPreviousSat = DateTime.now().setZone("America/Denver"); | |||
|     if (currentOrPreviousSat.weekday < 6) { | |||
|       // it's not Saturday or Sunday, | |||
|       // so move back one week before setting to the Saturday | |||
|       currentOrPreviousSat = currentOrPreviousSat.minus({ week: 1 }); | |||
|     } | |||
|     const eventStartDateObj = currentOrPreviousSat | |||
|       .set({ weekday: 6 }) | |||
|       .set({ hour: 9 }) | |||
|       .startOf("hour"); | |||
| 
 | |||
|     // Hack, but full ISO pushes the length to 340 which crashes verifyJWT! | |||
|     this.todayOrPreviousStartDate = | |||
|       eventStartDateObj.toISO({ | |||
|         suppressMilliseconds: true, | |||
|       }) || ""; | |||
|   } | |||
| 
 | |||
|   record() { | |||
|     const hoursNum = numberOrZero(this.hours); | |||
|     alert("Nope" + hoursNum); | |||
|   } | |||
| } | |||
| </script> | |||
| @ -0,0 +1,228 @@ | |||
| <template> | |||
|   <QuickNav /> | |||
|   <TopMessage /> | |||
| 
 | |||
|   <!-- CONTENT --> | |||
|   <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> | |||
|     <!-- Back --> | |||
|     <div class="text-lg text-center font-light relative px-7"> | |||
|       <h1 | |||
|         class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" | |||
|         @click="$router.back()" | |||
|       > | |||
|         <fa icon="chevron-left" class="fa-fw"></fa> | |||
|       </h1> | |||
|     </div> | |||
| 
 | |||
|     <!-- Heading --> | |||
|     <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4"> | |||
|       End of BVC Saturday Meeting | |||
|     </h1> | |||
| 
 | |||
|     <div> | |||
|       <h2 class="text-2xl m-4">Confirm</h2> | |||
|       <div v-if="claimsToConfirm.length === 0"> | |||
|         There are no claims today yet for you to confirm. | |||
|         <span v-if="claimCountWithHidden"> | |||
|           (There are {{ claimCountWithHidden }} hidden claims.) | |||
|         </span> | |||
|       </div> | |||
|       <ul class="border-t border-slate-300"> | |||
|         <li | |||
|           class="border-b border-slate-300 py-2" | |||
|           v-for="record in claimsToConfirm" | |||
|           :key="record.id" | |||
|         > | |||
|           <div class="grid grid-cols-12"> | |||
|             <span class="col-span-11 justify-self-start"> | |||
|               <span> | |||
|                 <input type="checkbox" class="mr-2 h-6 w-6" /> | |||
|               </span> | |||
|               {{ | |||
|                 claimSpecialDescription( | |||
|                   record, | |||
|                   activeDid, | |||
|                   allMyDids, | |||
|                   allContacts, | |||
|                 ) | |||
|               }} | |||
|               <a @click="onClickLoadClaim(record.id)"> | |||
|                 <fa | |||
|                   icon="circle-info" | |||
|                   class="pl-2 text-blue-500 cursor-pointer" | |||
|                 /> | |||
|               </a> | |||
|             </span> | |||
|           </div> | |||
|         </li> | |||
|       </ul> | |||
|     </div> | |||
| 
 | |||
|     <div> | |||
|       <h2 class="text-2xl m-4">Anything else?</h2> | |||
|       <div class="m-4 flex"> | |||
|         <input type="checkbox" v-model="someoneGave" class="h-6 w-6" /> | |||
|         <span class="pb-2 pl-2 pr-2">Someone gave</span> | |||
|         <span v-if="someoneGave"> | |||
|           <input | |||
|             type="text" | |||
|             v-model="description" | |||
|             size="20" | |||
|             class="border border-slate-400 h-6 px-2" | |||
|           /> | |||
|         </span> | |||
|         <!-- This is to match input height to avoid shifting when hiding & showing. --> | |||
|         <span v-else class="h-6">...</span> | |||
|       </div> | |||
|     </div> | |||
| 
 | |||
|     <div class="m-4" v-if="someoneGave && description"> | |||
|       <button | |||
|         @click="record()" | |||
|         class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md" | |||
|       > | |||
|         Sign & Send | |||
|       </button> | |||
|     </div> | |||
|   </section> | |||
| </template> | |||
| 
 | |||
| <script lang="ts"> | |||
| import { DateTime } from "luxon"; | |||
| import * as R from "ramda"; | |||
| import { IIdentifier } from "@veramo/core"; | |||
| import { Component, Vue } from "vue-facing-decorator"; | |||
| 
 | |||
| import QuickNav from "@/components/QuickNav.vue"; | |||
| import TopMessage from "@/components/TopMessage.vue"; | |||
| import { NotificationIface } from "@/constants/app"; | |||
| import { accountsDB, db } from "@/db/index"; | |||
| import { Account } from "@/db/tables/accounts"; | |||
| import { Contact } from "@/db/tables/contacts"; | |||
| import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; | |||
| import { accessToken } from "@/libs/crypto"; | |||
| import { | |||
|   claimSpecialDescription, | |||
|   containsHiddenDid, | |||
|   GenericServerRecord, | |||
| } from "@/libs/endorserServer"; | |||
| 
 | |||
| @Component({ | |||
|   methods: { claimSpecialDescription }, | |||
|   components: { | |||
|     QuickNav, | |||
|     TopMessage, | |||
|   }, | |||
| }) | |||
| export default class QuickActionBvcBeginView extends Vue { | |||
|   $notify!: (notification: NotificationIface, timeout?: number) => void; | |||
| 
 | |||
|   activeDid = ""; | |||
|   allContacts: Array<Contact> = []; | |||
|   allMyDids: Array<string> = []; | |||
|   apiServer = ""; | |||
|   claimCountWithHidden = 0; | |||
|   claimsToConfirm: GenericServerRecord[] = []; | |||
|   description = ""; | |||
|   someoneGave = false; | |||
| 
 | |||
|   async created() { | |||
|     await db.open(); | |||
|     const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; | |||
|     this.apiServer = settings?.apiServer || ""; | |||
|     this.activeDid = settings?.activeDid || ""; | |||
|     this.allContacts = await db.contacts.toArray(); | |||
|   } | |||
| 
 | |||
|   async mounted() { | |||
|     let currentOrPreviousSat = DateTime.now().setZone("America/Denver"); | |||
|     if (currentOrPreviousSat.weekday < 6) { | |||
|       // it's not Saturday or Sunday, | |||
|       // so move back one week before setting to the Saturday | |||
|       currentOrPreviousSat = currentOrPreviousSat.minus({ week: 1 }); | |||
|     } | |||
|     const eventStartDateObj = currentOrPreviousSat | |||
|       .set({ weekday: 6 }) | |||
|       .set({ hour: 9 }) | |||
|       .startOf("hour"); | |||
| 
 | |||
|     // Hack, but full ISO pushes the length to 340 which crashes verifyJWT! | |||
|     const todayOrPreviousStartDate = | |||
|       eventStartDateObj.toISO({ | |||
|         suppressMilliseconds: true, | |||
|       }) || ""; | |||
| 
 | |||
|     await accountsDB.open(); | |||
|     const allAccounts = await accountsDB.accounts.toArray(); | |||
|     this.allMyDids = allAccounts.map((acc) => acc.did); | |||
|     const account: Account | undefined = await accountsDB.accounts | |||
|       .where("did") | |||
|       .equals(this.activeDid) | |||
|       .first(); | |||
|     const identity: IIdentifier = JSON.parse( | |||
|       (account?.identity as string) || "null", | |||
|     ); | |||
|     const headers = { | |||
|       Authorization: "Bearer " + (await accessToken(identity)), | |||
|     }; | |||
|     console.log("todayOrPreviousStartDate", todayOrPreviousStartDate); | |||
|     try { | |||
|       console.log( | |||
|         this.apiServer + | |||
|           "/api/claim/?" + | |||
|           "issuedAt_greaterThanOrEqualTo=" + | |||
|           encodeURIComponent(todayOrPreviousStartDate) + | |||
|           "&excludeConfirmations=true", | |||
|       ); | |||
|       const response = await fetch( | |||
|         this.apiServer + | |||
|           "/api/claim/?" + | |||
|           "issuedAt_greaterThanOrEqualTo=" + | |||
|           encodeURIComponent(todayOrPreviousStartDate) + | |||
|           "&excludeConfirmations=true", | |||
|         { headers }, | |||
|       ); | |||
| 
 | |||
|       if (!response.ok) { | |||
|         console.log("Bad response", response); | |||
|         throw new Error("Bad response when retrieving claims."); | |||
|       } | |||
|       response.json().then((data) => { | |||
|         const dataByOthers = R.reject( | |||
|           (claim: GenericServerRecord) => claim.issuer === this.activeDid, | |||
|           data, | |||
|         ); | |||
|         const dataByOthersWithoutHidden = R.reject( | |||
|           containsHiddenDid, | |||
|           dataByOthers, | |||
|         ); | |||
|         this.claimsToConfirm = dataByOthersWithoutHidden; | |||
|         this.claimCountWithHidden = | |||
|           dataByOthers.length - dataByOthersWithoutHidden.length; | |||
|       }); | |||
|     } catch (error) { | |||
|       console.error("Error:", error); | |||
|       this.$notify( | |||
|         { | |||
|           group: "alert", | |||
|           type: "danger", | |||
|           title: "Error", | |||
|           text: "There was an error retrieving today's claims to confirm.", | |||
|         }, | |||
|         -1, | |||
|       ); | |||
|     } | |||
|   } | |||
| 
 | |||
|   onClickLoadClaim(jwtId: string) { | |||
|     const route = { | |||
|       path: "/claim/" + encodeURIComponent(jwtId), | |||
|     }; | |||
|     this.$router.push(route); | |||
|   } | |||
| 
 | |||
|   record() { | |||
|     alert("Nope"); | |||
|   } | |||
| } | |||
| </script> | |||
| @ -0,0 +1,52 @@ | |||
| <template> | |||
|   <QuickNav /> | |||
|   <TopMessage /> | |||
| 
 | |||
|   <!-- CONTENT --> | |||
|   <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> | |||
|     <!-- Back --> | |||
|     <div class="text-lg text-center font-light relative px-7"> | |||
|       <h1 | |||
|         class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" | |||
|         @click="$router.back()" | |||
|       > | |||
|         <fa icon="chevron-left" class="fa-fw"></fa> | |||
|       </h1> | |||
|     </div> | |||
| 
 | |||
|     <!-- Heading --> | |||
|     <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4"> | |||
|       Bountiful Voluntaryist Community Actions | |||
|     </h1> | |||
| 
 | |||
|     <div> | |||
|       <router-link | |||
|         :to="{ name: 'quick-action-bvc-begin' }" | |||
|         class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md" | |||
|       > | |||
|         Beginning of Meeting | |||
|       </router-link> | |||
|       <router-link | |||
|         :to="{ name: 'quick-action-bvc-end' }" | |||
|         class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md" | |||
|       > | |||
|         End of Meeting | |||
|       </router-link> | |||
|     </div> | |||
|   </section> | |||
| </template> | |||
| 
 | |||
| <script lang="ts"> | |||
| import { Component, Vue } from "vue-facing-decorator"; | |||
| 
 | |||
| import QuickNav from "@/components/QuickNav.vue"; | |||
| import TopMessage from "@/components/TopMessage.vue"; | |||
| 
 | |||
| @Component({ | |||
|   components: { | |||
|     QuickNav, | |||
|     TopMessage, | |||
|   }, | |||
| }) | |||
| export default class QuickActionBvcView extends Vue {} | |||
| </script> | |||
					Loading…
					
					
				
		Reference in new issue