diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index 54831045ec..f673aa9f64 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -185,6 +185,10 @@ function BookingListItem(booking: BookingItemProps) { const isTabUnconfirmed = booking.listingStatus === "unconfirmed"; const isBookingFromRoutingForm = isBookingReroutable(parsedBooking); + const userSeat = booking.seatsReferences.find((seat) => !!userEmail && seat.attendee?.email === userEmail); + + const isAttendee = !!userSeat; + const paymentAppData = getPaymentAppData(booking.eventType); const location = booking.location as ReturnType; @@ -223,10 +227,7 @@ function BookingListItem(booking: BookingItemProps) { }; const getSeatReferenceUid = () => { - if (!booking.seatsReferences[0]) { - return undefined; - } - return booking.seatsReferences[0].referenceUid; + return userSeat?.referenceUid; }; const actionContext: BookingActionContext = { @@ -250,6 +251,7 @@ function BookingListItem(booking: BookingItemProps) { booking.location === "integrations:daily" || (typeof booking.location === "string" && booking.location.trim() === ""), showPendingPayment: paymentAppData.enabled && booking.payment.length && !booking.paid, + isAttendee, cardCharged, attendeeList, getSeatReferenceUid, diff --git a/apps/web/components/booking/bookingActions.ts b/apps/web/components/booking/bookingActions.ts index cdc6982717..f98e99266a 100644 --- a/apps/web/components/booking/bookingActions.ts +++ b/apps/web/components/booking/bookingActions.ts @@ -21,6 +21,7 @@ export interface BookingActionContext { isDisabledRescheduling: boolean; isCalVideoLocation: boolean; showPendingPayment: boolean; + isAttendee: boolean; cardCharged: boolean; attendeeList: Array<{ name: string; @@ -102,6 +103,7 @@ export function getEditEventActions(context: BookingActionContext): ActionType[] isDisabledRescheduling, isBookingFromRoutingForm, getSeatReferenceUid, + isAttendee, t, } = context; @@ -111,7 +113,7 @@ export function getEditEventActions(context: BookingActionContext): ActionType[] icon: "clock", label: t("reschedule_booking"), href: `/reschedule/${booking.uid}${ - booking.seatsReferences.length ? `?seatReferenceUid=${getSeatReferenceUid()}` : "" + booking.seatsReferences.length && isAttendee ? `?seatReferenceUid=${getSeatReferenceUid()}` : "" }`, disabled: (isBookingInPast && !booking.eventType.allowReschedulingPastBookings) || isDisabledRescheduling, diff --git a/apps/web/lib/reschedule/[uid]/getServerSideProps.ts b/apps/web/lib/reschedule/[uid]/getServerSideProps.ts index 386972e980..88d7a26dfb 100644 --- a/apps/web/lib/reschedule/[uid]/getServerSideProps.ts +++ b/apps/web/lib/reschedule/[uid]/getServerSideProps.ts @@ -5,8 +5,8 @@ import { z } from "zod"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { determineReschedulePreventionRedirect } from "@calcom/features/bookings/lib/reschedule/determineReschedulePreventionRedirect"; -import { buildEventUrlFromBooking } from "@calcom/lib/bookings/buildEventUrlFromBooking"; import { getDefaultEvent } from "@calcom/features/eventtypes/lib/defaultEvents"; +import { buildEventUrlFromBooking } from "@calcom/lib/bookings/buildEventUrlFromBooking"; import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; import { UserRepository } from "@calcom/lib/server/repository/user"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; @@ -39,7 +39,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { uid, seatReferenceUid: maybeSeatReferenceUid, bookingSeat, - } = await maybeGetBookingUidFromSeat(prisma, bookingUid); + } = await maybeGetBookingUidFromSeat(prisma, seatReferenceUid ? seatReferenceUid : bookingUid); const booking = await prisma.booking.findUnique({ where: { diff --git a/apps/web/playwright/booking-seats.e2e.ts b/apps/web/playwright/booking-seats.e2e.ts index 97fe26ef07..9b6224f0a7 100644 --- a/apps/web/playwright/booking-seats.e2e.ts +++ b/apps/web/playwright/booking-seats.e2e.ts @@ -458,5 +458,128 @@ test.describe("Reschedule for booking with seats", () => { await expect(page.locator('[data-testid="confirm-reschedule-button"]')).toHaveCount(1); }); + test("Host reschedule from /upcoming page should have rescheduleUid parameter set to bookingUid", async ({ + page, + users, + bookings, + }) => { + const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ + { name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" }, + ]); + await user.apiLogin(); + + const bookingAttendees = await prisma.attendee.findMany({ + where: { bookingId: booking.id }, + select: { + id: true, + name: true, + email: true, + }, + }); + + const bookingSeats = bookingAttendees.map((attendee) => ({ + bookingId: booking.id, + attendeeId: attendee.id, + referenceUid: uuidv4(), + data: { + responses: { + name: attendee.name, + email: attendee.email, + }, + }, + })); + + await prisma.bookingSeat.createMany({ + data: bookingSeats, + }); + + await page.goto("/bookings/upcoming"); + await page.waitForSelector('[data-testid="bookings"]'); + + await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click(); + await page.locator('[data-testid="reschedule"]').click(); + + await page.waitForURL((url) => { + const rescheduleUid = url.searchParams.get("rescheduleUid"); + return !!rescheduleUid && rescheduleUid === booking.uid; + }); + }); + + test("Second attendee reschedule from /upcoming page should use correct seatReferenceUid and show attendee info", async ({ + page, + users, + bookings, + }) => { + const { user, booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ + { name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" }, + ]); + + const bookingAttendees = await prisma.attendee.findMany({ + where: { bookingId: booking.id }, + select: { + id: true, + name: true, + email: true, + }, + }); + + const bookingSeats = bookingAttendees.map((attendee) => ({ + bookingId: booking.id, + attendeeId: attendee.id, + referenceUid: uuidv4(), + data: { + responses: { + name: attendee.name, + email: attendee.email, + }, + }, + })); + + await prisma.bookingSeat.createMany({ + data: bookingSeats, + }); + + const references = await prisma.bookingSeat.findMany({ + where: { bookingId: booking.id }, + orderBy: { id: "asc" }, + }); + + const secondUser = await users.create({ + name: "Jane Second", + email: "second+seats@cal.com", + }); + await secondUser.apiLogin(); + + await page.goto("/bookings/upcoming"); + await page.waitForSelector('[data-testid="bookings"]'); + + await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click(); + await page.locator('[data-testid="reschedule"]').click(); + + await page.waitForURL((url) => { + const rescheduleUid = url.searchParams.get("rescheduleUid"); + return !!rescheduleUid && rescheduleUid === references[1].referenceUid; + }); + + await selectFirstAvailableTimeSlotNextMonth(page); + + const nameElement = page.locator("input[name=name]"); + const name = await nameElement.inputValue(); + expect(name).toBe("Jane Second"); + + const emailElement = page.locator("input[name=email]"); + const email = await emailElement.inputValue(); + expect(email).toBe("second+seats@cal.com"); + + // Complete the reschedule + await confirmReschedule(page); + + // Verify successful reschedule + await page.waitForURL(/\/booking\/.*/); + await expect(page).toHaveURL(/\/booking\/.*/); + }); + // @TODO: force 404 when rescheduleUid is not found }); diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts index 076a29fe66..9aea7ee396 100644 --- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts @@ -578,7 +578,6 @@ export async function getBookings({ .selectFrom("Attendee") .select(["Attendee.email"]) .whereRef("BookingSeat.attendeeId", "=", "Attendee.id") - .where("Attendee.email", "=", user.email) ).as("attendee"), ]) .whereRef("BookingSeat.bookingId", "=", "Booking.id")