From d9133cb39558fb70f4243cdcc941e3e8c1d33580 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Thu, 12 Sep 2024 17:58:08 -0400 Subject: [PATCH] chore: App router migration - Create/Use `EventRepository` and Extract `/team/**/*` and `/teams/*` into `/modules` from `/pages` (#16453) --- apps/web/app/WithEmbedSSR.tsx | 4 - apps/web/app/future/[user]/[type]/page.tsx | 28 +- apps/web/app/future/d/[link]/[slug]/page.tsx | 30 +- .../app/future/org/[orgSlug]/embed/page.tsx | 7 +- apps/web/app/future/org/[orgSlug]/page.tsx | 4 +- .../org/[orgSlug]/team/[slug]/[type]/page.tsx | 30 +- .../future/org/[orgSlug]/team/[slug]/page.tsx | 4 +- .../future/team/[slug]/[type]/embed/page.tsx | 39 ++- .../app/future/team/[slug]/[type]/page.tsx | 30 +- .../web/app/future/team/[slug]/embed/page.tsx | 20 +- apps/web/app/future/team/[slug]/page.tsx | 12 +- apps/web/app/future/teams/page.tsx | 10 +- .../[orgSlug]/[user]/getServerSideProps.tsx | 3 +- apps/web/lib/teams/getServerSideProps.tsx | 2 +- apps/web/lib/withEmbedSsr.test.ts | 132 --------- apps/web/lib/withEmbedSsr.tsx | 4 - apps/web/middleware.ts | 61 ++-- apps/web/modules/team/team-view.tsx | 265 +++++++++++++++++ apps/web/modules/team/type-view.tsx | 75 +++++ apps/web/modules/teams/teams-view.tsx | 45 +++ .../org/[orgSlug]/[user]/[type]/index.tsx | 5 +- apps/web/pages/org/[orgSlug]/[user]/index.tsx | 5 +- apps/web/pages/org/[orgSlug]/index.ts | 2 +- .../pages/org/[orgSlug]/team/[slug]/[type].ts | 2 +- .../pages/org/[orgSlug]/team/[slug]/index.ts | 2 +- apps/web/pages/team/[slug].tsx | 269 +----------------- apps/web/pages/team/[slug]/[type].tsx | 78 +---- apps/web/pages/team/[slug]/[type]/embed.tsx | 13 +- apps/web/pages/team/[slug]/embed.tsx | 13 +- apps/web/pages/teams/index.tsx | 47 +-- packages/embeds/embed-core/playground.ts | 3 + packages/embeds/embed-core/src/embed.ts | 7 + packages/lib/server/repository/event.ts | 3 +- 33 files changed, 628 insertions(+), 626 deletions(-) create mode 100644 apps/web/modules/team/team-view.tsx create mode 100644 apps/web/modules/team/type-view.tsx create mode 100644 apps/web/modules/teams/teams-view.tsx diff --git a/apps/web/app/WithEmbedSSR.tsx b/apps/web/app/WithEmbedSSR.tsx index 0b1ba099a1..ddfe5bd9ef 100644 --- a/apps/web/app/WithEmbedSSR.tsx +++ b/apps/web/app/WithEmbedSSR.tsx @@ -14,10 +14,6 @@ export default function withEmbedSsrAppDir>( ) { return async (context: GetServerSidePropsContext): Promise => { const { embed, layout } = context.query; - const isCOEPEnabled = context.query["flag.coep"] === "true"; - if (isCOEPEnabled) { - context.res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); - } try { const props = await getData(context); diff --git a/apps/web/app/future/[user]/[type]/page.tsx b/apps/web/app/future/[user]/[type]/page.tsx index 7e32f5a80a..024c56c91b 100644 --- a/apps/web/app/future/[user]/[type]/page.tsx +++ b/apps/web/app/future/[user]/[type]/page.tsx @@ -4,6 +4,9 @@ import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { headers, cookies } 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 LegacyPage from "~/users/views/users-type-public-view"; @@ -13,21 +16,20 @@ import { } from "~/users/views/users-type-public-view.getServerSideProps"; export const generateMetadata = async ({ params, searchParams }: PageProps) => { - const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); + const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); + const props = await getData(legacyCtx); - const { eventData, booking, user, slug } = props; + const { booking, user: username, slug: eventSlug } = props; const rescheduleUid = booking?.uid; - const { trpc } = await import("@calcom/trpc"); - const { data: event } = trpc.viewer.public.event.useQuery( - { - username: user, - eventSlug: slug, - isTeamEvent: false, - org: eventData.entity.orgSlug ?? null, - fromRedirectOfNonOrgLink: eventData.entity.fromRedirectOfNonOrgLink, - }, - { refetchOnWindowFocus: false } - ); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug); + + const event = await EventRepository.getPublicEvent({ + username, + eventSlug, + isTeamEvent: false, + org: isValidOrgDomain ? currentOrgDomain : null, + fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true", + }); const profileName = event?.profile?.name ?? ""; const title = event?.title ?? ""; diff --git a/apps/web/app/future/d/[link]/[slug]/page.tsx b/apps/web/app/future/d/[link]/[slug]/page.tsx index 5ac2b4886e..03c250363f 100644 --- a/apps/web/app/future/d/[link]/[slug]/page.tsx +++ b/apps/web/app/future/d/[link]/[slug]/page.tsx @@ -5,6 +5,9 @@ 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/d/[link]/[slug]/getServerSideProps"; @@ -15,21 +18,22 @@ export const generateMetadata = async ({ params: Params; searchParams: SearchParams; }) => { - const pageProps = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); + const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); + const pageProps = await getData(legacyCtx); - const { entity, booking, user, slug, isTeamEvent } = pageProps; + const { booking, user: username, slug: eventSlug, isTeamEvent } = pageProps; const rescheduleUid = booking?.uid; - const { trpc } = await import("@calcom/trpc"); - const { data: event } = trpc.viewer.public.event.useQuery( - { - username: user ?? "", - eventSlug: slug ?? "", - isTeamEvent, - org: entity.orgSlug ?? null, - fromRedirectOfNonOrgLink: entity.fromRedirectOfNonOrgLink, - }, - { refetchOnWindowFocus: false } - ); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req); + const org = isValidOrgDomain ? currentOrgDomain : null; + + const event = await EventRepository.getPublicEvent({ + username, + eventSlug, + isTeamEvent, + org, + fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true", + }); + const profileName = event?.profile?.name ?? ""; const title = event?.title ?? ""; return await _generateMetadata( diff --git a/apps/web/app/future/org/[orgSlug]/embed/page.tsx b/apps/web/app/future/org/[orgSlug]/embed/page.tsx index 18a1fd11dc..499173cf98 100644 --- a/apps/web/app/future/org/[orgSlug]/embed/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/embed/page.tsx @@ -1,5 +1,3 @@ -import { type PageProps } from "@pages/team/[slug]"; -import EmbedPage from "@pages/team/[slug]/embed"; import { withAppDirSsr } from "app/WithAppDirSsr"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; import type { Params, SearchParams } from "app/_types"; @@ -10,6 +8,9 @@ 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"; + const getData = withAppDirSsr(getServerSideProps); export const generateMetadata = async ({ @@ -31,7 +32,7 @@ export const generateMetadata = async ({ const getEmbedData = withEmbedSsrAppDir(getData); export default WithLayout({ - Page: EmbedPage, + Page: TeamPage, getData: getEmbedData, getLayout: null, isBookingPage: true, diff --git a/apps/web/app/future/org/[orgSlug]/page.tsx b/apps/web/app/future/org/[orgSlug]/page.tsx index c64eb8bd1b..d6572bbb91 100644 --- a/apps/web/app/future/org/[orgSlug]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/page.tsx @@ -1,4 +1,3 @@ -import TeamPage, { type PageProps } from "@pages/team/[slug]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import type { Params, SearchParams } from "app/_types"; import { _generateMetadata } from "app/_utils"; @@ -8,6 +7,9 @@ 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, 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 index 7e75d5bb9b..22cb127b72 100644 --- a/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx @@ -1,13 +1,18 @@ -import TypePage, { type PageProps } from "@pages/team/[slug]/[type]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import type { Params, SearchParams } 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, @@ -17,19 +22,16 @@ export const generateMetadata = async ({ }) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); const props = await getData(legacyCtx); - const { eventData, user, slug, booking } = props; - const entity = eventData.entity; - const { trpc } = await import("@calcom/trpc"); - const { data: event } = trpc.viewer.public.event.useQuery( - { - username: user, - eventSlug: slug, - isTeamEvent: false, - org: entity.orgSlug ?? null, - fromRedirectOfNonOrgLink: entity.fromRedirectOfNonOrgLink, - }, - { refetchOnWindowFocus: false } - ); + 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 ?? ""; diff --git a/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx index c64eb8bd1b..d6572bbb91 100644 --- a/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx @@ -1,4 +1,3 @@ -import TeamPage, { type PageProps } from "@pages/team/[slug]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import type { Params, SearchParams } from "app/_types"; import { _generateMetadata } from "app/_utils"; @@ -8,6 +7,9 @@ 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, diff --git a/apps/web/app/future/team/[slug]/[type]/embed/page.tsx b/apps/web/app/future/team/[slug]/[type]/embed/page.tsx index a39a2b28f9..378a528ff5 100644 --- a/apps/web/app/future/team/[slug]/[type]/embed/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/embed/page.tsx @@ -1,10 +1,43 @@ -import LegacyPage, { getServerSideProps } from "@pages/team/[slug]/[type]/embed"; import { withAppDirSsr } from "app/WithAppDirSsr"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; +import type { PageProps as _PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; +import { cookies, headers } from "next/headers"; -const getData = withAppDirSsr(getServerSideProps); +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 LegacyPage 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); const getEmbedData = withEmbedSsrAppDir(getData); -// @ts-expect-error Type is missing the following properties from type: entity, duration, booking, away, and 7 more. export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">; diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx index 948291446d..debf7575b8 100644 --- a/apps/web/app/future/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -1,28 +1,30 @@ -import LegacyPage, { type PageProps as LegacyPageProps, getServerSideProps } from "@pages/team/[slug]/[type]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import type { 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 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 { eventData, user, slug, booking } = props; - const entity = eventData.entity; - const { trpc } = await import("@calcom/trpc"); - const { data: event } = trpc.viewer.public.event.useQuery( - { - username: user, - eventSlug: slug, - isTeamEvent: false, - org: entity.orgSlug ?? null, - fromRedirectOfNonOrgLink: entity.fromRedirectOfNonOrgLink, - }, - { refetchOnWindowFocus: false } - ); + 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 ?? ""; diff --git a/apps/web/app/future/team/[slug]/embed/page.tsx b/apps/web/app/future/team/[slug]/embed/page.tsx index 407fb30e44..db822e4a91 100644 --- a/apps/web/app/future/team/[slug]/embed/page.tsx +++ b/apps/web/app/future/team/[slug]/embed/page.tsx @@ -1,7 +1,25 @@ -import LegacyPage, { type PageProps, getServerSideProps } from "@pages/team/[slug]"; import { withAppDirSsr } from "app/WithAppDirSsr"; import withEmbedSsrAppDir from "app/WithEmbedSSR"; +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 LegacyPage from "~/team/team-view"; + +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { + const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); + const teamName = props.team.name || "Nameless Team"; + + return await _generateMetadata( + () => teamName, + () => teamName + ); +}; const getData = withAppDirSsr(getServerSideProps); const getEmbedData = withEmbedSsrAppDir(getData); diff --git a/apps/web/app/future/team/[slug]/page.tsx b/apps/web/app/future/team/[slug]/page.tsx index df553afa9a..73e8b4e81d 100644 --- a/apps/web/app/future/team/[slug]/page.tsx +++ b/apps/web/app/future/team/[slug]/page.tsx @@ -1,13 +1,16 @@ -import LegacyPage, { getServerSideProps, type PageProps as LegacyPageProps } from "@pages/team/[slug]"; import { withAppDirSsr } from "app/WithAppDirSsr"; -import type { PageProps } from "app/_types"; +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"; -export const generateMetadata = async ({ params, searchParams }: PageProps) => { +import type { PageProps } from "~/team/team-view"; +import LegacyPage from "~/team/team-view"; + +export const generateMetadata = async ({ params, searchParams }: _PageProps) => { const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams)); const teamName = props.team.name || "Nameless Team"; @@ -17,11 +20,10 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => { ); }; -const getData = withAppDirSsr(getServerSideProps); +const getData = withAppDirSsr(getServerSideProps); export default WithLayout({ Page: LegacyPage, getData, getLayout: null, - isBookingPage: true, })<"P">; diff --git a/apps/web/app/future/teams/page.tsx b/apps/web/app/future/teams/page.tsx index 793126c360..5008c9ebd8 100644 --- a/apps/web/app/future/teams/page.tsx +++ b/apps/web/app/future/teams/page.tsx @@ -1,14 +1,20 @@ -import Page from "@pages/teams/index"; import { withAppDirSsr } from "app/WithAppDirSsr"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { getServerSideProps } from "@lib/teams/getServerSideProps"; +import type { PageProps } from "~/teams/teams-view"; +import Page from "~/teams/teams-view"; + export const generateMetadata = async () => await _generateMetadata( (t) => t("teams"), (t) => t("create_manage_teams_collaborative") ); -export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout: null, Page })<"P">; +export default WithLayout({ + getData: withAppDirSsr(getServerSideProps), + getLayout: null, + Page, +})<"P">; diff --git a/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx b/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx index 5045da2167..4fce1e1edd 100644 --- a/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx +++ b/apps/web/lib/org/[orgSlug]/[user]/getServerSideProps.tsx @@ -1,10 +1,11 @@ import { getServerSideProps as GSSUserPage } from "@pages/[user]"; -import { getServerSideProps as GSSTeamPage } from "@pages/team/[slug]"; import type { GetServerSidePropsContext } from "next"; import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; import prisma from "@calcom/prisma"; +import { getServerSideProps as GSSTeamPage } from "@lib/team/[slug]/getServerSideProps"; + export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const team = await prisma.team.findFirst({ where: { diff --git a/apps/web/lib/teams/getServerSideProps.tsx b/apps/web/lib/teams/getServerSideProps.tsx index ae63a5f811..d75c4ecfca 100644 --- a/apps/web/lib/teams/getServerSideProps.tsx +++ b/apps/web/lib/teams/getServerSideProps.tsx @@ -7,7 +7,7 @@ import { ssrInit } from "@server/lib/ssr"; export const getServerSideProps = async (context: GetServerSidePropsContext) => { const ssr = await ssrInit(context); await ssr.viewer.me.prefetch(); - const session = await getServerSession({ req: context.req, res: context.res }); + const session = await getServerSession({ req: context.req }); const token = Array.isArray(context.query?.token) ? context.query.token[0] : context.query?.token; const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null; diff --git a/apps/web/lib/withEmbedSsr.test.ts b/apps/web/lib/withEmbedSsr.test.ts index 825fbfe24f..cdb4a4f233 100644 --- a/apps/web/lib/withEmbedSsr.test.ts +++ b/apps/web/lib/withEmbedSsr.test.ts @@ -133,24 +133,6 @@ describe("withEmbedSsr", () => { }, }); }); - - it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - redirectUrl: "/reschedule", - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - "flag.coep": "true", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp"); - }); }); describe("when redirect destination is absolute, should add /embed to end of the path", () => { @@ -224,23 +206,6 @@ describe("withEmbedSsr", () => { }, }); }); - it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - redirectUrl: "https://calcom.cal.local/owner", - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - "flag.coep": "true", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp"); - }); }); }); @@ -268,85 +233,6 @@ describe("withEmbedSsr", () => { }, }); }); - - it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - props: { - prop1: "value1", - }, - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - "flag.coep": "true", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp"); - }); - - it('should not add Cross-Origin-Embedder-Policy header when "flag.coep" query param is false', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - props: { - prop1: "value1", - }, - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - "flag.coep": "false", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBeUndefined(); - }); - - it('should not add Cross-Origin-Embedder-Policy header when "flag.coep" query param is not set', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - props: { - prop1: "value1", - }, - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBeUndefined(); - }); - - it('should not add Cross-Origin-Embedder-Policy header when "flag.coep" query param is set to something other than true', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - props: { - prop1: "value1", - }, - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - "flag.coep": "abasdf", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBeUndefined(); - }); }); describe("when gSSP doesn't have props or redirect ", () => { @@ -368,23 +254,5 @@ describe("withEmbedSsr", () => { expect(ret).toEqual({ notFound: true }); }); - - it('should add Cross-Origin-Embedder-Policy header when "flag.coep" query param is true', async () => { - const withEmbedGetSsr = withEmbedSsr( - getServerSidePropsFnGenerator({ - notFound: true, - }) - ); - - const context = getServerSidePropsContextArg({ - embedRelatedParams: { - layout: "week_view", - embed: "", - "flag.coep": "true", - }, - }); - await withEmbedGetSsr(context); - expect(context.res.getHeader("Cross-Origin-Embedder-Policy")).toBe("require-corp"); - }); }); }); diff --git a/apps/web/lib/withEmbedSsr.tsx b/apps/web/lib/withEmbedSsr.tsx index 8d8371780b..1dc50d6c70 100644 --- a/apps/web/lib/withEmbedSsr.tsx +++ b/apps/web/lib/withEmbedSsr.tsx @@ -11,10 +11,6 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) { const ssrResponse = await getServerSideProps(context); const embed = context.query.embed; const layout = context.query.layout; - const isCOEPEnabled = context.query["flag.coep"] === "true"; - if (isCOEPEnabled) { - context.res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); - } if ("redirect" in ssrResponse) { const destinationUrl = ssrResponse.redirect.destination; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index ed749dbe5d..7e94fe4c6d 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -41,21 +41,9 @@ const middleware = async (req: NextRequest): Promise> => { } } - const res = routingForms.handle(url); - - const { nonce } = csp(req, res ?? null); - - if (!process.env.CSP_POLICY) { - req.headers.set("x-csp", "not-opted-in"); - } else if (!req.headers.get("x-csp")) { - // If x-csp not set by gSSP, then it's initialPropsOnly - req.headers.set("x-csp", "initialPropsOnly"); - } else { - req.headers.set("x-csp", nonce ?? ""); - } - - if (res) { - return res; + const routingFormRewriteResponse = routingForms.handleRewrite(url); + if (routingFormRewriteResponse) { + return responseWithHeaders({ url, res: routingFormRewriteResponse, req }); } if (url.pathname.startsWith("/api/trpc/")) { @@ -66,6 +54,7 @@ const middleware = async (req: NextRequest): Promise> => { const isSignupDisabled = await safeGet("isSignupDisabled"); // If is in maintenance mode, point the url pathname to the maintenance page if (isSignupDisabled) { + // TODO: Consider using responseWithHeaders here return NextResponse.json({ error: "Signup is disabled" }, { status: 503 }); } } @@ -88,6 +77,7 @@ const middleware = async (req: NextRequest): Promise> => { const nextUrl = url.clone(); nextUrl.pathname = validPathname; + // TODO: Consider using responseWithHeaders here return NextResponse.redirect(nextUrl, { headers: requestHeaders }); } } @@ -105,15 +95,17 @@ const middleware = async (req: NextRequest): Promise> => { requestHeaders.set("x-locale", locale); - return NextResponse.next({ + const res = NextResponse.next({ request: { headers: requestHeaders, }, }); + + return responseWithHeaders({ url, res, req }); }; const routingForms = { - handle: (url: URL) => { + handleRewrite: (url: URL) => { // Don't 404 old routing_forms links if (url.pathname.startsWith("/apps/routing_forms")) { url.pathname = url.pathname.replace(/^\/apps\/routing_forms($|\/)/, "/apps/routing-forms/"); @@ -122,6 +114,41 @@ const routingForms = { }, }; +const embeds = { + addResponseHeaders: ({ url, res }: { url: URL; res: NextResponse }) => { + if (!url.pathname.endsWith("/embed")) { + return res; + } + const isCOEPEnabled = url.searchParams.get("flag.coep") === "true"; + if (isCOEPEnabled) { + res.headers.set("Cross-Origin-Embedder-Policy", "require-corp"); + } + return res; + }, +}; + +const contentSecurityPolicy = { + addResponseHeaders: ({ res, req }: { res: NextResponse; req: NextRequest }) => { + const { nonce } = csp(req, res ?? null); + + if (!process.env.CSP_POLICY) { + res.headers.set("x-csp", "not-opted-in"); + } else if (!res.headers.get("x-csp")) { + // If x-csp not set by gSSP, then it's initialPropsOnly + res.headers.set("x-csp", "initialPropsOnly"); + } else { + res.headers.set("x-csp", nonce ?? ""); + } + return res; + }, +}; + +function responseWithHeaders({ url, res, req }: { url: URL; res: NextResponse; req: NextRequest }) { + const resWithCSP = contentSecurityPolicy.addResponseHeaders({ res, req }); + const resWithEmbeds = embeds.addResponseHeaders({ url, res: resWithCSP }); + return resWithEmbeds; +} + export const config = { // Next.js Doesn't support spread operator in config matcher, so, we must list all paths explicitly here. // https://github.com/vercel/next.js/discussions/42458 diff --git a/apps/web/modules/team/team-view.tsx b/apps/web/modules/team/team-view.tsx new file mode 100644 index 0000000000..a39a718734 --- /dev/null +++ b/apps/web/modules/team/team-view.tsx @@ -0,0 +1,265 @@ +"use client"; + +// This route is reachable by +// 1. /team/[slug] +// 2. / (when on org domain e.g. http://calcom.cal.com/. This is through a rewrite from next.config.js) +// Also the getServerSideProps and default export are reused by +// 1. org/[orgSlug]/team/[slug] +// 2. org/[orgSlug]/[user]/[type] +import classNames from "classnames"; +import Link from "next/link"; +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"; +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 { useToggleQuery } from "@lib/hooks/useToggleQuery"; +import type { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; +import type { inferSSRProps } from "@lib/types/inferSSRProps"; + +import Team from "@components/team/screens/Team"; + +export type PageProps = inferSSRProps; +function TeamPage({ + team, + considerUnpublished, + markdownStrippedBio, + isValidOrgDomain, + currentOrgDomain, +}: PageProps) { + useTheme(team.theme); + const routerQuery = useRouterQuery(); + const pathname = usePathname(); + const showMembers = useToggleQuery("members"); + const { t } = useLocale(); + const isEmbed = useIsEmbed(); + const telemetry = useTelemetry(); + const teamName = team.name || "Nameless Team"; + const isBioEmpty = !team.bio || !team.bio.replace("


", "").length; + const metadata = teamMetadataSchema.parse(team.metadata); + + const teamOrOrgIsPrivate = team.isPrivate || (team?.parent?.isOrganization && team.parent?.isPrivate); + + useEffect(() => { + telemetry.event( + telemetryEventTypes.pageView, + collectPageParameters("/team/[slug]", { isTeamBooking: true }) + ); + }, [telemetry, pathname]); + + if (considerUnpublished) { + const teamSlug = team.slug || metadata?.requestedSlug; + const parentSlug = team.parent?.slug || team.parent?.requestedSlug; + // Show unpublished state for parent Organization itself, if the team is a subteam(team.parent is NOT NULL) + const slugPropertyName = team.parent || team.isOrganization ? "orgSlug" : "teamSlug"; + return ( +
+ +
+ ); + } + + // slug is a route parameter, we don't want to forward it to the next route + const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = routerQuery; + + const EventTypes = ({ eventTypes }: { eventTypes: NonNullable<(typeof team)["eventTypes"]> }) => ( +
    + {eventTypes.map((type, index) => ( +
  • +
    + { + sdkActionManager?.fire("eventTypeSelected", { + eventType: type, + }); + }} + data-testid="event-type-link" + className="flex justify-between"> +
    +
    +

    {type.title}

    +
    + +
    +
    + +
    + +
    +
  • + ))} +
+ ); + + const SubTeams = () => + team.children.length ? ( +
    + {team.children.map((ch, i) => { + const memberCount = team.members.filter( + (mem) => mem.subteams?.includes(ch.slug) && mem.accepted + ).length; + return ( +
  • + +
    +
    + {ch.name} + + {t("number_member", { + count: memberCount, + })} + +
    +
    + mem.subteams?.includes(ch.slug) && mem.accepted)} + /> + +
  • + ); + })} +
+ ) : ( +
+
+
+

{` ${t("org_no_teams_yet")}`}

+

{t("org_no_teams_yet_description")}

+
+
+
+ ); + + const profileImageSrc = getOrgOrTeamAvatar(team); + + return ( + <> + +
+
+
+ +
+

+ {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 && ( +
+
+ + + +
+ )} +
+ )} + + )} +
+ + ); +} + +TeamPage.isBookingPage = true; + +export default TeamPage; diff --git a/apps/web/modules/team/type-view.tsx b/apps/web/modules/team/type-view.tsx new file mode 100644 index 0000000000..c7fc57d7a5 --- /dev/null +++ b/apps/web/modules/team/type-view.tsx @@ -0,0 +1,75 @@ +"use client"; + +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 "@lib/withEmbedSsr"; + +export type PageProps = inferSSRProps & EmbedProps; + +export const getMultipleDurationValue = ( + multipleDurationConfig: number[] | undefined, + queryDuration: string | string[] | null | undefined, + defaultValue: number +) => { + if (!multipleDurationConfig) return null; + if (multipleDurationConfig.includes(Number(queryDuration))) return Number(queryDuration); + return defaultValue; +}; + +function Type({ + slug, + user, + booking, + isEmbed, + isBrandingHidden, + eventData, + isInstantMeeting, + orgBannerUrl, + teamMemberEmail, +}: PageProps) { + const searchParams = useSearchParams(); + + return ( +
+ + +
+ ); +} + +Type.isBookingPage = true; + +export default Type; diff --git a/apps/web/modules/teams/teams-view.tsx b/apps/web/modules/teams/teams-view.tsx new file mode 100644 index 0000000000..63e338dbc1 --- /dev/null +++ b/apps/web/modules/teams/teams-view.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { TeamsListing } from "@calcom/features/ee/teams/components"; +import Shell from "@calcom/features/shell/Shell"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; +import type { inferSSRProps } from "@calcom/types/inferSSRProps"; +import { Button } from "@calcom/ui"; + +import type { getServerSideProps } from "@lib/teams/getServerSideProps"; + +export type PageProps = inferSSRProps; +function Teams(props: PageProps) { + const { t } = useLocale(); + const [user] = trpc.viewer.me.useSuspenseQuery(); + + return ( + + {t("new")} + + ) + }> + + + ); +} + +Teams.requiresLicense = false; + +export default Teams; diff --git a/apps/web/pages/org/[orgSlug]/[user]/[type]/index.tsx b/apps/web/pages/org/[orgSlug]/[user]/[type]/index.tsx index bdf4144857..152e5c7aad 100644 --- a/apps/web/pages/org/[orgSlug]/[user]/[type]/index.tsx +++ b/apps/web/pages/org/[orgSlug]/[user]/[type]/index.tsx @@ -2,12 +2,11 @@ import PageWrapper from "@components/PageWrapper"; +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.getServerSideProps"; -import type { PageProps as TeamTypePageProps } from "../../../../team/[slug]/[type]"; -import TeamTypePage from "../../../../team/[slug]/[type]"; - export { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps"; export type PageProps = UserTypePageProps | TeamTypePageProps; diff --git a/apps/web/pages/org/[orgSlug]/[user]/index.tsx b/apps/web/pages/org/[orgSlug]/[user]/index.tsx index b7f4c42267..001514be6c 100644 --- a/apps/web/pages/org/[orgSlug]/[user]/index.tsx +++ b/apps/web/pages/org/[orgSlug]/[user]/index.tsx @@ -2,12 +2,11 @@ import PageWrapper from "@components/PageWrapper"; +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 UserPageProps } from "~/users/views/users-public-view.getServerSideProps"; -import type { PageProps as TeamPageProps } from "../../../team/[slug]"; -import TeamPage from "../../../team/[slug]"; - export { getServerSideProps } from "@lib/org/[orgSlug]/[user]/getServerSideProps"; export type PageProps = UserPageProps | TeamPageProps; diff --git a/apps/web/pages/org/[orgSlug]/index.ts b/apps/web/pages/org/[orgSlug]/index.ts index c2d35106ab..a29283de79 100644 --- a/apps/web/pages/org/[orgSlug]/index.ts +++ b/apps/web/pages/org/[orgSlug]/index.ts @@ -1 +1 @@ -export { getServerSideProps, default } from "../../team/[slug]"; +export { getServerSideProps, default } from "@pages/team/[slug]"; diff --git a/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts b/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts index 2bfabba062..85e0b39d1a 100644 --- a/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts +++ b/apps/web/pages/org/[orgSlug]/team/[slug]/[type].ts @@ -1 +1 @@ -export { getServerSideProps, default } from "../../../../team/[slug]/[type]"; +export { getServerSideProps, default } from "@pages/team/[slug]/[type]"; diff --git a/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts b/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts index 3a1589d4c4..a29283de79 100644 --- a/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts +++ b/apps/web/pages/org/[orgSlug]/team/[slug]/index.ts @@ -1 +1 @@ -export { getServerSideProps, default } from "../../../../team/[slug]"; +export { getServerSideProps, default } from "@pages/team/[slug]"; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 5e563c6393..34386176f1 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -1,269 +1,10 @@ -"use client"; - -// This route is reachable by -// 1. /team/[slug] -// 2. / (when on org domain e.g. http://calcom.cal.com/. This is through a rewrite from next.config.js) -// Also the getServerSideProps and default export are reused by -// 1. org/[orgSlug]/team/[slug] -// 2. org/[orgSlug]/[user]/[type] -import classNames from "classnames"; -import Link from "next/link"; -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"; -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 { useToggleQuery } from "@lib/hooks/useToggleQuery"; -import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; -import type { inferSSRProps } from "@lib/types/inferSSRProps"; - import PageWrapper from "@components/PageWrapper"; -import Team from "@components/team/screens/Team"; -export { getServerSideProps }; +import TeamPage, { type PageProps } from "~/team/team-view"; -export type PageProps = inferSSRProps; -function TeamPage({ - team, - considerUnpublished, - markdownStrippedBio, - isValidOrgDomain, - currentOrgDomain, -}: PageProps) { - useTheme(team.theme); - const routerQuery = useRouterQuery(); - const pathname = usePathname(); - const showMembers = useToggleQuery("members"); - const { t } = useLocale(); - const isEmbed = useIsEmbed(); - const telemetry = useTelemetry(); - const teamName = team.name || "Nameless Team"; - const isBioEmpty = !team.bio || !team.bio.replace("


", "").length; - const metadata = teamMetadataSchema.parse(team.metadata); +const Page = (props: PageProps) => ; - const teamOrOrgIsPrivate = team.isPrivate || (team?.parent?.isOrganization && team.parent?.isPrivate); +Page.PageWrapper = PageWrapper; - useEffect(() => { - telemetry.event( - telemetryEventTypes.pageView, - collectPageParameters("/team/[slug]", { isTeamBooking: true }) - ); - }, [telemetry, pathname]); - - if (considerUnpublished) { - const teamSlug = team.slug || metadata?.requestedSlug; - const parentSlug = team.parent?.slug || team.parent?.requestedSlug; - // Show unpublished state for parent Organization itself, if the team is a subteam(team.parent is NOT NULL) - const slugPropertyName = team.parent || team.isOrganization ? "orgSlug" : "teamSlug"; - return ( -
- -
- ); - } - - // slug is a route parameter, we don't want to forward it to the next route - const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = routerQuery; - - const EventTypes = ({ eventTypes }: { eventTypes: NonNullable<(typeof team)["eventTypes"]> }) => ( -
    - {eventTypes.map((type, index) => ( -
  • -
    - { - sdkActionManager?.fire("eventTypeSelected", { - eventType: type, - }); - }} - data-testid="event-type-link" - className="flex justify-between"> -
    -
    -

    {type.title}

    -
    - -
    -
    - -
    - -
    -
  • - ))} -
- ); - - const SubTeams = () => - team.children.length ? ( -
    - {team.children.map((ch, i) => { - const memberCount = team.members.filter( - (mem) => mem.subteams?.includes(ch.slug) && mem.accepted - ).length; - return ( -
  • - -
    -
    - {ch.name} - - {t("number_member", { - count: memberCount, - })} - -
    -
    - mem.subteams?.includes(ch.slug) && mem.accepted)} - /> - -
  • - ); - })} -
- ) : ( -
-
-
-

{` ${t("org_no_teams_yet")}`}

-

{t("org_no_teams_yet_description")}

-
-
-
- ); - - const profileImageSrc = getOrgOrTeamAvatar(team); - - return ( - <> - -
-
-
- -
-

- {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 && ( -
-
- - - -
- )} -
- )} - - )} -
- - ); -} - -TeamPage.isBookingPage = true; -TeamPage.PageWrapper = PageWrapper; - -export default TeamPage; +export default Page; +export { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 0482582ccc..1138cc20a1 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -1,78 +1,10 @@ -"use client"; - -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 { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; -import type { inferSSRProps } from "@lib/types/inferSSRProps"; -import type { EmbedProps } from "@lib/withEmbedSsr"; - import PageWrapper from "@components/PageWrapper"; -export type PageProps = inferSSRProps & EmbedProps; +import TypePage, { type PageProps } from "~/team/type-view"; -export { getServerSideProps }; +const Page = (props: PageProps) => ; -export const getMultipleDurationValue = ( - multipleDurationConfig: number[] | undefined, - queryDuration: string | string[] | null | undefined, - defaultValue: number -) => { - if (!multipleDurationConfig) return null; - if (multipleDurationConfig.includes(Number(queryDuration))) return Number(queryDuration); - return defaultValue; -}; +Page.PageWrapper = PageWrapper; -export default function Type({ - slug, - user, - booking, - isEmbed, - isBrandingHidden, - eventData, - isInstantMeeting, - orgBannerUrl, - teamMemberEmail, -}: PageProps) { - const searchParams = useSearchParams(); - - return ( -
- - -
- ); -} - -Type.PageWrapper = PageWrapper; -Type.isBookingPage = true; +export default Page; +export { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; diff --git a/apps/web/pages/team/[slug]/[type]/embed.tsx b/apps/web/pages/team/[slug]/[type]/embed.tsx index 63531e5f39..3f41c896ab 100644 --- a/apps/web/pages/team/[slug]/[type]/embed.tsx +++ b/apps/web/pages/team/[slug]/[type]/embed.tsx @@ -1,9 +1,14 @@ -"use client"; - +import { getServerSideProps as _getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; import withEmbedSsr from "@lib/withEmbedSsr"; -import { getServerSideProps as _getServerSideProps } from "../[type]"; +import PageWrapper from "@components/PageWrapper"; -export { default, type PageProps } from "../[type]"; +import TypePage, { type PageProps } from "~/team/type-view"; export const getServerSideProps = withEmbedSsr(_getServerSideProps); + +const Page = (props: PageProps) => ; + +Page.PageWrapper = PageWrapper; + +export default Page; diff --git a/apps/web/pages/team/[slug]/embed.tsx b/apps/web/pages/team/[slug]/embed.tsx index fa255bc512..1ef45e647a 100644 --- a/apps/web/pages/team/[slug]/embed.tsx +++ b/apps/web/pages/team/[slug]/embed.tsx @@ -1,9 +1,14 @@ -"use client"; - +import { getServerSideProps as _getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; import withEmbedSsr from "@lib/withEmbedSsr"; -import { getServerSideProps as _getServerSideProps } from "../../team/[slug]"; +import PageWrapper from "@components/PageWrapper"; -export { default } from "../../team/[slug]"; +import TeamPage, { type PageProps } from "~/team/team-view"; export const getServerSideProps = withEmbedSsr(_getServerSideProps); + +const Page = (props: PageProps) => ; + +Page.PageWrapper = PageWrapper; + +export default Page; diff --git a/apps/web/pages/teams/index.tsx b/apps/web/pages/teams/index.tsx index dc33b4640b..271dd9941a 100644 --- a/apps/web/pages/teams/index.tsx +++ b/apps/web/pages/teams/index.tsx @@ -1,45 +1,10 @@ -"use client"; - -import { TeamsListing } from "@calcom/features/ee/teams/components"; -import Shell from "@calcom/features/shell/Shell"; -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { trpc } from "@calcom/trpc/react"; -import { Button } from "@calcom/ui"; - import PageWrapper from "@components/PageWrapper"; +import type { PageProps } from "~/teams/teams-view"; +import Teams from "~/teams/teams-view"; + export { getServerSideProps } from "@lib/teams/getServerSideProps"; -function Teams() { - const { t } = useLocale(); - const [user] = trpc.viewer.me.useSuspenseQuery(); - - return ( - - {t("new")} - - ) - }> - - - ); -} - -Teams.requiresLicense = false; -Teams.PageWrapper = PageWrapper; -export default Teams; +const Page = (props: PageProps) => ; +Page.PageWrapper = PageWrapper; +export default Page; diff --git a/packages/embeds/embed-core/playground.ts b/packages/embeds/embed-core/playground.ts index c4d1f6fdf3..f6986c8b0f 100644 --- a/packages/embeds/embed-core/playground.ts +++ b/packages/embeds/embed-core/playground.ts @@ -26,6 +26,9 @@ const searchParams = new URL(document.URL).searchParams; const only = searchParams.get("only"); const colorScheme = searchParams.get("color-scheme"); const prerender = searchParams.get("prerender"); + +window.ENABLE_FUTURE_ROUTES = searchParams.get("future-routes") === "true"; + if (colorScheme) { document.documentElement.style.colorScheme = colorScheme; } diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index b2bef057fe..5ad5554fc9 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -272,6 +272,13 @@ export class Cal { // TODO: Make a list of patterns that are embeddable. All except that should be allowed with a warning that "The page isn't optimized for embedding" urlInstance.pathname = `${urlInstance.pathname}/embed`; } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (window.ENABLE_FUTURE_ROUTES) { + urlInstance.pathname = `/future${urlInstance.pathname}`; + } + urlInstance.searchParams.set("embed", this.namespace); if (config.debug) { diff --git a/packages/lib/server/repository/event.ts b/packages/lib/server/repository/event.ts index 3b25e2e0ce..a7f78bfaed 100644 --- a/packages/lib/server/repository/event.ts +++ b/packages/lib/server/repository/event.ts @@ -1,7 +1,6 @@ import { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent"; import prisma from "@calcom/prisma"; - -import type { TEventInputSchema } from "../../../trpc/server/routers/publicViewer/event.schema"; +import type { TEventInputSchema } from "@calcom/trpc/server/routers/publicViewer/event.schema"; export class EventRepository { static async getPublicEvent(input: TEventInputSchema) {