|
|
|
<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 px-4 mb-4">
|
|
|
|
End of BVC Saturday Meeting
|
|
|
|
</h1>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h2 class="text-2xl m-2">Confirm</h2>
|
|
|
|
<div v-if="loadingConfirms" class="flex justify-center">
|
|
|
|
<fa icon="spinner" class="animate-spin" />
|
|
|
|
</div>
|
|
|
|
<div v-else-if="claimsToConfirm.length === 0">
|
|
|
|
There are no claims yet today for you to confirm.
|
|
|
|
</div>
|
|
|
|
<ul class="border-t border-slate-300 m-2">
|
|
|
|
<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"
|
|
|
|
:checked="claimsToConfirmSelected.includes(record.id)"
|
|
|
|
@click="
|
|
|
|
claimsToConfirmSelected.includes(record.id)
|
|
|
|
? claimsToConfirmSelected.splice(
|
|
|
|
claimsToConfirmSelected.indexOf(record.id),
|
|
|
|
1,
|
|
|
|
)
|
|
|
|
: claimsToConfirmSelected.push(record.id)
|
|
|
|
"
|
|
|
|
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 v-if="claimCountWithHidden > 0" class="border-b border-slate-300 pb-2">
|
|
|
|
<span>
|
|
|
|
{{
|
|
|
|
claimCountWithHidden === 1
|
|
|
|
? "There is 1 other claim with hidden details,"
|
|
|
|
: `There are ${claimCountWithHidden} other claims with hidden details,`
|
|
|
|
}}
|
|
|
|
so if you expected but do not see details from someone then ask them to
|
|
|
|
check that their activity is visible to you on their Contacts
|
|
|
|
<fa icon="users" class="text-slate-500" />
|
|
|
|
page.
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h2 class="text-2xl m-2">Anything else?</h2>
|
|
|
|
<div class="m-2 flex">
|
|
|
|
<input type="checkbox" v-model="someoneGave" class="h-6 w-6" />
|
|
|
|
<span class="pb-2 pl-2 pr-2">Someone else 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
|
|
|
|
v-if="claimsToConfirmSelected.length || (someoneGave && description)"
|
|
|
|
class="flex justify-center mt-4"
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
@click="record()"
|
|
|
|
class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md w-56"
|
|
|
|
>
|
|
|
|
Sign & Send
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div v-else class="flex justify-center mt-4">
|
|
|
|
<button
|
|
|
|
class="block text-center text-md font-bold bg-slate-500 text-white px-2 py-3 rounded-md w-56"
|
|
|
|
>
|
|
|
|
Choose What To Confirm
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import axios from "axios";
|
|
|
|
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 {
|
|
|
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
|
|
|
claimSpecialDescription,
|
|
|
|
containsHiddenDid,
|
|
|
|
createAndSubmitConfirmation,
|
|
|
|
createAndSubmitGive,
|
|
|
|
ErrorResult,
|
|
|
|
GenericServerRecord,
|
|
|
|
GenericVerifiableCredential,
|
|
|
|
} from "@/libs/endorserServer";
|
|
|
|
import * as libsUtil from "@/libs/util";
|
|
|
|
|
|
|
|
@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[] = [];
|
|
|
|
claimsToConfirmSelected: string[] = [];
|
|
|
|
description = "breakfast";
|
|
|
|
loadingConfirms = true;
|
|
|
|
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() {
|
|
|
|
this.loadingConfirms = true;
|
|
|
|
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)),
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
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.");
|
|
|
|
}
|
|
|
|
await 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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.loadingConfirms = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
onClickLoadClaim(jwtId: string) {
|
|
|
|
const route = {
|
|
|
|
path: "/claim/" + encodeURIComponent(jwtId),
|
|
|
|
};
|
|
|
|
this.$router.push(route);
|
|
|
|
}
|
|
|
|
|
|
|
|
async record() {
|
|
|
|
try {
|
|
|
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
|
|
|
|
|
|
|
// in parallel, make a confirmation for each selected claim and send them all to the server
|
|
|
|
const confirmResults = await Promise.allSettled(
|
|
|
|
this.claimsToConfirmSelected.map(async (jwtId) => {
|
|
|
|
const record = this.claimsToConfirm.find(
|
|
|
|
(claim) => claim.id === jwtId,
|
|
|
|
);
|
|
|
|
if (!record) {
|
|
|
|
return { type: "error", error: "Record not found." };
|
|
|
|
}
|
|
|
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
|
|
|
return createAndSubmitConfirmation(
|
|
|
|
identity,
|
|
|
|
record.claim as GenericVerifiableCredential,
|
|
|
|
record.id,
|
|
|
|
record.handleId,
|
|
|
|
this.apiServer,
|
|
|
|
axios,
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
// check for any rejected confirmations
|
|
|
|
const confirmsSucceeded = confirmResults.filter(
|
|
|
|
(result) =>
|
|
|
|
result.status === "fulfilled" && result.value.type === "success",
|
|
|
|
);
|
|
|
|
if (confirmsSucceeded.length < this.claimsToConfirmSelected.length) {
|
|
|
|
console.error("Error sending confirmations:", confirmResults);
|
|
|
|
const howMany = confirmsSucceeded.length === 0 ? "all" : "some";
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: `There was an error sending ${howMany} of the confirmations.`,
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// now send the give for the description
|
|
|
|
let giveSucceeded = false;
|
|
|
|
if (this.someoneGave) {
|
|
|
|
const giveResult = await createAndSubmitGive(
|
|
|
|
axios,
|
|
|
|
this.apiServer,
|
|
|
|
identity,
|
|
|
|
undefined,
|
|
|
|
this.activeDid,
|
|
|
|
this.description,
|
|
|
|
undefined,
|
|
|
|
undefined,
|
|
|
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
|
|
|
);
|
|
|
|
giveSucceeded = giveResult.type === "success";
|
|
|
|
if (!giveSucceeded) {
|
|
|
|
console.error("Error sending give:", giveResult);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text:
|
|
|
|
(giveResult as ErrorResult)?.error?.userMessage ||
|
|
|
|
"There was an error sending the give.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (confirmsSucceeded.length > 0 || giveSucceeded) {
|
|
|
|
const confirms =
|
|
|
|
confirmsSucceeded.length === 1 ? "confirmation" : "confirmations";
|
|
|
|
const actions =
|
|
|
|
confirmsSucceeded.length > 0 && giveSucceeded
|
|
|
|
? `${confirms} and give have been`
|
|
|
|
: giveSucceeded
|
|
|
|
? "give has been"
|
|
|
|
: confirms +
|
|
|
|
" " +
|
|
|
|
(confirmsSucceeded.length === 1 ? "has" : "have") +
|
|
|
|
" been";
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "success",
|
|
|
|
title: "Success",
|
|
|
|
text: `Your ${actions} recorded.`,
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
} catch (error: any) {
|
|
|
|
console.error("Error sending claims.", error);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: error.userMessage || "There was an error sending claims.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|