diff --git a/apps/web/app/_not-found.tsx b/apps/web/app/_not-found.tsx new file mode 100644 index 00000000000000..68a3d2c13d9f40 --- /dev/null +++ b/apps/web/app/_not-found.tsx @@ -0,0 +1,36 @@ +import NotFoundPage from "@pages/404"; +import { cookies, headers } from "next/headers"; + +import { getLocale } from "@calcom/features/auth/lib/getLocale"; +import { serverSideTranslations } from "@calcom/web/server/lib/serverSideTranslations"; + +import PageWrapper from "@components/PageWrapperAppDir"; + +const getProps = async (h: ReturnType, c: ReturnType) => { + // @ts-expect-error we cannot access ctx.req in app dir, however headers and cookies are only properties needed to extract the locale + const locale = await getLocale({ headers: h, cookies: c }); + + const i18n = (await serverSideTranslations(locale)) || "en"; + + return { + i18n, + }; +}; + +const NotFound = async () => { + const h = headers(); + const c = cookies(); + + const nonce = h.get("x-nonce") ?? undefined; + + const { i18n } = await getProps(h, c); + + return ( + // @ts-expect-error withTrpc expects AppProps + + + + ); +}; + +export default NotFound; diff --git a/apps/web/app/event-types-1/layout.tsx b/apps/web/app/event-types-1/layout.tsx new file mode 100644 index 00000000000000..b903941842b888 --- /dev/null +++ b/apps/web/app/event-types-1/layout.tsx @@ -0,0 +1,27 @@ +import { headers } from "next/headers"; +import { type ReactElement } from "react"; + +import { getLayout } from "@calcom/features/MainLayout"; + +import PageWrapper from "@components/PageWrapperAppDir"; + +type EventTypesLayoutProps = { + children: ReactElement; +}; + +export default function EventTypesLayout({ children }: EventTypesLayoutProps) { + const h = headers(); + const nonce = h.get("x-nonce") ?? undefined; + + return ( + // @ts-expect-error withTrpc expects AppProps + + {children} + + ); +} diff --git a/apps/web/app/event-types-1/page.tsx b/apps/web/app/event-types-1/page.tsx new file mode 100644 index 00000000000000..78017ecf6488f1 --- /dev/null +++ b/apps/web/app/event-types-1/page.tsx @@ -0,0 +1,18 @@ +import EventTypes from "@pages/event-types"; +import type { Metadata } from "next"; + +import { IS_CALCOM, WEBAPP_URL } from "@calcom/lib/constants"; + +export const metadata: Metadata = { + viewport: "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0", + metadataBase: new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL), + alternates: { + canonical: "/event-types", + }, + twitter: { + card: "summary_large_image", + title: "@calcom", + }, +}; + +export default EventTypes; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 8f8b8da78f1e06..c5cbe32421ba05 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -5,10 +5,14 @@ import { NextResponse } from "next/server"; import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry"; +import { csp } from "@lib/csp"; + const middleware: NextMiddleware = async (req) => { const url = req.nextUrl; const requestHeaders = new Headers(req.headers); + requestHeaders.set("x-url", req.url); + if (!url.pathname.startsWith("/api")) { // // NOTE: When tRPC hits an error a 500 is returned, when this is received @@ -32,6 +36,18 @@ const middleware: NextMiddleware = async (req) => { } const res = routingForms.handle(url); + + const { nonce } = csp(req, res ?? null); + + if (!process.env.CSP_POLICY) { + req.headers.set("x-csp", "not-opted-in"); + } else if (!req.headers.get("x-csp")) { + // If x-csp not set by gSSP, then it's initialPropsOnly + req.headers.set("x-csp", "initialPropsOnly"); + } else { + req.headers.set("x-csp", nonce ?? ""); + } + if (res) { return res; } @@ -74,6 +90,10 @@ export const config = { * Paths required by routingForms.handle */ "/apps/routing_forms/:path*", + + // middleware should be executed on each page request, in order to set x-url correctly + // matchers above, can be removed + "/:path*/", ], }; diff --git a/apps/web/pages/404.tsx b/apps/web/pages/404.tsx index fc23e64fd144eb..5548cc5a3ac728 100644 --- a/apps/web/pages/404.tsx +++ b/apps/web/pages/404.tsx @@ -52,7 +52,7 @@ export default function Custom404() { useEffect(() => { const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(window.location.host); const [routerUsername] = pathname?.replace("%20", "-").split(/[?#]/) ?? []; - if (routerUsername && (!isValidOrgDomain || !currentOrgDomain)) { + if (!isValidOrgDomain || !currentOrgDomain) { const splitPath = routerUsername.split("/"); if (splitPath[1] === "team" && splitPath.length === 3) { // Accessing a non-existent team diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 6ffa6f293df260..ea468428949280 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -20,7 +20,7 @@ function setHeader(ctx: NextPageContext, name: string, value: string) { } class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { - const { nonce } = csp(ctx.req || null, ctx.res || null); + const { nonce } = csp(ctx.req ?? null, ctx.res ?? null); if (!process.env.CSP_POLICY) { setHeader(ctx, "x-csp", "not-opted-in"); } else if (!ctx.res?.getHeader("x-csp")) { diff --git a/apps/web/pages/auth/forgot-password/[id].tsx b/apps/web/pages/auth/forgot-password/[id].tsx index 4bbb7e6a8c8523..117dfc1a6e65fb 100644 --- a/apps/web/pages/auth/forgot-password/[id].tsx +++ b/apps/web/pages/auth/forgot-password/[id].tsx @@ -1,6 +1,5 @@ import type { GetServerSidePropsContext } from "next"; import { getCsrfToken } from "next-auth/react"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import Link from "next/link"; import type { CSSProperties } from "react"; import { useForm } from "react-hook-form"; @@ -9,6 +8,7 @@ import { getLocale } from "@calcom/features/auth/lib/getLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; import { Button, PasswordField, Form } from "@calcom/ui"; +import { serverSideTranslations } from "@calcom/web/server/lib/serverSideTranslations"; import PageWrapper from "@components/PageWrapper"; import AuthContainer from "@components/ui/AuthContainer"; diff --git a/apps/web/pages/auth/forgot-password/index.tsx b/apps/web/pages/auth/forgot-password/index.tsx index fe68bc4e99dfa6..4d0e3443ec0672 100644 --- a/apps/web/pages/auth/forgot-password/index.tsx +++ b/apps/web/pages/auth/forgot-password/index.tsx @@ -2,7 +2,6 @@ import { debounce } from "lodash"; import type { GetServerSidePropsContext } from "next"; import { getCsrfToken } from "next-auth/react"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import Link from "next/link"; import type { CSSProperties, SyntheticEvent } from "react"; import React from "react"; @@ -11,6 +10,7 @@ import { getLocale } from "@calcom/features/auth/lib/getLocale"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, EmailField } from "@calcom/ui"; +import { serverSideTranslations } from "@calcom/web/server/lib/serverSideTranslations"; import PageWrapper from "@components/PageWrapper"; import AuthContainer from "@components/ui/AuthContainer"; diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index 4d68795e92a933..736b71eaf489f6 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { User } from "@prisma/client"; import { Trans } from "next-i18next"; diff --git a/apps/web/pages/getting-started/[[...step]].tsx b/apps/web/pages/getting-started/[[...step]].tsx index c973b257545c3a..4779a4fe2424d7 100644 --- a/apps/web/pages/getting-started/[[...step]].tsx +++ b/apps/web/pages/getting-started/[[...step]].tsx @@ -1,5 +1,4 @@ import type { GetServerSidePropsContext } from "next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import Head from "next/head"; import { usePathname, useRouter } from "next/navigation"; import type { CSSProperties } from "react"; @@ -15,6 +14,7 @@ import prisma from "@calcom/prisma"; import { trpc } from "@calcom/trpc"; import { Button, StepCard, Steps } from "@calcom/ui"; import { Loader } from "@calcom/ui/components/icon"; +import { serverSideTranslations } from "@calcom/web/server/lib/serverSideTranslations"; import PageWrapper from "@components/PageWrapper"; import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars"; diff --git a/apps/web/server/lib/serverSideTranslations.ts b/apps/web/server/lib/serverSideTranslations.ts new file mode 100644 index 00000000000000..c20665df5da0ce --- /dev/null +++ b/apps/web/server/lib/serverSideTranslations.ts @@ -0,0 +1,8 @@ +import { serverSideTranslations as _serverSideTranslations } from "next-i18next/serverSideTranslations"; + +//@ts-expect-error no type definitions +import config from "@calcom/web/next-i18next.config"; + +export const serverSideTranslations: typeof _serverSideTranslations = async (locale, namespaces) => { + return _serverSideTranslations(locale, namespaces, config); +}; diff --git a/apps/web/server/lib/ssg.ts b/apps/web/server/lib/ssg.ts index 469653da9729d8..f9312ea65118fc 100644 --- a/apps/web/server/lib/ssg.ts +++ b/apps/web/server/lib/ssg.ts @@ -1,11 +1,11 @@ import type { GetStaticPropsContext } from "next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import superjson from "superjson"; import { CALCOM_VERSION } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; import { createProxySSGHelpers } from "@calcom/trpc/react/ssg"; import { appRouter } from "@calcom/trpc/server/routers/_app"; +import { serverSideTranslations } from "@calcom/web/server/lib/serverSideTranslations"; // eslint-disable-next-line @typescript-eslint/no-var-requires const { i18n } = require("@calcom/config/next-i18next.config"); diff --git a/apps/web/server/lib/ssr.ts b/apps/web/server/lib/ssr.ts index 08922d5fe5c58b..bd43c5e5761ebe 100644 --- a/apps/web/server/lib/ssr.ts +++ b/apps/web/server/lib/ssr.ts @@ -1,5 +1,4 @@ import type { GetServerSidePropsContext } from "next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import superjson from "superjson"; import { getLocale } from "@calcom/features/auth/lib/getLocale"; @@ -7,6 +6,7 @@ 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 { serverSideTranslations } from "@calcom/web/server/lib/serverSideTranslations"; /** * Initialize server-side rendering tRPC helpers. diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index ca5be263cd447e..831bb9eb69036a 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -5,6 +5,7 @@ "paths": { "~/*": ["modules/*"], "@components/*": ["components/*"], + "@pages/*": ["pages/*"], "@lib/*": ["lib/*"], "@server/*": ["server/*"], "@prisma/client/*": ["@calcom/prisma/client/*"] diff --git a/packages/features/MainLayout.tsx b/packages/features/MainLayout.tsx index 03a739c8750677..acf4a2b5227bcd 100644 --- a/packages/features/MainLayout.tsx +++ b/packages/features/MainLayout.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { ComponentProps } from "react"; import React from "react"; diff --git a/packages/lib/server/i18n.ts b/packages/lib/server/i18n.ts index b49658d07e294b..300f84274a00e1 100644 --- a/packages/lib/server/i18n.ts +++ b/packages/lib/server/i18n.ts @@ -1,6 +1,7 @@ import i18next from "i18next"; import { i18n as nexti18next } from "next-i18next"; -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; + +import { serverSideTranslations } from "../../../apps/web/server/lib/serverSideTranslations"; export const getTranslation = async (locale: string, ns: string) => { const create = async () => { diff --git a/packages/trpc/server/routers/publicViewer/i18n.handler.ts b/packages/trpc/server/routers/publicViewer/i18n.handler.ts index e21acb5f22ed6c..3156291efccf8f 100644 --- a/packages/trpc/server/routers/publicViewer/i18n.handler.ts +++ b/packages/trpc/server/routers/publicViewer/i18n.handler.ts @@ -6,7 +6,7 @@ type I18nOptions = { export const i18nHandler = async ({ input }: I18nOptions) => { const { locale } = input; - const { serverSideTranslations } = await import("next-i18next/serverSideTranslations"); + const { serverSideTranslations } = await import("@calcom/web/server/lib/serverSideTranslations"); const i18n = await serverSideTranslations(locale, ["common", "vital"]); return {