chore: [app-router-migration 16]: migrate routing forms page (#13015)
Co-authored-by: Benny Joo <sldisek783@gmail.com>
This commit is contained in:
co-authored by
Benny Joo
parent
dd7673b1f9
commit
bdd1d3a99d
@@ -0,0 +1,197 @@
|
||||
import LegacyPage, { getLayout } from "@pages/apps/[slug]/[...pages]";
|
||||
import type { PageProps, SearchParams } from "app/_types";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import z from "zod";
|
||||
|
||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||
import RoutingFormsRoutingConfig, {
|
||||
serverSidePropsConfig,
|
||||
} from "@calcom/app-store/routing-forms/pages/app-routing.config";
|
||||
import TypeformRoutingConfig from "@calcom/app-store/typeform/pages/app-routing.config";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
import type { AppProps } from "@lib/app-providers";
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
import PageWrapper from "@components/PageWrapperAppDir";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
type AppPageType = {
|
||||
getServerSideProps: AppGetServerSideProps;
|
||||
// A component than can accept any properties
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
default: ((props: any) => JSX.Element) &
|
||||
Pick<AppProps["Component"], "isBookingPage" | "getLayout" | "PageWrapper">;
|
||||
};
|
||||
|
||||
type Found = {
|
||||
notFound: false;
|
||||
Component: AppPageType["default"];
|
||||
getServerSideProps: AppPageType["getServerSideProps"];
|
||||
};
|
||||
|
||||
const AppsRouting = {
|
||||
"routing-forms": RoutingFormsRoutingConfig,
|
||||
typeform: TypeformRoutingConfig,
|
||||
};
|
||||
|
||||
const paramsSchema = z.object({
|
||||
slug: z.string(),
|
||||
pages: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}) => {
|
||||
const p = paramsSchema.safeParse(params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const mainPage = p.data.pages[0];
|
||||
|
||||
if (mainPage === "forms") {
|
||||
return await _generateMetadata(
|
||||
() => `Forms | ${APP_NAME}`,
|
||||
() => ""
|
||||
);
|
||||
}
|
||||
|
||||
const legacyContext = buildLegacyCtx(
|
||||
headers(),
|
||||
cookies(),
|
||||
params,
|
||||
searchParams
|
||||
) as unknown as GetServerSidePropsContext;
|
||||
const { form } = await getPageProps(legacyContext);
|
||||
|
||||
return await _generateMetadata(
|
||||
() => `${form.name} | ${APP_NAME}`,
|
||||
() => form.description
|
||||
);
|
||||
};
|
||||
|
||||
function getRoute(appName: string, pages: string[]) {
|
||||
const routingConfig = AppsRouting[appName as keyof typeof AppsRouting] as Record<string, AppPageType>;
|
||||
|
||||
if (!routingConfig) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const mainPage = pages[0];
|
||||
const appPage = routingConfig.layoutHandler || (routingConfig[mainPage] as AppPageType);
|
||||
|
||||
const getServerSidePropsHandler = serverSidePropsConfig[mainPage];
|
||||
|
||||
if (!appPage) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
notFound: false,
|
||||
Component: appPage.default,
|
||||
...appPage,
|
||||
getServerSideProps: getServerSidePropsHandler,
|
||||
} as Found;
|
||||
}
|
||||
|
||||
const getPageProps = async ({ params, query, req }: GetServerSidePropsContext) => {
|
||||
const p = paramsSchema.safeParse(params);
|
||||
|
||||
if (!p.success) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { slug: appName, pages } = p.data;
|
||||
|
||||
const route = getRoute(appName, pages);
|
||||
|
||||
if (route.notFound) {
|
||||
return route;
|
||||
}
|
||||
|
||||
if (route.getServerSideProps) {
|
||||
// TODO: Document somewhere that right now it is just a convention that filename should have appPages in it's name.
|
||||
// appPages is actually hardcoded here and no matter the fileName the same variable would be used.
|
||||
// We can write some validation logic later on that ensures that [...appPages].tsx file exists
|
||||
params!.appPages = pages.slice(1);
|
||||
|
||||
const ctx = { req, params, query };
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
const user = session?.user;
|
||||
const app = await getAppWithMetadata({ slug: appName });
|
||||
|
||||
if (!app) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const result = await route.getServerSideProps(
|
||||
{
|
||||
...ctx,
|
||||
params: {
|
||||
...ctx.params,
|
||||
appPages: pages.slice(1),
|
||||
},
|
||||
} as GetServerSidePropsContext<{
|
||||
slug: string;
|
||||
pages: string[];
|
||||
appPages: string[];
|
||||
}>,
|
||||
prisma,
|
||||
user,
|
||||
ssrInit
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
|
||||
if (result.notFound) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
if (result.redirect) {
|
||||
redirect(result.redirect.destination);
|
||||
}
|
||||
|
||||
return {
|
||||
appName,
|
||||
appUrl: app.simplePath || `/apps/${appName}`,
|
||||
...result.props,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
appName,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
const h = headers();
|
||||
const nonce = h.get("x-nonce") ?? undefined;
|
||||
|
||||
const legacyContext = buildLegacyCtx(
|
||||
h,
|
||||
cookies(),
|
||||
params,
|
||||
searchParams
|
||||
) as unknown as GetServerSidePropsContext;
|
||||
const props = await getPageProps(legacyContext);
|
||||
return (
|
||||
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
||||
<LegacyPage {...props} />
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
const getPageProps = () => {
|
||||
return redirect(`/apps/routing-forms/forms`);
|
||||
};
|
||||
const Page = () => {
|
||||
getPageProps();
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { type SearchParams } from "app/_types";
|
||||
import { type GetServerSidePropsContext } from "next";
|
||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import z from "zod";
|
||||
|
||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
pages: z.array(z.string()),
|
||||
})
|
||||
.catch({
|
||||
pages: [],
|
||||
});
|
||||
|
||||
const getPageProps = async (context: GetServerSidePropsContext) => {
|
||||
const { pages } = paramsSchema.parse(context.params);
|
||||
|
||||
return redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`);
|
||||
};
|
||||
|
||||
type PageProps = Readonly<{
|
||||
params: Params;
|
||||
searchParams: SearchParams;
|
||||
}>;
|
||||
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||
await getPageProps(legacyCtx as unknown as GetServerSidePropsContext);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -89,8 +89,7 @@ function PageWrapper(props: AppProps) {
|
||||
</LicenseRequired>
|
||||
) : (
|
||||
<Component {...pageProps} err={err} />
|
||||
),
|
||||
router
|
||||
)
|
||||
)}
|
||||
</AppProviders>
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ export type AppProps = Omit<
|
||||
requiresLicense?: boolean;
|
||||
isThemeSupported?: boolean;
|
||||
isBookingPage?: boolean | ((arg: { router: NextAppProps["router"] }) => boolean);
|
||||
getLayout?: (page: React.ReactElement, router: NextAppProps["router"]) => ReactNode;
|
||||
getLayout?: (page: React.ReactElement) => ReactNode;
|
||||
PageWrapper?: (props: AppProps) => JSX.Element;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// returns query object same as ctx.query but for app dir
|
||||
export const getQuery = (url: string, params: Record<string, string | string[]>) => {
|
||||
if (!url.length) {
|
||||
return params;
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(url);
|
||||
const searchParamsObj = Object.fromEntries(searchParams.entries());
|
||||
|
||||
return { ...searchParamsObj, ...params };
|
||||
};
|
||||
@@ -116,6 +116,7 @@ export const config = {
|
||||
* Paths required by routingForms.handle
|
||||
*/
|
||||
"/apps/routing_forms/:path*",
|
||||
|
||||
"/event-types",
|
||||
"/future/event-types/",
|
||||
"/settings/admin/:path*",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||
@@ -48,7 +50,6 @@ function getRoute(appName: string, pages: string[]) {
|
||||
}
|
||||
const mainPage = pages[0];
|
||||
const appPage = routingConfig.layoutHandler || (routingConfig[mainPage] as AppPageType);
|
||||
|
||||
if (!appPage) {
|
||||
return {
|
||||
notFound: true,
|
||||
@@ -60,7 +61,7 @@ function getRoute(appName: string, pages: string[]) {
|
||||
const AppPage: AppPageType["default"] = function AppPage(props) {
|
||||
const appName = props.appName;
|
||||
const params = useParamsWithFallback();
|
||||
const pages = (params.pages || []) as string[];
|
||||
const pages = Array.isArray(params.pages) ? params.pages : params.pages?.split("/") ?? [];
|
||||
const route = getRoute(appName, pages);
|
||||
|
||||
const componentProps = {
|
||||
@@ -87,18 +88,22 @@ AppPage.isBookingPage = ({ router }) => {
|
||||
return !!isBookingPage;
|
||||
};
|
||||
|
||||
AppPage.getLayout = (page, router) => {
|
||||
const route = getRoute(router.query.slug as string, router.query.pages as string[]);
|
||||
export const getLayout: NonNullable<(typeof AppPage)["getLayout"]> = (page) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { slug, pages } = useParamsWithFallback();
|
||||
const route = getRoute(slug as string, pages as string[]);
|
||||
|
||||
if (route.notFound) {
|
||||
return null;
|
||||
}
|
||||
if (!route.Component.getLayout) {
|
||||
return page;
|
||||
}
|
||||
return route.Component.getLayout(page, router);
|
||||
return route.Component.getLayout(page);
|
||||
};
|
||||
|
||||
AppPage.PageWrapper = PageWrapper;
|
||||
AppPage.getLayout = getLayout;
|
||||
|
||||
export default AppPage;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { AppProps as NextAppProps } from "next/app";
|
||||
import { redirect } from "next/navigation";
|
||||
import { redirect, useRouter } from "next/navigation";
|
||||
|
||||
import { AddNewTeamsForm } from "@calcom/features/ee/organizations/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -22,8 +21,11 @@ const AddNewTeamsPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
AddNewTeamsPage.getLayout = (page: React.ReactElement, router: NextAppProps["router"]) => (
|
||||
<>
|
||||
AddNewTeamsPage.getLayout = (page: React.ReactElement) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<WizardLayout
|
||||
currentStep={5}
|
||||
maxSteps={5}
|
||||
@@ -32,8 +34,8 @@ AddNewTeamsPage.getLayout = (page: React.ReactElement, router: NextAppProps["rou
|
||||
}}>
|
||||
{page}
|
||||
</WizardLayout>
|
||||
</>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
AddNewTeamsPage.PageWrapper = PageWrapper;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import type { AppProps as NextAppProps } from "next/app";
|
||||
import { redirect } from "next/navigation";
|
||||
import { redirect, useRouter } from "next/navigation";
|
||||
|
||||
import { AddNewOrgAdminsForm } from "@calcom/features/ee/organizations/components";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Meta, WizardLayout, WizardLayoutAppDir } from "@calcom/ui";
|
||||
|
||||
@@ -25,16 +25,23 @@ const OnboardTeamMembersPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
OnboardTeamMembersPage.getLayout = (page: React.ReactElement, router: NextAppProps["router"]) => (
|
||||
<WizardLayout
|
||||
currentStep={4}
|
||||
maxSteps={5}
|
||||
isOptionalCallback={() => {
|
||||
router.push(`/settings/organizations/${router.query.id}/add-teams`);
|
||||
}}>
|
||||
{page}
|
||||
</WizardLayout>
|
||||
);
|
||||
OnboardTeamMembersPage.getLayout = (page: React.ReactElement) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const router = useRouter();
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const query = useCompatSearchParams();
|
||||
|
||||
return (
|
||||
<WizardLayout
|
||||
currentStep={4}
|
||||
maxSteps={5}
|
||||
isOptionalCallback={() => {
|
||||
router.push(`/settings/organizations/${query.get("id")}/add-teams`);
|
||||
}}>
|
||||
{page}
|
||||
</WizardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const buildWrappedOnboardTeamMembersPage = (
|
||||
id: string | string[] | undefined,
|
||||
|
||||
@@ -9,12 +9,6 @@ import { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import useApp from "@calcom/lib/hooks/useApp";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type {
|
||||
AppGetServerSidePropsContext,
|
||||
AppPrisma,
|
||||
AppSsrInit,
|
||||
AppUser,
|
||||
} from "@calcom/types/AppGetServerSideProps";
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
@@ -45,13 +39,13 @@ import {
|
||||
} from "@calcom/ui/components/icon";
|
||||
|
||||
import { RoutingPages } from "../lib/RoutingPages";
|
||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
||||
import { isFallbackRoute } from "../lib/isFallbackRoute";
|
||||
import { processRoute } from "../lib/processRoute";
|
||||
import type { Response, Route, SerializableForm } from "../types/types";
|
||||
import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions";
|
||||
import FormInputFields from "./FormInputFields";
|
||||
import RoutingNavBar from "./RoutingNavBar";
|
||||
import { getServerSidePropsForSingleFormView } from "./getServerSidePropsSingleForm";
|
||||
|
||||
type RoutingForm = SerializableForm<App_RoutingForms_Form>;
|
||||
|
||||
@@ -548,70 +542,4 @@ export default function SingleFormWrapper({ form: _form, ...props }: SingleFormC
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSidePropsForSingleFormView = async function getServerSidePropsForSingleFormView(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma,
|
||||
user: AppUser,
|
||||
ssrInit: AppSsrInit
|
||||
) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "/auth/login",
|
||||
},
|
||||
};
|
||||
}
|
||||
const { params } = context;
|
||||
if (!params) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const formId = params.appPages[0];
|
||||
if (!formId || params.appPages.length > 1) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const isFormCreateEditAllowed = (await import("../lib/isFormCreateEditAllowed")).isFormCreateEditAllowed;
|
||||
if (!(await isFormCreateEditAllowed({ userId: user.id, formId, targetTeamId: null }))) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findUnique({
|
||||
where: {
|
||||
id: formId,
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
responses: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!form) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
trpcState: ssr.dehydrate(),
|
||||
form: await getSerializableForm({ form }),
|
||||
},
|
||||
};
|
||||
};
|
||||
export { getServerSidePropsForSingleFormView };
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import type {
|
||||
AppGetServerSidePropsContext,
|
||||
AppPrisma,
|
||||
AppSsrInit,
|
||||
AppUser,
|
||||
} from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
||||
|
||||
export const getServerSidePropsForSingleFormView = async function getServerSidePropsForSingleFormView(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma,
|
||||
user: AppUser,
|
||||
ssrInit: AppSsrInit
|
||||
) {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "/auth/login",
|
||||
},
|
||||
};
|
||||
}
|
||||
const { params } = context;
|
||||
if (!params) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const formId = params.appPages[0];
|
||||
if (!formId || params.appPages.length > 1) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const isFormCreateEditAllowed = (await import("../lib/isFormCreateEditAllowed")).isFormCreateEditAllowed;
|
||||
if (!(await isFormCreateEditAllowed({ userId: user.id, formId, targetTeamId: null }))) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findUnique({
|
||||
where: {
|
||||
id: formId,
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
name: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
responses: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!form) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
trpcState: await ssr.dehydrate(),
|
||||
form: await getSerializableForm({ form }),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { App_RoutingForms_Form } from "@prisma/client";
|
||||
import { Utils as QbUtils } from "react-awesome-query-builder";
|
||||
import type { z } from "zod";
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//TODO: Generate this file automatically so that like in Next.js file based routing can work automatically
|
||||
import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
import { getServerSidePropsForSingleFormView as getServerSidePropsSingleForm } from "../components/getServerSidePropsSingleForm";
|
||||
import * as formEdit from "./form-edit/[...appPages]";
|
||||
import * as forms from "./forms/[...appPages]";
|
||||
// extracts getServerSideProps function from the client component
|
||||
import { getServerSideProps as getServerSidePropsForms } from "./forms/getServerSideProps";
|
||||
import * as LayoutHandler from "./layout-handler/[...appPages]";
|
||||
import * as Reporting from "./reporting/[...appPages]";
|
||||
import * as RouteBuilder from "./route-builder/[...appPages]";
|
||||
import * as Router from "./router/[...appPages]";
|
||||
import { getServerSideProps as getServerSidePropsRouter } from "./router/getServerSideProps";
|
||||
import * as RoutingLink from "./routing-link/[...appPages]";
|
||||
import { getServerSideProps as getServerSidePropsRoutingLink } from "./routing-link/getServerSideProps";
|
||||
|
||||
const routingConfig = {
|
||||
"form-edit": formEdit,
|
||||
@@ -17,4 +24,13 @@ const routingConfig = {
|
||||
layoutHandler: LayoutHandler,
|
||||
};
|
||||
|
||||
export const serverSidePropsConfig: Record<string, AppGetServerSideProps> = {
|
||||
forms: getServerSidePropsForms,
|
||||
"form-edit": getServerSidePropsSingleForm,
|
||||
"route-builder": getServerSidePropsSingleForm,
|
||||
"routing-link": getServerSidePropsRoutingLink,
|
||||
router: getServerSidePropsRouter,
|
||||
reporting: getServerSidePropsSingleForm,
|
||||
};
|
||||
|
||||
export default routingConfig;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
// TODO: i18n
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useEffect } from "react";
|
||||
@@ -16,12 +18,6 @@ import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type {
|
||||
AppGetServerSidePropsContext,
|
||||
AppPrisma,
|
||||
AppSsrInit,
|
||||
AppUser,
|
||||
} from "@calcom/types/AppGetServerSideProps";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -61,6 +57,7 @@ import {
|
||||
} from "../../components/FormActions";
|
||||
import type { RoutingFormWithResponseCount } from "../../components/SingleForm";
|
||||
import { isFallbackRoute } from "../../lib/isFallbackRoute";
|
||||
import { getServerSideProps } from "./getServerSideProps";
|
||||
|
||||
function NewFormButton() {
|
||||
const { t } = useLocale();
|
||||
@@ -372,32 +369,4 @@ RoutingForms.getLayout = (page: React.ReactElement) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma,
|
||||
user: AppUser,
|
||||
ssrInit: AppSsrInit
|
||||
) {
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "/auth/login",
|
||||
},
|
||||
};
|
||||
}
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const filters = getTeamsFiltersFromQuery(context.query);
|
||||
|
||||
await ssr.viewer.appRoutingForms.forms.prefetch({
|
||||
filters,
|
||||
});
|
||||
// Prefetch this so that New Button is immediately available
|
||||
await ssr.viewer.teamsAndUserProfilesQuery.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
};
|
||||
export { getServerSideProps };
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
|
||||
import type {
|
||||
AppGetServerSidePropsContext,
|
||||
AppPrisma,
|
||||
AppSsrInit,
|
||||
AppUser,
|
||||
} from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma,
|
||||
user: AppUser,
|
||||
ssrInit: AppSsrInit
|
||||
) {
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "/auth/login",
|
||||
},
|
||||
};
|
||||
}
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const filters = getTeamsFiltersFromQuery(context.query);
|
||||
|
||||
await ssr.viewer.appRoutingForms.forms.prefetch({
|
||||
filters,
|
||||
});
|
||||
// Prefetch this so that New Button is immediately available
|
||||
await ssr.viewer.teamsAndUserProfilesQuery.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: await ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { AppProps as NextAppProps } from "next/app";
|
||||
import React from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
@@ -10,22 +11,23 @@ import type { AppProps } from "@lib/app-providers";
|
||||
|
||||
import RoutingFormsRoutingConfig from "../app-routing.config";
|
||||
|
||||
const DEFAULT_ROUTE = "forms";
|
||||
|
||||
type GetServerSidePropsRestArgs = [AppPrisma, AppUser, AppSsrInit];
|
||||
type Component = {
|
||||
default: React.ComponentType & Pick<AppProps["Component"], "getLayout">;
|
||||
getServerSideProps?: (context: GetServerSidePropsContext, ...rest: GetServerSidePropsRestArgs) => void;
|
||||
};
|
||||
const getComponent = (route: string | NextAppProps["router"]): Component => {
|
||||
const defaultRoute = "forms";
|
||||
const routeKey =
|
||||
typeof route === "string" ? route || defaultRoute : route?.query?.pages?.[0] || defaultRoute;
|
||||
return (RoutingFormsRoutingConfig as unknown as Record<string, Component>)[routeKey];
|
||||
const getComponent = (route: string): Component => {
|
||||
return (RoutingFormsRoutingConfig as unknown as Record<string, Component>)[route];
|
||||
};
|
||||
|
||||
export default function LayoutHandler(props: { [key: string]: unknown }) {
|
||||
const params = useParamsWithFallback();
|
||||
const methods = useForm();
|
||||
const pageKey = params?.pages?.[0] || "forms";
|
||||
const pageKey = Array.isArray(params.pages)
|
||||
? params.pages[0]
|
||||
: params.pages?.split("/")[0] ?? DEFAULT_ROUTE;
|
||||
const PageComponent = getComponent(pageKey).default;
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
@@ -34,10 +36,16 @@ export default function LayoutHandler(props: { [key: string]: unknown }) {
|
||||
);
|
||||
}
|
||||
|
||||
LayoutHandler.getLayout = (page: React.ReactElement, router: NextAppProps["router"]) => {
|
||||
const component = getComponent(router).default;
|
||||
LayoutHandler.getLayout = (page: React.ReactElement) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const params = useParamsWithFallback();
|
||||
const pageKey = Array.isArray(params.pages)
|
||||
? params.pages[0]
|
||||
: params.pages?.split("/")[0] ?? DEFAULT_ROUTE;
|
||||
|
||||
const component = getComponent(pageKey).default;
|
||||
if (component && "getLayout" in component) {
|
||||
return component.getLayout?.(page, router);
|
||||
return component.getLayout?.(page);
|
||||
} else {
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import type {
|
||||
BuilderProps,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import Link from "next/link";
|
||||
import React, { useCallback, useState } from "react";
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import Head from "next/head";
|
||||
import { stringify } from "querystring";
|
||||
import z from "zod";
|
||||
"use client";
|
||||
|
||||
import Head from "next/head";
|
||||
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { TRPCError } from "@calcom/trpc";
|
||||
import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
|
||||
import getFieldIdentifier from "../../lib/getFieldIdentifier";
|
||||
import { getSerializableForm } from "../../lib/getSerializableForm";
|
||||
import { processRoute } from "../../lib/processRoute";
|
||||
import transformResponse from "../../lib/transformResponse";
|
||||
import type { Response } from "../../types/types";
|
||||
import { getServerSideProps } from "./getServerSideProps";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[routing-forms]", "[router]"] });
|
||||
export default function Router({ form, message }: inferSSRProps<typeof getServerSideProps>) {
|
||||
return (
|
||||
<>
|
||||
@@ -33,111 +23,4 @@ export default function Router({ form, message }: inferSSRProps<typeof getServer
|
||||
);
|
||||
}
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
form: z.string(),
|
||||
slug: z.string(),
|
||||
pages: z.array(z.string()),
|
||||
})
|
||||
.catchall(z.string().or(z.array(z.string())));
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma
|
||||
) {
|
||||
const queryParsed = querySchema.safeParse(context.query);
|
||||
if (!queryParsed.success) {
|
||||
log.warn("Error parsing query", queryParsed.error);
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { form: formId, slug: _slug, pages: _pages, ...fieldsResponses } = queryParsed.data;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
id: formId,
|
||||
user: {
|
||||
organization: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!form) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const serializableForm = await getSerializableForm({ form });
|
||||
|
||||
const response: Response = {};
|
||||
serializableForm.fields?.forEach((field) => {
|
||||
const fieldResponse = fieldsResponses[getFieldIdentifier(field)] || "";
|
||||
|
||||
response[field.id] = {
|
||||
label: field.label,
|
||||
value: transformResponse({ field, value: fieldResponse }),
|
||||
};
|
||||
});
|
||||
|
||||
const decidedAction = processRoute({ form: serializableForm, response });
|
||||
|
||||
if (!decidedAction) {
|
||||
throw new Error("No matching route could be found");
|
||||
}
|
||||
|
||||
const { createContext } = await import("@calcom/trpc/server/createContext");
|
||||
const ctx = await createContext(context);
|
||||
|
||||
const { default: trpcRouter } = await import("@calcom/app-store/routing-forms/trpc/_router");
|
||||
const caller = trpcRouter.createCaller(ctx);
|
||||
const { v4: uuidv4 } = await import("uuid");
|
||||
try {
|
||||
await caller.public.response({
|
||||
formId: form.id,
|
||||
formFillerId: uuidv4(),
|
||||
response: response,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof TRPCError) {
|
||||
return {
|
||||
props: {
|
||||
form: serializableForm,
|
||||
message: e.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Maybe take action after successful mutation
|
||||
if (decidedAction.type === "customPageMessage") {
|
||||
return {
|
||||
props: {
|
||||
form: serializableForm,
|
||||
message: decidedAction.value,
|
||||
},
|
||||
};
|
||||
} else if (decidedAction.type === "eventTypeRedirectUrl") {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `/${decidedAction.value}?${stringify(context.query)}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
} else if (decidedAction.type === "externalRedirectUrl") {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `${decidedAction.value}?${stringify(context.query)}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
form: serializableForm,
|
||||
},
|
||||
};
|
||||
};
|
||||
export { getServerSideProps };
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { stringify } from "querystring";
|
||||
import z from "zod";
|
||||
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
import getFieldIdentifier from "../../lib/getFieldIdentifier";
|
||||
import { getSerializableForm } from "../../lib/getSerializableForm";
|
||||
import { processRoute } from "../../lib/processRoute";
|
||||
import transformResponse from "../../lib/transformResponse";
|
||||
import type { Response } from "../../types/types";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["[routing-forms]", "[router]"] });
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
form: z.string(),
|
||||
slug: z.string(),
|
||||
pages: z.array(z.string()),
|
||||
})
|
||||
.catchall(z.string().or(z.array(z.string())));
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma
|
||||
) {
|
||||
const queryParsed = querySchema.safeParse(context.query);
|
||||
if (!queryParsed.success) {
|
||||
log.warn("Error parsing query", queryParsed.error);
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { form: formId, slug: _slug, pages: _pages, ...fieldsResponses } = queryParsed.data;
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
id: formId,
|
||||
user: {
|
||||
organization: isValidOrgDomain && currentOrgDomain ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!form) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const serializableForm = await getSerializableForm({ form });
|
||||
|
||||
const response: Response = {};
|
||||
serializableForm.fields?.forEach((field) => {
|
||||
const fieldResponse = fieldsResponses[getFieldIdentifier(field)] || "";
|
||||
|
||||
response[field.id] = {
|
||||
label: field.label,
|
||||
value: transformResponse({ field, value: fieldResponse }),
|
||||
};
|
||||
});
|
||||
|
||||
const decidedAction = processRoute({ form: serializableForm, response });
|
||||
|
||||
if (!decidedAction) {
|
||||
throw new Error("No matching route could be found");
|
||||
}
|
||||
|
||||
const { createContext } = await import("@calcom/trpc/server/createContext");
|
||||
const ctx = await createContext(context);
|
||||
|
||||
const { default: trpcRouter } = await import("@calcom/app-store/routing-forms/trpc/_router");
|
||||
const caller = trpcRouter.createCaller(ctx);
|
||||
const { v4: uuidv4 } = await import("uuid");
|
||||
try {
|
||||
await caller.public.response({
|
||||
formId: form.id,
|
||||
formFillerId: uuidv4(),
|
||||
response: response,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof TRPCError) {
|
||||
return {
|
||||
props: {
|
||||
form: serializableForm,
|
||||
message: e.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Maybe take action after successful mutation
|
||||
if (decidedAction.type === "customPageMessage") {
|
||||
return {
|
||||
props: {
|
||||
form: serializableForm,
|
||||
message: decidedAction.value,
|
||||
},
|
||||
};
|
||||
} else if (decidedAction.type === "eventTypeRedirectUrl") {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `/${decidedAction.value}?${stringify(context.query)}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
} else if (decidedAction.type === "externalRedirectUrl") {
|
||||
return {
|
||||
redirect: {
|
||||
destination: `${decidedAction.value}?${stringify(context.query)}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
form: serializableForm,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { FormEvent } from "react";
|
||||
@@ -6,23 +8,21 @@ import { Toaster } from "react-hot-toast";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import { Button, showToast, useCalcomTheme } from "@calcom/ui";
|
||||
|
||||
import FormInputFields from "../../components/FormInputFields";
|
||||
import getFieldIdentifier from "../../lib/getFieldIdentifier";
|
||||
import { getSerializableForm } from "../../lib/getSerializableForm";
|
||||
import { processRoute } from "../../lib/processRoute";
|
||||
import transformResponse from "../../lib/transformResponse";
|
||||
import type { Response, Route } from "../../types/types";
|
||||
import { getServerSideProps } from "./getServerSideProps";
|
||||
|
||||
type Props = inferSSRProps<typeof getServerSideProps>;
|
||||
const useBrandColors = ({
|
||||
@@ -233,68 +233,7 @@ export default function RoutingLink(props: inferSSRProps<typeof getServerSidePro
|
||||
|
||||
RoutingLink.isBookingPage = true;
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma
|
||||
) {
|
||||
const { params } = context;
|
||||
if (!params) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const formId = params.appPages[0];
|
||||
if (!formId || params.appPages.length > 2) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
||||
|
||||
const isEmbed = params.appPages[1] === "embed";
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
id: formId,
|
||||
user: {
|
||||
organization: isValidOrgDomain
|
||||
? {
|
||||
slug: currentOrgDomain,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
username: true,
|
||||
theme: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!form || form.disabled) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
isEmbed,
|
||||
themeBasis: form.user.username,
|
||||
profile: {
|
||||
theme: form.user.theme,
|
||||
brandColor: form.user.brandColor,
|
||||
darkBrandColor: form.user.darkBrandColor,
|
||||
},
|
||||
form: await getSerializableForm({ form }),
|
||||
},
|
||||
};
|
||||
};
|
||||
export { getServerSideProps };
|
||||
|
||||
const usePrefilledResponse = (form: Props["form"]) => {
|
||||
const searchParams = useCompatSearchParams();
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps";
|
||||
|
||||
import { getSerializableForm } from "../../lib/getSerializableForm";
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
context: AppGetServerSidePropsContext,
|
||||
prisma: AppPrisma
|
||||
) {
|
||||
const { params } = context;
|
||||
if (!params) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const formId = params.appPages[0];
|
||||
if (!formId || params.appPages.length > 2) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
||||
|
||||
const isEmbed = params.appPages[1] === "embed";
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
id: formId,
|
||||
user: {
|
||||
organization: isValidOrgDomain
|
||||
? {
|
||||
slug: currentOrgDomain,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
username: true,
|
||||
theme: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!form || form.disabled) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
isEmbed,
|
||||
themeBasis: form.user.username,
|
||||
profile: {
|
||||
theme: form.user.theme,
|
||||
brandColor: form.user.brandColor,
|
||||
darkBrandColor: form.user.darkBrandColor,
|
||||
},
|
||||
form: await getSerializableForm({ form }),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
//TODO: Generate this file automatically so that like in Next.js file based routing can work automatically
|
||||
import * as HowToUse from "./how-to-use/[...appPages]";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Trans, type TFunction } from "next-i18next";
|
||||
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { Trans } from "next-i18next";
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import type { ParsedUrlQuery } from "querystring";
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user