Browse Source
Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/pulls/103pull/104/head
trentlarson
9 months ago
22 changed files with 1051 additions and 127 deletions
@ -0,0 +1,220 @@ |
|||||
|
<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"> |
||||
|
Beginning of BVC Saturday Meeting |
||||
|
</h1> |
||||
|
|
||||
|
<div> |
||||
|
<h2 class="text-2xl m-2">You're Here</h2> |
||||
|
<div class="m-2 flex"> |
||||
|
<input type="checkbox" v-model="attended" class="h-6 w-6" /> |
||||
|
<span class="pb-2 pl-2 pr-2">Attended</span> |
||||
|
</div> |
||||
|
<div class="m-2 flex"> |
||||
|
<input type="checkbox" v-model="gaveTime" class="h-6 w-6" /> |
||||
|
<span class="pb-2 pl-2 pr-2">Spent Time</span> |
||||
|
<span v-if="gaveTime"> |
||||
|
<input |
||||
|
type="text" |
||||
|
placeholder="How much time" |
||||
|
v-model="hoursStr" |
||||
|
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 |
||||
|
v-if="attended || (gaveTime && hoursStr && hoursStr != '0')" |
||||
|
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" |
||||
|
> |
||||
|
Select Your Actions |
||||
|
</button> |
||||
|
</div> |
||||
|
</section> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import axios from "axios"; |
||||
|
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 { db } from "@/db/index"; |
||||
|
import { |
||||
|
BVC_MEETUPS_PROJECT_CLAIM_ID, |
||||
|
bvcMeetingJoinClaim, |
||||
|
createAndSubmitClaim, |
||||
|
createAndSubmitGive, |
||||
|
} from "@/libs/endorserServer"; |
||||
|
import * as libsUtil from "@/libs/util"; |
||||
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings"; |
||||
|
|
||||
|
@Component({ |
||||
|
components: { |
||||
|
QuickNav, |
||||
|
TopMessage, |
||||
|
}, |
||||
|
}) |
||||
|
export default class QuickActionBvcBeginView extends Vue { |
||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void; |
||||
|
|
||||
|
attended = true; |
||||
|
gaveTime = true; |
||||
|
hoursStr = "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, |
||||
|
}) || ""; |
||||
|
} |
||||
|
|
||||
|
async record() { |
||||
|
await db.open(); |
||||
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings; |
||||
|
const activeDid = settings?.activeDid || ""; |
||||
|
const apiServer = settings?.apiServer || ""; |
||||
|
|
||||
|
try { |
||||
|
const hoursNum = libsUtil.numberOrZero(this.hoursStr); |
||||
|
const identity = await libsUtil.getIdentity(activeDid); |
||||
|
|
||||
|
// first send the claim for time given |
||||
|
let timeSuccess = false; |
||||
|
if (this.gaveTime && hoursNum > 0) { |
||||
|
const timeResult = await createAndSubmitGive( |
||||
|
axios, |
||||
|
apiServer, |
||||
|
identity, |
||||
|
activeDid, |
||||
|
undefined, |
||||
|
undefined, |
||||
|
hoursNum, |
||||
|
"HUR", |
||||
|
BVC_MEETUPS_PROJECT_CLAIM_ID, |
||||
|
); |
||||
|
if (timeResult.type === "success") { |
||||
|
timeSuccess = true; |
||||
|
} else { |
||||
|
console.error("Error sending give:", timeResult); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: |
||||
|
timeResult?.error?.userMessage || |
||||
|
"There was an error sending the time.", |
||||
|
}, |
||||
|
-1, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// now send the claim for attendance |
||||
|
let attendedSuccess = false; |
||||
|
if (this.attended) { |
||||
|
const attendResult = await createAndSubmitClaim( |
||||
|
bvcMeetingJoinClaim(activeDid, this.todayOrPreviousStartDate), |
||||
|
identity, |
||||
|
apiServer, |
||||
|
axios, |
||||
|
); |
||||
|
if (attendResult.type === "success") { |
||||
|
attendedSuccess = true; |
||||
|
} else { |
||||
|
console.error("Error sending give:", attendResult); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error", |
||||
|
text: |
||||
|
attendResult?.error?.userMessage || |
||||
|
"There was an error sending the attendance.", |
||||
|
}, |
||||
|
-1, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (timeSuccess || attendedSuccess) { |
||||
|
const actions = |
||||
|
timeSuccess && attendedSuccess |
||||
|
? "attendance and time have been" |
||||
|
: timeSuccess |
||||
|
? "time has been" |
||||
|
: "attendance has 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> |
@ -0,0 +1,368 @@ |
|||||
|
<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. |
||||
|
<span v-if="claimCountWithHidden > 0"> |
||||
|
{{ |
||||
|
claimCountWithHidden === 1 |
||||
|
? "(There is 1 claim with hidden details.)" |
||||
|
: `(There are ${claimCountWithHidden} claims with hidden details.)` |
||||
|
}} |
||||
|
</span> |
||||
|
</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> |
||||
|
<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> |
@ -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 px-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