diff --git a/apps/web/components/booking/AvailableTimes.tsx b/apps/web/components/booking/AvailableTimes.tsx index 394225ba3a..a48b802d98 100644 --- a/apps/web/components/booking/AvailableTimes.tsx +++ b/apps/web/components/booking/AvailableTimes.tsx @@ -72,6 +72,7 @@ const AvailableTimes: FC = ({ slotUtcStartDate: slot.time, eventTypeId, slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(), + bookingAttendees: bookingAttendees || undefined, }); }; diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index 98a390f8a4..4b208c5951 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -230,12 +230,14 @@ const BookingPage = ({ }), {} ); + const reserveSlot = () => { if (queryDuration) { reserveSlotMutation.mutate({ eventTypeId: eventType.id, slotUtcStartDate: dayjs(queryDate).utc().format(), slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(), + bookingAttendees: currentSlotBooking ? currentSlotBooking.attendees.length : undefined, }); } }; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 338ecda022..9a69df44c7 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1014,9 +1014,17 @@ async function handler( message?: string; }) | null = null; - const booking = await prisma.booking.findUniqueOrThrow({ + const booking = await prisma.booking.findFirst({ where: { - uid: rescheduleUid || reqBody.bookingUid, + OR: [ + { + uid: rescheduleUid || reqBody.bookingUid, + }, + { + eventTypeId: eventType.id, + startTime: evt.startTime, + }, + ], }, select: { uid: true, @@ -1029,6 +1037,11 @@ async function handler( status: true, }, }); + + if (!booking) { + throw new HttpError({ statusCode: 404, message: "Could not find booking" }); + } + // See if attendee is already signed up for timeslot if ( booking.attendees.find((attendee) => attendee.email === invitee[0].email) && diff --git a/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts b/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts index 9f98448535..b258c78b46 100644 --- a/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/reserveSlot.handler.ts @@ -21,41 +21,65 @@ interface ReserveSlotOptions { export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) => { const { prisma, req, res } = ctx; const uid = req?.cookies?.uid || uuid(); - const { slotUtcStartDate, slotUtcEndDate, eventTypeId } = input; + const { slotUtcStartDate, slotUtcEndDate, eventTypeId, bookingAttendees } = input; const releaseAt = dayjs.utc().add(parseInt(MINUTES_TO_BOOK), "minutes").format(); const eventType = await prisma.eventType.findUnique({ where: { id: eventTypeId }, select: { users: { select: { id: true } }, seatsPerTimeSlot: true }, }); - if (eventType) { - await Promise.all( - eventType.users.map((user) => - prisma.selectedSlots.upsert({ - where: { selectedSlotUnique: { userId: user.id, slotUtcStartDate, slotUtcEndDate, uid } }, - update: { - slotUtcStartDate, - slotUtcEndDate, - releaseAt, - eventTypeId, - }, - create: { - userId: user.id, - eventTypeId, - slotUtcStartDate, - slotUtcEndDate, - uid, - releaseAt, - isSeat: eventType.seatsPerTimeSlot !== null, - }, - }) - ) - ); - } else { + + if (!eventType) { throw new TRPCError({ message: "Event type not found", code: "NOT_FOUND", }); } + + let shouldReserveSlot = true; + + // If this is a seated event then don't reserve a slot + if (eventType.seatsPerTimeSlot) { + // Check to see if this is the last attendee + if (bookingAttendees) { + const seatsLeft = eventType.seatsPerTimeSlot - bookingAttendees; + if (seatsLeft < 1) shouldReserveSlot = false; + } else { + // If there is no booking yet then don't reserve the slot + shouldReserveSlot = false; + } + } + + if (eventType && shouldReserveSlot) { + try { + await Promise.all( + eventType.users.map((user) => + prisma.selectedSlots.upsert({ + where: { selectedSlotUnique: { userId: user.id, slotUtcStartDate, slotUtcEndDate, uid } }, + update: { + slotUtcStartDate, + slotUtcEndDate, + releaseAt, + eventTypeId, + }, + create: { + userId: user.id, + eventTypeId, + slotUtcStartDate, + slotUtcEndDate, + uid, + releaseAt, + isSeat: eventType.seatsPerTimeSlot !== null, + }, + }) + ) + ); + } catch { + throw new TRPCError({ + message: "Event type not found", + code: "NOT_FOUND", + }); + } + } res?.setHeader("Set-Cookie", serialize("uid", uid, { path: "/", sameSite: "lax" })); return; }; diff --git a/packages/trpc/server/routers/viewer/slots/types.ts b/packages/trpc/server/routers/viewer/slots/types.ts index f387f7059e..c980f558b2 100644 --- a/packages/trpc/server/routers/viewer/slots/types.ts +++ b/packages/trpc/server/routers/viewer/slots/types.ts @@ -33,6 +33,7 @@ export const reserveSlotSchema = z slotUtcStartDate: z.string(), // endTime ISOString slotUtcEndDate: z.string(), + bookingAttendees: z.number().optional(), }) .refine( (data) => !!data.eventTypeId || !!data.slotUtcStartDate || !!data.slotUtcEndDate,