diff --git a/.env.example b/.env.example index dd9aa60e05..1dd2f66d99 100644 --- a/.env.example +++ b/.env.example @@ -295,6 +295,7 @@ AB_TEST_BUCKET_PROBABILITY=50 # whether we redirect to the future/event-types from event-types or not APP_ROUTER_EVENT_TYPES_ENABLED=1 APP_ROUTER_SETTINGS_ADMIN_ENABLED=1 +APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=1 APP_ROUTER_APPS_SLUG_ENABLED=1 APP_ROUTER_APPS_SLUG_SETUP_ENABLED=1 # whether we redirect to the future/apps/categories from /apps/categories or not diff --git a/apps/web/abTest/middlewareFactory.ts b/apps/web/abTest/middlewareFactory.ts index f6743672da..3f23bde3e4 100644 --- a/apps/web/abTest/middlewareFactory.ts +++ b/apps/web/abTest/middlewareFactory.ts @@ -6,6 +6,7 @@ import z from "zod"; const ROUTES: [URLPattern, boolean][] = [ ["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const, ["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const, + ["/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, diff --git a/apps/web/app/future/(individual-page-wrapper)/apps/installed/[category]/layout.tsx b/apps/web/app/future/(individual-page-wrapper)/apps/installed/[category]/layout.tsx new file mode 100644 index 0000000000..918ae3fa16 --- /dev/null +++ b/apps/web/app/future/(individual-page-wrapper)/apps/installed/[category]/layout.tsx @@ -0,0 +1,15 @@ +import { type ReactElement } from "react"; + +import PageWrapper from "@components/PageWrapperAppDir"; + +type EventTypesLayoutProps = { + children: ReactElement; +}; + +export default function Layout({ children }: EventTypesLayoutProps) { + return ( + + {children} + + ); +} diff --git a/apps/web/app/future/(individual-page-wrapper)/apps/installed/[category]/page.tsx b/apps/web/app/future/(individual-page-wrapper)/apps/installed/[category]/page.tsx new file mode 100644 index 0000000000..203ad830b5 --- /dev/null +++ b/apps/web/app/future/(individual-page-wrapper)/apps/installed/[category]/page.tsx @@ -0,0 +1,36 @@ +import LegacyPage from "@pages/apps/installed/[category]"; +import { _generateMetadata } from "app/_utils"; +import { notFound } from "next/navigation"; +import { z } from "zod"; + +import { APP_NAME } from "@calcom/lib/constants"; +import { AppCategories } from "@calcom/prisma/enums"; + +const querySchema = z.object({ + category: z.nativeEnum(AppCategories), +}); + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => `${t("installed_apps")} | ${APP_NAME}`, + (t) => t("manage_your_connected_apps") + ); +}; + +const getPageProps = async ({ params }: { params: Record }) => { + const p = querySchema.safeParse(params); + + if (!p.success) { + return notFound(); + } + + return { + category: p.data.category, + }; +}; + +export default async function Page({ params }: { params: Record }) { + const { category } = await getPageProps({ params }); + + return ; +} diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index fa4da2d680..dac4ef0d21 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -64,6 +64,23 @@ const middleware = async (req: NextRequest): Promise> => { requestHeaders.set("x-csp-enforce", "true"); } + if (url.pathname.startsWith("/future/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 { + validPathname = new URL(returnTo).pathname; + } catch (e) {} + + const nextUrl = url.clone(); + nextUrl.pathname = validPathname; + return NextResponse.redirect(nextUrl, { headers: requestHeaders }); + } + } + requestHeaders.set("x-pathname", url.pathname); const locale = await getLocale(req); @@ -103,6 +120,8 @@ export const config = { "/future/event-types/", "/settings/admin/:path*", "/future/settings/admin/:path*", + "/apps/installed/:category/", + "/future/apps/installed/:category/", "/apps/:slug/", "/future/apps/:slug/", "/apps/:slug/setup/", diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 2106aeb0c1..9ac7032c8c 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -516,6 +516,11 @@ const nextConfig = { destination: "/apps/installed/conferencing", permanent: true, }, + { + source: "/apps/installed", + destination: "/apps/installed/calendar", + permanent: true, + }, // OAuth callbacks when sent to localhost:3000(w would be expected) should be redirected to corresponding to WEBAPP_URL ...(process.env.NODE_ENV === "development" && // Safer to enable the redirect only when the user is opting to test out organizations diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx index 2cffde2cdf..5a207ecc13 100644 --- a/apps/web/pages/apps/installed/[category].tsx +++ b/apps/web/pages/apps/installed/[category].tsx @@ -1,3 +1,5 @@ +"use client"; + import { useReducer } from "react"; import { z } from "zod"; diff --git a/apps/web/playwright/ab-tests-redirect.e2e.ts b/apps/web/playwright/ab-tests-redirect.e2e.ts index 601c04f282..330da414c1 100644 --- a/apps/web/playwright/ab-tests-redirect.e2e.ts +++ b/apps/web/playwright/ab-tests-redirect.e2e.ts @@ -5,6 +5,33 @@ import { test } from "./lib/fixtures"; test.describe.configure({ mode: "parallel" }); test.describe("apps/ A/B tests", () => { + test("should point to the /future/apps/installed/[category]", async ({ page, users, context }) => { + await context.addCookies([ + { + name: "x-calcom-future-routes-override", + value: "1", + url: "http://localhost:3000", + }, + ]); + const user = await users.create(); + + await user.apiLogin(); + + await page.goto("/apps/installed/messaging"); + + await page.waitForLoadState(); + + const dataNextJsRouter = await page.evaluate(() => + window.document.documentElement.getAttribute("data-nextjs-router") + ); + + expect(dataNextJsRouter).toEqual("app"); + + const locator = page.getByRole("heading", { name: "Messaging" }); + + await expect(locator).toBeVisible(); + }); + test("should point to the /future/apps/[slug]", async ({ page, users, context }) => { await context.addCookies([ { diff --git a/turbo.json b/turbo.json index f6a0f7145b..4820f14d8a 100644 --- a/turbo.json +++ b/turbo.json @@ -198,6 +198,7 @@ "ALLOWED_HOSTNAMES", "ANALYZE", "API_KEY_PREFIX", + "APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED", "APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED", "APP_ROUTER_APPS_CATEGORIES_ENABLED", "APP_ROUTER_APPS_SLUG_ENABLED",