You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

299 lines
8.7 KiB

<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="goBack()"
>
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
</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 v-model="attended" type="checkbox" class="h-6 w-6" />
<span class="pb-2 pl-2 pr-2">Attended</span>
</div>
<div class="m-2 flex">
<input v-model="gaveTime" type="checkbox" class="h-6 w-6" />
<span class="pb-2 pl-2 pr-2">Spent Time</span>
<span v-if="gaveTime">
<input
v-model="hoursStr"
type="text"
placeholder="How much time"
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="canSubmit" class="flex justify-center mt-4">
<button :class="activeButtonClass" @click="record()">Sign & Send</button>
</div>
<div v-else class="flex justify-center mt-4">
<button :class="disabledButtonClass">Select Your Actions</button>
</div>
</section>
</template>
<script lang="ts">
import axios from "axios";
import { DateTime } from "luxon";
import { Router } from "vue-router";
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 {
NOTIFY_BVC_PROCESSING,
NOTIFY_BVC_TIME_ERROR,
NOTIFY_BVC_ATTENDANCE_ERROR,
NOTIFY_BVC_SUBMISSION_ERROR,
createBvcSuccessMessage,
} from "../constants/notifications";
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
import {
BVC_MEETUPS_PROJECT_CLAIM_ID,
bvcMeetingJoinClaim,
createAndSubmitClaim,
createAndSubmitGive,
} from "../libs/endorserServer";
import * as libsUtil from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
/**
* @file QuickActionBvcBeginView.vue
* @description BVC (Bountiful Volunteerism Community) meeting attendance tracker
* for Saturday meetings. Allows users to record attendance and time contributions
* for weekly BVC meetings in Bountiful, using America/Denver timezone.
* @author Matthew Raymer
*/
@Component({
components: {
QuickNav,
TopMessage,
},
mixins: [PlatformServiceMixin],
})
export default class QuickActionBvcBeginView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
$router!: Router;
// Notification helper system
private notify = createNotifyHelpers(this.$notify);
attended = true;
gaveTime = true;
hoursStr = "1";
todayOrPreviousStartDate = "";
/**
* Lifecycle hook to calculate the current or previous Saturday meeting date
* Uses America/Denver timezone for Bountiful location
*/
async mounted() {
logger.debug(
"[QuickActionBvcBeginView] Mounted - calculating meeting date",
);
// use the time zone for Bountiful
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,
}) || "";
logger.debug(
"[QuickActionBvcBeginView] Meeting date calculated:",
this.todayOrPreviousStartDate,
);
}
/**
* Records attendance and/or time contribution to BVC meeting
* Creates claims for both attendance and time if applicable
*/
async record() {
logger.debug(
"[QuickActionBvcBeginView] Recording BVC meeting participation",
);
// Get account settings using PlatformServiceMixin
const settings = await this.$accountSettings();
const activeDid = settings.activeDid || "";
const apiServer = settings.apiServer || "";
if (!activeDid || !apiServer) {
logger.error("[QuickActionBvcBeginView] Missing required settings:", {
activeDid: !!activeDid,
apiServer: !!apiServer,
});
return;
}
try {
const hoursNum = libsUtil.numberOrZero(this.hoursStr);
logger.debug("[QuickActionBvcBeginView] Processing submission:", {
attended: this.attended,
gaveTime: this.gaveTime,
hours: hoursNum,
});
// Use notification helper with proper timeout
this.notify.toast(
NOTIFY_BVC_PROCESSING.title,
NOTIFY_BVC_PROCESSING.message,
TIMEOUTS.BRIEF,
);
// first send the claim for time given
let timeSuccess = false;
if (this.gaveTime && hoursNum > 0) {
logger.debug("[QuickActionBvcBeginView] Submitting time gift:", {
hours: hoursNum,
});
const timeResult = await createAndSubmitGive(
axios,
apiServer,
activeDid,
activeDid,
undefined,
undefined,
hoursNum,
"HUR",
BVC_MEETUPS_PROJECT_CLAIM_ID,
);
if (timeResult.success) {
timeSuccess = true;
logger.debug(
"[QuickActionBvcBeginView] Time gift submission successful",
);
} else {
logger.error(
"[QuickActionBvcBeginView] Error sending time:",
timeResult,
);
this.notify.error(
timeResult?.error || NOTIFY_BVC_TIME_ERROR.message,
TIMEOUTS.LONG,
);
}
}
// now send the claim for attendance
let attendedSuccess = false;
if (this.attended) {
logger.debug("[QuickActionBvcBeginView] Submitting attendance claim");
const attendResult = await createAndSubmitClaim(
bvcMeetingJoinClaim(activeDid, this.todayOrPreviousStartDate),
activeDid,
apiServer,
axios,
);
if (attendResult.success) {
attendedSuccess = true;
logger.debug(
"[QuickActionBvcBeginView] Attendance claim submission successful",
);
} else {
logger.error(
"[QuickActionBvcBeginView] Error sending attendance:",
attendResult,
);
this.notify.error(
attendResult?.error || NOTIFY_BVC_ATTENDANCE_ERROR.message,
TIMEOUTS.LONG,
);
}
}
if (timeSuccess || attendedSuccess) {
const successMessage = createBvcSuccessMessage(
timeSuccess,
attendedSuccess,
);
logger.debug(
"[QuickActionBvcBeginView] Submission completed successfully:",
{ timeSuccess, attendedSuccess },
);
this.notify.success(successMessage, TIMEOUTS.STANDARD);
this.$router.push({ path: "/quick-action-bvc" });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
logger.error("[QuickActionBvcBeginView] Error sending claims:", error);
this.notify.error(
error.userMessage || NOTIFY_BVC_SUBMISSION_ERROR.message,
TIMEOUTS.LONG,
);
}
}
/**
* Navigates back to the previous page
*/
goBack() {
this.$router.back();
}
/**
* Computed property for active button styling
*/
get activeButtonClass() {
return "block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56";
}
/**
* Computed property for disabled button styling
*/
get disabledButtonClass() {
return "block text-center text-md font-bold bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56";
}
/**
* Computed property to determine if the submit button should be enabled
* Returns true if user has attended or provided valid time contribution
*/
get canSubmit() {
return (
this.attended || (this.gaveTime && this.hoursStr && this.hoursStr !== "0")
);
}
}
</script>