feat: Upgrade to Next 15 (#18834)

* wip

* update layoutHOC

* wip

* remove app router related code no longer used

* remove more

* await cookies, headers, params, serachparams

* update yarn.lock

* await cookies, headers, params, serachparams more

* update yarn lock again

* downgrade next-auth to 4.22.1

* update yarn lock

* fix

* update yarn lock

* fix type checks

* update yarn lock

* await headers and cookies

* restore pages folder

* restore yarn.lock

* update yarn.lock

* await headers and cookies

* remove

* await params in API routes

* updates

* restore next.config.js

* remove i18n from next.config.js

* Fixed tests

* Fixed types

* Removed duplicate favicon.ico

* Fixing more types

* ImageResponse moved to next/og

* Fixed prisma import issues

* dynamic import for @ewsjs/xhr

* remove deasync dep

* dynamic import for @tryvital/vital-node

* fix type checks

* add back turbopack command

* Type fix

* Removed unneeded file

* fix turbopack relative path errors

* add comments

* remove unneeded code

* Fixed build errors

* await apis

* use Promise<Params> type in defaultResponderForAppDir util

* refactor scim api route

* fix type checks

* separate app-routing.config into client-config and server-config

* wip

* refactor routing forms components

* revert unneeded changes for easier review

* fix

* fix

* use CustomTrans

* fix type

* fix unit tests

* fix type error

* fix build error

* fix build error

* fix build error

* fix warnings

* fix warnings

* upgrade @tremor/react and tailwindcss

* numCols -> numItems

* fix forgot-password e2e test

* fix 1 more e2e test

* fix login e2e test

* fix e2e tests

* fix e2e tests

* clean up

* remove error

* use tremor/react 2.11.0

* fix

* rename CustomTrans to ServerTrans

* address comment

* fix test

* fix ServerTrans

* fix

* fix type

* fix ServerTrans usages 1

* fix translations

* fix type checks

* fix type checks

* link styling

* fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
Benny Joo
2025-03-20 21:30:51 -03:00
committed by GitHub
co-authored by Keith Williams Anik Dhabal Babu
parent 695a4d7a59
commit 8ad442f2be
263 changed files with 2002 additions and 1663 deletions
-7
View File
@@ -357,13 +357,6 @@ E2E_TEST_OIDC_USER_PASSWORD=
# ***********************************************************************************************************
# provide a value between 0 and 100 to ensure the percentage of traffic
# redirected from the legacy to the future pages
AB_TEST_BUCKET_PROBABILITY=50
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
APP_ROUTER_APPS_ENABLED=0
APP_ROUTER_TEAM_ENABLED=0
# disable setry server source maps
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
@@ -11,7 +11,7 @@ import TypePage, { type PageProps as ClientPageProps } from "~/users/views/users
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <TypePage {...props} />;
};
@@ -13,7 +13,7 @@ import type { PageProps as LegacyPageProps } from "~/users/views/users-type-publ
import LegacyPage from "~/users/views/users-type-public-view";
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx);
const { booking, isSEOIndexable = true, eventData, isBrandingHidden } = props;
@@ -31,7 +31,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
})),
],
};
const decodedParams = decodeParams(params);
const decodedParams = decodeParams(await params);
const metadata = await generateMeetingMetadata(
meeting,
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
@@ -52,7 +52,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx);
return <LegacyPage {...props} />;
@@ -11,7 +11,7 @@ import User, { type PageProps as ClientPageProps } from "~/users/views/users-pub
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <User {...props} />;
};
@@ -13,7 +13,9 @@ import type { PageProps as LegacyPageProps } from "~/users/views/users-public-vi
import LegacyPage from "~/users/views/users-public-view";
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
const { profile, markdownStrippedBio, isOrgSEOIndexable, entity } = props;
const isOrg = !!profile?.organization;
@@ -31,7 +33,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
() => markdownStrippedBio,
false,
getOrgFullOrigin(entity.orgSlug ?? null),
`/${decodeParams(params).user}`
`/${decodeParams(await params).user}`
);
return {
@@ -45,7 +47,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <LegacyPage {...props} />;
};
@@ -3,7 +3,7 @@ import { headers } from "next/headers";
import PageWrapper from "@components/PageWrapperAppDir";
export default async function BookingPageWrapperLayout({ children }: { children: React.ReactNode }) {
const h = headers();
const h = await headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
@@ -15,7 +15,7 @@ const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
export type ClientPageProps = UserTypePageProps | TeamTypePageProps;
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
if ((props as TeamTypePageProps)?.teamId) return <TeamTypePage {...(props as TeamTypePageProps)} />;
return <UserTypePage {...(props as UserTypePageProps)} />;
@@ -17,7 +17,7 @@ export type OrgTypePageProps = UserTypePageProps | TeamTypePageProps;
const getData = withAppDirSsr<OrgTypePageProps>(getServerSideProps);
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx);
const { booking, isSEOIndexable = true, eventData, isBrandingHidden } = props;
@@ -36,7 +36,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
})),
],
};
const decodedParams = decodeParams(params);
const decodedParams = decodeParams(await params);
const metadata = await generateMeetingMetadata(
meeting,
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
@@ -56,7 +56,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
};
const ServerPage = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
if ((props as TeamTypePageProps)?.teamId) {
return <TeamTypePage {...(props as TeamTypePageProps)} />;
}
@@ -15,7 +15,7 @@ const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
export type ClientPageProps = UserPageProps | TeamPageProps;
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
if ((props as TeamPageProps)?.team) return <TeamPage {...(props as TeamPageProps)} />;
return <UserPage {...(props as UserPageProps)} />;
@@ -18,7 +18,7 @@ export type OrgPageProps = UserPageProps | TeamPageProps;
const getData = withAppDirSsr<OrgPageProps>(getServerSideProps);
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx);
if ((props as TeamPageProps)?.team) {
@@ -30,7 +30,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
image: getOrgOrTeamAvatar(team),
},
};
const decodedParams = decodeParams(params);
const decodedParams = decodeParams(await params);
return {
...(await generateMeetingMetadata(
meeting,
@@ -78,7 +78,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
};
const ServerPage = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
if ((props as TeamPageProps)?.team) {
return <TeamPage {...(props as TeamPageProps)} />;
}
@@ -12,7 +12,7 @@ import type { Props } from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/i
import Page from "~/org/[orgSlug]/instant-meeting/team/[slug]/[type]/instant-meeting-view";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const { isBrandingHidden, eventData } = await getData(context);
const profileName = eventData?.profile.name ?? "";
@@ -29,7 +29,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
})),
],
};
const decodedParams = decodeParams(params);
const decodedParams = decodeParams(await params);
const metadata = await generateMeetingMetadata(
meeting,
() => `${title} | ${profileName}`,
@@ -51,7 +51,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
const getData = withAppDirSsr<Props>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: _PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <Page {...props} />;
};
@@ -10,7 +10,7 @@ import TypePage, { type PageProps as ClientPageProps } from "~/team/type-view";
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <TypePage {...props} />;
};
@@ -12,7 +12,7 @@ import LegacyPage from "~/team/type-view";
import type { PageProps as LegacyPageProps } from "~/team/type-view";
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx);
const { booking, isSEOIndexable, eventData, isBrandingHidden } = props;
@@ -29,7 +29,7 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
})),
],
};
const decodedParams = decodeParams(params);
const decodedParams = decodeParams(await params);
const metadata = await generateMeetingMetadata(
meeting,
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
@@ -51,7 +51,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <LegacyPage {...props} />;
};
export default ServerPage;
@@ -10,7 +10,7 @@ import TeamPage, { type PageProps as ClientPageProps } from "~/team/team-view";
const getData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <TeamPage {...props} />;
};
@@ -14,7 +14,7 @@ import LegacyPage from "~/team/team-view";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const { team, markdownStrippedBio, isSEOIndexable, currentOrgDomain } = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams)
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
const meeting = {
@@ -24,7 +24,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
image: getOrgOrTeamAvatar(team),
},
};
const decodedParams = decodeParams(params);
const decodedParams = decodeParams(await params);
const metadata = await generateMeetingMetadata(
meeting,
(t) => team.name || t("nameless_team"),
@@ -45,7 +45,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: _PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <LegacyPage {...props} />;
};
export default ServerPage;
@@ -17,7 +17,7 @@ export const generateMetadata = async () => {
};
const Page = async () => {
// const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
// const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
// const userId = session?.user?.id;
// const orgId = session?.user?.org?.id;
// if (!userId || !orgId) {
@@ -18,7 +18,7 @@ export const generateMetadata = async () =>
);
const Page = async ({ params }: PageProps) => {
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
redirect("/bookings/upcoming");
}
@@ -19,7 +19,7 @@ export const generateMetadata = async () =>
);
const Page = async ({ params, searchParams }: PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const session = await getServerSession({ req: context.req });
if (!session?.user?.id) {
@@ -16,8 +16,9 @@ export const generateMetadata = async () =>
(t) => t("create_manage_teams_collaborative")
);
const ServerPage = async ({ searchParams }: ServerPageProps) => {
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const ServerPage = async ({ searchParams: _searchParams }: ServerPageProps) => {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const searchParams = await _searchParams;
const token = Array.isArray(searchParams?.token) ? searchParams.token[0] : searchParams?.token;
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
@@ -12,7 +12,7 @@ const paramsSchema = z.object({
});
export const generateMetadata = async ({ params }: _PageProps) => {
const p = paramsSchema.safeParse(params);
const p = paramsSchema.safeParse(await params);
if (!p.success) {
return notFound();
@@ -33,7 +33,7 @@ export const generateMetadata = async ({ params }: _PageProps) => {
};
async function Page({ params }: _PageProps) {
const p = paramsSchema.safeParse(params);
const p = paramsSchema.safeParse(await params);
if (!p.success) {
return notFound();
@@ -9,7 +9,8 @@ import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import SetupView, { type PageProps as ClientPageProps } from "~/apps/[slug]/setup/setup-view";
export const generateMetadata = async ({ params }: ServerPageProps) => {
export const generateMetadata = async ({ params: _params }: ServerPageProps) => {
const params = await _params;
const metadata = await _generateMetadata(
() => `${params.slug}`,
() => ""
@@ -26,7 +27,7 @@ export const generateMetadata = async ({ params }: ServerPageProps) => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const { dehydratedState, ...props } = await getData(context);
return <SetupView {...props} />;
@@ -10,7 +10,9 @@ import Page from "~/apps/categories/categories-view";
const getData = withAppDirSsr(getServerSideProps);
async function ServerPage({ params, searchParams }: PageProps) {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <Page {...props} />;
}
@@ -10,7 +10,7 @@ import type { OnboardingPageProps } from "~/apps/installation/[[...step]]/step-v
import Page from "~/apps/installation/[[...step]]/step-view";
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const { appMetadata } = await getData(legacyCtx);
return await _generateMetadata(
@@ -22,7 +22,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const getData = withAppDirSsr<OnboardingPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <Page {...props} />;
};
export default ServerPage;
@@ -19,7 +19,7 @@ export const generateMetadata = async () => {
};
const InstalledAppsWrapper = async ({ params }: PageProps) => {
const parsedParams = querySchema.safeParse(params);
const parsedParams = querySchema.safeParse(await params);
if (!parsedParams.success) {
redirect("/apps/installed/calendar");
@@ -18,7 +18,7 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <AppsPage {...props} />;
@@ -0,0 +1,9 @@
"use client";
import { FormProvider as ReactHookFormProvider, useForm } from "react-hook-form";
export default function FormProvider({ children }: { children: React.ReactNode }) {
const methods = useForm();
return <ReactHookFormProvider {...methods}>{children}</ReactHookFormProvider>;
}
@@ -3,23 +3,25 @@ import type { PageProps as ServerPageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { cookies, headers } from "next/headers";
import type { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing.config";
import LayoutHandler from "@calcom/app-store/routing-forms/pages/layout-handler/[...appPages]";
import { routingFormsComponents } from "@calcom/app-store/routing-forms/pages/app-routing.client-config";
import type { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing.server-config";
import Shell from "@calcom/features/shell/Shell";
import { getServerSideProps } from "@lib/apps/routing-forms/[...pages]/getServerSideProps";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import FormProvider from "./FormProvider";
const normalizePages = (pages: string[] | string | undefined) => {
const normalizedPages = Array.isArray(pages) ? pages : pages?.split("/") ?? [];
return {
mainPage: normalizedPages[0],
mainPage: normalizedPages[0] ?? "forms",
subPages: normalizedPages.slice(1),
};
};
export const generateMetadata = async ({ params }: { params: { pages: string[] } }) => {
const { mainPage } = normalizePages(params.pages);
export const generateMetadata = async ({ params }: { params: Promise<{ pages: string[] }> }) => {
const { mainPage } = normalizePages((await params).pages);
return await _generateMetadata(
// TODO: Need to show the actual form name instead of "Form"
(t) => (mainPage === "routing-link" ? `Form | Cal.com Forms` : `${t("routing_forms")} | Cal.com Forms`),
@@ -32,22 +34,24 @@ type GetServerSidePropsResult =
const getData = withAppDirSsr<GetServerSidePropsResult>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
const { mainPage, subPages } = normalizePages(params.pages);
const { mainPage } = normalizePages((await params).pages);
const componentProps = {
...props,
pages: subPages,
};
const Component = await routingFormsComponents[mainPage as keyof typeof routingFormsComponents]();
const FinalComponent = () => (
<FormProvider>
<Component {...props as any} />
</FormProvider>
);
if (mainPage === "routing-link") {
return <LayoutHandler {...componentProps} />;
return <FinalComponent />;
}
return (
<Shell withoutMain withoutSeo>
<LayoutHandler {...componentProps} />
<FinalComponent />
</Shell>
);
};
@@ -21,7 +21,7 @@ const querySchema = z.object({
const ServerPage = async ({ searchParams }: PageProps) => {
const t = await getTranslate();
const { error } = querySchema.parse({ error: searchParams?.error || undefined });
const { error } = querySchema.parse({ error: (await searchParams)?.error || undefined });
const errorMsg = t("error_during_login") + (error ? ` Error code: ${error}` : "");
return (
<AuthContainer>
@@ -19,7 +19,7 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <SetNewUserPassword {...props} />;
@@ -18,7 +18,7 @@ export const generateMetadata = async () => {
};
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const session = await getServerSession({ req: context.req });
if (session) {
@@ -20,7 +20,9 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <Login {...props} />;
};
@@ -17,8 +17,8 @@ export const generateMetadata = async () => {
const Page = async ({ params, searchParams }: PageProps) => {
// cookie will be cleared in `/apps/web/middleware.ts`
const h = headers();
const context = buildLegacyCtx(h, cookies(), params, searchParams);
const h = await headers();
const context = buildLegacyCtx(h, await cookies(), await params, await searchParams);
await ssrInit(context);
return <Logout query={context.query} />;
@@ -20,7 +20,9 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <Setup {...props} />;
};
@@ -14,7 +14,7 @@ import type { PageProps as ClientPageProps } from "~/auth/signin-view";
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const session = await getServerSession({ req: context.req });
if (session) {
redirect("/");
@@ -11,7 +11,7 @@ import SSOProviderView from "~/auth/sso/provider-view";
const getData = withAppDirSsr<SSOProviderPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <SSOProviderView {...props} />;
@@ -11,7 +11,7 @@ import SSODirectView from "~/auth/sso/direct-view";
const getData = withAppDirSsr<SSODirectPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <SSODirectView {...props} />;
@@ -19,7 +19,9 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <VerifyEmailChange {...props} />;
};
export default ServerPage;
@@ -25,7 +25,7 @@ const querySchema = z.object({
const getSchedule = cache((id: number) => ScheduleRepository.findScheduleById({ id }));
export const generateMetadata = async ({ params }: PageProps) => {
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
notFound();
}
@@ -43,13 +43,13 @@ export const generateMetadata = async ({ params }: PageProps) => {
};
const Page = async ({ params }: PageProps) => {
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
notFound();
}
// const scheduleId = Number(params.schedule);
// const scheduleId = Number(await params.schedule);
// const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
// const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
// const userId = session?.user?.id;
// if (!userId) {
// notFound();
@@ -13,7 +13,7 @@ import {
const getEmbedData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getEmbedData(context);
return <OldPage {...props} />;
};
@@ -16,7 +16,7 @@ import {
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const { bookingInfo, eventType, recurringBookings, orgSlug } = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams)
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
@@ -33,7 +33,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: _PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <OldPage {...props} />;
};
@@ -10,7 +10,7 @@ import { type PageProps } from "@lib/d/[link]/[slug]/getServerSideProps";
import Type from "~/d/[link]/d-type-view";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const pageProps = await getData(legacyCtx);
const { booking, eventData, isBrandingHidden } = pageProps;
@@ -27,7 +27,7 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: _PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const pageProps = await getData(legacyCtx);
return <Type {...pageProps} />;
@@ -22,7 +22,7 @@ const querySchema = z.object({
});
export const generateMetadata = async ({ params }: _PageProps) => {
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
return await _generateMetadata(
(t) => `${t("event_type")}`,
@@ -43,7 +43,7 @@ export const generateMetadata = async ({ params }: _PageProps) => {
const getData = withAppDirSsr<EventTypePageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: _PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(legacyCtx);
return <EventTypePageWrapper {...props} />;
@@ -22,7 +22,7 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <Page {...props} />;
+1 -1
View File
@@ -3,7 +3,7 @@ import { headers } from "next/headers";
import PageWrapper from "@components/PageWrapperAppDir";
export default async function PageWrapperLayout({ children }: { children: React.ReactNode }) {
const h = headers();
const h = await headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
@@ -10,7 +10,9 @@ import { APP_NAME } from "@calcom/lib/constants";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
const eventName = props.booking.title;
return await _generateMetadata(
(t) => `${t("payment")} | ${eventName} | ${APP_NAME}`,
@@ -21,7 +23,9 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => {
const getData = withAppDirSsr<PaymentPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const props = await getData(
buildLegacyCtx(await headers(), await cookies(), await params, await searchParams)
);
return <PaymentPage {...props} />;
};
@@ -11,7 +11,7 @@ import { OrganizationRepository } from "@calcom/lib/server/repository/organizati
const orgIdSchema = z.object({ id: z.coerce.number() });
export const generateMetadata = async ({ params }: { params: Params }) => {
const input = orgIdSchema.safeParse(params);
const input = orgIdSchema.safeParse(await params);
if (!input.success) {
return await _generateMetadata(
(t) => t("editing_org"),
@@ -28,7 +28,7 @@ export const generateMetadata = async ({ params }: { params: Params }) => {
};
const Page = async ({ params }: { params: Params }) => {
const input = orgIdSchema.safeParse(params);
const input = orgIdSchema.safeParse(await params);
if (!input.success) notFound();
@@ -11,7 +11,7 @@ import { UserRepository } from "@calcom/lib/server/repository/user";
const userIdSchema = z.object({ id: z.coerce.number() });
export const generateMetadata = async ({ params }: { params: Params }) => {
const input = userIdSchema.safeParse(params);
const input = userIdSchema.safeParse(await params);
if (!input.success) {
return await _generateMetadata(
(t) => t("editing_user"),
@@ -28,7 +28,7 @@ export const generateMetadata = async ({ params }: { params: Params }) => {
};
const Page = async ({ params }: { params: Params }) => {
const input = userIdSchema.safeParse(params);
const input = userIdSchema.safeParse(await params);
if (!input.success) {
notFound();
@@ -12,7 +12,7 @@ import AdminLayoutAppDirClient from "./AdminLayoutAppDirClient";
type AdminLayoutAppDirProps = Omit<AdminLayoutProps, "userRole">;
export default async function AdminLayoutAppDir(props: AdminLayoutAppDirProps) {
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const userRole = session?.user?.role;
return await SettingsLayoutAppDir({ children: <AdminLayoutAppDirClient {...props} userRole={userRole} /> });
@@ -12,8 +12,9 @@ export const generateMetadata = async () =>
(t) => t("add_webhook_description", { appName: APP_NAME })
);
const Page = async ({ params }: PageProps) => {
const Page = async ({ params: _params }: PageProps) => {
const t = await getTranslate();
const params = await _params;
const id = typeof params?.id === "string" ? params.id : undefined;
const webhook = await WebhookRepository.findByWebhookId(id);
@@ -12,7 +12,7 @@ import SettingsLayoutAppDirClient from "./SettingsLayoutAppDirClient";
type SettingsLayoutAppDirProps = Omit<SettingsLayoutProps, "currentOrg" | "otherTeams">;
export default async function SettingsLayoutAppDir(props: SettingsLayoutAppDirProps) {
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const userId = session?.user?.id ?? -1;
const orgId = session?.user?.org?.id ?? -1;
@@ -19,7 +19,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<CreateANewLicenseKeyForm />
@@ -17,7 +17,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LegacyPage />
@@ -17,7 +17,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LegacyPage />
@@ -18,7 +18,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LegacyPage />
@@ -17,7 +17,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LegacyPage />
@@ -23,7 +23,7 @@ type Props = {
const getData = withAppDirSsr<Props>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LicenseRequired>
@@ -17,7 +17,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LegacyPage />
@@ -23,7 +23,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr<Props>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
await getData(buildLegacyCtx(await headers(), await cookies(), await params, await searchParams));
return (
<LayoutWrapper>
<LicenseRequired>
@@ -18,7 +18,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr<SignupProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: PageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <Signup {...props} />;
@@ -39,7 +39,7 @@ export const generateMetadata = async () => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <VideosSingleView {...props} />;
@@ -18,7 +18,7 @@ export const generateMetadata = async () =>
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <MeetingEnded {...props} />;
@@ -18,7 +18,7 @@ const querySchema = z.object({
});
export const generateMetadata = async ({ params }: ServerPageProps) => {
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
notFound();
}
@@ -35,7 +35,7 @@ export const generateMetadata = async ({ params }: ServerPageProps) => {
const getData = withAppDirSsr<ClientPageProps>(getServerSideProps);
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
const context = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
const props = await getData(context);
return <MeetingNotStarted {...props} />;
@@ -23,7 +23,7 @@ const querySchema = z.object({
const getWorkflow = cache((id: number) => WorkflowRepository.getById({ id }));
export const generateMetadata = async ({ params }: PageProps): Promise<Metadata | null> => {
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
notFound();
}
@@ -38,9 +38,9 @@ export const generateMetadata = async ({ params }: PageProps): Promise<Metadata
};
const Page = async ({ params }: PageProps) => {
// const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
// const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
// const user = session?.user;
const parsed = querySchema.safeParse(params);
const parsed = querySchema.safeParse(await params);
if (!parsed.success) {
notFound();
}
@@ -15,7 +15,7 @@ export const generateMetadata = async () =>
);
const Page = async ({ params, searchParams }: PageProps) => {
// const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
// const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
// const user = session?.user;
// const filters = getTeamsFiltersFromQuery({ ...searchParams, ...params });
+3 -3
View File
@@ -7,8 +7,8 @@ export type SearchParams = {
};
export type PageProps = {
params: Params;
searchParams: SearchParams;
params: Promise<Params>;
searchParams: Promise<SearchParams>;
};
export type LayoutProps = { params: Params; children: React.ReactElement };
export type LayoutProps = { params: Promise<Params>; children: React.ReactElement };
+7 -39
View File
@@ -1,6 +1,4 @@
import { type TFunction } from "i18next";
import i18next from "i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { cookies, headers } from "next/headers";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
@@ -8,48 +6,19 @@ import type { AppImageProps, MeetingImageProps } from "@calcom/lib/OgImages";
import { constructAppImage, constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages";
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG, CAL_URL } from "@calcom/lib/constants";
import { buildCanonical } from "@calcom/lib/next-seo.config";
import { getTranslation } from "@calcom/lib/server/i18n";
import { truncateOnWord } from "@calcom/lib/text";
//@ts-expect-error no type definitions
import config from "@calcom/web/next-i18next.config";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
const i18nInstanceCache: Record<string, any> = {};
const createI18nInstance = async (locale: string, ns: string) => {
const cacheKey = `${locale}-${ns}`;
// Check module-level cache first
if (i18nInstanceCache[cacheKey]) {
return i18nInstanceCache[cacheKey];
}
const { _nextI18Next } = await serverSideTranslations(locale, [ns], config);
const _i18n = i18next.createInstance();
await _i18n.init({
lng: locale,
resources: _nextI18Next?.initialI18nStore,
fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale,
});
// Cache the instance
i18nInstanceCache[cacheKey] = _i18n;
return _i18n;
};
const getTranslationWithCache = async (locale: string, ns = "common") => {
const localeWithFallback = locale ?? "en";
const i18n = await createI18nInstance(localeWithFallback, ns);
return i18n.getFixedT(localeWithFallback, ns);
};
export const getTranslate = async () => {
const headersList = await headers();
// If "x-locale" does not exist in header,
// ensure that config.matcher in middleware includes the page you are testing
const locale = headersList.get("x-locale") ?? (await getLocale(buildLegacyRequest(headersList, cookies())));
const t = await getTranslationWithCache(locale ?? "en");
return t;
const locale =
headersList.get("x-locale") ?? (await getLocale(buildLegacyRequest(headersList, await cookies())));
return await getTranslation(locale ?? "en", "common");
};
const _generateMetadataWithoutImage = async (
@@ -59,11 +28,10 @@ const _generateMetadataWithoutImage = async (
origin?: string,
pathname?: string
) => {
const h = headers();
const h = await headers();
const _pathname = h.get("x-pathname") ?? pathname ?? "";
const canonical = buildCanonical({ path: _pathname, origin: origin ?? CAL_URL });
const locale = h.get("x-locale") ?? (await getLocale(buildLegacyRequest(h, cookies()))) ?? "en";
const t = await getTranslationWithCache(locale);
const t = await getTranslate();
const title = getTitle(t);
const description = getDescription(t);
@@ -16,7 +16,7 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx";
async function handler(req: NextRequest) {
const body = await parseRequestData(req);
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
@@ -14,7 +14,7 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx";
async function postHandler(req: NextRequest) {
const body = await parseRequestData(req);
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
@@ -18,7 +18,7 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx";
async function postHandler(req: NextRequest) {
const body = await parseRequestData(req);
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
@@ -23,7 +23,7 @@ const selectedCalendarSelectSchema = z.object({
});
async function authMiddleware() {
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session?.user?.id) {
throw new HttpError({ statusCode: 401, message: "Not authenticated" });
+2 -2
View File
@@ -26,8 +26,8 @@ const handleValidationError = (error: z.ZodError): NextResponse => {
);
};
async function handler(req: NextRequest, { params }: { params: Params }) {
const result = querySchema.safeParse(params);
async function handler(req: NextRequest, { params }: { params: Promise<Params> }) {
const result = querySchema.safeParse(await params);
if (!result.success) {
return handleValidationError(result.error);
}
+1 -1
View File
@@ -15,7 +15,7 @@ async function handler(req: NextRequest) {
} catch (error) {
return NextResponse.json({ success: false, message: "Invalid JSON" }, { status: 400 });
}
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
const result = await handleCancelBooking({
appDirRequestBody,
userId: session?.user?.id || -1,
+1 -1
View File
@@ -457,7 +457,7 @@ const inputSchema = z.object({
});
async function handler(request: NextRequest) {
const headersList = headers();
const headersList = await headers();
const requestBody = await request.json();
// HMAC verification
@@ -1,3 +1,4 @@
import type { Params } from "app/_types";
import { NextResponse, type NextRequest } from "next/server";
import { describe, expect, it, vi } from "vitest";
@@ -21,7 +22,7 @@ describe("defaultResponderForAppDir", () => {
it("should return a JSON response when handler resolves with a result", async () => {
const f = vi.fn().mockResolvedValue(NextResponse.json({ success: true }));
const req = { method: "GET", url: "/api/test" } as unknown as NextRequest;
const params = {};
const params = Promise.resolve<Params>({})
const response = await defaultResponderForAppDir(f)(req, { params });
const json = await response.json();
@@ -33,7 +34,7 @@ describe("defaultResponderForAppDir", () => {
it("should return an empty JSON response when handler resolves with no result", async () => {
const f = vi.fn().mockResolvedValue(null);
const req = { method: "GET", url: "/api/test" } as unknown as NextRequest;
const params = {};
const params = Promise.resolve<Params>({})
const response = await defaultResponderForAppDir(f)(req, { params });
const json = await response.json();
@@ -45,7 +46,7 @@ describe("defaultResponderForAppDir", () => {
it("should respond with status code 409 for NoAvailableUsersFound", async () => {
const f = vi.fn().mockRejectedValue(new Error(ErrorCode.NoAvailableUsersFound));
const req = { method: "GET", url: "/api/test" } as unknown as NextRequest;
const params = {};
const params = Promise.resolve<Params>({})
const response = await defaultResponderForAppDir(f)(req, { params });
const json = await response.json();
@@ -61,7 +62,7 @@ describe("defaultResponderForAppDir", () => {
it("should respond with a 429 status code for rate limit errors", async () => {
const f = vi.fn().mockRejectedValue(new TRPCError({ code: "TOO_MANY_REQUESTS" }));
const req = { method: "POST", url: "/api/test" } as unknown as NextRequest;
const params = {};
const params = Promise.resolve<Params>({})
const response = await defaultResponderForAppDir(f)(req, { params });
const json = await response.json();
@@ -9,14 +9,14 @@ import { performance } from "@calcom/lib/server/perfObserver";
type Handler<T extends NextResponse | Response = NextResponse> = (
req: NextRequest,
{ params }: { params: Params }
{ params }: { params: Promise<Params> }
) => Promise<T>;
export const defaultResponderForAppDir = <T extends NextResponse | Response = NextResponse>(
handler: Handler<T>,
endpointRoute?: string
) => {
return async (req: NextRequest, { params }: { params: Params }) => {
return async (req: NextRequest, { params }: { params: Promise<Params> }) => {
let ok = false;
try {
performance.mark("Start");
-40
View File
@@ -1,40 +0,0 @@
import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import { cookies, headers } from "next/headers";
import { NextResponse } from "next/server";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { FUTURE_ROUTES_OVERRIDE_COOKIE_NAME as COOKIE_NAME } from "@calcom/lib/constants";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
async function getHandler() {
const headersList = headers();
const cookiesList = cookies();
const legacyReq = buildLegacyRequest(headersList, cookiesList);
const session = await getServerSession({ req: legacyReq });
if (!session || !session.user || !session.user.email) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
}
let redirectUrl = "/";
// We take you back where you came from if possible
const referer = headersList.get("referer");
if (referer) redirectUrl = referer;
const response = NextResponse.redirect(redirectUrl);
// If has the cookie, Opt-out of V2
if (cookiesList.has(COOKIE_NAME) && cookiesList.get(COOKIE_NAME)?.value === "1") {
response.cookies.set(COOKIE_NAME, "0", { maxAge: 0, path: "/" });
} else {
/* Opt-in to V2 */
response.cookies.set(COOKIE_NAME, "1", { path: "/" });
}
return response;
}
export const GET = defaultResponderForAppDir(getHandler);
@@ -9,8 +9,8 @@ import prisma from "@calcom/prisma";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
async function postHandler() {
const headersList = headers();
const cookiesList = cookies();
const headersList = await headers();
const cookiesList = await cookies();
const legacyReq = buildLegacyRequest(headersList, cookiesList);
const session = await getServerSession({ req: legacyReq });
+1 -1
View File
@@ -3,7 +3,7 @@ import { headers } from "next/headers";
import { NextResponse } from "next/server";
async function getHandler() {
const headersList = headers();
const headersList = await headers();
const country = headersList.get("x-vercel-ip-country") || "Unknown";
const response = NextResponse.json({ country });
+2 -2
View File
@@ -8,8 +8,8 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
async function getHandler() {
const headersList = headers();
const cookiesList = cookies();
const headersList = await headers();
const cookiesList = await cookies();
const legacyReq = buildLegacyRequest(headersList, cookiesList);
const session = await getServerSession({ req: legacyReq });
+1 -1
View File
@@ -79,7 +79,7 @@ async function handler(request: NextRequest) {
try {
/** @see https://trpc.io/docs/server-side-calls */
// Create a legacy request object for compatibility
const legacyReq = buildLegacyRequest(headers(), cookies());
const legacyReq = buildLegacyRequest(await headers(), await cookies());
const res = {} as any; // Response is still mocked as it's not used in this context
const ctx = await createContext({ req: legacyReq, res }, sessionGetter);
+1 -1
View File
@@ -168,7 +168,7 @@ async function getHandler(request: NextRequest) {
const parsedQuery = logoApiSchema.parse(Object.fromEntries(searchParams.entries()));
// Create a legacy request object for compatibility
const legacyReq = buildLegacyRequest(headers(), cookies());
const legacyReq = buildLegacyRequest(await headers(), await cookies());
const { isValidOrgDomain } = orgDomainConfig(legacyReq);
const hostname = request.headers.get("host");
+1 -1
View File
@@ -15,7 +15,7 @@ async function getHandler() {
const preSessionDate = performance.now();
// Create a legacy request object for compatibility
const legacyReq = buildLegacyRequest(headers(), cookies());
const legacyReq = buildLegacyRequest(await headers(), await cookies());
const session = await getServerSession({ req: legacyReq });
if (!session) {
+1 -1
View File
@@ -26,7 +26,7 @@ async function handler() {
return NextResponse.json({ error: "Plain Chat is not enabled" }, { status: 404 });
}
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized - No session email found" }, { status: 401 });
}
@@ -62,7 +62,7 @@ export async function postHandler(request: NextRequest) {
return NextResponse.json({ message: "Test request successful" });
}
const headersList = headers();
const headersList = await headers();
const testMode = process.env.NEXT_PUBLIC_IS_E2E || process.env.INTEGRATION_TEST_MODE;
if (!testMode) {
@@ -1,7 +1,9 @@
import type { DirectorySyncEvent, DirectorySyncRequest } from "@boxyhq/saml-jackson";
import type { Params } from "app/_types";
import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { z } from "zod";
import handleGroupEvents from "@calcom/features/ee/dsync/lib/handleGroupEvents";
import handleUserEvents from "@calcom/features/ee/dsync/lib/handleUserEvents";
@@ -51,35 +53,40 @@ const handleEvents = async (event: DirectorySyncEvent) => {
}
};
const querySchema = z.object({
directory: z.string().array(),
});
// This is the handler for the SCIM API requests
async function getHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) {
return handleScimRequest(request, "GET", params.directory);
async function getHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
return handleScimRequest(request, "GET", params);
}
async function postHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) {
return handleScimRequest(request, "POST", params.directory);
async function postHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
return handleScimRequest(request, "POST", params);
}
async function putHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) {
return handleScimRequest(request, "PUT", params.directory);
async function putHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
return handleScimRequest(request, "PUT", params);
}
async function patchHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) {
return handleScimRequest(request, "PATCH", params.directory);
async function patchHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
return handleScimRequest(request, "PATCH", params);
}
async function deleteHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) {
return handleScimRequest(request, "DELETE", params.directory);
async function deleteHandler(request: NextRequest, { params }: { params: Promise<Params> }) {
return handleScimRequest(request, "DELETE", params);
}
async function handleScimRequest(request: NextRequest, method: string, directoryParams?: string[]) {
const { dsyncController } = await jackson();
if (!directoryParams || directoryParams.length === 0) {
async function handleScimRequest(request: NextRequest, method: string, params: Promise<Params>) {
const parsed = querySchema.safeParse(await params);
if (!parsed.success || parsed.data.directory.length === 0) {
return NextResponse.json({ error: "Missing directory parameters" }, { status: 400 });
}
const [directoryId, path, resourceId] = directoryParams;
const { dsyncController } = await jackson();
const [directoryId, path, resourceId] = parsed.data.directory;
const shouldLog = DIRECTORY_IDS_TO_LOG.includes(directoryId);
const searchParams = request.nextUrl.searchParams;
@@ -90,7 +97,7 @@ async function handleScimRequest(request: NextRequest, method: string, directory
safeStringify({
method,
url: request.url,
params: directoryParams,
params: parsed.data.directory,
searchParams: Object.fromEntries(searchParams.entries()),
body: request.body
? await request
+4 -12
View File
@@ -1,19 +1,11 @@
import { ImageResponse } from "next/og";
import type { NextRequest } from "next/server";
import { ImageResponse } from "next/server";
import type { SatoriOptions } from "satori";
import { z } from "zod";
import { Meeting, App, Generic } from "@calcom/lib/OgImages";
import { WEBAPP_URL } from "@calcom/lib/constants";
const calFont = fetch(new URL("/fonts/cal.ttf", WEBAPP_URL)).then((res) => res.arrayBuffer());
const interFont = fetch(new URL("/fonts/Inter-Regular.ttf", WEBAPP_URL)).then((res) => res.arrayBuffer());
const interFontMedium = fetch(new URL("/fonts/Inter-Medium.ttf", WEBAPP_URL)).then((res) =>
res.arrayBuffer()
);
export const runtime = "edge";
const meetingSchema = z.object({
@@ -43,9 +35,9 @@ async function handler(req: NextRequest) {
const imageType = searchParams.get("type");
const [calFontData, interFontData, interFontMediumData] = await Promise.all([
calFont,
interFont,
interFontMedium,
fetch(new URL("/fonts/cal.ttf", WEBAPP_URL)).then((res) => res.arrayBuffer()),
fetch(new URL("/fonts/Inter-Regular.ttf", WEBAPP_URL)).then((res) => res.arrayBuffer()),
fetch(new URL("/fonts/Inter-Medium.ttf", WEBAPP_URL)).then((res) => res.arrayBuffer()),
]);
const ogConfig = {
width: 1200,
+1 -1
View File
@@ -29,7 +29,7 @@ async function postHandler(request: NextRequest) {
if (!process.env.CALENDSO_ENCRYPTION_KEY)
return NextResponse.json({ message: "Missing encryption key" }, { status: 500 });
const legacyRequest = buildLegacyRequest(headers(), cookies());
const legacyRequest = buildLegacyRequest(await headers(), await cookies());
// Get the raw request body
const rawBody = await getRawBody(legacyRequest);
@@ -21,11 +21,11 @@ const querySchema = z.object({
session_id: z.string().min(1),
});
async function getHandler(req: NextRequest, { params }: { params: Params }) {
async function getHandler(req: NextRequest, { params }: { params: Promise<Params> }) {
try {
const searchParams = req.nextUrl.searchParams;
const { team: id, session_id } = querySchema.parse({
team: params.team,
team: (await params).team,
session_id: searchParams.get("session_id"),
});
@@ -86,7 +86,7 @@ async function getHandler(req: NextRequest, { params }: { params: Params }) {
}
}
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session) {
return NextResponse.json({ message: "Team upgraded successfully" });
@@ -20,8 +20,8 @@ const stateSchema = z.object({
async function getHandler(request: NextRequest) {
try {
const headersList = headers();
const cookiesList = cookies();
const headersList = await headers();
const cookiesList = await cookies();
const legacyReq = buildLegacyRequest(headersList, cookiesList);
const session = await getServerSession({ req: legacyReq });
+1 -1
View File
@@ -19,7 +19,7 @@ async function postHandler(request: NextRequest) {
const body = await request.json();
const { username, orgSlug } = bodySchema.parse(body);
const legacyReq = buildLegacyRequest(headers(), cookies());
const legacyReq = buildLegacyRequest(await headers(), await cookies());
// Get current org domain from request headers
const { currentOrgDomain } = orgDomainConfig(legacyReq);
@@ -118,7 +118,7 @@ async function handleBookingAction(
const createCaller = createCallerFactory(bookingsRouter);
// Use buildLegacyRequest to create a request object compatible with Pages Router
const legacyReq = request ? buildLegacyRequest(headers(), cookies()) : ({} as any);
const legacyReq = request ? buildLegacyRequest(await headers(), await cookies()) : ({} as any);
const res = {} as any;
const ctx = await createContext({ req: legacyReq, res }, sessionGetter);
-1
View File
@@ -44,4 +44,3 @@ export default async function IconsPage() {
</div>
);
}
export const dynamic = "force-static";
+57 -5
View File
@@ -9,7 +9,6 @@ import { IconSprites } from "@calcom/ui/components/icon";
import { NotificationSoundHandler } from "@calcom/web/components/notification-sound-handler";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { prepareRootMetadata } from "@lib/metadata";
import { ssrInit } from "@server/lib/ssr";
@@ -26,7 +25,60 @@ const calFont = localFont({
weight: "600",
});
export const generateMetadata = () => prepareRootMetadata();
export const viewport = {
width: "device-width",
initialScale: 1.0,
maximumScale: 1.0,
userScalable: false,
viewportFit: "cover",
themeColor: [
{
media: "(prefers-color-scheme: light)",
color: "#f9fafb",
},
{
media: "(prefers-color-scheme: dark)",
color: "#1C1C1C",
},
],
};
export const metadata = {
icons: {
icon: "/favicon.ico",
apple: "/api/logo?type=apple-touch-icon",
other: [
{
rel: "icon-mask",
url: "/safari-pinned-tab.svg",
color: "#000000",
},
{
url: "/api/logo?type=favicon-16",
sizes: "16x16",
type: "image/png",
},
{
url: "/api/logo?type=favicon-32",
sizes: "32x32",
type: "image/png",
},
],
},
manifest: "/site.webmanifest",
other: {
"application-TileColor": "#ff0000",
},
twitter: {
site: "@calcom",
creator: "@calcom",
card: "summary_large_image",
},
robots: {
index: true,
follow: true,
},
};
const getInitialProps = async (url: string) => {
const { pathname, searchParams } = new URL(url);
@@ -34,7 +86,7 @@ const getInitialProps = async (url: string) => {
const isEmbed = pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null;
const embedColorScheme = searchParams?.get("ui.color-scheme");
const req = { headers: headers(), cookies: cookies() };
const req = { headers: await headers(), cookies: await cookies() };
const newLocale = await getLocale(req);
const direction = dir(newLocale);
@@ -49,7 +101,7 @@ const getFallbackProps = () => ({
});
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const h = headers();
const h = await headers();
const fullUrl = h.get("x-url") ?? "";
const nonce = h.get("x-csp") ?? "";
@@ -60,7 +112,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
? getFallbackProps()
: await getInitialProps(fullUrl);
const ssr = await ssrInit(buildLegacyCtx(h, cookies(), {}, {}));
const ssr = await ssrInit(buildLegacyCtx(h, await cookies(), {}, {}));
return (
<html
lang={locale}
+2 -2
View File
@@ -221,7 +221,7 @@ function NotFound({ t, headers }: { t: any; headers: ReadonlyHeaders }) {
}
export const generateMetadata = async () => {
const headersList = headers();
const headersList = await headers();
const pathname = headersList.get("x-pathname") ?? "";
const isInsights = pathname?.startsWith("/insights");
@@ -243,7 +243,7 @@ export const generateMetadata = async () => {
const ServerPage = async () => {
const t = await getTranslate();
const h = headers();
const h = await headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
<PageWrapper requiresLicense={false} nonce={nonce}>
+1 -1
View File
@@ -6,7 +6,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
const RedirectPage = async () => {
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (!session?.user?.id) {
redirect("/auth/login");
+1 -1
View File
@@ -8,7 +8,7 @@ import { getServerSideProps } from "@lib/reschedule/[uid]/getServerSideProps";
const getEmbedData = withEmbedSsrAppDir(getServerSideProps);
const Page = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
await getEmbedData(legacyCtx);
return null;
+1 -1
View File
@@ -8,7 +8,7 @@ import { getServerSideProps } from "@lib/reschedule/[uid]/getServerSideProps";
const getData = withAppDirSsr(getServerSideProps);
const Page = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
await getData(legacyCtx);
@@ -10,7 +10,7 @@ const paramsSchema = z
pages: [],
});
export default function RoutingForms({ params }: PageProps) {
const { pages } = paramsSchema.parse(params);
export default async function RoutingForms({ params }: PageProps) {
const { pages } = paramsSchema.parse(await params);
redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`);
}
+1 -1
View File
@@ -1,6 +1,6 @@
"use client";
import type { SSRConfig } from "next-i18next";
import type { SSRConfig } from "next-i18next/dist/types/types";
// import I18nLanguageHandler from "@components/I18nLanguageHandler";
import { usePathname } from "next/navigation";
import Script from "next/script";
+376
View File
@@ -0,0 +1,376 @@
import type { TFunction } from "i18next";
import type { ReactNode, ReactElement, FC } from "react";
import React, { isValidElement, Fragment, createElement, cloneElement } from "react";
type ServerTransProps = {
i18nKey: string; // Translation key
components?: ReactElement[] | Record<string, ReactElement>; // Components to inject
t: TFunction;
values?: Record<string, string | number>; // Values for interpolation
children?: ReactNode; // Children as fallback content
parent?: React.ElementType; // Parent element to wrap content in
};
/**
* A custom Trans component that doesn't use React Context
* Handles HTML tags and component interpolation in translations
*/
const ServerTrans: FC<ServerTransProps> = ({
i18nKey,
components = [],
t,
values = {},
children,
parent,
}) => {
const translationOptions = { ...values };
// Get translated content
const content = t(i18nKey, translationOptions);
// If no content at all, use children as fallback
if (!content && children) {
return <>{children}</>;
}
// If no content, return empty fragment
if (!content) {
return <></>;
}
let result: ReactNode[];
// Process array-based components like <0>content</0>
if (Array.isArray(components) && components.length > 0) {
result = parseArrayComponents(content, components);
}
// Process object-based components like <tag>content</tag> or {{tag}}
else if (isObject(components) && Object.keys(components).length > 0) {
result = parseObjectComponents(content, components as Record<string, ReactElement>);
}
// Process just HTML tags
else {
result = parseHtmlTags(content);
}
// Wrap in parent element if specified
if (parent) {
const Parent = parent;
return <Parent>{result}</Parent>;
}
return <>{result}</>;
};
// Utility function to check if object
const isObject = (obj: any): obj is Record<string, unknown> =>
obj !== null && typeof obj === "object" && !Array.isArray(obj);
// Parse array-based components like <0>content</0>
const parseArrayComponents = (content: string, components: ReactElement[]): ReactNode[] => {
const parts: ReactNode[] = [];
let currentText = "";
// Simple state machine parser to handle nested tags
const parseContent = (text: string) => {
for (let i = 0; i < text.length; i++) {
// Look for opening tag
if (text[i] === "<" && /\d/.test(text[i + 1])) {
// Found potential component tag
let tagIndex = "";
let j = i + 1;
// Read the tag index
while (j < text.length && /\d/.test(text[j])) {
tagIndex += text[j];
j++;
}
// Check if it's a valid tag
if (j < text.length && text[j] === ">") {
// It's an opening tag, save text before it
if (currentText) {
// Process any HTML in the text
parts.push(...parseHtmlTags(currentText));
currentText = "";
}
// Get the content inside the tag
const closingTag = `</${tagIndex}>`;
const tagContent = getTextBetweenTags(text.substring(j + 1), tagIndex);
if (tagContent !== null) {
// We found the content, now add the component
const componentIndex = parseInt(tagIndex, 10);
if (componentIndex < components.length) {
const component = components[componentIndex];
if (isValidElement(component)) {
// Add the component with its content
parts.push(
cloneElement(
component,
{
...(component.props || {}),
key: component.key || `comp-${componentIndex}`,
},
// Process any HTML in the inner content
...parseHtmlTags(tagContent)
)
);
} else {
// If not a valid element, just add the content
parts.push(...parseHtmlTags(tagContent));
}
} else {
// Component index out of bounds, just add the content
parts.push(...parseHtmlTags(tagContent));
}
// Skip to after the closing tag
i = j + tagContent.length + closingTag.length;
continue;
}
}
}
// Regular character, add to current text
currentText += text[i];
}
// Add any remaining text
if (currentText) {
parts.push(...parseHtmlTags(currentText));
}
};
// Helper to find matching content between tags
const getTextBetweenTags = (text: string, tagIndex: string): string | null => {
let depth = 1;
let content = "";
for (let i = 0; i < text.length; i++) {
// Check for nested opening tag
if (
text[i] === "<" &&
text.substring(i + 1, i + 1 + tagIndex.length) === tagIndex &&
text[i + 1 + tagIndex.length] === ">"
) {
depth++;
content += text.substring(i, i + 2 + tagIndex.length);
i += 1 + tagIndex.length;
continue;
}
// Check for closing tag
if (
text[i] === "<" &&
text.substring(i + 1, i + 2 + tagIndex.length) === `/${tagIndex}` &&
text[i + 2 + tagIndex.length] === ">"
) {
depth--;
if (depth === 0) {
// Found the matching closing tag
return content;
}
content += text.substring(i, i + 3 + tagIndex.length);
i += 2 + tagIndex.length;
continue;
}
content += text[i];
}
return null; // No matching closing tag found
};
// Parse the content
parseContent(content);
return parts.length > 0 ? parts : [content];
};
// Parse object-based components like <tag>content</tag> or {{tag}}
const parseObjectComponents = (content: string, components: Record<string, ReactElement>): ReactNode[] => {
let processedContent = content;
const placeholders: Record<string, ReactNode> = {};
// First handle {{tag}} interpolation
Object.keys(components).forEach((tag) => {
const interpolationRegex = new RegExp(`{{\\s*${tag}\\s*}}`, "g");
processedContent = processedContent.replace(interpolationRegex, (match) => {
const placeholder = `__INTERP_${tag}_${Math.random().toString(36).substring(2)}__`;
if (isValidElement(components[tag])) {
placeholders[placeholder] = cloneElement(components[tag], {
...(components[tag].props || {}),
key: components[tag].key || `interp-${tag}`,
});
}
return placeholder;
});
});
// Then handle <tag>content</tag>
Object.keys(components).forEach((tag) => {
const tagRegex = new RegExp(`<${tag}>(.*?)<\\/${tag}>`, "gs");
processedContent = processedContent.replace(tagRegex, (match, content) => {
const placeholder = `__TAG_${tag}_${Math.random().toString(36).substring(2)}__`;
if (isValidElement(components[tag])) {
// Process any HTML in the content
const parsedContent = parseHtmlTags(content);
placeholders[placeholder] = cloneElement(
components[tag],
{
...(components[tag].props || {}),
key: components[tag].key || `tag-${tag}`,
},
...(parsedContent.length > 1 ? parsedContent : [content])
);
}
return placeholder;
});
});
// Replace placeholders with components and split text parts
const parts: ReactNode[] = [];
let lastIndex = 0;
// Find all placeholders in order
const placeholderRegex = /__(?:INTERP|TAG)_[^_]+_[a-z0-9]+__/g;
let match;
while ((match = placeholderRegex.exec(processedContent)) !== null) {
const [placeholder] = match;
const index = match.index;
// Add text before placeholder
if (index > lastIndex) {
const textBefore = processedContent.substring(lastIndex, index);
parts.push(...parseHtmlTags(textBefore));
}
// Add the component
if (placeholders[placeholder]) {
parts.push(placeholders[placeholder]);
}
lastIndex = index + placeholder.length;
}
// Add any remaining text
if (lastIndex < processedContent.length) {
const textAfter = processedContent.substring(lastIndex);
parts.push(...parseHtmlTags(textAfter));
}
return parts.length > 0 ? parts : [processedContent];
};
// Parse HTML tags like <strong>content</strong>
const parseHtmlTags = (content: string): ReactNode[] => {
if (!content || typeof content !== "string") {
return [content].filter(Boolean);
}
// Check if there are any HTML tags
if (!/<[a-z][\s\S]*>/i.test(content)) {
return [content];
}
// HTML tags to process
const htmlTags = [
{ tag: "strong", component: "strong" },
{ tag: "b", component: "b" },
{ tag: "i", component: "i" },
{ tag: "em", component: "em" },
{ tag: "p", component: "p" },
{ tag: "br", component: "br", selfClosing: true },
{ tag: "div", component: "div" },
{ tag: "span", component: "span" },
{ tag: "ul", component: "ul" },
{ tag: "ol", component: "ol" },
{ tag: "li", component: "li" },
];
let processedContent = content;
const placeholders: Record<string, ReactNode> = {};
// Process each HTML tag
htmlTags.forEach(({ tag, component, selfClosing }) => {
if (selfClosing) {
// Handle self-closing tags like <br/>
const selfClosingRegex = new RegExp(`<${tag}\\s*\\/>`, "g");
processedContent = processedContent.replace(selfClosingRegex, (match) => {
const placeholder = `__HTML_${tag}_${Math.random().toString(36).substring(2)}__`;
placeholders[placeholder] = createElement(component, { key: placeholder });
return placeholder;
});
} else {
// Handle regular tags like <strong>content</strong>
const tagRegex = new RegExp(`<${tag}>(.*?)<\\/${tag}>`, "gs");
processedContent = processedContent.replace(tagRegex, (match, content) => {
const placeholder = `__HTML_${tag}_${Math.random().toString(36).substring(2)}__`;
// Process nested tags recursively
const innerContent = parseHtmlTags(content);
placeholders[placeholder] = createElement(
component,
{ key: placeholder },
...(innerContent.length > 1 ? innerContent : [content])
);
return placeholder;
});
}
});
// If no tags were processed, return the original content
if (Object.keys(placeholders).length === 0) {
return [processedContent];
}
// Replace placeholders with components and split text parts
const parts: ReactNode[] = [];
let lastIndex = 0;
// Find all placeholders in order
const placeholderRegex = /__HTML_[^_]+_[a-z0-9]+__/g;
let match;
while ((match = placeholderRegex.exec(processedContent)) !== null) {
const [placeholder] = match;
const index = match.index;
// Add text before placeholder
if (index > lastIndex) {
parts.push(processedContent.substring(lastIndex, index));
}
// Add the component
if (placeholders[placeholder]) {
parts.push(placeholders[placeholder]);
}
lastIndex = index + placeholder.length;
}
// Add any remaining text
if (lastIndex < processedContent.length) {
parts.push(processedContent.substring(lastIndex));
}
return parts.length > 0 ? parts : [processedContent];
};
export default ServerTrans;

Some files were not shown because too many files have changed in this diff Show More