Skip to content

Commit b8d4fcc

Browse files
feat: Rerouting a booking with corrected responses (calcom#17074)
* POC working with no TS error * cleanup * Fix query booking * Simplify things * Support rerouting to different event * self-review fixes * Some improvements * add more tests * Remove members relation query * Udits feedback * Dont show CTAs when non-eventTypeRedirectUrl action route is chosen --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
1 parent 4f14775 commit b8d4fcc

33 files changed

+1989
-97
lines changed

apps/web/components/booking/BookingListItem.tsx

+60-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
2020
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
2121
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
2222
import { trpc } from "@calcom/trpc/react";
23+
import type { Ensure } from "@calcom/types/utils";
2324
import type { ActionType } from "@calcom/ui";
2425
import {
2526
Badge,
@@ -48,6 +49,7 @@ import { AddGuestsDialog } from "@components/dialog/AddGuestsDialog";
4849
import { ChargeCardDialog } from "@components/dialog/ChargeCardDialog";
4950
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
5051
import { ReassignDialog } from "@components/dialog/ReassignDialog";
52+
import { RerouteDialog } from "@components/dialog/RerouteDialog";
5153
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
5254

5355
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["filters"]["status"];
@@ -65,9 +67,43 @@ type BookingItemProps = BookingItem & {
6567
};
6668
};
6769

70+
type ParsedBooking = ReturnType<typeof buildParsedBooking>;
71+
type TeamEvent = Ensure<NonNullable<ParsedBooking["eventType"]>, "team">;
72+
type TeamEventBooking = Omit<ParsedBooking, "eventType"> & {
73+
eventType: TeamEvent;
74+
};
75+
type ReroutableBooking = Ensure<TeamEventBooking, "routedFromRoutingFormReponse">;
76+
77+
function buildParsedBooking(booking: BookingItemProps) {
78+
// The way we fetch bookings there could be eventType object even without an eventType, but id confirms its existence
79+
const bookingEventType = booking.eventType.id
80+
? (booking.eventType as Ensure<
81+
typeof booking.eventType,
82+
// It would only ensure that the props are present, if they are optional in the original type. So, it is safe to assert here.
83+
"id" | "length" | "title" | "slug" | "schedulingType" | "team"
84+
>)
85+
: null;
86+
87+
const bookingMetadata = bookingMetadataSchema.parse(booking.metadata ?? null);
88+
return {
89+
...booking,
90+
eventType: bookingEventType,
91+
metadata: bookingMetadata,
92+
};
93+
}
94+
95+
const isBookingReroutable = (booking: ParsedBooking): booking is ReroutableBooking => {
96+
// We support only team bookings for now for rerouting
97+
// Though `routedFromRoutingFormReponse` could be there for a non-team booking, we don't want to support it for now.
98+
// Let's not support re-routing for a booking without an event-type for now.
99+
// Such a booking has its event-type deleted and there might not be something to reroute to.
100+
return !!booking.routedFromRoutingFormReponse && !!booking.eventType?.team;
101+
};
102+
68103
function BookingListItem(booking: BookingItemProps) {
69-
const { userId, userTimeZone, userTimeFormat, userEmail } = booking.loggedInUser;
104+
const parsedBooking = buildParsedBooking(booking);
70105

106+
const { userId, userTimeZone, userTimeFormat, userEmail } = booking.loggedInUser;
71107
const {
72108
t,
73109
i18n: { language },
@@ -107,7 +143,7 @@ function BookingListItem(booking: BookingItemProps) {
107143
const paymentAppData = getPaymentAppData(booking.eventType);
108144

109145
const location = booking.location as ReturnType<typeof getEventLocationValue>;
110-
const locationVideoCallUrl = bookingMetadataSchema.parse(booking?.metadata || {})?.videoCallUrl;
146+
const locationVideoCallUrl = parsedBooking.metadata?.videoCallUrl;
111147

112148
const { resolvedTheme, forcedTheme } = useGetTheme();
113149
const hasDarkTheme = !forcedTheme && resolvedTheme === "dark";
@@ -191,6 +227,18 @@ function BookingListItem(booking: BookingItemProps) {
191227
setIsOpenRescheduleDialog(true);
192228
},
193229
},
230+
...(isBookingReroutable(parsedBooking)
231+
? [
232+
{
233+
id: "reroute",
234+
label: t("reroute"),
235+
onClick: () => {
236+
setRerouteDialogIsOpen(true);
237+
},
238+
icon: "waypoints" as const,
239+
},
240+
]
241+
: []),
194242
{
195243
id: "change_location",
196244
label: t("edit_location"),
@@ -275,6 +323,7 @@ function BookingListItem(booking: BookingItemProps) {
275323
const [isOpenReassignDialog, setIsOpenReassignDialog] = useState(false);
276324
const [isOpenSetLocationDialog, setIsOpenLocationDialog] = useState(false);
277325
const [isOpenAddGuestsDialog, setIsOpenAddGuestsDialog] = useState(false);
326+
const [rerouteDialogIsOpen, setRerouteDialogIsOpen] = useState(false);
278327
const setLocationMutation = trpc.viewer.bookings.editLocation.useMutation({
279328
onSuccess: () => {
280329
showToast(t("location_updated"), "success");
@@ -360,6 +409,7 @@ function BookingListItem(booking: BookingItemProps) {
360409
phoneNumber: attendee.phoneNumber,
361410
};
362411
});
412+
363413
return (
364414
<>
365415
<RescheduleDialog
@@ -620,6 +670,13 @@ function BookingListItem(booking: BookingItemProps) {
620670
)}
621671
</td>
622672
</tr>
673+
{isBookingReroutable(parsedBooking) && (
674+
<RerouteDialog
675+
isOpenDialog={rerouteDialogIsOpen}
676+
setIsOpenDialog={setRerouteDialogIsOpen}
677+
booking={{ ...parsedBooking, eventType: parsedBooking.eventType }}
678+
/>
679+
)}
623680
</>
624681
);
625682
}
@@ -722,7 +779,7 @@ const FirstAttendee = ({
722779
className=" hover:text-blue-500"
723780
href={`mailto:${user.email}`}
724781
onClick={(e) => e.stopPropagation()}>
725-
{user.name}
782+
{user.name || user.email}
726783
</a>
727784
);
728785
};

0 commit comments

Comments
 (0)