Fix/seats-cancel-links (#8394)

* Fix cancel links

* Use searchParams API to build cancelLink

* Fix cancel showSeats

* Added test for owner cancel attendee list

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
pull/8446/head
alannnc 2023-04-21 06:49:53 -07:00 committed by GitHub
parent 5ce341093b
commit afe1f5b72f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 16 deletions

View File

@ -891,16 +891,31 @@ const getEventTypesFromDB = async (id: number) => {
};
};
const handleSeatsEventTypeOnBooking = (
const handleSeatsEventTypeOnBooking = async (
eventType: {
seatsPerTimeSlot?: number | null;
seatsShowAttendees: boolean | null;
[x: string | number | symbol]: unknown;
},
bookingInfo: Partial<
Prisma.BookingGetPayload<{ include: { attendees: { select: { name: true; email: true } } } }>
Prisma.BookingGetPayload<{
include: {
attendees: { select: { name: true; email: true } };
seatsReferences: { select: { referenceUid: true } };
user: {
select: {
id: true;
name: true;
email: true;
username: true;
timeZone: true;
};
};
};
}>
>,
email: string
seatReferenceUid?: string,
userId?: number
) => {
if (eventType?.seatsPerTimeSlot !== null) {
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
@ -908,12 +923,34 @@ const handleSeatsEventTypeOnBooking = (
} else {
return;
}
// @TODO: If handling teams, we need to do more check ups for this.
if (bookingInfo?.user?.id === userId) {
return;
}
if (!eventType.seatsShowAttendees) {
const attendee = bookingInfo?.attendees?.find((a) => {
return a.email === email;
const seatAttendee = await prisma.bookingSeat.findFirst({
where: {
referenceUid: seatReferenceUid,
},
include: {
attendee: {
select: {
name: true,
email: true,
},
},
},
});
bookingInfo["attendees"] = attendee ? [attendee] : [];
if (seatAttendee) {
const attendee = bookingInfo?.attendees?.find((a) => {
return a.email === seatAttendee.attendee?.email;
});
bookingInfo["attendees"] = attendee ? [attendee] : [];
} else {
bookingInfo["attendees"] = [];
}
}
return bookingInfo;
};
@ -931,7 +968,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const parsedQuery = querySchema.safeParse(context.query);
if (!parsedQuery.success) return { notFound: true };
const { uid, email, eventTypeSlug, cancel, isSuccessBookingPage } = parsedQuery.data;
const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data;
const bookingInfoRaw = await prisma.booking.findFirst({
where: {
@ -1037,8 +1074,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
slug: eventType.team?.slug || eventType.users[0]?.username || null,
};
if (bookingInfo !== null && email && eventType.seatsPerTimeSlot) {
handleSeatsEventTypeOnBooking(eventType, bookingInfo, email);
if (bookingInfo !== null && eventType.seatsPerTimeSlot) {
await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id);
}
const payment = await prisma.payment.findFirst({

View File

@ -313,5 +313,145 @@ test.describe("Booking with Seats", () => {
// Validate that the number of seats its 10
expect(await page.locator("text=9 / 10 Seats available").count()).toEqual(0);
});
test("Should cancel with seats but event should be still accesible and with one less attendee/seat", 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.login();
const bookingAttendees = await prisma.attendee.findMany({
where: { bookingId: booking.id },
select: {
id: true,
},
});
const bookingSeats = [
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
];
await prisma.bookingSeat.createMany({
data: bookingSeats,
});
// Now we cancel the booking as the first attendee
// booking/${bookingUid}?cancel=true&allRemainingBookings=false&seatReferenceUid={bookingSeat.referenceUid}
await page.goto(
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[0].referenceUid}`
);
await page.locator('[data-testid="cancel"]').click();
await page.waitForLoadState("networkidle");
await expect(page).toHaveURL(/.*booking/);
await page.goto(
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[1].referenceUid}`
);
// Page should not be 404
await page.locator('[data-testid="cancel"]').click();
await page.waitForLoadState("networkidle");
await expect(page).toHaveURL(/.*booking/);
});
test("Should book with seats and hide attendees info from showAttendees true", 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.login();
const bookingWithEventType = await prisma.booking.findFirst({
where: { uid: booking.uid },
select: {
id: true,
eventTypeId: true,
},
});
await prisma.eventType.update({
data: {
seatsShowAttendees: false,
},
where: {
id: bookingWithEventType?.eventTypeId || -1,
},
});
const bookingAttendees = await prisma.attendee.findMany({
where: { bookingId: booking.id },
select: {
id: true,
},
});
const bookingSeats = [
{ bookingId: booking.id, attendeeId: bookingAttendees[0].id, referenceUid: uuidv4() },
{ bookingId: booking.id, attendeeId: bookingAttendees[1].id, referenceUid: uuidv4() },
];
await prisma.bookingSeat.createMany({
data: bookingSeats,
});
// Go to cancel page and see that attendees are listed and myself as I'm owner of the booking
await page.goto(`/booking/${booking.uid}?cancel=true&allRemainingBookings=false`);
const foundFirstAttendeeAsOwner = await page.locator('p[data-testid="attendee-first+seats@cal.com"]');
await expect(foundFirstAttendeeAsOwner).toHaveCount(1);
const foundSecondAttendeeAsOwner = await page.locator('p[data-testid="attendee-second+seats@cal.com"]');
await expect(foundSecondAttendeeAsOwner).toHaveCount(1);
await page.pause();
await page.goto("auth/logout");
// Now we cancel the booking as the first attendee
// booking/${bookingUid}?cancel=true&allRemainingBookings=false&seatReferenceUid={bookingSeat.referenceUid}
await page.goto(
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[0].referenceUid}`
);
// No attendees should be displayed only the one that it's cancelling
const notFoundSecondAttendee = await page.locator('p[data-testid="attendee-second+seats@cal.com"]');
await expect(notFoundSecondAttendee).toHaveCount(0);
const foundFirstAttendee = await page.locator('p[data-testid="attendee-first+seats@cal.com"]');
await expect(foundFirstAttendee).toHaveCount(1);
await prisma.eventType.update({
data: {
seatsShowAttendees: true,
},
where: {
id: bookingWithEventType?.eventTypeId || -1,
},
});
await page.goto(
`/booking/${booking.uid}?cancel=true&allRemainingBookings=false&seatReferenceUid=${bookingSeats[1].referenceUid}`
);
// Now attendees should be displayed
const foundSecondAttendee = await page.locator('p[data-testid="attendee-second+seats@cal.com"]');
await expect(foundSecondAttendee).toHaveCount(1);
const foundFirstAttendeeAgain = await page
.locator('p[data-testid="attendee-first+seats@cal.com"]')
.first();
await expect(foundFirstAttendeeAgain).toHaveCount(1);
});
});
});

View File

@ -1443,6 +1443,7 @@ async function handler(
* deep cloning evt to avoid this
*/
const copyEvent = cloneDeep(evt);
copyEvent.uid = booking.uid;
await sendScheduledSeatsEmails(copyEvent, invitee[0], newSeat, !!eventType.seatsShowAttendees);
const credentials = await refreshCredentials(organizerUser.credentials);

View File

@ -150,7 +150,7 @@ export const getUid = (calEvent: CalendarEvent): string => {
};
const getSeatReferenceId = (calEvent: CalendarEvent): string => {
return calEvent.attendeeSeatId ? `seatReferenceUid=${calEvent.attendeeSeatId}` : "";
return calEvent.attendeeSeatId ? calEvent.attendeeSeatId : "";
};
export const getManageLink = (calEvent: CalendarEvent) => {
@ -161,12 +161,12 @@ ${WEBAPP_URL + "/booking/" + getUid(calEvent) + "?changes=true"}
};
export const getCancelLink = (calEvent: CalendarEvent): string => {
return (
WEBAPP_URL +
`/booking/${getUid(
calEvent
)}?cancel=true&allRemainingBookings=${!!calEvent.recurringEvent}&${getSeatReferenceId}`
);
const cancelLink = new URL(WEBAPP_URL + `/booking/${getUid(calEvent)}`);
cancelLink.searchParams.append("cancel", "true");
cancelLink.searchParams.append("allRemainingBookings", String(!!calEvent.recurringEvent));
const seatReferenceUid = getSeatReferenceId(calEvent);
if (seatReferenceUid) cancelLink.searchParams.append("seatReferenceUid", seatReferenceUid);
return cancelLink.toString();
};
export const getRescheduleLink = (calEvent: CalendarEvent): string => {