diff --git a/apps/web/server/lib/router/getServerSideProps.ts b/apps/web/server/lib/router/getServerSideProps.ts index ecf9f6d08a..8653967c02 100644 --- a/apps/web/server/lib/router/getServerSideProps.ts +++ b/apps/web/server/lib/router/getServerSideProps.ts @@ -1,7 +1,6 @@ -import type { GetServerSidePropsContext } from "next"; import { stringify } from "querystring"; import z from "zod"; - +import { GetServerSidePropsContext } from "next"; import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData"; import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "@calcom/app-store/routing-forms/getEventTypeRedirectUrl"; import getFieldIdentifier from "@calcom/app-store/routing-forms/lib/getFieldIdentifier"; @@ -20,7 +19,6 @@ import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm import { TRPCError } from "@calcom/trpc/server"; const log = logger.getSubLogger({ prefix: ["[routing-forms]", "[router]"] }); - const querySchema = z .object({ form: z.string(), @@ -46,8 +44,16 @@ export const getServerSideProps = async function getServerSideProps(context: Get }; } - // Known params reserved by Cal.com are form, embed, layout. We should exclude all of them. - const { form: formId, ...fieldsResponses } = queryParsed.data; + // TODO: Known params reserved by Cal.com are form, embed, layout and other cal. prefixed params. We should exclude all of them from fieldsResponses. + // But they must be present in `paramsToBeForwardedAsIs` as they could be needed by Booking Page as well. + const { form: formId, "cal.isBookingDryRun": isBookingDryRunParam, ...fieldsResponses } = queryParsed.data; + const isBookingDryRun = isBookingDryRunParam === "true"; + const paramsToBeForwardedAsIs = { + ...fieldsResponses, + // Must be forwarded if present to Booking Page. Setting it explicitly here as it is critical to be present in the URL. + ...(isBookingDryRunParam ? { "cal.isBookingDryRun": isBookingDryRunParam } : null), + }; + const { currentOrgDomain } = orgDomainConfig(context.req); let timeTaken: Record = {}; @@ -115,6 +121,7 @@ export const getServerSideProps = async function getServerSideProps(context: Get formFillerId: uuidv4(), response: response, chosenRouteId: matchingRoute.id, + isPreview: isBookingDryRun, }); teamMembersMatchingAttributeLogic = result.teamMembersMatchingAttributeLogic; formResponseId = result.formResponse.id; @@ -162,7 +169,7 @@ export const getServerSideProps = async function getServerSideProps(context: Get allURLSearchParams: getUrlSearchParamsToForward({ formResponse: response, fields: serializableForm.fields, - searchParams: new URLSearchParams(stringify(fieldsResponses)), + searchParams: new URLSearchParams(stringify(paramsToBeForwardedAsIs)), teamMembersMatchingAttributeLogic, // formResponseId is guaranteed to be set because in catch block of trpc request we return from the function and otherwise it would have been set formResponseId: formResponseId!, diff --git a/packages/app-store/routing-forms/lib/handleResponse.ts b/packages/app-store/routing-forms/lib/handleResponse.ts index 407fd6db5e..4f5579ca2f 100644 --- a/packages/app-store/routing-forms/lib/handleResponse.ts +++ b/packages/app-store/routing-forms/lib/handleResponse.ts @@ -35,11 +35,13 @@ export const handleResponse = async ({ // Unused but probably should be used // formFillerId, chosenRouteId, + isPreview, }: { response: z.infer["response"]; form: Form; formFillerId: string; chosenRouteId: string | null; + isPreview: boolean; }) => { try { if (!form.fields) { @@ -162,25 +164,39 @@ export const handleResponse = async ({ // It currently happens for a Router route. Such a route id isn't present in the form.routes } - const dbFormResponse = await prisma.app_RoutingForms_FormResponse.create({ - data: { - // TODO: Why do we not save formFillerId available in the input? - // formFillerId, - formId: form.id, - response: response, - chosenRouteId, - }, - }); + let dbFormResponse; + if (!isPreview) { + dbFormResponse = await prisma.app_RoutingForms_FormResponse.create({ + data: { + // TODO: Why do we not save formFillerId available in the input? + // formFillerId, + formId: form.id, + response: response, + chosenRouteId, + }, + }); - await onFormSubmission( - { ...serializableFormWithFields, userWithEmails }, - dbFormResponse.response as FormResponse, - dbFormResponse.id, - chosenRoute ? ("action" in chosenRoute ? chosenRoute.action : undefined) : undefined - ); + await onFormSubmission( + { ...serializableFormWithFields, userWithEmails }, + dbFormResponse.response as FormResponse, + dbFormResponse.id, + chosenRoute ? ("action" in chosenRoute ? chosenRoute.action : undefined) : undefined + ); + } else { + moduleLogger.debug("Dry run mode - Form response not stored and also webhooks and emails not sent"); + // Create a mock response for dry run + dbFormResponse = { + id: 0, + formId: form.id, + response, + chosenRouteId, + createdAt: new Date(), + updatedAt: new Date(), + }; + } return { - isPreview: false, + isPreview: !!isPreview, formResponse: dbFormResponse, teamMembersMatchingAttributeLogic: teamMemberIdsMatchingAttributeLogic, attributeRoutingConfig: chosenRoute diff --git a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx index 17b5a572dd..42c983e402 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx @@ -54,6 +54,8 @@ function RoutingForm({ form, profile, ...restProps }: Props) { }); const [response, setResponse] = usePrefilledResponse(form); + const pageSearchParams = useCompatSearchParams(); + const isBookingDryRun = pageSearchParams?.get("cal.isBookingDryRun") === "true"; // TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews // But technically, a user can fill form multiple times due to any number of reasons and we currently can't differentiate b/w that. @@ -79,6 +81,7 @@ function RoutingForm({ form, profile, ...restProps }: Props) { formFillerId, response: response, chosenRouteId: chosenRoute.id, + isPreview: isBookingDryRun, }); chosenRouteWithFormResponseRef.current = { diff --git a/packages/app-store/routing-forms/trpc/utils.ts b/packages/app-store/routing-forms/trpc/utils.ts index 21503cf646..09122a702a 100644 --- a/packages/app-store/routing-forms/trpc/utils.ts +++ b/packages/app-store/routing-forms/trpc/utils.ts @@ -92,6 +92,9 @@ export function getFieldResponse({ }; } +/** + * Not called in preview mode or dry run mode + */ export async function onFormSubmission( form: Ensure< SerializableForm & { user: Pick; userWithEmails?: string[] }, diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx index 612ac7c712..4714aa72da 100644 --- a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx +++ b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx @@ -73,7 +73,10 @@ export const BookEventForm = ({ // Cloudflare Turnstile Captcha const shouldRenderCaptcha = - renderCaptcha && CLOUDFLARE_SITE_ID && CLOUDFLARE_USE_TURNSTILE_IN_BOOKER === "1"; + !process.env.NEXT_PUBLIC_IS_E2E && + renderCaptcha && + CLOUDFLARE_SITE_ID && + CLOUDFLARE_USE_TURNSTILE_IN_BOOKER === "1"; const [responseVercelIdHeader] = useState(null); const { t } = useLocale(); diff --git a/packages/trpc/server/routers/viewer/routing-forms/response.handler.ts b/packages/trpc/server/routers/viewer/routing-forms/response.handler.ts index 6b4da77b2a..d58c347b89 100644 --- a/packages/trpc/server/routers/viewer/routing-forms/response.handler.ts +++ b/packages/trpc/server/routers/viewer/routing-forms/response.handler.ts @@ -13,7 +13,7 @@ interface ResponseHandlerOptions { } export const responseHandler = async ({ ctx, input }: ResponseHandlerOptions) => { const { prisma } = ctx; - const { formId, response, formFillerId, chosenRouteId = null } = input; + const { formId, response, formFillerId, chosenRouteId = null, isPreview = false } = input; const form = await prisma.app_RoutingForms_Form.findFirst({ where: { id: formId, @@ -43,7 +43,7 @@ export const responseHandler = async ({ ctx, input }: ResponseHandlerOptions) => form, }); - return handleResponse({ response, form: serializableForm, formFillerId, chosenRouteId }); + return handleResponse({ response, form: serializableForm, formFillerId, chosenRouteId, isPreview }); }; export default responseHandler;