feat: atoms team booking (#14525)
* feat: setup usePublicEvent with orgSlug and duration * feat: setup useAvailableSlots with eventTypeSlug and orgSlug * fix & feat: fix TS issues and allow passing multiple users to Booker * refactor: dont show [15min] [30min] [60min] [1h30min] picker * fix: pass orgSlug to handleNewBooking -> loadUsers -> findUsersByUsername to not return empty users[] * refactor: display attendees in booking confirmation screen * fix: getting slots get orgSlug from props not event * revert: Booker username use props instead of hardcoded values * refactor: setup BookerStore org and use it for selectedTime + isDynamic logic * refactor: hide 'what is this meeting about' input in final booking step * fix: TS error * revert: what is the meeting about field hiding * revert: make org as org? in booker store * revert: createNewBooking -> loadUsers -> get forced orgSlug from header not body * refactor: useHandleBookEvent get orgSlug from booker store not props * fix: if entity undefined dont access orgSlug * refactor: add isDynamic prop as queryKey for usePublicEvent * refactor: remove unused prop * re-add docs * fix: typescript error * fix: force platform orgSlug in handleNewBooking.ts * fix: remove duplicate setSelectedDuration declaration * refactor: destructure properies from body instead of req.body * fix: entity optional and hide event members --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
18 files changed
+148
-33
No files matched your search
@@ -150,7 +150,9 @@ export class BookingsController {
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
): Promise<ApiResponse<unknown>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
const locationUrl = body.locationUrl;
|
||||
|
||||
const { orgSlug, locationUrl } = body;
|
||||
req.headers["x-cal-force-slug"] = orgSlug;
|
||||
try {
|
||||
const booking = await handleNewBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl)
|
||||
|
||||
@@ -99,6 +99,10 @@ export class CreateBookingInput {
|
||||
@Type(() => Response)
|
||||
responses!: Response;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
orgSlug?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
locationUrl?: string;
|
||||
|
||||
@@ -12,5 +12,5 @@ export class GetPublicEventTypeQueryParams {
|
||||
@ApiProperty({ required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
org?: string;
|
||||
org?: string | null;
|
||||
}
|
||||
@@ -722,6 +722,7 @@
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -4226,6 +4227,9 @@
|
||||
"responses": {
|
||||
"$ref": "#/components/schemas/Response"
|
||||
},
|
||||
"orgSlug": {
|
||||
"type": "string"
|
||||
},
|
||||
"locationUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -92,12 +92,14 @@ export const EventMeta = ({
|
||||
)}
|
||||
{!isPending && !!event && (
|
||||
<m.div {...fadeInUp} layout transition={{ ...fadeInUp.transition, delay: 0.3 }}>
|
||||
<EventMembers
|
||||
schedulingType={event.schedulingType}
|
||||
users={event.users}
|
||||
profile={event.profile}
|
||||
entity={event.entity}
|
||||
/>
|
||||
{!isPlatform && (
|
||||
<EventMembers
|
||||
schedulingType={event.schedulingType}
|
||||
users={event.users}
|
||||
profile={event.profile}
|
||||
entity={event.entity}
|
||||
/>
|
||||
)}
|
||||
<EventTitle className={`${classNames?.eventMetaTitle} my-2`}>{event?.title}</EventTitle>
|
||||
{event.description && (
|
||||
<EventMetaBlock contentClassName="mb-8 break-words max-w-full max-h-[180px] scroll-bar pr-4">
|
||||
|
||||
@@ -137,11 +137,13 @@ export type BookerStore = {
|
||||
* both the slug and the event slug.
|
||||
*/
|
||||
isTeamEvent: boolean;
|
||||
org?: string | null;
|
||||
seatedEventData: SeatedEventData;
|
||||
setSeatedEventData: (seatedEventData: SeatedEventData) => void;
|
||||
|
||||
isInstantMeeting?: boolean;
|
||||
|
||||
org?: string | null;
|
||||
setOrg: (org: string | null | undefined) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -342,6 +344,10 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
setFormValues: (formValues: Record<string, any>) => {
|
||||
set({ formValues });
|
||||
},
|
||||
org: null,
|
||||
setOrg: (org: string | null | undefined) => {
|
||||
set({ org });
|
||||
},
|
||||
}));
|
||||
|
||||
export const useInitializeBookerStore = ({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -37,6 +38,7 @@ export const getDurationFormatted = (mins: number | undefined, t: TFunction) =>
|
||||
|
||||
export const EventDuration = ({ event }: { event: PublicEvent }) => {
|
||||
const { t } = useLocale();
|
||||
const isPlatform = useIsPlatform();
|
||||
const [selectedDuration, setSelectedDuration, state] = useBookerStore((state) => [
|
||||
state.selectedDuration,
|
||||
state.setSelectedDuration,
|
||||
@@ -52,7 +54,7 @@ export const EventDuration = ({ event }: { event: PublicEvent }) => {
|
||||
setSelectedDuration(event.length);
|
||||
}, [selectedDuration, setSelectedDuration, event.metadata?.multipleDuration, event.length, isDynamicEvent]);
|
||||
|
||||
if (!event?.metadata?.multipleDuration && !isDynamicEvent)
|
||||
if ((!event?.metadata?.multipleDuration && !isDynamicEvent) || isPlatform)
|
||||
return <>{getDurationFormatted(event.length, t)}</>;
|
||||
|
||||
const durations = event?.metadata?.multipleDuration || [15, 30, 60, 90];
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
@@ -19,10 +18,9 @@ export interface EventMembersProps {
|
||||
}
|
||||
|
||||
export const EventMembers = ({ schedulingType, users, profile, entity }: EventMembersProps) => {
|
||||
const isPlatform = useIsPlatform();
|
||||
const isEmbed = useIsEmbed();
|
||||
const showMembers = !!schedulingType && schedulingType !== SchedulingType.ROUND_ROBIN;
|
||||
const shownUsers = showMembers && !isPlatform ? users : [];
|
||||
const shownUsers = showMembers ? users : [];
|
||||
|
||||
// In some cases we don't show the user's names, but only show the profile name.
|
||||
const showOnlyProfileName =
|
||||
|
||||
@@ -19,6 +19,7 @@ export type BookingOptions = {
|
||||
bookingUid?: string;
|
||||
seatReferenceUid?: string;
|
||||
hashedLink?: string | null;
|
||||
orgSlug?: string;
|
||||
};
|
||||
|
||||
export const mapBookingToMutationInput = ({
|
||||
@@ -34,6 +35,7 @@ export const mapBookingToMutationInput = ({
|
||||
bookingUid,
|
||||
seatReferenceUid,
|
||||
hashedLink,
|
||||
orgSlug,
|
||||
}: BookingOptions): BookingCreateBody => {
|
||||
return {
|
||||
...values,
|
||||
@@ -53,6 +55,7 @@ export const mapBookingToMutationInput = ({
|
||||
bookingUid,
|
||||
seatReferenceUid,
|
||||
hashedLink,
|
||||
orgSlug,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -53,10 +53,17 @@ export function getOrgSlug(hostname: string, forcedSlug?: string) {
|
||||
}
|
||||
|
||||
export function orgDomainConfig(req: IncomingMessage | undefined, fallback?: string | string[]) {
|
||||
const forPlatform = isPlatformRequest(req);
|
||||
const forcedSlugHeader = req?.headers?.["x-cal-force-slug"];
|
||||
|
||||
const forcedSlug = forcedSlugHeader instanceof Array ? forcedSlugHeader[0] : forcedSlugHeader;
|
||||
|
||||
if (forPlatform && forcedSlug) {
|
||||
return {
|
||||
isValidOrgDomain: true,
|
||||
currentOrgDomain: forcedSlug,
|
||||
};
|
||||
}
|
||||
|
||||
const hostname = req?.headers?.host || "";
|
||||
return getOrgDomainConfigFromHostname({
|
||||
hostname,
|
||||
@@ -65,6 +72,10 @@ export function orgDomainConfig(req: IncomingMessage | undefined, fallback?: str
|
||||
});
|
||||
}
|
||||
|
||||
function isPlatformRequest(req: IncomingMessage | undefined) {
|
||||
return !!req?.headers?.["x-cal-client-id"];
|
||||
}
|
||||
|
||||
export function getOrgDomainConfigFromHostname({
|
||||
hostname,
|
||||
fallback,
|
||||
|
||||
@@ -34,13 +34,15 @@ import { usePublicEvent } from "../hooks/usePublicEvent";
|
||||
import { useSlots } from "../hooks/useSlots";
|
||||
import { AtomsWrapper } from "../src/components/atoms-wrapper";
|
||||
|
||||
type BookerPlatformWrapperAtomProps = Omit<BookerProps, "entity"> & {
|
||||
type BookerPlatformWrapperAtomProps = Omit<BookerProps, "username" | "entity"> & {
|
||||
rescheduleUid?: string;
|
||||
bookingUid?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
guests?: string[];
|
||||
name?: string;
|
||||
username: string | string[];
|
||||
entity?: BookerProps["entity"];
|
||||
onCreateBookingSuccess?: (data: ApiSuccessResponse<BookingResponse>) => void;
|
||||
onCreateBookingError?: (data: ApiErrorResponse | Error) => void;
|
||||
onCreateRecurringBookingSuccess?: (data: ApiSuccessResponse<BookingResponse[]>) => void;
|
||||
@@ -59,8 +61,8 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
const setSelectedDate = useBookerStore((state) => state.setSelectedDate);
|
||||
const setSelectedDuration = useBookerStore((state) => state.setSelectedDuration);
|
||||
const setBookingData = useBookerStore((state) => state.setBookingData);
|
||||
const setOrg = useBookerStore((state) => state.setOrg);
|
||||
const bookingData = useBookerStore((state) => state.bookingData);
|
||||
|
||||
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
|
||||
const setSelectedMonth = useBookerStore((state) => state.setMonth);
|
||||
const { data: booking } = useGetBookingForReschedule({
|
||||
@@ -70,18 +72,36 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
},
|
||||
});
|
||||
|
||||
const username = useMemo(() => {
|
||||
return formatUsername(props.username);
|
||||
}, [props.username]);
|
||||
|
||||
useEffect(() => {
|
||||
// reset booker whenever it's unmounted
|
||||
return () => {
|
||||
setBookerState("loading");
|
||||
setSelectedDate(null);
|
||||
setSelectedTimeslot(null);
|
||||
setSelectedDuration(null);
|
||||
setOrg(null);
|
||||
setSelectedMonth(null);
|
||||
setSelectedDuration(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const event = usePublicEvent({ username: props.username, eventSlug: props.eventSlug });
|
||||
setSelectedDuration(props.duration ?? null);
|
||||
setOrg(props.entity?.orgSlug ?? null);
|
||||
|
||||
const isDynamic = useMemo(() => {
|
||||
return getUsernameList(username ?? "").length > 1;
|
||||
}, [username]);
|
||||
|
||||
const event = usePublicEvent({
|
||||
username,
|
||||
eventSlug: props.eventSlug,
|
||||
isDynamic,
|
||||
});
|
||||
|
||||
const bookerLayout = useBookerLayout(event.data);
|
||||
useInitializeBookerStore({
|
||||
...props,
|
||||
@@ -89,7 +109,8 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
rescheduleUid: props.rescheduleUid ?? null,
|
||||
bookingUid: props.bookingUid ?? null,
|
||||
layout: bookerLayout.defaultLayout,
|
||||
org: event.data?.entity.orgSlug,
|
||||
org: props.entity?.orgSlug,
|
||||
username,
|
||||
bookingData,
|
||||
});
|
||||
const [dayCount] = useBookerStore((state) => [state.dayCount, state.setDayCount], shallow);
|
||||
@@ -133,8 +154,9 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
prefetchNextMonth,
|
||||
selectedDate,
|
||||
});
|
||||
|
||||
const schedule = useAvailableSlots({
|
||||
usernameList: getUsernameList(props.username ?? ""),
|
||||
usernameList: getUsernameList(username ?? ""),
|
||||
eventTypeId: event?.data?.id ?? 0,
|
||||
startTime,
|
||||
endTime,
|
||||
@@ -142,11 +164,13 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
duration: selectedDuration ?? undefined,
|
||||
rescheduleUid: props.rescheduleUid,
|
||||
enabled:
|
||||
Boolean(props.username) &&
|
||||
Boolean(username) &&
|
||||
Boolean(month) &&
|
||||
Boolean(timezone) &&
|
||||
// Should only wait for one or the other, not both.
|
||||
(Boolean(eventSlug) || Boolean(event?.data?.id) || event?.data?.id === 0),
|
||||
orgSlug: props.entity?.orgSlug ?? undefined,
|
||||
eventTypeSlug: isDynamic ? "dynamic" : undefined,
|
||||
});
|
||||
|
||||
const bookerForm = useBookingForm({
|
||||
@@ -242,7 +266,7 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
<BookerComponent
|
||||
customClassNames={props.customClassNames}
|
||||
eventSlug={props.eventSlug}
|
||||
username={props.username}
|
||||
username={username}
|
||||
entity={
|
||||
event?.data?.entity ?? {
|
||||
considerUnpublished: false,
|
||||
@@ -324,3 +348,10 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) =>
|
||||
</AtomsWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
function formatUsername(username: string | string[]): string {
|
||||
if (typeof username === "string") {
|
||||
return username;
|
||||
}
|
||||
return username.join("+");
|
||||
}
|
||||
@@ -43,6 +43,8 @@ export const useHandleBookEvent = ({
|
||||
const bookingData = useBookerStore((state) => state.bookingData);
|
||||
const seatedEventData = useBookerStore((state) => state.seatedEventData);
|
||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
||||
const orgSlug = useBookerStore((state) => state.org);
|
||||
|
||||
const handleBookEvent = () => {
|
||||
const values = bookingForm.getValues();
|
||||
if (timeslot) {
|
||||
@@ -77,6 +79,7 @@ export const useHandleBookEvent = ({
|
||||
username: username || "",
|
||||
metadata: metadata,
|
||||
hashedLink,
|
||||
orgSlug: orgSlug ? orgSlug : undefined,
|
||||
};
|
||||
|
||||
if (isInstantMeeting) {
|
||||
|
||||
@@ -11,16 +11,23 @@ import http from "../lib/http";
|
||||
export const QUERY_KEY = "get-public-event";
|
||||
export type UsePublicEventReturnType = ReturnType<typeof usePublicEvent>;
|
||||
|
||||
export const usePublicEvent = (props: { username: string; eventSlug: string }) => {
|
||||
type Props = {
|
||||
username: string;
|
||||
eventSlug: string;
|
||||
isDynamic?: boolean;
|
||||
};
|
||||
|
||||
export const usePublicEvent = (props: Props) => {
|
||||
const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
|
||||
const isTeamEvent = useBookerStore((state) => state.isTeamEvent);
|
||||
const org = useBookerStore((state) => state.org);
|
||||
const selectedDuration = useBookerStore((state) => state.selectedDuration);
|
||||
|
||||
const requestUsername = username ?? props.username;
|
||||
const requestEventSlug = eventSlug ?? props.eventSlug;
|
||||
|
||||
const event = useQuery({
|
||||
queryKey: [QUERY_KEY, username ?? props.username, eventSlug ?? props.eventSlug],
|
||||
queryKey: [QUERY_KEY, username ?? props.username, eventSlug ?? props.eventSlug, props.isDynamic],
|
||||
queryFn: () => {
|
||||
return http
|
||||
.get<ApiResponse<PublicEventType>>(
|
||||
@@ -34,6 +41,12 @@ export const usePublicEvent = (props: { username: string; eventSlug: string }) =
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.data.status === SUCCESS_STATUS) {
|
||||
if (props.isDynamic && selectedDuration && res.data.data) {
|
||||
// note(Lauris): Mandatory - In case of "dynamic" event type default event duration returned by the API is 30,
|
||||
// but we are re-using the dynamic event type as a team event, so we must set the event length to whatever the event length is.
|
||||
res.data.data.length = selectedDuration;
|
||||
}
|
||||
|
||||
return res.data.data;
|
||||
}
|
||||
throw new Error(res.data.error.message);
|
||||
|
||||
@@ -93,12 +93,17 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
<p>{booking.user?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<h4>{`${booking.responses.name}`}</h4>
|
||||
<p>{`${booking.responses.email}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
{booking.attendees.map((attendee, i) => {
|
||||
return (
|
||||
<div key={`${i}-${attendee.name}`}>
|
||||
<br />
|
||||
<div>
|
||||
<h4>{`${attendee.name}`}</h4>
|
||||
<p>{`${attendee.email}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{!!booking.location && booking.location.startsWith("http") && (
|
||||
|
||||
@@ -11,10 +11,12 @@ const inter = Inter({ subsets: ["latin"] });
|
||||
export default function Bookings(props: { calUsername: string; calEmail: string }) {
|
||||
const [bookingTitle, setBookingTitle] = useState<string | null>(null);
|
||||
const [eventTypeSlug, setEventTypeSlug] = useState<string | null>(null);
|
||||
const [eventTypeDuration, setEventTypeDuration] = useState<number | null>(null);
|
||||
const router = useRouter();
|
||||
const { isLoading: isLoadingEvents, data: eventTypes } = useEventTypesPublic(props.calUsername);
|
||||
const rescheduleUid = (router.query.rescheduleUid as string) ?? "";
|
||||
const eventTypeSlugQueryParam = (router.query.eventTypeSlug as string) ?? "";
|
||||
|
||||
return (
|
||||
<main
|
||||
className={`flex min-h-screen flex-col ${inter.className} main text-default flex min-h-full w-full flex-col items-center overflow-visible`}>
|
||||
@@ -34,7 +36,10 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => setEventTypeSlug(event.slug)}
|
||||
onClick={() => {
|
||||
setEventTypeSlug(event.slug);
|
||||
setEventTypeDuration(event.length);
|
||||
}}
|
||||
className="mx-10 w-[80vw] cursor-pointer rounded-md border-[0.8px] border-black px-10 py-4"
|
||||
key={event.id}>
|
||||
<h1 className="text-lg font-semibold">{formatEventSlug}</h1>
|
||||
@@ -51,9 +56,23 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
eventSlug={eventTypeSlug}
|
||||
username={props.calUsername ?? ""}
|
||||
onCreateBookingSuccess={(data) => {
|
||||
setBookingTitle(data.data.title);
|
||||
setBookingTitle(data.data.title ?? "");
|
||||
router.push(`/${data.data.uid}`);
|
||||
}}
|
||||
duration={eventTypeDuration}
|
||||
customClassNames={{
|
||||
bookerContainer: "!bg-[#F5F2FE] [&_button:!rounded-full] border-subtle border",
|
||||
datePickerCustomClassNames: {
|
||||
datePickerDatesActive: "!bg-[#D7CEF5]",
|
||||
},
|
||||
eventMetaCustomClassNames: {
|
||||
eventMetaTitle: "text-[#7151DC]",
|
||||
},
|
||||
availableTimeSlotsCustomClassNames: {
|
||||
availableTimeSlotsHeaderContainer: "!bg-[#F5F2FE]",
|
||||
availableTimes: "!bg-[#D7CEF5]",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!bookingTitle && rescheduleUid && eventTypeSlugQueryParam && (
|
||||
@@ -62,9 +81,14 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
eventSlug={eventTypeSlugQueryParam}
|
||||
username={props.calUsername ?? ""}
|
||||
onCreateBookingSuccess={(data) => {
|
||||
setBookingTitle(data.data.title);
|
||||
setBookingTitle(data.data.title ?? "");
|
||||
router.push(`/${data.data.uid}`);
|
||||
}}
|
||||
duration={eventTypeDuration}
|
||||
entity={{
|
||||
orgSlug: "ecorp",
|
||||
considerUnpublished: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{bookingTitle && <p>Booking created: {bookingTitle}</p>}
|
||||
|
||||
@@ -30,6 +30,8 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
<h1 className="my-4 text-2xl font-semibold">{props.calUsername} Bookings</h1>
|
||||
{isLoading && <p>Loading...</p>}
|
||||
{!isLoading &&
|
||||
pastBookings?.bookings &&
|
||||
upcomingBookings?.bookings &&
|
||||
(Boolean(upcomingBookings?.bookings.length) || Boolean(pastBookings?.bookings.length)) &&
|
||||
[...pastBookings?.bookings, ...upcomingBookings?.bookings].map((booking) => {
|
||||
const date = dayjs(booking.startTime).toDate();
|
||||
@@ -62,7 +64,7 @@ export default function Bookings(props: { calUsername: string; calEmail: string
|
||||
</div>
|
||||
<div className="px-6">
|
||||
<p>
|
||||
{booking.user.name} and {booking.attendees[0].name}
|
||||
{booking?.user?.name} and {booking.attendees[0].name}
|
||||
</p>{" "}
|
||||
<p />
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,10 @@ export class GetAvailableSlotsInput {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
timeZone?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
orgSlug?: string;
|
||||
}
|
||||
|
||||
export class RemoveSelectedSlotInput {
|
||||
|
||||
@@ -228,6 +228,7 @@ export const bookingCreateBodySchema = z.object({
|
||||
hasHashedBookingLink: z.boolean().optional(),
|
||||
hashedLink: z.string().nullish(),
|
||||
seatReferenceUid: z.string().optional(),
|
||||
orgSlug: z.string().optional(),
|
||||
});
|
||||
|
||||
export const requiredCustomInputSchema = z.union([
|
||||
|
||||
Reference in new issue
Block a user