diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index bd57791e52f61c..4591aa92b54cb8 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -6,10 +6,11 @@ import { sdkActionManager } from "@calcom/embed-core/embed-iframe"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRefreshData } from "@calcom/lib/hooks/useRefreshData"; import { useTelemetry } from "@calcom/lib/hooks/useTelemetry"; +import { shouldChargeNoShowCancellationFee } from "@calcom/lib/payment/shouldChargeNoShowCancellationFee"; import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry"; import type { RecurringEvent } from "@calcom/types/Calendar"; import { Button } from "@calcom/ui/components/button"; -import { Label, Select, TextArea } from "@calcom/ui/components/form"; +import { Label, Select, TextArea, CheckboxField } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; interface InternalNotePresetsSelectProps { @@ -76,6 +77,12 @@ type Props = { title?: string; uid?: string; id?: number; + startTime: Date; + payment?: { + amount: number; + currency: string; + appId: string | null; + } | null; }; profile: { name: string | null; @@ -100,6 +107,7 @@ type Props = { }; isHost: boolean; internalNotePresets: { id: number; name: string; cancellationReason: string | null }[]; + eventTypeMetadata?: Record | null; }; export default function CancelBooking(props: Props) { @@ -112,13 +120,48 @@ export default function CancelBooking(props: Props) { seatReferenceUid, bookingCancelledEventProps, currentUserEmail, - teamId, + eventTypeMetadata, } = props; const [loading, setLoading] = useState(false); const telemetry = useTelemetry(); const [error, setError] = useState(booking ? null : t("booking_already_cancelled")); const [internalNote, setInternalNote] = useState<{ id: number; name: string } | null>(null); + const [acknowledgeCancellationNoShowFee, setAcknowledgeCancellationNoShowFee] = useState(false); + const getAppMetadata = (appId: string): Record | null => { + if (!eventTypeMetadata?.apps || !appId) return null; + const apps = eventTypeMetadata.apps as Record; + return (apps[appId] as Record) || null; + }; + + const timeValue = booking?.payment?.appId + ? (getAppMetadata(booking.payment.appId) as Record | null)?.autoChargeNoShowFeeTimeValue + : null; + const timeUnit = booking?.payment?.appId + ? (getAppMetadata(booking.payment.appId) as Record | null)?.autoChargeNoShowFeeTimeUnit + : null; + + const autoChargeNoShowFee = () => { + if (props.isHost) return false; // Hosts/organizers are exempt + + if (!booking?.startTime) return false; + + if (!booking?.payment) return false; + + return shouldChargeNoShowCancellationFee({ + eventTypeMetadata: eventTypeMetadata || null, + booking, + payment: booking.payment, + }); + }; + + const cancellationNoShowFeeWarning = autoChargeNoShowFee(); + + const hostMissingCancellationReason = + props.isHost && + (!cancellationReason?.trim() || (props.internalNotePresets.length > 0 && !internalNote?.id)); + const cancellationNoShowFeeNotAcknowledged = + !props.isHost && cancellationNoShowFeeWarning && !acknowledgeCancellationNoShowFee; const cancelBookingRef = useCallback((node: HTMLTextAreaElement) => { if (node !== null) { // eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- CancelBooking is not usually used in embed mode @@ -187,6 +230,23 @@ export default function CancelBooking(props: Props) {

) : null} + {cancellationNoShowFeeWarning && booking?.payment && ( +
+
+ setAcknowledgeCancellationNoShowFee(e.target.checked)} + descriptionClassName="text-info font-semibold" + /> +

{t("contact_organizer")}

+
+
+ )}
- {seatsEnabled && paymentOption === "HOLD" && ( )} - {paymentOption !== "HOLD" && (
@@ -210,6 +218,54 @@ const EventTypeAppSettingsInterface: EventTypeAppSettingsComponent = ({
)} + {paymentOption === "HOLD" && ( +
+
+ setAppData("autoChargeNoShowFeeIfCancelled", e.target.checked)} + description={t("auto_charge_for_last_minute_cancellation")} + /> +
+ {autoChargeNoShowFeeIfCancelled && ( +
+
+ + setAppData("autoChargeNoShowFeeTimeValue", parseInt(e.currentTarget.value)) + } + /> +