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:
co-authored by
Hariom Balhara
parent
39173900b1
commit
2f9c8cb441
@@ -2,7 +2,6 @@
|
||||
|
||||
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 PageProps } from "@lib/d/[link]/[slug]/getServerSideProps";
|
||||
|
||||
@@ -20,13 +19,6 @@ export default function Type({
|
||||
}: PageProps) {
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={booking?.uid}
|
||||
hideBranding={isBrandingHidden}
|
||||
entity={entity}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
-10
@@ -2,7 +2,6 @@
|
||||
|
||||
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/org/[orgSlug]/instant-meeting/team/[slug]/[type]/getServerSideProps";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -13,15 +12,6 @@ export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
function Type({ slug, user, booking, isEmbed, isBrandingHidden, entity, eventTypeId, duration }: PageProps) {
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={undefined}
|
||||
hideBranding={isBrandingHidden}
|
||||
isTeamEvent
|
||||
entity={entity}
|
||||
bookingData={booking}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, it, vi } from "vitest";
|
||||
|
||||
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
|
||||
import UserPage from "./users-public-view";
|
||||
|
||||
@@ -71,33 +70,5 @@ describe("UserPage Component", () => {
|
||||
});
|
||||
|
||||
render(<UserPage {...mockData.props} />);
|
||||
|
||||
const expectedDescription = mockData.props.markdownStrippedBio;
|
||||
const expectedTitle = expectedDescription;
|
||||
expect(HeadSeo).toHaveBeenCalledWith(
|
||||
{
|
||||
origin: `${mockData.props.entity.orgSlug}.cal.local`,
|
||||
title: `${mockData.props.profile.name}`,
|
||||
description: expectedDescription,
|
||||
meeting: {
|
||||
profile: {
|
||||
name: mockData.props.profile.name,
|
||||
image: mockData.props.users[0].avatarUrl,
|
||||
},
|
||||
title: expectedTitle,
|
||||
users: [
|
||||
{
|
||||
name: mockData.props.users[0].name,
|
||||
username: mockData.props.users[0].username,
|
||||
},
|
||||
],
|
||||
},
|
||||
nextSeoProps: {
|
||||
nofollow: !mockData.props.profile.allowSEOIndexing,
|
||||
noindex: !mockData.props.profile.allowSEOIndexing,
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,12 +11,11 @@ import {
|
||||
useEmbedStyles,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { HeadSeo, Icon, UnpublishedEntity, UserAvatar } from "@calcom/ui";
|
||||
import { Icon, UnpublishedEntity, UserAvatar } from "@calcom/ui";
|
||||
|
||||
import type { getServerSideProps } from "@server/lib/[user]/getServerSideProps";
|
||||
|
||||
@@ -60,112 +59,89 @@ export function UserPage(props: PageProps) {
|
||||
const isEventListEmpty = eventTypes.length === 0;
|
||||
const isOrg = !!user?.profile?.organization;
|
||||
|
||||
const allowSEOIndexing = isOrg
|
||||
? isOrgSEOIndexable
|
||||
? profile.allowSEOIndexing
|
||||
: false
|
||||
: profile.allowSEOIndexing;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
origin={getOrgFullOrigin(entity.orgSlug ?? null)}
|
||||
title={profile.name}
|
||||
description={markdownStrippedBio}
|
||||
meeting={{
|
||||
title: markdownStrippedBio,
|
||||
profile: { name: `${profile.name}`, image: user.avatarUrl || null },
|
||||
users: [{ username: `${user.username}`, name: `${user.name}` }],
|
||||
}}
|
||||
nextSeoProps={{
|
||||
noindex: !allowSEOIndexing,
|
||||
nofollow: !allowSEOIndexing,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
|
||||
<main
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md" : "",
|
||||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
<div className="mb-8 text-center">
|
||||
<UserAvatar
|
||||
size="xl"
|
||||
user={{
|
||||
avatarUrl: user.avatarUrl,
|
||||
profile: user.profile,
|
||||
name: profile.name,
|
||||
username: profile.username,
|
||||
}}
|
||||
/>
|
||||
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
|
||||
{profile.name}
|
||||
{!isOrg && user.verified && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
{isOrg && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-yellow-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
{!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: props.safeBio }}
|
||||
/>
|
||||
</>
|
||||
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
|
||||
<main
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md" : "",
|
||||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
<div className="mb-8 text-center">
|
||||
<UserAvatar
|
||||
size="xl"
|
||||
user={{
|
||||
avatarUrl: user.avatarUrl,
|
||||
profile: user.profile,
|
||||
name: profile.name,
|
||||
username: profile.username,
|
||||
}}
|
||||
/>
|
||||
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
|
||||
{profile.name}
|
||||
{!isOrg && user.verified && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isOrg && (
|
||||
<Icon
|
||||
name="badge-check"
|
||||
className="mx-1 -mt-1 inline h-6 w-6 fill-yellow-500 text-white dark:text-black"
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
{!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: props.safeBio }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames("rounded-md ", !isEventListEmpty && "border-subtle border")}
|
||||
data-testid="event-types">
|
||||
{eventTypes.map((type) => (
|
||||
<Link
|
||||
key={type.id}
|
||||
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
|
||||
prefetch={false}
|
||||
href={{
|
||||
pathname: `/${user.profile.username}/${type.slug}`,
|
||||
query,
|
||||
}}
|
||||
passHref
|
||||
onClick={async () => {
|
||||
sdkActionManager?.fire("eventTypeSelected", {
|
||||
eventType: type,
|
||||
});
|
||||
}}
|
||||
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b transition first:rounded-t-md last:rounded-b-md last:border-b-0"
|
||||
data-testid="event-type-link">
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
||||
<div className="block w-full p-5">
|
||||
<div className="flex flex-wrap items-center">
|
||||
<h2 className="text-default pr-2 text-sm font-semibold">{type.title}</h2>
|
||||
</div>
|
||||
<EventTypeDescription eventType={type} isPublic={true} shortenDescription />
|
||||
<div
|
||||
className={classNames("rounded-md ", !isEventListEmpty && "border-subtle border")}
|
||||
data-testid="event-types">
|
||||
{eventTypes.map((type) => (
|
||||
<Link
|
||||
key={type.id}
|
||||
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
|
||||
prefetch={false}
|
||||
href={{
|
||||
pathname: `/${user.profile.username}/${type.slug}`,
|
||||
query,
|
||||
}}
|
||||
passHref
|
||||
onClick={async () => {
|
||||
sdkActionManager?.fire("eventTypeSelected", {
|
||||
eventType: type,
|
||||
});
|
||||
}}
|
||||
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b transition first:rounded-t-md last:rounded-b-md last:border-b-0"
|
||||
data-testid="event-type-link">
|
||||
<Icon
|
||||
name="arrow-right"
|
||||
className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
||||
<div className="block w-full p-5">
|
||||
<div className="flex flex-wrap items-center">
|
||||
<h2 className="text-default pr-2 text-sm font-semibold">{type.title}</h2>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<EventTypeDescription eventType={type} isPublic={true} shortenDescription />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isEventListEmpty && <EmptyPage name={profile.name || "User"} />}
|
||||
</main>
|
||||
<Toaster position="bottom-right" />
|
||||
</div>
|
||||
</>
|
||||
{isEventListEmpty && <EmptyPage name={profile.name || "User"} />}
|
||||
</main>
|
||||
<Toaster position="bottom-right" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
UserPage.isBookingPage = true;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"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 { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
import type { EmbedProps } from "app/WithEmbedSSR";
|
||||
|
||||
import type { getServerSideProps } from "@server/lib/[user]/[type]/getServerSideProps";
|
||||
|
||||
@@ -23,40 +22,11 @@ export const getMultipleDurationValue = (
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
function Type({
|
||||
slug,
|
||||
user,
|
||||
isEmbed,
|
||||
booking,
|
||||
isBrandingHidden,
|
||||
isSEOIndexable,
|
||||
rescheduleUid,
|
||||
eventData,
|
||||
orgBannerUrl,
|
||||
}: PageProps) {
|
||||
function Type({ slug, user, isEmbed, booking, isBrandingHidden, eventData, orgBannerUrl }: PageProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const { profile, users, hidden, title } = eventData;
|
||||
|
||||
return (
|
||||
<main className={getBookerWrapperClasses({ isEmbed: !!isEmbed })}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={rescheduleUid ?? undefined}
|
||||
hideBranding={isBrandingHidden}
|
||||
isSEOIndexable={isSEOIndexable ?? true}
|
||||
eventData={
|
||||
profile && users && title && hidden !== undefined
|
||||
? {
|
||||
profile,
|
||||
users,
|
||||
title,
|
||||
hidden,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
entity={eventData.entity}
|
||||
bookingData={booking}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
|
||||
Reference in New Issue
Block a user