Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dashboard/src/components/AttendeeFormControl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
import { Tooltip } from "frappe-ui";
import { formatPriceOrFree } from "@/utils/currency";
import CustomFieldInput from "./CustomFieldInput.vue";
import { getFieldDefaultValue } from "@/composables/useCustomFields.js";
import { getFieldDefaultValue } from "@/composables/useCustomFields";

const props = defineProps({
attendee: { type: Object, required: true },
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/BookingForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ import LucideCheck from "~icons/lucide/check";
import LucideCheckCircle from "~icons/lucide/check-circle";
import LucideGift from "~icons/lucide/gift";
import LucideX from "~icons/lucide/x";
import { useBookingFormStorage } from "../composables/useBookingFormStorage.js";
import { useBookingFormStorage } from "@/composables/useBookingFormStorage";
import AttendeeFormControl from "./AttendeeFormControl.vue";
import BookingSummary from "./BookingSummary.vue";
import CustomFieldsSection from "./CustomFieldsSection.vue";
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/CustomFieldInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
getFieldPlaceholder,
isDateField,
isDateTimeField,
} from "@/composables/useCustomFields.js";
} from "@/composables/useCustomFields";

const props = defineProps({
field: {
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/CustomFieldsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

<script setup>
import CustomFieldInput from "./CustomFieldInput.vue";
import { getFieldDefaultValue } from "@/composables/useCustomFields.js";
import { getFieldDefaultValue } from "@/composables/useCustomFields";

const props = defineProps({
customFields: {
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/QRScanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { Button, Spinner, TextInput, toast } from "frappe-ui";
import { Html5Qrcode } from "html5-qrcode";
import { onMounted, onUnmounted, ref } from "vue";
import LucideQrCode from "~icons/lucide/qr-code";
import { useTicketValidation } from "../composables/useTicketValidation.js";
import { useTicketValidation } from "@/composables/useTicketValidation";
const { validateTicket, isProcessingTicket } = useTicketValidation();
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/TicketDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ import { Button, Dialog } from "frappe-ui";
import LucideCheckCircle from "~icons/lucide/check-circle";
import LucideUserCheck from "~icons/lucide/user-check";
import LucideXCircle from "~icons/lucide/x-circle";
import { useTicketValidation } from "../composables/useTicketValidation.js";
import { useTicketValidation } from "@/composables/useTicketValidation";
import { formatPriceOrFree } from "@/utils/currency";

const props = defineProps({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useStorage } from "@vueuse/core";
* This allows components to access and clear booking form data stored in localStorage
* @param {string} eventRoute - The event route to scope the storage keys
*/
export function useBookingFormStorage(eventRoute) {
export function useBookingFormStorage(eventRoute: string) {
if (!eventRoute) {
throw new Error("eventRoute is required for useBookingFormStorage");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@
* parsing field options, and generating placeholders.
*/

/**
* Interface representing the structure of a Frappe Field
*/
export interface FrappeField {
fieldname: string;
fieldtype: string;
label?: string;
options?: string | any[];
placeholder?: string;
mandatory?: number | boolean;
default_value?: string | number | boolean;
}

export interface SelectOption {
label: string;
value: string;
}

/**
* Convert Frappe field types to FormControl types
* @param {string} fieldtype - Frappe field type
* @returns {string} - FormControl type
*/
export function getFormControlType(fieldtype) {
export function getFormControlType(fieldtype: string): string {
switch (fieldtype) {
case "Phone":
return "text";
Expand All @@ -35,7 +53,7 @@ export function getFormControlType(fieldtype) {
* @param {string} fieldtype - Frappe field type
* @returns {boolean}
*/
export function isDateField(fieldtype) {
export function isDateField(fieldtype: string): boolean {
return fieldtype === "Date";
}

Expand All @@ -44,7 +62,7 @@ export function isDateField(fieldtype) {
* @param {string} fieldtype - Frappe field type
* @returns {boolean}
*/
export function isDateTimeField(fieldtype) {
export function isDateTimeField(fieldtype: string): boolean {
return fieldtype === "Datetime";
}

Expand All @@ -53,7 +71,7 @@ export function isDateTimeField(fieldtype) {
* @param {Object} field - Field definition object
* @returns {Array} - Array of { label, value } objects
*/
export function getFieldOptions(field) {
export function getFieldOptions(field: FrappeField): SelectOption[] {
const isSelectType = field.fieldtype === "Select" || field.fieldtype === "Multi Select";
if (isSelectType && field.options) {
let options = [];
Expand Down Expand Up @@ -105,7 +123,7 @@ export function getFieldOptions(field) {
* @param {Object} field - Field definition object
* @returns {string} - Placeholder text
*/
export function getFieldPlaceholder(field) {
export function getFieldPlaceholder(field: FrappeField): string {
// If custom placeholder is provided, use it
if (field.placeholder?.trim()) {
const placeholder = field.placeholder.trim();
Expand All @@ -122,7 +140,7 @@ export function getFieldPlaceholder(field) {
* @param {Function} getFieldOptionsFn - Function to get field options
* @returns {*} - Default value or empty string
*/
export function getFieldDefaultValue(field) {
export function getFieldDefaultValue(field: FrappeField): string | number | boolean {
// For checkbox fields, handle 0/1 values explicitly
if (field.fieldtype === "Check") {
if (field.default_value === 1 || field.default_value === "1") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { createResource } from "frappe-ui";
import { computed } from "vue";
import { computed, type ComputedRef } from "vue";
import { userResource } from "@/data/user";

export function useLanguage() {
interface LanguageComposable {
availableLanguages: any;
currentLanguage: ComputedRef<string>;
changeLanguage: (languageCode: string) => void;
isSwitching: ComputedRef<boolean>;
}

export function useLanguage(): LanguageComposable {
const availableLanguages = createResource({
url: "buzz.api.get_enabled_languages",
auto: true,
Expand All @@ -21,7 +28,7 @@ export function useLanguage() {
},
});

function changeLanguage(languageCode) {
function changeLanguage(languageCode: string) {
switchLanguage.submit({ language_code: languageCode });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { ref, onMounted } from "vue";
import { ref, onMounted, type Ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { triggerCelebrationConfetti } from "@/utils/confetti";

interface PaymentSuccessOptions {
onSuccess?: () => void;
messageDuration?: number;
enableConfetti?: boolean;
cleanupUrl?: boolean;
}

interface PaymentSuccessReturn {
showSuccessMessage: Ref<boolean>;
triggerSuccessFlow: () => void;
checkForSuccess: () => void;
hideSuccessMessage: () => void;
showSuccess: () => void;
}

/**
* Composable for handling payment success flow
* Handles success message display, confetti animation, URL cleanup, and data refresh
Expand All @@ -13,7 +28,7 @@ import { triggerCelebrationConfetti } from "@/utils/confetti";
* @param {boolean} options.cleanupUrl - Whether to clean up success parameter from URL (default: true)
* @returns {Object} - Returns reactive state and helper functions
*/
export function usePaymentSuccess(options = {}) {
export function usePaymentSuccess(options: PaymentSuccessOptions = {}): PaymentSuccessReturn {
const {
onSuccess,
messageDuration = 10000,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,65 @@
import { createResource, toast } from "frappe-ui";
import { ref } from "vue";
import beepFailSound from "../assets/audio/beep-fail.wav";
import beepSound from "../assets/audio/beep.wav";
import { ref, type Ref } from "vue";
import beepFailSound from "@/assets/audio/beep-fail.wav";
import beepSound from "@/assets/audio/beep.wav";
import { TicketAddOnValue } from "@/types/Ticketing/TicketAddOnValue";

interface ValidationTicket {
id: string;
attendee_name: string;
attendee_email: string;
event_title: string;
ticket_type: string;
venue: string;
start_date: string;
start_time: string;
end_date: string;
end_time: string;
is_checked_in: boolean;
check_in_time: string | null;
check_in_date?: string | null;
booking_id: string;
add_ons: TicketAddOnValue[];
}

interface ValidationResult {
message: string;
ticket: ValidationTicket;
}

interface TicketValidationState {
isProcessingTicket: Ref<boolean>;
isCheckingIn: Ref<boolean>;
validationResult: Ref<ValidationResult | null>;
showTicketModal: Ref<boolean>;
validateTicket: (ticketId: string) => void;
checkInTicket: () => void;
clearResults: () => void;
closeModal: () => void;
}

let ticketValidationState = null;
let ticketValidationState: TicketValidationState | null = null;

const isProcessingTicket = ref(false);
const isCheckingIn = ref(false);
const validationResult = ref(null);
const validationResult = ref<ValidationResult | null>(null);
const showTicketModal = ref(false);

let lastToastMessage = null;
let lastToastMessage: string | null = null;
let lastToastTime = 0;
const TOAST_DEBOUNCE_MS = 500;

const playSuccessSound = () => {
const playSuccessSound = (): void => {
const audio = new Audio(beepSound);
audio.play();
};

const playErrorSound = () => {
const playErrorSound = (): void => {
const audio = new Audio(beepFailSound);
audio.play();
};

const showDebouncedToast = (message, type = "error") => {
const showDebouncedToast = (message: string, type: "error" | "success" = "error"): void => {
const now = Date.now();
if (lastToastMessage === message && now - lastToastTime < TOAST_DEBOUNCE_MS) {
return;
Expand All @@ -42,13 +77,13 @@ const showDebouncedToast = (message, type = "error") => {
// Ticket validation resource
const validateTicketResource = createResource({
url: "buzz.api.validate_ticket_for_checkin",
onSuccess: (data) => {
onSuccess: (data: ValidationResult) => {
validationResult.value = data;
showTicketModal.value = true;
playSuccessSound();
isProcessingTicket.value = false;
},
onError: (error) => {
onError: (error: any) => {
validationResult.value = null;
isProcessingTicket.value = false;
const errorData = JSON.stringify(error);
Expand All @@ -73,42 +108,42 @@ const validateTicketResource = createResource({
// Check-in resource
const checkInResource = createResource({
url: "buzz.api.checkin_ticket",
onSuccess: (data) => {
onSuccess: (data: ValidationResult) => {
validationResult.value = data;
showTicketModal.value = false;
isCheckingIn.value = false;
},
onError: (error) => {
onError: (error: any) => {
isCheckingIn.value = false;
},
});

export function useTicketValidation() {
export function useTicketValidation(): TicketValidationState {
if (ticketValidationState) {
return ticketValidationState;
}

// Methods
const validateTicket = (ticketId) => {
const validateTicket = (ticketId: string): void => {
isProcessingTicket.value = true;
validateTicketResource.submit({ ticket_id: ticketId });
};

const checkInTicket = () => {
const checkInTicket = (): void => {
if (!validationResult.value?.ticket?.id) return;

isCheckingIn.value = true;
checkInResource.submit({ ticket_id: validationResult.value.ticket.id });
};

const clearResults = () => {
const clearResults = (): void => {
validationResult.value = null;
isProcessingTicket.value = false;
isCheckingIn.value = false;
showTicketModal.value = false;
};

const closeModal = () => {
const closeModal = (): void => {
showTicketModal.value = false;
};

Expand Down
15 changes: 15 additions & 0 deletions dashboard/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,19 @@ declare global {
}

function __(str: string, values?: any[]): string;

declare module "*.wav" {
const value: string;
export default value;
}

declare module "*.mp3" {
const value: string;
export default value;
}

declare module "*.svg" {
const value: string;
export default value;
}
}
4 changes: 2 additions & 2 deletions dashboard/src/pages/BookingDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
import { ref, computed } from "vue";
import { createResource, Spinner } from "frappe-ui";
import { useRoute } from "vue-router";
import { usePaymentSuccess } from "../composables/usePaymentSuccess.js";
import { useBookingFormStorage } from "../composables/useBookingFormStorage.js";
import { usePaymentSuccess } from "@/composables/usePaymentSuccess";
import { useBookingFormStorage } from "@/composables/useBookingFormStorage";
import BookingHeader from "../components/BookingHeader.vue";
import SuccessMessage from "../components/SuccessMessage.vue";
import CancellationRequestNotice from "../components/CancellationRequestNotice.vue";
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/CheckInScanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ import BackButton from "../components/common/BackButton.vue";
import EventSelector from "../components/EventSelector.vue";
import QRScanner from "../components/QRScanner.vue";
import TicketDetailsModal from "../components/TicketDetailsModal.vue";
import { useTicketValidation } from "../composables/useTicketValidation.js";
import { useTicketValidation } from "@/composables/useTicketValidation";
import LucideShieldX from "~icons/lucide/shield-x";
import { userResource } from "@/data/user";

Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/SponsorshipDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ import LucideCheckCircle from "~icons/lucide/check-circle";
import LucideClock from "~icons/lucide/clock";
import LucideXCircle from "~icons/lucide/x-circle";
import SponsorshipPaymentDialog from "../components/SponsorshipPaymentDialog.vue";
import { usePaymentSuccess } from "../composables/usePaymentSuccess.js";
import { usePaymentSuccess } from "@/composables/usePaymentSuccess";
import BackButton from "../components/common/BackButton.vue";

const props = defineProps({
Expand Down
Loading