diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 31ac347fd6..56def4c1c8 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -35,6 +35,12 @@ function toRunBeforeReactOnClient() { const namespace = url.searchParams.get("embed"); return namespace; }; + + window.isPageOptimizedForEmbed = () => { + // Those pages are considered optimized, which know at backend that they are rendering for embed. + // Such pages can be shown straightaway without a loader for a better embed experience + return location.pathname.includes("forms/"); + }; } class MyDocument extends Document { @@ -76,7 +82,6 @@ class MyDocument extends Document { /> - {/* Keep the embed hidden till parent initializes and gives it the appropriate styles */}
@@ -86,7 +91,9 @@ class MyDocument extends Document { dangerouslySetInnerHTML={{ __html: ` if (isEmbed()) { - document.body.style.display="none"; + if(!isPageOptimizedForEmbed()) { + document.body.style.display="none"; + } document.body.style.background="transparent"; }`, }} diff --git a/packages/embeds/embed-core/playwright/config/playwright.config.ts b/packages/embeds/embed-core/playwright/config/playwright.config.ts index bb6ec2054e..bc387a9fe0 100644 --- a/packages/embeds/embed-core/playwright/config/playwright.config.ts +++ b/packages/embeds/embed-core/playwright/config/playwright.config.ts @@ -168,8 +168,11 @@ expect.extend({ initialValuesSet: window.initialValuesSet, }; }); + const isOptimizedPage = u.pathname.includes("forms/"); expect(initialValuesSet).toBe(true); - expect(displayBefore).toBe("none"); + if (!isOptimizedPage) { + expect(displayBefore).toBe("none"); + } expect(backgroundBefore).toBe("transparent"); const { display: displayAfter, background: backgroundAfter } = await iframe.evaluate(() => { diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index 9082e3b631..da8daf6f06 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -19,6 +19,7 @@ declare global { resetEmbedStatus: () => void; getEmbedNamespace: () => string | null; getEmbedTheme: () => "dark" | "light" | null; + isPageOptimizedForEmbed: (calLink: string) => boolean; } } @@ -221,7 +222,8 @@ export const useIsEmbed = () => { // We can't simply return isEmbed() from this method. // isEmbed() returns different values on server and browser, which messes up the hydration. // TODO: We can avoid using document.URL and instead use Router. - const [isEmbed, setIsEmbed] = useState(null); + const router = useRouter(); + const [isEmbed, setIsEmbed] = useState(typeof router.query.embed === "string"); useEffect(() => { const namespace = getNamespace(); const _isValidNamespace = isValidNamespace(namespace); @@ -283,7 +285,11 @@ export const methods = { return; } // No UI change should happen in sight. Let the parent height adjust and in next cycle show it. - requestAnimationFrame(unhideBody); + // HACK: React components take time to commit their updates of isEmbed=true to DOM, so add this random 500ms delay which seems to work + // TODO: Optimize page by page and then remove this unnecessary delay + setTimeout(() => { + unhideBody(); + }, 500); sdkActionManager?.fire("linkReady", {}); }); }, diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index acdff40ca4..92dc0b4cb3 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -20,6 +20,7 @@ type Namespace = string; type Config = { origin: string; debug?: boolean; + uiDebug?: boolean; }; const globalCal = (window as CalWindow).Cal; @@ -184,6 +185,9 @@ export class Cal { if (config.debug) { urlInstance.searchParams.set("debug", "" + config.debug); } + if (config.uiDebug) { + iframe.style.border = "1px solid green"; + } // Merge searchParams from config onto the URL which might have query params already // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -199,10 +203,11 @@ export class Cal { if (typeof namespaceOrConfig !== "string") { config = (namespaceOrConfig || {}) as Config; } - if (config?.origin) { - this.__config.origin = config.origin; + const { origin, ...restConfig } = config; + if (origin) { + this.__config.origin = origin; } - this.__config.debug = config.debug; + this.__config = { ...this.__config, ...restConfig }; } getConfig() { @@ -242,8 +247,8 @@ export class Cal { }, }, }); + const isCalPageOptimized = calLink.includes("forms/"); config = config || {}; - // Keeping auto-scroll disabled for two reasons: // - If user scrolls the content to an appropriate position, it again resets it to default position which might not be for the liking of the user // - Sometimes, the position can be wrong(e.g. if there is a fixed position header on top coming above the iframe content). @@ -263,7 +268,9 @@ export class Cal { throw new Error("Element not found"); } const template = document.createElement("template"); - template.innerHTML = ``; + template.innerHTML = ``; this.inlineEl = template.content.children[0]; (this.inlineEl as unknown as any).__CalAutoScroll = config.__autoScroll; this.inlineEl.appendChild(iframe); @@ -483,8 +490,11 @@ export class Cal { }); this.actionManager.on("__routeChanged", () => { - if (this.inlineEl && (this.inlineEl as unknown as any).__CalAutoScroll) { - this.inlineEl.scrollIntoView(); + const { top, height } = this.inlineEl.getBoundingClientRect(); + // Try to readjust and scroll into view if more than 25% is hidden. + // Otherwise we assume that user might have positioned the content appropriately already + if (top < 0 && Math.abs(top / height) >= 0.25) { + this.inlineEl.scrollIntoView({ behavior: "smooth" }); } }); diff --git a/packages/embeds/embed-react/README.md b/packages/embeds/embed-react/README.md index b5fb1f54d2..8b59251836 100644 --- a/packages/embeds/embed-react/README.md +++ b/packages/embeds/embed-react/README.md @@ -4,6 +4,17 @@ Embed Cal Link as a React Component To know how to use it, follow the steps at +## Development + +Following command starts a hot reloading server +`yarn dev` + +If you are working with embed on website, don't forget to do `yarn build` after every change. + +## Running Tests + +Runs tests and updates the snapshots. Right now we don't care about snapshots +`yarn embed-tests-quick --update-snapshots` TODO - Playwright tests. diff --git a/packages/embeds/embed-react/src/Cal.tsx b/packages/embeds/embed-react/src/Cal.tsx index 1a78cd6610..e2b77be5a2 100644 --- a/packages/embeds/embed-react/src/Cal.tsx +++ b/packages/embeds/embed-react/src/Cal.tsx @@ -6,12 +6,16 @@ import useEmbed from "./useEmbed"; type CalProps = { calOrigin?: string; calLink: string; + initConfig?: { + debug?: boolean; + uiDebug?: boolean; + }; config?: any; embedJsUrl?: string; } & React.HTMLAttributes; const Cal = function Cal(props: CalProps) { - const { calLink, calOrigin, config, embedJsUrl, ...restProps } = props; + const { calLink, calOrigin, config, initConfig = {}, embedJsUrl, ...restProps } = props; if (!calLink) { throw new Error("calLink is required"); } @@ -24,20 +28,19 @@ const Cal = function Cal(props: CalProps) { } initializedRef.current = true; const element = ref.current; - let initConfig = {}; - if (calOrigin) { - (initConfig as any).origin = calOrigin; - } - Cal("init", initConfig); + Cal("init", { + ...initConfig, + origin: calOrigin, + }); Cal("inline", { elementOrSelector: element, calLink, config, }); - }, [Cal, calLink, config, calOrigin]); + }, [Cal, calLink, config, calOrigin, initConfig]); if (!Cal) { - return
Loading {calLink}
; + return null; } return
; diff --git a/packages/embeds/embed-react/test-cal.tsx b/packages/embeds/embed-react/test-cal.tsx index 4b9fb65807..a6b4aa9bd7 100644 --- a/packages/embeds/embed-react/test-cal.tsx +++ b/packages/embeds/embed-react/test-cal.tsx @@ -45,9 +45,10 @@ function App() { notes: "Test Meeting", guests: ["janedoe@gmail.com"], theme: "dark", - }}> + }} + /> ); } -ReactDom.render(, document.getElementById("root")); +ReactDom.render(, document.getElementById("root"));