diff --git a/apps/web/app/not-found.tsx b/apps/web/app/not-found.tsx index 3468198ca62d99..ff586f73eaed9b 100644 --- a/apps/web/app/not-found.tsx +++ b/apps/web/app/not-found.tsx @@ -2,13 +2,13 @@ import NotFoundPage from "@pages/404"; import { WithLayout } from "app/layoutHOC"; import type { GetStaticPropsContext } from "next"; -import { ssgInit } from "@server/lib/ssg"; +import { getTranslations } from "@server/lib/getTranslations"; const getData = async (context: GetStaticPropsContext) => { - const ssg = await ssgInit(context); + const i18n = await getTranslations(context); return { - dehydratedState: ssg.dehydrate(), + i18n, }; }; diff --git a/apps/web/lib/app-providers.tsx b/apps/web/lib/app-providers.tsx index a7bfcba9ea7303..cffb6cf497681b 100644 --- a/apps/web/lib/app-providers.tsx +++ b/apps/web/lib/app-providers.tsx @@ -37,6 +37,7 @@ export type AppProps = Omit< WithNonceProps<{ themeBasis?: string; session: Session; + i18n?: SSRConfig; }> > >, @@ -108,7 +109,7 @@ const CustomI18nextProvider = (props: AppPropsWithoutNonce) => { }, [locale]); const clientViewerI18n = useViewerI18n(locale); - const i18n = clientViewerI18n.data?.i18n; + const i18n = clientViewerI18n.data?.i18n ?? props.pageProps.i18n; const passedProps = { ...props, diff --git a/apps/web/lib/bookings/[status]/getStaticProps.tsx b/apps/web/lib/bookings/[status]/getStaticProps.tsx index b4a8de083bffff..0415a9a66cd952 100644 --- a/apps/web/lib/bookings/[status]/getStaticProps.tsx +++ b/apps/web/lib/bookings/[status]/getStaticProps.tsx @@ -1,7 +1,7 @@ import { type GetStaticProps } from "next"; import { z } from "zod"; -import { ssgInit } from "@server/lib/ssg"; +import { getTranslations } from "@server/lib/getTranslations"; const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; @@ -11,14 +11,14 @@ const querySchema = z.object({ export const getStaticProps: GetStaticProps = async (ctx) => { const params = querySchema.safeParse(ctx.params); - const ssg = await ssgInit(ctx); + const i18n = await getTranslations(ctx); if (!params.success) return { notFound: true }; return { props: { status: params.data.status, - trpcState: ssg.dehydrate(), + i18n, }, }; }; diff --git a/apps/web/pages/404.tsx b/apps/web/pages/404.tsx index a08b01b8af0b76..06d23a8223c3f9 100644 --- a/apps/web/pages/404.tsx +++ b/apps/web/pages/404.tsx @@ -17,7 +17,7 @@ import { Discord } from "@calcom/ui/components/icon/Discord"; import PageWrapper from "@components/PageWrapper"; -import { ssgInit } from "@server/lib/ssg"; +import { getTranslations } from "@server/lib/getTranslations"; enum pageType { ORG = "org", @@ -273,11 +273,11 @@ export default function Custom404() { Custom404.PageWrapper = PageWrapper; export const getStaticProps = async (context: GetStaticPropsContext) => { - const ssr = await ssgInit(context); + const i18n = await getTranslations(context); return { props: { - trpcState: ssr.dehydrate(), + i18n, }, }; }; diff --git a/apps/web/pages/auth/error.tsx b/apps/web/pages/auth/error.tsx index 502fead3ffc31c..114ecabbc2cc4e 100644 --- a/apps/web/pages/auth/error.tsx +++ b/apps/web/pages/auth/error.tsx @@ -10,7 +10,7 @@ import { X } from "@calcom/ui/components/icon"; import PageWrapper from "@components/PageWrapper"; import AuthContainer from "@components/ui/AuthContainer"; -import { ssgInit } from "@server/lib/ssg"; +import { getTranslations } from "@server/lib/getTranslations"; const querySchema = z.object({ error: z.string().optional(), @@ -50,11 +50,11 @@ export default function Error() { Error.PageWrapper = PageWrapper; export const getStaticProps = async (context: GetStaticPropsContext) => { - const ssr = await ssgInit(context); + const i18n = await getTranslations(context); return { props: { - trpcState: ssr.dehydrate(), + i18n, }, }; }; diff --git a/apps/web/server/lib/getTranslations.ts b/apps/web/server/lib/getTranslations.ts new file mode 100644 index 00000000000000..e8aaba052f05c8 --- /dev/null +++ b/apps/web/server/lib/getTranslations.ts @@ -0,0 +1,22 @@ +import type { GetStaticPropsContext } from "next"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { i18n } = require("@calcom/config/next-i18next.config"); + +export async function getTranslations( + opts: GetStaticPropsContext +) { + const requestedLocale = opts.params?.locale || opts.locale || i18n.defaultLocale; + const isSupportedLocale = i18n.locales.includes(requestedLocale); + if (!isSupportedLocale) { + console.warn(`Requested unsupported locale "${requestedLocale}"`); + } + const locale = isSupportedLocale ? requestedLocale : i18n.defaultLocale; + + const _i18n = await serverSideTranslations(locale, ["common"]); + + return { + i18n: _i18n, + }; +} diff --git a/apps/web/server/lib/ssr.ts b/apps/web/server/lib/ssr.ts index 08922d5fe5c58b..eaf5e4790c9932 100644 --- a/apps/web/server/lib/ssr.ts +++ b/apps/web/server/lib/ssr.ts @@ -2,11 +2,51 @@ import type { GetServerSidePropsContext } from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import superjson from "superjson"; +import { forms } from "@calcom/app-store/routing-forms/trpc/procedures/forms"; import { getLocale } from "@calcom/features/auth/lib/getLocale"; +import { map } from "@calcom/features/flags/server/procedures/map"; import { CALCOM_VERSION } from "@calcom/lib/constants"; import { createProxySSGHelpers } from "@calcom/trpc/react/ssg"; import { createContext } from "@calcom/trpc/server/createContext"; -import { appRouter } from "@calcom/trpc/server/routers/_app"; +import { me } from "@calcom/trpc/server/routers/loggedInViewer/procedures/me"; +import { teamsAndUserProfilesQuery } from "@calcom/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery"; +import { event } from "@calcom/trpc/server/routers/publicViewer/procedures/event"; +import { session } from "@calcom/trpc/server/routers/publicViewer/procedures/session"; +import { get } from "@calcom/trpc/server/routers/viewer/eventTypes/procedures/get"; +import { hasTeamPlan } from "@calcom/trpc/server/routers/viewer/teams/procedures/hasTeamPlan"; +import { router, mergeRouters } from "@calcom/trpc/server/trpc"; + +const loggedInRouter = router({ + me, +}); + +// Temporary workaround for OOM issue, import only procedures that are called on the server side +const routerSlice = router({ + viewer: mergeRouters( + loggedInRouter, + router({ + features: router({ + map, + }), + public: router({ + session, + event, + }), + teams: router({ + hasTeamPlan, + }), + appRoutingForms: router({ + forms, + }), + teamsAndUserProfilesQuery: router({ + teamsAndUserProfilesQuery, + }), + eventTypes: router({ + get, + }), + }) + ), +}); /** * Initialize server-side rendering tRPC helpers. @@ -20,7 +60,7 @@ export async function ssrInit(context: GetServerSidePropsContext, options?: { no const i18n = await serverSideTranslations(locale, ["common", "vital"]); const ssr = createProxySSGHelpers({ - router: appRouter, + router: routerSlice, transformer: superjson, ctx: { ...ctx, locale, i18n }, }); diff --git a/packages/app-store/routing-forms/trpc/_router.ts b/packages/app-store/routing-forms/trpc/_router.ts index 3b38d6e2c4493e..6f514c568b7fcb 100644 --- a/packages/app-store/routing-forms/trpc/_router.ts +++ b/packages/app-store/routing-forms/trpc/_router.ts @@ -5,7 +5,7 @@ import { router } from "@calcom/trpc/server/trpc"; import { ZDeleteFormInputSchema } from "./deleteForm.schema"; import { ZFormMutationInputSchema } from "./formMutation.schema"; import { ZFormQueryInputSchema } from "./formQuery.schema"; -import { ZFormsInputSchema } from "./forms.schema"; +import { forms } from "./procedures/forms"; import { ZReportInputSchema } from "./report.schema"; import { ZResponseInputSchema } from "./response.schema"; @@ -50,10 +50,7 @@ const appRoutingForms = router({ return handler({ ctx, input }); }), }), - forms: authedProcedure.input(ZFormsInputSchema).query(async ({ ctx, input }) => { - const handler = await getHandler("forms", () => import("./forms.handler")); - return handler({ ctx, input }); - }), + forms, formQuery: authedProcedure.input(ZFormQueryInputSchema).query(async ({ ctx, input }) => { const handler = await getHandler("formQuery", () => import("./formQuery.handler")); return handler({ ctx, input }); diff --git a/packages/app-store/routing-forms/trpc/procedures/forms.ts b/packages/app-store/routing-forms/trpc/procedures/forms.ts new file mode 100644 index 00000000000000..f7fc9063bc50ea --- /dev/null +++ b/packages/app-store/routing-forms/trpc/procedures/forms.ts @@ -0,0 +1,8 @@ +import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure"; + +import { ZFormsInputSchema } from "../forms.schema"; + +export const forms = authedProcedure.input(ZFormsInputSchema).query(async ({ ctx, input }) => { + const handler = (await import("../forms.handler")).default; + return handler({ ctx, input }); +}); diff --git a/packages/features/flags/server/procedures/map.ts b/packages/features/flags/server/procedures/map.ts new file mode 100644 index 00000000000000..fdd23e0d7efb69 --- /dev/null +++ b/packages/features/flags/server/procedures/map.ts @@ -0,0 +1,8 @@ +import publicProcedure from "@calcom/trpc/server/procedures/publicProcedure"; + +import { getFeatureFlagMap } from "../utils"; + +export const map = publicProcedure.query(async ({ ctx }) => { + const { prisma } = ctx; + return getFeatureFlagMap(prisma); +}); diff --git a/packages/features/flags/server/router.ts b/packages/features/flags/server/router.ts index 93de96b4b5a427..9a151cbf62f3a0 100644 --- a/packages/features/flags/server/router.ts +++ b/packages/features/flags/server/router.ts @@ -1,7 +1,7 @@ import publicProcedure from "@calcom/trpc/server/procedures/publicProcedure"; import { router } from "@calcom/trpc/server/trpc"; -import { getFeatureFlagMap } from "./utils"; +import { map } from "./procedures/map"; export const featureFlagRouter = router({ list: publicProcedure.query(async ({ ctx }) => { @@ -11,8 +11,5 @@ export const featureFlagRouter = router({ cacheStrategy: { swr: 300, ttl: 300 }, }); }), - map: publicProcedure.query(async ({ ctx }) => { - const { prisma } = ctx; - return getFeatureFlagMap(prisma); - }), + map, }); diff --git a/packages/trpc/server/routers/loggedInViewer/_router.tsx b/packages/trpc/server/routers/loggedInViewer/_router.tsx index e5b92352bab964..ab73b1eadc5807 100644 --- a/packages/trpc/server/routers/loggedInViewer/_router.tsx +++ b/packages/trpc/server/routers/loggedInViewer/_router.tsx @@ -13,6 +13,8 @@ import { ZGetDownloadLinkOfCalVideoRecordingsInputSchema } from "./getDownloadLi import { ZIntegrationsInputSchema } from "./integrations.schema"; import { ZLocationOptionsInputSchema } from "./locationOptions.schema"; import { ZOutOfOfficeInputSchema, ZOutOfOfficeDelete } from "./outOfOffice.schema"; +import { me } from "./procedures/me"; +import { teamsAndUserProfilesQuery } from "./procedures/teamsAndUserProfilesQuery"; import { ZRoutingFormOrderInputSchema } from "./routingFormOrder.schema"; import { ZSetDestinationCalendarInputSchema } from "./setDestinationCalendar.schema"; import { ZSubmitFeedbackInputSchema } from "./submitFeedback.schema"; @@ -56,18 +58,7 @@ type AppsRouterHandlerCache = { const UNSTABLE_HANDLER_CACHE: AppsRouterHandlerCache = {}; export const loggedInViewerRouter = router({ - me: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.me) { - UNSTABLE_HANDLER_CACHE.me = (await import("./me.handler")).meHandler; - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.me) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.me({ ctx }); - }), + me, avatar: authedProcedure.query(async ({ ctx }) => { if (!UNSTABLE_HANDLER_CACHE.avatar) { @@ -424,20 +415,7 @@ export const loggedInViewerRouter = router({ return UNSTABLE_HANDLER_CACHE.shouldVerifyEmail({ ctx }); }), - teamsAndUserProfilesQuery: authedProcedure.query(async ({ ctx }) => { - if (!UNSTABLE_HANDLER_CACHE.teamsAndUserProfilesQuery) { - UNSTABLE_HANDLER_CACHE.teamsAndUserProfilesQuery = ( - await import("./teamsAndUserProfilesQuery.handler") - ).teamsAndUserProfilesQuery; - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.teamsAndUserProfilesQuery) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.teamsAndUserProfilesQuery({ ctx }); - }), + teamsAndUserProfilesQuery, connectAndJoin: authedProcedure.input(ZConnectAndJoinInputSchema).mutation(async ({ ctx, input }) => { if (!UNSTABLE_HANDLER_CACHE.connectAndJoin) { UNSTABLE_HANDLER_CACHE.connectAndJoin = (await import("./connectAndJoin.handler")).Handler; diff --git a/packages/trpc/server/routers/loggedInViewer/procedures/me.ts b/packages/trpc/server/routers/loggedInViewer/procedures/me.ts new file mode 100644 index 00000000000000..ac5d1f5367cb29 --- /dev/null +++ b/packages/trpc/server/routers/loggedInViewer/procedures/me.ts @@ -0,0 +1,7 @@ +import authedProcedure from "../../../procedures/authedProcedure"; + +export const me = authedProcedure.query(async ({ ctx }) => { + const handler = (await import("../me.handler")).meHandler; + + return handler({ ctx }); +}); diff --git a/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts b/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts new file mode 100644 index 00000000000000..7a10afce7bead1 --- /dev/null +++ b/packages/trpc/server/routers/loggedInViewer/procedures/teamsAndUserProfilesQuery.ts @@ -0,0 +1,7 @@ +import authedProcedure from "../../../procedures/authedProcedure"; + +export const teamsAndUserProfilesQuery = authedProcedure.query(async ({ ctx }) => { + const handler = (await import("../teamsAndUserProfilesQuery.handler")).teamsAndUserProfilesQuery; + + return handler({ ctx }); +}); diff --git a/packages/trpc/server/routers/publicViewer/_router.tsx b/packages/trpc/server/routers/publicViewer/_router.tsx index b071d304b418c0..9e1cd26753622a 100644 --- a/packages/trpc/server/routers/publicViewer/_router.tsx +++ b/packages/trpc/server/routers/publicViewer/_router.tsx @@ -1,9 +1,9 @@ -import sessionMiddleware from "../../middlewares/sessionMiddleware"; import publicProcedure from "../../procedures/publicProcedure"; import { importHandler, router } from "../../trpc"; import { slotsRouter } from "../viewer/slots/_router"; -import { ZEventInputSchema } from "./event.schema"; import { i18nInputSchema } from "./i18n.schema"; +import { event } from "./procedures/event"; +import { session } from "./procedures/session"; import { ZSamlTenantProductInputSchema } from "./samlTenantProduct.schema"; import { ZStripeCheckoutSessionInputSchema } from "./stripeCheckoutSession.schema"; @@ -13,10 +13,7 @@ const namespaced = (s: string) => `${NAMESPACE}.${s}`; // things that unauthenticated users can query about themselves export const publicViewerRouter = router({ - session: publicProcedure.use(sessionMiddleware).query(async (opts) => { - const handler = await importHandler(namespaced("session"), () => import("./session.handler")); - return handler(opts); - }), + session, i18n: publicProcedure.input(i18nInputSchema).query(async (opts) => { const handler = await importHandler(namespaced("i18n"), () => import("./i18n.handler")); return handler(opts); @@ -45,10 +42,7 @@ export const publicViewerRouter = router({ }), // REVIEW: This router is part of both the public and private viewer router? slots: slotsRouter, - event: publicProcedure.input(ZEventInputSchema).query(async (opts) => { - const handler = await importHandler(namespaced("event"), () => import("./event.handler")); - return handler(opts); - }), + event, ssoConnections: publicProcedure.query(async () => { const handler = await importHandler( namespaced("ssoConnections"), diff --git a/packages/trpc/server/routers/publicViewer/procedures/event.ts b/packages/trpc/server/routers/publicViewer/procedures/event.ts new file mode 100644 index 00000000000000..734e7d11b36289 --- /dev/null +++ b/packages/trpc/server/routers/publicViewer/procedures/event.ts @@ -0,0 +1,11 @@ +import publicProcedure from "../../../procedures/publicProcedure"; +import { importHandler } from "../../../trpc"; +import { ZEventInputSchema } from "../event.schema"; + +const NAMESPACE = "publicViewer"; +const namespaced = (s: string) => `${NAMESPACE}.${s}`; + +export const event = publicProcedure.input(ZEventInputSchema).query(async (opts) => { + const handler = await importHandler(namespaced("event"), () => import("../event.handler")); + return handler(opts); +}); diff --git a/packages/trpc/server/routers/publicViewer/procedures/session.ts b/packages/trpc/server/routers/publicViewer/procedures/session.ts new file mode 100644 index 00000000000000..559851e825c146 --- /dev/null +++ b/packages/trpc/server/routers/publicViewer/procedures/session.ts @@ -0,0 +1,12 @@ +import sessionMiddleware from "../../../middlewares/sessionMiddleware"; +import publicProcedure from "../../../procedures/publicProcedure"; +import { importHandler } from "../../../trpc"; + +const NAMESPACE = "publicViewer"; + +const namespaced = (s: string) => `${NAMESPACE}.${s}`; + +export const session = publicProcedure.use(sessionMiddleware).query(async (opts) => { + const handler = await importHandler(namespaced("session"), () => import("../session.handler")); + return handler(opts); +}); diff --git a/packages/trpc/server/routers/viewer/eventTypes/_router.ts b/packages/trpc/server/routers/viewer/eventTypes/_router.ts index d85d6f032a5d40..f1eeaf054646ac 100644 --- a/packages/trpc/server/routers/viewer/eventTypes/_router.ts +++ b/packages/trpc/server/routers/viewer/eventTypes/_router.ts @@ -7,8 +7,8 @@ import { router } from "../../../trpc"; import { ZCreateInputSchema } from "./create.schema"; import { ZDeleteInputSchema } from "./delete.schema"; import { ZDuplicateInputSchema } from "./duplicate.schema"; -import { ZGetInputSchema } from "./get.schema"; import { ZEventTypeInputSchema } from "./getByViewer.schema"; +import { get } from "./procedures/get"; import { ZUpdateInputSchema } from "./update.schema"; import { eventOwnerProcedure } from "./util"; @@ -101,21 +101,7 @@ export const eventTypesRouter = router({ }); }), - get: eventOwnerProcedure.input(ZGetInputSchema).query(async ({ ctx, input }) => { - if (!UNSTABLE_HANDLER_CACHE.get) { - UNSTABLE_HANDLER_CACHE.get = await import("./get.handler").then((mod) => mod.getHandler); - } - - // Unreachable code but required for type safety - if (!UNSTABLE_HANDLER_CACHE.get) { - throw new Error("Failed to load handler"); - } - - return UNSTABLE_HANDLER_CACHE.get({ - ctx, - input, - }); - }), + get, update: eventOwnerProcedure.input(ZUpdateInputSchema).mutation(async ({ ctx, input }) => { if (!UNSTABLE_HANDLER_CACHE.update) { diff --git a/packages/trpc/server/routers/viewer/eventTypes/procedures/get.ts b/packages/trpc/server/routers/viewer/eventTypes/procedures/get.ts new file mode 100644 index 00000000000000..96d0a4413ee694 --- /dev/null +++ b/packages/trpc/server/routers/viewer/eventTypes/procedures/get.ts @@ -0,0 +1,11 @@ +import { ZGetInputSchema } from "../get.schema"; +import { eventOwnerProcedure } from "../util"; + +export const get = eventOwnerProcedure.input(ZGetInputSchema).query(async ({ ctx, input }) => { + const handler = (await import("../get.handler")).getHandler; + + return handler({ + ctx, + input, + }); +}); diff --git a/packages/trpc/server/routers/viewer/teams/_router.tsx b/packages/trpc/server/routers/viewer/teams/_router.tsx index 174dd335a0ab89..fa609818141e15 100644 --- a/packages/trpc/server/routers/viewer/teams/_router.tsx +++ b/packages/trpc/server/routers/viewer/teams/_router.tsx @@ -13,6 +13,7 @@ import { ZHasEditPermissionForUserSchema } from "./hasEditPermissionForUser.sche import { ZInviteMemberInputSchema } from "./inviteMember/inviteMember.schema"; import { ZInviteMemberByTokenSchemaInputSchema } from "./inviteMemberByToken.schema"; import { ZListMembersInputSchema } from "./listMembers.schema"; +import { hasTeamPlan } from "./procedures/hasTeamPlan"; import { ZPublishInputSchema } from "./publish.schema"; import { ZRemoveMemberInputSchema } from "./removeMember.schema"; import { ZResendInvitationInputSchema } from "./resendInvitation.schema"; @@ -111,10 +112,7 @@ export const viewerTeamsRouter = router({ const handler = await importHandler(namespaced("listMembers"), () => import("./listMembers.handler")); return handler(opts); }), - hasTeamPlan: authedProcedure.query(async (opts) => { - const handler = await importHandler(namespaced("hasTeamPlan"), () => import("./hasTeamPlan.handler")); - return handler(opts); - }), + hasTeamPlan, listInvites: authedProcedure.query(async (opts) => { const handler = await importHandler(namespaced("listInvites"), () => import("./listInvites.handler")); return handler(opts); diff --git a/packages/trpc/server/routers/viewer/teams/procedures/hasTeamPlan.ts b/packages/trpc/server/routers/viewer/teams/procedures/hasTeamPlan.ts new file mode 100644 index 00000000000000..d001bf41164ee1 --- /dev/null +++ b/packages/trpc/server/routers/viewer/teams/procedures/hasTeamPlan.ts @@ -0,0 +1,10 @@ +import authedProcedure from "../../../../procedures/authedProcedure"; +import { importHandler } from "../../../../trpc"; + +const NAMESPACE = "teams"; +const namespaced = (s: string) => `${NAMESPACE}.${s}`; + +export const hasTeamPlan = authedProcedure.query(async (opts) => { + const handler = await importHandler(namespaced("hasTeamPlan"), () => import("../hasTeamPlan.handler")); + return handler(opts); +});