Files
Hariom BalharaGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
a0e5e03903 feat: Add async spam check and decoy booking response (#24326)
* feat: Add async spam check integration and decoy booking response

- Integrate SpamCheckService with handleNewBooking workflow
- Implement parallel spam check execution for minimal performance impact
- Add decoy booking response with localStorage-based success page
- Extract organization ID from event type for org-specific blocking
- Add comprehensive test coverage for spam detection scenarios
- Create reusable components for booking success cards
- Implement fail-open behavior to never block legitimate bookings

This builds on the spam blocker DI infrastructure from PR #24040 by
adding the actual integration into the booking flow and implementing
the decoy response mechanism to avoid revealing spam detection to
malicious actors.

Related: #24040
Co-Authored-By: [email protected] <[email protected]>

* Do checks in paralle

* Fix leaking host name in title

* Reduce expoiry time localstorage

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-10-14 11:36:51 +00:00

115 lines
4.4 KiB
TypeScript

import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge } from "@calcom/ui/components/badge";
import { Icon } from "@calcom/ui/components/icon";
export interface BookingSuccessCardProps {
title: string;
formattedDate: string;
formattedTime: string;
endTime: string;
formattedTimeZone: string;
hostName: string | null;
hostEmail: string | null;
attendeeName: string | null;
attendeeEmail: string | null;
location: string | null;
}
export function BookingSuccessCard({
title,
formattedDate,
formattedTime,
endTime,
formattedTimeZone,
hostName,
hostEmail,
attendeeName,
attendeeEmail,
location,
}: BookingSuccessCardProps) {
const { t } = useLocale();
return (
<div className="h-screen">
<main className="mx-auto max-w-3xl">
<div className="overflow-y-auto">
<div className="flex items-end justify-center px-4 pb-20 pt-4 text-center sm:flex sm:p-0">
<div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true">
<div
className="bg-default dark:bg-muted border-booker border-booker-width inline-block transform overflow-hidden rounded-lg px-8 pb-4 pt-5 text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-xl sm:py-8 sm:align-middle"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div>
<div className="bg-success mx-auto flex h-12 w-12 items-center justify-center rounded-full">
<Icon name="check" className="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
</div>
<div className="mb-8 mt-6 text-center last:mb-0">
<h3 className="text-emphasis text-2xl font-semibold leading-6" id="modal-headline">
{t("meeting_is_scheduled")}
</h3>
<div className="mt-3">
<p className="text-default">{t("emailed_you_and_any_other_attendees")}</p>
</div>
<div className="border-subtle text-default mt-8 grid grid-cols-3 gap-x-4 border-t pt-8 text-left rtl:text-right sm:gap-x-0">
<div className="font-medium">{t("what")}</div>
<div className="col-span-2 mb-6 last:mb-0">{title}</div>
{formattedDate && (
<>
<div className="font-medium">{t("when")}</div>
<div className="col-span-2 mb-6 last:mb-0">
{formattedDate}
{formattedTime && (
<>
<br />
{formattedTime}
{endTime && ` - ${endTime}`}
{formattedTimeZone && (
<span className="text-bookinglight"> ({formattedTimeZone})</span>
)}
</>
)}
</div>
</>
)}
<div className="font-medium">{t("who")}</div>
<div className="col-span-2 last:mb-0">
{hostName && (
<div className="mb-3">
<div>
<span className="mr-2">{hostName}</span>
<Badge variant="blue">{t("Host")}</Badge>
</div>
{hostEmail && <p className="text-default">{hostEmail}</p>}
</div>
)}
{attendeeName && (
<div className="mb-3 last:mb-0">
<p>{attendeeName}</p>
{attendeeEmail && <p>{attendeeEmail}</p>}
</div>
)}
</div>
{location && (
<>
<div className="mt-3 font-medium">{t("where")}</div>
<div className="col-span-2 mt-3">{t("web_conferencing_details_to_follow")}</div>
</>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}