feat: Add dry run mode functionality and message display in booking process (#18429)

- Introduced a new `DryRunMessage` component to inform users when in dry run mode.
- Implemented utility function `isBookingDryRun` to check for dry run status based on URL search parameters.
- Updated the `Booker` component to conditionally render the dry run message.
- Adjusted booking mutation input mapping to reuse the utility

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
Hariom Balhara
2025-01-01 11:40:53 +00:00
committed by GitHub
co-authored by Udit Takkar
parent fec504496a
commit bac2d1f6f0
6 changed files with 45 additions and 5 deletions
+6 -1
View File
@@ -395,6 +395,11 @@ const nextConfig = {
value: "cross-origin",
};
const ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = {
key: "Access-Control-Allow-Origin",
value: "*",
};
return [
{
source: "/auth/:path*",
@@ -464,7 +469,7 @@ const nextConfig = {
},
{
source: "/icons/sprite.svg",
headers: [CORP_CROSS_ORIGIN_HEADER],
headers: [CORP_CROSS_ORIGIN_HEADER, ACCESS_CONTROL_ALLOW_ORIGIN_HEADER],
},
],
...(isOrganizationsEnabled
@@ -2905,5 +2905,6 @@
"routing_form": "Routing Form",
"something_unexpected_occurred": "Something unexpected occurred",
"500_error_message": "It's not you, it's us.",
"dry_run_mode_active": "You are doing a test booking.",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+6 -2
View File
@@ -11,13 +11,14 @@ import dayjs from "@calcom/dayjs";
import { getQueryParam } from "@calcom/features/bookings/Booker/utils/query-param";
import { useNonEmptyScheduleDays } from "@calcom/features/schedules";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import { VerifyCodeDialog } from "../components/VerifyCodeDialog";
import { AvailableTimeSlots } from "./components/AvailableTimeSlots";
import { BookEventForm } from "./components/BookEventForm";
import { BookFormAsModal } from "./components/BookEventForm/BookFormAsModal";
import { DryRunMessage } from "./components/DryRunMessage";
import { EventMeta } from "./components/EventMeta";
import { HavingTroubleFindingTime } from "./components/HavingTroubleFindingTime";
import { Header } from "./components/Header";
@@ -30,6 +31,7 @@ import { NotFound } from "./components/Unavailable";
import { fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config";
import { useBookerStore } from "./store";
import type { BookerProps, WrappedBookerProps } from "./types";
import { isBookingDryRun } from "./utils/isBookingDryRun";
const loadFramerFeatures = () => import("./framer-features").then((res) => res.default);
const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy").then((mod) => mod.default));
@@ -71,7 +73,7 @@ const BookerComponent = ({
userLocale,
hasValidLicense,
}: BookerProps & WrappedBookerProps) => {
const { t } = useLocale();
const searchParams = useCompatSearchParams();
const isPlatformBookerEmbed = useIsPlatformBookerEmbed();
const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
const selectedDate = useBookerStore((state) => state.selectedDate);
@@ -269,6 +271,8 @@ const BookerComponent = ({
<>
{event.data && !isPlatform ? <BookingPageTagManager eventType={event.data} /> : <></>}
{isBookingDryRun(searchParams) && <DryRunMessage isEmbed={isEmbed} />}
<div
className={classNames(
// In a popup embed, if someone clicks outside the main(having main class or main tag), it closes the embed
@@ -0,0 +1,26 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
export const DryRunMessage = ({ isEmbed }: { isEmbed?: boolean }) => {
const { t } = useLocale();
const [isVisible, setIsVisible] = useState(true);
if (!isVisible) return null;
return (
<div
onClick={() => setIsVisible(false)}
className={`bg-default border-subtle fixed left-1/2 ${
!isEmbed ? "top-4" : "top-0"
} z-50 -translate-x-1/2 transform cursor-pointer items-center gap-3 rounded-xl border p-3 text-sm shadow-md`}>
<div className="flex items-center gap-3">
<div className="relative">
<Icon name="info" className="h-5 w-5 text-orange-500" />
</div>
<div className="text-emphasis font-medium">{t("dry_run_mode_active")}</div>
</div>
</div>
);
};
@@ -0,0 +1,3 @@
export const isBookingDryRun = (searchParams: URLSearchParams) => {
return searchParams.get("cal.isBookingDryRun") === "true";
};
@@ -1,6 +1,7 @@
import { v4 as uuidv4 } from "uuid";
import dayjs from "@calcom/dayjs";
import { isBookingDryRun } from "@calcom/features/bookings/Booker/utils/isBookingDryRun";
import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/getRoutedTeamMemberIdsFromSearchParams";
import { parseRecurringDates } from "@calcom/lib/parse-dates";
@@ -52,7 +53,7 @@ export const mapBookingToMutationInput = ({
const routingFormResponseId = routingFormResponseIdParam ? Number(routingFormResponseIdParam) : undefined;
const skipContactOwner = searchParams.get("cal.skipContactOwner") === "true";
const reroutingFormResponses = searchParams.get("cal.reroutingFormResponses");
const isBookingDryRun = searchParams.get("cal.isBookingDryRun") === "true";
const _isDryRun = isBookingDryRun(searchParams);
const _cacheParam = searchParams?.get("cal.cache");
const _shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined;
@@ -84,7 +85,7 @@ export const mapBookingToMutationInput = ({
skipContactOwner,
// In case of rerouting, the form responses are actually the responses that we need to update.
reroutingFormResponses: reroutingFormResponses ? JSON.parse(reroutingFormResponses) : undefined,
_isDryRun: isBookingDryRun,
_isDryRun,
_shouldServeCache,
};
};