perf: Speed up User Type booking page (#20516)

* refactor [user]/[type] server logic

* refactor useSlot

* refactor

* fix

* fix OG images

* fix
This commit is contained in:
Benny Joo authored and GitHub committed 2025-04-03 22:13:53 -03:00
1 parent 3b945ba439
commit 19a0f968ca
7 files changed
+53 -78

No files matched your search

@@ -25,7 +25,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
title,
profile: { name: profileName, image: profileImage },
users: [
...(eventData?.users || []).map((user) => ({
...(eventData?.subsetOfUsers || []).map((user) => ({
name: `${user.name}`,
username: `${user.username}`,
})),
@@ -30,7 +30,11 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
title,
profile: { name: profileName, image: profileImage },
users: [
...(eventData?.users || []).map((user) => ({
...(
(eventData as UserTypePageProps["eventData"])?.subsetOfUsers ??
(eventData as TeamTypePageProps["eventData"])?.users ??
[]
).map((user) => ({
name: `${user.name}`,
username: `${user.username}`,
})),
@@ -35,6 +35,7 @@ function Type({ slug, user, isEmbed, booking, isBrandingHidden, eventData, orgBa
eventSlug={slug}
bookingData={booking}
hideBranding={isBrandingHidden}
eventData={eventData}
entity={{ ...eventData.entity, eventTypeId: eventData?.id }}
durationConfig={eventData.metadata?.multipleDuration}
orgBannerUrl={orgBannerUrl}
@@ -1,4 +1,3 @@
import { createRouterCaller } from "app/_trpc/context";
import { type GetServerSidePropsContext } from "next";
import type { Session } from "next-auth";
import { z } from "zod";
@@ -9,33 +8,16 @@ import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import type { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import { EventRepository } from "@calcom/lib/server/repository/event";
import { UserRepository } from "@calcom/lib/server/repository/user";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { RedirectType } from "@calcom/prisma/client";
import { publicViewerRouter } from "@calcom/trpc/server/routers/publicViewer/_router";
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
type Props = {
eventData: Omit<
Pick<
NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>,
"id" | "length" | "metadata" | "entity" | "profile" | "title" | "subsetOfUsers" | "hidden"
>,
"profile" | "subsetOfUsers"
> & {
profile: {
image: string | undefined;
name: string | null;
username: string | null;
};
users: {
username: string;
name: string;
}[];
};
eventData: NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>;
booking?: GetBookingType;
rescheduleUid: string | null;
bookingUid: string | null;
@@ -144,13 +126,16 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const caller = await createRouterCaller(publicViewerRouter);
const eventData = await caller.event({
username: usernames.join("+"),
eventSlug: slug,
org,
fromRedirectOfNonOrgLink: context.query.orgRedirection === "true",
});
const eventData = await EventRepository.getPublicEvent(
{
username: usernames.join("+"),
eventSlug: slug,
org,
fromRedirectOfNonOrgLink: context.query.orgRedirection === "true",
},
session?.user?.id
);
if (!eventData) {
return {
@@ -160,24 +145,11 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
const props: Props = {
eventData: {
id: eventData.id,
entity: eventData.entity,
length: eventData.length,
...eventData,
metadata: {
...eventData.metadata,
multipleDuration: [15, 30, 45, 60, 90],
},
profile: {
image: eventData.profile.image,
name: eventData.profile.name ?? null,
username: eventData.profile.username ?? null,
},
title: eventData.title,
users: eventData.subsetOfUsers.map((user) => ({
username: user.username ?? "",
name: user.name ?? "",
})),
hidden: eventData.hidden,
},
user: usernames.join("+"),
slug,
@@ -238,13 +210,15 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
const org = isValidOrgDomain ? currentOrgDomain : null;
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we can show a 404 otherwise.
const caller = await createRouterCaller(publicViewerRouter);
const eventData = await caller.event({
username,
eventSlug: slug,
org,
fromRedirectOfNonOrgLink: context.query.orgRedirection === "true",
});
const eventData = await EventRepository.getPublicEvent(
{
username,
eventSlug: slug,
org,
fromRedirectOfNonOrgLink: context.query.orgRedirection === "true",
},
session?.user?.id
);
if (!eventData) {
return {
@@ -259,23 +233,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
: user?.allowSEOIndexing;
const props: Props = {
eventData: {
id: eventData.id,
entity: eventData.entity,
length: eventData.length,
metadata: eventData.metadata,
profile: {
image: eventData.profile.image,
name: eventData.profile.name ?? null,
username: eventData.profile.username ?? null,
},
title: eventData.title,
users: eventData.subsetOfUsers.map((user) => ({
username: user.username ?? "",
name: user.name ?? "",
})),
hidden: eventData.hidden,
},
eventData: eventData,
user: username,
slug,
isBrandingHidden: user?.hideBranding,
@@ -5,7 +5,6 @@ import dayjs from "@calcom/dayjs";
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
import { useSlotReservationId } from "@calcom/features/bookings/Booker/useSlotReservationId";
import { isBookingDryRun } from "@calcom/features/bookings/Booker/utils/isBookingDryRun";
import type { BookerEvent } from "@calcom/features/bookings/types";
import {
MINUTES_TO_BOOK,
PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS,
@@ -78,7 +77,7 @@ const useQuickAvailabilityChecks = ({
export type UseSlotsReturnType = ReturnType<typeof useSlots>;
export const useSlots = (event: { data?: Pick<BookerEvent, "id" | "length"> | null }) => {
export const useSlots = (event: { id: number; length: number } | null) => {
const selectedDuration = useBookerStore((state) => state.selectedDuration);
const searchParams = useCompatSearchParams();
const [selectedTimeslot, setSelectedTimeslot, tentativeSelectedTimeslots, setTentativeSelectedTimeslots] =
@@ -107,13 +106,13 @@ export const useSlots = (event: { data?: Pick<BookerEvent, "id" | "length"> | nu
});
const handleRemoveSlot = () => {
if (event?.data) {
if (event?.id && slotReservationId) {
removeSelectedSlot.mutate({ uid: slotReservationId });
}
};
const eventTypeId = event.data?.id;
const eventDuration = selectedDuration || event.data?.length || 0;
const eventTypeId = event?.id;
const eventDuration = selectedDuration || event?.length || 0;
const allSelectedTimeslots = [...tentativeSelectedTimeslots, selectedTimeslot].filter(
(slot): slot is string => slot !== null
);
@@ -153,7 +152,7 @@ export const useSlots = (event: { data?: Pick<BookerEvent, "id" | "length"> | nu
clearInterval(interval);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [event?.data?.id, timeSlotToBeBooked]);
}, [event?.id, timeSlotToBeBooked]);
return {
setSelectedTimeslot,
@@ -18,7 +18,7 @@ export type useScheduleForEventReturnType = ReturnType<typeof useScheduleForEven
* Using this hook means you only need to use one hook, instead
* of combining multiple conditional hooks.
*/
export const useEvent = (props?: { fromRedirectOfNonOrgLink?: boolean }) => {
export const useEvent = (props?: { fromRedirectOfNonOrgLink?: boolean; disabled?: boolean }) => {
const [username, eventSlug, isTeamEvent, org] = useBookerStore(
(state) => [state.username, state.eventSlug, state.isTeamEvent, state.org],
shallow
@@ -34,7 +34,7 @@ export const useEvent = (props?: { fromRedirectOfNonOrgLink?: boolean }) => {
},
{
refetchOnWindowFocus: false,
enabled: Boolean(username) && Boolean(eventSlug),
enabled: !props?.disabled && Boolean(username) && Boolean(eventSlug),
}
);
@@ -22,19 +22,32 @@ import { useBookerStore, useInitializeBookerStore } from "@calcom/features/booki
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
import { getLastBookingResponse } from "@calcom/features/bookings/Booker/utils/lastBookingResponse";
import { useBrandColors } from "@calcom/features/bookings/Booker/utils/use-brand-colors";
import type { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR, WEBAPP_URL } from "@calcom/lib/constants";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
type BookerWebWrapperAtomProps = BookerProps;
type BookerWebWrapperAtomProps = BookerProps & {
eventData?: NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>;
};
export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const event = useEvent({
const clientFetchedEvent = useEvent({
disabled: !!props.eventData,
fromRedirectOfNonOrgLink: props.entity.fromRedirectOfNonOrgLink,
});
const event = props.eventData
? {
data: props.eventData,
isSuccess: true,
isError: false,
isPending: false,
}
: clientFetchedEvent;
const bookerLayout = useBookerLayout(event.data);
const selectedDate = searchParams?.get("date");
@@ -110,7 +123,7 @@ export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
requiresBookerEmailVerification: event?.data?.requiresBookerEmailVerification,
onVerifyEmail: bookerForm.beforeVerifyEmail,
});
const slots = useSlots(event);
const slots = useSlots(event?.data ? { id: event.data.id, length: event.data.length } : null);
const isEmbed = useIsEmbed();