diff --git a/.env.example b/.env.example index 771e2b0574..c0e3a2bb30 100644 --- a/.env.example +++ b/.env.example @@ -363,6 +363,7 @@ APP_ROUTER_APPS_CATEGORIES_ENABLED=0 # whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0 APP_ROUTER_APPS_ENABLED=0 +APP_ROUTER_TEAM_ENABLED=0 APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0 APP_ROUTER_AUTH_LOGIN_ENABLED=0 APP_ROUTER_AUTH_LOGOUT_ENABLED=0 diff --git a/apps/web/abTest/middlewareFactory.ts b/apps/web/abTest/middlewareFactory.ts index 6f22156259..2793847237 100644 --- a/apps/web/abTest/middlewareFactory.ts +++ b/apps/web/abTest/middlewareFactory.ts @@ -18,6 +18,7 @@ const ROUTES: [URLPattern, boolean][] = [ ["/auth/error", process.env.APP_ROUTER_AUTH_ERROR_ENABLED === "1"] as const, ["/auth/platform/:path*", process.env.APP_ROUTER_AUTH_PLATFORM_ENABLED === "1"] as const, ["/auth/oauth2/:path*", process.env.APP_ROUTER_AUTH_OAUTH2_ENABLED === "1"] as const, + ["/team", process.env.APP_ROUTER_TEAM_ENABLED === "1"] as const, ].map(([pathname, enabled]) => [ new URLPattern({ pathname, diff --git a/apps/web/app/_utils.tsx b/apps/web/app/_utils.tsx index d51c1a128c..a487888800 100644 --- a/apps/web/app/_utils.tsx +++ b/apps/web/app/_utils.tsx @@ -3,8 +3,7 @@ import i18next from "i18next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { headers } from "next/headers"; -import type { MeetingImageProps } from "@calcom/lib/OgImages"; -import { constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages"; +import { constructGenericImage } from "@calcom/lib/OgImages"; import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG, CAL_URL } from "@calcom/lib/constants"; import { buildCanonical } from "@calcom/lib/next-seo.config"; import { truncateOnWord } from "@calcom/lib/text"; @@ -104,21 +103,3 @@ export const _generateMetadata = async ( }, }; }; - -export const generateMeetingMetadata = async ( - meeting: MeetingImageProps, - getTitle: (t: TFunction) => string, - getDescription: (t: TFunction) => string, - hideBranding?: boolean, - origin?: string -) => { - const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin); - const image = SEO_IMG_OGIMG + constructMeetingImage(meeting); - return { - ...metadata, - openGraph: { - ...metadata.openGraph, - images: [image], - }, - }; -}; diff --git a/apps/web/app/d/[link]/[slug]/page.tsx b/apps/web/app/d/[link]/[slug]/page.tsx index 73eb631fa8..0d194c6853 100644 --- a/apps/web/app/d/[link]/[slug]/page.tsx +++ b/apps/web/app/d/[link]/[slug]/page.tsx @@ -17,7 +17,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) => const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); const pageProps = await getData(legacyCtx); - const { booking, user: username, slug: eventSlug, isTeamEvent, isBrandingHidden } = pageProps; + const { booking, user: username, slug: eventSlug, isTeamEvent } = pageProps; const rescheduleUid = booking?.uid; const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req); const org = isValidOrgDomain ? currentOrgDomain : null; @@ -34,8 +34,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) => const title = event?.title ?? ""; return await _generateMetadata( (t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, - (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`, - isBrandingHidden + (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}` ); }; diff --git a/apps/web/app/[user]/[type]/page.tsx b/apps/web/app/future/[user]/[type]/page.tsx similarity index 69% rename from apps/web/app/[user]/[type]/page.tsx rename to apps/web/app/future/[user]/[type]/page.tsx index 5a55bc792f..3a61c20e5b 100644 --- a/apps/web/app/[user]/[type]/page.tsx +++ b/apps/web/app/future/[user]/[type]/page.tsx @@ -1,6 +1,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; import type { PageProps } from "app/_types"; -import { generateEventBookingPageMetadata } from "app/generateBookingPageMetadata"; +import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { headers, cookies } from "next/headers"; @@ -18,7 +18,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); const props = await getData(legacyCtx); - const { booking, user: username, slug: eventSlug, isSEOIndexable, eventData, isBrandingHidden } = props; + const { booking, user: username, slug: eventSlug } = props; const rescheduleUid = booking?.uid; const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug); @@ -31,21 +31,12 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => { }); const profileName = event?.profile?.name ?? ""; - const profileImage = event?.profile.image; const title = event?.title ?? ""; - const metadata = await generateEventBookingPageMetadata({ - event: { - hidden: event?.hidden ?? false, - title, - users: event?.users ?? [], - }, - hideBranding: isBrandingHidden, - orgSlug: eventData?.entity.orgSlug ?? null, - isSEOIndexable: !!isSEOIndexable, - profile: { name: profileName, image: profileImage ?? "" }, - isReschedule: !!rescheduleUid, - }); - return metadata; + + return await _generateMetadata( + (t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, + (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}` + ); }; const getData = withAppDirSsr(getServerSideProps); diff --git a/apps/web/app/[user]/page.tsx b/apps/web/app/future/[user]/page.tsx similarity index 53% rename from apps/web/app/[user]/page.tsx rename to apps/web/app/future/[user]/page.tsx index 6ae2fac2f6..1a05b75cd1 100644 --- a/apps/web/app/[user]/page.tsx +++ b/apps/web/app/future/[user]/page.tsx @@ -1,6 +1,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; import type { PageProps } from "app/_types"; -import { generateUserProfilePageMetadata } from "app/generateBookingPageMetadata"; +import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { headers, cookies } from "next/headers"; @@ -14,25 +14,11 @@ import LegacyPage from "~/users/views/users-public-view"; export const generateMetadata = async ({ params, searchParams }: PageProps) => { const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); - const { profile, markdownStrippedBio, isOrgSEOIndexable, entity } = props; - - const isOrg = !!profile?.organization; - const allowSEOIndexing = !!( - (!isOrg && profile.allowSEOIndexing) || - (isOrg && isOrgSEOIndexable && profile.allowSEOIndexing) + const { profile, markdownStrippedBio } = props; + return await _generateMetadata( + () => profile.name, + () => markdownStrippedBio ); - return await generateUserProfilePageMetadata({ - profile: { - name: profile.name, - image: profile.image ?? "", - username: profile.username ?? "", - markdownStrippedBio: markdownStrippedBio, - }, - event: null, - hideBranding: false, - orgSlug: entity.orgSlug ?? null, - isSEOIndexable: allowSEOIndexing, - }); }; const getData = withAppDirSsr(getServerSideProps); diff --git a/apps/web/app/future/org/[orgSlug]/[user]/[type]/embed/page.tsx b/apps/web/app/future/org/[orgSlug]/[user]/[type]/embed/page.tsx index 98819cebc6..4cbd35891f 100644 --- a/apps/web/app/future/org/[orgSlug]/[user]/[type]/embed/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/[user]/[type]/embed/page.tsx @@ -1,21 +1,10 @@ +import { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]"; +import Page from "@pages/org/[orgSlug]/[user]/[type]/embed"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; import { WithLayout } from "app/layoutHOC"; -import { type OrgTypePageProps } from "app/org/[orgSlug]/[user]/[type]/page"; import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps"; -import type { PageProps as TeamTypePageProps } from "~/team/type-view"; -import TeamTypePage from "~/team/type-view"; -import UserTypePage from "~/users/views/users-type-public-view"; -import type { PageProps as UserTypePageProps } from "~/users/views/users-type-public-view"; +const getEmbedData = withEmbedSsrAppDir(getServerSideProps); -const getEmbedData = withEmbedSsrAppDir(getServerSideProps); - -const Page = async (props: OrgTypePageProps) => { - if ((props as TeamTypePageProps)?.teamId) { - return ; - } - return ; -}; - -export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, ServerPage: Page }); +export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page }); diff --git a/apps/web/app/future/org/[orgSlug]/[user]/[type]/page.tsx b/apps/web/app/future/org/[orgSlug]/[user]/[type]/page.tsx new file mode 100644 index 0000000000..b96a381cef --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/[user]/[type]/page.tsx @@ -0,0 +1,9 @@ +import Page, { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]"; +import { withAppDirSsr } from "app/WithAppDirSsr"; +import { WithLayout } from "app/layoutHOC"; + +import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps"; + +const getData = withAppDirSsr(getServerSideProps); + +export default WithLayout({ getLayout: null, getData, Page }); diff --git a/apps/web/app/future/org/[orgSlug]/[user]/embed/page.tsx b/apps/web/app/future/org/[orgSlug]/[user]/embed/page.tsx index d90e61ee3d..02c31c9929 100644 --- a/apps/web/app/future/org/[orgSlug]/[user]/embed/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/[user]/embed/page.tsx @@ -1,21 +1,8 @@ +import { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]"; +import Page from "@pages/org/[orgSlug]/[user]/embed"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; import { WithLayout } from "app/layoutHOC"; -import { type OrgPageProps } from "app/org/[orgSlug]/[user]/page"; -import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps"; +const getEmbedData = withEmbedSsrAppDir(getServerSideProps); -import type { PageProps as TeamPageProps } from "~/team/team-view"; -import TeamPage from "~/team/team-view"; -import UserPage from "~/users/views/users-public-view"; -import type { PageProps as UserPageProps } from "~/users/views/users-public-view"; - -const getEmbedData = withEmbedSsrAppDir(getServerSideProps); - -const Page = async (props: OrgPageProps) => { - if ((props as TeamPageProps)?.team) { - return ; - } - return ; -}; - -export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, ServerPage: Page }); +export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page }); diff --git a/apps/web/app/future/org/[orgSlug]/[user]/page.tsx b/apps/web/app/future/org/[orgSlug]/[user]/page.tsx new file mode 100644 index 0000000000..a783b6d61c --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/[user]/page.tsx @@ -0,0 +1,7 @@ +import Page, { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]"; +import { withAppDirSsr } from "app/WithAppDirSsr"; +import { WithLayout } from "app/layoutHOC"; + +const getData = withAppDirSsr(getServerSideProps); + +export default WithLayout({ getLayout: null, getData, Page }); diff --git a/apps/web/app/org/[orgSlug]/instant-meeting/team/[slug]/[type]/page.tsx b/apps/web/app/future/org/[orgSlug]/instant-meeting/team/[slug]/[type]/page.tsx similarity index 88% rename from apps/web/app/org/[orgSlug]/instant-meeting/team/[slug]/[type]/page.tsx rename to apps/web/app/future/org/[orgSlug]/instant-meeting/team/[slug]/[type]/page.tsx index 5626ac8c4e..d853d90be4 100644 --- a/apps/web/app/org/[orgSlug]/instant-meeting/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/instant-meeting/team/[slug]/[type]/page.tsx @@ -15,7 +15,7 @@ import Page from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/instant-mee export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const context = buildLegacyCtx(headers(), cookies(), params, searchParams); - const { slug: eventSlug, user: username, isBrandingHidden } = await getData(context); + const { slug: eventSlug, user: username } = await getData(context); const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); const org = isValidOrgDomain ? currentOrgDomain : null; @@ -33,10 +33,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) => return await _generateMetadata( () => `${title} | ${profileName}`, - () => `${title}`, - isBrandingHidden + () => `${title}` ); }; const getData = withAppDirSsr(getServerSideProps); -export default WithLayout({ getData, Page, isBookingPage: true })<"P">; +export default WithLayout({ getData, Page })<"P">; diff --git a/apps/web/app/future/org/[orgSlug]/page.tsx b/apps/web/app/future/org/[orgSlug]/page.tsx new file mode 100644 index 0000000000..745d7e9547 --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/page.tsx @@ -0,0 +1,29 @@ +import { withAppDirSsr } from "app/WithAppDirSsr"; +import type { PageProps as _PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { cookies, headers } from "next/headers"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; + +import type { PageProps } from "~/team/team-view"; +import TeamPage from "~/team/team-view"; + +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { + const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); + + return await _generateMetadata( + (t) => props.team.name || t("nameless_team"), + (t) => props.team.name || t("nameless_team") + ); +}; + +const getData = withAppDirSsr(getServerSideProps); + +export default WithLayout({ + Page: TeamPage, + getData, + getLayout: null, + isBookingPage: true, +})<"P">; diff --git a/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx b/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx new file mode 100644 index 0000000000..ea51f5cd9b --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx @@ -0,0 +1,45 @@ +import { withAppDirSsr } from "app/WithAppDirSsr"; +import type { PageProps as _PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { cookies, headers } from "next/headers"; + +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { EventRepository } from "@calcom/lib/server/repository/event"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; + +import type { PageProps } from "~/team/type-view"; +import TypePage from "~/team/type-view"; + +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { + const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); + const props = await getData(legacyCtx); + const { user: username, slug: eventSlug, booking } = props; + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug); + + const event = await EventRepository.getPublicEvent({ + username, + eventSlug, + isTeamEvent: true, + org: isValidOrgDomain ? currentOrgDomain : null, + fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true", + }); + + const profileName = event?.profile?.name ?? ""; + const title = event?.title ?? ""; + + return await _generateMetadata( + (t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, + (t) => `${booking?.uid ? t("reschedule") : ""} ${title}` + ); +}; +const getData = withAppDirSsr(getServerSideProps); + +export default WithLayout({ + Page: TypePage, + getData, + getLayout: null, + isBookingPage: true, +})<"P">; diff --git a/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx new file mode 100644 index 0000000000..745d7e9547 --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx @@ -0,0 +1,29 @@ +import { withAppDirSsr } from "app/WithAppDirSsr"; +import type { PageProps as _PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { cookies, headers } from "next/headers"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; + +import type { PageProps } from "~/team/team-view"; +import TeamPage from "~/team/team-view"; + +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { + const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); + + return await _generateMetadata( + (t) => props.team.name || t("nameless_team"), + (t) => props.team.name || t("nameless_team") + ); +}; + +const getData = withAppDirSsr(getServerSideProps); + +export default WithLayout({ + Page: TeamPage, + getData, + getLayout: null, + isBookingPage: true, +})<"P">; diff --git a/apps/web/app/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx similarity index 63% rename from apps/web/app/team/[slug]/[type]/page.tsx rename to apps/web/app/future/team/[slug]/[type]/page.tsx index 6e8af71749..debf7575b8 100644 --- a/apps/web/app/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -1,6 +1,6 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; import type { PageProps } from "app/_types"; -import { generateEventBookingPageMetadata } from "app/generateBookingPageMetadata"; +import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; @@ -15,7 +15,7 @@ import LegacyPage, { type PageProps as LegacyPageProps } from "~/team/type-view" export const generateMetadata = async ({ params, searchParams }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); const props = await getData(legacyCtx); - const { user: username, slug: eventSlug, booking, isSEOIndexable, eventData, isBrandingHidden } = props; + const { user: username, slug: eventSlug, booking } = props; const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug); const event = await EventRepository.getPublicEvent({ @@ -26,26 +26,13 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => { fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true", }); - return await generateEventBookingPageMetadata({ - profile: { - name: event?.profile?.name ?? "", - image: event?.profile.image ?? "", - }, - event: { - title: event?.title ?? "", - hidden: event?.hidden ?? false, - users: [ - ...(event?.users || []).map((user) => ({ - name: `${user.name}`, - username: `${user.username}`, - })), - ], - }, - hideBranding: isBrandingHidden, - orgSlug: eventData?.entity.orgSlug ?? null, - isSEOIndexable, - isReschedule: !!booking, - }); + const profileName = event?.profile?.name ?? ""; + const title = event?.title ?? ""; + + return await _generateMetadata( + (t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, + (t) => `${booking?.uid ? t("reschedule") : ""} ${title}` + ); }; const getData = withAppDirSsr(getServerSideProps); diff --git a/apps/web/app/team/[slug]/page.tsx b/apps/web/app/future/team/[slug]/page.tsx similarity index 51% rename from apps/web/app/team/[slug]/page.tsx rename to apps/web/app/future/team/[slug]/page.tsx index 976e6e2d70..8cd056fe22 100644 --- a/apps/web/app/team/[slug]/page.tsx +++ b/apps/web/app/future/team/[slug]/page.tsx @@ -1,11 +1,9 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; import type { PageProps as _PageProps } from "app/_types"; -import { generateTeamProfilePageMetadata } from "app/generateBookingPageMetadata"; +import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; -import { getOrgOrTeamAvatar } from "@calcom/lib/defaultAvatarImage"; - import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; @@ -13,21 +11,12 @@ import type { PageProps } from "~/team/team-view"; import LegacyPage from "~/team/team-view"; export const generateMetadata = async ({ params, searchParams }: _PageProps) => { - const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = await getData( - buildLegacyCtx(headers(), cookies(), params, searchParams) - ); + const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); - return await generateTeamProfilePageMetadata({ - profile: { - teamName: team.name, - image: getOrgOrTeamAvatar(team), - markdownStrippedBio: markdownStrippedBio, - }, - event: null, - hideBranding: false, - orgSlug: currentOrgDomain ?? null, - isSEOIndexable: !!isSEOIndexable, - }); + return await _generateMetadata( + (t) => props.team.name || t("nameless_team"), + (t) => props.team.name || t("nameless_team") + ); }; const getData = withAppDirSsr(getServerSideProps); @@ -36,5 +25,4 @@ export default WithLayout({ Page: LegacyPage, getData, getLayout: null, - isBookingPage: true, })<"P">; diff --git a/apps/web/app/generateBookingPageMetadata.ts b/apps/web/app/generateBookingPageMetadata.ts deleted file mode 100644 index 566a5cae6f..0000000000 --- a/apps/web/app/generateBookingPageMetadata.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { TFunction } from "next-i18next"; - -import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; - -import { generateMeetingMetadata } from "./_utils"; - -type MetadataPayload = { - title: (t: TFunction) => string; - description: (t: TFunction) => string; - meeting: { - title: string; - profile: { name: string; image: string }; - users?: { name: string; username: string }[]; - }; - robots: { - index: boolean; - follow: boolean; - }; -}; - -type UserProfilePageProfile = { - name: string | null; - username: string | null; - image: string | null; - markdownStrippedBio: string | null | undefined; -}; - -type TeamProfilePageProfile = { - teamName: string | null; - image: string | null; - markdownStrippedBio: string | null | undefined; -}; - -type EventBookingPageProfile = { name: string; image: string }; -type BaseMetadataArgs = { - hideBranding: boolean; - orgSlug: string | null; - isSEOIndexable: boolean; - isReschedule?: boolean; -}; - -type TeamProfilePageArgs = BaseMetadataArgs & { - profile: TeamProfilePageProfile; - event: null; -}; - -type UserProfilePageArgs = BaseMetadataArgs & { - profile: UserProfilePageProfile; - event: null; -}; - -type EventBookingPageArgs = BaseMetadataArgs & { - profile: EventBookingPageProfile; - event: { - title: string; - hidden: boolean; - users: { name: string | null; username: string | null }[]; - }; -}; - -export const generateEventBookingPageMetadata = async (props: EventBookingPageArgs) => { - const { profile, isReschedule, hideBranding, orgSlug, isSEOIndexable, event } = props; - const fullOrigin = getOrgFullOrigin(orgSlug); - - const payload: MetadataPayload = { - title: (t) => `${isReschedule ? t("reschedule") : ""} ${event.title} | ${profile.name}`, - description: (t) => `${isReschedule ? t("reschedule") : ""} ${event.title}`, - meeting: { - title: event.title, - profile: { name: profile.name, image: profile.image }, - users: event.users.map((user) => ({ - name: `${user.name}`, - username: `${user.username}`, - })), - }, - robots: { - index: !(event.hidden || !isSEOIndexable), - follow: !(event.hidden || !isSEOIndexable), - }, - }; - - return { - ...(await generateMeetingMetadata( - payload.meeting, - payload.title, - payload.description, - hideBranding, - fullOrigin - )), - robots: payload.robots, - }; -}; - -export const generateUserProfilePageMetadata = async (props: UserProfilePageArgs) => { - const { profile, hideBranding, orgSlug, isSEOIndexable } = props; - const fullOrigin = getOrgFullOrigin(orgSlug); - - const payload: MetadataPayload = { - title: () => profile.name ?? "", - description: () => profile.markdownStrippedBio ?? "", - meeting: { - title: profile.markdownStrippedBio ?? "", - profile: { name: profile.name ?? "", image: profile.image ?? "" }, - users: [{ name: profile.name ?? "", username: profile.username ?? "" }], - }, - robots: { - index: isSEOIndexable, - follow: isSEOIndexable, - }, - }; - - return { - ...(await generateMeetingMetadata( - payload.meeting, - payload.title, - payload.description, - hideBranding, - fullOrigin - )), - robots: payload.robots, - }; -}; - -export const generateTeamProfilePageMetadata = async (props: TeamProfilePageArgs) => { - const { profile, hideBranding, orgSlug, isSEOIndexable } = props; - const fullOrigin = getOrgFullOrigin(orgSlug); - - const payload: MetadataPayload = { - title: (t) => profile.teamName || t("nameless_team"), - description: () => profile.markdownStrippedBio ?? "", - meeting: { - title: profile.markdownStrippedBio ?? "", - profile: { name: `${profile.teamName}`, image: profile.image ?? "" }, - }, - robots: { - index: isSEOIndexable, - follow: isSEOIndexable, - }, - }; - - return { - ...(await generateMeetingMetadata( - payload.meeting, - payload.title, - payload.description, - hideBranding, - fullOrigin - )), - robots: payload.robots, - }; -}; diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 906dbe5b9b..575e7b949e 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -26,10 +26,7 @@ export const generateMetadata = () => prepareRootMetadata(); const getInitialProps = async (url: string) => { const { pathname, searchParams } = new URL(url); - const isEmbedSnippetGeneratorPath = pathname.startsWith("/event-types"); - const isEmbed = - (pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null) && - !isEmbedSnippetGeneratorPath; + const isEmbed = pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null; const embedColorScheme = searchParams?.get("ui.color-scheme"); const req = { headers: headers(), cookies: cookies() }; diff --git a/apps/web/app/org/[orgSlug]/[user]/[type]/page.tsx b/apps/web/app/org/[orgSlug]/[user]/[type]/page.tsx deleted file mode 100644 index 44fe4a385b..0000000000 --- a/apps/web/app/org/[orgSlug]/[user]/[type]/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { PageProps } from "app/_types"; -import { generateEventBookingPageMetadata } from "app/generateBookingPageMetadata"; -import { WithLayout } from "app/layoutHOC"; -import { cookies, headers } from "next/headers"; - -import { buildLegacyCtx } from "@lib/buildLegacyCtx"; -import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps"; - -import type { PageProps as TeamTypePageProps } from "~/team/type-view"; -import TeamTypePage from "~/team/type-view"; -import UserTypePage from "~/users/views/users-type-public-view"; -import type { PageProps as UserTypePageProps } from "~/users/views/users-type-public-view"; - -export type OrgTypePageProps = UserTypePageProps | TeamTypePageProps; -const getData = withAppDirSsr(getServerSideProps); - -export const generateMetadata = async ({ params, searchParams }: PageProps) => { - const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); - const props = await getData(legacyCtx); - - const { booking, isSEOIndexable, eventData, isBrandingHidden } = props; - const rescheduleUid = booking?.uid; - - const profileName = eventData?.profile?.name ?? ""; - const profileImage = eventData?.profile.image ?? ""; - - return await generateEventBookingPageMetadata({ - profile: { name: profileName, image: profileImage }, - event: { - title: eventData?.title ?? "", - hidden: eventData?.hidden ?? false, - users: [ - ...(eventData?.users || []).map((user) => ({ - name: `${user.name}`, - username: `${user.username}`, - })), - ], - }, - hideBranding: isBrandingHidden, - orgSlug: eventData?.entity.orgSlug ?? null, - isSEOIndexable: !!isSEOIndexable, - isReschedule: !!rescheduleUid, - }); -}; - -const Page = async (props: OrgTypePageProps) => { - if ((props as TeamTypePageProps)?.teamId) { - return ; - } - return ; -}; - -export default WithLayout({ getLayout: null, getData, ServerPage: Page, isBookingPage: true }); diff --git a/apps/web/app/org/[orgSlug]/[user]/page.tsx b/apps/web/app/org/[orgSlug]/[user]/page.tsx deleted file mode 100644 index d9e11f8395..0000000000 --- a/apps/web/app/org/[orgSlug]/[user]/page.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { PageProps } from "app/_types"; -import { - generateTeamProfilePageMetadata, - generateUserProfilePageMetadata, -} from "app/generateBookingPageMetadata"; -import { WithLayout } from "app/layoutHOC"; -import { cookies, headers } from "next/headers"; - -import { getOrgOrTeamAvatar } from "@calcom/lib/defaultAvatarImage"; - -import { buildLegacyCtx } from "@lib/buildLegacyCtx"; -import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps"; - -import type { PageProps as TeamPageProps } from "~/team/team-view"; -import TeamPage from "~/team/team-view"; -import UserPage from "~/users/views/users-public-view"; -import type { PageProps as UserPageProps } from "~/users/views/users-public-view"; - -export type OrgPageProps = UserPageProps | TeamPageProps; -const getData = withAppDirSsr(getServerSideProps); - -export const generateMetadata = async ({ params, searchParams }: PageProps) => { - const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); - const props = await getData(legacyCtx); - - if ((props as TeamPageProps)?.team) { - const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = props as TeamPageProps; - return await generateTeamProfilePageMetadata({ - profile: { - teamName: team.name, - image: getOrgOrTeamAvatar(team), - markdownStrippedBio: markdownStrippedBio, - }, - event: null, - hideBranding: false, - orgSlug: currentOrgDomain ?? null, - isSEOIndexable: !!isSEOIndexable, - }); - } else { - const { profile, markdownStrippedBio, isOrgSEOIndexable, entity } = props as UserPageProps; - - const isOrg = !!profile?.organization; - const allowSEOIndexing = - (!isOrg && profile.allowSEOIndexing) || (isOrg && isOrgSEOIndexable && profile.allowSEOIndexing); - - return await generateUserProfilePageMetadata({ - profile: { name: profile.name, username: profile.username, image: profile.image, markdownStrippedBio }, - hideBranding: false, - orgSlug: entity.orgSlug ?? null, - isSEOIndexable: !!allowSEOIndexing, - event: null, - }); - } -}; - -const Page = async (props: OrgPageProps) => { - if ((props as TeamPageProps)?.team) { - return ; - } - return ; -}; - -export default WithLayout({ getLayout: null, getData, ServerPage: Page, isBookingPage: true }); diff --git a/apps/web/app/org/[orgSlug]/page.tsx b/apps/web/app/org/[orgSlug]/page.tsx deleted file mode 100644 index 99b51c0aa2..0000000000 --- a/apps/web/app/org/[orgSlug]/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default, generateMetadata } from "app/team/[slug]/page"; diff --git a/apps/web/app/org/[orgSlug]/team/[slug]/[type]/page.tsx b/apps/web/app/org/[orgSlug]/team/[slug]/[type]/page.tsx deleted file mode 100644 index ebcf985632..0000000000 --- a/apps/web/app/org/[orgSlug]/team/[slug]/[type]/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default, generateMetadata } from "app/team/[slug]/[type]/page"; diff --git a/apps/web/app/org/[orgSlug]/team/[slug]/page.tsx b/apps/web/app/org/[orgSlug]/team/[slug]/page.tsx deleted file mode 100644 index 99b51c0aa2..0000000000 --- a/apps/web/app/org/[orgSlug]/team/[slug]/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default, generateMetadata } from "app/team/[slug]/page"; diff --git a/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx b/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx index 6b5c35d4a4..4fce1e1edd 100644 --- a/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx +++ b/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx @@ -1,3 +1,4 @@ +import { getServerSideProps as GSSUserPage } from "@pages/[user]"; import type { GetServerSidePropsContext } from "next"; import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; @@ -5,8 +6,6 @@ import prisma from "@calcom/prisma"; import { getServerSideProps as GSSTeamPage } from "@lib/team/[slug]/getServerSideProps"; -import { getServerSideProps as GSSUserPage } from "@server/lib/[user]/getServerSideProps"; - export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const team = await prisma.team.findFirst({ where: { diff --git a/apps/web/lib/team/[slug]/getServerSideProps.tsx b/apps/web/lib/team/[slug]/getServerSideProps.tsx index e37410b34a..4369bba85c 100644 --- a/apps/web/lib/team/[slug]/getServerSideProps.tsx +++ b/apps/web/lib/team/[slug]/getServerSideProps.tsx @@ -55,6 +55,7 @@ const getTheLastArrayElement = (value: ReadonlyArray | string | undefine export const getServerSideProps = async (context: GetServerSidePropsContext) => { const slug = getTheLastArrayElement(context.query.slug) ?? getTheLastArrayElement(context.query.orgSlug); + const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig( context.req, context.params?.orgSlug ?? context.query?.orgSlug diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 8992d36864..192cb9b426 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -194,10 +194,7 @@ export const config = { "/settings/:path*", "/reschedule/:path*", "/availability/:path*", - "/org/:path*", - "/team/:path*", - "/:user/:type/", - "/:user/", + "/booking/:path*", ], }; diff --git a/apps/web/modules/d/[link]/d-type-view.tsx b/apps/web/modules/d/[link]/d-type-view.tsx index d4b781b162..46f7315074 100644 --- a/apps/web/modules/d/[link]/d-type-view.tsx +++ b/apps/web/modules/d/[link]/d-type-view.tsx @@ -2,6 +2,7 @@ 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"; @@ -19,6 +20,13 @@ export default function Type({ }: PageProps) { return (
+ & EmbedProps; function Type({ slug, user, booking, isEmbed, isBrandingHidden, entity, eventTypeId, duration }: PageProps) { return (
+ -
-
- + <> + +
+
+
+ +
+

+ {team.parent && `${team.parent.name} `} + {teamName} +

+ {!isBioEmpty && ( + <> +
+ + )}
-

- {team.parent && `${team.parent.name} `} - {teamName} -

- {!isBioEmpty && ( + {team.isOrganization ? ( + !teamOrOrgIsPrivate ? ( + + ) : ( +
+

{t("you_cannot_see_teams_of_org")}

+
+ ) + ) : ( <> -
+ {(showMembers.isOn || !team.eventTypes?.length) && + (teamOrOrgIsPrivate ? ( +
+

+ {t("you_cannot_see_team_members")} +

+
+ ) : ( + + ))} + {!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && ( +
+ + + {/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */} + {!team.hideBookATeamMember && !teamOrOrgIsPrivate && ( +
+
+ + + +
+ )} +
+ )} )} -
- {team.isOrganization ? ( - !teamOrOrgIsPrivate ? ( - - ) : ( -
-

{t("you_cannot_see_teams_of_org")}

-
- ) - ) : ( - <> - {(showMembers.isOn || !team.eventTypes?.length) && - (teamOrOrgIsPrivate ? ( -
-

- {t("you_cannot_see_team_members")} -

-
- ) : ( - - ))} - {!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && ( -
- - - {/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */} - {!team.hideBookATeamMember && !teamOrOrgIsPrivate && ( -
-
- - - -
- )} -
- )} - - )} -
+
+ ); } diff --git a/apps/web/modules/team/type-view.tsx b/apps/web/modules/team/type-view.tsx index 895eee92c5..ed9ded88ab 100644 --- a/apps/web/modules/team/type-view.tsx +++ b/apps/web/modules/team/type-view.tsx @@ -5,6 +5,7 @@ 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"; @@ -33,11 +34,33 @@ function Type({ teamMemberEmail, crmOwnerRecordType, crmAppSlug, + isSEOIndexable, }: PageProps) { const searchParams = useSearchParams(); + const { profile, users, hidden, title } = eventData; return (
+