diff --git a/apps/web/app/_trpc/createTRPCNextLayout.ts b/apps/web/app/_trpc/createTRPCNextLayout.ts deleted file mode 100644 index c65b4fe941..0000000000 --- a/apps/web/app/_trpc/createTRPCNextLayout.ts +++ /dev/null @@ -1,212 +0,0 @@ -// originally from in the "experimental playground for tRPC + next.js 13" repo owned by trpc team -// file link: https://github.com/trpc/next-13/blob/main/%40trpc/next-layout/createTRPCNextLayout.ts -// repo link: https://github.com/trpc/next-13 -// code is / will continue to be adapted for our usage -import { dehydrate, QueryClient } from "@tanstack/query-core"; -import type { DehydratedState, QueryKey } from "@tanstack/react-query"; - -import type { Maybe, TRPCClientError, TRPCClientErrorLike } from "@calcom/trpc"; -import { - callProcedure, - type AnyProcedure, - type AnyQueryProcedure, - type AnyRouter, - type DataTransformer, - type inferProcedureInput, - type inferProcedureOutput, - type inferRouterContext, - type MaybePromise, - type ProcedureRouterRecord, -} from "@calcom/trpc/server"; - -import { createRecursiveProxy, createFlatProxy } from "@trpc/server/shared"; - -export function getArrayQueryKey( - queryKey: string | [string] | [string, ...unknown[]] | unknown[], - type: string -): QueryKey { - const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey]; - const [arrayPath, input] = queryKeyArrayed; - - if (!input && (!type || type === "any")) { - return Array.isArray(arrayPath) && arrayPath.length !== 0 ? [arrayPath] : ([] as unknown as QueryKey); - } - - return [ - arrayPath, - { - ...(typeof input !== "undefined" && { input: input }), - ...(type && type !== "any" && { type: type }), - }, - ]; -} - -// copy starts -// copied from trpc/trpc repo -// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L37-#L58 -function transformQueryOrMutationCacheErrors< - TState extends DehydratedState["queries"][0] | DehydratedState["mutations"][0] ->(result: TState): TState { - const error = result.state.error as Maybe>; - if (error instanceof Error && error.name === "TRPCClientError") { - const newError: TRPCClientErrorLike = { - message: error.message, - data: error.data, - shape: error.shape, - }; - return { - ...result, - state: { - ...result.state, - error: newError, - }, - }; - } - return result; -} -// copy ends - -interface CreateTRPCNextLayoutOptions { - router: TRouter; - createContext: () => MaybePromise>; - transformer?: DataTransformer; -} - -/** - * @internal - */ -export type DecorateProcedure = TProcedure extends AnyQueryProcedure - ? { - fetch(input: inferProcedureInput): Promise>; - fetchInfinite(input: inferProcedureInput): Promise>; - prefetch(input: inferProcedureInput): Promise>; - prefetchInfinite(input: inferProcedureInput): Promise>; - } - : never; - -type OmitNever = Pick< - TType, - { - [K in keyof TType]: TType[K] extends never ? never : K; - }[keyof TType] ->; -/** - * @internal - */ -export type DecoratedProcedureRecord< - TProcedures extends ProcedureRouterRecord, - TPath extends string = "" -> = OmitNever<{ - [TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter - ? DecoratedProcedureRecord - : TProcedures[TKey] extends AnyQueryProcedure - ? DecorateProcedure - : never; -}>; - -type CreateTRPCNextLayout = DecoratedProcedureRecord & { - dehydrate(): Promise; - queryClient: QueryClient; -}; - -const getStateContainer = (opts: CreateTRPCNextLayoutOptions) => { - let _trpc: { - queryClient: QueryClient; - context: inferRouterContext; - } | null = null; - - return () => { - if (_trpc === null) { - _trpc = { - context: opts.createContext(), - queryClient: new QueryClient(), - }; - } - - return _trpc; - }; -}; - -export function createTRPCNextLayout( - opts: CreateTRPCNextLayoutOptions -): CreateTRPCNextLayout { - const getState = getStateContainer(opts); - - const transformer = opts.transformer ?? { - serialize: (v) => v, - deserialize: (v) => v, - }; - - return createFlatProxy((key) => { - const state = getState(); - const { queryClient } = state; - if (key === "queryClient") { - return queryClient; - } - - if (key === "dehydrate") { - // copy starts - // copied from trpc/trpc repo - // ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L214-#L229 - const dehydratedCache = dehydrate(queryClient, { - shouldDehydrateQuery() { - // makes sure errors are also dehydrated - return true; - }, - }); - - // since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects - const dehydratedCacheWithErrors = { - ...dehydratedCache, - queries: dehydratedCache.queries.map(transformQueryOrMutationCacheErrors), - mutations: dehydratedCache.mutations.map(transformQueryOrMutationCacheErrors), - }; - - return () => transformer.serialize(dehydratedCacheWithErrors); - } - // copy ends - - return createRecursiveProxy(async (callOpts) => { - const path = [key, ...callOpts.path]; - const utilName = path.pop(); - const ctx = await state.context; - - const caller = opts.router.createCaller(ctx); - - const pathStr = path.join("."); - const input = callOpts.args[0]; - - if (utilName === "fetchInfinite") { - return queryClient.fetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () => - caller.query(pathStr, input) - ); - } - - if (utilName === "prefetch") { - return queryClient.prefetchQuery({ - queryKey: getArrayQueryKey([path, input], "query"), - queryFn: async () => { - const res = await callProcedure({ - procedures: opts.router._def.procedures, - path: pathStr, - rawInput: input, - ctx, - type: "query", - }); - return res; - }, - }); - } - - if (utilName === "prefetchInfinite") { - return queryClient.prefetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () => - caller.query(pathStr, input) - ); - } - - return queryClient.fetchQuery(getArrayQueryKey([path, input], "query"), () => - caller.query(pathStr, input) - ); - }) as CreateTRPCNextLayout; - }); -} diff --git a/apps/web/app/_trpc/ssgInit.ts b/apps/web/app/_trpc/ssgInit.ts deleted file mode 100644 index 45e38c519d..0000000000 --- a/apps/web/app/_trpc/ssgInit.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import { headers } from "next/headers"; -import superjson from "superjson"; - -import { CALCOM_VERSION } from "@calcom/lib/constants"; -import prisma, { readonlyPrisma } from "@calcom/prisma"; -import { appRouter } from "@calcom/trpc/server/routers/_app"; - -import { createTRPCNextLayout } from "./createTRPCNextLayout"; - -export async function ssgInit() { - const locale = headers().get("x-locale") ?? "en"; - - const i18n = (await serverSideTranslations(locale, ["common"])) || "en"; - - const ssg = createTRPCNextLayout({ - router: appRouter, - transformer: superjson, - createContext() { - return { prisma, insightsDb: readonlyPrisma, session: null, locale, i18n }; - }, - }); - - // i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch - // we can set query data directly to the queryClient - const queryKey = [ - ["viewer", "public", "i18n"], - { input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" }, - ]; - - ssg.queryClient.setQueryData(queryKey, { i18n }); - - return ssg; -} diff --git a/apps/web/app/_trpc/ssrInit.ts b/apps/web/app/_trpc/ssrInit.ts deleted file mode 100644 index ab56003578..0000000000 --- a/apps/web/app/_trpc/ssrInit.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { type GetServerSidePropsContext } from "next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import { headers, cookies } from "next/headers"; -import superjson from "superjson"; - -import { getLocale } from "@calcom/features/auth/lib/getLocale"; -import { CALCOM_VERSION } from "@calcom/lib/constants"; -import prisma, { readonlyPrisma } from "@calcom/prisma"; -import { appRouter } from "@calcom/trpc/server/routers/_app"; - -import { createTRPCNextLayout } from "./createTRPCNextLayout"; - -export async function ssrInit(options?: { noI18nPreload: boolean }) { - const req = { - headers: headers(), - cookies: cookies(), - }; - - const locale = await getLocale(req); - - const i18n = (await serverSideTranslations(locale, ["common", "vital"])) || "en"; - - const ssr = createTRPCNextLayout({ - router: appRouter, - transformer: superjson, - createContext() { - return { - prisma, - insightsDb: readonlyPrisma, - session: null, - locale, - i18n, - req: req as unknown as GetServerSidePropsContext["req"], - }; - }, - }); - - // i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch - // we can set query data directly to the queryClient - const queryKey = [ - ["viewer", "public", "i18n"], - { input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" }, - ]; - if (!options?.noI18nPreload) { - ssr.queryClient.setQueryData(queryKey, { i18n }); - } - - await Promise.allSettled([ - // So feature flags are available on first render - ssr.viewer.features.map.prefetch(), - // Provides a better UX to the users who have already upgraded. - ssr.viewer.teams.hasTeamPlan.prefetch(), - ssr.viewer.public.session.prefetch(), - ]); - - return ssr; -} diff --git a/apps/web/app/future/apps/categories/page.tsx b/apps/web/app/future/apps/categories/page.tsx index c878d37732..d3b36bb356 100644 --- a/apps/web/app/future/apps/categories/page.tsx +++ b/apps/web/app/future/apps/categories/page.tsx @@ -1,13 +1,15 @@ import LegacyPage from "@pages/apps/categories/index"; -import { ssrInit } from "app/_trpc/ssrInit"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import { cookies, headers } from "next/headers"; import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { APP_NAME } from "@calcom/lib/constants"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssrInit } from "@server/lib/ssr"; + export const generateMetadata = async () => { return await _generateMetadata( () => `Categories | ${APP_NAME}`, @@ -15,12 +17,12 @@ export const generateMetadata = async () => { ); }; -async function getPageProps() { - const ssr = await ssrInit(); - const req = { headers: headers(), cookies: cookies() }; +const getData = async (ctx: ReturnType) => { + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. + const ssr = await ssrInit(ctx); // @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | IncomingMessage - const session = await getServerSession({ req }); + const session = await getServerSession({ req: ctx.req }); let appStore; if (session?.user?.id) { @@ -38,8 +40,8 @@ async function getPageProps() { return { categories: Object.entries(categories).map(([name, count]) => ({ name, count })), - dehydratedState: await ssr.dehydrate(), + dehydratedState: ssr.dehydrate(), }; -} +}; -export default WithLayout({ getData: getPageProps, Page: LegacyPage, getLayout: null })<"P">; +export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">; diff --git a/apps/web/app/future/apps/page.tsx b/apps/web/app/future/apps/page.tsx index 80c04c5491..0a24ec567e 100644 --- a/apps/web/app/future/apps/page.tsx +++ b/apps/web/app/future/apps/page.tsx @@ -1,7 +1,6 @@ import AppsPage from "@pages/apps"; -import { ssrInit } from "app/_trpc/ssrInit"; import { _generateMetadata } from "app/_utils"; -import { cookies, headers } from "next/headers"; +import { WithLayout } from "app/layoutHOC"; import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; import { getLayout } from "@calcom/features/MainLayoutAppDir"; @@ -11,7 +10,9 @@ import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams"; import { APP_NAME } from "@calcom/lib/constants"; import type { AppCategories } from "@calcom/prisma/enums"; -import PageWrapper from "@components/PageWrapperAppDir"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssrInit } from "@server/lib/ssr"; export const generateMetadata = async () => { return await _generateMetadata( @@ -20,12 +21,12 @@ export const generateMetadata = async () => { ); }; -const getPageProps = async () => { - const ssr = await ssrInit(); - const req = { headers: headers(), cookies: cookies() }; +const getData = async (ctx: ReturnType) => { + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. + const ssr = await ssrInit(ctx); // @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest - const session = await getServerSession({ req }); + const session = await getServerSession({ req: ctx.req }); let appStore, userAdminTeams: UserAdminTeams; if (session?.user?.id) { @@ -58,24 +59,8 @@ const getPageProps = async () => { }), appStore, userAdminTeams, - dehydratedState: await ssr.dehydrate(), + dehydratedState: ssr.dehydrate(), }; }; -export default async function AppPageAppDir() { - const { categories, appStore, userAdminTeams, dehydratedState } = await getPageProps(); - - const h = headers(); - const nonce = h.get("x-nonce") ?? undefined; - - return ( - - - - ); -} +export default WithLayout({ getLayout, getData, Page: AppsPage }); diff --git a/apps/web/app/future/bookings/[status]/layout.tsx b/apps/web/app/future/bookings/[status]/layout.tsx index 7391f9996f..3eff385303 100644 --- a/apps/web/app/future/bookings/[status]/layout.tsx +++ b/apps/web/app/future/bookings/[status]/layout.tsx @@ -1,22 +1,21 @@ -import { ssgInit } from "app/_trpc/ssgInit"; -import type { Params } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { notFound } from "next/navigation"; -import type { ReactElement } from "react"; import { z } from "zod"; import { getLayout } from "@calcom/features/MainLayoutAppDir"; import { APP_NAME } from "@calcom/lib/constants"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssgInit } from "@server/lib/ssg"; + const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; const querySchema = z.object({ status: z.enum(validStatuses), }); -type Props = { params: Params; children: ReactElement }; - export const generateMetadata = async () => await _generateMetadata( (t) => `${APP_NAME} | ${t("bookings")}`, @@ -27,18 +26,18 @@ export const generateStaticParams = async () => { return validStatuses.map((status) => ({ status })); }; -const getData = async ({ params }: { params: Params }) => { - const parsedParams = querySchema.safeParse(params); +const getData = async (ctx: ReturnType) => { + const parsedParams = querySchema.safeParse(ctx.params); if (!parsedParams.success) { notFound(); } - const ssg = await ssgInit(); + const ssg = await ssgInit(ctx); return { status: parsedParams.data.status, - dehydratedState: await ssg.dehydrate(), + dehydratedState: ssg.dehydrate(), }; }; diff --git a/apps/web/app/future/(individual-page-wrapper)/getting-started/[[...step]]/page.tsx b/apps/web/app/future/getting-started/[[...step]]/page.tsx similarity index 66% rename from apps/web/app/future/(individual-page-wrapper)/getting-started/[[...step]]/page.tsx rename to apps/web/app/future/getting-started/[[...step]]/page.tsx index 5175764d9e..2751566cd5 100644 --- a/apps/web/app/future/(individual-page-wrapper)/getting-started/[[...step]]/page.tsx +++ b/apps/web/app/future/getting-started/[[...step]]/page.tsx @@ -1,14 +1,16 @@ import LegacyPage from "@pages/getting-started/[[...step]]"; -import { ssrInit } from "app/_trpc/ssrInit"; +import { WithLayout } from "app/layoutHOC"; import { cookies, headers } from "next/headers"; import { redirect } from "next/navigation"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import prisma from "@calcom/prisma"; -import PageWrapper from "@components/PageWrapperAppDir"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; -async function getData() { +import { ssrInit } from "@server/lib/ssr"; + +const getData = async (ctx: ReturnType) => { const req = { headers: headers(), cookies: cookies() }; //@ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest @@ -17,8 +19,8 @@ async function getData() { if (!session?.user?.id) { return redirect("/auth/login"); } - - const ssr = await ssrInit(); + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. + const ssr = await ssrInit(ctx); await ssr.viewer.me.prefetch(); const user = await prisma.user.findUnique({ @@ -51,20 +53,11 @@ async function getData() { } return { - dehydratedState: await ssr.dehydrate(), + dehydratedState: ssr.dehydrate(), hasPendingInvites: user.teams.find((team: any) => team.accepted === false) ?? false, + requiresLicense: false, + themeBasis: null, }; -} +}; -export default async function Page() { - const props = await getData(); - - const h = headers(); - const nonce = h.get("x-nonce") ?? undefined; - - return ( - - - - ); -} +export default WithLayout({ getLayout: null, getData, Page: LegacyPage }); diff --git a/apps/web/app/future/teams/page.tsx b/apps/web/app/future/teams/page.tsx index 0f19dd1523..fe17ec9fb4 100644 --- a/apps/web/app/future/teams/page.tsx +++ b/apps/web/app/future/teams/page.tsx @@ -1,24 +1,29 @@ import OldPage from "@pages/teams/index"; -import { ssrInit } from "app/_trpc/ssrInit"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; -import { type GetServerSidePropsContext } from "next"; import { redirect } from "next/navigation"; import { getLayout } from "@calcom/features/MainLayoutAppDir"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssrInit } from "@server/lib/ssr"; + export const generateMetadata = async () => await _generateMetadata( (t) => t("teams"), (t) => t("create_manage_teams_collaborative") ); -async function getData(context: Omit) { - const ssr = await ssrInit(); +async function getData(context: ReturnType) { + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. + const ssr = await ssrInit(context); + await ssr.viewer.me.prefetch(); const session = await getServerSession({ + // @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | (IncomingMessage & { cookies: Partial<{ [key: string]: string; }>; })'. req: context.req, }); @@ -29,8 +34,7 @@ async function getData(context: Omit; diff --git a/apps/web/app/future/video/[uid]/page.tsx b/apps/web/app/future/video/[uid]/page.tsx index b781376423..1e4d7ee00a 100644 --- a/apps/web/app/future/video/[uid]/page.tsx +++ b/apps/web/app/future/video/[uid]/page.tsx @@ -1,15 +1,17 @@ import OldPage from "@pages/video/[uid]"; -import { ssrInit } from "app/_trpc/ssrInit"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import MarkdownIt from "markdown-it"; -import { type GetServerSidePropsContext } from "next"; import { redirect } from "next/navigation"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { APP_NAME } from "@calcom/lib/constants"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssrInit } from "@server/lib/ssr"; + export const generateMetadata = async () => await _generateMetadata( () => `${APP_NAME} Video`, @@ -18,8 +20,9 @@ export const generateMetadata = async () => const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true }); -async function getData(context: Omit) { - const ssr = await ssrInit(); +async function getData(context: ReturnType) { + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. + const ssr = await ssrInit(context); const booking = await prisma.booking.findUnique({ where: { @@ -76,6 +79,7 @@ async function getData(context: Omit; })'. const session = await getServerSession({ req: context.req }); // set meetingPassword to null for guests @@ -94,9 +98,8 @@ async function getData(context: Omit; diff --git a/apps/web/app/future/video/no-meeting-found/page.tsx b/apps/web/app/future/video/no-meeting-found/page.tsx index 31a15a2ad6..9c69388ad3 100644 --- a/apps/web/app/future/video/no-meeting-found/page.tsx +++ b/apps/web/app/future/video/no-meeting-found/page.tsx @@ -1,19 +1,23 @@ import LegacyPage from "@pages/video/no-meeting-found"; -import { ssrInit } from "app/_trpc/ssrInit"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; +import type { buildLegacyCtx } from "@lib/buildLegacyCtx"; + +import { ssrInit } from "@server/lib/ssr"; + export const generateMetadata = async () => await _generateMetadata( (t) => t("no_meeting_found"), (t) => t("no_meeting_found") ); -const getData = async () => { - const ssr = await ssrInit(); +const getData = async (context: ReturnType) => { + // @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'. + const ssr = await ssrInit(context); return { - dehydratedState: await ssr.dehydrate(), + dehydratedState: ssr.dehydrate(), }; };