feat: add customer email functionality for booking confirmations

- Implemented email confirmation for admin and public bookings.
- Added customerEmail field to booking schemas and services.
- Created email templates for booking confirmation, cancellation, and no-show notifications.
- Updated booking handlers to send emails upon booking creation and status updates.
- Enhanced frontend forms to capture customer email during booking creation.
This commit is contained in:
Jose Selesan
2026-06-02 19:21:04 -03:00
parent 50fa4ed9a5
commit 85f234b05e
22 changed files with 610 additions and 1536 deletions

View File

@@ -276,6 +276,8 @@ function mapBookingResponse(input: {
endTime: string;
customerName: string;
customerPhone: string;
customerEmail: string | null;
price: number;
status: 'CONFIRMED' | 'CANCELLED' | 'COMPLETED' | 'NOSHOW';
court: {
id: string;
@@ -304,7 +306,9 @@ function mapBookingResponse(input: {
endTime: input.endTime,
customerName: input.customerName,
customerPhone: input.customerPhone,
customerEmail: input.customerEmail ?? undefined,
status: input.status,
price: input.price,
createdAt: input.createdAt.toISOString(),
};
}
@@ -325,6 +329,7 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
bookingDate: true,
startTime: true,
endTime: true,
customerEmail: true,
status: true,
createdAt: true,
court: {
@@ -381,6 +386,7 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
slug: booking.court.sport.slug,
},
status: booking.status,
customerEmail: booking.customerEmail ?? undefined,
createdAt: booking.createdAt.toISOString(),
};
}
@@ -625,6 +631,7 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
endTime: selectedSlot.endTime,
customerName: input.customerName.trim(),
customerPhone: input.customerPhone.trim(),
customerEmail: input.customerEmail?.trim() || null,
status: 'CONFIRMED',
},
select: {
@@ -636,11 +643,14 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
endTime: true,
customerName: true,
customerPhone: true,
customerEmail: true,
status: true,
},
});
});
const price = resolveSlotPrice(selectedCourt, dayOfWeek, selectedSlot);
return mapBookingResponse({
bookingId: booking.id,
bookingCode: booking.bookingCode,
@@ -650,6 +660,8 @@ export async function createPublicBooking(complexSlug: string, input: CreatePubl
endTime: booking.endTime,
customerName: booking.customerName,
customerPhone: booking.customerPhone,
customerEmail: booking.customerEmail,
price,
status: booking.status,
court: {
id: selectedCourt.id,