Files
cal.diy/packages/features/schedules/lib/use-schedule/useSchedule.ts
T
22f136d19b feat: Headless router - queue recording booking response (#21805)
* Add queued booking response table

* Create `RoutingFormResponseRepository`

* Pass `queueFormResponse` param

* Queue up form response if param is passed

* Forward queued form response parma to booker

* Pass `queuedFormResponse` from booker to `handleNewBooking`

* Write queued routing form response

* Type fixes

* Clean up

* Allow dry run to work which wont have any QueuedFormResponse or FormResponse

* Support passing the time when the modal was actually shown to the user and consider that time as the time of form submission

* fix ts error

* Queue -> Response through separate endpoint that would be triggered by
embed

* Make queueResponseId a non-guessable uid

* Change queueFormResponse query param

* fix ts error

* Support useQueuedResponse to record new response data

* revert handleNewBooking

* Remove dead code formResponse

* Refactor use repository

* Unify migration files

* refactor: moved api endpoint to app dir

Signed-off-by: Omar López <zomars@me.com>

* Update formResponse.ts

* Refactor use-queued-response for test

* Add tests

* Fix ts error and unit test. recordFormResponse cant return nullish response

* fix schema

* feat: Support full reuse of preloaded iframe (#21803)

* feat: support updating cal video settings in API v2 (#21784)

* feat: support updating cal video settings in API v2

* chore: update descriptio

* feat: support create event type

* test: add test for updating event type

* test: add test for create event type

* chore: undo openapi

* chore: bump libraries

* Revert "chore: bump libraries"

This reverts commit bdf36d09b021fc531497a7b7ea66ab9c52b7d136.

* chore: bump libraries

---------

Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>

* fix tests and ts

* Fix tests

* wip-useQueuedResponseEndpoint

* Add one more test

* Change queueFormResponse query param

* wip

* Support useQueuedResponse to record new response data

* Use the update useQueuedResponse endpoint

* self-review addressed

* Use queuedResponse if available in slots/utils

* Add documentation

* Remove use-queued-response from critical-path

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>

* Update schema.prisma

* refactor: renamed to avoid react hooks confusion

Signed-off-by: Omar López <zomars@me.com>

---------

Signed-off-by: Omar López <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2025-06-17 12:11:49 -03:00

179 lines
6.5 KiB
TypeScript

import { useSearchParams } from "next/navigation";
import { updateEmbedBookerState } from "@calcom/embed-core/src/embed-iframe";
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
import { isBookingDryRun } from "@calcom/features/bookings/Booker/utils/isBookingDryRun";
import { useTimesForSchedule } from "@calcom/features/schedules/lib/use-schedule/useTimesForSchedule";
import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/getRoutedTeamMemberIdsFromSearchParams";
import { PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS } from "@calcom/lib/constants";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import { trpc } from "@calcom/trpc/react";
import { useApiV2AvailableSlots } from "./useApiV2AvailableSlots";
export type UseScheduleWithCacheArgs = {
username?: string | null;
eventSlug?: string | null;
eventId?: number | null;
month?: string | null;
timezone?: string | null;
selectedDate?: string | null;
prefetchNextMonth?: boolean;
duration?: number | null;
monthCount?: number | null;
dayCount?: number | null;
rescheduleUid?: string | null;
isTeamEvent?: boolean;
orgSlug?: string;
teamMemberEmail?: string | null;
useApiV2?: boolean;
enabled?: boolean;
};
export const useSchedule = ({
month,
timezone,
username,
eventSlug,
eventId,
selectedDate,
prefetchNextMonth,
duration,
monthCount,
dayCount,
rescheduleUid,
isTeamEvent,
orgSlug,
teamMemberEmail,
useApiV2 = false,
enabled: enabledProp = true,
}: UseScheduleWithCacheArgs) => {
const bookerState = useBookerStore((state) => state.state);
const [startTime, endTime] = useTimesForSchedule({
month,
monthCount,
dayCount,
prefetchNextMonth,
selectedDate,
});
const searchParams = useSearchParams();
const routedTeamMemberIds = searchParams
? getRoutedTeamMemberIdsFromSearchParams(new URLSearchParams(searchParams.toString()))
: null;
const skipContactOwner = searchParams ? searchParams.get("cal.skipContactOwner") === "true" : false;
const _cacheParam = searchParams?.get("cal.cache");
const _shouldServeCache = _cacheParam ? _cacheParam === "true" : undefined;
const utils = trpc.useUtils();
const routingFormResponseIdParam = searchParams?.get("cal.routingFormResponseId");
const queuedFormResponseId = searchParams?.get("cal.queuedFormResponseId");
const email = searchParams?.get("email");
// We allow skipping the schedule fetch as a requirement for prerendering in iframe through embed as when the pre-rendered iframe is connected, then we would fetch the availability, which would be upto-date
// Also, a reuse through Headless Router could completely change the availability as different team members are selected and thus it is unnecessary to fetch the schedule
const skipGetSchedule = searchParams?.get("cal.skipSlotsFetch") === "true";
const routingFormResponseId = routingFormResponseIdParam
? parseInt(routingFormResponseIdParam, 10)
: undefined;
const embedConnectVersion = searchParams?.get("cal.embed.connectVersion") || "0";
const input = {
isTeamEvent,
usernameList: getUsernameList(username ?? ""),
// Prioritize slug over id, since slug is the first value we get available.
// If we have a slug, we don't need to fetch the id.
// TODO: are queries using eventTypeId faster? Even tho we lost time fetching the id with the slug.
...(eventSlug ? { eventTypeSlug: eventSlug } : { eventTypeId: eventId ?? 0 }),
// @TODO: Old code fetched 2 days ago if we were fetching the current month.
// Do we want / need to keep that behavior?
startTime,
// if `prefetchNextMonth` is true, two months are fetched at once.
endTime,
timeZone: timezone!,
duration: duration ? `${duration}` : undefined,
rescheduleUid,
orgSlug,
teamMemberEmail,
routedTeamMemberIds,
skipContactOwner,
_shouldServeCache,
...(queuedFormResponseId ? { queuedFormResponseId } : { routingFormResponseId }),
email,
// Ensures that connectVersion causes a refresh of the data
...(embedConnectVersion ? { embedConnectVersion } : {}),
_isDryRun: searchParams ? isBookingDryRun(searchParams) : false,
};
const options = {
trpc: {
context: {
skipBatch: true,
},
},
// It allows people who might not have the tab in focus earlier, to get latest available slots
// It might not work correctly in iframes, so we have refetchInterval to take care of that.
// But where it works, it should give user latest availability even if they come back to tab before refetchInterval.
refetchOnWindowFocus: true,
// It allows long sitting users to get latest available slots
refetchInterval: PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS * 1000,
enabled:
!skipGetSchedule &&
Boolean(username) &&
Boolean(month) &&
Boolean(timezone) &&
// Should only wait for one or the other, not both.
(Boolean(eventSlug) || Boolean(eventId) || eventId === 0) &&
enabledProp,
};
const isCallingApiV2Slots = useApiV2 && Boolean(isTeamEvent) && options.enabled;
// API V2 query for team events
const teamScheduleV2 = useApiV2AvailableSlots({
...input,
enabled: isCallingApiV2Slots,
duration: input.duration ? Number(input.duration) : undefined,
routedTeamMemberIds: input.routedTeamMemberIds ?? undefined,
teamMemberEmail: input.teamMemberEmail ?? undefined,
eventTypeId: eventId ?? undefined,
});
const schedule = isTeamEvent
? trpc.viewer.highPerf.getTeamSchedule.useQuery(input, {
...options,
// Only enable if we're not using API V2 or if API V2 failed
enabled: options.enabled && (!isCallingApiV2Slots || !!teamScheduleV2.failureReason),
})
: trpc.viewer.slots.getSchedule.useQuery(input, options);
if (isCallingApiV2Slots && !teamScheduleV2.failureReason) {
updateEmbedBookerState({
bookerState,
slotsQuery: teamScheduleV2,
});
return {
...teamScheduleV2,
/**
* Invalidates the request and resends it regardless of any other configuration including staleTime
*/
invalidate: () => {
return teamScheduleV2.refetch();
},
};
}
updateEmbedBookerState({
bookerState,
slotsQuery: schedule,
});
return {
...schedule,
/**
* Invalidates the request and resends it regardless of any other configuration including staleTime
*/
invalidate: () => {
return utils.viewer.slots.getSchedule.invalidate(input);
},
};
};