diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fcc7c6ed1f..d8ca18d282 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,3 +15,5 @@ jobs: - uses: ./.github/actions/yarn-install # Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners - run: yarn test + # We could add different timezones here that we need to run our tests in + - run: TZ=America/Los_Angeles yarn test -- --timeZoneDependentTestsOnly diff --git a/.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch b/.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch new file mode 100644 index 0000000000..7cfa242404 --- /dev/null +++ b/.yarn/patches/libphonenumber-js-npm-1.10.12-51c84f8bf1.patch @@ -0,0 +1,15 @@ +diff --git a/index.cjs b/index.cjs +index b645707a3549fc298508726e404243499bbed499..f34b0891e99b275a9218e253f303f43d31ef3f73 100644 +--- a/index.cjs ++++ b/index.cjs +@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) { + // https://github.com/babel/babel/issues/2212#issuecomment-131827986 + // An alternative approach: + // https://www.npmjs.com/package/babel-plugin-add-module-exports +-exports = module.exports = min.parsePhoneNumberFromString +-exports['default'] = min.parsePhoneNumberFromString ++// exports = module.exports = min.parsePhoneNumberFromString ++// exports['default'] = min.parsePhoneNumberFromString + + // `parsePhoneNumberFromString()` named export is now considered legacy: + // it has been promoted to a default export due to being too verbose. diff --git a/.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch b/.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch new file mode 100644 index 0000000000..43667e8668 --- /dev/null +++ b/.yarn/patches/next-i18next-npm-13.3.0-bf25b0943c.patch @@ -0,0 +1,26 @@ +diff --git a/dist/commonjs/serverSideTranslations.js b/dist/commonjs/serverSideTranslations.js +index bcad3d02fbdfab8dacb1d85efd79e98623a0c257..fff668f598154a13c4030d1b4a90d5d9c18214ad 100644 +--- a/dist/commonjs/serverSideTranslations.js ++++ b/dist/commonjs/serverSideTranslations.js +@@ -36,7 +36,6 @@ var _fs = _interopRequireDefault(require("fs")); + var _path = _interopRequireDefault(require("path")); + var _createConfig = require("./config/createConfig"); + var _node = _interopRequireDefault(require("./createClient/node")); +-var _appWithTranslation = require("./appWithTranslation"); + var _utils = require("./utils"); + function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } + function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } +@@ -110,12 +109,8 @@ var serverSideTranslations = /*#__PURE__*/function () { + lng: initialLocale + })); + localeExtension = config.localeExtension, localePath = config.localePath, fallbackLng = config.fallbackLng, reloadOnPrerender = config.reloadOnPrerender; +- if (!reloadOnPrerender) { +- _context.next = 18; +- break; +- } + _context.next = 18; +- return _appWithTranslation.globalI18n === null || _appWithTranslation.globalI18n === void 0 ? void 0 : _appWithTranslation.globalI18n.reloadResources(); ++ return void 0; + case 18: + _createClient = (0, _node["default"])(_objectSpread(_objectSpread({}, config), {}, { + lng: initialLocale diff --git a/__checks__/README.md b/__checks__/README.md new file mode 100644 index 0000000000..7063c1b63a --- /dev/null +++ b/__checks__/README.md @@ -0,0 +1,4 @@ +# Checkly Tests + +Run as `yarn checkly test` +Deploy the tests as `yarn checkly deploy` diff --git a/__checks__/organization.spec.ts b/__checks__/organization.spec.ts new file mode 100644 index 0000000000..685f97e281 --- /dev/null +++ b/__checks__/organization.spec.ts @@ -0,0 +1,53 @@ +import type { Page } from "@playwright/test"; +import { test, expect } from "@playwright/test"; + +test.describe("Org", () => { + // Because these pages involve next.config.js rewrites, it's better to test them on production + test.describe("Embeds - i.cal.com", () => { + test("Org Profile Page should be embeddable", async ({ page }) => { + const response = await page.goto("https://i.cal.com/embed"); + expect(response?.status()).toBe(200); + await page.screenshot({ path: "screenshot.jpg" }); + await expectPageToBeServerSideRendered(page); + }); + + test("Org User(Peer) Page should be embeddable", async ({ page }) => { + const response = await page.goto("https://i.cal.com/peer/embed"); + expect(response?.status()).toBe(200); + await expect(page.locator("text=Peer Richelsen")).toBeVisible(); + await expectPageToBeServerSideRendered(page); + }); + + test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => { + const response = await page.goto("https://i.cal.com/peer/meet/embed"); + expect(response?.status()).toBe(200); + await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible(); + await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible(); + await expectPageToBeServerSideRendered(page); + }); + + test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => { + const response = await page.goto("https://i.cal.com/sales/embed"); + expect(response?.status()).toBe(200); + await expect(page.locator("text=Cal.com Sales")).toBeVisible(); + await expectPageToBeServerSideRendered(page); + }); + + test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => { + const response = await page.goto("https://i.cal.com/sales/hipaa/embed"); + expect(response?.status()).toBe(200); + await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible(); + await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible(); + await expectPageToBeServerSideRendered(page); + }); + }); +}); + +// This ensures that the route is actually mapped to a page that is using withEmbedSsr +async function expectPageToBeServerSideRendered(page: Page) { + expect( + await page.evaluate(() => { + return window.__NEXT_DATA__.props.pageProps.isEmbed; + }) + ).toBe(true); +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx new file mode 100644 index 0000000000..23d224b6e1 --- /dev/null +++ b/apps/web/app/layout.tsx @@ -0,0 +1,109 @@ +import type { Metadata } from "next"; +import { headers as nextHeaders, cookies as nextCookies } from "next/headers"; +import Script from "next/script"; +import React from "react"; + +import { getLocale } from "@calcom/features/auth/lib/getLocale"; +import { IS_PRODUCTION } from "@calcom/lib/constants"; + +import "../styles/globals.css"; + +export const metadata: Metadata = { + icons: { + icon: [ + { + sizes: "32x32", + url: "/api/logo?type=favicon-32", + }, + { + sizes: "16x16", + url: "/api/logo?type=favicon-16", + }, + ], + apple: { + sizes: "180x180", + url: "/api/logo?type=apple-touch-icon", + }, + other: [ + { + url: "/safari-pinned-tab.svg", + rel: "mask-icon", + }, + ], + }, + manifest: "/site.webmanifest", + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "#f9fafb" }, + { media: "(prefers-color-scheme: dark)", color: "#1C1C1C" }, + ], + other: { + "msapplication-TileColor": "#000000", + }, +}; + +const getInitialProps = async ( + url: string, + headers: ReturnType, + cookies: ReturnType +) => { + const { pathname, searchParams } = new URL(url); + + const isEmbed = pathname.endsWith("/embed") || (searchParams?.get("embedType") ?? null) !== null; + const embedColorScheme = searchParams?.get("ui.color-scheme"); + + // @ts-expect-error we cannot access ctx.req in app dir, however headers and cookies are only properties needed to extract the locale + const newLocale = await getLocale({ headers, cookies }); + let direction = "ltr"; + + try { + const intlLocale = new Intl.Locale(newLocale); + // @ts-expect-error INFO: Typescript does not know about the Intl.Locale textInfo attribute + direction = intlLocale.textInfo?.direction; + } catch (e) { + console.error(e); + } + + return { isEmbed, embedColorScheme, locale: newLocale, direction }; +}; + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const headers = nextHeaders(); + const cookies = nextCookies(); + + const fullUrl = headers.get("x-url") ?? ""; + const nonce = headers.get("x-csp") ?? ""; + + const { locale, direction, isEmbed, embedColorScheme } = await getInitialProps(fullUrl, headers, cookies); + return ( + + + {!IS_PRODUCTION && process.env.VERCEL_ENV === "preview" && ( + // eslint-disable-next-line @next/next/no-sync-scripts +