From 5566e77f30ff30d3d68fd020ff3cc072050c34eb Mon Sep 17 00:00:00 2001 From: iharsh02 Date: Mon, 20 Jan 2025 22:59:06 +0530 Subject: [PATCH 1/7] feat: Add cancelledBy input to cancellation page --- apps/web/components/booking/CancelBooking.tsx | 151 ++++++++++++------ .../bookings/views/bookings-single-view.tsx | 1 + apps/web/public/static/locales/en/common.json | 3 + 3 files changed, 110 insertions(+), 45 deletions(-) diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index 0c1189ca9cf7ac..01c313804b78ea 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -1,3 +1,4 @@ +import { useSearchParams } from "next/navigation"; import { useCallback, useState } from "react"; import { sdkActionManager } from "@calcom/embed-core/embed-iframe"; @@ -5,7 +6,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRefreshData } from "@calcom/lib/hooks/useRefreshData"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import type { RecurringEvent } from "@calcom/types/Calendar"; -import { Button, Icon, TextArea } from "@calcom/ui"; +import { Button, Icon, TextArea, Dialog, DialogContent, DialogHeader, Input } from "@calcom/ui"; type Props = { booking: { @@ -44,6 +45,10 @@ export default function CancelBooking(props: Props) { const [loading, setLoading] = useState(false); const telemetry = useTelemetry(); const [error, setError] = useState(booking ? null : t("booking_already_cancelled")); + const [showVerificationDialog, setShowVerificationDialog] = useState(false); + const [verificationEmail, setVerificationEmail] = useState(""); + const [verificationError, setVerificationError] = useState(""); + const searchParams = useSearchParams(); const cancelBookingRef = useCallback((node: HTMLTextAreaElement) => { if (node !== null) { @@ -54,6 +59,75 @@ export default function CancelBooking(props: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleCancellation = async () => { + setLoading(true); + + telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters()); + + const res = await fetch("/api/cancel", { + body: JSON.stringify({ + uid: booking?.uid, + cancellationReason: cancellationReason, + allRemainingBookings, + // @NOTE: very important this shouldn't cancel with number ID use uid instead + seatReferenceUid, + cancelledBy: currentUserEmail, + }), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }); + + const bookingWithCancellationReason = { + ...(bookingCancelledEventProps.booking as object), + cancellationReason, + } as unknown; + + if (res.status >= 200 && res.status < 300) { + // tested by apps/web/playwright/booking-pages.e2e.ts + sdkActionManager?.fire("bookingCancelled", { + ...bookingCancelledEventProps, + booking: bookingWithCancellationReason, + }); + refreshData(); + } else { + setLoading(false); + setError(`${t("error_with_status_code_occured", { status: res.status })} ${t("please_try_again")}`); + } + }; + + const verifyAndCancel = () => { + const emailFromParams = searchParams?.get("email"); + + if (!emailFromParams) { + setShowVerificationDialog(true); + return; + } + + if (emailFromParams.toLowerCase() !== currentUserEmail?.toLowerCase()) { + setError(t("email_verification_error") || "Email doesn't match the booking email"); + return; + } + + handleCancellation(); + }; + + const handleVerification = () => { + setVerificationError(""); + if (!verificationEmail) { + setVerificationError(t("email_required") || "Email is required"); + return; + } + + if (verificationEmail.toLowerCase() === currentUserEmail?.toLowerCase()) { + setShowVerificationDialog(false); + handleCancellation(); + } else { + setVerificationError(t("email_verification_error") || "Email doesn't match the booking email"); + } + }; + return ( <> {error && ( @@ -88,56 +162,43 @@ export default function CancelBooking(props: Props) { onClick={() => props.setIsCancellationMode(false)}> {t("nevermind")} - )} + + + +
+

+ {t("verify_email_description") || + "Please enter the email address used for this booking to proceed with cancellation"} +

+ setVerificationEmail(e.target.value)} + className="mb-2" + /> + {verificationError &&

{verificationError}

} +
+ + +
+
+
+
); } diff --git a/apps/web/modules/bookings/views/bookings-single-view.tsx b/apps/web/modules/bookings/views/bookings-single-view.tsx index a230107db33f4b..0a1f8278d440da 100644 --- a/apps/web/modules/bookings/views/bookings-single-view.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.tsx @@ -163,6 +163,7 @@ export default function Success(props: PageProps) { searchParams?.get("rescheduledBy") ?? searchParams?.get("cancelledBy") ?? session?.user?.email ?? + (bookingInfo.responses?.email as string) ?? undefined; const defaultRating = isNaN(parsedRating) ? 3 : parsedRating > 5 ? 5 : parsedRating < 1 ? 1 : parsedRating; diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 430d3aee89c11c..d9c0d19f74324f 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2928,5 +2928,8 @@ "select_oAuth_client": "Select Oauth Client", "on_every_instance": "On every instance", "next": "Next", + "email_verification_error" : "Email dosen't match the booking email", + "verify_email" : "Verify Email", + "verify_email_description" : "Please enter the email address used for this booking to proceed with cancellation", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } From 9d508564c3e70522afc9b1fbe5fc88d1a313ab9f Mon Sep 17 00:00:00 2001 From: iharsh02 Date: Mon, 20 Jan 2025 23:24:46 +0530 Subject: [PATCH 2/7] fix: Update email verification error message --- apps/web/components/booking/CancelBooking.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index 01c313804b78ea..60ed49f188cf52 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -106,7 +106,7 @@ export default function CancelBooking(props: Props) { } if (emailFromParams.toLowerCase() !== currentUserEmail?.toLowerCase()) { - setError(t("email_verification_error") || "Email doesn't match the booking email"); + setError(t("email_verification_error")); return; } @@ -116,7 +116,7 @@ export default function CancelBooking(props: Props) { const handleVerification = () => { setVerificationError(""); if (!verificationEmail) { - setVerificationError(t("email_required") || "Email is required"); + setVerificationError(t("email_required")); return; } @@ -124,7 +124,7 @@ export default function CancelBooking(props: Props) { setShowVerificationDialog(false); handleCancellation(); } else { - setVerificationError(t("email_verification_error") || "Email doesn't match the booking email"); + setVerificationError(t("email_verification_error")); } }; @@ -171,15 +171,12 @@ export default function CancelBooking(props: Props) { )} - +
-

- {t("verify_email_description") || - "Please enter the email address used for this booking to proceed with cancellation"} -

+

{t("verify_email_description")}

setVerificationEmail(e.target.value)} className="mb-2" From 300467a25f2e6915feabbaa33887b0910fff1df0 Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Tue, 25 Feb 2025 18:59:58 +0530 Subject: [PATCH 3/7] Update safe-parse.ts --- apps/api/v2/src/lib/safe-parse/safe-parse.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/api/v2/src/lib/safe-parse/safe-parse.ts b/apps/api/v2/src/lib/safe-parse/safe-parse.ts index 16e11c06868d4e..fdaacd409f5a52 100644 --- a/apps/api/v2/src/lib/safe-parse/safe-parse.ts +++ b/apps/api/v2/src/lib/safe-parse/safe-parse.ts @@ -3,7 +3,12 @@ import { ZodSchema } from "zod"; const logger = new Logger("safeParse"); -export function safeParse(schema: ZodSchema, value: unknown, defaultValue: T, logError = true): T { +export function safeParse( + schema: ZodSchema, + value: unknown, + defaultValue: T, + logError = true +): T { const result = schema.safeParse(value); if (result.success) { return result.data; From 8e541ac34ce1d76cffdf091f56482ade775dfbd4 Mon Sep 17 00:00:00 2001 From: Kartik Saini <41051387+kart1ka@users.noreply.github.com> Date: Sat, 5 Jul 2025 23:36:41 +0530 Subject: [PATCH 4/7] fix: check attendees and booking owner email --- apps/web/components/booking/CancelBooking.tsx | 16 +++++++++------- .../bookings/views/bookings-single-view.tsx | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index 0dd7f92bf7d047..947342114a85aa 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -78,6 +78,8 @@ type Props = { title?: string; uid?: string; id?: number; + userEmail?: string; + attendees?: { email: string }[]; }; profile: { name: string | null; @@ -176,16 +178,11 @@ export default function CancelBooking(props: Props) { const verifyAndCancel = () => { const emailFromParams = searchParams?.get("email"); - if (!emailFromParams) { + if (!emailFromParams && !currentUserEmail) { setShowVerificationDialog(true); return; } - if (emailFromParams.toLowerCase() !== currentUserEmail?.toLowerCase()) { - setError(t("email_verification_error")); - return; - } - handleCancellation(); }; @@ -196,7 +193,12 @@ export default function CancelBooking(props: Props) { return; } - if (verificationEmail.toLowerCase() === currentUserEmail?.toLowerCase()) { + if ( + booking.attendees?.some( + (attendee) => attendee.email.toLowerCase() === verificationEmail.toLowerCase() + ) || + verificationEmail.toLowerCase() === booking?.userEmail?.toLowerCase() + ) { setShowVerificationDialog(false); handleCancellation(); } else { diff --git a/apps/web/modules/bookings/views/bookings-single-view.tsx b/apps/web/modules/bookings/views/bookings-single-view.tsx index 6491758b6a7282..ab6cd5611455b6 100644 --- a/apps/web/modules/bookings/views/bookings-single-view.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.tsx @@ -184,9 +184,7 @@ export default function Success(props: PageProps) { searchParams?.get("rescheduledBy") ?? searchParams?.get("cancelledBy") ?? session?.user?.email ?? - (bookingInfo.responses?.email as string) ?? undefined; - const defaultRating = isNaN(parsedRating) ? 3 : parsedRating > 5 ? 5 : parsedRating < 1 ? 1 : parsedRating; const [rateValue, setRateValue] = useState(defaultRating); const [isFeedbackSubmitted, setIsFeedbackSubmitted] = useState(false); @@ -860,6 +858,8 @@ export default function Success(props: PageProps) { uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id, + attendees: bookingInfo?.attendees, + userEmail: bookingInfo?.user?.email as string, }} profile={{ name: props.profile.name, slug: props.profile.slug }} recurringEvent={eventType.recurringEvent} From 1bc263cb28442d4310fbda5d97ff990afca261ad Mon Sep 17 00:00:00 2001 From: Kartik Saini <41051387+kart1ka@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:35:25 +0530 Subject: [PATCH 5/7] fix: minor ui --- apps/web/components/booking/CancelBooking.tsx | 5 +++-- apps/web/public/static/locales/en/common.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/web/components/booking/CancelBooking.tsx b/apps/web/components/booking/CancelBooking.tsx index 947342114a85aa..c2a87401dc84d0 100644 --- a/apps/web/components/booking/CancelBooking.tsx +++ b/apps/web/components/booking/CancelBooking.tsx @@ -202,7 +202,7 @@ export default function CancelBooking(props: Props) { setShowVerificationDialog(false); handleCancellation(); } else { - setVerificationError(t("email_verification_error")); + setVerificationError(t("proceed_with_cancellation_error")); } }; @@ -291,7 +291,7 @@ export default function CancelBooking(props: Props) {
-

{t("verify_email_description")}

+

{t("proceed_with_cancellation_description")}

{verificationError}

}