From 1de007ad218b7e96ab93e300439279278d87aa6a Mon Sep 17 00:00:00 2001 From: Vishnu <130341088+VK-RED@users.noreply.github.com> Date: Thu, 12 Dec 2024 02:48:46 +0530 Subject: [PATCH] feat: migrate /apps/routing-forms/forms to /routing (#18001) * feat: add getServerSideProps and getStaticProps * feat: add routing pages view * feat: add routing and related pages * feat: setup redirect * feat: remove routing configs in getStaticProps and getServerSideProps * feat: add redirects in next.config and middleware * feat: add split icon * feat: rename href to /routing/forms * feat: fix redirects * feat: finish routing page * feat: remove /apps/routing-forms/forms redirect in next-config * feat: revert back redirects in serverSideProps * fix: minor issues * fix: routing-form url cloning * chore: remove comments * fix: routing page link * fix: routing hover link --------- Co-authored-by: Prashant Varma --- apps/web/lib/routing/getServerSideProps.ts | 97 +++++++++++++++++++ apps/web/middleware.ts | 6 +- apps/web/modules/routing/pages-view.tsx | 88 +++++++++++++++++ apps/web/pages/routing/index.tsx | 15 +++ apps/web/public/icons/sprite.svg | 6 ++ .../pages/layout-handler/[...appPages].tsx | 6 +- .../playwright/tests/basic.e2e.ts | 2 +- .../features/shell/navigation/Navigation.tsx | 8 +- packages/ui/components/icon/icon-list.mjs | 1 + packages/ui/components/icon/icon-names.ts | 1 + 10 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 apps/web/lib/routing/getServerSideProps.ts create mode 100644 apps/web/modules/routing/pages-view.tsx create mode 100644 apps/web/pages/routing/index.tsx diff --git a/apps/web/lib/routing/getServerSideProps.ts b/apps/web/lib/routing/getServerSideProps.ts new file mode 100644 index 0000000000..1b8021fac7 --- /dev/null +++ b/apps/web/lib/routing/getServerSideProps.ts @@ -0,0 +1,97 @@ +import type { GetServerSidePropsContext } from "next"; + +import { getAppWithMetadata } from "@calcom/app-store/_appRegistry"; +import RoutingFormsRoutingConfig from "@calcom/app-store/routing-forms/pages/app-routing.config"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import prisma from "@calcom/prisma"; +import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"; + +import type { AppProps } from "@lib/app-providers"; + +import { ssrInit } from "@server/lib/ssr"; + +type AppPageType = { + getServerSideProps?: AppGetServerSideProps; + // A component than can accept any properties + default: ((props: unknown) => JSX.Element) & + Pick; +}; + +type Found = { + notFound: false; + Component: AppPageType["default"]; + getServerSideProps: AppPageType["getServerSideProps"]; +}; + +type NotFound = { + notFound: true; +}; + +function getRoute(pages: string[]) { + const mainPage = pages[0] as keyof typeof RoutingFormsRoutingConfig; + const appPage = + RoutingFormsRoutingConfig.layoutHandler || (RoutingFormsRoutingConfig[mainPage] as AppPageType); + + if (!appPage) { + return { + notFound: true, + } as NotFound; + } + return { notFound: false, Component: appPage.default, ...appPage } as Found; +} + +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const { req } = context; + const appName = "routing-forms"; + const pages = ["forms"]; // set forms page to be the default one + const route = getRoute(pages); + + if (route.notFound) { + return { notFound: true }; + } + + if (route.getServerSideProps) { + const session = await getServerSession({ req }); + const user = session?.user; + const app = await getAppWithMetadata({ slug: appName }); + + if (!app) { + return { + notFound: true, + }; + } + + const result = await route.getServerSideProps( + context as GetServerSidePropsContext<{ + slug: string; + pages: string[]; + appPages: string[]; + }>, + prisma, + user, + ssrInit + ); + + if (result.notFound) { + return { notFound: true }; + } + + if (result.redirect) { + return { redirect: result.redirect }; + } + + return { + props: { + appName, + appUrl: "/apps/routing-forms", + ...result.props, + }, + }; + } else { + return { + props: { + appName, + }, + }; + } +}; diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 95556aed89..9d135c3418 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -41,6 +41,10 @@ const middleware = async (req: NextRequest): Promise> => { } } + if (url.pathname.startsWith("/apps/routing-forms/forms")) { + return NextResponse.redirect(new URL("/routing", url.origin), { headers: requestHeaders }); + } + const routingFormRewriteResponse = routingForms.handleRewrite(url); if (routingFormRewriteResponse) { return responseWithHeaders({ url, res: routingFormRewriteResponse, req }); @@ -163,7 +167,7 @@ export const config = { * Paths required by routingForms.handle */ "/apps/routing_forms/:path*", - + "/apps/routing-forms/forms", "/event-types", "/future/event-types/", "/apps/installed/:category/", diff --git a/apps/web/modules/routing/pages-view.tsx b/apps/web/modules/routing/pages-view.tsx new file mode 100644 index 0000000000..6aaac609ea --- /dev/null +++ b/apps/web/modules/routing/pages-view.tsx @@ -0,0 +1,88 @@ +"use client"; + +import RoutingFormsRoutingConfig from "@calcom/app-store/routing-forms/pages/app-routing.config"; +import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; +import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"; +import type { inferSSRProps } from "@calcom/types/inferSSRProps"; + +import type { AppProps } from "@lib/app-providers"; +import type { getServerSideProps } from "@lib/apps/[slug]/[...pages]/getServerSideProps"; + +type AppPageType = { + getServerSideProps: AppGetServerSideProps; + // A component than can accept any properties + default: ((props: unknown) => JSX.Element) & + Pick; +}; + +type Found = { + notFound: false; + Component: AppPageType["default"]; + getServerSideProps: AppPageType["getServerSideProps"]; +}; + +type NotFound = { + notFound: true; +}; + +function getRoute(pages: string[]) { + const mainPage = pages[0] as keyof typeof RoutingFormsRoutingConfig; + const appPage = + RoutingFormsRoutingConfig.layoutHandler || (RoutingFormsRoutingConfig[mainPage] as AppPageType); + if (!appPage) { + return { + notFound: true, + } as NotFound; + } + return { notFound: false, Component: appPage.default, ...appPage } as Found; +} + +export type PageProps = inferSSRProps; + +const AppPage: AppPageType["default"] = function AppPage(props: PageProps) { + const params = useParamsWithFallback(); + const defaultPage = ["forms"]; + const pages = Array.isArray(params.pages) ? params.pages : params.pages?.split("/") ?? defaultPage; + + const route = getRoute(pages); + + const componentProps = { + ...props, + pages: pages.slice(1), + }; + + if (!route || route.notFound) { + throw new Error("Route can't be undefined"); + } + return ; +}; + +AppPage.isBookingPage = ({ router }) => { + const route = getRoute(router.query.pages as string[]); + if (route.notFound) { + return false; + } + const isBookingPage = route.Component.isBookingPage; + if (typeof isBookingPage === "function") { + return isBookingPage({ router }); + } + + return !!isBookingPage; +}; + +export const GetFormLayout: NonNullable<(typeof AppPage)["getLayout"]> = (page) => { + const { pages: paramsPages } = useParamsWithFallback(); + const defaultPage = ["forms"]; + const pages = paramsPages?.length ? (paramsPages as string[]) : defaultPage; + const route = getRoute(pages); + + if (route.notFound) { + return null; + } + if (!route.Component.getLayout) { + return page; + } + return route.Component.getLayout(page); +}; + +export default AppPage; diff --git a/apps/web/pages/routing/index.tsx b/apps/web/pages/routing/index.tsx new file mode 100644 index 0000000000..22b65b6e5c --- /dev/null +++ b/apps/web/pages/routing/index.tsx @@ -0,0 +1,15 @@ +import { getServerSideProps } from "@lib/routing/getServerSideProps"; + +import PageWrapper from "@components/PageWrapper"; + +import type { PageProps } from "~/routing/pages-view"; +import PagesView, { GetFormLayout as getLayout } from "~/routing/pages-view"; + +const Page = (props: PageProps) => ; + +Page.PageWrapper = PageWrapper; +Page.getLayout = getLayout; + +export { getServerSideProps }; + +export default Page; diff --git a/apps/web/public/icons/sprite.svg b/apps/web/public/icons/sprite.svg index 949437c333..a9d82df2dd 100644 --- a/apps/web/public/icons/sprite.svg +++ b/apps/web/public/icons/sprite.svg @@ -558,6 +558,12 @@ + + + + + + diff --git a/packages/app-store/routing-forms/pages/layout-handler/[...appPages].tsx b/packages/app-store/routing-forms/pages/layout-handler/[...appPages].tsx index 5d1058cb28..359f94352f 100644 --- a/packages/app-store/routing-forms/pages/layout-handler/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/layout-handler/[...appPages].tsx @@ -55,6 +55,10 @@ export async function getServerSideProps( context: GetServerSidePropsContext, ...rest: GetServerSidePropsRestArgs ) { - const component = getComponent(context.params?.pages?.[0] || ""); + //add forms to be the default page, if no pages are found + //this is useful when rendering /routing as no params would be available + const defaultPage = "forms"; + const page = context.params?.pages?.[0] || defaultPage; + const component = getComponent(page); return component.getServerSideProps?.(context, ...rest) || { props: {} }; } diff --git a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts index ce421eee54..771c7c98b6 100644 --- a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts @@ -84,7 +84,7 @@ test.describe("Routing Forms", () => { test("should be able to add a new form and view it", async ({ page }) => { const formId = await addForm(page); - await page.click('[href*="/forms"]'); + await page.click('[href="/routing"]'); await page.waitForSelector('[data-testid="routing-forms-list"]'); // Ensure that it's visible in forms list diff --git a/packages/features/shell/navigation/Navigation.tsx b/packages/features/shell/navigation/Navigation.tsx index 000c9a15f1..cf5a21b964 100644 --- a/packages/features/shell/navigation/Navigation.tsx +++ b/packages/features/shell/navigation/Navigation.tsx @@ -87,10 +87,10 @@ const getNavigationItems = (orgBranding: OrganizationBranding): NavigationItemTy icon: "ellipsis", }, { - name: "routing_forms", - href: "/apps/routing-forms/forms", - icon: "file-text", - isCurrent: ({ pathname }) => pathname?.startsWith("/apps/routing-forms/") ?? false, + name: "routing", + href: "/routing", + icon: "split", + isCurrent: ({ pathname }) => pathname?.startsWith("/routing") ?? false, moreOnMobile: true, }, { diff --git a/packages/ui/components/icon/icon-list.mjs b/packages/ui/components/icon/icon-list.mjs index c8513f1ed7..eef2e727cb 100644 --- a/packages/ui/components/icon/icon-list.mjs +++ b/packages/ui/components/icon/icon-list.mjs @@ -114,6 +114,7 @@ export const lucideIconList = new Set([ "sliders-vertical", "smartphone", "sparkles", + "split", "square-check", "star", "sun", diff --git a/packages/ui/components/icon/icon-names.ts b/packages/ui/components/icon/icon-names.ts index a08e8f9041..7915b661ff 100644 --- a/packages/ui/components/icon/icon-names.ts +++ b/packages/ui/components/icon/icon-names.ts @@ -115,6 +115,7 @@ export type IconName = | "sliders-vertical" | "smartphone" | "sparkles" + | "split" | "square-check" | "star" | "sun"