chore: salesforce for Router / Booker atoms and api v2 (#19217)
* wip * small refactor * small refactor * fixup! Merge branch 'main' into enable-salesforce-router-apiv2-atom * crm props * fixup! crm props * bump platform libraries * bump platform libraries
This commit is contained in:
@@ -30,7 +30,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.95",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.99",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -169,4 +169,19 @@ export class CreateBookingInput_2024_04_15 {
|
||||
label?: string | undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
teamMemberEmail?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
crmAppSlug?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
crmOwnerRecordType?: string;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
|
||||
import { Controller, Req, NotFoundException, Param, Post, Body } from "@nestjs/common";
|
||||
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
|
||||
import { getRoutedUrl } from "@calcom/platform-libraries";
|
||||
import {
|
||||
getRoutedUrl,
|
||||
getTeamMemberEmailForResponseOrContactUsingUrlQuery,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { ApiResponse } from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
@@ -13,6 +17,8 @@ import { ApiResponse } from "@calcom/platform-types";
|
||||
@DocsTags("Router controller")
|
||||
@DocsExcludeController(true)
|
||||
export class RouterController {
|
||||
constructor(private readonly teamsEventTypesRepository: TeamsEventTypesRepository) {}
|
||||
|
||||
@Post("/forms/:formId/submit")
|
||||
async getRoutingFormResponse(
|
||||
@Req() request: Request,
|
||||
@@ -27,13 +33,54 @@ export class RouterController {
|
||||
}
|
||||
|
||||
if (routedUrlData?.redirect?.destination) {
|
||||
return { status: "success", data: routedUrlData?.redirect?.destination, redirect: true };
|
||||
return this.handleRedirect(routedUrlData.redirect.destination);
|
||||
}
|
||||
|
||||
if (routedUrlData?.props) {
|
||||
return { status: "success", data: { message: routedUrlData?.props?.message ?? "" }, redirect: false };
|
||||
return { status: "success", data: { message: routedUrlData.props.message ?? "" }, redirect: false };
|
||||
}
|
||||
|
||||
return { status: "success", data: { message: "No Route nor custom message found." }, redirect: false };
|
||||
}
|
||||
|
||||
private async handleRedirect(destination: string): Promise<ApiResponse<unknown> & { redirect: boolean }> {
|
||||
const routingUrl = new URL(destination);
|
||||
const routingSearchParams = routingUrl.searchParams;
|
||||
if (
|
||||
routingSearchParams.get("cal.action") === "eventTypeRedirectUrl" &&
|
||||
routingSearchParams.has("email") &&
|
||||
routingSearchParams.has("cal.teamId") &&
|
||||
!routingSearchParams.has("cal.skipContactOwner")
|
||||
) {
|
||||
return this.handleRedirectWithContactOwner(routingUrl, routingSearchParams);
|
||||
}
|
||||
|
||||
return { status: "success", data: destination, redirect: true };
|
||||
}
|
||||
|
||||
private async handleRedirectWithContactOwner(
|
||||
routingUrl: URL,
|
||||
routingSearchParams: URLSearchParams
|
||||
): Promise<ApiResponse<unknown> & { redirect: boolean }> {
|
||||
const pathNameParams = routingUrl.pathname.split("/");
|
||||
const eventTypeSlug = pathNameParams[pathNameParams.length - 1];
|
||||
const teamId = Number(routingSearchParams.get("cal.teamId"));
|
||||
const eventTypeData = this.teamsEventTypesRepository.getTeamEventTypeBySlug(teamId, eventTypeSlug, 3);
|
||||
|
||||
// get the salesforce record owner email for the email given as a form response.
|
||||
const {
|
||||
email: teamMemberEmail,
|
||||
recordType: crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query: Object.fromEntries(routingSearchParams),
|
||||
eventData: eventTypeData,
|
||||
});
|
||||
|
||||
Boolean(teamMemberEmail) && routingUrl.searchParams.set("cal.teamMemberEmail", teamMemberEmail);
|
||||
Boolean(crmOwnerRecordType) && routingUrl.searchParams.set("cal.crmOwnerRecordType", crmOwnerRecordType);
|
||||
Boolean(crmAppSlug) && routingUrl.searchParams.set("cal.crmAppSlug", crmAppSlug);
|
||||
|
||||
return { status: "success", data: routingUrl.toString(), redirect: true };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RouterController } from "@/modules/router/controllers/router.controller";
|
||||
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [],
|
||||
providers: [TeamsEventTypesRepository],
|
||||
exports: [],
|
||||
controllers: [RouterController],
|
||||
})
|
||||
|
||||
@@ -5593,6 +5593,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamMemberEmail",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { GetServerSidePropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrganizationSEOSettings } from "@calcom/features/ee/organizations/lib/orgSettings";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
@@ -21,9 +21,6 @@ const paramsSchema = z.object({
|
||||
slug: z.string().transform((s) => slugify(s)),
|
||||
});
|
||||
|
||||
// Booker page fetches a tiny bit of data server side:
|
||||
// 1. Check if team exists, to show 404
|
||||
// 2. If rescheduling, get the booking details
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const { req, params, query } = context;
|
||||
const session = await getServerSession({ req });
|
||||
@@ -45,7 +42,90 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
}
|
||||
}
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
const team = await getTeamWithEventsData(teamSlug, meetingSlug, isValidOrgDomain, currentOrgDomain);
|
||||
|
||||
if (!team || !team.eventTypes?.[0]) {
|
||||
return { notFound: true } as const;
|
||||
}
|
||||
|
||||
const eventData = team.eventTypes[0];
|
||||
const eventTypeId = eventData.id;
|
||||
const eventHostsUserData = await getUsersData(
|
||||
team.isPrivate,
|
||||
eventTypeId,
|
||||
eventData.hosts.map((h) => h.user)
|
||||
);
|
||||
const orgSlug = isValidOrgDomain ? currentOrgDomain : null;
|
||||
const name = team.parent?.name ?? team.name ?? null;
|
||||
|
||||
const booking = rescheduleUid ? await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id) : null;
|
||||
const ssr = await ssrInit(context);
|
||||
const fromRedirectOfNonOrgLink = context.query.orgRedirection === "true";
|
||||
const isUnpublished = team.parent ? !team.parent.slug : !team.slug;
|
||||
|
||||
const { getTeamMemberEmailForResponseOrContactUsingUrlQuery } = await import(
|
||||
"@calcom/lib/server/getTeamMemberEmailFromCrm"
|
||||
);
|
||||
const {
|
||||
email: teamMemberEmail,
|
||||
recordType: crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query,
|
||||
eventData,
|
||||
});
|
||||
|
||||
const organizationSettings = getOrganizationSEOSettings(team);
|
||||
const allowSEOIndexing = organizationSettings?.allowSEOIndexing ?? false;
|
||||
|
||||
return {
|
||||
props: {
|
||||
eventData: {
|
||||
eventTypeId,
|
||||
entity: {
|
||||
fromRedirectOfNonOrgLink,
|
||||
considerUnpublished: isUnpublished && !fromRedirectOfNonOrgLink,
|
||||
orgSlug,
|
||||
teamSlug: team.slug ?? null,
|
||||
name,
|
||||
},
|
||||
length: eventData.length,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventData.metadata),
|
||||
profile: {
|
||||
image: team.parent
|
||||
? getPlaceholderAvatar(team.parent.logoUrl, team.parent.name)
|
||||
: getPlaceholderAvatar(team.logoUrl, team.name),
|
||||
name,
|
||||
username: orgSlug ?? null,
|
||||
},
|
||||
title: eventData.title,
|
||||
users: eventHostsUserData,
|
||||
hidden: eventData.hidden,
|
||||
},
|
||||
booking,
|
||||
user: teamSlug,
|
||||
teamId: team.id,
|
||||
slug: meetingSlug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: team?.hideBranding,
|
||||
isInstantMeeting: eventData && queryIsInstantMeeting ? true : false,
|
||||
themeBasis: null,
|
||||
orgBannerUrl: team.parent?.bannerUrl ?? "",
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isSEOIndexable: allowSEOIndexing,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const getTeamWithEventsData = async (
|
||||
teamSlug: string,
|
||||
meetingSlug: string,
|
||||
isValidOrgDomain: boolean,
|
||||
currentOrgDomain: string | null
|
||||
) => {
|
||||
return await prisma.team.findFirst({
|
||||
where: {
|
||||
...getSlugOrRequestedSlug(teamSlug),
|
||||
parent: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||
@@ -107,32 +187,22 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!team || !team.eventTypes?.[0]) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
const eventData = team.eventTypes[0];
|
||||
const eventTypeId = eventData.id;
|
||||
|
||||
// INFO: This code was pulled from getPublicEvent and used here.
|
||||
// Calling the tRPC fetch to get the public event data is incredibly slow
|
||||
// for large teams and we don't want to add it back. Future refactors will happen
|
||||
// to speed up this call.
|
||||
let users: { username: string; name: string }[] = [];
|
||||
|
||||
if (!team.isPrivate && eventData.hosts.length > 0) {
|
||||
users = eventData.hosts
|
||||
.filter((host) => host.user.username)
|
||||
.map((host) => ({
|
||||
username: host.user.username ?? "",
|
||||
name: host.user.name ?? "",
|
||||
const getUsersData = async (
|
||||
isPrivateTeam: boolean,
|
||||
eventTypeId: number,
|
||||
users: Pick<User, "username" | "name">[]
|
||||
) => {
|
||||
if (!isPrivateTeam && users.length > 0) {
|
||||
return users
|
||||
.filter((user) => user.username)
|
||||
.map((user) => ({
|
||||
username: user.username ?? "",
|
||||
name: user.name ?? "",
|
||||
}));
|
||||
}
|
||||
if (!team.isPrivate && eventData.hosts.length === 0) {
|
||||
// a minimalistic version of `getOwnerFromUsersArray` in `getPublicEvent.ts`
|
||||
// backward compatibility logic for team event types that have users[] but not hosts[]
|
||||
if (!isPrivateTeam && users.length === 0) {
|
||||
const { users: data } = await prisma.eventType.findUniqueOrThrow({
|
||||
where: { id: eventTypeId },
|
||||
select: {
|
||||
@@ -146,85 +216,15 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
},
|
||||
});
|
||||
|
||||
if (data.length > 0) {
|
||||
users = [
|
||||
{
|
||||
username: data[0].username ?? "",
|
||||
name: data[0].name ?? "",
|
||||
},
|
||||
];
|
||||
}
|
||||
return data.length > 0
|
||||
? [
|
||||
{
|
||||
username: data[0].username ?? "",
|
||||
name: data[0].name ?? "",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
const orgSlug = isValidOrgDomain ? currentOrgDomain : null;
|
||||
const name = team.parent?.name ?? team.name ?? null;
|
||||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||
}
|
||||
|
||||
const ssr = await ssrInit(context);
|
||||
const fromRedirectOfNonOrgLink = context.query.orgRedirection === "true";
|
||||
const isUnpublished = team.parent ? !team.parent.slug : !team.slug;
|
||||
const { getTeamMemberEmailForResponseOrContactUsingUrlQuery } = await import(
|
||||
"@calcom/lib/server/getTeamMemberEmailFromCrm"
|
||||
);
|
||||
const {
|
||||
email: teamMemberEmail,
|
||||
recordType: crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query,
|
||||
eventData,
|
||||
});
|
||||
|
||||
const organizationSettings = getOrganizationSEOSettings(team);
|
||||
const allowSEOIndexing = organizationSettings?.allowSEOIndexing ?? false;
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
eventData: {
|
||||
eventTypeId,
|
||||
entity: {
|
||||
fromRedirectOfNonOrgLink,
|
||||
considerUnpublished: isUnpublished && !fromRedirectOfNonOrgLink,
|
||||
orgSlug,
|
||||
teamSlug: team.slug ?? null,
|
||||
name,
|
||||
},
|
||||
length: eventData.length,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventData.metadata),
|
||||
profile: {
|
||||
image: team.parent
|
||||
? getPlaceholderAvatar(team.parent.logoUrl, team.parent.name)
|
||||
: getPlaceholderAvatar(team.logoUrl, team.name),
|
||||
name,
|
||||
username: orgSlug ?? null,
|
||||
},
|
||||
title: eventData.title,
|
||||
users,
|
||||
hidden: eventData.hidden,
|
||||
},
|
||||
booking,
|
||||
user: teamSlug,
|
||||
teamId: team.id,
|
||||
slug: meetingSlug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: team?.hideBranding,
|
||||
isInstantMeeting: eventData && queryIsInstantMeeting ? true : false,
|
||||
themeBasis: null,
|
||||
orgBannerUrl: team.parent?.bannerUrl ?? "",
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
isSEOIndexable: allowSEOIndexing,
|
||||
},
|
||||
};
|
||||
return [];
|
||||
};
|
||||
|
||||
@@ -5380,6 +5380,14 @@
|
||||
"enum": ["range", "time"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamMemberEmail",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -7338,7 +7346,7 @@
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' radio button will be selected and the select field will be disabled."
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' radio buttom will be selected and the select field will be disabled."
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
@@ -7946,7 +7954,7 @@
|
||||
},
|
||||
"beforeEventBuffer": {
|
||||
"type": "number",
|
||||
"description": "Time spaces that can be prepended before an event to give more time before it."
|
||||
"description": "Time spaces that can be pre-pended before an event to give more time before it."
|
||||
},
|
||||
"afterEventBuffer": {
|
||||
"type": "number",
|
||||
@@ -8997,7 +9005,7 @@
|
||||
},
|
||||
"disableOnPrefill": {
|
||||
"type": "boolean",
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' radio button will be selected and the select field will be disabled."
|
||||
"description": "Disable this booking field if the URL contains query parameter with key equal to the slug and prefill it with the provided value. For example, if the slug is `language` and options of this select field are ['english', 'italian'] and the URL contains query parameter `&language=italian`, the 'italian' radio buttom will be selected and the select field will be disabled."
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
@@ -9638,7 +9646,7 @@
|
||||
},
|
||||
"beforeEventBuffer": {
|
||||
"type": "number",
|
||||
"description": "Time spaces that can be prepended before an event to give more time before it."
|
||||
"description": "Time spaces that can be pre-pended before an event to give more time before it."
|
||||
},
|
||||
"afterEventBuffer": {
|
||||
"type": "number",
|
||||
@@ -10992,7 +11000,7 @@
|
||||
},
|
||||
"beforeEventBuffer": {
|
||||
"type": "number",
|
||||
"description": "Time spaces that can be prepended before an event to give more time before it."
|
||||
"description": "Time spaces that can be pre-pended before an event to give more time before it."
|
||||
},
|
||||
"afterEventBuffer": {
|
||||
"type": "number",
|
||||
@@ -11763,7 +11771,7 @@
|
||||
},
|
||||
"beforeEventBuffer": {
|
||||
"type": "number",
|
||||
"description": "Time spaces that can be prepended before an event to give more time before it."
|
||||
"description": "Time spaces that can be pre-pended before an event to give more time before it."
|
||||
},
|
||||
"afterEventBuffer": {
|
||||
"type": "number",
|
||||
@@ -15366,7 +15374,7 @@
|
||||
"externalId": {
|
||||
"type": "string",
|
||||
"example": "https://caldav.icloud.com/26962146906/calendars/1644422A-1945-4438-BBC0-4F0Q23A57R7S/",
|
||||
"description": "Unique identifier used to represent the specific calendar, as returned by the /calendars endpoint"
|
||||
"description": "Unique identifier used to represent the specfic calendar, as returned by the /calendars endpoint"
|
||||
}
|
||||
},
|
||||
"required": ["integration", "externalId"]
|
||||
|
||||
@@ -15,7 +15,7 @@ import isRouter from "../lib/isRouter";
|
||||
import { onFormSubmission } from "../trpc/utils";
|
||||
import type { FormResponse, SerializableForm } from "../types/types";
|
||||
|
||||
type Form = SerializableForm<
|
||||
export type Form = SerializableForm<
|
||||
App_RoutingForms_Form & {
|
||||
user: {
|
||||
id: number;
|
||||
|
||||
@@ -41,6 +41,9 @@ export const BookerEmbed = (
|
||||
eventTypeSlug,
|
||||
username,
|
||||
defaultFormValues,
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
...routingFormSearchParams
|
||||
} = routingFormUrlProps;
|
||||
const { onDryRunSuccess, ...rest } = props;
|
||||
@@ -67,6 +70,9 @@ export const BookerEmbed = (
|
||||
})}
|
||||
routingFormSearchParams={routingFormSearchParams}
|
||||
defaultFormValues={defaultFormValues}
|
||||
teamMemberEmail={teamMemberEmail}
|
||||
crmOwnerRecordType={crmOwnerRecordType}
|
||||
crmAppSlug={crmAppSlug}
|
||||
onDryRunSuccess={() => {
|
||||
if (onDryRunSuccess) {
|
||||
onDryRunSuccess();
|
||||
|
||||
@@ -81,6 +81,15 @@ export const useGetRoutingFormUrlProps = ({ routingFormUrl }: { routingFormUrl?:
|
||||
["cal.salesforce.rrSkipToAccountLookupField"]:
|
||||
routingSearchParams.get("cal.salesforce.rrSkipToAccountLookupField") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.teamMemberEmail") && {
|
||||
teamMemberEmail: routingSearchParams.get("cal.teamMemberEmail") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.crmOwnerRecordType") && {
|
||||
crmOwnerRecordType: routingSearchParams.get("cal.crmOwnerRecordType") ?? undefined,
|
||||
}),
|
||||
...(routingSearchParams.get("cal.crmAppSlug") && {
|
||||
crmAppSlug: routingSearchParams.get("cal.crmAppSlug") ?? undefined,
|
||||
}),
|
||||
} satisfies RoutingFormSearchParamsForEmbed;
|
||||
return { ...routingformProps, defaultFormValues };
|
||||
}
|
||||
|
||||
@@ -102,7 +102,14 @@ export type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomPro
|
||||
export const BookerPlatformWrapper = (
|
||||
props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam
|
||||
) => {
|
||||
const { view = "MONTH_VIEW", bannerUrl, routingFormSearchParams } = props;
|
||||
const {
|
||||
view = "MONTH_VIEW",
|
||||
bannerUrl,
|
||||
routingFormSearchParams,
|
||||
teamMemberEmail,
|
||||
crmAppSlug,
|
||||
crmOwnerRecordType,
|
||||
} = props;
|
||||
const layout = BookerLayouts[view];
|
||||
|
||||
const { clientId } = useAtomsContext();
|
||||
@@ -205,6 +212,9 @@ export const BookerPlatformWrapper = (
|
||||
const bookerLayout = useBookerLayout(event.data);
|
||||
useInitializeBookerStore({
|
||||
...props,
|
||||
teamMemberEmail,
|
||||
crmAppSlug,
|
||||
crmOwnerRecordType,
|
||||
eventId: event.data?.id,
|
||||
rescheduleUid: props.rescheduleUid ?? null,
|
||||
bookingUid: props.bookingUid ?? null,
|
||||
@@ -296,7 +306,6 @@ export const BookerPlatformWrapper = (
|
||||
...(isBookingDryRun ? { isBookingDryRun } : {}),
|
||||
});
|
||||
}, [routingFormSearchParams]);
|
||||
|
||||
const schedule = useAvailableSlots({
|
||||
usernameList: getUsernameList(username),
|
||||
eventTypeId: event?.data?.id ?? 0,
|
||||
@@ -305,6 +314,7 @@ export const BookerPlatformWrapper = (
|
||||
timeZone: timezone,
|
||||
duration: selectedDuration ?? undefined,
|
||||
rescheduleUid: props.rescheduleUid,
|
||||
teamMemberEmail: teamMemberEmail ?? undefined,
|
||||
...(props.isTeamEvent
|
||||
? {
|
||||
isTeamEvent: props.isTeamEvent,
|
||||
@@ -476,6 +486,9 @@ export const BookerPlatformWrapper = (
|
||||
return (
|
||||
<AtomsWrapper customClassName={props?.customClassNames?.atomsWrapper}>
|
||||
<BookerComponent
|
||||
teamMemberEmail={teamMemberEmail}
|
||||
crmAppSlug={crmAppSlug}
|
||||
crmOwnerRecordType={crmOwnerRecordType}
|
||||
customClassNames={props.customClassNames}
|
||||
eventSlug={props.eventSlug}
|
||||
username={username}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const useAvailableSlots = ({
|
||||
rest.routedTeamMemberIds,
|
||||
rest.skipContactOwner,
|
||||
rest.shouldServeCache,
|
||||
rest.teamMemberEmail,
|
||||
],
|
||||
queryFn: () => {
|
||||
return http
|
||||
|
||||
@@ -3,6 +3,9 @@ export type RoutingFormSearchParamsForEmbed = {
|
||||
teamId?: number;
|
||||
eventTypeSlug: string;
|
||||
username?: string;
|
||||
crmAppSlug?: string;
|
||||
crmOwnerRecordType?: string;
|
||||
teamMemberEmail?: string;
|
||||
} & RoutingFormSearchParams;
|
||||
|
||||
export type RoutingFormSearchParams = {
|
||||
@@ -12,5 +15,8 @@ export type RoutingFormSearchParams = {
|
||||
["cal.isBookingDryRun"]?: string;
|
||||
["cal.cache"]?: string;
|
||||
["cal.routingFormResponseId"]?: string;
|
||||
["cal.crmAppSlug"]?: string;
|
||||
["cal.crmOwnerRecordType"]?: string;
|
||||
["cal.teamMemberEmail"]?: string;
|
||||
["cal.salesforce.rrSkipToAccountLookupField"]?: string;
|
||||
};
|
||||
|
||||
@@ -124,6 +124,11 @@ export class GetAvailableSlotsInput {
|
||||
@IsNumber({}, { each: true })
|
||||
@ApiHideProperty()
|
||||
routedTeamMemberIds?: number[];
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
teamMemberEmail?: string;
|
||||
}
|
||||
|
||||
export class RemoveSelectedSlotInput {
|
||||
|
||||
Reference in New Issue
Block a user