Booking success query refactor (#5298)
* Booking succes query refactor The query is now using the uid as its main identifier for the success page * Minor changes to the succes.tsx and tests * Convert eventtype dates to string, and only select eventtype slug from db to have a smaller query (we don't need more data, and this way we don't need to convert the dates in here to smaller strings either.) * In the payment component get the bookingUid from props instead of the query * Changed the recurringMutation to use the uid for the success booking page Co-authored-by: alannnc <alannnc@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>pull/5514/head
parent
d66f3d1dc9
commit
6af0428a18
|
@ -185,26 +185,13 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
.concat(booking.recurringInfo?.bookings[BookingStatus.PENDING])
|
||||
.sort((date1: Date, date2: Date) => date1.getTime() - date2.getTime());
|
||||
|
||||
const location = booking.location || "";
|
||||
|
||||
const onClickTableData = () => {
|
||||
router.push({
|
||||
pathname: "/success",
|
||||
query: {
|
||||
date: booking.startTime,
|
||||
// TODO: Booking when fetched should have id 0 already(for Dynamic Events).
|
||||
type: booking.eventType.id || 0,
|
||||
eventSlug: booking.eventType.slug,
|
||||
username: user?.username || "",
|
||||
name: booking.attendees[0] ? booking.attendees[0].name : undefined,
|
||||
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
|
||||
location: location,
|
||||
eventName: booking.eventType.eventName || "",
|
||||
bookingId: booking.id,
|
||||
recur: booking.recurringEventId,
|
||||
reschedule: isConfirmed,
|
||||
uid: booking.uid,
|
||||
listingStatus: booking.listingStatus,
|
||||
status: booking.status,
|
||||
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -122,7 +122,7 @@ const BookingPage = ({
|
|||
|
||||
const mutation = useMutation(createBooking, {
|
||||
onSuccess: async (responseData) => {
|
||||
const { id, paymentUid } = responseData;
|
||||
const { uid, paymentUid } = responseData;
|
||||
if (paymentUid) {
|
||||
return await router.push(
|
||||
createPaymentLink({
|
||||
|
@ -138,17 +138,10 @@ const BookingPage = ({
|
|||
return router.push({
|
||||
pathname: "/success",
|
||||
query: {
|
||||
date,
|
||||
type: eventType.id,
|
||||
eventSlug: eventType.slug,
|
||||
username: profile.slug,
|
||||
reschedule: !!rescheduleUid,
|
||||
name: bookingForm.getValues("name"),
|
||||
email: bookingForm.getValues("email"),
|
||||
location: responseData.location,
|
||||
eventName: profile.eventName || "",
|
||||
bookingId: id,
|
||||
uid,
|
||||
isSuccessBookingPage: true,
|
||||
email: bookingForm.getValues("email"),
|
||||
eventTypeSlug: eventType.slug,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -156,31 +149,14 @@ const BookingPage = ({
|
|||
|
||||
const recurringMutation = useMutation(createRecurringBooking, {
|
||||
onSuccess: async (responseData = []) => {
|
||||
const { attendees = [], id, recurringEventId } = responseData[0] || {};
|
||||
const location = (function humanReadableLocation(location) {
|
||||
if (!location) {
|
||||
return;
|
||||
}
|
||||
if (location.includes("integration")) {
|
||||
return t("web_conferencing_details_to_follow");
|
||||
}
|
||||
return location;
|
||||
})(responseData[0].location);
|
||||
const { uid } = responseData[0] || {};
|
||||
|
||||
return router.push({
|
||||
pathname: "/success",
|
||||
query: {
|
||||
date,
|
||||
type: eventType.id,
|
||||
eventSlug: eventType.slug,
|
||||
recur: recurringEventId,
|
||||
username: profile.slug,
|
||||
reschedule: !!rescheduleUid,
|
||||
name: attendees[0].name,
|
||||
email: attendees[0].email,
|
||||
location,
|
||||
eventName: profile.eventName || "",
|
||||
bookingId: id,
|
||||
uid,
|
||||
email: bookingForm.getValues("email"),
|
||||
eventTypeSlug: eventType.slug,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import classNames from "classnames";
|
||||
import { createEvent } from "ics";
|
||||
|
@ -33,6 +33,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
|||
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import prisma, { baseUserSelect } from "@calcom/prisma";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
|
@ -144,28 +145,26 @@ type SuccessProps = inferSSRProps<typeof getServerSideProps>;
|
|||
export default function Success(props: SuccessProps) {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const {
|
||||
location: _location,
|
||||
name,
|
||||
email,
|
||||
reschedule,
|
||||
listingStatus,
|
||||
status,
|
||||
isSuccessBookingPage,
|
||||
} = router.query;
|
||||
const location: ReturnType<typeof getEventLocationValue> = Array.isArray(_location)
|
||||
? _location[0] || ""
|
||||
: _location || "";
|
||||
const { listingStatus, isSuccessBookingPage } = router.query;
|
||||
|
||||
const location: ReturnType<typeof getEventLocationValue> = Array.isArray(props.bookingInfo.location)
|
||||
? props.bookingInfo.location[0] || ""
|
||||
: props.bookingInfo.location || "";
|
||||
|
||||
if (!location) {
|
||||
// Can't use logger.error because it throws error on client. stdout isn't available to it.
|
||||
console.error(`No location found `);
|
||||
}
|
||||
|
||||
const name = props.bookingInfo?.user?.name;
|
||||
const email = props.bookingInfo?.user?.email;
|
||||
const status = props.bookingInfo?.status;
|
||||
const reschedule = props.bookingInfo.status === BookingStatus.ACCEPTED;
|
||||
|
||||
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
||||
const { data: session } = useSession();
|
||||
|
||||
const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date)));
|
||||
const [date, setDate] = useState(dayjs.utc(props.bookingInfo.startTime));
|
||||
const { eventType, bookingInfo } = props;
|
||||
|
||||
const isBackgroundTransparent = useIsBackgroundTransparent();
|
||||
|
@ -189,7 +188,7 @@ export default function Success(props: SuccessProps) {
|
|||
const giphyImage = giphyAppData?.thankYouPage;
|
||||
|
||||
const eventName = getEventName(eventNameObject, true);
|
||||
const needsConfirmation = eventType.requiresConfirmation && reschedule != "true";
|
||||
const needsConfirmation = eventType.requiresConfirmation && reschedule != true;
|
||||
const isCancelled = status === "CANCELLED" || status === "REJECTED";
|
||||
const telemetry = useTelemetry();
|
||||
useEffect(() => {
|
||||
|
@ -596,7 +595,7 @@ export default function Success(props: SuccessProps) {
|
|||
<EmailInput
|
||||
name="email"
|
||||
id="email"
|
||||
defaultValue={router.query.email}
|
||||
defaultValue={email || ""}
|
||||
className="focus:border-brand border-bookinglightest dark:border-darkgray-300 mt-0 block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:bg-black dark:text-white sm:text-sm"
|
||||
placeholder="rick.astley@cal.com"
|
||||
/>
|
||||
|
@ -740,6 +739,8 @@ const getEventTypesFromDB = async (id: number) => {
|
|||
metadata: true,
|
||||
seatsPerTimeSlot: true,
|
||||
seatsShowAttendees: true,
|
||||
periodStartDate: true,
|
||||
periodEndDate: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -756,24 +757,10 @@ const getEventTypesFromDB = async (id: number) => {
|
|||
};
|
||||
};
|
||||
|
||||
const strToNumber = z.string().transform((val, ctx) => {
|
||||
const parsed = parseInt(val);
|
||||
if (isNaN(parsed)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Not a number" });
|
||||
return parsed;
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
type: strToNumber,
|
||||
date: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
reschedule: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
uid: z.string(),
|
||||
email: z.string().optional(),
|
||||
recur: z.string().optional(),
|
||||
location: z.string().optional(),
|
||||
eventSlug: z.string().default("15min"),
|
||||
eventName: z.string().default(""),
|
||||
bookingId: strToNumber,
|
||||
eventTypeSlug: z.string().optional(),
|
||||
});
|
||||
|
||||
const handleSeatsEventTypeOnBooking = (
|
||||
|
@ -804,18 +791,60 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
const ssr = await ssrInit(context);
|
||||
const parsedQuery = schema.safeParse(context.query);
|
||||
if (!parsedQuery.success) return { notFound: true };
|
||||
const {
|
||||
type: eventTypeId,
|
||||
recur: recurringEventIdQuery,
|
||||
eventSlug: eventTypeSlug,
|
||||
eventName: dynamicEventName,
|
||||
bookingId,
|
||||
username,
|
||||
name,
|
||||
email,
|
||||
} = parsedQuery.data;
|
||||
const { uid, email, eventTypeSlug } = parsedQuery.data;
|
||||
|
||||
const eventTypeRaw = !eventTypeId ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId);
|
||||
const bookingInfo = await prisma.booking.findFirst({
|
||||
where: {
|
||||
uid,
|
||||
},
|
||||
select: {
|
||||
title: true,
|
||||
id: true,
|
||||
uid: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
smsReminderNumber: true,
|
||||
recurringEventId: true,
|
||||
startTime: true,
|
||||
location: true,
|
||||
status: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
eventTypeId: true,
|
||||
eventType: {
|
||||
select: {
|
||||
eventName: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!bookingInfo) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
// @NOTE: had to do this because Server side cant return [Object objects]
|
||||
// probably fixable with json.stringify -> json.parse
|
||||
bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date;
|
||||
|
||||
const eventTypeRaw = !bookingInfo.eventTypeId
|
||||
? getDefaultEvent(eventTypeSlug || "")
|
||||
: await getEventTypesFromDB(bookingInfo.eventTypeId);
|
||||
if (!eventTypeRaw) {
|
||||
return {
|
||||
notFound: true,
|
||||
|
@ -843,6 +872,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
|
||||
const eventType = {
|
||||
...eventTypeRaw,
|
||||
periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null,
|
||||
periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata),
|
||||
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
|
||||
};
|
||||
|
@ -856,59 +887,16 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
slug: eventType.team?.slug || eventType.users[0]?.username || null,
|
||||
};
|
||||
|
||||
const where: Prisma.BookingWhereInput = {
|
||||
id: bookingId,
|
||||
attendees: { some: { email, name } },
|
||||
};
|
||||
// Dynamic Event uses EventType from @calcom/lib/defaultEvents(a fake EventType) which doesn't have a real user/team/eventTypeId
|
||||
// So, you can't look them up in DB.
|
||||
if (!eventType.isDynamic) {
|
||||
// A Team Event doesn't have a correct user query param as of now. It is equal to team/{eventSlug} which is not a user, so you can't look it up in DB.
|
||||
if (!eventType.team) {
|
||||
// username being equal to profile.slug isn't applicable for Team or Dynamic Events.
|
||||
where.user = { username };
|
||||
}
|
||||
where.eventTypeId = eventType.id;
|
||||
} else {
|
||||
// username being equal to eventSlug for Dynamic Event Booking, it can't be used for user lookup. So, just use eventTypeId which would always be null for Dynamic Event Bookings
|
||||
where.eventTypeId = null;
|
||||
}
|
||||
|
||||
const bookingInfo = await prisma.booking.findFirst({
|
||||
where,
|
||||
select: {
|
||||
title: true,
|
||||
id: true,
|
||||
uid: true,
|
||||
description: true,
|
||||
customInputs: true,
|
||||
smsReminderNumber: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (bookingInfo !== null && email) {
|
||||
handleSeatsEventTypeOnBooking(eventType, bookingInfo, email);
|
||||
}
|
||||
|
||||
let recurringBookings = null;
|
||||
if (recurringEventIdQuery) {
|
||||
if (bookingInfo.recurringEventId) {
|
||||
// We need to get the dates for the bookings to be able to show them in the UI
|
||||
recurringBookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
recurringEventId: recurringEventIdQuery,
|
||||
recurringEventId: bookingInfo.recurringEventId,
|
||||
},
|
||||
select: {
|
||||
startTime: true,
|
||||
|
@ -923,7 +911,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
eventType,
|
||||
recurringBookings: recurringBookings ? recurringBookings.map((obj) => obj.startTime.toString()) : null,
|
||||
trpcState: ssr.dehydrate(),
|
||||
dynamicEventName,
|
||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
||||
bookingInfo,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -92,7 +92,7 @@ test.describe("pro user", () => {
|
|||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname === "/success" && url.searchParams.get("reschedule") === "true";
|
||||
return url.pathname === "/success";
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ test("dynamic booking", async ({ page, users }) => {
|
|||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname === "/success" && url.searchParams.get("reschedule") === "true";
|
||||
return url.pathname === "/success";
|
||||
},
|
||||
});
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
|
|
|
@ -510,6 +510,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||
uid: reqBody.bookingUid,
|
||||
},
|
||||
select: {
|
||||
uid: true,
|
||||
id: true,
|
||||
attendees: true,
|
||||
userId: true,
|
||||
|
|
|
@ -36,6 +36,7 @@ type Props = {
|
|||
user: { username: string | null };
|
||||
location?: string | null;
|
||||
bookingId: number;
|
||||
bookingUid: string;
|
||||
};
|
||||
|
||||
type States =
|
||||
|
@ -47,7 +48,6 @@ type States =
|
|||
export default function PaymentComponent(props: Props) {
|
||||
const { t, i18n } = useLocale();
|
||||
const router = useRouter();
|
||||
const { email, name, date } = router.query;
|
||||
const [state, setState] = useState<States>({ status: "idle" });
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
|
@ -83,12 +83,7 @@ export default function PaymentComponent(props: Props) {
|
|||
});
|
||||
} else {
|
||||
const params: { [k: string]: any } = {
|
||||
date,
|
||||
type: props.eventType.id,
|
||||
username: props.user.username,
|
||||
email,
|
||||
name,
|
||||
bookingId: props.bookingId,
|
||||
uid: props.bookingUid,
|
||||
};
|
||||
|
||||
if (props.location) {
|
||||
|
|
|
@ -136,6 +136,7 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
|
|||
user={props.user}
|
||||
location={props.booking.location}
|
||||
bookingId={props.booking.id}
|
||||
bookingUid={props.booking.uid}
|
||||
/>
|
||||
</Elements>
|
||||
)}
|
||||
|
|
|
@ -31,6 +31,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
booking: {
|
||||
select: {
|
||||
id: true,
|
||||
uid: true,
|
||||
description: true,
|
||||
title: true,
|
||||
startTime: true,
|
||||
|
|
Loading…
Reference in New Issue