From e25fe055201c3230e1f56f0342b8413f37bd07a4 Mon Sep 17 00:00:00 2001 From: Hariom Date: Fri, 27 Dec 2024 16:45:32 +0530 Subject: [PATCH 1/2] Support acme.cal.com to be the org domain even when acme.cal.com is the WEBAPP_URL --- .env.example | 5 ++ README.md | 7 +++ apps/web/getNextjsOrgRewriteConfig.js | 46 ++++++++++++++ apps/web/getSubdomainRegExp.js | 15 ----- apps/web/next.config.js | 35 ++++++----- apps/web/pagesAndRewritePaths.js | 7 +-- apps/web/test/lib/next-config.test.ts | 89 +++++++++++++++------------ turbo.json | 3 +- 8 files changed, 131 insertions(+), 76 deletions(-) create mode 100644 apps/web/getNextjsOrgRewriteConfig.js delete mode 100644 apps/web/getSubdomainRegExp.js diff --git a/.env.example b/.env.example index 8d501dd1263720..b38028e0235a0c 100644 --- a/.env.example +++ b/.env.example @@ -415,3 +415,8 @@ REPLEXICA_API_KEY= # Comma-separated list of DSyncData.directoryId to log SCIM API requests for. It can be enabled temporarily for debugging the requests being sent to SCIM server. DIRECTORY_IDS_TO_LOG= + + +# Set this when Cal.com is used to serve only one organization's booking pages +# Read more about it in the README.md +NEXT_PUBLIC_SINGLE_ORG_SLUG= diff --git a/README.md b/README.md index 3ea2b22c626ab5..dd01a0c238ef92 100644 --- a/README.md +++ b/README.md @@ -485,6 +485,13 @@ Don't code but still want to contribute? Join our [Discussions](https://github.c - Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported. +## Single Org Mode +If you want to have booker.yourcompany.com to be the domain used for both dashboard(e.g. https://booker.yourcompany.com/event-types) and booking pages(e.g. https://booker.yourcompany.com/john.joe/15min). +- Set the `NEXT_PUBLIC_SINGLE_ORG_SLUG` environment variable to the slug of the organization you want to use. `NEXT_PUBLIC_SINGLE_ORG_SLUG=booker` +- Set the `NEXT_PUBLIC_WEBAPP_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBAPP_URL=https://booker.yourcompany.com`. +- Set the `NEXT_PUBLIC_WEBSITE_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBSITE_URL=https://booker.yourcompany.com`. +- Set the `NEXTAUTH_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXTAUTH_URL=https://booker.yourcompany.com`. + ## Integrations ### Obtaining the Google API Credentials diff --git a/apps/web/getNextjsOrgRewriteConfig.js b/apps/web/getNextjsOrgRewriteConfig.js new file mode 100644 index 00000000000000..9599c7f79f316d --- /dev/null +++ b/apps/web/getNextjsOrgRewriteConfig.js @@ -0,0 +1,46 @@ +const isSingleOrgModeEnabled = !!process.env.NEXT_PUBLIC_SINGLE_ORG_SLUG; +const orgSlugCaptureGroupName = "orgSlug"; +/** + * Returns the leftmost subdomain from a given URL. + * It needs the URL domain to have atleast two dots. + * app.cal.com -> app + * app.company.cal.com -> app + * app.company.com -> app + */ +const getLeftMostSubdomain = (url) => { + if (!url.startsWith("http:") && !url.startsWith("https:")) { + // Make it a valid URL. Mabe we can simply return null and opt-out from orgs support till the use a URL scheme. + url = `https://${url}`; + } + const _url = new URL(url); + const regex = new RegExp(/^([a-z]+\:\/{2})?((?[\w-.]+)\.[\w-]+\.\w+)$/); + //console.log(_url.hostname, _url.hostname.match(regex)); + return _url.hostname.match(regex)?.groups?.subdomain || null; +}; + +const getRegExpNotMatchingLeftMostSubdomain = (url) => { + const leftMostSubdomain = getLeftMostSubdomain(url); + const subdomain = leftMostSubdomain ? `(?!${leftMostSubdomain})[^.]+` : "[^.]+"; + return subdomain; +}; + +// For app.cal.com, it will match all domains that are not starting with "app". Technically we would want to match domains like acme.cal.com, dunder.cal.com and not app.cal.com +const getRegExpThatMatchesAllOrgDomains = (exports.getRegExpThatMatchesAllOrgDomains = ({ webAppUrl }) => { + if (isSingleOrgModeEnabled) { + console.log("Single-Org-Mode enabled - Consider all domains to be org domains"); + // It works in combination with next.config.js where in this case we use orgSlug=NEXT_PUBLIC_SINGLE_ORG_SLUG + return `.*`; + } + const subdomainRegExp = getRegExpNotMatchingLeftMostSubdomain(webAppUrl); + return `^(?<${orgSlugCaptureGroupName}>${subdomainRegExp})\\.(?!vercel\.app).*`; +}); + +const nextJsOrgRewriteConfig = { + // :orgSlug is special value which would get matching group from the regex in orgHostPath + orgSlug: process.env.NEXT_PUBLIC_SINGLE_ORG_SLUG || `:${orgSlugCaptureGroupName}`, + orgHostPath: getRegExpThatMatchesAllOrgDomains({ + webAppUrl: process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}`, + }), +}; + +exports.nextJsOrgRewriteConfig = nextJsOrgRewriteConfig; diff --git a/apps/web/getSubdomainRegExp.js b/apps/web/getSubdomainRegExp.js deleted file mode 100644 index 33f38e150c726d..00000000000000 --- a/apps/web/getSubdomainRegExp.js +++ /dev/null @@ -1,15 +0,0 @@ -const getDefaultSubdomain = (url) => { - if (!url.startsWith("http:") && !url.startsWith("https:")) { - // Make it a valid URL. Mabe we can simply return null and opt-out from orgs support till the use a URL scheme. - url = `https://${url}`; - } - const _url = new URL(url); - const regex = new RegExp(/^([a-z]+\:\/{2})?((?[\w-.]+)\.[\w-]+\.\w+)$/); - //console.log(_url.hostname, _url.hostname.match(regex)); - return _url.hostname.match(regex)?.groups?.subdomain || null; -}; -exports.getSubdomainRegExp = (url) => { - const defaultSubdomain = getDefaultSubdomain(url); - const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+"; - return subdomain; -}; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 0399dcfcb06343..071d197496ce1b 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -7,12 +7,11 @@ const { withSentryConfig } = require("@sentry/nextjs"); const { version } = require("./package.json"); const { i18n } = require("./next-i18next.config"); const { - orgHostPath, + nextJsOrgRewriteConfig, orgUserRoutePath, orgUserTypeRoutePath, orgUserTypeEmbedRoutePath, } = require("./pagesAndRewritePaths"); - if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET"); if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY"); const isOrganizationsEnabled = @@ -124,7 +123,7 @@ const matcherConfigRootPath = { has: [ { type: "host", - value: orgHostPath, + value: nextJsOrgRewriteConfig.orgHostPath, }, ], source: "/", @@ -134,7 +133,7 @@ const matcherConfigRootPathEmbed = { has: [ { type: "host", - value: orgHostPath, + value: nextJsOrgRewriteConfig.orgHostPath, }, ], source: "/embed", @@ -144,7 +143,7 @@ const matcherConfigUserRoute = { has: [ { type: "host", - value: orgHostPath, + value: nextJsOrgRewriteConfig.orgHostPath, }, ], source: orgUserRoutePath, @@ -154,7 +153,7 @@ const matcherConfigUserTypeRoute = { has: [ { type: "host", - value: orgHostPath, + value: nextJsOrgRewriteConfig.orgHostPath, }, ], source: orgUserTypeRoutePath, @@ -164,7 +163,7 @@ const matcherConfigUserTypeEmbedRoute = { has: [ { type: "host", - value: orgHostPath, + value: nextJsOrgRewriteConfig.orgHostPath, }, ], source: orgUserTypeEmbedRoutePath, @@ -287,6 +286,7 @@ const nextConfig = { return config; }, async rewrites() { + const { orgSlug } = nextJsOrgRewriteConfig; const beforeFiles = [ { source: "/forms/:formQuery*", @@ -328,23 +328,23 @@ const nextConfig = { ? [ { ...matcherConfigRootPath, - destination: "/team/:orgSlug?isOrgProfile=1", + destination: `/team/${orgSlug}?isOrgProfile=1`, }, { ...matcherConfigRootPathEmbed, - destination: "/team/:orgSlug/embed?isOrgProfile=1", + destination: `/team/${orgSlug}/embed?isOrgProfile=1`, }, { ...matcherConfigUserRoute, - destination: "/org/:orgSlug/:user", + destination: `/org/${orgSlug}/:user`, }, { ...matcherConfigUserTypeRoute, - destination: "/org/:orgSlug/:user/:type", + destination: `/org/${orgSlug}/:user/:type`, }, { ...matcherConfigUserTypeEmbedRoute, - destination: "/org/:orgSlug/:user/:type/embed", + destination: `/org/${orgSlug}/:user/:type/embed`, }, ] : []), @@ -392,6 +392,7 @@ const nextConfig = { }; }, async headers() { + const { orgSlug } = nextJsOrgRewriteConfig; // This header can be set safely as it ensures the browser will load the resources even when COEP is set. // But this header must be set only on those resources that are safe to be loaded in a cross-origin context e.g. all embeddable pages's resources const CORP_CROSS_ORIGIN_HEADER = { @@ -478,7 +479,7 @@ const nextConfig = { headers: [ { key: "X-Cal-Org-path", - value: "/team/:orgSlug", + value: `/team/${orgSlug}`, }, ], }, @@ -487,7 +488,7 @@ const nextConfig = { headers: [ { key: "X-Cal-Org-path", - value: "/org/:orgSlug/:user", + value: `/org/${orgSlug}/:user`, }, ], }, @@ -496,7 +497,7 @@ const nextConfig = { headers: [ { key: "X-Cal-Org-path", - value: "/org/:orgSlug/:user/:type", + value: `/org/${orgSlug}/:user/:type`, }, ], }, @@ -505,7 +506,7 @@ const nextConfig = { headers: [ { key: "X-Cal-Org-path", - value: "/org/:orgSlug/:user/:type/embed", + value: `/org/${orgSlug}/:user/:type/embed`, }, ], }, @@ -591,7 +592,7 @@ const nextConfig = { { type: "header", key: "host", - value: orgHostPath, + value: nextJsOrgRewriteConfig.orgHostPath, }, ], destination: "/event-types?openPlain=true", diff --git a/apps/web/pagesAndRewritePaths.js b/apps/web/pagesAndRewritePaths.js index 2b02b69dfabdb9..722d95417d24c9 100644 --- a/apps/web/pagesAndRewritePaths.js +++ b/apps/web/pagesAndRewritePaths.js @@ -1,5 +1,5 @@ const glob = require("glob"); -const { getSubdomainRegExp } = require("./getSubdomainRegExp"); +const { nextJsOrgRewriteConfig } = require("./getNextjsOrgRewriteConfig"); /** Needed to rewrite public booking page, gets all static pages but [user] */ // Pages found here are excluded from redirects in beforeFiles in next.config.js let pages = (exports.pages = glob @@ -35,10 +35,7 @@ let pages = (exports.pages = glob // [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work. // book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked -let subdomainRegExp = (exports.subdomainRegExp = getSubdomainRegExp( - process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}` -)); -exports.orgHostPath = `^(?${subdomainRegExp})\\.(?!vercel\.app).*`; +exports.nextJsOrgRewriteConfig = nextJsOrgRewriteConfig; /** * Returns a regex that matches all existing routes, virtual routes (like /forms, /router, /success etc) and nextjs special paths (_next, public) diff --git a/apps/web/test/lib/next-config.test.ts b/apps/web/test/lib/next-config.test.ts index 9330269fbc827f..04cf189f30ff8f 100644 --- a/apps/web/test/lib/next-config.test.ts +++ b/apps/web/test/lib/next-config.test.ts @@ -1,7 +1,7 @@ import { it, expect, describe, beforeAll } from "vitest"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { getSubdomainRegExp } = require("../../getSubdomainRegExp"); +import { getRegExpThatMatchesAllOrgDomains } from "../../getNextjsOrgRewriteConfig"; + // eslint-disable-next-line @typescript-eslint/no-var-requires const { match, pathToRegexp } = require("next/dist/compiled/path-to-regexp"); type MatcherRes = (path: string) => { params: Record }; @@ -30,51 +30,64 @@ beforeAll(async () => { }); describe("next.config.js - Org Rewrite", () => { - const orgHostRegExp = (subdomainRegExp: string) => - // RegExp copied from pagesAndRewritePaths.js orgHostPath. Do make the change there as well. - new RegExp(`^(?${subdomainRegExp})\\.(?!vercel\.app).*`); - - describe("Host matching based on NEXT_PUBLIC_WEBAPP_URL", () => { - it("https://app.cal.com", () => { - const subdomainRegExp = getSubdomainRegExp("https://app.cal.com"); - expect(orgHostRegExp(subdomainRegExp).exec("app.cal.com")).toEqual(null); - expect(orgHostRegExp(subdomainRegExp).exec("company.app.cal.com")?.groups?.orgSlug).toEqual("company"); - expect(orgHostRegExp(subdomainRegExp).exec("org.cal.com")?.groups?.orgSlug).toEqual("org"); - - expect(orgHostRegExp(subdomainRegExp).exec("localhost:3000")).toEqual(null); + describe("getRegExpThatMatchesAllOrgDomains", () => { + it("WEBAPP_URL=app.cal.com", () => { + const regExp = new RegExp(getRegExpThatMatchesAllOrgDomains({ webAppUrl: "app.cal.com" })); + expect(regExp.exec("acme.cal.com")?.groups?.orgSlug).toEqual("acme"); + expect(regExp.exec("app.cal.com")).toEqual(null); + // Even though it matches abc. We shouldn't match it as it isn't a subdomain of cal.com(derived from WEBAPP_URL) + // We could fix the RegExp, but that might break some unexpected self-hosted scenarios. So, we can fix it separately. + expect(regExp.exec("abc.sdafasdf.com")?.groups?.orgSlug).toEqual("abc"); }); - it("app.cal.com", () => { - const subdomainRegExp = getSubdomainRegExp("app.cal.com"); - expect(orgHostRegExp(subdomainRegExp).exec("app.cal.com")).toEqual(null); - expect(orgHostRegExp(subdomainRegExp).exec("company.app.cal.com")?.groups?.orgSlug).toEqual("company"); + it("WEBAPP_URL=https://app.cal.com", () => { + const regExp = new RegExp(getRegExpThatMatchesAllOrgDomains({ webAppUrl: "https://app.cal.com" })); + expect(regExp.exec("acme.cal.com")?.groups?.orgSlug).toEqual("acme"); + expect(regExp.exec("app.cal.com")).toEqual(null); + + // This approach though not used by managed cal.com, but might be in use by self-hosted users. + expect(regExp.exec("acme.app.cal.com")?.groups?.orgSlug).toEqual("acme"); + + // TODO: Even though it gives abc orgSlug. We shouldn't match it as it isn't a subdomain of cal.com(derived from WEBAPP_URL) + // We could fix the RegExp, but that might break some unexpected self-hosted scenarios. So, we can fix it separately. + expect(regExp.exec("abc.sdafasdf.com")?.groups?.orgSlug).toEqual("abc"); }); - it("https://calcom.app.company.com", () => { - const subdomainRegExp = getSubdomainRegExp("https://calcom.app.company.com"); - expect(orgHostRegExp(subdomainRegExp).exec("calcom.app.company.com")).toEqual(null); - expect(orgHostRegExp(subdomainRegExp).exec("acme.calcom.app.company.com")?.groups?.orgSlug).toEqual( - "acme" + it("WEBAPP_URL=https://booker.dashboard.company.com", () => { + const regExp = new RegExp( + getRegExpThatMatchesAllOrgDomains({ webAppUrl: "https://booker.dashboard.company.com" }) ); + + // This approach though not used by managed cal.com, but might be in use by self-hosted users. + expect(regExp.exec("acme.booker.dashboard.company.com")?.groups?.orgSlug).toEqual("acme"); + expect(regExp.exec("booker.dashboard.company.com")).toEqual(null); }); - it("https://calcom.example.com", () => { - const subdomainRegExp = getSubdomainRegExp("https://calcom.example.com"); - expect(orgHostRegExp(subdomainRegExp).exec("calcom.example.com")).toEqual(null); - expect(orgHostRegExp(subdomainRegExp).exec("acme.calcom.example.com")?.groups?.orgSlug).toEqual("acme"); - // The following also matches which causes anything other than the domain in NEXT_PUBLIC_WEBAPP_URL to give 404 - expect(orgHostRegExp(subdomainRegExp).exec("some-other.company.com")?.groups?.orgSlug).toEqual( - "some-other" + it("WEBAPP_URL=http://app.cal.local:3000", () => { + const regExp = new RegExp( + getRegExpThatMatchesAllOrgDomains({ webAppUrl: "http://app.cal.local:3000" }) ); + expect(regExp.exec("acme.cal.local:3000")?.groups?.orgSlug).toEqual("acme"); + expect(regExp.exec("acme.app.cal.local:3000")?.groups?.orgSlug).toEqual("acme"); + expect(regExp.exec("app.cal.local:3000")).toEqual(null); }); - it("Should ignore Vercel preview URLs", () => { - const subdomainRegExp = getSubdomainRegExp("https://cal-xxxxxxxx-cal.vercel.app"); - expect( - orgHostRegExp(subdomainRegExp).exec("https://cal-xxxxxxxx-cal.vercel.app") - ).toMatchInlineSnapshot("null"); - expect(orgHostRegExp(subdomainRegExp).exec("cal-xxxxxxxx-cal.vercel.app")).toMatchInlineSnapshot( - "null" - ); + + it("Vercel Preview special handling - vercel.app. Cal.com deployed on vercel apps have different subdomains, so we can't consider them org domains", () => { + const regExp = new RegExp(getRegExpThatMatchesAllOrgDomains({ webAppUrl: "http://app.vercel.app" })); + // It is not matching on vercel.app but would have matched in any other case + expect(regExp.exec("acme.vercel.app")).toEqual(null); + expect(regExp.exec("app.vercel.app")).toEqual(null); + }); + + describe("NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED=1", () => { + process.env.NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED = "1"; + it("WEBAPP_URL=http://app.cal.local:3000", () => { + const regExp = new RegExp( + getRegExpThatMatchesAllOrgDomains({ webAppUrl: "http://app.cal.local:3000" }) + ); + expect(regExp.exec("acme.cal.local:3000")?.groups?.orgSlug).toEqual("acme"); + expect(regExp.exec("app.cal.local:3000")).toEqual(null); + }); }); }); diff --git a/turbo.json b/turbo.json index 0cc8e703e48dbb..c78883e1e8b0c9 100644 --- a/turbo.json +++ b/turbo.json @@ -457,6 +457,7 @@ "VAPID_PRIVATE_KEY", "HUDDLE01_API_TOKEN", "REPLEXICA_API_KEY", - "DIRECTORY_IDS_TO_LOG" + "DIRECTORY_IDS_TO_LOG", + "NEXT_PUBLIC_SINGLE_ORG_SLUG" ] } From 0cf66289cc393bc85bc190fbae49facade230e24 Mon Sep 17 00:00:00 2001 From: Hariom Date: Sat, 28 Dec 2024 12:23:41 +0530 Subject: [PATCH 2/2] Make the domain root serve dashboard --- README.md | 2 + apps/web/getNextjsOrgRewriteConfig.js | 2 + apps/web/next.config.js | 151 ++++++++++++++------------ 3 files changed, 85 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index dd01a0c238ef92..6f481c5414ae2a 100644 --- a/README.md +++ b/README.md @@ -492,6 +492,8 @@ If you want to have booker.yourcompany.com to be the domain used for both dashbo - Set the `NEXT_PUBLIC_WEBSITE_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBSITE_URL=https://booker.yourcompany.com`. - Set the `NEXTAUTH_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXTAUTH_URL=https://booker.yourcompany.com`. +Note: It causes root to serve the dashboard and not the organization profile page which shows all bookable users in the organization. + ## Integrations ### Obtaining the Google API Credentials diff --git a/apps/web/getNextjsOrgRewriteConfig.js b/apps/web/getNextjsOrgRewriteConfig.js index 9599c7f79f316d..ffb87f5e339480 100644 --- a/apps/web/getNextjsOrgRewriteConfig.js +++ b/apps/web/getNextjsOrgRewriteConfig.js @@ -41,6 +41,8 @@ const nextJsOrgRewriteConfig = { orgHostPath: getRegExpThatMatchesAllOrgDomains({ webAppUrl: process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}`, }), + // We disable root path rewrite because we want to serve dashboard on root path + disableRootPathRewrite: isSingleOrgModeEnabled, }; exports.nextJsOrgRewriteConfig = nextJsOrgRewriteConfig; diff --git a/apps/web/next.config.js b/apps/web/next.config.js index e04afbdd21f7e7..c4d064a060fd2f 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -118,55 +118,60 @@ if (process.env.ANALYZE === "true") { } plugins.push(withAxiom); +const orgDomainMatcherConfig = { + root: nextJsOrgRewriteConfig.disableRootPathRewrite + ? null + : { + has: [ + { + type: "host", + value: nextJsOrgRewriteConfig.orgHostPath, + }, + ], + source: "/", + }, -const matcherConfigRootPath = { - has: [ - { - type: "host", - value: nextJsOrgRewriteConfig.orgHostPath, - }, - ], - source: "/", -}; - -const matcherConfigRootPathEmbed = { - has: [ - { - type: "host", - value: nextJsOrgRewriteConfig.orgHostPath, - }, - ], - source: "/embed", -}; + rootEmbed: nextJsOrgRewriteConfig.disableRootEmbedPathRewrite + ? null + : { + has: [ + { + type: "host", + value: nextJsOrgRewriteConfig.orgHostPath, + }, + ], + source: "/embed", + }, -const matcherConfigUserRoute = { - has: [ - { - type: "host", - value: nextJsOrgRewriteConfig.orgHostPath, - }, - ], - source: orgUserRoutePath, -}; + user: { + has: [ + { + type: "host", + value: nextJsOrgRewriteConfig.orgHostPath, + }, + ], + source: orgUserRoutePath, + }, -const matcherConfigUserTypeRoute = { - has: [ - { - type: "host", - value: nextJsOrgRewriteConfig.orgHostPath, - }, - ], - source: orgUserTypeRoutePath, -}; + userType: { + has: [ + { + type: "host", + value: nextJsOrgRewriteConfig.orgHostPath, + }, + ], + source: orgUserTypeRoutePath, + }, -const matcherConfigUserTypeEmbedRoute = { - has: [ - { - type: "host", - value: nextJsOrgRewriteConfig.orgHostPath, - }, - ], - source: orgUserTypeEmbedRoutePath, + userTypeEmbed: { + has: [ + { + type: "host", + value: nextJsOrgRewriteConfig.orgHostPath, + }, + ], + source: orgUserTypeEmbedRoutePath, + }, }; /** @type {import("next").NextConfig} */ @@ -322,29 +327,33 @@ const nextConfig = { // These rewrites are other than booking pages rewrites and so that they aren't redirected to org pages ensure that they happen in beforeFiles ...(isOrganizationsEnabled ? [ + orgDomainMatcherConfig.root + ? { + ...orgDomainMatcherConfig.root, + destination: `/team/${orgSlug}?isOrgProfile=1`, + } + : null, + orgDomainMatcherConfig.rootEmbed + ? { + ...orgDomainMatcherConfig.rootEmbed, + destination: `/team/${orgSlug}/embed?isOrgProfile=1`, + } + : null, { - ...matcherConfigRootPath, - destination: `/team/${orgSlug}?isOrgProfile=1`, - }, - { - ...matcherConfigRootPathEmbed, - destination: `/team/${orgSlug}/embed?isOrgProfile=1`, - }, - { - ...matcherConfigUserRoute, + ...orgDomainMatcherConfig.user, destination: `/org/${orgSlug}/:user`, }, { - ...matcherConfigUserTypeRoute, + ...orgDomainMatcherConfig.userType, destination: `/org/${orgSlug}/:user/:type`, }, { - ...matcherConfigUserTypeEmbedRoute, + ...orgDomainMatcherConfig.userTypeEmbed, destination: `/org/${orgSlug}/:user/:type/embed`, }, ] : []), - ]; + ].filter(Boolean); let afterFiles = [ { @@ -470,17 +479,19 @@ const nextConfig = { ], ...(isOrganizationsEnabled ? [ + orgDomainMatcherConfig.root + ? { + ...orgDomainMatcherConfig.root, + headers: [ + { + key: "X-Cal-Org-path", + value: `/team/${orgSlug}`, + }, + ], + } + : null, { - ...matcherConfigRootPath, - headers: [ - { - key: "X-Cal-Org-path", - value: `/team/${orgSlug}`, - }, - ], - }, - { - ...matcherConfigUserRoute, + ...orgDomainMatcherConfig.user, headers: [ { key: "X-Cal-Org-path", @@ -489,7 +500,7 @@ const nextConfig = { ], }, { - ...matcherConfigUserTypeRoute, + ...orgDomainMatcherConfig.userType, headers: [ { key: "X-Cal-Org-path", @@ -498,7 +509,7 @@ const nextConfig = { ], }, { - ...matcherConfigUserTypeEmbedRoute, + ...orgDomainMatcherConfig.userTypeEmbed, headers: [ { key: "X-Cal-Org-path", @@ -508,7 +519,7 @@ const nextConfig = { }, ] : []), - ]; + ].filter(Boolean); }, async redirects() { const redirects = [