23 changed files with 739 additions and 58 deletions
			
			
		| 
		 Before Width: | Height: | Size: 4.5 MiB  | 
| 
		 After Width: | Height: | Size: 705 KiB  | 
@ -0,0 +1,104 @@ | 
				
			|||||
 | 
					<template> | 
				
			||||
 | 
					  <div v-if="visible" class="dialog-overlay"> | 
				
			||||
 | 
					    <div class="dialog"> | 
				
			||||
 | 
					      <h1 class="text-lg text-center"> | 
				
			||||
 | 
					        Received from {{ contact?.name || "nobody in particular" }} | 
				
			||||
 | 
					      </h1> | 
				
			||||
 | 
					      <p class="py-2">{{ message }}</p> | 
				
			||||
 | 
					      <input | 
				
			||||
 | 
					        type="text" | 
				
			||||
 | 
					        class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" | 
				
			||||
 | 
					        placeholder="What you received" | 
				
			||||
 | 
					        v-model="description" | 
				
			||||
 | 
					      /> | 
				
			||||
 | 
					      <div class="flex flex-row"> | 
				
			||||
 | 
					        <span class="py-4">Hours</span> | 
				
			||||
 | 
					        <input | 
				
			||||
 | 
					          type="text" | 
				
			||||
 | 
					          class="block w-8 rounded border border-slate-400 ml-4 text-center" | 
				
			||||
 | 
					          v-model="hours" | 
				
			||||
 | 
					        /> | 
				
			||||
 | 
					        <div class="flex flex-col px-1"> | 
				
			||||
 | 
					          <div> | 
				
			||||
 | 
					            <fa icon="square-caret-up" size="2xl" @click="increment()" /> | 
				
			||||
 | 
					          </div> | 
				
			||||
 | 
					          <div> | 
				
			||||
 | 
					            <fa icon="square-caret-down" size="2xl" @click="decrement()" /> | 
				
			||||
 | 
					          </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					      <div class="text-right"> | 
				
			||||
 | 
					        <button class="rounded border border-slate-400" @click="confirm"> | 
				
			||||
 | 
					          <span class="m-2">Confirm</span> | 
				
			||||
 | 
					        </button> | 
				
			||||
 | 
					          | 
				
			||||
 | 
					        <button class="rounded border border-slate-400" @click="cancel"> | 
				
			||||
 | 
					          <span class="m-2">Cancel</span> | 
				
			||||
 | 
					        </button> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					  </div> | 
				
			||||
 | 
					</template> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<script> | 
				
			||||
 | 
					export default { | 
				
			||||
 | 
					  props: ["message"], | 
				
			||||
 | 
					  data() { | 
				
			||||
 | 
					    return { | 
				
			||||
 | 
					      contact: null, | 
				
			||||
 | 
					      description: "", | 
				
			||||
 | 
					      hours: "0", | 
				
			||||
 | 
					      visible: false, | 
				
			||||
 | 
					    }; | 
				
			||||
 | 
					  }, | 
				
			||||
 | 
					  methods: { | 
				
			||||
 | 
					    open(contact) { | 
				
			||||
 | 
					      this.contact = contact; | 
				
			||||
 | 
					      this.visible = true; | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    close() { | 
				
			||||
 | 
					      this.visible = false; | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    increment() { | 
				
			||||
 | 
					      this.hours = `${(parseFloat(this.hours) || 0) + 1}`; | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    decrement() { | 
				
			||||
 | 
					      this.hours = `${Math.max(0, (parseFloat(this.hours) || 1) - 1)}`; | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    confirm() { | 
				
			||||
 | 
					      this.close(); | 
				
			||||
 | 
					      this.$emit("dialog-result", { | 
				
			||||
 | 
					        action: "confirm", | 
				
			||||
 | 
					        contact: this.contact, | 
				
			||||
 | 
					        hours: parseFloat(this.hours), | 
				
			||||
 | 
					        description: this.description, | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    cancel() { | 
				
			||||
 | 
					      this.close(); | 
				
			||||
 | 
					      this.$emit("dialog-result", { action: "cancel" }); | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					  }, | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					</script> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					<style> | 
				
			||||
 | 
					.dialog-overlay { | 
				
			||||
 | 
					  position: fixed; | 
				
			||||
 | 
					  top: 0; | 
				
			||||
 | 
					  left: 0; | 
				
			||||
 | 
					  right: 0; | 
				
			||||
 | 
					  bottom: 0; | 
				
			||||
 | 
					  background-color: rgba(0, 0, 0, 0.5); | 
				
			||||
 | 
					  display: flex; | 
				
			||||
 | 
					  justify-content: center; | 
				
			||||
 | 
					  align-items: center; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					.dialog { | 
				
			||||
 | 
					  background-color: white; | 
				
			||||
 | 
					  padding: 1rem; | 
				
			||||
 | 
					  border-radius: 0.5rem; | 
				
			||||
 | 
					  width: 50%; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					</style> | 
				
			||||
@ -1,15 +1,360 @@ | 
				
			|||||
<template> | 
					<template> | 
				
			||||
  <section></section> | 
					  <!-- QUICK NAV --> | 
				
			||||
 | 
					  <nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50"> | 
				
			||||
 | 
					    <ul class="flex text-2xl p-2 gap-2"> | 
				
			||||
 | 
					      <!-- Home Feed --> | 
				
			||||
 | 
					      <li class="basis-1/5 rounded-md bg-slate-400 text-white"> | 
				
			||||
 | 
					        <router-link :to="{ name: 'home' }" class="block text-center py-3 px-1" | 
				
			||||
 | 
					          ><fa icon="house-chimney" class="fa-fw"></fa | 
				
			||||
 | 
					        ></router-link> | 
				
			||||
 | 
					      </li> | 
				
			||||
 | 
					      <!-- Search --> | 
				
			||||
 | 
					      <li class="basis-1/5 rounded-md text-slate-500"> | 
				
			||||
 | 
					        <router-link | 
				
			||||
 | 
					          :to="{ name: 'discover' }" | 
				
			||||
 | 
					          class="block text-center py-3 px-1" | 
				
			||||
 | 
					          ><fa icon="magnifying-glass" class="fa-fw"></fa | 
				
			||||
 | 
					        ></router-link> | 
				
			||||
 | 
					      </li> | 
				
			||||
 | 
					      <!-- Projects --> | 
				
			||||
 | 
					      <li class="basis-1/5 rounded-md text-slate-500"> | 
				
			||||
 | 
					        <router-link | 
				
			||||
 | 
					          :to="{ name: 'projects' }" | 
				
			||||
 | 
					          class="block text-center py-3 px-1" | 
				
			||||
 | 
					          ><fa icon="folder-open" class="fa-fw"></fa | 
				
			||||
 | 
					        ></router-link> | 
				
			||||
 | 
					      </li> | 
				
			||||
 | 
					      <!-- Contacts --> | 
				
			||||
 | 
					      <li class="basis-1/5 rounded-md text-slate-500"> | 
				
			||||
 | 
					        <router-link | 
				
			||||
 | 
					          :to="{ name: 'contacts' }" | 
				
			||||
 | 
					          class="block text-center py-3 px-1" | 
				
			||||
 | 
					          ><fa icon="users" class="fa-fw"></fa | 
				
			||||
 | 
					        ></router-link> | 
				
			||||
 | 
					      </li> | 
				
			||||
 | 
					      <!-- Profile --> | 
				
			||||
 | 
					      <li class="basis-1/5 rounded-md text-slate-500"> | 
				
			||||
 | 
					        <router-link | 
				
			||||
 | 
					          :to="{ name: 'account' }" | 
				
			||||
 | 
					          class="block text-center py-3 px-1" | 
				
			||||
 | 
					          ><fa icon="circle-user" class="fa-fw"></fa | 
				
			||||
 | 
					        ></router-link> | 
				
			||||
 | 
					      </li> | 
				
			||||
 | 
					    </ul> | 
				
			||||
 | 
					  </nav> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  <!-- CONTENT --> | 
				
			||||
 | 
					  <section id="Content" class="p-6 pb-24"> | 
				
			||||
 | 
					    <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> | 
				
			||||
 | 
					      Time Safari | 
				
			||||
 | 
					    </h1> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    <div> | 
				
			||||
 | 
					      <h1 class="text-2xl">Quick Action</h1> | 
				
			||||
 | 
					      <p>Choose a contact to whom to show appreciation:</p> | 
				
			||||
 | 
					      <div class="px-4"> | 
				
			||||
 | 
					        <button | 
				
			||||
 | 
					          v-for="contact in allContacts" | 
				
			||||
 | 
					          :key="contact.did" | 
				
			||||
 | 
					          @click="openDialog(contact)" | 
				
			||||
 | 
					          class="text-blue-500" | 
				
			||||
 | 
					        > | 
				
			||||
 | 
					           {{ contact.name }}, | 
				
			||||
 | 
					        </button> | 
				
			||||
 | 
					        or | 
				
			||||
 | 
					        <button @click="openDialog()" class="text-blue-500"> | 
				
			||||
 | 
					          nobody in particular | 
				
			||||
 | 
					        </button> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    <GiftedDialog | 
				
			||||
 | 
					      ref="customDialog" | 
				
			||||
 | 
					      @dialog-result="handleDialogResult" | 
				
			||||
 | 
					      message="Confirm to publish to the world." | 
				
			||||
 | 
					    > | 
				
			||||
 | 
					    </GiftedDialog> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    <div class="py-4"> | 
				
			||||
 | 
					      <h1 class="text-2xl">Latest Activity</h1> | 
				
			||||
 | 
					      <span :class="{ hidden: isHiddenSpinner }"> | 
				
			||||
 | 
					        <fa icon="spinner" class="fa-fw"></fa> | 
				
			||||
 | 
					        Loading… | 
				
			||||
 | 
					      </span> | 
				
			||||
 | 
					      <ul class=""> | 
				
			||||
 | 
					        <li | 
				
			||||
 | 
					          class="border-b border-slate-300" | 
				
			||||
 | 
					          v-for="record in feedData" | 
				
			||||
 | 
					          :key="record.jwtId" | 
				
			||||
 | 
					        > | 
				
			||||
 | 
					          <div | 
				
			||||
 | 
					            class="border-b text-orange-400 px-8 py-4" | 
				
			||||
 | 
					            v-if="record.jwtId == feedLastViewedId" | 
				
			||||
 | 
					          > | 
				
			||||
 | 
					            You've seen all claims below. | 
				
			||||
 | 
					          </div> | 
				
			||||
 | 
					          {{ this.giveDescription(record) }} | 
				
			||||
 | 
					        </li> | 
				
			||||
 | 
					      </ul> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					  </section> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  <!-- 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> | 
				
			||||
</template> | 
					</template> | 
				
			||||
 | 
					
 | 
				
			||||
<script lang="ts"> | 
					<script lang="ts"> | 
				
			||||
 | 
					import * as R from "ramda"; | 
				
			||||
import { Options, Vue } from "vue-class-component"; | 
					import { Options, Vue } from "vue-class-component"; | 
				
			||||
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src | 
					
 | 
				
			||||
 | 
					import GiftedDialog from "@/components/GiftedDialog.vue"; | 
				
			||||
 | 
					import { db, accountsDB } from "@/db"; | 
				
			||||
 | 
					import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; | 
				
			||||
 | 
					import { accessToken } from "@/libs/crypto"; | 
				
			||||
 | 
					import { createAndSubmitGive, didInfo } from "@/libs/endorserServer"; | 
				
			||||
 | 
					import { Account } from "@/db/tables/accounts"; | 
				
			||||
 | 
					import { Contact } from "@/db/tables/contacts"; | 
				
			||||
 | 
					
 | 
				
			||||
@Options({ | 
					@Options({ | 
				
			||||
  components: { | 
					  components: { GiftedDialog }, | 
				
			||||
    HelloWorld, | 
					 | 
				
			||||
  }, | 
					 | 
				
			||||
}) | 
					}) | 
				
			||||
export default class HomeView extends Vue {} | 
					export default class HomeView extends Vue { | 
				
			||||
 | 
					  activeDid = ""; | 
				
			||||
 | 
					  allAccounts: Array<Account> = []; | 
				
			||||
 | 
					  allContacts: Array<Contact> = []; | 
				
			||||
 | 
					  apiServer = ""; | 
				
			||||
 | 
					  feedAllLoaded = false; | 
				
			||||
 | 
					  feedData = []; | 
				
			||||
 | 
					  feedPreviousOldestId = null; | 
				
			||||
 | 
					  feedLastViewedId = null; | 
				
			||||
 | 
					  isHiddenSpinner = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // 'created' hook runs when the Vue instance is first created | 
				
			||||
 | 
					  async created() { | 
				
			||||
 | 
					    await accountsDB.open(); | 
				
			||||
 | 
					    this.allAccounts = await accountsDB.accounts.toArray(); | 
				
			||||
 | 
					    await db.open(); | 
				
			||||
 | 
					    const settings = await db.settings.get(MASTER_SETTINGS_KEY); | 
				
			||||
 | 
					    this.activeDid = settings?.activeDid || ""; | 
				
			||||
 | 
					    this.allContacts = await db.contacts.toArray(); | 
				
			||||
 | 
					    this.feedLastViewedId = settings?.lastViewedClaimId; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // 'mounted' hook runs after initial render | 
				
			||||
 | 
					  async mounted() { | 
				
			||||
 | 
					    try { | 
				
			||||
 | 
					      await db.open(); | 
				
			||||
 | 
					      const settings = await db.settings.get(MASTER_SETTINGS_KEY); | 
				
			||||
 | 
					      this.apiServer = settings?.apiServer || ""; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      this.updateAllFeed(); | 
				
			||||
 | 
					    } catch (err) { | 
				
			||||
 | 
					      console.log("Error in mounted():", err); | 
				
			||||
 | 
					      this.alertTitle = "Error"; | 
				
			||||
 | 
					      this.alertMessage = | 
				
			||||
 | 
					        err.userMessage || | 
				
			||||
 | 
					        "There was an error retrieving the latest sweet, sweet action."; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  updateAllFeed = async () => { | 
				
			||||
 | 
					    this.isHiddenSpinner = false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    await this.retrieveClaims(this.apiServer, null, this.feedPreviousOldestId) | 
				
			||||
 | 
					      .then(async (results) => { | 
				
			||||
 | 
					        if (results.data.length > 0) { | 
				
			||||
 | 
					          this.feedData = this.feedData.concat(results.data); | 
				
			||||
 | 
					          //console.log("Feed data:", this.feedData); | 
				
			||||
 | 
					          this.feedAllLoaded = results.hitLimit; | 
				
			||||
 | 
					          this.feedPreviousOldestId = | 
				
			||||
 | 
					            results.data[results.data.length - 1].jwtId; | 
				
			||||
 | 
					          if ( | 
				
			||||
 | 
					            this.feedLastViewedId == null || | 
				
			||||
 | 
					            this.feedLastViewedId < results.data[0].jwtId | 
				
			||||
 | 
					          ) { | 
				
			||||
 | 
					            // save it to storage | 
				
			||||
 | 
					            await db.open(); | 
				
			||||
 | 
					            db.settings.update(MASTER_SETTINGS_KEY, { | 
				
			||||
 | 
					              lastViewedClaimId: results.data[0].jwtId, | 
				
			||||
 | 
					            }); | 
				
			||||
 | 
					            // but not for this page because we need to remember what it was before | 
				
			||||
 | 
					          } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					      .catch((e) => { | 
				
			||||
 | 
					        console.log("Error with feed load:", e); | 
				
			||||
 | 
					        this.alertMessage = | 
				
			||||
 | 
					          e.userMessage || "There was an error retrieving feed data."; | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.isHiddenSpinner = true; | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  retrieveClaims = async (endorserApiServer, identifier, beforeId) => { | 
				
			||||
 | 
					    //const afterQuery = afterId == null ? "" : "&afterId=" + afterId; | 
				
			||||
 | 
					    const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId; | 
				
			||||
 | 
					    const headers = { "Content-Type": "application/json" }; | 
				
			||||
 | 
					    if (this.activeDid) { | 
				
			||||
 | 
					      const account = R.find( | 
				
			||||
 | 
					        (acc) => acc.did === this.activeDid, | 
				
			||||
 | 
					        this.allAccounts | 
				
			||||
 | 
					      ); | 
				
			||||
 | 
					      //console.log("about to parse from", this.activeDid, account?.identity); | 
				
			||||
 | 
					      const identity = JSON.parse(account?.identity || "null"); | 
				
			||||
 | 
					      if (!identity) { | 
				
			||||
 | 
					        throw new Error("No identity found."); | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      const token = await accessToken(identity); | 
				
			||||
 | 
					      headers["Authorization"] = "Bearer " + token; | 
				
			||||
 | 
					    } else { | 
				
			||||
 | 
					      // it's OK without auth... we just won't get any identifiers | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    return fetch(this.apiServer + "/api/v2/report/gives?" + beforeQuery, { | 
				
			||||
 | 
					      method: "GET", | 
				
			||||
 | 
					      headers: headers, | 
				
			||||
 | 
					    }) | 
				
			||||
 | 
					      .then(async (response) => { | 
				
			||||
 | 
					        if (response.status !== 200) { | 
				
			||||
 | 
					          const details = await response.text(); | 
				
			||||
 | 
					          throw details; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					        return response.json(); | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					      .then((results) => { | 
				
			||||
 | 
					        if (results.data) { | 
				
			||||
 | 
					          return results; | 
				
			||||
 | 
					        } else { | 
				
			||||
 | 
					          throw JSON.stringify(results); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  giveDescription(giveRecord) { | 
				
			||||
 | 
					    let claim = giveRecord.fullClaim; | 
				
			||||
 | 
					    if (claim.claim) { | 
				
			||||
 | 
					      // it's probably a Verified Credential | 
				
			||||
 | 
					      claim = claim.claim; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // agent.did is for legacy data, before March 2023 | 
				
			||||
 | 
					    const giver = | 
				
			||||
 | 
					      claim.agent?.identifier || claim.agent?.did || giveRecord.issuer; | 
				
			||||
 | 
					    const giverInfo = didInfo(giver, this.allAccounts, this.allContacts); | 
				
			||||
 | 
					    const gaveAmount = claim.object?.amountOfThisGood | 
				
			||||
 | 
					      ? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood) | 
				
			||||
 | 
					      : claim.description || "something unknown"; | 
				
			||||
 | 
					    // recipient.did is for legacy data, before March 2023 | 
				
			||||
 | 
					    const gaveRecipientId = claim.recipient?.identifier || claim.recipient?.did; | 
				
			||||
 | 
					    const gaveRecipientInfo = gaveRecipientId | 
				
			||||
 | 
					      ? " to " + didInfo(gaveRecipientId, this.allAccounts, this.allContacts) | 
				
			||||
 | 
					      : ""; | 
				
			||||
 | 
					    return giverInfo + " gave " + gaveAmount + gaveRecipientInfo; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  displayAmount(code, amt) { | 
				
			||||
 | 
					    return "" + amt + " " + this.currencyShortWordForCode(code, amt === 1); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  currencyShortWordForCode(unitCode, single) { | 
				
			||||
 | 
					    return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  openDialog(contact) { | 
				
			||||
 | 
					    this.$refs.customDialog.open(contact); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  handleDialogResult(result) { | 
				
			||||
 | 
					    if (result.action === "confirm") { | 
				
			||||
 | 
					      return new Promise((resolve) => { | 
				
			||||
 | 
					        this.recordGive(result.contact, result.description, result.hours); | 
				
			||||
 | 
					        resolve(); | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					    } else { | 
				
			||||
 | 
					      // action was "cancel" so do nothing | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  /** | 
				
			||||
 | 
					   * | 
				
			||||
 | 
					   * @param contact may be null | 
				
			||||
 | 
					   * @param description may be an empty string | 
				
			||||
 | 
					   * @param hours may be 0 | 
				
			||||
 | 
					   */ | 
				
			||||
 | 
					  recordGive(contact, description, hours) { | 
				
			||||
 | 
					    if (this.activeDid == null) { | 
				
			||||
 | 
					      this.alertTitle = "Error"; | 
				
			||||
 | 
					      this.alertMessage = | 
				
			||||
 | 
					        "You must select an identity before you can record a give."; | 
				
			||||
 | 
					      return; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    const account = R.find( | 
				
			||||
 | 
					      (acc) => acc.did === this.activeDid, | 
				
			||||
 | 
					      this.allAccounts | 
				
			||||
 | 
					    ); | 
				
			||||
 | 
					    //console.log("about to parse from", this.activeDid, account?.identity); | 
				
			||||
 | 
					    const identity = JSON.parse(account?.identity || "null"); | 
				
			||||
 | 
					    if (!identity) { | 
				
			||||
 | 
					      throw new Error("No identity found."); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					    createAndSubmitGive( | 
				
			||||
 | 
					      this.axios, | 
				
			||||
 | 
					      this.apiServer, | 
				
			||||
 | 
					      identity, | 
				
			||||
 | 
					      contact?.did, | 
				
			||||
 | 
					      this.activeDid, | 
				
			||||
 | 
					      description, | 
				
			||||
 | 
					      hours | 
				
			||||
 | 
					    ) | 
				
			||||
 | 
					      .then((result) => { | 
				
			||||
 | 
					        if (result.status != 201 || result.data?.error) { | 
				
			||||
 | 
					          console.log("Error with give result:", result); | 
				
			||||
 | 
					          this.alertTitle = "Error"; | 
				
			||||
 | 
					          this.alertMessage = | 
				
			||||
 | 
					            result.data?.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) => { | 
				
			||||
 | 
					        console.log("Error with give caught:", e); | 
				
			||||
 | 
					        this.alertTitle = "Error"; | 
				
			||||
 | 
					        this.alertMessage = | 
				
			||||
 | 
					          e.userMessage || "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> | 
					</script> | 
				
			||||
 | 
				
			|||||
					Loading…
					
					
				
		Reference in new issue