diff --git a/companion/app.json b/companion/app.json
index e002c996a1..8fe55d6366 100644
--- a/companion/app.json
+++ b/companion/app.json
@@ -40,6 +40,9 @@
}
},
"owner": "calcoms-organization",
- "plugins": ["expo-router", "expo-secure-store", "expo-web-browser"]
+ "plugins": ["expo-router", "expo-secure-store", "expo-web-browser"],
+ "experiments": {
+ "typedRoutes": true
+ }
}
}
diff --git a/companion/app/(tabs)/(bookings)/_layout.tsx b/companion/app/(tabs)/(bookings)/_layout.tsx
index 4521f9ea72..44898bcd33 100644
--- a/companion/app/(tabs)/(bookings)/_layout.tsx
+++ b/companion/app/(tabs)/(bookings)/_layout.tsx
@@ -5,6 +5,7 @@ export default function BookingsLayout() {
return (
+
);
}
diff --git a/companion/app/(tabs)/(bookings)/booking-detail.tsx b/companion/app/(tabs)/(bookings)/booking-detail.tsx
new file mode 100644
index 0000000000..1a9d1e13f0
--- /dev/null
+++ b/companion/app/(tabs)/(bookings)/booking-detail.tsx
@@ -0,0 +1,137 @@
+import { BookingDetailScreen } from "../../../components/screens/BookingDetailScreen";
+import { Stack, useLocalSearchParams } from "expo-router";
+import React from "react";
+import { Alert } from "react-native";
+
+export default function BookingDetail() {
+ const { uid } = useLocalSearchParams<{ uid: string }>();
+
+ if (!uid) {
+ return null;
+ }
+
+ // Define booking actions organized by sections
+ const bookingActionsSections = {
+ editEvent: [
+ {
+ id: "reschedule",
+ label: "Reschedule Booking",
+ icon: "calendar",
+ onPress: () => Alert.alert("Reschedule", "Reschedule functionality coming soon"),
+ },
+ {
+ id: "edit-location",
+ label: "Edit Location",
+ icon: "location",
+ onPress: () => Alert.alert("Edit Location", "Edit location functionality coming soon"),
+ },
+ {
+ id: "add-guests",
+ label: "Add Guests",
+ icon: "person.badge.plus",
+ onPress: () => Alert.alert("Add Guests", "Add guests functionality coming soon"),
+ },
+ ],
+ afterEvent: [
+ {
+ id: "view-recordings",
+ label: "View Recordings",
+ icon: "video",
+ onPress: () => Alert.alert("View Recordings", "View recordings functionality coming soon"),
+ },
+ {
+ id: "session-details",
+ label: "Meeting Session Details",
+ icon: "info.circle",
+ onPress: () =>
+ Alert.alert("Session Details", "Meeting session details functionality coming soon"),
+ },
+ {
+ id: "mark-no-show",
+ label: "Mark as No-Show",
+ icon: "eye.slash",
+ onPress: () => Alert.alert("Mark No-Show", "Mark as no-show functionality coming soon"),
+ },
+ ],
+ standalone: [
+ {
+ id: "report",
+ label: "Report Booking",
+ icon: "flag",
+ onPress: () => Alert.alert("Report", "Report booking functionality coming soon"),
+ destructive: true,
+ },
+ {
+ id: "cancel",
+ label: "Cancel Event",
+ icon: "xmark.circle",
+ onPress: () => Alert.alert("Cancel Event", "Cancel event functionality coming soon"),
+ destructive: true,
+ },
+ ],
+ } as const;
+
+ return (
+ <>
+
+
+
+ Booking Details
+
+ {/* Header right and left API only works on iOS ATM see: https://docs.expo.dev/versions/unversioned/sdk/router/#stackheaderright */}
+
+
+ Actions
+
+
+ {/* Edit Event Section */}
+
+ {bookingActionsSections.editEvent.map((action) => (
+
+ {action.label}
+
+ ))}
+
+
+ {/* After Event Section */}
+
+ {bookingActionsSections.afterEvent.map((action) => (
+
+ {action.label}
+
+ ))}
+
+
+ {/* Danger Zone Submenu */}
+
+ {bookingActionsSections.standalone.map((action) => (
+
+ {action.label}
+
+ ))}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/companion/app/booking-detail.tsx b/companion/app/booking-detail.tsx
deleted file mode 100644
index 146b2c3c5f..0000000000
--- a/companion/app/booking-detail.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BookingDetailScreen } from "../components/screens/BookingDetailScreen";
-import { useLocalSearchParams, Stack } from "expo-router";
-import React from "react";
-
-export default function BookingDetail() {
- const { uid } = useLocalSearchParams<{ uid: string }>();
-
- if (!uid) {
- return null;
- }
-
- return (
- <>
-
-
- >
- );
-}
diff --git a/companion/components/screens/BookingDetailScreen.tsx b/companion/components/screens/BookingDetailScreen.tsx
index 40fbc43102..3a4ec14afc 100644
--- a/companion/components/screens/BookingDetailScreen.tsx
+++ b/companion/components/screens/BookingDetailScreen.tsx
@@ -10,10 +10,18 @@ import { BookingActionsModal } from "../BookingActionsModal";
import { FullScreenModal } from "../FullScreenModal";
import { SvgImage } from "../SvgImage";
import { Ionicons } from "@expo/vector-icons";
-import { GlassView } from "expo-glass-effect";
-import { useRouter } from "expo-router";
+import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
+import { Stack, useRouter } from "expo-router";
import React, { useState, useEffect } from "react";
-import { View, Text, ScrollView, Alert, ActivityIndicator, TextInput } from "react-native";
+import {
+ View,
+ Text,
+ ScrollView,
+ Alert,
+ ActivityIndicator,
+ TextInput,
+ Platform,
+} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
// Format date: "Tuesday, November 25, 2025"
@@ -334,371 +342,351 @@ export function BookingDetailScreen({ uid }: BookingDetailScreenProps) {
const locationProvider = getLocationProvider(booking.location, booking.responses);
return (
-
- {/* Glass Header */}
-
-
- router.back()}
- >
-
-
+ <>
+ {/* This header right is used for non iOS platforms only */}
+ {Platform.OS !== "ios" && (
+ (
+ setShowActionsModal(true)}
+ >
+
+
+ ),
+ }}
+ />
+ )}
+
+
+ {/* Title */}
+
+ {booking.title}
+
+ {dateFormatted} {timeFormatted} ({timezone})
+
+
-
- {booking?.title || "Booking Details"}
-
-
- setShowActionsModal(true)}
- >
-
-
-
-
-
-
- {/* Title */}
-
- {booking.title}
-
- {dateFormatted} {timeFormatted} ({timezone})
-
-
-
- {/* Who Section */}
-
- Who
- {/* Show host from user field or hosts array */}
- {booking.user || (booking.hosts && booking.hosts.length > 0) ? (
-
- {booking.user ? (
-
-
-
- {getInitials(booking.user.name)}
-
-
-
-
- {booking.user.name}
-
- host
-
-
- {booking.user.email}
-
-
- ) : booking.hosts && booking.hosts.length > 0 ? (
- booking.hosts.map((host, hostIndex) => (
- 0 ? "mt-4" : ""}`}
- >
+ {/* Who Section */}
+
+ Who
+ {/* Show host from user field or hosts array */}
+ {booking.user || (booking.hosts && booking.hosts.length > 0) ? (
+
+ {booking.user ? (
+
- {getInitials(host.name || "Host")}
+ {getInitials(booking.user.name)}
- {host.name || "Host"}
+ {booking.user.name}
host
- {host.email && {host.email}}
+ {booking.user.email}
- ))
- ) : null}
-
- ) : null}
- {booking.attendees && booking.attendees.length > 0 ? (
-
- {booking.attendees.map((attendee, index) => (
- 0 ? "mt-4" : ""}`}>
-
-
- {getInitials(attendee.name)}
-
-
-
- {attendee.name}
- {attendee.email}
-
-
- ))}
-
- ) : null}
-
-
- {/* Where Section */}
- {locationProvider ? (
-
- Where
- {locationProvider.url ? (
-
- {locationProvider.iconUrl ? (
-
+ ) : booking.hosts && booking.hosts.length > 0 ? (
+ booking.hosts.map((host, hostIndex) => (
+ 0 ? "mt-4" : ""}`}
+ >
+
+
+ {getInitials(host.name || "Host")}
+
+
+
+
+
+ {host.name || "Host"}
+
+
+ host
+
+
+ {host.email && {host.email}}
+
+
+ ))
) : null}
- {locationProvider.label}:
-
- {locationProvider.url}
-
-
- ) : (
-
- {locationProvider.iconUrl ? (
-
- ) : null}
- {locationProvider.label}
- )}
-
- ) : null}
-
- {/* Recurring Event Section */}
- {booking.recurringEventId ||
- (booking as { recurringBookingUid?: string }).recurringBookingUid ? (
-
- Recurring Event
- Every 2 weeks for 6 occurrences
-
- ) : null}
-
- {/* Description Section */}
- {booking.description ? (
-
- Description
- {booking.description}
-
- ) : null}
-
- {/* Join Meeting Button */}
- {locationProvider?.url ? (
-
- {locationProvider.iconUrl ? (
-
) : null}
-
- Join {locationProvider.label}
-
-
- ) : null}
-
+ {booking.attendees && booking.attendees.length > 0 ? (
+
+ {booking.attendees.map((attendee, index) => (
+ 0 ? "mt-4" : ""}`}>
+
+
+ {getInitials(attendee.name)}
+
+
+
+
+ {attendee.name}
+
+ {attendee.email}
+
+
+ ))}
+
+ ) : null}
+
- {/* Booking Actions Modal */}
- setShowActionsModal(false)}
- booking={booking}
- hasLocationUrl={!!locationProvider?.url}
- isUpcoming={booking ? new Date(booking.startTime) > new Date() : false}
- isPast={booking ? new Date(booking.startTime) <= new Date() : false}
- isCancelled={booking?.status?.toUpperCase() === "CANCELLED"}
- isUnconfirmed={booking?.status?.toUpperCase() === "PENDING"}
- onReschedule={openRescheduleModal}
- onEditLocation={() => {
- Alert.alert("Edit Location", "Edit location functionality coming soon");
- }}
- onAddGuests={() => {
- Alert.alert("Add Guests", "Add guests functionality coming soon");
- }}
- onViewRecordings={() => {
- Alert.alert("View Recordings", "View recordings functionality coming soon");
- }}
- onMeetingSessionDetails={() => {
- Alert.alert(
- "Meeting Session Details",
- "Meeting session details functionality coming soon"
- );
- }}
- onMarkNoShow={() => {
- Alert.alert("Mark as No-Show", "Mark as no-show functionality coming soon");
- }}
- onReportBooking={() => {
- Alert.alert("Report Booking", "Report booking functionality coming soon");
- }}
- onCancelBooking={() => {
- Alert.alert("Cancel Booking", "Are you sure you want to cancel this booking?", [
- { text: "No", style: "cancel" },
- {
- text: "Yes, Cancel",
- style: "destructive",
- onPress: () => {
- // TODO: Implement cancel booking
- if (__DEV__)
- console.debug("[BookingDetailScreen] cancel booking (not implemented yet)");
+ {/* Where Section */}
+ {locationProvider ? (
+
+ Where
+ {locationProvider.url ? (
+
+ {locationProvider.iconUrl ? (
+
+ ) : null}
+ {locationProvider.label}:
+
+ {locationProvider.url}
+
+
+ ) : (
+
+ {locationProvider.iconUrl ? (
+
+ ) : null}
+ {locationProvider.label}
+
+ )}
+
+ ) : null}
+
+ {/* Recurring Event Section */}
+ {booking.recurringEventId ||
+ (booking as { recurringBookingUid?: string }).recurringBookingUid ? (
+
+ Recurring Event
+ Every 2 weeks for 6 occurrences
+
+ ) : null}
+
+ {/* Description Section */}
+ {booking.description ? (
+
+ Description
+ {booking.description}
+
+ ) : null}
+
+ {/* Join Meeting Button */}
+ {locationProvider?.url ? (
+
+ {locationProvider.iconUrl ? (
+
+ ) : null}
+
+ Join {locationProvider.label}
+
+
+ ) : null}
+
+
+ {/* Booking Actions Modal */}
+ setShowActionsModal(false)}
+ booking={booking}
+ hasLocationUrl={!!locationProvider?.url}
+ isUpcoming={booking ? new Date(booking.startTime) > new Date() : false}
+ isPast={booking ? new Date(booking.startTime) <= new Date() : false}
+ isCancelled={booking?.status?.toUpperCase() === "CANCELLED"}
+ isUnconfirmed={booking?.status?.toUpperCase() === "PENDING"}
+ onReschedule={openRescheduleModal}
+ onEditLocation={() => {
+ Alert.alert("Edit Location", "Edit location functionality coming soon");
+ }}
+ onAddGuests={() => {
+ Alert.alert("Add Guests", "Add guests functionality coming soon");
+ }}
+ onViewRecordings={() => {
+ Alert.alert("View Recordings", "View recordings functionality coming soon");
+ }}
+ onMeetingSessionDetails={() => {
+ Alert.alert(
+ "Meeting Session Details",
+ "Meeting session details functionality coming soon"
+ );
+ }}
+ onMarkNoShow={() => {
+ Alert.alert("Mark as No-Show", "Mark as no-show functionality coming soon");
+ }}
+ onReportBooking={() => {
+ Alert.alert("Report Booking", "Report booking functionality coming soon");
+ }}
+ onCancelBooking={() => {
+ Alert.alert("Cancel Booking", "Are you sure you want to cancel this booking?", [
+ { text: "No", style: "cancel" },
+ {
+ text: "Yes, Cancel",
+ style: "destructive",
+ onPress: () => {
+ // TODO: Implement cancel booking
+ if (__DEV__)
+ console.debug("[BookingDetailScreen] cancel booking (not implemented yet)");
+ },
},
- },
- ]);
- }}
- />
+ ]);
+ }}
+ />
- {/* Reschedule Modal */}
- setShowRescheduleModal(false)}
- >
- setShowRescheduleModal(false)}
+ {/* Reschedule Modal */}
+ setShowRescheduleModal(false)}
>
e.stopPropagation()}
- style={shadows.xl()}
+ className="flex-1 items-center justify-center bg-black/50 p-2 md:p-4"
+ onPress={() => setShowRescheduleModal(false)}
>
- {/* Header */}
-
-
-
-
-
-
-
- Reschedule Booking
-
-
- Select a new date and time for this booking.
-
+ e.stopPropagation()}
+ style={shadows.xl()}
+ >
+ {/* Header */}
+
+
+
+
+
+
+
+ Reschedule Booking
+
+
+ Select a new date and time for this booking.
+
+
-
- {/* Content */}
-
- {/* Date Input */}
-
-
- New Date
- (YYYY-MM-DD)
-
-
-
+ {/* Content */}
+
+ {/* Date Input */}
+
+
+ New Date
+ (YYYY-MM-DD)
+
+
+
- {/* Time Input */}
-
-
- New Time
- (HH:MM, 24-hour)
-
-
-
+ {/* Time Input */}
+
+
+ New Time
+ (HH:MM, 24-hour)
+
+
+
- {/* Reason Input */}
-
-
- Reason
- (Optional)
-
-
-
-
+ {/* Reason Input */}
+
+
+ Reason
+ (Optional)
+
+
+
+
- {/* Footer */}
-
-
- {
- setShowRescheduleModal(false);
- setRescheduleDate("");
- setRescheduleTime("");
- setRescheduleReason("");
- }}
- disabled={rescheduling}
- >
- Cancel
-
-
- {rescheduling ? (
-
- ) : (
- Reschedule
- )}
-
+ {/* Footer */}
+
+
+ {
+ setShowRescheduleModal(false);
+ setRescheduleDate("");
+ setRescheduleTime("");
+ setRescheduleReason("");
+ }}
+ disabled={rescheduling}
+ >
+ Cancel
+
+
+ {rescheduling ? (
+
+ ) : (
+ Reschedule
+ )}
+
+
-
+
-
-
-
+
+
+ >
);
}
diff --git a/companion/hooks/useBookingActions.ts b/companion/hooks/useBookingActions.ts
index ca4233e1ef..2226df686c 100644
--- a/companion/hooks/useBookingActions.ts
+++ b/companion/hooks/useBookingActions.ts
@@ -69,7 +69,7 @@ export const useBookingActions = ({
*/
const handleBookingPress = (booking: Booking) => {
router.push({
- pathname: "/booking-detail",
+ pathname: "/(tabs)/(bookings)/booking-detail",
params: { uid: booking.uid },
});
};
diff --git a/companion/tsconfig.json b/companion/tsconfig.json
index b365f86f06..871ce9c924 100644
--- a/companion/tsconfig.json
+++ b/companion/tsconfig.json
@@ -2,5 +2,6 @@
"compilerOptions": {
"types": ["nativewind/types"]
},
- "extends": "expo/tsconfig.base"
+ "extends": "expo/tsconfig.base",
+ "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}