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:
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();
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -197,6 +197,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
isDynamicGroupBooking,
|
||||
hasHashedBookingLink: false,
|
||||
hashedLink: null,
|
||||
isEmbed: typeof context.query.embed === "string",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[user]";
|
||||
|
||||
export { default } from "../[user]";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[slug]";
|
||||
|
||||
export { default } from "../[slug]";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[slug]";
|
||||
|
||||
export { default } from "../[slug]";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user