@@ -20,6 +20,7 @@ import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
20
20
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils" ;
21
21
import type { RouterInputs , RouterOutputs } from "@calcom/trpc/react" ;
22
22
import { trpc } from "@calcom/trpc/react" ;
23
+ import type { Ensure } from "@calcom/types/utils" ;
23
24
import type { ActionType } from "@calcom/ui" ;
24
25
import {
25
26
Badge ,
@@ -48,6 +49,7 @@ import { AddGuestsDialog } from "@components/dialog/AddGuestsDialog";
48
49
import { ChargeCardDialog } from "@components/dialog/ChargeCardDialog" ;
49
50
import { EditLocationDialog } from "@components/dialog/EditLocationDialog" ;
50
51
import { ReassignDialog } from "@components/dialog/ReassignDialog" ;
52
+ import { RerouteDialog } from "@components/dialog/RerouteDialog" ;
51
53
import { RescheduleDialog } from "@components/dialog/RescheduleDialog" ;
52
54
53
55
type BookingListingStatus = RouterInputs [ "viewer" ] [ "bookings" ] [ "get" ] [ "filters" ] [ "status" ] ;
@@ -65,9 +67,43 @@ type BookingItemProps = BookingItem & {
65
67
} ;
66
68
} ;
67
69
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
+
68
103
function BookingListItem ( booking : BookingItemProps ) {
69
- const { userId , userTimeZone , userTimeFormat , userEmail } = booking . loggedInUser ;
104
+ const parsedBooking = buildParsedBooking ( booking ) ;
70
105
106
+ const { userId, userTimeZone, userTimeFormat, userEmail } = booking . loggedInUser ;
71
107
const {
72
108
t,
73
109
i18n : { language } ,
@@ -107,7 +143,7 @@ function BookingListItem(booking: BookingItemProps) {
107
143
const paymentAppData = getPaymentAppData ( booking . eventType ) ;
108
144
109
145
const location = booking . location as ReturnType < typeof getEventLocationValue > ;
110
- const locationVideoCallUrl = bookingMetadataSchema . parse ( booking ?. metadata || { } ) ?. videoCallUrl ;
146
+ const locationVideoCallUrl = parsedBooking . metadata ?. videoCallUrl ;
111
147
112
148
const { resolvedTheme, forcedTheme } = useGetTheme ( ) ;
113
149
const hasDarkTheme = ! forcedTheme && resolvedTheme === "dark" ;
@@ -191,6 +227,18 @@ function BookingListItem(booking: BookingItemProps) {
191
227
setIsOpenRescheduleDialog ( true ) ;
192
228
} ,
193
229
} ,
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
+ : [ ] ) ,
194
242
{
195
243
id : "change_location" ,
196
244
label : t ( "edit_location" ) ,
@@ -275,6 +323,7 @@ function BookingListItem(booking: BookingItemProps) {
275
323
const [ isOpenReassignDialog , setIsOpenReassignDialog ] = useState ( false ) ;
276
324
const [ isOpenSetLocationDialog , setIsOpenLocationDialog ] = useState ( false ) ;
277
325
const [ isOpenAddGuestsDialog , setIsOpenAddGuestsDialog ] = useState ( false ) ;
326
+ const [ rerouteDialogIsOpen , setRerouteDialogIsOpen ] = useState ( false ) ;
278
327
const setLocationMutation = trpc . viewer . bookings . editLocation . useMutation ( {
279
328
onSuccess : ( ) => {
280
329
showToast ( t ( "location_updated" ) , "success" ) ;
@@ -360,6 +409,7 @@ function BookingListItem(booking: BookingItemProps) {
360
409
phoneNumber : attendee . phoneNumber ,
361
410
} ;
362
411
} ) ;
412
+
363
413
return (
364
414
< >
365
415
< RescheduleDialog
@@ -620,6 +670,13 @@ function BookingListItem(booking: BookingItemProps) {
620
670
) }
621
671
</ td >
622
672
</ tr >
673
+ { isBookingReroutable ( parsedBooking ) && (
674
+ < RerouteDialog
675
+ isOpenDialog = { rerouteDialogIsOpen }
676
+ setIsOpenDialog = { setRerouteDialogIsOpen }
677
+ booking = { { ...parsedBooking , eventType : parsedBooking . eventType } }
678
+ />
679
+ ) }
623
680
</ >
624
681
) ;
625
682
}
@@ -722,7 +779,7 @@ const FirstAttendee = ({
722
779
className = " hover:text-blue-500"
723
780
href = { `mailto:${ user . email } ` }
724
781
onClick = { ( e ) => e . stopPropagation ( ) } >
725
- { user . name }
782
+ { user . name || user . email }
726
783
</ a >
727
784
) ;
728
785
} ;
0 commit comments