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:
co-authored by
Keith Williams
parent
982a715c4b
commit
03704b2e73
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user