From b7bb8f4a95a5fb4c9a6bbe72e604d77c15491bdf Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 8 Jan 2024 14:09:30 +0000 Subject: [PATCH 01/10] Migrate /team/* page group --- apps/web/app/AppDirSSRHOC.tsx | 17 ++++++++++ .../future/team/[slug]/[type]/embed/page.tsx | 21 ++++++++++++ .../app/future/team/[slug]/[type]/page.tsx | 33 +++++++++++++++++++ .../web/app/future/team/[slug]/embed/page.tsx | 21 ++++++++++++ apps/web/app/future/team/[slug]/page.tsx | 25 ++++++++++++++ apps/web/pages/team/[slug].tsx | 2 ++ apps/web/pages/team/[slug]/[type].tsx | 2 ++ apps/web/pages/team/[slug]/[type]/embed.tsx | 2 ++ apps/web/pages/team/[slug]/embed.tsx | 2 ++ 9 files changed, 125 insertions(+) create mode 100644 apps/web/app/AppDirSSRHOC.tsx create mode 100644 apps/web/app/future/team/[slug]/[type]/embed/page.tsx create mode 100644 apps/web/app/future/team/[slug]/[type]/page.tsx create mode 100644 apps/web/app/future/team/[slug]/embed/page.tsx create mode 100644 apps/web/app/future/team/[slug]/page.tsx diff --git a/apps/web/app/AppDirSSRHOC.tsx b/apps/web/app/AppDirSSRHOC.tsx new file mode 100644 index 00000000000000..87b419ca49f2f6 --- /dev/null +++ b/apps/web/app/AppDirSSRHOC.tsx @@ -0,0 +1,17 @@ +import type { GetServerSideProps, GetServerSidePropsContext } from "next"; +import { notFound, redirect } from "next/navigation"; + +export const withAppDir = + (getServerSideProps: GetServerSideProps) => async (context: GetServerSidePropsContext) => { + const ssrResponse = await getServerSideProps(context); + + if ("redirect" in ssrResponse) { + redirect(ssrResponse.redirect.destination); + } + + if ("notFound" in ssrResponse) { + notFound(); + } + + return ssrResponse.props; + }; diff --git a/apps/web/app/future/team/[slug]/[type]/embed/page.tsx b/apps/web/app/future/team/[slug]/[type]/embed/page.tsx new file mode 100644 index 00000000000000..ff4f138108b0f7 --- /dev/null +++ b/apps/web/app/future/team/[slug]/[type]/embed/page.tsx @@ -0,0 +1,21 @@ +import { getServerSideProps } from "@pages/team/[slug]/[type]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; +import { cookies, headers } from "next/headers"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import withEmbedSsr from "@lib/withEmbedSsr"; + +type PageProps = Readonly<{ + params: Params; +}>; + +const Page = async ({ params }: PageProps) => { + const legacyCtx = buildLegacyCtx(headers(), cookies(), params); + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' + await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx); + + return null; +}; + +export default Page; diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx new file mode 100644 index 00000000000000..916a09301447be --- /dev/null +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -0,0 +1,33 @@ +import LegacyPage, { getServerSideProps as _getServerSideProps } from "@pages/team/[slug]/[type]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { cookies, headers } from "next/headers"; + +import { trpc } from "@calcom/trpc"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +export const generateMetadata = async ({ params }: { params: Record }) => { + const legacyCtx = buildLegacyCtx(headers(), cookies(), params); + + // @ts-expect-error context arg + const pageProps = await getData(legacyCtx); + const { entity, booking, user, slug } = pageProps; + const rescheduleUid = booking?.uid; + const { data: event } = trpc.viewer.public.event.useQuery( + { username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null }, + { refetchOnWindowFocus: false } + ); + const profileName = event?.profile?.name ?? ""; + const title = event?.title ?? ""; + return await _generateMetadata( + (t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, + (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}` + ); +}; + +const getData = withAppDir(_getServerSideProps); + +// @ts-expect-error getData arg +export default WithLayout({ Page: LegacyPage, getData, getLayout: null })<"P">; diff --git a/apps/web/app/future/team/[slug]/embed/page.tsx b/apps/web/app/future/team/[slug]/embed/page.tsx new file mode 100644 index 00000000000000..e33a236a9bc571 --- /dev/null +++ b/apps/web/app/future/team/[slug]/embed/page.tsx @@ -0,0 +1,21 @@ +import { getServerSideProps } from "@pages/team/[slug]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; +import { cookies, headers } from "next/headers"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import withEmbedSsr from "@lib/withEmbedSsr"; + +type PageProps = Readonly<{ + params: Params; +}>; + +const Page = async ({ params }: PageProps) => { + const legacyCtx = buildLegacyCtx(headers(), cookies(), params); + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' + await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx); + + return null; +}; + +export default Page; diff --git a/apps/web/app/future/team/[slug]/page.tsx b/apps/web/app/future/team/[slug]/page.tsx new file mode 100644 index 00000000000000..b540d59621a4dc --- /dev/null +++ b/apps/web/app/future/team/[slug]/page.tsx @@ -0,0 +1,25 @@ +import LegacyPage, { getServerSideProps as _getServerSideProps } from "@pages/team/[slug]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { cookies, headers } from "next/headers"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +export const generateMetadata = async ({ params }: { params: Record }) => { + const legacyCtx = buildLegacyCtx(headers(), cookies(), params); + + // @ts-expect-error context arg + const props = await getData(legacyCtx); + const teamName = props.team.name || "Nameless Team"; + + return await _generateMetadata( + () => teamName, + () => teamName + ); +}; + +const getData = withAppDir(_getServerSideProps); + +// @ts-expect-error getData arg +export default WithLayout({ Page: LegacyPage, getData, getLayout: null })<"P">; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index a5cdaca7fa4b4b..b45a41e076d44c 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -1,3 +1,5 @@ +"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) diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 781f17d4b8d888..8f584cb906e045 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -1,3 +1,5 @@ +"use client"; + import type { GetServerSidePropsContext } from "next"; import { z } from "zod"; diff --git a/apps/web/pages/team/[slug]/[type]/embed.tsx b/apps/web/pages/team/[slug]/[type]/embed.tsx index 5cfd1726cadada..9b0fb7a7a8b365 100644 --- a/apps/web/pages/team/[slug]/[type]/embed.tsx +++ b/apps/web/pages/team/[slug]/[type]/embed.tsx @@ -1,3 +1,5 @@ +"use client"; + import withEmbedSsr from "@lib/withEmbedSsr"; import { getServerSideProps as _getServerSideProps } from "../[type]"; diff --git a/apps/web/pages/team/[slug]/embed.tsx b/apps/web/pages/team/[slug]/embed.tsx index d220355cc9f9d3..fa255bc51293ad 100644 --- a/apps/web/pages/team/[slug]/embed.tsx +++ b/apps/web/pages/team/[slug]/embed.tsx @@ -1,3 +1,5 @@ +"use client"; + import withEmbedSsr from "@lib/withEmbedSsr"; import { getServerSideProps as _getServerSideProps } from "../../team/[slug]"; From c385eec2c0c0dfeae1023b706a0a5b435bd34edb Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 8 Jan 2024 16:01:41 +0000 Subject: [PATCH 02/10] improve --- apps/web/app/future/team/[slug]/[type]/embed/page.tsx | 5 +++-- apps/web/app/future/team/[slug]/embed/page.tsx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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 ff4f138108b0f7..8ef5d3f197f222 100644 --- a/apps/web/app/future/team/[slug]/[type]/embed/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/embed/page.tsx @@ -1,5 +1,6 @@ import { getServerSideProps } from "@pages/team/[slug]/[type]"; import { withAppDir } from "app/AppDirSSRHOC"; +import type { GetServerSidePropsContext } from "next"; import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; import { cookies, headers } from "next/headers"; @@ -12,8 +13,8 @@ type PageProps = Readonly<{ const Page = async ({ params }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params); - // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' - await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx); + + await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx as unknown as GetServerSidePropsContext); return null; }; diff --git a/apps/web/app/future/team/[slug]/embed/page.tsx b/apps/web/app/future/team/[slug]/embed/page.tsx index e33a236a9bc571..5c7fe8bbf9a2cf 100644 --- a/apps/web/app/future/team/[slug]/embed/page.tsx +++ b/apps/web/app/future/team/[slug]/embed/page.tsx @@ -1,5 +1,6 @@ import { getServerSideProps } from "@pages/team/[slug]"; import { withAppDir } from "app/AppDirSSRHOC"; +import type { GetServerSidePropsContext } from "next"; import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; import { cookies, headers } from "next/headers"; @@ -12,8 +13,8 @@ type PageProps = Readonly<{ const Page = async ({ params }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params); - // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' - await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx); + + await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx as unknown as GetServerSidePropsContext); return null; }; From b82893918e4be3ba12065a14c329a471baa05c58 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 8 Jan 2024 16:16:19 +0000 Subject: [PATCH 03/10] import type --- apps/web/app/future/team/[slug]/[type]/embed/page.tsx | 6 +----- apps/web/app/future/team/[slug]/embed/page.tsx | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) 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 8ef5d3f197f222..edaf30bea25f1f 100644 --- a/apps/web/app/future/team/[slug]/[type]/embed/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/embed/page.tsx @@ -1,16 +1,12 @@ import { getServerSideProps } from "@pages/team/[slug]/[type]"; import { withAppDir } from "app/AppDirSSRHOC"; +import type { PageProps } from "app/_types"; import type { GetServerSidePropsContext } from "next"; -import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; import { cookies, headers } from "next/headers"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import withEmbedSsr from "@lib/withEmbedSsr"; -type PageProps = Readonly<{ - params: Params; -}>; - const Page = async ({ params }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params); diff --git a/apps/web/app/future/team/[slug]/embed/page.tsx b/apps/web/app/future/team/[slug]/embed/page.tsx index 5c7fe8bbf9a2cf..a0116a7dcf23dd 100644 --- a/apps/web/app/future/team/[slug]/embed/page.tsx +++ b/apps/web/app/future/team/[slug]/embed/page.tsx @@ -1,16 +1,12 @@ import { getServerSideProps } from "@pages/team/[slug]"; import { withAppDir } from "app/AppDirSSRHOC"; +import type { PageProps } from "app/_types"; import type { GetServerSidePropsContext } from "next"; -import type { Params } from "next/dist/shared/lib/router/utils/route-matcher"; import { cookies, headers } from "next/headers"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import withEmbedSsr from "@lib/withEmbedSsr"; -type PageProps = Readonly<{ - params: Params; -}>; - const Page = async ({ params }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params); From 1b1d390de9869abbf450fb3e4f82d0aa8022deaf Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 8 Jan 2024 16:35:06 +0000 Subject: [PATCH 04/10] Finalize --- .../team/[slug]/[type]/getServerSideProps.tsx | 102 +++++++++++++ .../lib/team/[slug]/getServerSideProps.tsx | 137 ++++++++++++++++++ apps/web/pages/team/[slug].tsx | 135 +---------------- apps/web/pages/team/[slug]/[type].tsx | 104 +------------ 4 files changed, 243 insertions(+), 235 deletions(-) create mode 100644 apps/web/lib/team/[slug]/[type]/getServerSideProps.tsx create mode 100644 apps/web/lib/team/[slug]/getServerSideProps.tsx diff --git a/apps/web/lib/team/[slug]/[type]/getServerSideProps.tsx b/apps/web/lib/team/[slug]/[type]/getServerSideProps.tsx new file mode 100644 index 00000000000000..169e275c376f6c --- /dev/null +++ b/apps/web/lib/team/[slug]/[type]/getServerSideProps.tsx @@ -0,0 +1,102 @@ +import type { GetServerSidePropsContext } from "next"; +import { z } from "zod"; + +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking"; +import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; +import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; +import slugify from "@calcom/lib/slugify"; +import prisma from "@calcom/prisma"; +import { RedirectType } from "@calcom/prisma/client"; + +import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect"; + +const paramsSchema = z.object({ + type: z.string().transform((s) => slugify(s)), + slug: z.string().transform((s) => slugify(s)), +}); + +// Booker page fetches a tiny bit of data server side: +// 1. Check if team exists, to show 404 +// 2. If rescheduling, get the booking details +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const session = await getServerSession(context); + const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params); + const { rescheduleUid, duration: queryDuration, isInstantMeeting: queryIsInstantMeeting } = context.query; + const { ssrInit } = await import("@server/lib/ssr"); + const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + const isOrgContext = currentOrgDomain && isValidOrgDomain; + + if (!isOrgContext) { + const redirect = await getTemporaryOrgRedirect({ + slug: teamSlug, + redirectType: RedirectType.Team, + eventTypeSlug: meetingSlug, + currentQuery: context.query, + }); + + if (redirect) { + return redirect; + } + } + + const team = await prisma.team.findFirst({ + where: { + ...getSlugOrRequestedSlug(teamSlug), + parent: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null, + }, + select: { + id: true, + hideBranding: true, + }, + }); + + if (!team) { + return { + notFound: true, + } as const; + } + + let booking: GetBookingType | null = null; + if (rescheduleUid) { + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); + } + + const org = isValidOrgDomain ? currentOrgDomain : null; + // We use this to both prefetch the query on the server, + // as well as to check if the event exist, so we c an show a 404 otherwise. + const eventData = await ssr.viewer.public.event.fetch({ + username: teamSlug, + eventSlug: meetingSlug, + isTeamEvent: true, + org, + }); + + if (!eventData) { + return { + notFound: true, + } as const; + } + + return { + props: { + entity: eventData.entity, + duration: getMultipleDurationValue( + eventData.metadata?.multipleDuration, + queryDuration, + eventData.length + ), + booking, + away: false, + user: teamSlug, + teamId: team.id, + slug: meetingSlug, + trpcState: ssr.dehydrate(), + isBrandingHidden: team?.hideBranding, + isInstantMeeting: eventData.isInstantEvent && queryIsInstantMeeting ? true : false, + themeBasis: null, + }, + }; +}; diff --git a/apps/web/lib/team/[slug]/getServerSideProps.tsx b/apps/web/lib/team/[slug]/getServerSideProps.tsx new file mode 100644 index 00000000000000..b6b535eeab4c20 --- /dev/null +++ b/apps/web/lib/team/[slug]/getServerSideProps.tsx @@ -0,0 +1,137 @@ +import type { GetServerSidePropsContext } from "next"; + +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; +import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; +import logger from "@calcom/lib/logger"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; +import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; +import slugify from "@calcom/lib/slugify"; +import { stripMarkdown } from "@calcom/lib/stripMarkdown"; +import prisma from "@calcom/prisma"; +import { RedirectType } from "@calcom/prisma/client"; +import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; + +import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect"; + +import { ssrInit } from "@server/lib/ssr"; + +const log = logger.getSubLogger({ prefix: ["team/[slug]"] }); + +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug; + const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + const isOrgContext = isValidOrgDomain && currentOrgDomain; + + // Provided by Rewrite from next.config.js + const isOrgProfile = context.query?.isOrgProfile === "1"; + const flags = await getFeatureFlagMap(prisma); + const isOrganizationFeatureEnabled = flags["organizations"]; + + log.debug("getServerSideProps", { + isOrgProfile, + isOrganizationFeatureEnabled, + isValidOrgDomain, + currentOrgDomain, + }); + + const team = await getTeamWithMembers({ + slug: slugify(slug ?? ""), + orgSlug: currentOrgDomain, + isTeamView: true, + isOrgView: isValidOrgDomain && isOrgProfile, + }); + + if (!isOrgContext && slug) { + const redirect = await getTemporaryOrgRedirect({ + slug: slug, + redirectType: RedirectType.Team, + eventTypeSlug: null, + currentQuery: context.query, + }); + + if (redirect) { + return redirect; + } + } + + const ssr = await ssrInit(context); + const metadata = teamMetadataSchema.parse(team?.metadata ?? {}); + + // Taking care of sub-teams and orgs + if ( + (!isValidOrgDomain && team?.parent) || + (!isValidOrgDomain && !!metadata?.isOrganization) || + !isOrganizationFeatureEnabled + ) { + return { notFound: true } as const; + } + + if (!team || (team.parent && !team.parent.slug)) { + const unpublishedTeam = await prisma.team.findFirst({ + where: { + ...(team?.parent + ? { id: team.parent.id } + : { + metadata: { + path: ["requestedSlug"], + equals: slug, + }, + }), + }, + }); + + if (!unpublishedTeam) return { notFound: true } as const; + + return { + props: { + isUnpublished: true, + team: { ...unpublishedTeam, createdAt: null }, + trpcState: ssr.dehydrate(), + }, + } as const; + } + + team.eventTypes = + team.eventTypes?.map((type) => ({ + ...type, + users: type.users.map((user) => ({ + ...user, + avatar: `/${user.username}/avatar.png`, + })), + descriptionAsSafeHTML: markdownToSafeHTML(type.description), + })) ?? null; + + const safeBio = markdownToSafeHTML(team.bio) || ""; + + const members = !team.isPrivate + ? team.members.map((member) => { + return { + name: member.name, + id: member.id, + bio: member.bio, + subteams: member.subteams, + username: member.username, + accepted: member.accepted, + organizationId: member.organizationId, + safeBio: markdownToSafeHTML(member.bio || ""), + bookerUrl: getBookerBaseUrlSync(member.organization?.slug || ""), + }; + }) + : []; + + const markdownStrippedBio = stripMarkdown(team?.bio || ""); + + const { inviteToken: _inviteToken, ...serializableTeam } = team; + + return { + props: { + team: { ...serializableTeam, safeBio, members, metadata }, + themeBasis: serializableTeam.slug, + trpcState: ssr.dehydrate(), + markdownStrippedBio, + isValidOrgDomain, + currentOrgDomain, + }, + } as const; +}; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index b45a41e076d44c..5cbff4f3f4043c 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -7,45 +7,32 @@ // 1. org/[orgSlug]/team/[slug] // 2. org/[orgSlug]/[user]/[type] import classNames from "classnames"; -import type { GetServerSidePropsContext } from "next"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect } from "react"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; -import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription"; -import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { WEBAPP_URL } from "@calcom/lib/constants"; -import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; -import logger from "@calcom/lib/logger"; -import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; -import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; -import slugify from "@calcom/lib/slugify"; -import { stripMarkdown } from "@calcom/lib/stripMarkdown"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; -import prisma from "@calcom/prisma"; -import { RedirectType } from "@calcom/prisma/client"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { Avatar, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; import { useToggleQuery } from "@lib/hooks/useToggleQuery"; +import type { 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"; import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup"; -import { ssrInit } from "@server/lib/ssr"; - -import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect"; +export { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; export type PageProps = inferSSRProps; -const log = logger.getSubLogger({ prefix: ["team/[slug]"] }); function TeamPage({ team, isUnpublished, @@ -270,124 +257,6 @@ function TeamPage({ ); } -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug; - const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); - const isOrgContext = isValidOrgDomain && currentOrgDomain; - - // Provided by Rewrite from next.config.js - const isOrgProfile = context.query?.isOrgProfile === "1"; - const flags = await getFeatureFlagMap(prisma); - const isOrganizationFeatureEnabled = flags["organizations"]; - - log.debug("getServerSideProps", { - isOrgProfile, - isOrganizationFeatureEnabled, - isValidOrgDomain, - currentOrgDomain, - }); - - const team = await getTeamWithMembers({ - slug: slugify(slug ?? ""), - orgSlug: currentOrgDomain, - isTeamView: true, - isOrgView: isValidOrgDomain && isOrgProfile, - }); - - if (!isOrgContext && slug) { - const redirect = await getTemporaryOrgRedirect({ - slug: slug, - redirectType: RedirectType.Team, - eventTypeSlug: null, - currentQuery: context.query, - }); - - if (redirect) { - return redirect; - } - } - - const ssr = await ssrInit(context); - const metadata = teamMetadataSchema.parse(team?.metadata ?? {}); - - // Taking care of sub-teams and orgs - if ( - (!isValidOrgDomain && team?.parent) || - (!isValidOrgDomain && !!metadata?.isOrganization) || - !isOrganizationFeatureEnabled - ) { - return { notFound: true } as const; - } - - if (!team || (team.parent && !team.parent.slug)) { - const unpublishedTeam = await prisma.team.findFirst({ - where: { - ...(team?.parent - ? { id: team.parent.id } - : { - metadata: { - path: ["requestedSlug"], - equals: slug, - }, - }), - }, - }); - - if (!unpublishedTeam) return { notFound: true } as const; - - return { - props: { - isUnpublished: true, - team: { ...unpublishedTeam, createdAt: null }, - trpcState: ssr.dehydrate(), - }, - } as const; - } - - team.eventTypes = - team.eventTypes?.map((type) => ({ - ...type, - users: type.users.map((user) => ({ - ...user, - avatar: `/${user.username}/avatar.png`, - })), - descriptionAsSafeHTML: markdownToSafeHTML(type.description), - })) ?? null; - - const safeBio = markdownToSafeHTML(team.bio) || ""; - - const members = !team.isPrivate - ? team.members.map((member) => { - return { - name: member.name, - id: member.id, - bio: member.bio, - subteams: member.subteams, - username: member.username, - accepted: member.accepted, - organizationId: member.organizationId, - safeBio: markdownToSafeHTML(member.bio || ""), - bookerUrl: getBookerBaseUrlSync(member.organization?.slug || ""), - }; - }) - : []; - - const markdownStrippedBio = stripMarkdown(team?.bio || ""); - - const { inviteToken: _inviteToken, ...serializableTeam } = team; - - return { - props: { - team: { ...serializableTeam, safeBio, members, metadata }, - themeBasis: serializableTeam.slug, - trpcState: ssr.dehydrate(), - markdownStrippedBio, - isValidOrgDomain, - currentOrgDomain, - }, - } as const; -}; - TeamPage.isBookingPage = true; TeamPage.PageWrapper = PageWrapper; diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 8f584cb906e045..1a3304dc4b740a 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -1,29 +1,18 @@ "use client"; -import type { GetServerSidePropsContext } from "next"; -import { z } from "zod"; - import { Booker } from "@calcom/atoms"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses"; import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; -import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking"; -import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; -import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains"; -import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; -import slugify from "@calcom/lib/slugify"; -import prisma from "@calcom/prisma"; -import { RedirectType } from "@calcom/prisma/client"; +import type { 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"; -import { getTemporaryOrgRedirect } from "../../../lib/getTemporaryOrgRedirect"; - export type PageProps = inferSSRProps & EmbedProps; +export { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; export default function Type({ slug, user, @@ -63,92 +52,3 @@ export default function Type({ Type.PageWrapper = PageWrapper; Type.isBookingPage = true; - -const paramsSchema = z.object({ - type: z.string().transform((s) => slugify(s)), - slug: z.string().transform((s) => slugify(s)), -}); - -// Booker page fetches a tiny bit of data server side: -// 1. Check if team exists, to show 404 -// 2. If rescheduling, get the booking details -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const session = await getServerSession(context); - const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params); - const { rescheduleUid, duration: queryDuration, isInstantMeeting: queryIsInstantMeeting } = context.query; - const { ssrInit } = await import("@server/lib/ssr"); - const ssr = await ssrInit(context); - const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); - const isOrgContext = currentOrgDomain && isValidOrgDomain; - - if (!isOrgContext) { - const redirect = await getTemporaryOrgRedirect({ - slug: teamSlug, - redirectType: RedirectType.Team, - eventTypeSlug: meetingSlug, - currentQuery: context.query, - }); - - if (redirect) { - return redirect; - } - } - - const team = await prisma.team.findFirst({ - where: { - ...getSlugOrRequestedSlug(teamSlug), - parent: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null, - }, - select: { - id: true, - hideBranding: true, - }, - }); - - if (!team) { - return { - notFound: true, - } as const; - } - - let booking: GetBookingType | null = null; - if (rescheduleUid) { - booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); - } - - const org = isValidOrgDomain ? currentOrgDomain : null; - // We use this to both prefetch the query on the server, - // as well as to check if the event exist, so we c an show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ - username: teamSlug, - eventSlug: meetingSlug, - isTeamEvent: true, - org, - }); - - if (!eventData) { - return { - notFound: true, - } as const; - } - - return { - props: { - entity: eventData.entity, - duration: getMultipleDurationValue( - eventData.metadata?.multipleDuration, - queryDuration, - eventData.length - ), - booking, - away: false, - user: teamSlug, - teamId: team.id, - slug: meetingSlug, - trpcState: ssr.dehydrate(), - isBrandingHidden: team?.hideBranding, - isInstantMeeting: eventData.isInstantEvent && queryIsInstantMeeting ? true : false, - themeBasis: null, - }, - }; -}; From ca5118e875f8c60bddb3b0813c483b3628488033 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 8 Jan 2024 16:46:09 +0000 Subject: [PATCH 05/10] support isBookingPage --- apps/web/app/future/team/[slug]/[type]/page.tsx | 9 +++++++-- apps/web/app/future/team/[slug]/page.tsx | 2 +- apps/web/app/layoutHOC.tsx | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx index 916a09301447be..1647c8c906a919 100644 --- a/apps/web/app/future/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -2,13 +2,18 @@ import LegacyPage, { getServerSideProps as _getServerSideProps } from "@pages/te import { withAppDir } from "app/AppDirSSRHOC"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; +import type { Metadata } from "next"; import { cookies, headers } from "next/headers"; import { trpc } from "@calcom/trpc"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; -export const generateMetadata = async ({ params }: { params: Record }) => { +export const generateMetadata = async ({ + params, +}: { + params: Record; +}): Promise => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params); // @ts-expect-error context arg @@ -30,4 +35,4 @@ export const generateMetadata = async ({ params }: { params: Record; +export default WithLayout({ Page: LegacyPage, getData, getLayout: null, isBookingPage: true })<"P">; diff --git a/apps/web/app/future/team/[slug]/page.tsx b/apps/web/app/future/team/[slug]/page.tsx index b540d59621a4dc..23d78b009cb9c2 100644 --- a/apps/web/app/future/team/[slug]/page.tsx +++ b/apps/web/app/future/team/[slug]/page.tsx @@ -22,4 +22,4 @@ export const generateMetadata = async ({ params }: { params: Record; +export default WithLayout({ Page: LegacyPage, getData, getLayout: null, isBookingPage: true })<"P">; diff --git a/apps/web/app/layoutHOC.tsx b/apps/web/app/layoutHOC.tsx index 5e41ed11840789..208e5629fddea3 100644 --- a/apps/web/app/layoutHOC.tsx +++ b/apps/web/app/layoutHOC.tsx @@ -9,9 +9,15 @@ type WithLayoutParams> = { getLayout: ((page: React.ReactElement) => React.ReactNode) | null; Page?: (props: T) => React.ReactElement; getData?: (arg: ReturnType) => Promise; + isBookingPage?: boolean; }; -export function WithLayout>({ getLayout, getData, Page }: WithLayoutParams) { +export function WithLayout>({ + getLayout, + getData, + Page, + isBookingPage, +}: WithLayoutParams) { return async

(p: P extends "P" ? PageProps : LayoutProps) => { const h = headers(); const nonce = h.get("x-nonce") ?? undefined; @@ -20,7 +26,13 @@ export function WithLayout>({ getLayout, getData, const children = "children" in p ? p.children : null; return ( - + {Page ? : children} ); From 0042e86e9b8ec16aff0a4b6062acbd1c409d1172 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 9 Jan 2024 15:50:25 +0000 Subject: [PATCH 06/10] finalize --- apps/web/app/AppDirSSRHOC.tsx | 9 +++++++-- apps/web/app/future/team/[slug]/[type]/page.tsx | 14 +++++++++----- apps/web/app/layoutHOC.tsx | 7 +++++-- apps/web/pages/team/[slug].tsx | 4 ++-- apps/web/pages/team/[slug]/[type].tsx | 5 +++-- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/web/app/AppDirSSRHOC.tsx b/apps/web/app/AppDirSSRHOC.tsx index 87b419ca49f2f6..5a06c74787ff70 100644 --- a/apps/web/app/AppDirSSRHOC.tsx +++ b/apps/web/app/AppDirSSRHOC.tsx @@ -2,7 +2,8 @@ import type { GetServerSideProps, GetServerSidePropsContext } from "next"; import { notFound, redirect } from "next/navigation"; export const withAppDir = - (getServerSideProps: GetServerSideProps) => async (context: GetServerSidePropsContext) => { + >(getServerSideProps: GetServerSideProps) => + async (context: GetServerSidePropsContext): Promise => { const ssrResponse = await getServerSideProps(context); if ("redirect" in ssrResponse) { @@ -13,5 +14,9 @@ export const withAppDir = notFound(); } - return ssrResponse.props; + return { + ...ssrResponse.props, + // includes dehydratedState required for future page trpcPropvider + ...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }), + }; }; diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx index 1647c8c906a919..fc8d6c954af4d7 100644 --- a/apps/web/app/future/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -2,7 +2,7 @@ import LegacyPage, { getServerSideProps as _getServerSideProps } from "@pages/te import { withAppDir } from "app/AppDirSSRHOC"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import type { Metadata } from "next"; +import type { GetServerSidePropsContext, Metadata } from "next"; import { cookies, headers } from "next/headers"; import { trpc } from "@calcom/trpc"; @@ -16,8 +16,7 @@ export const generateMetadata = async ({ }): Promise => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params); - // @ts-expect-error context arg - const pageProps = await getData(legacyCtx); + const pageProps = await getData(legacyCtx as unknown as GetServerSidePropsContext); const { entity, booking, user, slug } = pageProps; const rescheduleUid = booking?.uid; const { data: event } = trpc.viewer.public.event.useQuery( @@ -34,5 +33,10 @@ export const generateMetadata = async ({ const getData = withAppDir(_getServerSideProps); -// @ts-expect-error getData arg -export default WithLayout({ Page: LegacyPage, getData, getLayout: null, isBookingPage: true })<"P">; +export default WithLayout({ + Page: LegacyPage, + // @ts-expect-error getData arg type is not compatible with PageProps + getData, + getLayout: null, + isBookingPage: true, +})<"P">; diff --git a/apps/web/app/layoutHOC.tsx b/apps/web/app/layoutHOC.tsx index 208e5629fddea3..d81ede5d459d38 100644 --- a/apps/web/app/layoutHOC.tsx +++ b/apps/web/app/layoutHOC.tsx @@ -1,4 +1,5 @@ import type { LayoutProps, PageProps } from "app/_types"; +import { type GetServerSidePropsContext } from "next"; import { cookies, headers } from "next/headers"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; @@ -8,7 +9,7 @@ import PageWrapper from "@components/PageWrapperAppDir"; type WithLayoutParams> = { getLayout: ((page: React.ReactElement) => React.ReactNode) | null; Page?: (props: T) => React.ReactElement; - getData?: (arg: ReturnType) => Promise; + getData?: (arg: GetServerSidePropsContext) => Promise; isBookingPage?: boolean; }; @@ -21,7 +22,9 @@ export function WithLayout>({ return async

(p: P extends "P" ? PageProps : LayoutProps) => { const h = headers(); const nonce = h.get("x-nonce") ?? undefined; - const props = getData ? await getData(buildLegacyCtx(h, cookies(), p.params)) : ({} as T); + const props = getData + ? await getData(buildLegacyCtx(h, cookies(), p.params) as unknown as GetServerSidePropsContext) + : ({} as T); const children = "children" in p ? p.children : null; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 5cbff4f3f4043c..678303d0290ac4 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -23,14 +23,14 @@ import { Avatar, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; import { useToggleQuery } from "@lib/hooks/useToggleQuery"; -import type { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; +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"; import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup"; -export { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; +export { getServerSideProps }; export type PageProps = inferSSRProps; function TeamPage({ diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 1a3304dc4b740a..933dd5e695faca 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -4,7 +4,7 @@ import { Booker } from "@calcom/atoms"; 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 { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; import type { EmbedProps } from "@lib/withEmbedSsr"; @@ -12,7 +12,8 @@ import PageWrapper from "@components/PageWrapper"; export type PageProps = inferSSRProps & EmbedProps; -export { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; +export { getServerSideProps }; + export default function Type({ slug, user, From 152bcd1717c0b9c23f58a3d0b7027b383442249c Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 9 Jan 2024 17:12:43 +0000 Subject: [PATCH 07/10] fix --- apps/web/app/future/team/[slug]/[type]/page.tsx | 1 - apps/web/app/future/team/[slug]/page.tsx | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx index fc8d6c954af4d7..91e6a812803fac 100644 --- a/apps/web/app/future/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -35,7 +35,6 @@ const getData = withAppDir(_getServerSideProps); export default WithLayout({ Page: LegacyPage, - // @ts-expect-error getData arg type is not compatible with PageProps getData, getLayout: null, isBookingPage: true, diff --git a/apps/web/app/future/team/[slug]/page.tsx b/apps/web/app/future/team/[slug]/page.tsx index 23d78b009cb9c2..d6c32e57f221ea 100644 --- a/apps/web/app/future/team/[slug]/page.tsx +++ b/apps/web/app/future/team/[slug]/page.tsx @@ -2,15 +2,15 @@ import LegacyPage, { getServerSideProps as _getServerSideProps } from "@pages/te import { withAppDir } from "app/AppDirSSRHOC"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; +import { type GetServerSidePropsContext } from "next"; import { cookies, headers } from "next/headers"; import { buildLegacyCtx } from "@lib/buildLegacyCtx"; export const generateMetadata = async ({ params }: { params: Record }) => { - const legacyCtx = buildLegacyCtx(headers(), cookies(), params); - - // @ts-expect-error context arg - const props = await getData(legacyCtx); + const props = await getData( + buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext + ); const teamName = props.team.name || "Nameless Team"; return await _generateMetadata( @@ -21,5 +21,4 @@ export const generateMetadata = async ({ params }: { params: Record; From 85ed0785e14aa3a7eef7c88bc702d865e51c0128 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 9 Jan 2024 18:55:33 +0000 Subject: [PATCH 08/10] Fix build error --- .../app/future/team/[slug]/[type]/page.tsx | 32 +++---------------- apps/web/app/future/team/[slug]/page.tsx | 8 ++++- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx index 91e6a812803fac..b86bd40858ffa2 100644 --- a/apps/web/app/future/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -2,39 +2,17 @@ import LegacyPage, { getServerSideProps as _getServerSideProps } from "@pages/te import { withAppDir } from "app/AppDirSSRHOC"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import type { GetServerSidePropsContext, Metadata } from "next"; -import { cookies, headers } from "next/headers"; -import { trpc } from "@calcom/trpc"; - -import { buildLegacyCtx } from "@lib/buildLegacyCtx"; - -export const generateMetadata = async ({ - params, -}: { - params: Record; -}): Promise => { - const legacyCtx = buildLegacyCtx(headers(), cookies(), params); - - const pageProps = await getData(legacyCtx as unknown as GetServerSidePropsContext); - const { entity, booking, user, slug } = pageProps; - const rescheduleUid = booking?.uid; - const { data: event } = trpc.viewer.public.event.useQuery( - { username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null }, - { refetchOnWindowFocus: false } +export const generateMetadata = async () => + await _generateMetadata( + () => "", + () => "" ); - const profileName = event?.profile?.name ?? ""; - const title = event?.title ?? ""; - return await _generateMetadata( - (t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`, - (t) => `${rescheduleUid ? t("reschedule") : ""} ${title}` - ); -}; - const getData = withAppDir(_getServerSideProps); export default WithLayout({ Page: LegacyPage, + // @ts-expect-error getData arg getData, getLayout: null, isBookingPage: true, diff --git a/apps/web/app/future/team/[slug]/page.tsx b/apps/web/app/future/team/[slug]/page.tsx index d6c32e57f221ea..7984f34f02d16d 100644 --- a/apps/web/app/future/team/[slug]/page.tsx +++ b/apps/web/app/future/team/[slug]/page.tsx @@ -21,4 +21,10 @@ export const generateMetadata = async ({ params }: { params: Record; +export default WithLayout({ + Page: LegacyPage, + // @ts-expect-error getData arg + getData, + getLayout: null, + isBookingPage: true, +})<"P">; From 797ed07b4df0e0103362cdb23c6b478dd882d2ae Mon Sep 17 00:00:00 2001 From: Greg Pabian <35925521+grzpab@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:50:39 +0100 Subject: [PATCH 09/10] chore: migrate the "/org/*" page group --- .../app/future/org/[orgSlug]/embed/page.tsx | 22 +++++++++++++++++++ apps/web/app/future/org/[orgSlug]/page.tsx | 21 ++++++++++++++++++ .../org/[orgSlug]/team/[slug]/[type]/page.tsx | 21 ++++++++++++++++++ .../future/org/[orgSlug]/team/[slug]/page.tsx | 21 ++++++++++++++++++ .../app/future/team/[slug]/[type]/page.tsx | 4 ++-- apps/web/app/layoutHOC.tsx | 2 +- .../lib/team/[slug]/getServerSideProps.tsx | 16 ++++++++++++-- 7 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 apps/web/app/future/org/[orgSlug]/embed/page.tsx create mode 100644 apps/web/app/future/org/[orgSlug]/page.tsx create mode 100644 apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx create mode 100644 apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx diff --git a/apps/web/app/future/org/[orgSlug]/embed/page.tsx b/apps/web/app/future/org/[orgSlug]/embed/page.tsx new file mode 100644 index 00000000000000..c8437cb966290d --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/embed/page.tsx @@ -0,0 +1,22 @@ +import TeamPage from "@pages/team/[slug]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import type { WithLayoutParams } from "app/layoutHOC"; +import { WithLayout } from "app/layoutHOC"; + +import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; +import withEmbedSsr from "@lib/withEmbedSsr"; + +export const generateMetadata = async () => + await _generateMetadata( + // TODO use a simple prisma call to get the organization name + (t) => t("TODO"), + (t) => t("TODO") + ); + +export default WithLayout({ + Page: TeamPage, + getData: withAppDir(withEmbedSsr(getServerSideProps)) as WithLayoutParams["getData"], + getLayout: null, + isBookingPage: true, +})<"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 00000000000000..f47dddf916f260 --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/page.tsx @@ -0,0 +1,21 @@ +import TeamPage from "@pages/team/[slug]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import type { WithLayoutParams } from "app/layoutHOC"; +import { WithLayout } from "app/layoutHOC"; + +import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; + +export const generateMetadata = async () => + await _generateMetadata( + // TODO use a simple prisma call to get the organization name + (t) => t("TODO"), + (t) => t("TODO") + ); + +export default WithLayout({ + Page: TeamPage, + getData: withAppDir(getServerSideProps) as WithLayoutParams["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 00000000000000..1baeba5478589f --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/[type]/page.tsx @@ -0,0 +1,21 @@ +import TypePage from "@pages/team/[slug]/[type]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import type { WithLayoutParams } from "app/layoutHOC"; +import { WithLayout } from "app/layoutHOC"; + +import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps"; + +export const generateMetadata = async () => + await _generateMetadata( + // TODO use a simple prisma call to get the organization name + (t) => t("TODO"), + (t) => t("TODO") + ); + +export default WithLayout({ + Page: TypePage, + getData: withAppDir(getServerSideProps) as WithLayoutParams["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 00000000000000..f47dddf916f260 --- /dev/null +++ b/apps/web/app/future/org/[orgSlug]/team/[slug]/page.tsx @@ -0,0 +1,21 @@ +import TeamPage from "@pages/team/[slug]"; +import { withAppDir } from "app/AppDirSSRHOC"; +import { _generateMetadata } from "app/_utils"; +import type { WithLayoutParams } from "app/layoutHOC"; +import { WithLayout } from "app/layoutHOC"; + +import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps"; + +export const generateMetadata = async () => + await _generateMetadata( + // TODO use a simple prisma call to get the organization name + (t) => t("TODO"), + (t) => t("TODO") + ); + +export default WithLayout({ + Page: TeamPage, + getData: withAppDir(getServerSideProps) as WithLayoutParams["getData"], + getLayout: null, + isBookingPage: true, +})<"P">; diff --git a/apps/web/app/future/team/[slug]/[type]/page.tsx b/apps/web/app/future/team/[slug]/[type]/page.tsx index b86bd40858ffa2..2df3f054e294e7 100644 --- a/apps/web/app/future/team/[slug]/[type]/page.tsx +++ b/apps/web/app/future/team/[slug]/[type]/page.tsx @@ -8,11 +8,11 @@ export const generateMetadata = async () => () => "", () => "" ); -const getData = withAppDir(_getServerSideProps); + +const getData = withAppDir(_getServerSideProps) as any; export default WithLayout({ Page: LegacyPage, - // @ts-expect-error getData arg getData, getLayout: null, isBookingPage: true, diff --git a/apps/web/app/layoutHOC.tsx b/apps/web/app/layoutHOC.tsx index d81ede5d459d38..2e56329b753fd8 100644 --- a/apps/web/app/layoutHOC.tsx +++ b/apps/web/app/layoutHOC.tsx @@ -6,7 +6,7 @@ import { buildLegacyCtx } from "@lib/buildLegacyCtx"; import PageWrapper from "@components/PageWrapperAppDir"; -type WithLayoutParams> = { +export type WithLayoutParams> = { getLayout: ((page: React.ReactElement) => React.ReactNode) | null; Page?: (props: T) => React.ReactElement; getData?: (arg: GetServerSidePropsContext) => Promise; diff --git a/apps/web/lib/team/[slug]/getServerSideProps.tsx b/apps/web/lib/team/[slug]/getServerSideProps.tsx index b6b535eeab4c20..4cf5042dcb42e8 100644 --- a/apps/web/lib/team/[slug]/getServerSideProps.tsx +++ b/apps/web/lib/team/[slug]/getServerSideProps.tsx @@ -18,9 +18,21 @@ import { ssrInit } from "@server/lib/ssr"; const log = logger.getSubLogger({ prefix: ["team/[slug]"] }); +const getTheLastArrayElement = (value: ReadonlyArray | string | undefined): string | undefined => { + if (value === undefined || typeof value === "string") { + return value; + } + + return value.at(-1); +}; + export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug; - const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); + const slug = getTheLastArrayElement(context.query.slug) ?? getTheLastArrayElement(context.query.orgSlug); + + const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig( + context.req, + context.params?.orgSlug ?? context.query?.orgSlug + ); const isOrgContext = isValidOrgDomain && currentOrgDomain; // Provided by Rewrite from next.config.js From 85ccb5ba1dac3b95c5a6ef36404f4eae9e5e3a3a Mon Sep 17 00:00:00 2001 From: Greg Pabian <35925521+grzpab@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:54:02 +0100 Subject: [PATCH 10/10] search params fixes --- apps/web/app/layoutHOC.tsx | 7 ++++++- apps/web/lib/buildLegacyCtx.tsx | 22 ++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/web/app/layoutHOC.tsx b/apps/web/app/layoutHOC.tsx index 2e56329b753fd8..b6e9e5b3586dc6 100644 --- a/apps/web/app/layoutHOC.tsx +++ b/apps/web/app/layoutHOC.tsx @@ -22,8 +22,13 @@ export function WithLayout>({ return async

(p: P extends "P" ? PageProps : LayoutProps) => { const h = headers(); const nonce = h.get("x-nonce") ?? undefined; + + const searchParams = "searchParams" in p ? p.searchParams : {}; + const props = getData - ? await getData(buildLegacyCtx(h, cookies(), p.params) as unknown as GetServerSidePropsContext) + ? await getData( + buildLegacyCtx(h, cookies(), p.params, searchParams) as unknown as GetServerSidePropsContext + ) : ({} as T); const children = "children" in p ? p.children : null; diff --git a/apps/web/lib/buildLegacyCtx.tsx b/apps/web/lib/buildLegacyCtx.tsx index 2dfbf3ff2f3ed2..97cfaaa6431090 100644 --- a/apps/web/lib/buildLegacyCtx.tsx +++ b/apps/web/lib/buildLegacyCtx.tsx @@ -1,22 +1,16 @@ +import type { SearchParams } from "app/_types"; import { type Params } from "app/_types"; import { type ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers"; import { type ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; -// returns query object same as ctx.query but for app dir -export const getQuery = (url: string, params: Params) => { - if (!url.length) { - return params; - } - - const { searchParams } = new URL(url); - const searchParamsObj = Object.fromEntries(searchParams.entries()); - - return { ...searchParamsObj, ...params }; -}; - -export const buildLegacyCtx = (headers: ReadonlyHeaders, cookies: ReadonlyRequestCookies, params: Params) => { +export const buildLegacyCtx = ( + headers: ReadonlyHeaders, + cookies: ReadonlyRequestCookies, + params: Params, + searchParams: SearchParams +) => { return { - query: getQuery(headers.get("x-url") ?? "", params), + query: { ...searchParams, ...params }, params, req: { headers, cookies }, };