feat: fetch SEO data for booking pages in server (#17987)

* feat: fetch public event data for booking pages server-side

* fix types

* refactor

* Replace the getPublicEvent with other smaller calls

* Change username to teamSlug

* Fixing types

* Type checks

* Pushing latest for Benny - this still has type errors

* fix type checks

* fill data for team pages too

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
Benny Joo
2024-12-04 14:08:27 +01:00
committed by GitHub
co-authored by Keith Williams
parent 982a715c4b
commit 03704b2e73
5 changed files with 109 additions and 9 deletions
@@ -5,7 +5,11 @@ 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 { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
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";
@@ -57,6 +61,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
slug: true,
name: true,
bannerUrl: true,
logoUrl: true,
organizationSettings: {
select: {
allowSEOIndexing: true,
@@ -64,6 +69,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
},
},
logoUrl: true,
name: true,
slug: true,
eventTypes: {
@@ -72,10 +78,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
select: {
id: true,
title: true,
isInstantEvent: true,
schedulingType: true,
metadata: true,
length: true,
hidden: true,
},
},
isOrganization: true,
@@ -93,6 +101,18 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
} as const;
}
// 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.
const usernameList = getUsernameList(teamSlug);
const orgSlug = isValidOrgDomain ? currentOrgDomain : null;
const name = team.parent?.name ?? team.name ?? null;
const usersInOrgContext = await UserRepository.findUsersByUsername({
usernameList,
orgSlug: team.parent?.slug || null,
});
const eventData = team.eventTypes[0];
const eventTypeId = eventData.id;
@@ -119,6 +139,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const organizationSettings = OrganizationRepository.utils.getOrganizationSEOSettings(team);
const allowSEOIndexing = organizationSettings?.allowSEOIndexing ?? false;
if (!eventData) {
return {
notFound: true,
} as const;
}
return {
props: {
eventData: {
@@ -126,12 +152,26 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
entity: {
fromRedirectOfNonOrgLink,
considerUnpublished: isUnpublished && !fromRedirectOfNonOrgLink,
orgSlug: isValidOrgDomain ? currentOrgDomain : null,
orgSlug,
teamSlug: team.slug ?? null,
name: team.parent?.name ?? team.name ?? 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: usersInOrgContext.map((user) => ({
...user,
metadata: undefined,
bookerUrl: getBookerBaseUrlSync(user.profile?.organization?.slug ?? null),
})),
hidden: eventData.hidden,
},
booking,
user: teamSlug,
+11
View File
@@ -37,6 +37,7 @@ function Type({
isSEOIndexable,
}: PageProps) {
const searchParams = useSearchParams();
const { profile, users, hidden, title } = eventData;
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
@@ -46,6 +47,16 @@ function Type({
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
isTeamEvent
eventData={
profile && users && title && hidden !== undefined
? {
profile,
users,
title,
hidden,
}
: undefined
}
entity={eventData.entity}
bookingData={booking}
isSEOIndexable={isSEOIndexable}
@@ -35,7 +35,7 @@ function Type({
orgBannerUrl,
}: PageProps) {
const searchParams = useSearchParams();
const { profile, users, hidden, title } = eventData;
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
<BookerSeo
@@ -44,6 +44,16 @@ function Type({
rescheduleUid={rescheduleUid ?? undefined}
hideBranding={isBrandingHidden}
isSEOIndexable={isSEOIndexable ?? true}
eventData={
profile && users && title && hidden !== undefined
? {
profile,
users,
title,
hidden,
}
: undefined
}
entity={eventData.entity}
bookingData={booking}
/>
@@ -17,10 +17,20 @@ import { RedirectType } from "@calcom/prisma/client";
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
type Props = {
eventData: Pick<
NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>,
"id" | "length" | "metadata" | "entity"
>;
eventData: Omit<
Pick<
NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>,
"id" | "length" | "metadata" | "entity" | "profile" | "title" | "users" | "hidden"
>,
"profile"
> & {
profile: {
image: string | undefined;
name: string | null;
username: string | null;
};
};
booking?: GetBookingType;
rescheduleUid: string | null;
bookingUid: string | null;
@@ -143,6 +153,14 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
...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.users,
hidden: eventData.hidden,
},
user: usernames.join("+"),
slug,
@@ -231,6 +249,14 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
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.users,
hidden: eventData.hidden,
},
user: username,
slug,
@@ -1,5 +1,6 @@
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import type { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { HeadSeo } from "@calcom/ui";
@@ -11,6 +12,16 @@ interface BookerSeoProps {
hideBranding?: boolean;
isSEOIndexable?: boolean;
isTeamEvent?: boolean;
eventData?: Omit<
Pick<NonNullable<Awaited<ReturnType<typeof getPublicEvent>>>, "profile" | "title" | "users" | "hidden">,
"profile"
> & {
profile: {
image: string | undefined;
name: string | null;
username: string | null;
};
};
entity: {
fromRedirectOfNonOrgLink: boolean;
orgSlug?: string | null;
@@ -30,9 +41,10 @@ export const BookerSeo = (props: BookerSeoProps) => {
entity,
isSEOIndexable,
bookingData,
eventData,
} = props;
const { t } = useLocale();
const { data: event } = trpc.viewer.public.event.useQuery(
const { data: _event } = trpc.viewer.public.event.useQuery(
{
username,
eventSlug,
@@ -40,8 +52,9 @@ export const BookerSeo = (props: BookerSeoProps) => {
org: entity.orgSlug ?? null,
fromRedirectOfNonOrgLink: entity.fromRedirectOfNonOrgLink,
},
{ refetchOnWindowFocus: false }
{ refetchOnWindowFocus: false, enabled: !eventData }
);
const event = eventData ?? _event;
const profileName = event?.profile.name ?? "";
const profileImage = event?.profile.image;