The links that can be directly given to embed should pre-render(Either SSG/SSR) (#4975)

* Embed SSG and consistently pass embedType query param across pages

* Embed fixes

* Code cleanup

* Add main class which tells embed which helps in identifying which area is outside the main content

* remove any special optimization handling for routing forms

* Add comments

* Small fixes

* Fix broken team booking page in embed

* Fix Fallback message dark theme

* TS Fixes

* Fixes

* Fix tests

* Remove not required code

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Hariom BalharaandPeer Richelsen authored and GitHub committed 2022-10-19 21:25:03 +00:00
1 parent 477941545e
commit 7756b9c3a1
31 files changed
+175 -89

No files matched your search

@@ -65,7 +65,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
query: Record<string, string | number | string[] | undefined>;
};
const bookingUrl: BookingURL = {
pathname: "book",
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
query: {
...router.query,
date: dayjs(slot.time).format(),
@@ -63,7 +63,7 @@ export default function CancelBooking(props: Props) {
<Button
color="secondary"
className="border-0 sm:border"
onClick={() => router.push("/reschedule/" + booking?.uid)}>
onClick={() => router.push(`/reschedule/${booking?.uid}`)}>
{t("reschedule_this")}
</Button>
</div>
@@ -304,11 +304,11 @@ const timeFormatTotimeFormatString = (timeFormat?: number | null) => {
return timeFormat === 24 ? "HH:mm" : "h:mma";
};
const AvailabilityPage = ({ profile, eventType }: Props) => {
const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
const { data: user } = trpc.useQuery(["viewer.me"]);
const timeFormatFromProfile = timeFormatTotimeFormatString(user?.timeFormat);
const router = useRouter();
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(restProps.isEmbed);
const query = dateQuerySchema.parse(router.query);
const { rescheduleUid } = query;
useTheme(profile.theme);
@@ -1,6 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { EventTypeCustomInputType, WorkflowActions } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import { useMutation } from "@tanstack/react-query";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useSession } from "next-auth/react";
@@ -84,9 +83,10 @@ const BookingPage = ({
recurringEventCount,
hasHashedBookingLink,
hashedLink,
...restProps
}: BookingPageProps) => {
const { t, i18n } = useLocale();
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(restProps.isEmbed);
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const router = useRouter();
+21
View File
@@ -0,0 +1,21 @@
import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next";
export type EmbedProps = {
isEmbed?: boolean;
};
export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
return async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EmbedProps>> => {
const ssrResponse = await getServerSideProps(context);
if (!("props" in ssrResponse)) {
return ssrResponse;
}
return {
...ssrResponse,
props: {
...ssrResponse.props,
isEmbed: true,
},
};
};
}
+7
View File
@@ -23,6 +23,13 @@ const middleware: NextMiddleware = async (req) => {
}
}
// Ensure that embed query param is there in when /embed is added.
// query param is the way in which client side code knows that it is in embed mode.
if (url.pathname.endsWith("/embed") && typeof url.searchParams.get("embed") !== "string") {
url.searchParams.set("embed", "");
return NextResponse.redirect(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");
+2 -2
View File
@@ -134,8 +134,8 @@ const nextConfig = {
destination: "/api/user/avatar?teamname=:teamname",
},
{
source: "/forms/:formId",
destination: "/apps/routing-forms/routing-link/:formId",
source: "/forms/:formQuery*",
destination: "/apps/routing-forms/routing-link/:formQuery*",
},
{
source: "/router",
+5 -2
View File
@@ -30,6 +30,7 @@ import { BadgeCheckIcon, Icon } from "@calcom/ui/Icon";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvatarGroup from "@components/ui/AvatarGroup";
import { AvatarSSR } from "@components/ui/AvatarSSR";
@@ -38,7 +39,7 @@ import { ssrInit } from "@server/lib/ssr";
const EventTypeDescription = dynamic(() => import("@calcom/ui/v2/modules/event-types/EventTypeDescription"));
const HeadSeo = dynamic(() => import("@components/seo/head-seo"));
export default function User(props: inferSSRProps<typeof getServerSideProps>) {
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(user.theme);
@@ -86,7 +87,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
</ul>
);
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(props.isEmbed);
const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem");
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
@@ -287,6 +288,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!users.length) {
return {
notFound: true,
} as {
notFound: true;
};
}
const isDynamicGroup = users.length > 1;
+2 -1
View File
@@ -11,10 +11,11 @@ import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps>;
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps> & EmbedProps;
export default function Type(props: AvailabilityPageProps) {
const { t } = useLocale();
+21
View File
@@ -0,0 +1,21 @@
import { GetStaticPropsContext } from "next";
import { getStaticProps as _getStaticProps } from "../[type]";
export { getStaticPaths } from "../[type]";
export { default } from "../[type]";
export const getStaticProps = async (context: GetStaticPropsContext) => {
const staticResponse = await _getStaticProps(context);
if (staticResponse.notFound) {
return staticResponse;
}
return {
...staticResponse,
props: {
...staticResponse.props,
isEmbed: true,
},
};
};
+1
View File
@@ -197,6 +197,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
isDynamicGroupBooking,
hasHashedBookingLink: false,
hashedLink: null,
isEmbed: typeof context.query.embed === "string",
},
};
}
+7
View File
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[user]";
export { default } from "../[user]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
+21 -26
View File
@@ -3,18 +3,13 @@ import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps
type Props = Record<string, unknown> & DocumentProps;
function toRunBeforeReactOnClient() {
const calEmbedMode = location.search.includes("embed=");
try {
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
window.sessionStorage.setItem("calEmbedMode", String(calEmbedMode));
} catch (e) {}
const calEmbedMode =
location.search.includes("embed=") ||
/* Iframe Name */
window.name.includes("cal-embed");
window.isEmbed = () => {
try {
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
return window.sessionStorage.getItem("calEmbedMode") === "true";
} catch (e) {}
// If we can't use sessionStorage to retrieve embed mode, just use the variable. It would fail to detect embed if page in iframe reloads without embed query param in it.
// Once an embed mode always an embed mode
return calEmbedMode;
};
@@ -45,14 +40,14 @@ function toRunBeforeReactOnClient() {
class MyDocument extends Document<Props> {
static async getInitialProps(ctx: DocumentContext) {
const isEmbed = ctx.asPath?.includes("/embed") || ctx.asPath?.includes("embedType=");
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
return { isEmbed, ...initialProps };
}
render() {
const { locale } = this.props.__NEXT_DATA__;
const dir = locale === "ar" || locale === "he" ? "rtl" : "ltr";
return (
<Html lang={locale} dir={dir}>
<Head>
@@ -82,22 +77,22 @@ class MyDocument extends Document<Props> {
/>
</Head>
<body className="dark:bg-darkgray-50 desktop-transparent bg-gray-100">
<body
className="dark:bg-darkgray-50 desktop-transparent bg-gray-100"
style={
this.props.isEmbed
? {
background: "transparent",
// Keep the embed hidden till parent initializes and
// - gives it the appropriate styles if UI instruction is there.
// - gives iframe the appropriate height(equal to document height) which can only be known after loading the page once in browser.
// - Tells iframe which mode it should be in (dark/light) - if there is a a UI instruction for that
visibility: "hidden",
}
: {}
}>
<Main />
<NextScript />
{/* In case of Embed we want background to be transparent so that it merges into the website seamlessly. Also, we keep the body hidden here and embed logic would take care of showing the body when it's ready */}
{/* We are doing it on browser and not on server because there are some pages which are not SSRd */}
<script
dangerouslySetInnerHTML={{
__html: `
if (isEmbed()) {
if(!isPageOptimizedForEmbed()) {
document.body.style.display="none";
}
document.body.style.background="transparent";
}`,
}}
/>
</body>
</Html>
);
+8 -1
View File
@@ -10,12 +10,13 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { getWorkingHours } from "@lib/availability";
import { GetBookingType } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssrInit } from "@server/lib/ssr";
export type DynamicAvailabilityPageProps = inferSSRProps<typeof getServerSideProps>;
export type DynamicAvailabilityPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function Type(props: DynamicAvailabilityPageProps) {
return <AvailabilityPage {...props} />;
@@ -48,11 +49,15 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!userId)
return {
notFound: true,
} as {
notFound: true;
};
if (hashedLink?.eventType.slug !== slug)
return {
notFound: true,
} as {
notFound: true;
};
const users = await prisma.user.findMany({
@@ -92,6 +97,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!users || !users.length) {
return {
notFound: true,
} as {
notFound: true;
};
}
+7
View File
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[slug]";
export { default } from "../[slug]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
+1 -1
View File
@@ -2,7 +2,6 @@ import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { bookEventTypeSelect } from "@calcom/prisma/selects";
@@ -124,6 +123,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
isDynamicGroupBooking: false,
hasHashedBookingLink: true,
hashedLink: link,
isEmbed: typeof context.query.embed === "string",
},
};
}
+1 -1
View File
@@ -442,7 +442,7 @@ export default function Success(props: SuccessProps) {
{!props.recurringBookings && (
<span className="text-bookinglight inline text-gray-700">
<span className="underline">
<Link href={"/reschedule/" + bookingInfo?.uid}>{t("reschedule")}</Link>
<Link href={`/reschedule/${bookingInfo?.uid}`}>{t("reschedule")}</Link>
</span>
<span className="mx-2">{t("or_lowercase")}</span>
</span>
+4 -5
View File
@@ -83,7 +83,6 @@ function TeamPage({ team }: TeamPageProps) {
return (
<div>
<HeadSeo title={teamName} description={teamName} />
<HeadSeo
title={teamName}
description={teamName}
@@ -92,7 +91,7 @@ function TeamPage({ team }: TeamPageProps) {
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
}}
/>
<div className="dark:bg-darkgray-50 h-screen rounded-md bg-gray-100 px-4 pt-12 pb-12">
<main className="dark:bg-darkgray-50 mx-auto max-w-3xl rounded-md bg-gray-100 px-4 pt-12 pb-12">
<div className="max-w-96 mx-auto mb-8 text-center">
<Avatar alt={teamName} imageSrc={getPlaceholderAvatar(team.logo, team.name)} size="lg" />
<p className="font-cal dark:text-darkgray-900 mb-2 text-2xl tracking-wider text-gray-900">
@@ -117,7 +116,7 @@ function TeamPage({ team }: TeamPageProps) {
</div>
</div>
<aside className="mt-8 mb-16 flex justify-center text-center dark:text-white">
<aside className="mt-8 flex justify-center text-center dark:text-white">
<Button
color="minimal"
EndIcon={Icon.FiArrowRight}
@@ -129,7 +128,7 @@ function TeamPage({ team }: TeamPageProps) {
</aside>
</div>
)}
</div>
</main>
</div>
);
}
@@ -139,7 +138,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const team = await getTeamWithMembers(undefined, slug);
if (!team) return { notFound: true };
if (!team) return { notFound: true } as { notFound: true };
const members = team.members.filter((member) => member.plan !== UserPlan.FREE);
+4 -1
View File
@@ -10,12 +10,13 @@ import { asStringOrNull } from "@lib/asStringOrNull";
import { getWorkingHours } from "@lib/availability";
import getBooking, { GetBookingType } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssgInit } from "@server/lib/ssg";
export type AvailabilityTeamPageProps = inferSSRProps<typeof getServerSideProps>;
export type AvailabilityTeamPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function TeamType(props: AvailabilityTeamPageProps) {
return <AvailabilityPage {...props} />;
@@ -99,6 +100,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!team || team.eventTypes.length != 1) {
return {
notFound: true,
} as {
notFound: true;
};
}
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
export { default } from "../[type]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
+1 -3
View File
@@ -3,7 +3,6 @@ import { JSONObject } from "superjson/dist/types";
import { LocationObject, privacyFilteredLocations } from "@calcom/app-store/locations";
import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
@@ -15,8 +14,6 @@ import BookingPage from "@components/booking/pages/BookingPage";
export type TeamBookingPageProps = inferSSRProps<typeof getServerSideProps>;
export default function TeamBookingPage(props: TeamBookingPageProps) {
const { t } = useLocale();
return <BookingPage {...props} />;
}
@@ -141,6 +138,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
isDynamicGroupBooking: false,
hasHashedBookingLink: false,
hashedLink: null,
isEmbed: typeof context.query.embed === "string",
},
};
}
+7
View File
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[slug]";
export { default } from "../[slug]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
+1 -1
View File
@@ -13,7 +13,7 @@ export const createEmbedsFixture = (page: Page) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyDisplay = document.body.style.display;
window.initialBodyVisibility = document.body.style.visibility;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
@@ -1,10 +1,11 @@
import Head from "next/head";
import { useRouter } from "next/router";
import { useState, useRef, FormEvent } from "react";
import { useEffect } from "react";
import { Toaster } from "react-hot-toast";
import { v4 as uuidv4 } from "uuid";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -22,10 +23,10 @@ import { processRoute } from "../../lib/processRoute";
import { Response, Route } from "../../types/types";
import { getQueryBuilderConfig } from "../route-builder/[...appPages]";
function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>) {
function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getServerSideProps>) {
const [customPageMessage, setCustomPageMessage] = useState<Route["action"]["value"]>("");
const formFillerIdRef = useRef(uuidv4());
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(restProps.isEmbed);
useTheme(profile.theme);
useExposePlanGlobally(profile.plan);
// TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews
@@ -53,6 +54,11 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
decidedActionRef.current = decidedAction;
};
useEffect(() => {
// Custom Page doesn't actually change Route, so fake it so that embed can adjust the scroll to make the content visible
sdkActionManager?.fire("__routeChanged", {});
}, [customPageMessage]);
const responseMutation = trpc.useMutation("viewer.app_routing_forms.public.response", {
onSuccess: () => {
const decidedAction = decidedActionRef.current;
@@ -186,8 +192,8 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
) : (
<div className="mx-auto my-0 max-w-3xl md:my-24">
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
<div className="-mx-4 rounded-sm border border-neutral-200 bg-white p-4 py-6 sm:mx-0 sm:px-8">
<div>{customPageMessage}</div>
<div className="main dark:bg-darkgray-100 sm:dark:border-darkgray-300 -mx-4 rounded-md border border-neutral-200 bg-white p-4 py-6 sm:mx-0 sm:px-8">
<div className="dark:text-white">{customPageMessage}</div>
</div>
</div>
</div>
@@ -197,8 +203,8 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
);
}
export default function RoutingLink({ form, profile }: inferSSRProps<typeof getServerSideProps>) {
return <RoutingForm form={form} profile={profile} />;
export default function RoutingLink(props: inferSSRProps<typeof getServerSideProps>) {
return <RoutingForm {...props} />;
}
RoutingLink.isThemeSupported = true;
@@ -214,12 +220,13 @@ export const getServerSideProps = async function getServerSideProps(
};
}
const formId = params.appPages[0];
if (!formId || params.appPages.length > 1) {
console.log(params.appPages);
if (!formId || params.appPages.length > 2) {
return {
notFound: true,
};
}
const isEmbed = params.appPages[1] === "embed";
const form = await prisma.app_RoutingForms_Form.findUnique({
where: {
id: formId,
@@ -244,6 +251,7 @@ export const getServerSideProps = async function getServerSideProps(
return {
props: {
isEmbed,
profile: {
theme: form.user.theme,
brandColor: form.user.brandColor,
+2 -2
View File
@@ -106,7 +106,7 @@
</div>
<i class="last-action"> You would see last Booking page action in my place </i>
<div >
<div class="place" style="width:50%;"></div>
<div class="place" style="width:100%;"></div>
<div class="loader" id="cal-booking-loader-">Loading .....</div>
</div>
</div>
@@ -415,7 +415,7 @@
"inline",
{
elementOrSelector: "#cal-booking-place-inline-routing-form .place",
calLink: "forms/9a7e8801-2f34-45ae-aa79-51859766a860",
calLink: "forms/948ae412-d995-4865-875a-48302588de03",
config: {
iframeAttrs: {
id: "cal-booking-place-inline-routing-form-iframe"
@@ -109,7 +109,7 @@ expect.extend({
};
}
const pathname = u.pathname;
const expectedPathname = expectedUrlDetails.pathname;
const expectedPathname = expectedUrlDetails.pathname + "/embed";
if (expectedPathname && expectedPathname !== pathname) {
return {
pass: false,
@@ -150,16 +150,16 @@ expect.extend({
}, 500);
});
//At this point we know that window.initialBodyDisplay would be set as DOM would already have been ready(because linkReady event can only fire after that)
//At this point we know that window.initialBodyVisibility would be set as DOM would already have been ready(because linkReady event can only fire after that)
const {
display: displayBefore,
visibility: visibilityBefore,
background: backgroundBefore,
initialValuesSet,
} = await iframe.evaluate(() => {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
display: window.initialBodyDisplay,
visibility: window.initialBodyVisibility,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
background: window.initialBodyBackground,
@@ -168,21 +168,18 @@ expect.extend({
initialValuesSet: window.initialValuesSet,
};
});
const isOptimizedPage = u.pathname.includes("forms/");
expect(initialValuesSet).toBe(true);
if (!isOptimizedPage) {
expect(displayBefore).toBe("none");
}
expect(visibilityBefore).toBe("hidden");
expect(backgroundBefore).toBe("transparent");
const { display: displayAfter, background: backgroundAfter } = await iframe.evaluate(() => {
const { visibility: visibilityAfter, background: backgroundAfter } = await iframe.evaluate(() => {
return {
display: document.body.style.display,
visibility: document.body.style.visibility,
background: document.body.style.background,
};
});
expect(displayAfter).not.toBe("none");
expect(visibilityAfter).toBe("visible");
expect(backgroundAfter).toBe("transparent");
if (!iframeReadyEventDetail) {
return {
@@ -18,7 +18,7 @@ export const test = base.extend<Fixtures>({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyDisplay = document.body.style.display;
window.initialBodyVisibility = document.body.style.visibility;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
@@ -55,7 +55,7 @@ export const getEmbedIframe = async ({ page, pathname }: { page: Page; pathname:
return null;
}
const u = new URL(embedIframe.url());
if (u.pathname === pathname) {
if (u.pathname === pathname + "/embed") {
return embedIframe;
}
return null;
@@ -1,8 +1,8 @@
const html = `<div id="wrapper" style="top:calc(50% - 30px); left:calc(50% - 30px)" class="absolute z-highest">
<div class="loader border-brand dark:border-darkmodebrand">
const html = `<div id="wrapper" style="top:50%; left:50%" class="absolute z-highest">
<div style="transform:translate(-50%,-50%)" class="loader border-brand dark:border-darkmodebrand">
<span class="loader-inner bg-brand dark:bg-darkmodebrand"></span>
</div>
<div id="error" class="hidden">
<div id="error" style="transform:translate(-50%,-50%)" class="hidden">
Something went wrong.
</div>
</div>
+5 -12
View File
@@ -218,12 +218,8 @@ function getEmbedType() {
}
}
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 router = useRouter();
const [isEmbed, setIsEmbed] = useState<boolean | null>(typeof router.query.embed === "string");
export const useIsEmbed = (embedSsr?: boolean) => {
const [isEmbed, setIsEmbed] = useState(embedSsr);
useEffect(() => {
const namespace = getNamespace();
const _isValidNamespace = isValidNamespace(namespace);
@@ -246,7 +242,7 @@ export const useEmbedType = () => {
};
function unhideBody() {
document.body.style.display = "block";
document.body.style.visibility = "visible";
}
// If you add a method here, give type safety to parent manually by adding it to embed.ts. Look for "parentKnowsIframeReady" in it
@@ -285,11 +281,7 @@ export const methods = {
return;
}
// No UI change should happen in sight. Let the parent height adjust and in next cycle show it.
// 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);
unhideBody();
sdkActionManager?.fire("linkReady", {});
});
},
@@ -372,6 +364,7 @@ function keepParentInformedAboutDimensionChanges() {
}
if (isBrowser) {
log("Embed SDK loaded", { isEmbed: window.isEmbed() });
const url = new URL(document.URL);
embedStore.theme = (window.getEmbedTheme() || "auto") as UiConfig["theme"];
if (url.searchParams.get("prerender") !== "true" && window.isEmbed()) {
+4
View File
@@ -181,6 +181,10 @@ export class Cal {
}
const urlInstance = new URL(`${config.origin}/${calLink}`);
if (!urlInstance.pathname.endsWith("embed")) {
// TODO: Make a list of patterns that are embeddable. All except that should be allowed with a warning that "The page isn't optimized for embedding"
urlInstance.pathname = `${urlInstance.pathname}/embed`;
}
urlInstance.searchParams.set("embed", this.namespace);
if (config.debug) {
urlInstance.searchParams.set("debug", "" + config.debug);