From bb40b2f6bf1f2280c30b468396815a76018978e2 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Fri, 10 Jan 2025 20:16:11 -0500 Subject: [PATCH] chore: app router - all sub-pages in `/apps` (#16976) * chore: apps/[slug] remove pages router * remove apps/[slug] pages from /future * chore: apps/installed remove pages router * chore: apps/installation remove pages router * remove Head element * fix metadata * fix test * fix another test * chore: apps/categories remove pages router * revert unneeded changes * update middleware * Remove * remove unused import and code * remove unused import and code again * fix * fix category page * add split icon * add /routing paths to middleware matcher * wip * remove HeadSeo from App.tsx * clean up head-seo test * add generateAppMetadata * use generateAppMetadata in apps/[slug] page * delete file * remove log * fix * fix * fix apps/installed pages * fix cateogires pages * fix * fix imports * wip * fix * fix * fix metadata * fix * redirect /apps/routing-forms to /routing * replace all usages of /apps/routing-forms to /routing * better naming * /routing -> /routing/forms * fix * fix * fix * fix * remove backPath as it is irrelevant when withoutMain is true * fix type checks * fix type check in apps/[slug] * refactors * fix * fix test * fix * fix * fix * Replace multiple leading slashes with a single slash * migrate routing-forms too * add re routing * fix * add redirection --------- Co-authored-by: Peer Richelsen Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> --- .env.example | 8 -- apps/web/abTest/middlewareFactory.ts | 5 - apps/web/app/_utils.tsx | 30 ++++- .../apps/[slug]/[...pages]/page.tsx | 0 apps/web/app/apps/[slug]/page.tsx | 68 ++++++++++ .../{future => }/apps/[slug]/setup/page.tsx | 22 +++- .../app/apps/categories/[category]/page.tsx | 40 ++++++ .../app/{future => }/apps/categories/page.tsx | 0 .../apps/installation/[[...step]]/page.tsx | 2 +- .../app/apps/installed/[category]/page.tsx | 32 +++++ .../app/{future => }/apps/installed/page.tsx | 0 .../apps/routing-forms/[...pages]/page.tsx | 1 + apps/web/app/apps/routing-forms/page.tsx | 1 + apps/web/app/future/apps/[slug]/page.tsx | 45 ------- .../apps/categories/[category]/page.tsx | 34 ----- .../future/apps/installed/[category]/page.tsx | 18 --- .../routing-forms/[...pages]/page.tsx | 6 +- .../app/{future => }/routing-forms/page.tsx | 4 +- apps/web/app/routing/[...pages]/page.tsx | 70 +++++++++++ apps/web/app/routing/page.tsx | 7 ++ apps/web/components/apps/App.tsx | 6 - apps/web/components/dialog/RerouteDialog.tsx | 2 +- .../[slug]/[...pages]/getServerSideProps.ts | 2 - apps/web/lib/apps/[slug]/getStaticProps.tsx | 30 ++--- .../categories/[category]/getStaticProps.tsx | 11 +- .../[category]/getServerSideProps.tsx | 78 ------------ .../lib/apps/installed/getServerSideProps.tsx | 3 - .../routing/[...pages]/getServerSideProps.ts | 118 ++++++++++++++++++ apps/web/middleware.ts | 37 +++--- .../apps/[slug]/[...pages]/pages-view.tsx | 2 - .../modules/apps/[slug]/setup/setup-view.tsx | 9 +- apps/web/modules/apps/[slug]/slug-view.tsx | 10 +- .../categories/[category]/category-view.tsx | 9 +- .../installation/[[...step]]/step-view.tsx | 7 -- .../[category]/installed-category-view.tsx | 11 +- .../modules/routing/[...pages]/pages-view.tsx | 76 +++++++++++ apps/web/next.config.js | 2 +- apps/web/pages/apps/[slug]/[...pages].tsx | 15 --- apps/web/pages/apps/[slug]/index.tsx | 39 ------ apps/web/pages/apps/[slug]/setup.tsx | 14 --- apps/web/pages/apps/categories/[category].tsx | 33 ----- apps/web/pages/apps/categories/index.tsx | 12 -- .../pages/apps/installation/[[...step]].tsx | 12 -- apps/web/pages/apps/installed/[category].tsx | 12 -- apps/web/pages/apps/installed/index.tsx | 7 -- apps/web/pages/routing-forms/[...pages].tsx | 25 ---- apps/web/pages/routing-forms/index.tsx | 12 -- apps/web/public/icons/sprite.svg | 6 + apps/web/scripts/vercel-app-router-deploy.sh | 5 - packages/app-store/routing-forms/api/add.ts | 2 +- packages/app-store/routing-forms/config.json | 2 +- .../pages/app-routing-server.config.ts | 13 ++ .../pages/app-routing.config.tsx | 14 --- .../pages/form-edit/[...appPages].tsx | 6 +- .../pages/reporting/[...appPages].tsx | 6 +- .../pages/route-builder/[...appPages].tsx | 6 +- .../pages/routing-link/[...appPages].tsx | 4 - .../pages/routing-link/getServerSideProps.ts | 3 - .../playwright/tests/basic.e2e.ts | 14 +-- .../playwright/tests/testUtils.ts | 2 +- .../pages/how-to-use/[...appPages].tsx | 2 +- packages/features/shell/Shell.tsx | 15 ++- .../features/shell/navigation/Navigation.tsx | 8 +- packages/types/App.d.ts | 2 +- .../ui/components/head-seo/head-seo.test.tsx | 11 -- packages/ui/components/icon/icon-list.mjs | 1 + packages/ui/components/icon/icon-names.ts | 1 + turbo.json | 5 - 68 files changed, 554 insertions(+), 561 deletions(-) rename apps/web/app/{future => }/apps/[slug]/[...pages]/page.tsx (100%) create mode 100644 apps/web/app/apps/[slug]/page.tsx rename apps/web/app/{future => }/apps/[slug]/setup/page.tsx (60%) create mode 100644 apps/web/app/apps/categories/[category]/page.tsx rename apps/web/app/{future => }/apps/categories/page.tsx (100%) rename apps/web/app/{future => }/apps/installation/[[...step]]/page.tsx (100%) create mode 100644 apps/web/app/apps/installed/[category]/page.tsx rename apps/web/app/{future => }/apps/installed/page.tsx (100%) create mode 100644 apps/web/app/apps/routing-forms/[...pages]/page.tsx create mode 100644 apps/web/app/apps/routing-forms/page.tsx delete mode 100644 apps/web/app/future/apps/[slug]/page.tsx delete mode 100644 apps/web/app/future/apps/categories/[category]/page.tsx delete mode 100644 apps/web/app/future/apps/installed/[category]/page.tsx rename apps/web/app/{future => }/routing-forms/[...pages]/page.tsx (54%) rename apps/web/app/{future => }/routing-forms/page.tsx (53%) create mode 100644 apps/web/app/routing/[...pages]/page.tsx create mode 100644 apps/web/app/routing/page.tsx delete mode 100644 apps/web/lib/apps/installed/[category]/getServerSideProps.tsx delete mode 100644 apps/web/lib/apps/installed/getServerSideProps.tsx create mode 100644 apps/web/lib/routing/[...pages]/getServerSideProps.ts create mode 100644 apps/web/modules/routing/[...pages]/pages-view.tsx delete mode 100644 apps/web/pages/apps/[slug]/[...pages].tsx delete mode 100644 apps/web/pages/apps/[slug]/index.tsx delete mode 100644 apps/web/pages/apps/[slug]/setup.tsx delete mode 100644 apps/web/pages/apps/categories/[category].tsx delete mode 100644 apps/web/pages/apps/categories/index.tsx delete mode 100644 apps/web/pages/apps/installation/[[...step]].tsx delete mode 100644 apps/web/pages/apps/installed/[category].tsx delete mode 100644 apps/web/pages/apps/installed/index.tsx delete mode 100644 apps/web/pages/routing-forms/[...pages].tsx delete mode 100644 apps/web/pages/routing-forms/index.tsx create mode 100644 packages/app-store/routing-forms/pages/app-routing-server.config.ts diff --git a/.env.example b/.env.example index c0e3a2bb30..cfb67a88df 100644 --- a/.env.example +++ b/.env.example @@ -355,14 +355,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_INSTALLED_CATEGORY_ENABLED=0 -APP_ROUTER_APPS_SLUG_ENABLED=0 -APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0 -# whether we redirect to the future/apps/categories from /apps/categories or not -APP_ROUTER_APPS_CATEGORIES_ENABLED=0 -# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not -APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0 -APP_ROUTER_APPS_ENABLED=0 APP_ROUTER_TEAM_ENABLED=0 APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0 APP_ROUTER_AUTH_LOGIN_ENABLED=0 diff --git a/apps/web/abTest/middlewareFactory.ts b/apps/web/abTest/middlewareFactory.ts index 2793847237..c103de9fd6 100644 --- a/apps/web/abTest/middlewareFactory.ts +++ b/apps/web/abTest/middlewareFactory.ts @@ -5,11 +5,6 @@ import { NextResponse, URLPattern } from "next/server"; import { FUTURE_ROUTES_ENABLED_COOKIE_NAME, FUTURE_ROUTES_OVERRIDE_COOKIE_NAME } from "@calcom/lib/constants"; const ROUTES: [URLPattern, boolean][] = [ - ["/apps/installed/:category", process.env.APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED === "1"] as const, - ["/apps/:slug", process.env.APP_ROUTER_APPS_SLUG_ENABLED === "1"] as const, - ["/apps/:slug/setup", process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED === "1"] as const, - ["/apps/categories", process.env.APP_ROUTER_APPS_CATEGORIES_ENABLED === "1"] as const, - ["/apps/categories/:category", process.env.APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED === "1"] as const, ["/auth/forgot-password/:path*", process.env.APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED === "1"] as const, ["/auth/login", process.env.APP_ROUTER_AUTH_LOGIN_ENABLED === "1"] as const, ["/auth/logout", process.env.APP_ROUTER_AUTH_LOGOUT_ENABLED === "1"] as const, diff --git a/apps/web/app/_utils.tsx b/apps/web/app/_utils.tsx index a487888800..f5b13fe1ac 100644 --- a/apps/web/app/_utils.tsx +++ b/apps/web/app/_utils.tsx @@ -3,7 +3,8 @@ import i18next from "i18next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { headers } from "next/headers"; -import { constructGenericImage } from "@calcom/lib/OgImages"; +import type { AppImageProps } from "@calcom/lib/OgImages"; +import { constructGenericImage, constructAppImage } 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 { truncateOnWord } from "@calcom/lib/text"; @@ -61,17 +62,18 @@ const _generateMetadataWithoutImage = async ( const t = await getTranslationWithCache(locale); const title = getTitle(t); - const description = getDescription(t); + const titleSuffix = `| ${APP_NAME}`; const displayedTitle = title.includes(titleSuffix) || hideBranding ? title : `${title} ${titleSuffix}`; const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL); + const truncatedDescription = truncateOnWord(getDescription(t), 158); return { title: title.length === 0 ? APP_NAME : displayedTitle, - description, + description: truncatedDescription, alternates: { canonical }, openGraph: { - description: truncateOnWord(description, 158), + description: truncatedDescription, url: canonical, type: "website", siteName: APP_NAME, @@ -103,3 +105,23 @@ export const _generateMetadata = async ( }, }; }; + +export const generateAppMetadata = async ( + app: AppImageProps, + getTitle: (t: TFunction) => string, + getDescription: (t: TFunction) => string, + hideBranding?: boolean, + origin?: string +) => { + const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin); + + const image = SEO_IMG_OGIMG + constructAppImage({ ...app, description: metadata.description }); + + return { + ...metadata, + openGraph: { + ...metadata.openGraph, + images: [image], + }, + }; +}; diff --git a/apps/web/app/future/apps/[slug]/[...pages]/page.tsx b/apps/web/app/apps/[slug]/[...pages]/page.tsx similarity index 100% rename from apps/web/app/future/apps/[slug]/[...pages]/page.tsx rename to apps/web/app/apps/[slug]/[...pages]/page.tsx diff --git a/apps/web/app/apps/[slug]/page.tsx b/apps/web/app/apps/[slug]/page.tsx new file mode 100644 index 0000000000..7cd655d5d3 --- /dev/null +++ b/apps/web/app/apps/[slug]/page.tsx @@ -0,0 +1,68 @@ +import type { PageProps as _PageProps } from "app/_types"; +import { generateAppMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { notFound } from "next/navigation"; +import { z } from "zod"; + +import { AppRepository } from "@calcom/lib/server/repository/app"; +import { isPrismaAvailableCheck } from "@calcom/prisma/is-prisma-available-check"; + +import { getStaticProps } from "@lib/apps/[slug]/getStaticProps"; + +import AppView from "~/apps/[slug]/slug-view"; + +const paramsSchema = z.object({ + slug: z.string(), +}); + +export const generateMetadata = async ({ params }: _PageProps) => { + const p = paramsSchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + + const props = await getStaticProps(p.data.slug); + + if (!props) { + notFound(); + } + const { name, logo, description } = props.data; + + return await generateAppMetadata( + { slug: logo, name, description }, + () => name, + () => description + ); +}; + +export const generateStaticParams = async () => { + const isPrismaAvailable = await isPrismaAvailableCheck(); + + if (!isPrismaAvailable) { + // Database is not available at build time. Make sure we fall back to building these pages on demand + return []; + } + const appStore = await AppRepository.findAppStore(); + return appStore.map(({ slug }) => ({ slug })); +}; + +async function Page({ params }: _PageProps) { + const p = paramsSchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + + const props = await getStaticProps(p.data.slug); + + if (!props) { + notFound(); + } + + return ; +} + +export default WithLayout({ getLayout: null, ServerPage: Page }); + +export const dynamic = "force-static"; diff --git a/apps/web/app/future/apps/[slug]/setup/page.tsx b/apps/web/app/apps/[slug]/setup/page.tsx similarity index 60% rename from apps/web/app/future/apps/[slug]/setup/page.tsx rename to apps/web/app/apps/[slug]/setup/page.tsx index c10edd12a2..9689b19e06 100644 --- a/apps/web/app/future/apps/[slug]/setup/page.tsx +++ b/apps/web/app/apps/[slug]/setup/page.tsx @@ -2,16 +2,34 @@ import { withAppDirSsr } from "app/WithAppDirSsr"; import type { PageProps as _PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; +import { notFound } from "next/navigation"; +import { z } from "zod"; import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps"; import Page, { type PageProps } from "~/apps/[slug]/setup/setup-view"; +const paramsSchema = z.object({ + slug: z.string(), +}); + export const generateMetadata = async ({ params }: _PageProps) => { - return await _generateMetadata( - () => `${params.slug}`, + const p = paramsSchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + const metadata = await _generateMetadata( + () => `${p.data.slug}`, () => "" ); + return { + ...metadata, + robots: { + follow: false, + index: false, + }, + }; }; const getData = withAppDirSsr(getServerSideProps); diff --git a/apps/web/app/apps/categories/[category]/page.tsx b/apps/web/app/apps/categories/[category]/page.tsx new file mode 100644 index 0000000000..0b9b5089c6 --- /dev/null +++ b/apps/web/app/apps/categories/[category]/page.tsx @@ -0,0 +1,40 @@ +import type { PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { redirect } from "next/navigation"; +import { z } from "zod"; + +import { AppCategories } from "@calcom/prisma/enums"; + +import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps"; + +import CategoryPage from "~/apps/categories/[category]/category-view"; + +const querySchema = z.object({ + category: z.enum(Object.values(AppCategories) as [AppCategories, ...AppCategories[]]), +}); + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => t("app_store"), + (t) => t("app_store_description") + ); +}; + +export const generateStaticParams = async () => { + const paths = Object.keys(AppCategories); + return paths.map((category) => ({ category })); +}; + +async function Page({ params, searchParams }: PageProps) { + const parsed = querySchema.safeParse({ ...params, ...searchParams }); + if (!parsed.success) { + redirect("/apps/categories/calendar"); + } + + const props = await getStaticProps(parsed.data.category); + + return ; +} + +export default WithLayout({ getLayout: null, ServerPage: Page }); diff --git a/apps/web/app/future/apps/categories/page.tsx b/apps/web/app/apps/categories/page.tsx similarity index 100% rename from apps/web/app/future/apps/categories/page.tsx rename to apps/web/app/apps/categories/page.tsx diff --git a/apps/web/app/future/apps/installation/[[...step]]/page.tsx b/apps/web/app/apps/installation/[[...step]]/page.tsx similarity index 100% rename from apps/web/app/future/apps/installation/[[...step]]/page.tsx rename to apps/web/app/apps/installation/[[...step]]/page.tsx index 650f732d9a..6b536803b6 100644 --- a/apps/web/app/future/apps/installation/[[...step]]/page.tsx +++ b/apps/web/app/apps/installation/[[...step]]/page.tsx @@ -12,7 +12,6 @@ import Page from "~/apps/installation/[[...step]]/step-view"; export const generateMetadata = async ({ params, searchParams }: PageProps) => { const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams); - const { appMetadata } = await getData(legacyCtx); return await _generateMetadata( (t) => `${t("install")} ${appMetadata?.name ?? ""}`, @@ -23,3 +22,4 @@ export const generateMetadata = async ({ params, searchParams }: PageProps) => { const getData = withAppDirSsr(getServerSideProps); export default WithLayout({ getLayout: null, getData, Page }); + diff --git a/apps/web/app/apps/installed/[category]/page.tsx b/apps/web/app/apps/installed/[category]/page.tsx new file mode 100644 index 0000000000..9eb1a77c8d --- /dev/null +++ b/apps/web/app/apps/installed/[category]/page.tsx @@ -0,0 +1,32 @@ +import { PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import { redirect } from "next/navigation"; +import { z } from "zod"; + +import { AppCategories } from "@calcom/prisma/enums"; + +import InstalledApps from "~/apps/installed/[category]/installed-category-view"; + +const querySchema = z.object({ + category: z.nativeEnum(AppCategories), +}); + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => t("installed_apps"), + (t) => t("manage_your_connected_apps") + ); +}; + +const InstalledAppsWrapper = async ({ params }: PageProps) => { + const parsedParams = querySchema.safeParse(params); + + if (!parsedParams.success) { + redirect("/apps/installed/calendar"); + } + + return ; +}; + +export default WithLayout({ getLayout: null, ServerPage: InstalledAppsWrapper }); diff --git a/apps/web/app/future/apps/installed/page.tsx b/apps/web/app/apps/installed/page.tsx similarity index 100% rename from apps/web/app/future/apps/installed/page.tsx rename to apps/web/app/apps/installed/page.tsx diff --git a/apps/web/app/apps/routing-forms/[...pages]/page.tsx b/apps/web/app/apps/routing-forms/[...pages]/page.tsx new file mode 100644 index 0000000000..a86b31568d --- /dev/null +++ b/apps/web/app/apps/routing-forms/[...pages]/page.tsx @@ -0,0 +1 @@ +export { default } from "app/routing-forms/[...pages]/page"; diff --git a/apps/web/app/apps/routing-forms/page.tsx b/apps/web/app/apps/routing-forms/page.tsx new file mode 100644 index 0000000000..3abdb64575 --- /dev/null +++ b/apps/web/app/apps/routing-forms/page.tsx @@ -0,0 +1 @@ +export { default } from "app/routing-forms/page"; diff --git a/apps/web/app/future/apps/[slug]/page.tsx b/apps/web/app/future/apps/[slug]/page.tsx deleted file mode 100644 index 3c7f717544..0000000000 --- a/apps/web/app/future/apps/[slug]/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Prisma } from "@prisma/client"; -import { withAppDirSsg } from "app/WithAppDirSsg"; -import type { PageProps as _PageProps } from "app/_types"; -import { _generateMetadata } from "app/_utils"; -import { WithLayout } from "app/layoutHOC"; -import { cookies, headers } from "next/headers"; - -import { AppRepository } from "@calcom/lib/server/repository/app"; - -import { getStaticProps } from "@lib/apps/[slug]/getStaticProps"; -import { buildLegacyCtx } from "@lib/buildLegacyCtx"; - -import type { PageProps } from "~/apps/[slug]/slug-view"; -import Page from "~/apps/[slug]/slug-view"; - -const getData = withAppDirSsg(getStaticProps, "future/apps/[slug]"); - -export const generateMetadata = async ({ params, searchParams }: _PageProps) => { - const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams); - const res = await getData(legacyContext); - - return await _generateMetadata( - () => res?.data.name ?? "", - () => res?.data.description ?? "" - ); -}; - -export const generateStaticParams = async () => { - try { - const appStore = await AppRepository.findAppStore(); - return appStore.map(({ slug }) => ({ slug })); - } catch (e: unknown) { - if (e instanceof Prisma.PrismaClientInitializationError) { - // Database is not available at build time, but that's ok – we fall back to resolving paths on demand - } else { - throw e; - } - } - - return []; -}; - -export default WithLayout({ getLayout: null, Page, getData }); - -export const dynamic = "force-static"; diff --git a/apps/web/app/future/apps/categories/[category]/page.tsx b/apps/web/app/future/apps/categories/[category]/page.tsx deleted file mode 100644 index 4aae6cb845..0000000000 --- a/apps/web/app/future/apps/categories/[category]/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { withAppDirSsg } from "app/WithAppDirSsg"; -import { _generateMetadata } from "app/_utils"; -import { WithLayout } from "app/layoutHOC"; - -import { AppCategories } from "@calcom/prisma/enums"; -import { isPrismaAvailableCheck } from "@calcom/prisma/is-prisma-available-check"; - -import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps"; - -import CategoryPage, { type PageProps } from "~/apps/categories/[category]/category-view"; - -export const generateMetadata = async () => { - return await _generateMetadata( - (t) => t("app_store"), - (t) => t("app_store_description") - ); -}; - -export const generateStaticParams = async () => { - const paths = Object.keys(AppCategories); - const isPrismaAvailable = await isPrismaAvailableCheck(); - - if (!isPrismaAvailable) { - // Database is not available at build time. Make sure we fall back to building these pages on demand - return []; - } - - return paths.map((category) => ({ category })); -}; - -const getData = withAppDirSsg(getStaticProps, "future/apps/categories/[category]"); - -export default WithLayout({ getData, Page: CategoryPage, getLayout: null })<"P">; -export const dynamic = "force-static"; diff --git a/apps/web/app/future/apps/installed/[category]/page.tsx b/apps/web/app/future/apps/installed/[category]/page.tsx deleted file mode 100644 index c20bbf0d32..0000000000 --- a/apps/web/app/future/apps/installed/[category]/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { withAppDirSsr } from "app/WithAppDirSsr"; -import { _generateMetadata } from "app/_utils"; -import { WithLayout } from "app/layoutHOC"; - -import { getServerSidePropsAppDir } from "@lib/apps/installed/[category]/getServerSideProps"; - -import Page from "~/apps/installed/[category]/installed-category-view"; - -export const generateMetadata = async () => { - return await _generateMetadata( - (t) => t("installed_apps"), - (t) => t("manage_your_connected_apps") - ); -}; - -const getData = withAppDirSsr(getServerSidePropsAppDir); - -export default WithLayout({ getLayout: null, getData, Page }); diff --git a/apps/web/app/future/routing-forms/[...pages]/page.tsx b/apps/web/app/routing-forms/[...pages]/page.tsx similarity index 54% rename from apps/web/app/future/routing-forms/[...pages]/page.tsx rename to apps/web/app/routing-forms/[...pages]/page.tsx index 4ecc5a1aa0..8bdeb00880 100644 --- a/apps/web/app/future/routing-forms/[...pages]/page.tsx +++ b/apps/web/app/routing-forms/[...pages]/page.tsx @@ -10,10 +10,10 @@ const paramsSchema = z pages: [], }); -const Page = ({ params, searchParams }: PageProps) => { - const { pages } = paramsSchema.parse({ ...params, ...searchParams }); +const Page = ({ params }: PageProps) => { + const { pages } = paramsSchema.parse(params); - redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`); + redirect(`/routing/${pages.length ? pages.join("/") : ""}`); }; export default Page; diff --git a/apps/web/app/future/routing-forms/page.tsx b/apps/web/app/routing-forms/page.tsx similarity index 53% rename from apps/web/app/future/routing-forms/page.tsx rename to apps/web/app/routing-forms/page.tsx index ba222ba476..39c972a385 100644 --- a/apps/web/app/future/routing-forms/page.tsx +++ b/apps/web/app/routing-forms/page.tsx @@ -1,7 +1,7 @@ import { redirect } from "next/navigation"; -const Page = () => { - redirect("/apps/routing-forms/forms"); +const Page = async () => { + redirect("/routing/forms"); }; export default Page; diff --git a/apps/web/app/routing/[...pages]/page.tsx b/apps/web/app/routing/[...pages]/page.tsx new file mode 100644 index 0000000000..8f91cfddc5 --- /dev/null +++ b/apps/web/app/routing/[...pages]/page.tsx @@ -0,0 +1,70 @@ +import { withAppDirSsr } from "app/WithAppDirSsr"; +import type { PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; +import type { GetServerSidePropsResult } from "next"; +import { cookies, headers } from "next/headers"; +import { notFound } from "next/navigation"; +import z from "zod"; + +import { buildLegacyCtx } from "@lib/buildLegacyCtx"; +import { getServerSideProps } from "@lib/routing/[...pages]/getServerSideProps"; + +import RoutingFormsPage, { getLayout } from "~/routing/[...pages]/pages-view"; + +const paramsSchema = z.object({ + pages: z.array(z.string()), +}); + +export const generateMetadata = async ({ params, searchParams }: PageProps) => { + const p = paramsSchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + const ctx = buildLegacyCtx(headers(), cookies(), params, searchParams); + const data = await getData({ + ...ctx, + params: { + slug: "routing-forms", + pages: params.pages, + }, + }); + const form = "form" in data ? (data.form as { name?: string; description?: string }) : null; + const formName = form?.name; + const formDescription = form?.description; + + const { pages } = p.data; + + if (pages.includes("routing-link")) { + return await _generateMetadata( + () => `${formName} | Cal.com Forms`, + () => "", + true + ); + } + + return await _generateMetadata( + (t) => formName ?? t("routing_forms"), + (t) => formDescription ?? t("routing_forms_description") + ); +}; + +const getData = withAppDirSsr>(getServerSideProps); + +const ServerPage = async ({ params, searchParams }: PageProps) => { + const ctx = buildLegacyCtx(headers(), cookies(), params, searchParams); + const props = await getData({ + ...ctx, + params: { + slug: "routing-forms", + pages: params.pages, + }, + }); + return ; +}; + +export default WithLayout({ + getLayout, + ServerPage, +}); diff --git a/apps/web/app/routing/page.tsx b/apps/web/app/routing/page.tsx new file mode 100644 index 0000000000..39c972a385 --- /dev/null +++ b/apps/web/app/routing/page.tsx @@ -0,0 +1,7 @@ +import { redirect } from "next/navigation"; + +const Page = async () => { + redirect("/routing/forms"); +}; + +export default Page; diff --git a/apps/web/components/apps/App.tsx b/apps/web/components/apps/App.tsx index 6ad0c4b60b..f0ef2ead98 100644 --- a/apps/web/components/apps/App.tsx +++ b/apps/web/components/apps/App.tsx @@ -1,7 +1,6 @@ import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; import Shell from "@calcom/features/shell/Shell"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { HeadSeo } from "@calcom/ui"; import type { AppPageProps } from "./AppPage"; import { AppPage } from "./AppPage"; @@ -14,11 +13,6 @@ const ShellHeading = () => { export default function WrappedApp(props: AppPageProps) { return ( } backPath="/apps" withoutSeo> - {props.licenseRequired ? ( diff --git a/apps/web/components/dialog/RerouteDialog.tsx b/apps/web/components/dialog/RerouteDialog.tsx index ca72e40dc8..d39d868682 100644 --- a/apps/web/components/dialog/RerouteDialog.tsx +++ b/apps/web/components/dialog/RerouteDialog.tsx @@ -807,7 +807,7 @@ const RerouteDialogContentAndFooterWithFormResponse = ({
{form.name} diff --git a/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts b/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts index 5cd62aade0..ced207cec2 100644 --- a/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts +++ b/apps/web/lib/apps/[slug]/[...pages]/getServerSideProps.ts @@ -2,7 +2,6 @@ import type { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; import { z } from "zod"; import { getAppWithMetadata } from "@calcom/app-store/_appRegistry"; -import RoutingFormsRoutingConfig 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 prisma from "@calcom/prisma"; @@ -32,7 +31,6 @@ type NotFound = { // TODO: It is a candidate for apps.*.generated.* const AppsRouting = { - "routing-forms": RoutingFormsRoutingConfig, typeform: TypeformRoutingConfig, }; diff --git a/apps/web/lib/apps/[slug]/getStaticProps.tsx b/apps/web/lib/apps/[slug]/getStaticProps.tsx index 9113ba9c4e..61079a2834 100644 --- a/apps/web/lib/apps/[slug]/getStaticProps.tsx +++ b/apps/web/lib/apps/[slug]/getStaticProps.tsx @@ -1,7 +1,5 @@ import fs from "fs"; import matter from "gray-matter"; -import MarkdownIt from "markdown-it"; -import type { GetStaticPropsContext } from "next"; import path from "path"; import { z } from "zod"; @@ -10,8 +8,6 @@ import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath"; import { IS_PRODUCTION } from "@calcom/lib/constants"; import prisma from "@calcom/prisma"; -const md = new MarkdownIt("default", { html: true, breaks: true }); - export const sourceSchema = z.object({ content: z.string(), data: z.object({ @@ -29,15 +25,15 @@ export const sourceSchema = z.object({ }), }); -export const getStaticProps = async (ctx: GetStaticPropsContext) => { - if (typeof ctx.params?.slug !== "string") return { notFound: true } as const; +export type AppDataProps = NonNullable>>; +export const getStaticProps = async (slug: string) => { const appMeta = await getAppWithMetadata({ - slug: ctx.params?.slug, + slug, }); const appFromDb = await prisma.app.findUnique({ - where: { slug: ctx.params.slug.toLowerCase() }, + where: { slug: slug.toLowerCase() }, }); const isAppAvailableInFileSystem = appMeta; @@ -45,16 +41,14 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => { if (!IS_PRODUCTION && isAppDisabled) { return { - props: { - isAppDisabled: true as const, - data: { - ...appMeta, - }, + isAppDisabled: true as const, + data: { + ...appMeta, }, }; } - if (!appFromDb || !appMeta || isAppDisabled) return { notFound: true } as const; + if (!appFromDb || !appMeta || isAppDisabled) return null; const isTemplate = appMeta.isTemplate; const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName); @@ -85,10 +79,8 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => { }); } return { - props: { - isAppDisabled: false as const, - source: { content, data }, - data: appMeta, - }, + isAppDisabled: false as const, + source: { content, data }, + data: appMeta, }; }; diff --git a/apps/web/lib/apps/categories/[category]/getStaticProps.tsx b/apps/web/lib/apps/categories/[category]/getStaticProps.tsx index 7afa898b1c..29daa45a82 100644 --- a/apps/web/lib/apps/categories/[category]/getStaticProps.tsx +++ b/apps/web/lib/apps/categories/[category]/getStaticProps.tsx @@ -1,12 +1,10 @@ -import type { GetStaticPropsContext } from "next"; - import { getAppRegistry } from "@calcom/app-store/_appRegistry"; import prisma from "@calcom/prisma"; import type { AppCategories } from "@calcom/prisma/enums"; -export const getStaticProps = async (context: GetStaticPropsContext) => { - const category = context.params?.category as AppCategories; +export type CategoryDataProps = NonNullable>>; +export const getStaticProps = async (category: AppCategories) => { const appQuery = await prisma.app.findMany({ where: { categories: { @@ -24,8 +22,7 @@ export const getStaticProps = async (context: GetStaticPropsContext) => { const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug)); return { - props: { - apps, - }, + apps, + category, }; }; diff --git a/apps/web/lib/apps/installed/[category]/getServerSideProps.tsx b/apps/web/lib/apps/installed/[category]/getServerSideProps.tsx deleted file mode 100644 index 109032a239..0000000000 --- a/apps/web/lib/apps/installed/[category]/getServerSideProps.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import type { GetServerSidePropsContext } from "next"; -import { z } from "zod"; - -import { AppCategories } from "@calcom/prisma/enums"; - -export type querySchemaType = z.infer; - -export const querySchema = z.object({ - category: z.nativeEnum(AppCategories), -}); - -export async function getServerSideProps(ctx: GetServerSidePropsContext) { - // get return-to cookie and redirect if needed - const { cookies } = ctx.req; - - const returnTo = cookies["return-to"]; - - if (cookies && returnTo) { - ctx.res.setHeader("Set-Cookie", "return-to=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"); - const redirect = { - redirect: { - destination: `${returnTo}`, - permanent: false, - }, - } as const; - - return redirect; - } - - const params = querySchema.safeParse(ctx.params); - - if (!params.success) { - const notFound = { notFound: true } as const; - - return notFound; - } - - return { - props: { - category: params.data.category, - }, - }; -} - -export async function getServerSidePropsAppDir(ctx: GetServerSidePropsContext) { - // get return-to cookie and redirect if needed - const { cookies } = ctx.req; - - const returnTo = cookies["return-to"]; - - if (cookies && returnTo) { - const NextResponse = await import("next/server").then((mod) => mod.NextResponse); - const response = NextResponse.next(); - response.headers.set("Set-Cookie", "return-to=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"); - const redirect = { - redirect: { - destination: `${returnTo}`, - permanent: false, - }, - } as const; - - return redirect; - } - - const params = querySchema.safeParse(ctx.params); - - if (!params.success) { - const notFound = { notFound: true } as const; - - return notFound; - } - - return { - props: { - category: params.data.category, - }, - }; -} diff --git a/apps/web/lib/apps/installed/getServerSideProps.tsx b/apps/web/lib/apps/installed/getServerSideProps.tsx deleted file mode 100644 index a43490aea5..0000000000 --- a/apps/web/lib/apps/installed/getServerSideProps.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export async function getServerSideProps() { - return { redirect: { permanent: false, destination: "/apps/installed/calendar" } }; -} diff --git a/apps/web/lib/routing/[...pages]/getServerSideProps.ts b/apps/web/lib/routing/[...pages]/getServerSideProps.ts new file mode 100644 index 0000000000..681d51b251 --- /dev/null +++ b/apps/web/lib/routing/[...pages]/getServerSideProps.ts @@ -0,0 +1,118 @@ +import type { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; +import { z } from "zod"; + +import { getAppWithMetadata } from "@calcom/app-store/_appRegistry"; +import { routingServerSidePropsConfig } from "@calcom/app-store/routing-forms/pages/app-routing-server.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 RoutingPageType = { + getServerSideProps?: AppGetServerSideProps; + // A component than can accept any properties + // eslint-disable-next-line @typescript-eslint/no-explicit-any + default: ((props: any) => JSX.Element) & + Pick; +}; + +type Found = { + notFound: false; + getServerSideProps: RoutingPageType["getServerSideProps"]; +}; + +type NotFound = { + notFound: true; +}; + +function getRoute(pages: string[]) { + const mainPage = pages[0]; + const getServerSideProps = routingServerSidePropsConfig[mainPage] as RoutingPageType["getServerSideProps"]; + + if (!getServerSideProps || typeof getServerSideProps !== "function") { + return { + notFound: true, + } as NotFound; + } + return { notFound: false, getServerSideProps } as Found; +} + +const paramsSchema = z.object({ + slug: z.string(), + pages: z.array(z.string()), +}); + +export async function getServerSideProps( + context: GetServerSidePropsContext +): Promise> { + const { params, req } = context; + if (!params) { + return { + notFound: true, + }; + } + + const parsedParams = paramsSchema.safeParse(params); + if (!parsedParams.success) { + return { + notFound: true, + }; + } + + const appName = parsedParams.data.slug; + const pages = parsedParams.data.pages; + const route = getRoute(pages); + + if (route.notFound) { + return { notFound: true }; + } + + if (route.getServerSideProps) { + params.appPages = pages.slice(1); + 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: app.simplePath, + ...result.props, + }, + }; + } else { + return { + props: { + appName, + }, + }; + } +} diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 192cb9b426..3e27481444 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -64,11 +64,9 @@ const middleware = async (req: NextRequest): Promise> => { requestHeaders.set("x-csp-enforce", "true"); } - if (url.pathname.startsWith("/future/apps/installed")) { + if (url.pathname.startsWith("/apps/installed")) { const returnTo = req.cookies.get("return-to")?.value; if (returnTo !== undefined) { - requestHeaders.set("Set-Cookie", "return-to=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"); - let validPathname = returnTo; try { @@ -77,8 +75,14 @@ const middleware = async (req: NextRequest): Promise> => { const nextUrl = url.clone(); nextUrl.pathname = validPathname; - // TODO: Consider using responseWithHeaders here - return NextResponse.redirect(nextUrl, { headers: requestHeaders }); + + const response = NextResponse.redirect(nextUrl); + response.cookies.set("return-to", "", { + expires: new Date(0), + path: "/", + }); + + return response; } } @@ -108,9 +112,10 @@ const routingForms = { handleRewrite: (url: URL) => { // Don't 404 old routing_forms links if (url.pathname.startsWith("/apps/routing_forms")) { - url.pathname = url.pathname.replace(/^\/apps\/routing_forms($|\/)/, "/apps/routing-forms/"); + url.pathname = url.pathname.replace(/^\/apps\/routing_forms($|\/)/, "/routing/"); return NextResponse.rewrite(url); } + return null; }, }; @@ -168,25 +173,11 @@ export const config = { "/login", "/auth/login", "/future/auth/login", - /** - * Paths required by routingForms.handle - */ - "/apps/routing_forms/:path*", - - "/event-types/:path*", - "/apps/installed/:category/", - "/future/apps/installed/:category/", - "/apps/:slug/", - "/future/apps/:slug/", - "/apps/:slug/setup/", - "/future/apps/:slug/setup/", - "/apps/categories/", - "/future/apps/categories/", - "/apps/categories/:category/", - "/future/apps/categories/:category/", "/workflows/:path*", "/getting-started/:path*", - "/apps", + "/event-types/:path*", + "/apps/:path*", + "/routing/:path*", "/bookings/:status/", "/video/:path*", "/teams", diff --git a/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx b/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx index 1a1ca5d06f..464cd901db 100644 --- a/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx +++ b/apps/web/modules/apps/[slug]/[...pages]/pages-view.tsx @@ -1,6 +1,5 @@ "use client"; -import RoutingFormsRoutingConfig from "@calcom/app-store/routing-forms/pages/app-routing.config"; import TypeformRoutingConfig from "@calcom/app-store/typeform/pages/app-routing.config"; import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"; @@ -29,7 +28,6 @@ type NotFound = { // TODO: It is a candidate for apps.*.generated.* const AppsRouting = { - "routing-forms": RoutingFormsRoutingConfig, typeform: TypeformRoutingConfig, }; diff --git a/apps/web/modules/apps/[slug]/setup/setup-view.tsx b/apps/web/modules/apps/[slug]/setup/setup-view.tsx index 921423ce79..da07c32b8e 100644 --- a/apps/web/modules/apps/[slug]/setup/setup-view.tsx +++ b/apps/web/modules/apps/[slug]/setup/setup-view.tsx @@ -7,7 +7,6 @@ import { AppSetupPage } from "@calcom/app-store/_pages/setup"; import type { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import type { inferSSRProps } from "@calcom/types/inferSSRProps"; -import { HeadSeo } from "@calcom/ui"; export type PageProps = inferSSRProps; @@ -28,11 +27,5 @@ export default function SetupInformation(props: PageProps) { router.replace(`/auth/login?${urlSearchParams.toString()}`); } - return ( - <> - {/* So that the set up page does not get indexed by search engines */} - - - - ); + return ; } diff --git a/apps/web/modules/apps/[slug]/slug-view.tsx b/apps/web/modules/apps/[slug]/slug-view.tsx index 6331f81e40..b0faacc283 100644 --- a/apps/web/modules/apps/[slug]/slug-view.tsx +++ b/apps/web/modules/apps/[slug]/slug-view.tsx @@ -1,7 +1,5 @@ "use client"; -import MarkdownIt from "markdown-it"; -import type { InferGetStaticPropsType } from "next"; import Link from "next/link"; import { IS_PRODUCTION } from "@calcom/lib/constants"; @@ -9,16 +7,12 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { showToast } from "@calcom/ui"; -import type { getStaticProps } from "@lib/apps/[slug]/getStaticProps"; +import type { AppDataProps } from "@lib/apps/[slug]/getStaticProps"; import useRouterQuery from "@lib/hooks/useRouterQuery"; import App from "@components/apps/App"; -const md = new MarkdownIt("default", { html: true, breaks: true }); - -export type PageProps = InferGetStaticPropsType; - -function SingleAppPage(props: PageProps) { +function SingleAppPage(props: AppDataProps) { const { error, setQuery: setError } = useRouterQuery("error"); const { t } = useLocale(); if (error === "account_already_linked") { diff --git a/apps/web/modules/apps/categories/[category]/category-view.tsx b/apps/web/modules/apps/categories/[category]/category-view.tsx index 519dca26dc..c3d1f7990b 100644 --- a/apps/web/modules/apps/categories/[category]/category-view.tsx +++ b/apps/web/modules/apps/categories/[category]/category-view.tsx @@ -1,20 +1,15 @@ "use client"; -import type { InferGetStaticPropsType } from "next"; import Link from "next/link"; import Shell from "@calcom/features/shell/Shell"; -import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { AppCard, SkeletonText } from "@calcom/ui"; -import type { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps"; +import type { CategoryDataProps } from "@lib/apps/categories/[category]/getStaticProps"; -export type PageProps = InferGetStaticPropsType; -export default function Apps({ apps }: PageProps) { - const searchParams = useCompatSearchParams(); +export default function Apps({ apps, category }: CategoryDataProps) { const { t, isLocaleReady } = useLocale(); - const category = searchParams?.get("category"); return ( <> diff --git a/apps/web/modules/apps/installation/[[...step]]/step-view.tsx b/apps/web/modules/apps/installation/[[...step]]/step-view.tsx index d5937d3251..6245054a03 100644 --- a/apps/web/modules/apps/installation/[[...step]]/step-view.tsx +++ b/apps/web/modules/apps/installation/[[...step]]/step-view.tsx @@ -1,6 +1,5 @@ "use client"; -import Head from "next/head"; import { usePathname, useRouter } from "next/navigation"; import { useEffect, useMemo, useRef, useState } from "react"; import { useForm } from "react-hook-form"; @@ -226,12 +225,6 @@ const OnboardingPage = ({ key={pathname} className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen px-4" data-testid="onboarding"> - - - {t("install")} {appMetadata?.name ?? ""} - - -
diff --git a/apps/web/modules/apps/installed/[category]/installed-category-view.tsx b/apps/web/modules/apps/installed/[category]/installed-category-view.tsx index ba392420aa..e59480a6db 100644 --- a/apps/web/modules/apps/installed/[category]/installed-category-view.tsx +++ b/apps/web/modules/apps/installed/[category]/installed-category-view.tsx @@ -8,11 +8,9 @@ import type { UpdateUsersDefaultConferencingAppParams } from "@calcom/features/a import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal"; import type { RemoveAppParams } from "@calcom/features/apps/components/DisconnectIntegrationModal"; import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; -import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { AppCategories } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; -import type { inferSSRProps } from "@calcom/types/inferSSRProps"; import type { Icon } from "@calcom/ui"; import { AppSkeletonLoader as SkeletonLoader, @@ -23,7 +21,6 @@ import { } from "@calcom/ui"; import { QueryCell } from "@lib/QueryCell"; -import type { querySchemaType, getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps"; import { CalendarListContainer } from "@components/apps/CalendarListContainer"; import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout"; @@ -183,13 +180,13 @@ type ModalState = { teamId?: number; }; -export type PageProps = inferSSRProps; +type PageProps = { + category: AppCategories; +}; -export default function InstalledApps(props: PageProps) { - const searchParams = useCompatSearchParams(); +export default function InstalledApps({ category }: PageProps) { const { t } = useLocale(); const utils = trpc.useUtils(); - const category = searchParams?.get("category") as querySchemaType["category"]; const categoryList: AppCategories[] = Object.values(AppCategories).filter((category) => { // Exclude calendar and other from categoryList, we handle those slightly differently below return !(category in { other: null, calendar: null }); diff --git a/apps/web/modules/routing/[...pages]/pages-view.tsx b/apps/web/modules/routing/[...pages]/pages-view.tsx new file mode 100644 index 0000000000..fe95221a1b --- /dev/null +++ b/apps/web/modules/routing/[...pages]/pages-view.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { useParams } from "next/navigation"; + +import RoutingFormsRoutingConfig from "@calcom/app-store/routing-forms/pages/app-routing.config"; +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 RoutingPageType = { + getServerSideProps: AppGetServerSideProps; + // A component than can accept any properties + // eslint-disable-next-line @typescript-eslint/no-explicit-any + default: ((props: any) => JSX.Element) & + Pick; +}; + +type Found = { + notFound: false; + Component: RoutingPageType["default"]; + getServerSideProps: RoutingPageType["getServerSideProps"]; +}; + +type NotFound = { + notFound: true; +}; + +function getRoute(pages: string[]) { + const routingConfig = RoutingFormsRoutingConfig as unknown as Record; + const mainPage = pages[0]; + const appPage = routingConfig.layoutHandler || (routingConfig[mainPage] as RoutingPageType); + if (!appPage) { + return { + notFound: true, + } as NotFound; + } + return { notFound: false, Component: appPage.default, ...appPage } as Found; +} +export type PageProps = inferSSRProps; + +const RoutingFormsPage: RoutingPageType["default"] = function RoutingFormsPage(props: PageProps) { + const params = useParams(); + const _pages = params?.pages ?? []; + const pages = Array.isArray(_pages) ? _pages : _pages.split("/"); + const route = getRoute(pages); + + const componentProps = { + ...props, + pages: pages.slice(1), + }; + + if (!route || route.notFound) { + throw new Error("Route can't be undefined"); + } + return ; +}; + +export const getLayout: NonNullable<(typeof RoutingFormsPage)["getLayout"]> = (page) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const params = useParams(); + const _pages = params?.pages ?? []; + const pages = Array.isArray(_pages) ? _pages : _pages.split("/"); + const route = getRoute(pages as string[]); + + if (route.notFound) { + return null; + } + if (!route.Component.getLayout) { + return page; + } + return route.Component.getLayout(page); +}; + +export default RoutingFormsPage; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index f66a47cedf..a5e69f50ef 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -295,7 +295,7 @@ const nextConfig = { const beforeFiles = [ { source: "/forms/:formQuery*", - destination: "/apps/routing-forms/routing-link/:formQuery*", + destination: "/routing/routing-link/:formQuery*", }, { source: "/success/:path*", diff --git a/apps/web/pages/apps/[slug]/[...pages].tsx b/apps/web/pages/apps/[slug]/[...pages].tsx deleted file mode 100644 index 6de4cdfd96..0000000000 --- a/apps/web/pages/apps/[slug]/[...pages].tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { getServerSideProps } from "@lib/apps/[slug]/[...pages]/getServerSideProps"; - -import PageWrapper from "@components/PageWrapper"; - -import type { PageProps } from "~/apps/[slug]/[...pages]/pages-view"; -import PagesView, { getLayout } from "~/apps/[slug]/[...pages]/pages-view"; - -const Page = (props: PageProps) => ; - -Page.PageWrapper = PageWrapper; -Page.getLayout = getLayout; - -export { getServerSideProps }; - -export default Page; diff --git a/apps/web/pages/apps/[slug]/index.tsx b/apps/web/pages/apps/[slug]/index.tsx deleted file mode 100644 index b3419ceb9b..0000000000 --- a/apps/web/pages/apps/[slug]/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Prisma } from "@prisma/client"; -import type { GetStaticPaths } from "next"; - -import { AppRepository } from "@calcom/lib/server/repository/app"; - -import { getStaticProps } from "@lib/apps/[slug]/getStaticProps"; - -import PageWrapper from "@components/PageWrapper"; - -import type { PageProps } from "~/apps/[slug]/slug-view"; -import SingleAppPage from "~/apps/[slug]/slug-view"; - -const Page = (props: PageProps) => ; - -export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => { - let paths: { params: { slug: string } }[] = []; - - try { - const appStore = await AppRepository.findAppStore(); - paths = appStore.map(({ slug }) => ({ params: { slug } })); - } catch (e: unknown) { - if (e instanceof Prisma.PrismaClientInitializationError) { - // Database is not available at build time, but that's ok – we fall back to resolving paths on demand - } else { - throw e; - } - } - - return { - paths, - fallback: "blocking", - }; -}; - -export { getStaticProps }; - -Page.PageWrapper = PageWrapper; - -export default Page; diff --git a/apps/web/pages/apps/[slug]/setup.tsx b/apps/web/pages/apps/[slug]/setup.tsx deleted file mode 100644 index 625040aa7a..0000000000 --- a/apps/web/pages/apps/[slug]/setup.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps"; - -import PageWrapper from "@components/PageWrapper"; - -import type { PageProps } from "~/apps/[slug]/setup/setup-view"; -import SetupView from "~/apps/[slug]/setup/setup-view"; - -const Page = (props: PageProps) => ; - -Page.PageWrapper = PageWrapper; - -export { getServerSideProps }; - -export default Page; diff --git a/apps/web/pages/apps/categories/[category].tsx b/apps/web/pages/apps/categories/[category].tsx deleted file mode 100644 index 7c1a1a39d6..0000000000 --- a/apps/web/pages/apps/categories/[category].tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { AppCategories } from "@calcom/prisma/enums"; -import { isPrismaAvailableCheck } from "@calcom/prisma/is-prisma-available-check"; - -import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps"; - -import PageWrapper from "@components/PageWrapper"; - -import type { PageProps } from "~/apps/categories/[category]/category-view"; -import CategoryView from "~/apps/categories/[category]/category-view"; - -const Page = (props: PageProps) => ; -Page.PageWrapper = PageWrapper; - -export const getStaticPaths = async () => { - const paths = Object.keys(AppCategories); - const isPrismaAvailable = await isPrismaAvailableCheck(); - if (!isPrismaAvailable) { - // Database is not available at build time. Make sure we fall back to building these pages on demand - return { - paths: [], - fallback: "blocking", - }; - } - - return { - paths: paths.map((category) => ({ params: { category } })), - fallback: false, - }; -}; - -export default Page; - -export { getStaticProps }; diff --git a/apps/web/pages/apps/categories/index.tsx b/apps/web/pages/apps/categories/index.tsx deleted file mode 100644 index 54c738a5c9..0000000000 --- a/apps/web/pages/apps/categories/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { getServerSideProps } from "@lib/apps/categories/getServerSideProps"; - -import PageWrapper from "@components/PageWrapper"; - -import type { PageProps } from "~/apps/categories/categories-view"; -import Apps from "~/apps/categories/categories-view"; - -const Page = (props: PageProps) => ; -Page.PageWrapper = PageWrapper; - -export default Page; -export { getServerSideProps }; diff --git a/apps/web/pages/apps/installation/[[...step]].tsx b/apps/web/pages/apps/installation/[[...step]].tsx deleted file mode 100644 index d50152f1be..0000000000 --- a/apps/web/pages/apps/installation/[[...step]].tsx +++ /dev/null @@ -1,12 +0,0 @@ -import PageWrapper from "@components/PageWrapper"; - -import type { OnboardingPageProps } from "~/apps/installation/[[...step]]/step-view"; -import StepView from "~/apps/installation/[[...step]]/step-view"; - -const Page = (props: OnboardingPageProps) => ; - -Page.PageWrapper = PageWrapper; - -export { getServerSideProps } from "@lib/apps/installation/[[...step]]/getServerSideProps"; - -export default Page; diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx deleted file mode 100644 index 7a8e670ce1..0000000000 --- a/apps/web/pages/apps/installed/[category].tsx +++ /dev/null @@ -1,12 +0,0 @@ -import PageWrapper from "@components/PageWrapper"; - -import type { PageProps } from "~/apps/installed/[category]/installed-category-view"; -import InstalledApps from "~/apps/installed/[category]/installed-category-view"; - -const Page = (props: PageProps) => ; - -Page.PageWrapper = PageWrapper; - -export { getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps"; - -export default Page; diff --git a/apps/web/pages/apps/installed/index.tsx b/apps/web/pages/apps/installed/index.tsx deleted file mode 100644 index 02786d55a8..0000000000 --- a/apps/web/pages/apps/installed/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export { getServerSideProps } from "@lib/apps/installed/getServerSideProps"; - -function RedirectPage() { - return; -} - -export default RedirectPage; diff --git a/apps/web/pages/routing-forms/[...pages].tsx b/apps/web/pages/routing-forms/[...pages].tsx deleted file mode 100644 index 13576b185a..0000000000 --- a/apps/web/pages/routing-forms/[...pages].tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { GetServerSidePropsContext } from "next"; -import z from "zod"; - -const paramsSchema = z - .object({ - pages: z.array(z.string()), - }) - .catch({ - pages: [], - }); - -export default function RoutingForms() { - return null; -} - -export const getServerSideProps = (context: GetServerSidePropsContext) => { - const { pages } = paramsSchema.parse(context.params); - - return { - redirect: { - destination: `/apps/routing-forms/${pages.length ? pages.join("/") : ""}`, - permanent: false, - }, - }; -}; diff --git a/apps/web/pages/routing-forms/index.tsx b/apps/web/pages/routing-forms/index.tsx deleted file mode 100644 index c2032de1dd..0000000000 --- a/apps/web/pages/routing-forms/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export default function RoutingFormsIndex() { - return null; -} - -export const getServerSideProps = () => { - return { - redirect: { - destination: `/apps/routing-forms/forms`, - permanent: false, - }, - }; -}; diff --git a/apps/web/public/icons/sprite.svg b/apps/web/public/icons/sprite.svg index 36cf696c71..23e3119694 100644 --- a/apps/web/public/icons/sprite.svg +++ b/apps/web/public/icons/sprite.svg @@ -570,6 +570,12 @@ + + + + + + diff --git a/apps/web/scripts/vercel-app-router-deploy.sh b/apps/web/scripts/vercel-app-router-deploy.sh index ec1256da7f..8f1a6ab423 100755 --- a/apps/web/scripts/vercel-app-router-deploy.sh +++ b/apps/web/scripts/vercel-app-router-deploy.sh @@ -6,11 +6,6 @@ checkRoute () { # These conditionals are used to remove directories from the build that are not needed in production # This is to reduce the size of the build and prevent OOM errors -checkRoute "$APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED" app/future/apps/installed -checkRoute "$APP_ROUTER_APPS_SLUG_ENABLED" app/future/apps/\[slug\] -checkRoute "$APP_ROUTER_APPS_SLUG_SETUP_ENABLED" app/future/apps/\[slug\]/setup -checkRoute "$APP_ROUTER_APPS_CATEGORIES_ENABLED" app/future/apps/categories -checkRoute "$APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED" app/future/apps/categories/\[category\] checkRoute "$APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED" app/future/auth/forgot-password checkRoute "$APP_ROUTER_AUTH_LOGIN_ENABLED" app/future/auth/login checkRoute "$APP_ROUTER_AUTH_LOGOUT_ENABLED" app/future/auth/logout diff --git a/packages/app-store/routing-forms/api/add.ts b/packages/app-store/routing-forms/api/add.ts index 5037d70571..2b9eac638f 100644 --- a/packages/app-store/routing-forms/api/add.ts +++ b/packages/app-store/routing-forms/api/add.ts @@ -20,7 +20,7 @@ const handler: AppDeclarativeHandler = { }); }, redirect: { - url: "/apps/routing-forms/forms", + url: "/routing/forms", }, }; diff --git a/packages/app-store/routing-forms/config.json b/packages/app-store/routing-forms/config.json index 22eacf8097..60e420affe 100644 --- a/packages/app-store/routing-forms/config.json +++ b/packages/app-store/routing-forms/config.json @@ -10,7 +10,7 @@ "variant": "other", "categories": ["automation"], "publisher": "Cal.com, Inc.", - "simplePath": "/apps/routing-forms", + "simplePath": "/routing", "email": "help@cal.com", "licenseRequired": true, "teamsPlanRequired": { diff --git a/packages/app-store/routing-forms/pages/app-routing-server.config.ts b/packages/app-store/routing-forms/pages/app-routing-server.config.ts new file mode 100644 index 0000000000..e0a85fdb75 --- /dev/null +++ b/packages/app-store/routing-forms/pages/app-routing-server.config.ts @@ -0,0 +1,13 @@ +import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"; + +import { getServerSidePropsForSingleFormView as getServerSidePropsSingleForm } from "../components/getServerSidePropsSingleForm"; +import { getServerSideProps as getServerSidePropsForms } from "./forms/getServerSideProps"; +import { getServerSideProps as getServerSidePropsRoutingLink } from "./routing-link/getServerSideProps"; + +export const routingServerSidePropsConfig: Record = { + forms: getServerSidePropsForms, + "form-edit": getServerSidePropsSingleForm, + "route-builder": getServerSidePropsSingleForm, + "routing-link": getServerSidePropsRoutingLink, + reporting: getServerSidePropsSingleForm, +}; diff --git a/packages/app-store/routing-forms/pages/app-routing.config.tsx b/packages/app-store/routing-forms/pages/app-routing.config.tsx index 0c4d581746..65749ef193 100644 --- a/packages/app-store/routing-forms/pages/app-routing.config.tsx +++ b/packages/app-store/routing-forms/pages/app-routing.config.tsx @@ -1,16 +1,10 @@ //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 RoutingLink from "./routing-link/[...appPages]"; -import { getServerSideProps as getServerSidePropsRoutingLink } from "./routing-link/getServerSideProps"; const routingConfig = { "form-edit": formEdit, @@ -21,12 +15,4 @@ const routingConfig = { layoutHandler: LayoutHandler, }; -export const serverSidePropsConfig: Record = { - forms: getServerSidePropsForms, - "form-edit": getServerSidePropsSingleForm, - "route-builder": getServerSidePropsSingleForm, - "routing-link": getServerSidePropsRoutingLink, - reporting: getServerSidePropsSingleForm, -}; - export default routingConfig; diff --git a/packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx b/packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx index 2aa2e63f43..b164fcd071 100644 --- a/packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/form-edit/[...appPages].tsx @@ -481,9 +481,5 @@ export default function FormEditPage({ } FormEditPage.getLayout = (page: React.ReactElement) => { - return ( - - {page} - - ); + return {page}; }; diff --git a/packages/app-store/routing-forms/pages/reporting/[...appPages].tsx b/packages/app-store/routing-forms/pages/reporting/[...appPages].tsx index 855cdeef79..4838b4a167 100644 --- a/packages/app-store/routing-forms/pages/reporting/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/reporting/[...appPages].tsx @@ -293,9 +293,5 @@ export default function ReporterWrapper({ } ReporterWrapper.getLayout = (page: React.ReactElement) => { - return ( - - {page} - - ); + return {page}; }; diff --git a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx b/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx index d3f6935295..068fb20522 100644 --- a/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/route-builder/[...appPages].tsx @@ -1092,11 +1092,7 @@ export default function RouteBuilder({ } RouteBuilder.getLayout = (page: React.ReactElement) => { - return ( - - {page} - - ); + return {page}; }; export { getServerSideProps }; diff --git a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx index 17b5a572dd..2a6661a77e 100644 --- a/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx +++ b/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx @@ -1,6 +1,5 @@ "use client"; -import Head from "next/head"; import { useRouter } from "next/navigation"; import type { FormEvent } from "react"; import { useEffect, useRef, useState } from "react"; @@ -160,9 +159,6 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
{!customPageMessage ? ( <> - - {`${form.name} | Cal.com Forms`} -
diff --git a/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts b/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts index a0671d861e..f17ef544de 100644 --- a/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts +++ b/packages/app-store/routing-forms/pages/routing-link/getServerSideProps.ts @@ -24,9 +24,6 @@ export const getServerSideProps = async function getServerSideProps( const { currentOrgDomain } = orgDomainConfig(context.req); const isEmbed = params.appPages[1] === "embed"; - if (context.query["flag.coep"] === "true") { - context.res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); - } const form = await prisma.app_RoutingForms_Form.findFirst({ where: { 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..0599c12bb4 100644 --- a/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/routing-forms/playwright/tests/basic.e2e.ts @@ -93,7 +93,7 @@ test.describe("Routing Forms", () => { await gotoRoutingLink({ page, formId }); await expect(page.locator("text=Test Form Name")).toBeVisible(); - await page.goto(`apps/routing-forms/route-builder/${formId}`); + await page.goto(`routing/route-builder/${formId}`); await disableForm(page); await gotoRoutingLink({ page, formId }); await expect(page.getByTestId(`404-page`)).toBeVisible(); @@ -101,12 +101,12 @@ test.describe("Routing Forms", () => { test("recently added form appears first in the list", async ({ page }) => { await addForm(page, { name: "Test Form 1" }); - await page.goto(`apps/routing-forms/forms`); + await page.goto(`routing/forms`); await page.waitForSelector('[data-testid="routing-forms-list"]'); await expect(page.locator('[data-testid="routing-forms-list"] > div h1')).toHaveCount(1); await addForm(page, { name: "Test Form 2" }); - await page.goto(`apps/routing-forms/forms`); + await page.goto(`routing/forms`); await page.waitForSelector('[data-testid="routing-forms-list"]'); await expect(page.locator('[data-testid="routing-forms-list"] > div h1')).toHaveCount(2); @@ -351,7 +351,7 @@ test.describe("Routing Forms", () => { ["event-routing", "Option-2", "Option-2", "Option-2", "Option-2", "", "", "", expect.any(String)], ]); - await page.goto(`apps/routing-forms/route-builder/${routingForm.id}`); + await page.goto(`routing/route-builder/${routingForm.id}`); const [download] = await Promise.all([ // Start waiting for the download page.waitForEvent("download"), @@ -446,7 +446,7 @@ test.describe("Routing Forms", () => { test("Test preview should return correct route", async ({ page, users }) => { const user = await createUserAndLogin({ users, page }); const routingForm = user.routingForms[0]; - await page.goto(`apps/routing-forms/form-edit/${routingForm.id}`); + await page.goto(`routing/form-edit/${routingForm.id}`); await page.click('[data-testid="test-preview"]'); //event redirect @@ -696,7 +696,7 @@ async function addAllTypesOfFieldsAndSaveForm( page: Page, form: { description: string; label: string } ) { - await page.goto(`apps/routing-forms/form-edit/${formId}`); + await page.goto(`routing/form-edit/${formId}`); await page.click('[data-testid="add-field"]'); await page.fill('[data-testid="description"]', form.description); @@ -752,7 +752,7 @@ async function addAllTypesOfFieldsAndSaveForm( } async function addShortTextFieldAndSaveForm({ page, formId }: { page: Page; formId: string }) { - await page.goto(`apps/routing-forms/form-edit/${formId}`); + await page.goto(`routing/form-edit/${formId}`); await page.click('[data-testid="add-field"]'); await page.locator(".data-testid-field-type").nth(0).click(); await page.fill(`[name="fields.0.label"]`, "Short Text"); diff --git a/packages/app-store/routing-forms/playwright/tests/testUtils.ts b/packages/app-store/routing-forms/playwright/tests/testUtils.ts index 7f86e19721..1c576978d4 100644 --- a/packages/app-store/routing-forms/playwright/tests/testUtils.ts +++ b/packages/app-store/routing-forms/playwright/tests/testUtils.ts @@ -36,7 +36,7 @@ export async function addOneFieldAndDescriptionAndSaveForm( page: Page, form: { description?: string; field?: { typeIndex: number; label: string } } ) { - await page.goto(`apps/routing-forms/form-edit/${formId}`); + await page.goto(`routing/form-edit/${formId}`); await page.click('[data-testid="add-field"]'); if (form.description) { await page.fill('[data-testid="description"]', form.description); diff --git a/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx b/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx index c4fd176dc3..ac486b340f 100644 --- a/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx +++ b/packages/app-store/typeform/pages/how-to-use/[...appPages].tsx @@ -17,7 +17,7 @@ export default function HowToUse() {
  1. Make sure that you have{" "} - + Routing Forms {" "} app installed diff --git a/packages/features/shell/Shell.tsx b/packages/features/shell/Shell.tsx index 4094e515e3..2dc9184482 100644 --- a/packages/features/shell/Shell.tsx +++ b/packages/features/shell/Shell.tsx @@ -25,7 +25,7 @@ import { useAppTheme } from "./useAppTheme"; const Layout = (props: LayoutProps) => { const { banners, bannersHeight } = useBanners(); const pathname = usePathname(); - const isFullPageWithoutSidebar = pathname?.startsWith("/apps/routing-forms/reporting/"); + const isFullPageWithoutSidebar = pathname?.startsWith("/routing/reporting/"); const pageTitle = typeof props.heading === "string" && !props.title ? props.heading : props.title; const withoutSeo = props.withoutSeo ?? props.withoutMain ?? false; @@ -138,23 +138,26 @@ export function ShellMain(props: LayoutProps) { const { buttonToShow, isLoading, enableNotifications, disableNotifications } = useNotifications(); + const backPath = props.backPath; + // Replace multiple leading slashes with a single slash + // e.g., `//routing/forms` -> `/routing/forms` + const validBackPath = typeof backPath === "string" ? backPath.replace(/^\/+/, "/") : null; + return ( <> - {(props.heading || !!props.backPath) && ( + {(props.heading || !!backPath) && (
    - {!!props.backPath && ( + {!!backPath && (