chore: app router - /team, /org, /[user] booking pages (excl. embeds) (#18186)

* update env vars

* update middleware

* remove pages router and move pages to /app

* move to /team

* update imports

* fix

* remove pages router and move org pages to /app

* wip

* fix orgSlug/user pages

* fix orgSlug/user/type pages

* remove generateMetadata from embed pages

* fix

* remove pages router for /user pages

* generateMetadata is not needed in embed pages

* remove future/org

* no layout in [user] page

* simplify

* fix

* fix OG image for [user]

* fix OG images for org/user and team/slug

* fix OG images for booking page

* fix all metadata

* use isBrandingHidden

* remove BookerSeo and its usages

* rename excludeAppNameFromTitle -> hideBranding

* remove logic for meeting type in HeadSeo

* remove BookerSeo instances from team-view and users-public-view

* create generateMeetingMetadata util and use it to reduce duplicate code

* remove BookerSeo imports

* fix spacing

* remove constructMeetingImage mock from head-seo.test

* fetch avatarUrl using user id for user page metadata

* fix test

* remove unused test cases

* index and follow must be true by default

* invert noindex/nofollow flags

* remove HeadSeo for already migrated pages and refactor prepareMetadata

* fix organization-settings.e2e.ts

* fix order

* enable parallel test execution for dynamic-booking e2e test

* fix

* + could be %2B in app router

* refactor handling logic for embeds in app router

* fix isEmbed

* fix embed-core

* remove dead code

* move embed pages back to /future

* add back embed pages in pages router

* revert some changes

* fix import type checks

* simplify

* fix

* feat: Implement generateBookingPageMetadata function for improved SEO and metadata handling across user and event pages (#18440)

- Added a new utility function `generateBookingPageMetadata` to streamline the generation of metadata for booking and user profile pages.
- Updated multiple page components to utilize the new function, enhancing SEO indexing and metadata consistency.
- Removed redundant code related to previous metadata generation methods, improving code clarity and maintainability.

* fix dirs

* Pr-review-fixes-app-router-team-pages (#18450)

* Remove unnecessary getPublicEvent call

* Remove unused variable

* Fix 404 for team page (#18451)

* Remove console log

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Benny Joo
2025-01-03 22:26:30 +00:00
committed by GitHub
co-authored by Hariom Balhara
parent 39173900b1
commit 2f9c8cb441
60 changed files with 651 additions and 825 deletions
+78 -97
View File
@@ -12,7 +12,6 @@ import { usePathname } from "next/navigation";
import { useEffect } from "react";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
import { getOrgOrTeamAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -20,7 +19,7 @@ import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { Avatar, Button, HeadSeo, UnpublishedEntity, UserAvatarGroup } from "@calcom/ui";
import { Avatar, Button, UnpublishedEntity, UserAvatarGroup } from "@calcom/ui";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import type { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
@@ -164,105 +163,87 @@ function TeamPage({
const profileImageSrc = getOrgOrTeamAvatar(team);
return (
<>
<HeadSeo
origin={getOrgFullOrigin(currentOrgDomain)}
title={teamName}
description={teamName}
meeting={{
title: markdownStrippedBio,
profile: {
name: `${team.name}`,
image: profileImageSrc,
},
}}
nextSeoProps={{
nofollow: !isSEOIndexable,
noindex: !isSEOIndexable,
}}
/>
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
<div className="mx-auto mb-8 max-w-3xl text-center">
<div className="relative">
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
</div>
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
{team.parent && `${team.parent.name} `}
{teamName}
</p>
{!isBioEmpty && (
<>
<div
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: team.safeBio }}
/>
</>
)}
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
<div className="mx-auto mb-8 max-w-3xl text-center">
<div className="relative">
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
</div>
{team.isOrganization ? (
!teamOrOrgIsPrivate ? (
<SubTeams />
) : (
<div className="w-full text-center">
<h2 className="text-emphasis font-semibold">{t("you_cannot_see_teams_of_org")}</h2>
</div>
)
) : (
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
{team.parent && `${team.parent.name} `}
{teamName}
</p>
{!isBioEmpty && (
<>
{(showMembers.isOn || !team.eventTypes?.length) &&
(teamOrOrgIsPrivate ? (
<div className="w-full text-center">
<h2 data-testid="you-cannot-see-team-members" className="text-emphasis font-semibold">
{t("you_cannot_see_team_members")}
</h2>
</div>
) : (
<Team members={team.members} teamName={team.name} />
))}
{!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && (
<div className="mx-auto max-w-3xl ">
<EventTypes eventTypes={team.eventTypes} />
{/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */}
{!team.hideBookATeamMember && !teamOrOrgIsPrivate && (
<div>
<div className="relative mt-12">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="border-subtle w-full border-t" />
</div>
<div className="relative flex justify-center">
<span className="dark:bg-darkgray-50 bg-subtle text-subtle px-2 text-sm">
{t("or")}
</span>
</div>
</div>
<aside className="dark:text-inverted mt-8 flex justify-center text-center">
<Button
color="minimal"
EndIcon="arrow-right"
data-testid="book-a-team-member-btn"
className="dark:hover:bg-darkgray-200"
href={{
pathname: `${isValidOrgDomain ? "" : "/team"}/${team.slug}`,
query: {
...queryParamsToForward,
members: "1",
},
}}
shallow={true}>
{t("book_a_team_member")}
</Button>
</aside>
</div>
)}
</div>
)}
<div
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: team.safeBio }}
/>
</>
)}
</main>
</>
</div>
{team.isOrganization ? (
!teamOrOrgIsPrivate ? (
<SubTeams />
) : (
<div className="w-full text-center">
<h2 className="text-emphasis font-semibold">{t("you_cannot_see_teams_of_org")}</h2>
</div>
)
) : (
<>
{(showMembers.isOn || !team.eventTypes?.length) &&
(teamOrOrgIsPrivate ? (
<div className="w-full text-center">
<h2 data-testid="you-cannot-see-team-members" className="text-emphasis font-semibold">
{t("you_cannot_see_team_members")}
</h2>
</div>
) : (
<Team members={team.members} teamName={team.name} />
))}
{!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && (
<div className="mx-auto max-w-3xl ">
<EventTypes eventTypes={team.eventTypes} />
{/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */}
{!team.hideBookATeamMember && !teamOrOrgIsPrivate && (
<div>
<div className="relative mt-12">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="border-subtle w-full border-t" />
</div>
<div className="relative flex justify-center">
<span className="dark:bg-darkgray-50 bg-subtle text-subtle px-2 text-sm">
{t("or")}
</span>
</div>
</div>
<aside className="dark:text-inverted mt-8 flex justify-center text-center">
<Button
color="minimal"
EndIcon="arrow-right"
data-testid="book-a-team-member-btn"
className="dark:hover:bg-darkgray-200"
href={{
pathname: `${isValidOrgDomain ? "" : "/team"}/${team.slug}`,
query: {
...queryParamsToForward,
members: "1",
},
}}
shallow={true}>
{t("book_a_team_member")}
</Button>
</aside>
</div>
)}
</div>
)}
</>
)}
</main>
);
}
+1 -24
View File
@@ -1,14 +1,13 @@
"use client";
import type { EmbedProps } from "app/WithEmbedSSR";
import { useSearchParams } from "next/navigation";
import { Booker } from "@calcom/atoms/monorepo";
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import type { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "app/WithEmbedSSR";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
@@ -34,33 +33,11 @@ function Type({
teamMemberEmail,
crmOwnerRecordType,
crmAppSlug,
isSEOIndexable,
}: PageProps) {
const searchParams = useSearchParams();
const { profile, users, hidden, title } = eventData;
return (
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
<BookerSeo
username={user}
eventSlug={slug}
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
isTeamEvent
eventData={
profile && users && title && hidden !== undefined
? {
profile,
users,
title,
hidden,
}
: undefined
}
entity={eventData.entity}
bookingData={booking}
isSEOIndexable={isSEOIndexable}
/>
<Booker
username={user}
eventSlug={slug}