/** * BookingActionsModal Component - Android/Web Implementation * * A reusable modal component for booking actions that can be used in both * the bookings list screen and the booking detail screen. * * This component uses the centralized action gating utility for consistent * action visibility and enabled state across the app. * * Note: iOS uses BookingActionsModal.ios.tsx with native Glass UI styling. */ import { Ionicons } from "@expo/vector-icons"; import { ScrollView, Text, TouchableOpacity, View } from "react-native"; import type { Booking } from "@/services/calcom"; import type { BookingActionsResult } from "@/utils/booking-actions"; import { FullScreenModal } from "./FullScreenModal"; export interface BookingActionsModalProps { visible: boolean; onClose: () => void; booking: Booking | null; actions: BookingActionsResult; onReschedule: () => void; onRequestReschedule?: () => void; onEditLocation: () => void; onAddGuests: () => void; onViewRecordings: () => void; onMeetingSessionDetails: () => void; onMarkNoShow: () => void; onReportBooking: () => void; onCancelBooking: () => void; } // Icon mapping for actions const ACTION_ICONS: Record = { reschedule: "calendar-outline", rescheduleRequest: "send-outline", changeLocation: "location-outline", addGuests: "person-add-outline", viewRecordings: "videocam-outline", meetingSessionDetails: "information-circle-outline", markNoShow: "eye-off-outline", report: "flag-outline", cancel: "close-circle-outline", }; interface ActionButtonProps { icon: keyof typeof Ionicons.glyphMap; label: string; onPress: () => void; visible: boolean; enabled: boolean; isDanger?: boolean; isLast?: boolean; } function ActionButton({ icon, label, onPress, visible, enabled, isDanger = false, isLast = false, }: ActionButtonProps) { if (!visible) return null; const iconColor = !enabled ? "#D1D5DB" : isDanger ? "#DC2626" : "#6B7280"; const textColor = !enabled ? "#D1D5DB" : isDanger ? "#DC2626" : "#111827"; return ( { if (!enabled) return; onPress(); }} disabled={!enabled} className={`flex-row items-center px-4 py-3 active:bg-gray-50 ${ !isLast ? "border-b border-gray-100" : "" }`} activeOpacity={0.7} > {label} {!enabled && ( Unavailable )} ); } interface SectionHeaderProps { title: string; } function SectionHeader({ title }: SectionHeaderProps) { return ( {title} ); } export function BookingActionsModal({ visible, onClose, booking, actions, onReschedule, onRequestReschedule, onEditLocation, onAddGuests, onViewRecordings, onMeetingSessionDetails, onMarkNoShow, onReportBooking, onCancelBooking, }: BookingActionsModalProps) { if (!booking) return null; const handleAction = (action: () => void) => { onClose(); action(); }; // Check if any edit event actions are visible const hasEditEventActions = actions.reschedule.visible || actions.rescheduleRequest.visible || actions.changeLocation.visible || actions.addGuests.visible; // Check if any after event actions are visible const hasAfterEventActions = actions.viewRecordings.visible || actions.meetingSessionDetails.visible || actions.markNoShow.visible; // Define edit event actions const editEventActions = [ { key: "reschedule", icon: ACTION_ICONS.reschedule, label: "Reschedule Booking", onPress: () => handleAction(onReschedule), visible: actions.reschedule.visible, enabled: actions.reschedule.enabled, }, ...(onRequestReschedule ? [ { key: "rescheduleRequest", icon: ACTION_ICONS.rescheduleRequest, label: "Request Reschedule", onPress: () => handleAction(onRequestReschedule), visible: actions.rescheduleRequest.visible, enabled: actions.rescheduleRequest.enabled, }, ] : []), { key: "changeLocation", icon: ACTION_ICONS.changeLocation, label: "Edit Location", onPress: () => handleAction(onEditLocation), visible: actions.changeLocation.visible, enabled: actions.changeLocation.enabled, }, { key: "addGuests", icon: ACTION_ICONS.addGuests, label: "Add Guests", onPress: () => handleAction(onAddGuests), visible: actions.addGuests.visible, enabled: actions.addGuests.enabled, }, ].filter((action) => action.visible); // Define after event actions const afterEventActions = [ { key: "viewRecordings", icon: ACTION_ICONS.viewRecordings, label: "View Recordings", onPress: () => handleAction(onViewRecordings), visible: actions.viewRecordings.visible, enabled: actions.viewRecordings.enabled, }, { key: "meetingSessionDetails", icon: ACTION_ICONS.meetingSessionDetails, label: "Meeting Session Details", onPress: () => handleAction(onMeetingSessionDetails), visible: actions.meetingSessionDetails.visible, enabled: actions.meetingSessionDetails.enabled, }, { key: "markNoShow", icon: ACTION_ICONS.markNoShow, label: "Mark as No-Show", onPress: () => handleAction(onMarkNoShow), visible: actions.markNoShow.visible, enabled: actions.markNoShow.enabled, }, ].filter((action) => action.visible); // Define danger zone actions const dangerZoneActions = [ { key: "report", icon: ACTION_ICONS.report, label: "Report Booking", onPress: () => handleAction(onReportBooking), visible: true, enabled: true, isDanger: true, }, { key: "cancel", icon: ACTION_ICONS.cancel, label: "Cancel Event", onPress: () => handleAction(onCancelBooking), visible: actions.cancel.visible, enabled: actions.cancel.enabled, isDanger: true, }, ].filter((action) => action.visible); return ( e.stopPropagation()} > {/* Actions Card */} {/* Booking Title Header */} {booking.title} {/* Edit Event Section */} {hasEditEventActions && ( <> {editEventActions.map((action, index) => ( ))} )} {/* After Event Section */} {hasAfterEventActions && ( <> {afterEventActions.map((action, index) => ( ))} )} {/* Danger Zone Section */} {dangerZoneActions.length > 0 && ( <> {dangerZoneActions.map((action, index) => ( ))} )} {/* Cancel Button */} Cancel ); }