feat: routing forms integration for booker atom (#18726)
* update booker atom for routing form * remove logs * hide routing form properties from docs * fixup * add comments to explain why certain fields are not defined for routing forms in v2 * chore: routing for handler return team and org id * bump libraries platform * fixup! bump libraries platform * chore: get routing form params to embed * logs to remove * fix import path * chore: handle routing form params in booker embed * wip need router api v2 endpoint * resolve merge conflicts * fixup * fixup! Merge branch 'main' into remove-redirect-on-success-for-platform * fixup! fixup! Merge branch 'main' into remove-redirect-on-success-for-platform * router atom * fix libraries version * cleanup * remove console logs * bump libraries --------- Co-authored-by: Morgan Vernay <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
co-authored by
Morgan Vernay
Morgan
parent
9608669f98
commit
8615ccb1aa
@@ -29,7 +29,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.92",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.93",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -126,4 +126,47 @@ export class CreateBookingInput_2024_04_15 {
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
locationUrl?: string;
|
||||
|
||||
// note(rajiv): after going through getUrlSearchParamsToForward.ts we found out
|
||||
// that the below properties were not being included inside of handleNewBooking :- cc @morgan
|
||||
// cal.salesforce.rrSkipToAccountLookupField, cal.rerouting & cal.isTestPreviewLink
|
||||
// hence no input values have been setup for them in CreateBookingInput_2024_04_15
|
||||
@IsArray()
|
||||
@Type(() => Number)
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
routedTeamMemberIds?: number[];
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
routingFormResponseId?: number;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
skipContactOwner?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
_shouldServeCache?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
_isDryRun?: boolean;
|
||||
|
||||
// reroutingFormResponses is similar to rescheduling which can only be done by the organiser
|
||||
// won't really be necessary here in our usecase though :- cc @Hariom
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
reroutingFormResponses?: Record<
|
||||
string,
|
||||
{
|
||||
value: (string | number | string[]) & (string | number | string[] | undefined);
|
||||
label?: string | undefined;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { Controller, Get, Req, NotFoundException, Res, Query, Param } from "@nestjs/common";
|
||||
import { Controller, Req, NotFoundException, Param, Post, Body } from "@nestjs/common";
|
||||
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||
import { Request, Response } from "express";
|
||||
import { Request } from "express";
|
||||
|
||||
import { getRoutedUrl } from "@calcom/platform-libraries";
|
||||
import { ApiResponse } from "@calcom/platform-types";
|
||||
@@ -13,27 +13,27 @@ import { ApiResponse } from "@calcom/platform-types";
|
||||
@DocsTags("Router controller")
|
||||
@DocsExcludeController(true)
|
||||
export class RouterController {
|
||||
@Get("/forms/:formId")
|
||||
@Post("/forms/:formId/submit")
|
||||
async getRoutingFormResponse(
|
||||
@Req() request: Request,
|
||||
@Res() res: Response,
|
||||
@Param("formId") formId: string,
|
||||
@Query() query: Record<string, string>
|
||||
): Promise<void | ApiResponse<unknown>> {
|
||||
const routedUrlData = await getRoutedUrl({ req: request, query: { ...query, form: formId } });
|
||||
@Body() body?: Record<string, string>
|
||||
): Promise<void | (ApiResponse<unknown> & { redirect: boolean })> {
|
||||
const params = Object.fromEntries(new URLSearchParams(body ?? {}));
|
||||
const routedUrlData = await getRoutedUrl({ req: request, query: { ...params, form: formId } });
|
||||
|
||||
if (routedUrlData?.notFound) {
|
||||
throw new NotFoundException("Route not found. Please check the provided form parameter.");
|
||||
}
|
||||
|
||||
if (routedUrlData?.redirect?.destination) {
|
||||
return res.redirect(307, routedUrlData.redirect.destination);
|
||||
return { status: "success", data: routedUrlData?.redirect?.destination, redirect: true };
|
||||
}
|
||||
|
||||
if (routedUrlData?.props) {
|
||||
return { status: "success", data: routedUrlData.props };
|
||||
return { status: "success", data: { message: routedUrlData?.props?.message ?? "" }, redirect: false };
|
||||
}
|
||||
|
||||
return { status: "success", data: "route nor custom message found" };
|
||||
return { status: "success", data: { message: "No Route nor custom message found." }, redirect: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ const BookerComponent = ({
|
||||
areInstantMeetingParametersSet = false,
|
||||
userLocale,
|
||||
hasValidLicense,
|
||||
isBookingDryRun: isBookingDryRunProp,
|
||||
renderCaptcha,
|
||||
}: BookerProps & WrappedBookerProps) => {
|
||||
const searchParams = useCompatSearchParams();
|
||||
@@ -273,14 +274,15 @@ const BookerComponent = ({
|
||||
<>
|
||||
{event.data && !isPlatform ? <BookingPageTagManager eventType={event.data} /> : <></>}
|
||||
|
||||
{isBookingDryRun(searchParams) && <DryRunMessage isEmbed={isEmbed} />}
|
||||
{(isBookingDryRunProp || 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
|
||||
"main",
|
||||
"text-default flex min-h-full w-full flex-col items-center",
|
||||
layout === BookerLayouts.MONTH_VIEW ? "overflow-visible" : "overflow-clip"
|
||||
layout === BookerLayouts.MONTH_VIEW ? "overflow-visible" : "overflow-clip",
|
||||
`${customClassNames?.bookerWrapper}`
|
||||
)}>
|
||||
<div
|
||||
ref={animationScope}
|
||||
|
||||
@@ -118,6 +118,7 @@ export type WrappedBookerPropsMain = {
|
||||
bookerLayout: UseBookerLayoutType;
|
||||
verifyEmail: UseVerifyEmailReturnType;
|
||||
customClassNames?: CustomClassNames;
|
||||
isBookingDryRun?: boolean;
|
||||
renderCaptcha?: boolean;
|
||||
};
|
||||
|
||||
@@ -138,6 +139,7 @@ export type BookerLayout = BookerLayouts | "mobile";
|
||||
export type BookerAreas = "calendar" | "timeslots" | "main" | "meta" | "header";
|
||||
|
||||
export type CustomClassNames = {
|
||||
bookerWrapper?: string;
|
||||
bookerContainer?: string;
|
||||
eventMetaCustomClassNames?: {
|
||||
eventMetaContainer?: string;
|
||||
|
||||
@@ -4,6 +4,7 @@ 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";
|
||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||
|
||||
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
|
||||
|
||||
@@ -26,6 +27,7 @@ export type BookingOptions = {
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
orgSlug?: string;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
export const mapBookingToMutationInput = ({
|
||||
@@ -46,8 +48,9 @@ export const mapBookingToMutationInput = ({
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
orgSlug,
|
||||
routingFormSearchParams,
|
||||
}: BookingOptions): BookingCreateBody => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const searchParams = new URLSearchParams(routingFormSearchParams ?? window.location.search);
|
||||
const routedTeamMemberIds = getRoutedTeamMemberIdsFromSearchParams(searchParams);
|
||||
const routingFormResponseIdParam = searchParams.get("cal.routingFormResponseId");
|
||||
const routingFormResponseId = routingFormResponseIdParam ? Number(routingFormResponseIdParam) : undefined;
|
||||
|
||||
@@ -171,7 +171,9 @@ export const getRoutedUrl = async (context: Pick<GetServerSidePropsContext, "que
|
||||
allURLSearchParams: getUrlSearchParamsToForward({
|
||||
formResponse: response,
|
||||
fields: serializableForm.fields,
|
||||
searchParams: new URLSearchParams(stringify(paramsToBeForwardedAsIs)),
|
||||
searchParams: new URLSearchParams(
|
||||
stringify({ ...paramsToBeForwardedAsIs, "cal.action": "eventTypeRedirectUrl" })
|
||||
),
|
||||
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!,
|
||||
@@ -187,7 +189,7 @@ export const getRoutedUrl = async (context: Pick<GetServerSidePropsContext, "que
|
||||
} else if (decidedAction.type === "externalRedirectUrl") {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `${decidedAction.value}?${stringify(context.query)}`,
|
||||
destination: `${decidedAction.value}?${stringify(context.query)}&cal.action=externalRedirectUrl`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,21 +4,78 @@ import type {
|
||||
} from "../booker/BookerPlatformWrapper";
|
||||
import { BookerPlatformWrapper } from "../booker/BookerPlatformWrapper";
|
||||
import { CalProvider } from "../cal-provider/CalProvider";
|
||||
import { useGetRoutingFormUrlProps } from "./useGetRoutingFormUrlProps";
|
||||
|
||||
export const BookerEmbed = (
|
||||
props:
|
||||
| (BookerPlatformWrapperAtomPropsForIndividual & { organizationId?: undefined })
|
||||
| (BookerPlatformWrapperAtomPropsForTeam & { organizationId?: number })
|
||||
| {
|
||||
routingFormUrl: string;
|
||||
bannerUrl?: BookerPlatformWrapperAtomPropsForTeam["bannerUrl"];
|
||||
customClassNames?: BookerPlatformWrapperAtomPropsForTeam["customClassNames"];
|
||||
}
|
||||
| (BookerPlatformWrapperAtomPropsForIndividual & {
|
||||
organizationId?: undefined;
|
||||
routingFormUrl?: undefined;
|
||||
})
|
||||
| (BookerPlatformWrapperAtomPropsForTeam & { organizationId?: number; routingFormUrl?: undefined })
|
||||
) => {
|
||||
return (
|
||||
<CalProvider
|
||||
clientId={import.meta.env.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID}
|
||||
isEmbed={true}
|
||||
organizationId={props?.organizationId}
|
||||
options={{
|
||||
apiUrl: import.meta.env.VITE_BOOKER_EMBED_API_URL,
|
||||
}}>
|
||||
<BookerPlatformWrapper {...props} />
|
||||
</CalProvider>
|
||||
);
|
||||
// Use Routing Form Url To Display Correct Booker
|
||||
const routingFormUrlProps = useGetRoutingFormUrlProps(props);
|
||||
if (props?.routingFormUrl && routingFormUrlProps) {
|
||||
const {
|
||||
organizationId,
|
||||
teamId: routingTeamId,
|
||||
eventTypeSlug,
|
||||
username,
|
||||
...routingFormSearchParams
|
||||
} = routingFormUrlProps;
|
||||
return (
|
||||
<CalProvider
|
||||
clientId={import.meta.env.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID}
|
||||
isEmbed={true}
|
||||
organizationId={organizationId}
|
||||
options={{
|
||||
apiUrl: import.meta.env.VITE_BOOKER_EMBED_API_URL,
|
||||
}}>
|
||||
<BookerPlatformWrapper
|
||||
{...(Boolean(routingTeamId)
|
||||
? {
|
||||
eventSlug: eventTypeSlug,
|
||||
isTeamEvent: true,
|
||||
teamId: routingTeamId || 0,
|
||||
username: "",
|
||||
customClassNames: props?.customClassNames,
|
||||
}
|
||||
: {
|
||||
eventSlug: eventTypeSlug,
|
||||
username: username ?? "",
|
||||
isTeamEvent: false,
|
||||
customClassNames: props?.customClassNames,
|
||||
})}
|
||||
routingFormSearchParams={routingFormSearchParams}
|
||||
bannerUrl={props.bannerUrl}
|
||||
onDryRunSuccess={() => {
|
||||
window.location.href = `https://app.cal.com/booking/dry-run-successful`;
|
||||
}}
|
||||
/>
|
||||
</CalProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// If Not For From Routing Form, Use Props
|
||||
if (props?.routingFormUrl === undefined) {
|
||||
return (
|
||||
<CalProvider
|
||||
clientId={import.meta.env.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID}
|
||||
isEmbed={true}
|
||||
organizationId={props?.organizationId}
|
||||
options={{
|
||||
apiUrl: import.meta.env.VITE_BOOKER_EMBED_API_URL,
|
||||
}}>
|
||||
<BookerPlatformWrapper {...props} />
|
||||
</CalProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import type { RoutingFormSearchParamsForEmbed } from "@calcom/platform-types";
|
||||
|
||||
export const useGetRoutingFormUrlProps = ({ routingFormUrl }: { routingFormUrl?: string }) => {
|
||||
const routingFormUrlProps = useMemo(() => {
|
||||
if (routingFormUrl) {
|
||||
const routingUrl = new URL(routingFormUrl);
|
||||
const pathNameParams = routingUrl.pathname.split("/");
|
||||
|
||||
if (pathNameParams.length < 2) {
|
||||
throw new Error("Invalid routing form url.");
|
||||
}
|
||||
|
||||
const eventTypeSlug = pathNameParams[pathNameParams.length - 1];
|
||||
const isTeamUrl = pathNameParams[1] === "team";
|
||||
const username = isTeamUrl ? undefined : pathNameParams[1];
|
||||
const routingSearchParams = routingUrl.searchParams;
|
||||
if (!eventTypeSlug) {
|
||||
throw new Error("Event type slug is not defined within the routing form url");
|
||||
}
|
||||
if (!isTeamUrl && !username) {
|
||||
throw new Error("username not defined within the routing form url");
|
||||
}
|
||||
return {
|
||||
organizationId: routingSearchParams.get("cal.orgId")
|
||||
? Number(routingSearchParams.get("cal.orgId"))
|
||||
: undefined,
|
||||
teamId: routingSearchParams.get("cal.teamId")
|
||||
? Number(routingSearchParams.get("cal.teamId"))
|
||||
: undefined,
|
||||
username,
|
||||
eventTypeSlug,
|
||||
...(routingSearchParams.get("cal.routedTeamMemberIds") && {
|
||||
["cal.routedTeamMemberIds"]: routingSearchParams.get("cal.routedTeamMemberIds") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.reroutingFormResponses") && {
|
||||
["cal.reroutingFormResponses"]: routingSearchParams.get("cal.reroutingFormResponses") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.skipContactOwner") && {
|
||||
["cal.skipContactOwner"]: routingSearchParams.get("cal.skipContactOwner") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.isBookingDryRun") && {
|
||||
["cal.isBookingDryRun"]: routingSearchParams.get("cal.isBookingDryRun") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.cache") && {
|
||||
["cal.cache"]: routingSearchParams.get("cal.cache") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.routingFormResponseId") && {
|
||||
["cal.routingFormResponseId"]: routingSearchParams.get("cal.routingFormResponseId") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.salesforce.rrSkipToAccountLookupField") && {
|
||||
["cal.salesforce.rrSkipToAccountLookupField"]:
|
||||
routingSearchParams.get("cal.salesforce.rrSkipToAccountLookupField") ?? undefined,
|
||||
}),
|
||||
} satisfies RoutingFormSearchParamsForEmbed;
|
||||
}
|
||||
return;
|
||||
}, [routingFormUrl]);
|
||||
|
||||
return routingFormUrlProps;
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import { useLocalSet } from "@calcom/features/bookings/Booker/components/hooks/u
|
||||
import { useBookerStore, useInitializeBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||
import { useTimesForSchedule } from "@calcom/features/schedules/lib/use-schedule/useTimesForSchedule";
|
||||
import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/getRoutedTeamMemberIdsFromSearchParams";
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import type { ConnectedDestinationCalendars } from "@calcom/platform-libraries";
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
ApiSuccessResponse,
|
||||
ApiSuccessResponseWithoutData,
|
||||
} from "@calcom/platform-types";
|
||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import {
|
||||
@@ -78,6 +80,7 @@ export type BookerPlatformWrapperAtomProps = Omit<
|
||||
view?: VIEW_TYPE;
|
||||
metadata?: Record<string, string>;
|
||||
bannerUrl?: string;
|
||||
onDryRunSuccess?: () => void;
|
||||
};
|
||||
|
||||
type VIEW_TYPE = keyof typeof BookerLayouts;
|
||||
@@ -85,18 +88,20 @@ type VIEW_TYPE = keyof typeof BookerLayouts;
|
||||
export type BookerPlatformWrapperAtomPropsForIndividual = BookerPlatformWrapperAtomProps & {
|
||||
username: string | string[];
|
||||
isTeamEvent?: false;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
export type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomProps & {
|
||||
username?: string | string[];
|
||||
isTeamEvent: true;
|
||||
teamId: number;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
export const BookerPlatformWrapper = (
|
||||
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||
) => {
|
||||
const { view = "MONTH_VIEW", bannerUrl } = props;
|
||||
const { view = "MONTH_VIEW", bannerUrl, routingFormSearchParams } = props;
|
||||
const layout = BookerLayouts[view];
|
||||
|
||||
const { clientId } = useAtomsContext();
|
||||
@@ -259,6 +264,32 @@ export const BookerPlatformWrapper = (
|
||||
selectedDate,
|
||||
});
|
||||
|
||||
const [routingParams, setRoutingParams] = useState<{
|
||||
routedTeamMemberIds?: number[];
|
||||
shouldServeCache?: boolean;
|
||||
skipContactOwner?: boolean;
|
||||
isBookingDryRun?: boolean;
|
||||
}>({});
|
||||
|
||||
useEffect(() => {
|
||||
const searchParams = routingFormSearchParams
|
||||
? new URLSearchParams(routingFormSearchParams)
|
||||
: new URLSearchParams(window.location.search);
|
||||
|
||||
const routedTeamMemberIds = getRoutedTeamMemberIdsFromSearchParams(searchParams);
|
||||
const skipContactOwner = searchParams.get("cal.skipContactOwner") === "true";
|
||||
|
||||
const _cacheParam = searchParams?.get("cal.cache");
|
||||
const shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined;
|
||||
const isBookingDryRun = searchParams?.get("cal.isBookingDryRun")?.toLowerCase() === "true";
|
||||
setRoutingParams({
|
||||
...(skipContactOwner ? { skipContactOwner } : {}),
|
||||
...(routedTeamMemberIds ? { routedTeamMemberIds } : {}),
|
||||
...(shouldServeCache ? { shouldServeCache } : {}),
|
||||
...(isBookingDryRun ? { isBookingDryRun } : {}),
|
||||
});
|
||||
}, [routingFormSearchParams]);
|
||||
|
||||
const schedule = useAvailableSlots({
|
||||
usernameList: getUsernameList(username),
|
||||
eventTypeId: event?.data?.id ?? 0,
|
||||
@@ -281,6 +312,7 @@ export const BookerPlatformWrapper = (
|
||||
Boolean(event?.data?.id),
|
||||
orgSlug: props.entity?.orgSlug ?? undefined,
|
||||
eventTypeSlug: isDynamic ? "dynamic" : eventSlug || "",
|
||||
...routingParams,
|
||||
});
|
||||
|
||||
const bookerForm = useBookingForm({
|
||||
@@ -302,6 +334,9 @@ export const BookerPlatformWrapper = (
|
||||
isError: isCreateBookingError,
|
||||
} = useCreateBooking({
|
||||
onSuccess: (data) => {
|
||||
if (data?.data?.isDryRun) {
|
||||
props?.onDryRunSuccess?.();
|
||||
}
|
||||
schedule.refetch();
|
||||
props.onCreateBookingSuccess?.(data);
|
||||
|
||||
@@ -319,6 +354,9 @@ export const BookerPlatformWrapper = (
|
||||
isError: isCreateRecBookingError,
|
||||
} = useCreateRecurringBooking({
|
||||
onSuccess: (data) => {
|
||||
if (data?.data?.[0]?.isDryRun) {
|
||||
props?.onDryRunSuccess?.();
|
||||
}
|
||||
schedule.refetch();
|
||||
props.onCreateRecurringBookingSuccess?.(data);
|
||||
|
||||
@@ -383,6 +421,7 @@ export const BookerPlatformWrapper = (
|
||||
handleInstantBooking: createInstantBooking,
|
||||
handleRecBooking: createRecBooking,
|
||||
locationUrl: props.locationUrl,
|
||||
routingFormSearchParams,
|
||||
});
|
||||
|
||||
const onOverlaySwitchStateChange = useCallback(
|
||||
@@ -514,6 +553,7 @@ export const BookerPlatformWrapper = (
|
||||
verifyCode={undefined}
|
||||
isPlatform
|
||||
hasValidLicense={true}
|
||||
isBookingDryRun={routingParams?.isBookingDryRun}
|
||||
/>
|
||||
</AtomsWrapper>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
@import "/packages/ui/styles/shared-globals.css";
|
||||
@import "/apps/web/styles/globals.css";
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
@@ -39,48 +40,6 @@
|
||||
--ring: 215 20.2% 65.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
/* background */
|
||||
|
||||
--cal-bg-emphasis: #e5e7eb;
|
||||
--cal-bg: white;
|
||||
--cal-bg-subtle: #f3f4f6;
|
||||
--cal-bg-muted: #f9fafb;
|
||||
--cal-bg-inverted: #111827;
|
||||
|
||||
/* background -> components*/
|
||||
--cal-bg-info: #dee9fc;
|
||||
--cal-bg-success: #e2fbe8;
|
||||
--cal-bg-attention: #fceed8;
|
||||
--cal-bg-error: #f9e3e2;
|
||||
--cal-bg-dark-error: #752522;
|
||||
|
||||
/* Borders */
|
||||
--cal-border-emphasis: #9ca3af;
|
||||
--cal-border: #d1d5db;
|
||||
--cal-border-subtle: #e5e7eb;
|
||||
--cal-border-booker: #e5e7eb;
|
||||
--cal-border-muted: #f3f4f6;
|
||||
--cal-border-error: #aa2e26;
|
||||
|
||||
/* Content/Text */
|
||||
--cal-text-emphasis: #111827;
|
||||
--cal-text: #374151;
|
||||
--cal-text-subtle: #6b7280;
|
||||
--cal-text-muted: #9ca3af;
|
||||
--cal-text-inverted: white;
|
||||
|
||||
/* Content/Text -> components */
|
||||
--cal-text-info: #253985;
|
||||
--cal-text-success: #285231;
|
||||
--cal-text-attention: #73321b;
|
||||
--cal-text-error: #752522;
|
||||
|
||||
/* Brand shinanigans
|
||||
-> These will be computed for the users theme at runtime.
|
||||
*/
|
||||
--cal-brand: #111827;
|
||||
--cal-brand-emphasis: #101010;
|
||||
--cal-brand-text: white;
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -114,46 +73,6 @@
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
--cal-bg-emphasis: #2b2b2b;
|
||||
--cal-bg: #101010;
|
||||
--cal-bg-subtle: #2b2b2b;
|
||||
--cal-bg-muted: #1c1c1c;
|
||||
--cal-bg-inverted: #f3f4f6;
|
||||
|
||||
/* background -> components*/
|
||||
--cal-bg-info: #263fa9;
|
||||
--cal-bg-success: #306339;
|
||||
--cal-bg-attention: #8e3b1f;
|
||||
--cal-bg-error: #8c2822;
|
||||
--cal-bg-dark-error: #752522;
|
||||
|
||||
/* Borders */
|
||||
--cal-border-emphasis: #575757;
|
||||
--cal-border: #444444;
|
||||
--cal-border-subtle: #2b2b2b;
|
||||
--cal-border-booker: #2b2b2b;
|
||||
--cal-border-muted: #1c1c1c;
|
||||
--cal-border-error: #aa2e26;
|
||||
|
||||
/* Content/Text */
|
||||
--cal-text-emphasis: #f3f4f6;
|
||||
--cal-text: #d6d6d6;
|
||||
--cal-text-subtle: #a5a5a5;
|
||||
--cal-text-muted: #575757;
|
||||
--cal-text-inverted: #101010;
|
||||
|
||||
/* Content/Text -> components */
|
||||
--cal-text-info: #dee9fc;
|
||||
--cal-text-success: #e2fbe8;
|
||||
--cal-text-attention: #fceed8;
|
||||
--cal-text-error: #f9e3e2;
|
||||
|
||||
/* Brand shenanigans
|
||||
-> These will be computed for the users theme at runtime.
|
||||
*/
|
||||
--cal-brand: white;
|
||||
--cal-brand-emphasis: #e1e1e1;
|
||||
--cal-brand-text: black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13321,68 +13240,101 @@ select {
|
||||
grid-area: timeslots
|
||||
}
|
||||
|
||||
|
||||
|
||||
:root {
|
||||
--cal-bg-emphasis: #e5e7eb;
|
||||
--cal-bg: #fff;
|
||||
--cal-bg-subtle: #f3f4f6;
|
||||
--cal-bg-muted: #f9fafb;
|
||||
--cal-bg-inverted: #0f0f0f;
|
||||
--cal-bg-info: #f6f9fe;
|
||||
--cal-bg-success: #e4fbe9;
|
||||
--cal-bg-attention: #fcefd9;
|
||||
--cal-bg-error: hsla(3,66,93,1);
|
||||
--cal-bg-dark-error: #772522;
|
||||
--cal-border-emphasis: #9ca3b0;
|
||||
--cal-border: #d1d5db;
|
||||
--cal-border-subtle: #e5e7eb;
|
||||
--cal-border-booker: #e5e7eb;
|
||||
--cal-border-muted: #f3f4f6;
|
||||
--cal-border-error: #aa2f27;
|
||||
--cal-text-emphasis: #384252;
|
||||
--cal-text: #384252;
|
||||
--cal-text-subtle: #6b7280;
|
||||
--cal-text-muted: #9ca3b0;
|
||||
--cal-text-inverted: #fff;
|
||||
--cal-text-info: #253883;
|
||||
--cal-text-success: #285231;
|
||||
--cal-text-attention: #74331b;
|
||||
--cal-text-error: #772522;
|
||||
--cal-brand: #111827;
|
||||
--cal-brand-emphasis: #0f0f0f;
|
||||
--cal-brand-text: #fff
|
||||
/* background */
|
||||
|
||||
--cal-bg-emphasis: hsla(220,13%,91%,1);
|
||||
--cal-bg: hsla(0,0%,100%,1);
|
||||
--cal-bg-subtle: hsla(220, 14%, 96%,1);
|
||||
--cal-bg-muted: hsla(210,20%,98%,1);
|
||||
--cal-bg-inverted: hsla(0,0%,6%,1);
|
||||
|
||||
/* background -> components*/
|
||||
--cal-bg-info: hsla(218,83%,98%,1);
|
||||
--cal-bg-success: hsla(134,76%,94%,1);
|
||||
--cal-bg-attention: hsla(37, 86%, 92%, 1);
|
||||
--cal-bg-error: hsla(3,66%,93%,1);
|
||||
--cal-bg-dark-error: hsla(2, 55%, 30%, 1);
|
||||
|
||||
/* Borders */
|
||||
--cal-border-emphasis: hsla(218, 11%, 65%, 1);
|
||||
--cal-border: hsla(216, 12%, 84%, 1);
|
||||
--cal-border-subtle: hsla(220, 13%, 91%, 1);
|
||||
--cal-border-booker: #e5e7eb;
|
||||
--cal-border-muted: hsla(220, 14%, 96%, 1);
|
||||
--cal-border-error: hsla(4, 63%, 41%, 1);
|
||||
--cal-border-focus: hsla(0, 0%, 10%, 1);
|
||||
|
||||
/* Content/Text */
|
||||
--cal-text-emphasis: hsla(217, 19%, 27%, 1);
|
||||
--cal-text: hsla(217, 19%, 27%, 1);
|
||||
--cal-text-subtle: hsla(220, 9%, 46%, 1);
|
||||
--cal-text-muted: hsla(218, 11%, 65%, 1);
|
||||
--cal-text-inverted: hsla(0, 0%, 100%, 1);
|
||||
|
||||
/* Content/Text -> components */
|
||||
--cal-text-info: hsla(228, 56%, 33%, 1);
|
||||
--cal-text-success: hsla(133, 34%, 24%, 1);
|
||||
--cal-text-attention: hsla(16, 62%, 28%, 1);
|
||||
--cal-text-error: hsla(2, 55%, 30%, 1);
|
||||
|
||||
/* Brand shinanigans
|
||||
-> These will be computed for the users theme at runtime.
|
||||
*/
|
||||
--cal-brand: hsla(221, 39%, 11%, 1);
|
||||
--cal-brand-emphasis: hsla(0, 0%, 6%, 1);
|
||||
--cal-brand-text: hsla(0, 0%, 100%, 1);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--cal-bg-emphasis: #404040;
|
||||
--cal-bg: #1a1a1a;
|
||||
--cal-bg-subtle: #2e2e2e;
|
||||
--cal-bg-muted: #1f1f1f;
|
||||
--cal-bg-inverted: #f3f4f6;
|
||||
--cal-bg-info: #253883;
|
||||
--cal-bg-success: #285231;
|
||||
--cal-bg-attention: #74331b;
|
||||
--cal-bg-error: #772522;
|
||||
--cal-bg-dark-error: #772522;
|
||||
--cal-border-emphasis: #757575;
|
||||
--cal-border: #575757;
|
||||
--cal-border-subtle: #383838;
|
||||
--cal-border-booker: #383838;
|
||||
--cal-border-muted: #2e2e2e;
|
||||
--cal-border-error: #aa2f27;
|
||||
--cal-text-emphasis: #fcfcfd;
|
||||
--cal-text: #d6d6d6;
|
||||
--cal-text-subtle: #a6a6a6;
|
||||
--cal-text-muted: #575757;
|
||||
--cal-text-inverted: #1a1a1a;
|
||||
--cal-text-info: #dee9fc;
|
||||
--cal-text-success: #e4fbe9;
|
||||
--cal-text-attention: #fcefd9;
|
||||
--cal-text-error: #f9e3e1;
|
||||
--cal-brand: #fff;
|
||||
--cal-brand-emphasis: #9ca3b0;
|
||||
--cal-brand-text: #000
|
||||
/* background */
|
||||
|
||||
--cal-bg-emphasis: hsla(0, 0%, 25%, 1);
|
||||
--cal-bg: hsla(0, 0%, 10%, 1);
|
||||
--cal-bg-subtle: hsla(0, 0%, 18%, 1);
|
||||
--cal-bg-muted: hsla(0, 0%, 12%, 1);
|
||||
--cal-bg-inverted: hsla(220, 14%, 96%, 1);
|
||||
|
||||
/* background -> components*/
|
||||
--cal-bg-info: hsla(228, 56%, 33%, 1);
|
||||
--cal-bg-success: hsla(133, 34%, 24%, 1);
|
||||
--cal-bg-attention: hsla(16, 62%, 28%, 1);
|
||||
--cal-bg-error: hsla(2, 55%, 30%, 1);
|
||||
--cal-bg-dark-error: hsla(2, 55%, 30%, 1);
|
||||
|
||||
/* Borders */
|
||||
--cal-border-emphasis: hsla(0, 0%, 46%, 1);
|
||||
--cal-border: hsla(0, 0%, 34%, 1);
|
||||
--cal-border-subtle: hsla(0, 0%, 22%, 1);
|
||||
--cal-border-booker: hsla(0, 0%, 22%, 1);
|
||||
--cal-border-muted: hsla(0, 0%, 18%, 1);
|
||||
--cal-border-error: hsla(4, 63%, 41%, 1);
|
||||
--cal-border-focus: hsla(0, 0%, 100%, 1);
|
||||
|
||||
/* Content/Text */
|
||||
--cal-text-emphasis: hsla(240, 20%, 99%, 1);
|
||||
--cal-text: hsla(0, 0%, 84%, 1);
|
||||
--cal-text-subtle: hsla(0, 0%, 65%, 1);
|
||||
--cal-text-muted: hsla(0, 0%, 34%, 1);
|
||||
--cal-text-inverted: hsla(0, 0%, 10%, 1);
|
||||
|
||||
/* Content/Text -> components */
|
||||
--cal-text-info: hsla(218, 83%, 93%, 1);
|
||||
--cal-text-success: hsla(134, 76%, 94%, 1);
|
||||
--cal-text-attention: hsla(37, 86%, 92%, 1);
|
||||
--cal-text-error: hsla(3, 66%, 93%, 1);
|
||||
|
||||
/* Brand shenanigans
|
||||
-> These will be computed for the users theme at runtime.
|
||||
*/
|
||||
--cal-brand: hsla(0, 0%, 100%, 1);
|
||||
--cal-brand-emphasis: hsla(218, 11%, 65%, 1);
|
||||
--cal-brand-text: hsla(0, 0%, 0%,1);
|
||||
}
|
||||
|
||||
|
||||
::-moz-selection {
|
||||
color: var(--cal-brand-text);
|
||||
background: var(--cal-brand)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { setLastBookingResponse } from "@calcom/features/bookings/Booker/utils/l
|
||||
import { mapBookingToMutationInput, mapRecurringBookingToMutationInput } from "@calcom/features/bookings/lib";
|
||||
import type { BookerEvent } from "@calcom/features/bookings/types";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RoutingFormSearchParams } from "@calcom/platform-types";
|
||||
import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { UseCreateBookingInput } from "./useCreateBooking";
|
||||
@@ -23,6 +24,7 @@ type UseHandleBookingProps = {
|
||||
handleInstantBooking: (input: BookingCreateBody) => void;
|
||||
handleRecBooking: (input: BookingCreateBody[]) => void;
|
||||
locationUrl?: string;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
};
|
||||
|
||||
export const useHandleBookEvent = ({
|
||||
@@ -34,6 +36,7 @@ export const useHandleBookEvent = ({
|
||||
handleInstantBooking,
|
||||
handleRecBooking,
|
||||
locationUrl,
|
||||
routingFormSearchParams,
|
||||
}: UseHandleBookingProps) => {
|
||||
const setFormValues = useBookerStore((state) => state.setFormValues);
|
||||
const timeslot = useBookerStore((state) => state.selectedTimeslot);
|
||||
@@ -93,6 +96,7 @@ export const useHandleBookEvent = ({
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
orgSlug: orgSlug ? orgSlug : undefined,
|
||||
routingFormSearchParams,
|
||||
};
|
||||
|
||||
if (isInstantMeeting) {
|
||||
|
||||
@@ -22,6 +22,9 @@ export const useAvailableSlots = ({
|
||||
rest.isTeamEvent ?? false,
|
||||
rest.teamId ?? false,
|
||||
rest.usernameList,
|
||||
rest.routedTeamMemberIds,
|
||||
rest.skipContactOwner,
|
||||
rest.shouldServeCache,
|
||||
],
|
||||
queryFn: () => {
|
||||
return http
|
||||
|
||||
@@ -16,6 +16,8 @@ export { useMe } from "./hooks/useMe";
|
||||
export { OutlookConnect } from "./connect/outlook/OutlookConnect";
|
||||
export * as Connect from "./connect";
|
||||
export { BookerEmbed } from "./booker-embed";
|
||||
export { Router } from "./router";
|
||||
|
||||
export { useDeleteCalendarCredentials } from "./hooks/calendars/useDeleteCalendarCredentials";
|
||||
export { useAddSelectedCalendar } from "./hooks/calendars/useAddSelectedCalendar";
|
||||
export { useRemoveSelectedCalendar } from "./hooks/calendars/useRemoveSelectedCalendar";
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { ReactElement } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { BookerEmbed } from "../booker-embed";
|
||||
import type { BookerPlatformWrapperAtomPropsForTeam } from "../booker/BookerPlatformWrapper";
|
||||
|
||||
/**
|
||||
* Renders the Router component with predefined props.
|
||||
* Depending on the routing form either renders a custom message, redirects or display Booker embed atom.
|
||||
* formResponsesURLParams contains the answers to the questions fields defined in the form.
|
||||
* ```tsx
|
||||
* <Router
|
||||
* formId="1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e"
|
||||
* formResponsesURLParams={new URLSearchParams({ Territory: "Europe" })}
|
||||
* bookerBannerUrl="https://i0.wp.com/mahala.co.uk/wp-content/uploads/2014/12/img_banner-thin_mountains.jpg?fit=800%2C258&ssl=1"
|
||||
* bookerCustomClassNames={{
|
||||
* bookerWrapper: "dark",
|
||||
* }}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
export const Router = React.memo(
|
||||
({
|
||||
formId,
|
||||
formResponsesURLParams,
|
||||
onExternalRedirect,
|
||||
onDisplayBookerEmbed,
|
||||
renderMessage,
|
||||
bookerBannerUrl,
|
||||
bookerCustomClassNames,
|
||||
}: {
|
||||
formId: string;
|
||||
formResponsesURLParams?: URLSearchParams;
|
||||
onExternalRedirect?: () => void;
|
||||
onDisplayBookerEmbed?: () => void;
|
||||
renderMessage?: (message?: string) => ReactElement | ReactElement[];
|
||||
bookerBannerUrl?: BookerPlatformWrapperAtomPropsForTeam["bannerUrl"];
|
||||
bookerCustomClassNames?: BookerPlatformWrapperAtomPropsForTeam["customClassNames"];
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>();
|
||||
const [routerUrl, setRouterUrl] = useState<string>();
|
||||
const [routingData, setRoutingData] = useState<{ message: string } | undefined>();
|
||||
const [isError, setIsError] = useState<boolean>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLoading) {
|
||||
setIsLoading(true);
|
||||
setIsError(false);
|
||||
setRoutingData(undefined);
|
||||
setRouterUrl("");
|
||||
|
||||
const baseUrl = import.meta.env.VITE_BOOKER_EMBED_API_URL;
|
||||
fetch(`${baseUrl}/router/forms/${formId}/submit`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: formResponsesURLParams
|
||||
? JSON.stringify(Object.fromEntries(formResponsesURLParams))
|
||||
: undefined,
|
||||
})
|
||||
.then(async (response) => {
|
||||
const body:
|
||||
| { status: string; data: string; redirect: true }
|
||||
| { status: string; data: { message: string }; redirect: false } = await response.json();
|
||||
if (body.redirect) {
|
||||
setRouterUrl(body.data);
|
||||
} else {
|
||||
setRoutingData({ message: body.data?.message ?? "" });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setIsError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isRedirect = !!routerUrl;
|
||||
|
||||
if (isLoading || isError) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (!isLoading && isRedirect && routerUrl) {
|
||||
const redirectParams = new URLSearchParams(routerUrl);
|
||||
if (redirectParams.get("cal.action") === "eventTypeRedirectUrl") {
|
||||
// display booker with redirect URL
|
||||
onDisplayBookerEmbed?.();
|
||||
return (
|
||||
<BookerEmbed
|
||||
routingFormUrl={routerUrl}
|
||||
customClassNames={bookerCustomClassNames}
|
||||
bannerUrl={bookerBannerUrl}
|
||||
/>
|
||||
);
|
||||
} else if (redirectParams.get("cal.action") === "externalRedirectUrl") {
|
||||
onExternalRedirect?.();
|
||||
window.location.href = routerUrl;
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRedirect && routingData?.message) {
|
||||
if (renderMessage) {
|
||||
return <>{renderMessage(routingData?.message)}</>;
|
||||
}
|
||||
return (
|
||||
<div className="mx-auto my-0 max-w-3xl md:my-24">
|
||||
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
|
||||
<div className="text-default bg-default -mx-4 rounded-sm border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
|
||||
<div>{routingData?.message}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
}
|
||||
);
|
||||
|
||||
Router.displayName = "RouterAtom";
|
||||
@@ -0,0 +1 @@
|
||||
export { Router } from "./Router";
|
||||
@@ -7,7 +7,7 @@ import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import Select from "react-select";
|
||||
|
||||
import { CalProvider, BookerEmbed } from "@calcom/atoms";
|
||||
import { CalProvider, BookerEmbed, Router } from "@calcom/atoms";
|
||||
import "@calcom/atoms/globals.min.css";
|
||||
|
||||
const poppins = Poppins({ subsets: ["latin"], weight: ["400", "800"] });
|
||||
@@ -121,6 +121,21 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{pathname === "/router" && (
|
||||
<div className="p-4">
|
||||
<Router
|
||||
formId="a63e6fce-899a-404e-8c38-e069710589c5"
|
||||
formResponsesURLParams={new URLSearchParams({ isBookingDryRun: "true", Territory: "Europe" })}
|
||||
onDisplayBookerEmbed={() => {
|
||||
console.log("render booker embed");
|
||||
}}
|
||||
bookerBannerUrl="https://i0.wp.com/mahala.co.uk/wp-content/uploads/2014/12/img_banner-thin_mountains.jpg?fit=800%2C258&ssl=1"
|
||||
bookerCustomClassNames={{
|
||||
bookerWrapper: "dark",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Navbar } from "@/components/Navbar";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export default function Router(props: { calUsername: string; calEmail: string }) {
|
||||
return (
|
||||
<main className={`flex ${inter.className} text-default dark flex flex-col`}>
|
||||
<Navbar username={props.calUsername} />
|
||||
<div>
|
||||
<h1 className="mx-8 my-4 text-2xl font-bold">This is the router atom</h1>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export type RoutingFormSearchParamsForEmbed = {
|
||||
organizationId?: number;
|
||||
teamId?: number;
|
||||
eventTypeSlug: string;
|
||||
username?: string;
|
||||
} & RoutingFormSearchParams;
|
||||
|
||||
export type RoutingFormSearchParams = {
|
||||
["cal.routedTeamMemberIds"]?: string;
|
||||
["cal.reroutingFormResponses"]?: string;
|
||||
["cal.skipContactOwner"]?: string;
|
||||
["cal.isBookingDryRun"]?: string;
|
||||
["cal.cache"]?: string;
|
||||
["cal.routingFormResponseId"]?: string;
|
||||
["cal.salesforce.rrSkipToAccountLookupField"]?: string;
|
||||
};
|
||||
@@ -9,3 +9,4 @@ export * from "./schedules";
|
||||
export * from "./event-types";
|
||||
export * from "./organizations";
|
||||
export * from "./teams";
|
||||
export * from "./embed";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
@@ -96,6 +96,34 @@ export class GetAvailableSlotsInput {
|
||||
enum: SlotFormat,
|
||||
})
|
||||
slotFormat?: SlotFormat;
|
||||
|
||||
// note(rajiv): after going through getUrlSearchParamsToForward.ts we found out
|
||||
// that the below properties were not being included inside getSlots :- cc @morgan
|
||||
// cal.salesforce.rrSkipToAccountLookupField, cal.rerouting, cal.routingFormResponseId, cal.reroutingFormResponses & cal.isTestPreviewLink
|
||||
// hence no input values have been setup for them in GetAvailableSlotsInput
|
||||
@Transform(({ value }) => value && value.toLowerCase() === "true")
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
skipContactOwner?: boolean;
|
||||
|
||||
@Transform(({ value }) => value && value.toLowerCase() === "true")
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
shouldServeCache?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((s: string) => parseInt(s));
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@IsArray()
|
||||
@IsNumber({}, { each: true })
|
||||
@ApiHideProperty()
|
||||
routedTeamMemberIds?: number[];
|
||||
}
|
||||
|
||||
export class RemoveSelectedSlotInput {
|
||||
|
||||
Reference in New Issue
Block a user