perf: no wait for session when calling getschedule 10552 cal 2311 (#10607)
* No batching on getting session * Fix usePublicPage hook to use new router search params * Move things so getSchedule data can be load as soon as we are rendering BookerComponent * pre fetch session * Removed custom code in favour of useTimePreferences --------- Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
parent
bee5011ec1
commit
fcd892bfa0
|
@ -18,7 +18,7 @@ import { useFlags } from "@calcom/features/flags/hooks";
|
|||
import { trpc } from "@calcom/trpc/react";
|
||||
import { MetaProvider } from "@calcom/ui";
|
||||
|
||||
import usePublicPage from "@lib/hooks/usePublicPage";
|
||||
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
|
||||
import type { WithNonceProps } from "@lib/withNonce";
|
||||
|
||||
import { useViewerI18n } from "@components/I18nLanguageHandler";
|
||||
|
@ -247,7 +247,7 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
|
|||
|
||||
const AppProviders = (props: AppPropsWithChildren) => {
|
||||
// No need to have intercom on public pages - Good for Page Performance
|
||||
const isPublicPage = usePublicPage();
|
||||
const isBookingPage = useIsBookingPage();
|
||||
const { pageProps, ...rest } = props;
|
||||
const { _nonce, ...restPageProps } = pageProps;
|
||||
const propsWithoutNonce = {
|
||||
|
@ -267,7 +267,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
|||
themeBasis={props.pageProps.themeBasis}
|
||||
nonce={props.pageProps.nonce}
|
||||
isThemeSupported={props.Component.isThemeSupported}
|
||||
isBookingPage={props.Component.isBookingPage}
|
||||
isBookingPage={props.Component.isBookingPage || isBookingPage}
|
||||
router={props.router}>
|
||||
<FeatureFlagsProvider>
|
||||
<OrgBrandProvider>
|
||||
|
@ -281,7 +281,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
|||
</EventCollectionProvider>
|
||||
);
|
||||
|
||||
if (isPublicPage) {
|
||||
if (isBookingPage) {
|
||||
return RemainingProviders;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
|
||||
export default function useIsBookingPage() {
|
||||
const pathname = usePathname();
|
||||
const isBookingPage = ["/booking", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route));
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const userParam = searchParams.get("user");
|
||||
const teamParam = searchParams.get("team");
|
||||
|
||||
return !!(isBookingPage || userParam || teamParam);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { usePathname } from "next/navigation";
|
||||
|
||||
export default function usePublicPage() {
|
||||
const pathname = usePathname();
|
||||
const isPublicPage = ["/[user]", "/booking", "/cancel", "/reschedule"].find((route) =>
|
||||
pathname?.startsWith(route)
|
||||
);
|
||||
return isPublicPage;
|
||||
}
|
|
@ -316,8 +316,6 @@ async function runTestStepsCommonForTeamAndUserEventType(
|
|||
await test.step("Do a reschedule and notice that we can't book without giving a value for rescheduleReason", async () => {
|
||||
const page = previewTabPage;
|
||||
await rescheduleFromTheLinkOnPage({ page });
|
||||
// eslint-disable-next-line playwright/no-page-pause
|
||||
await page.pause();
|
||||
await expectErrorToBeThereFor({ page, name: "rescheduleReason" });
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,5 +31,7 @@ export async function ssrInit(context: GetServerSidePropsContext) {
|
|||
// Provides a better UX to the users who have already upgraded.
|
||||
await ssr.viewer.teams.hasTeamPlan.prefetch();
|
||||
|
||||
await ssr.viewer.public.session.prefetch();
|
||||
|
||||
return ssr;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Away, NotFound } from "./components/Unavailable";
|
|||
import { extraDaysConfig, fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config";
|
||||
import { useBookerStore, useInitializeBookerStore } from "./store";
|
||||
import type { BookerLayout, BookerProps } from "./types";
|
||||
import { useEvent } from "./utils/event";
|
||||
import { useEvent, useScheduleForEvent } from "./utils/event";
|
||||
import { validateLayout } from "./utils/layout";
|
||||
import { getQueryParam } from "./utils/query-param";
|
||||
import { useBrandColors } from "./utils/use-brand-colors";
|
||||
|
@ -41,6 +41,17 @@ const BookerComponent = ({
|
|||
isTeamEvent,
|
||||
entity,
|
||||
}: BookerProps) => {
|
||||
/**
|
||||
* Prioritize dateSchedule load
|
||||
* Component will render but use data already fetched from here, and no duplicate requests will be made
|
||||
* */
|
||||
useScheduleForEvent({
|
||||
prefetchNextMonth: false,
|
||||
username,
|
||||
eventSlug,
|
||||
month,
|
||||
duration: undefined,
|
||||
});
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const isTablet = useMediaQuery("(max-width: 1024px)");
|
||||
const timeslotsRef = useRef<HTMLDivElement>(null);
|
||||
|
|
|
@ -27,7 +27,8 @@ export const useEvent = () => {
|
|||
|
||||
/**
|
||||
* Gets schedule for the current event and current month.
|
||||
* Gets all values from the booker store.
|
||||
* Gets all values right away and not the store because it increases network timing, only for the first render.
|
||||
* We can read from the store if we want to get the latest values.
|
||||
*
|
||||
* Using this hook means you only need to use one hook, instead
|
||||
* of combining multiple conditional hooks.
|
||||
|
@ -36,21 +37,35 @@ export const useEvent = () => {
|
|||
* useful when the user is viewing dates near the end of the month,
|
||||
* this way the multi day view will show data of both months.
|
||||
*/
|
||||
export const useScheduleForEvent = ({ prefetchNextMonth }: { prefetchNextMonth?: boolean } = {}) => {
|
||||
export const useScheduleForEvent = ({
|
||||
prefetchNextMonth,
|
||||
username,
|
||||
eventSlug,
|
||||
eventId,
|
||||
month,
|
||||
duration,
|
||||
}: {
|
||||
prefetchNextMonth?: boolean;
|
||||
username?: string | null;
|
||||
eventSlug?: string | null;
|
||||
eventId?: number | null;
|
||||
month?: string | null;
|
||||
duration?: number | null;
|
||||
} = {}) => {
|
||||
const { timezone } = useTimePreferences();
|
||||
const event = useEvent();
|
||||
const [username, eventSlug, month, duration] = useBookerStore(
|
||||
const [usernameFromStore, eventSlugFromStore, monthFromStore, durationFromStore] = useBookerStore(
|
||||
(state) => [state.username, state.eventSlug, state.month, state.selectedDuration],
|
||||
shallow
|
||||
);
|
||||
|
||||
return useSchedule({
|
||||
username,
|
||||
eventSlug,
|
||||
eventId: event.data?.id,
|
||||
month,
|
||||
username: usernameFromStore ?? username,
|
||||
eventSlug: eventSlugFromStore ?? eventSlug,
|
||||
eventId: event.data?.id ?? eventId,
|
||||
timezone,
|
||||
prefetchNextMonth,
|
||||
duration,
|
||||
month: monthFromStore ?? month,
|
||||
duration: durationFromStore ?? duration,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -29,24 +29,31 @@ export const useSchedule = ({
|
|||
return trpc.viewer.public.slots.getSchedule.useQuery(
|
||||
{
|
||||
usernameList: getUsernameList(username ?? ""),
|
||||
eventTypeSlug: eventSlug!,
|
||||
// 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: monthDayjs.startOf("month").toISOString(),
|
||||
// if `prefetchNextMonth` is true, two months are fetched at once.
|
||||
endTime: (prefetchNextMonth ? nextMonthDayjs : monthDayjs).endOf("month").toISOString(),
|
||||
timeZone: timezone!,
|
||||
eventTypeId: eventId!,
|
||||
duration: duration ? `${duration}` : undefined,
|
||||
},
|
||||
{
|
||||
trpc: {
|
||||
context: {
|
||||
skipBatch: true,
|
||||
},
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
enabled:
|
||||
Boolean(username) &&
|
||||
Boolean(eventSlug) &&
|
||||
(Boolean(eventId) || eventId === 0) &&
|
||||
Boolean(month) &&
|
||||
Boolean(timezone),
|
||||
Boolean(timezone) &&
|
||||
// Should only wait for one or the other, not both.
|
||||
(Boolean(eventSlug) || Boolean(eventId) || eventId === 0),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -70,9 +70,46 @@ export const checkIfIsAvailable = ({
|
|||
};
|
||||
|
||||
export async function getEventType(input: TGetScheduleInputSchema) {
|
||||
const { eventTypeSlug, usernameList } = input;
|
||||
let eventTypeId = input.eventTypeId;
|
||||
|
||||
if (eventTypeId === undefined && eventTypeSlug && usernameList && usernameList.length === 1) {
|
||||
// If we only have the slug and usernameList, we need to get the id first
|
||||
const username = usernameList[0];
|
||||
const userId = await getUserIdFromUsername(username);
|
||||
let teamId;
|
||||
|
||||
if (!userId) {
|
||||
teamId = await getTeamIdFromSlug(username);
|
||||
if (!teamId) {
|
||||
throw new TRPCError({
|
||||
message: "User or team not found",
|
||||
code: "NOT_FOUND",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
where: {
|
||||
slug: eventTypeSlug,
|
||||
...(teamId ? { teamId } : {}),
|
||||
...(userId ? { userId } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!eventType) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
|
||||
eventTypeId = eventType.id;
|
||||
}
|
||||
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: input.eventTypeId,
|
||||
id: eventTypeId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -175,7 +212,7 @@ export async function getDynamicEventType(input: TGetScheduleInputSchema) {
|
|||
}
|
||||
|
||||
export function getRegularOrDynamicEventType(input: TGetScheduleInputSchema) {
|
||||
const isDynamicBooking = !input.eventTypeId;
|
||||
const isDynamicBooking = input.usernameList && input.usernameList.length > 1;
|
||||
return isDynamicBooking ? getDynamicEventType(input) : getEventType(input);
|
||||
}
|
||||
|
||||
|
@ -237,7 +274,7 @@ export async function getAvailableSlots(input: TGetScheduleInputSchema) {
|
|||
username: currentUser.username || "",
|
||||
dateFrom: startTime.format(),
|
||||
dateTo: endTime.format(),
|
||||
eventTypeId: input.eventTypeId,
|
||||
eventTypeId: eventType.id,
|
||||
afterEventBuffer: eventType.afterEventBuffer,
|
||||
beforeEventBuffer: eventType.beforeEventBuffer,
|
||||
duration: input.duration || 0,
|
||||
|
@ -446,3 +483,27 @@ export async function getAvailableSlots(input: TGetScheduleInputSchema) {
|
|||
slots: computedAvailableSlots,
|
||||
};
|
||||
}
|
||||
|
||||
async function getUserIdFromUsername(username: string) {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
return user?.id;
|
||||
}
|
||||
|
||||
async function getTeamIdFromSlug(slug: string) {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
return team?.id;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user