diff --git a/apps/docs/pages/integrations/embed.mdx b/apps/docs/pages/integrations/embed.mdx index 652ead237a..40cde9d94c 100644 --- a/apps/docs/pages/integrations/embed.mdx +++ b/apps/docs/pages/integrations/embed.mdx @@ -9,8 +9,8 @@ The Embed allows your website visitors to book a meeting with you directly from ## Install on any website - _Step-1._ Install the Vanilla JS Snippet - - ```javascript +```html + +``` ## Install with a Framework @@ -72,18 +68,20 @@ Show the embed inline inside a container element. It would take the width and he
_Vanilla JS_ -```javascript -Cal("inline", { - elementOrSelector: "Your Embed Container Selector Path", // You can also provide an element directly - calLink: "jane", // The link that you want to embed. It would open https://cal.com/jane in embed - config: { - name: "John Doe", // Prefill Name - email: "johndoe@gmail.com", // Prefill Email - notes: "Test Meeting", // Prefill Notes - guests: ["janedoe@gmail.com", "test@gmail.com"], // Prefill Guests - theme: "dark", // "dark" or "light" theme - }, -}); +```html + ```
@@ -146,8 +144,10 @@ Consider an instruction as a function with that name and that would be called wi Appends embed inline as the child of the element. -```javascript +```html + ```` - `elementOrSelector` - Give it either a valid CSS selector or an HTMLElement instance directly @@ -158,8 +158,10 @@ Cal("inline", { elementOrSelector, calLink }); Configure UI for embed. Make it look part of your webpage. -```javascript +```html + ``` - `styles` - It supports styling for `body` and `eventTypeListItem`. Right now we support just background on these two. @@ -170,15 +172,18 @@ Usage: If you want to open cal link on some action. Make it pop open instantly by preloading it. -```javascript +```html + ``` - `calLink` - Cal Link that you want to embed e.g. john. Just give the username. No need to give the full URL [https://cal.com/john]() ## Actions You can listen to an action that occurs in embedded cal link as follows. You can think of them as DOM events. We are avoiding the term "events" to not confuse it with Cal Events. -```javascript +```html + ``` Following are the list of supported actions. diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx index 6adcfe08d4..fa549c7554 100644 --- a/apps/web/components/booking/pages/AvailabilityPage.tsx +++ b/apps/web/components/booking/pages/AvailabilityPage.tsx @@ -18,7 +18,14 @@ import { useRouter } from "next/router"; import { useEffect, useMemo, useState } from "react"; import { FormattedNumber, IntlProvider } from "react-intl"; -import { useEmbedStyles, useIsEmbed, useIsBackgroundTransparent, sdkActionManager } from "@calcom/embed-core"; +import { + useEmbedStyles, + useIsEmbed, + useIsBackgroundTransparent, + sdkActionManager, + useEmbedType, + useEmbedNonStylesConfig, +} from "@calcom/embed-core"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -56,6 +63,8 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage const { t, i18n } = useLocale(); const { contracts } = useContracts(); const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker"); + const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; + const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; let isBackgroundTransparent = useIsBackgroundTransparent(); useExposePlanGlobally(plan); useEffect(() => { @@ -146,18 +155,19 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
+ (selectedDate ? "max-w-5xl" : "max-w-3xl") + )}> {isReady && (
{/* mobile: details */} diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index dd22a8fb8c..78805642de 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -18,7 +18,13 @@ import { FormattedNumber, IntlProvider } from "react-intl"; import { ReactMultiEmail } from "react-multi-email"; import { useMutation } from "react-query"; -import { useIsEmbed, useIsBackgroundTransparent } from "@calcom/embed-core"; +import { + useIsEmbed, + useEmbedStyles, + useIsBackgroundTransparent, + useEmbedType, + useEmbedNonStylesConfig, +} from "@calcom/embed-core"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; @@ -71,6 +77,8 @@ const BookingPage = ({ }: BookingPageProps) => { const { t, i18n } = useLocale(); const isEmbed = useIsEmbed(); + const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; + const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; const router = useRouter(); const { contracts } = useContracts(); const { data: session } = useSession(); @@ -298,16 +306,17 @@ const BookingPage = ({
{isReady && (
diff --git a/apps/web/ee/components/stripe/PaymentPage.tsx b/apps/web/ee/components/stripe/PaymentPage.tsx index bed4afd91b..08606bdd67 100644 --- a/apps/web/ee/components/stripe/PaymentPage.tsx +++ b/apps/web/ee/components/stripe/PaymentPage.tsx @@ -1,5 +1,6 @@ import { CreditCardIcon } from "@heroicons/react/solid"; import { Elements } from "@stripe/react-stripe-js"; +import classNames from "classnames"; import dayjs from "dayjs"; import timezone from "dayjs/plugin/timezone"; import toArray from "dayjs/plugin/toArray"; @@ -8,6 +9,7 @@ import Head from "next/head"; import React, { FC, useEffect, useState } from "react"; import { FormattedNumber, IntlProvider } from "react-intl"; +import { sdkActionManager, useIsEmbed } from "@calcom/embed-core"; import getStripe from "@calcom/stripe/client"; import PaymentComponent from "@ee/components/stripe/Payment"; import { PaymentPageProps } from "@ee/pages/payment/[uid]"; @@ -26,16 +28,33 @@ const PaymentPage: FC = (props) => { const [is24h, setIs24h] = useState(isBrowserLocale24h()); const [date, setDate] = useState(dayjs.utc(props.booking.startTime)); const { isReady, Theme } = useTheme(props.profile.theme); - + const isEmbed = useIsEmbed(); useEffect(() => { + let embedIframeWidth = 0; setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess())); setIs24h(!!localStorage.getItem("timeOption.is24hClock")); - }, []); + if (isEmbed) { + requestAnimationFrame(function fixStripeIframe() { + // HACK: Look for stripe iframe and center position it just above the embed content + const stripeIframeWrapper = document.querySelector( + 'iframe[src*="https://js.stripe.com/v3/authorize-with-url-inner"]' + )?.parentElement; + if (stripeIframeWrapper) { + stripeIframeWrapper.style.margin = "0 auto"; + stripeIframeWrapper.style.width = embedIframeWidth + "px"; + } + requestAnimationFrame(fixStripeIframe); + }); + sdkActionManager?.on("__dimensionChanged", (e) => { + embedIframeWidth = e.detail.data.iframeWidth as number; + }); + } + }, [isEmbed]); const eventName = props.booking.title; return isReady ? ( -
+
@@ -51,7 +70,10 @@ const PaymentPage: FC<PaymentPageProps> = (props) => { ​ </span> <div - className="inline-block transform overflow-hidden rounded-sm border border-neutral-200 bg-white px-8 pt-5 pb-4 text-left align-bottom transition-all dark:border-neutral-700 dark:bg-gray-800 sm:my-8 sm:w-full sm:max-w-lg sm:py-6 sm:align-middle" + className={classNames( + "main inline-block transform overflow-hidden rounded-lg border border-neutral-200 bg-white px-8 pt-5 pb-4 text-left align-bottom transition-all dark:border-neutral-700 dark:bg-gray-800 sm:w-full sm:max-w-lg sm:py-6 sm:align-middle", + isEmbed ? "" : "sm:my-8" + )} role="dialog" aria-modal="true" aria-labelledby="modal-headline"> diff --git a/apps/web/lib/hooks/useTheme.tsx b/apps/web/lib/hooks/useTheme.tsx index fbcec07820..9af36630e8 100644 --- a/apps/web/lib/hooks/useTheme.tsx +++ b/apps/web/lib/hooks/useTheme.tsx @@ -18,6 +18,8 @@ function applyThemeAndAddListener(theme: string) { document.documentElement.classList.remove("dark"); } } else { + document.documentElement.classList.remove("dark"); + document.documentElement.classList.remove("light"); document.documentElement.classList.add(theme); } }; @@ -33,15 +35,16 @@ export default function useTheme(theme?: Maybe<string>) { const embedTheme = useEmbedTheme(); // Embed UI configuration takes more precedence over App Configuration theme = embedTheme || theme; - + const [_theme, setTheme] = useState<Maybe<string>>(null); useEffect(() => { // TODO: isReady doesn't seem required now. This is also impacting PSI Score for pages which are using isReady. setIsReady(true); + setTheme(theme); }, []); function Theme() { const code = applyThemeAndAddListener.toString(); - const themeStr = theme ? `"${theme}"` : null; + const themeStr = _theme ? `"${_theme}"` : null; return ( <Head> <script dangerouslySetInnerHTML={{ __html: `(${code})(${themeStr})` }}></script> diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index 1218612f71..ddf754b066 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -1,6 +1,7 @@ import { ArrowRightIcon } from "@heroicons/react/outline"; import { BadgeCheckIcon } from "@heroicons/react/solid"; import { UserPlan } from "@prisma/client"; +import classNames from "classnames"; import { GetServerSidePropsContext } from "next"; import dynamic from "next/dynamic"; import Link from "next/link"; @@ -9,9 +10,10 @@ import React, { useEffect, useState } from "react"; import { Toaster } from "react-hot-toast"; import { JSONObject } from "superjson/dist/types"; -import { sdkActionManager, useEmbedStyles, useIsEmbed } from "@calcom/embed-core"; +import { sdkActionManager, useEmbedNonStylesConfig, useEmbedStyles, useIsEmbed } from "@calcom/embed-core"; import defaultEvents, { getDynamicEventDescription, + getGroupName, getUsernameList, getUsernameSlugLink, } from "@calcom/lib/defaultEvents"; @@ -23,6 +25,7 @@ import prisma from "@lib/prisma"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry"; import { inferSSRProps } from "@lib/types/inferSSRProps"; +import CustomBranding from "@components/CustomBranding"; import AvatarGroup from "@components/ui/AvatarGroup"; import { AvatarSSR } from "@components/ui/AvatarSSR"; @@ -37,7 +40,7 @@ interface EvtsToVerify { } export default function User(props: inferSSRProps<typeof getServerSideProps>) { - const { users } = props; + const { users, profile } = props; const [user] = users; //To be used when we only have a single user, not dynamic group const { Theme } = useTheme(user.theme); const { t } = useLocale(); @@ -102,13 +105,15 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) { ))} </ul> ); + const isEmbed = useIsEmbed(); const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem"); + const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; + const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; const query = { ...router.query }; delete query.user; // So it doesn't display in the Link (and make tests fail) useExposePlanGlobally("PRO"); const nameOrUsername = user.name || user.username || ""; const [evtsToVerify, setEvtsToVerify] = useState<EvtsToVerify>({}); - const isEmbed = useIsEmbed(); const telemetry = useTelemetry(); useEffect(() => { @@ -128,8 +133,17 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) { username={isDynamicGroup ? dynamicUsernames.join(", ") : (user.username as string) || ""} // avatar={user.avatar || undefined} /> - <div className={"h-screen dark:bg-neutral-900" + isEmbed ? " bg:white m-auto max-w-3xl" : ""}> - <main className="mx-auto max-w-3xl px-4 py-24"> + <CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} /> + + <div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}> + <main + className={classNames( + shouldAlignCentrally ? "mx-auto" : "", + isEmbed + ? " border-bookinglightest rounded-md border bg-white dark:bg-neutral-900 sm:dark:border-gray-600" + : "", + "max-w-3xl py-24 px-4" + )}> {isSingleUser && ( // When we deal with a single user, not dynamic group <div className="mb-8 text-center"> <AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername}></AvatarSSR> @@ -284,6 +298,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => email: true, name: true, bio: true, + brandColor: true, + darkBrandColor: true, avatar: true, theme: true, plan: true, @@ -298,10 +314,36 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => notFound: true, }; } - const isDynamicGroup = users.length > 1; + const dynamicNames = isDynamicGroup + ? users.map((user) => { + return user.name || ""; + }) + : []; const [user] = users; //to be used when dealing with single user, not dynamic group + + const profile = isDynamicGroup + ? { + name: getGroupName(dynamicNames), + image: null, + theme: null, + weekStart: "Sunday", + brandColor: "", + darkBrandColor: "", + allowDynamicBooking: users.some((user) => { + return !user.allowDynamicBooking; + }) + ? false + : true, + } + : { + name: user.name || user.username, + image: user.avatar, + theme: user.theme, + brandColor: user.brandColor, + darkBrandColor: user.darkBrandColor, + }; const usersIds = users.map((user) => user.id); const credentials = await prisma.credential.findMany({ where: { @@ -337,6 +379,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => return { props: { users, + profile, user: { emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), }, diff --git a/apps/web/pages/_document.tsx b/apps/web/pages/_document.tsx index 9360d3aa3e..a927bd5f34 100644 --- a/apps/web/pages/_document.tsx +++ b/apps/web/pages/_document.tsx @@ -5,7 +5,7 @@ type Props = Record<string, unknown> & DocumentProps; class MyDocument extends Document<Props> { static async getInitialProps(ctx: DocumentContext) { const initialProps = await Document.getInitialProps(ctx); - const isEmbed = ctx.req?.url?.includes("embed"); + const isEmbed = ctx.req?.url?.includes("embed="); return { ...initialProps, isEmbed }; } @@ -27,7 +27,9 @@ class MyDocument extends Document<Props> { </Head> {/* Keep the embed hidden till parent initializes and gives it the appropriate styles */} - <body className="bg-gray-100 dark:bg-neutral-900" style={props.isEmbed ? { display: "none" } : {}}> + <body + className={props.isEmbed ? "bg-transparent" : "bg-gray-100 dark:bg-neutral-900"} + style={props.isEmbed ? { display: "none" } : {}}> <Main /> <NextScript /> </body> diff --git a/apps/web/pages/success.tsx b/apps/web/pages/success.tsx index f39a496f32..65cda6e253 100644 --- a/apps/web/pages/success.tsx +++ b/apps/web/pages/success.tsx @@ -12,7 +12,12 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useState, useRef } from "react"; -import { useIsEmbed, useEmbedStyles, useIsBackgroundTransparent } from "@calcom/embed-core"; +import { + useIsEmbed, + useEmbedStyles, + useIsBackgroundTransparent, + useEmbedNonStylesConfig, +} from "@calcom/embed-core"; import { sdkActionManager } from "@calcom/embed-core"; import { getDefaultEvent } from "@calcom/lib/defaultEvents"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -88,7 +93,7 @@ function RedirectionToast({ url }: { url: string }) { return ( <> - <div className="relative inset-x-0 top-0 z-[60] pb-2 sm:fixed sm:top-2 sm:pb-5"> + <div className="relative z-[60] pb-2 sm:pb-5"> <div className="mx-auto w-full sm:max-w-7xl sm:px-2 lg:px-8"> <div className="border border-green-600 bg-green-500 p-2 sm:p-3"> <div className="flex flex-wrap items-center justify-between"> @@ -142,6 +147,9 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>) const isBackgroundTransparent = useIsBackgroundTransparent(); const isEmbed = useIsEmbed(); + const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; + const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; + const attendeeName = typeof name === "string" ? name : "Nameless"; const eventNameObject = { @@ -214,19 +222,22 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>) description={needsConfirmation ? t("booking_submitted") : t("booking_confirmed")} /> <CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} /> - <main className={classNames("mx-auto", isEmbed ? "" : "max-w-3xl py-24")}> - <div className={classNames("overflow-y-auto", isEmbed ? "" : "fixed inset-0 z-50 ")}> + <main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}> + <div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}> {isSuccessRedirectAvailable(eventType) && eventType.successRedirectUrl ? ( <RedirectionToast url={eventType.successRedirectUrl}></RedirectionToast> ) : null}{" "} - <div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0"> + <div + className={classNames( + shouldAlignCentrally ? "text-center" : "", + "flex items-end justify-center px-4 pt-4 pb-20 sm:block sm:p-0" + )}> <div - className={classNames("my-4 transition-opacity sm:my-0", isEmbed ? "" : "fixed inset-0")} + className={classNames("my-4 transition-opacity sm:my-0", isEmbed ? "" : " inset-0")} aria-hidden="true"> <div className={classNames( - "inline-block transform overflow-hidden rounded-sm", - isEmbed ? "" : "border sm:my-8 sm:max-w-lg ", + "inline-block transform overflow-hidden rounded-md border sm:my-8 sm:max-w-lg", isBackgroundTransparent ? "" : "bg-white dark:border-neutral-700 dark:bg-gray-800", "px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-6 sm:align-middle" )} @@ -404,7 +415,7 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>) </form> </div> )} - {userIsOwner && ( + {userIsOwner && !isEmbed && ( <div className="mt-4"> <Link href="/bookings"> <a className="flex items-center text-black dark:text-white"> diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 12013b3c69..1e2160d417 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -86,7 +86,7 @@ function TeamPage({ team }: TeamPageProps) { <div> <Theme /> <HeadSeo title={teamName} description={teamName} /> - <div className="px-4 pt-24 pb-12"> + <div className="rounded-md bg-white px-4 pt-24 pb-12 dark:bg-gray-800 md:border"> <div className="max-w-96 mx-auto mb-8 text-center"> <Avatar alt={teamName} diff --git a/package.json b/package.json index c29a966702..7ffb1e8fb2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "start": "turbo run start --scope=\"@calcom/web\"", "test": "turbo run test", "test-playwright": "yarn playwright test --config=tests/config/playwright.config.ts", + "embed-tests-quick": "turbo run embed-tests-quick", + "embed-tests": "turbo run embed-tests", "test-e2e": "turbo run test-e2e --concurrency=1", "type-check": "turbo run type-check" }, diff --git a/packages/embeds/embed-core/.gitignore b/packages/embeds/embed-core/.gitignore new file mode 100644 index 0000000000..faea19472e --- /dev/null +++ b/packages/embeds/embed-core/.gitignore @@ -0,0 +1 @@ +src/tailwind.generated.css diff --git a/packages/embeds/embed-core/README.md b/packages/embeds/embed-core/README.md index 9234286dce..23795f3bcb 100644 --- a/packages/embeds/embed-core/README.md +++ b/packages/embeds/embed-core/README.md @@ -9,7 +9,7 @@ You can also see various example usages [here](https://github.com/calcom/cal.com ## Development -Run the following command and then you can test the embed in the automatically opened page `http://localhost:3002` +Run the following command and then you can test the embed in the automatically opened page `http://localhost:3100` ```bash yarn dev @@ -38,22 +38,21 @@ Make `dist/embed.umd.js` servable on URL <http://cal.com/embed.js> ## Known Bugs and Upcoming Improvements - Unsupported Browsers and versions. Documenting them and gracefully handling that. +- Need to create a booking Shell so that common changes for embed can be applied there. - Accessibility and UI/UX Issues - let user choose the loader for ModalBox - If website owner links the booking page directly for an event, should the user be able to go to events-listing page using back button ? - Let user specify both dark and light theme colors. Right now the colors specified are for light theme. - - Embed doesn't adapt to screen size without page refresh. - - Try opening in portrait mode and then go to landscape mode. - - In inline mode, due to changing height of iframe, the content goes beyond the fold. Automatic scroll needs to be implemented. - - On Availability page, when selecting date, width doesn't increase. max-width is there but because of strict width restriction with iframe, it doesn't allow it to expand. + - Transparent support is not properly done for team links + - Maybe don't set border radius in inline mode or give option to configure border radius. - Branding - Powered by Cal.com and 'Try it for free'. Should they be shown only for FREE account. - Branding at the bottom has been removed for UI improvements, need to see where to add it. - API - - Allow loader color customization using UI command itself too. + - Allow loader color customization using UI command itself too. Right now it's possible using CSS only. - Automation Tests - Run automation tests in CI @@ -71,8 +70,6 @@ Make `dist/embed.umd.js` servable on URL <http://cal.com/embed.js> - Need to reduce the number of colors on booking page, so that UI configuration is simpler - Dev Experience/Ease of Installation - - Improved Demo - - Seeding might be done for team event so that such an example is also available readily in index.html - Do we need a one liner(like `window.dataLayer.push`) to inform SDK of something even if snippet is not yet on the page but would be there e.g. through GTM it would come late on the page ? - Might be better to pass all configuration using a single base64encoded query param to booking page. @@ -81,7 +78,7 @@ Make `dist/embed.umd.js` servable on URL <http://cal.com/embed.js> - Custom written Tailwind CSS is sent multiple times for different custom elements. - Embed Code Generator - +- Option to disable redirect banner and let parent handle redirect. - Release Issues - Compatibility Issue - When embed-iframe.js is updated in such a way that it is not compatible with embed.js, doing a release might break the embed for some time. e.g. iframeReady event let's say get's changed to something else - Best Case scenario - App and Website goes live at the same time. A website using embed loads the same updated and thus compatible versions of embed.js and embed-iframe.js diff --git a/packages/embeds/embed-core/index.html b/packages/embeds/embed-core/index.html index 6f6a53a87c..cf7842daea 100644 --- a/packages/embeds/embed-core/index.html +++ b/packages/embeds/embed-core/index.html @@ -5,6 +5,15 @@ if (!location.search.includes("nonResponsive")) { document.write('<meta name="viewport" content="width=device-width"/>'); } + (()=> { + const url = new URL(document.URL); + // Only run the example specified by only=, avoids distraction and faster to test. + const only = url.searchParams.get("only"); + const namespace = only ? only.replace("ns:",""): null + if (namespace) { + location.hash="#cal-booking-place-" + namespace + "-iframe" + } + })() </script> <script> (function (C, A, L) { @@ -34,7 +43,7 @@ } p(cal, ar); }; - })(window, "//localhost:3002/dist/embed.umd.js", "init"); + })(window, "//localhost:3100/dist/embed.umd.js", "init"); </script> <style> @@ -55,8 +64,7 @@ color: green; } * { - --cal-brand-border-color: blue; - --cal-brand-background-color: blue; + --cal-brand-color: gray; } </style> </head> @@ -64,7 +72,7 @@ <h3>This page has a non responsive version accessible <a href="?nonResponsive">here</a></h3> <h3>Pre-render test page available at <a href="?only=prerender-test">here</a></h3> <div> - <button data-cal-namespace="prerendertestLightTheme" data-cal-link="free?light&popup">Book with Free User[Light Theme]</button> + <button data-cal-namespace="prerendertestLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="free?light&popup">Book with Free User[Light Theme]</button> <div> <i >Corresponding Cal Link is being preloaded. Assuming that it would take you some time to click this @@ -74,9 +82,12 @@ > </div> <h2>Other Popup Examples</h2> - <button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free?dark&popup">Book with Free User[Dark Theme]</button> - <button data-cal-namespace="popupTeamLinkLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="team/test-team?team&light&popup">Book with Test Team[Light Theme]</button> - <button data-cal-namespace="popupTeamLinkDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="team/test-team?team&dark&popup">Book with Test Team[Dark Theme]</button> + <button data-cal-namespace="popupAutoTheme" data-cal-link="free">Book with Free User[Auto Theme]</button> + <button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free">Book with Free User[Dark Theme]</button> + <button data-cal-namespace="popupTeamLinkLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Light Theme]</button> + <button data-cal-namespace="popupTeamLinkDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Dark Theme]</button> + <button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">See Team Links [Auto Theme]</button> + <button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">Book Paid Event [Auto Theme]</button> <div> <h2>Embed for Pages behind authentication</h2> <button data-cal-namespace="upcomingBookings" data-cal-config='{"theme":"dark"}' data-cal-link="bookings/upcoming">Show Upcoming Bookings</button> @@ -85,18 +96,14 @@ <div id="namespaces-test"> <div class="debug" id="cal-booking-place-default"> <h2> - Default Namespace(Cal)<i>[Dark Theme][inline][Guests(janedoe@gmail.com and test@gmail.com)]</i> + Default Namespace(Cal)<i>[Dark Theme][inline][Guests(janedoe@example.com and test@example.com)]</i> </h2> <div> <i><a href="?only=ns:default">Test in Zen Mode</a></i> </div> <i class="last-action"> You would see last Booking page action in my place </i> <div > - <div> - if you render booking embed in me, I would not let it be more than 30vh in height. So you would - have to scroll to see the entire content - </div> - <div class="place" style="width:50%; max-height: 30vh; overflow: scroll"></div> + <div class="place" style="width:50%;"></div> <div class="loader" id="cal-booking-loader-">Loading .....</div> </div> </div> @@ -109,7 +116,7 @@ <i>You would see last Booking page action in my place</i> </i> <div class="place"> - <div>If you render booking embed in me, I won't restrict you. The entire page is yours.</div> + <div>If you render booking embed in me, I won't restrict you. The entire page is yours. Content is by default aligned center</div> <button onclick="(function () {Cal.ns.second('ui', {styles:{eventTypeListItem:{backgroundColor:'blue'}}})})()"> Change <code>eventTypeListItem</code> bg color @@ -117,6 +124,21 @@ <button onclick="(function () {Cal.ns.second('ui', {styles:{body:{background:'red'}}})})()"> Change <code>body</code> bg color </button> + <button onclick="(function () {Cal.ns.second('ui', {styles:{align:'left'}})})()"> + Align left + </button> + <button onclick="(function () {Cal.ns.second('ui', {styles:{align:'center'}})})()"> + Align Center + </button> + <button onclick="(function () {Cal.ns.second('ui', {styles:{enabledDateButton: { + backgroundColor: '#D3D3D3', + }, + disabledDateButton: { + backgroundColor: 'lightslategray', + },}})})()"> + Change Date Button Color + </button> + <div class="loader" id="cal-booking-loader-second">Loading .....</div> </div> </div> @@ -136,7 +158,7 @@ </div> <div class="debug" id="cal-booking-place-fourth"> - <h2>Namespace "fourth"(Cal.ns.fourth)[Team Event Test][inline]</h2> + <h2>Namespace "fourth"(Cal.ns.fourth)[Team Event Test][inline taking entire width]</h2> <div> <i><a href="?only=ns:fourth">Test in Zen Mode</a></i> </div> @@ -150,6 +172,23 @@ </div> </div> + <div class="debug" id="cal-booking-place-fifth"> + <h2>Namespace "fifth"(Cal.ns.fifth)[Team Event Test][inline along with some content]</h2> + <div> + <i><a href="?only=ns:fifth">Test in Zen Mode</a></i> + </div> + <i class="last-action"> + <i>You would see last Booking page action in my place</i> + </i> + <div style="display:flex;align-items: center;"> + <h2 style="width: 30%"> + On the right side you can book a team meeting => + </h2> + <div style="width: 70%" class="place"> + </div> + </div> + </div> + <script> const callback = function (e) { const detail = e.detail; @@ -176,10 +215,14 @@ elementOrSelector: "#cal-booking-place-default .place", calLink: "pro?case=1", config: { - name: "John Doe", + __autoScroll:true, + iframeAttrs: { + id: "cal-booking-place-default-iframe" + }, + name: "John", email: "johndoe@gmail.com", notes: "Test Meeting", - guests: ["janedoe@gmail.com", "test@gmail.com"], + guests: ["janedoe@example.com", "test@example.com"], theme: "dark", }, }); @@ -202,25 +245,11 @@ { elementOrSelector: "#cal-booking-place-second .place", calLink: "pro?case=2", - }, - ], - [ - "ui", - { - styles: { - body: { - background: "white", + config: { + iframeAttrs: { + id: "cal-booking-place-second-iframe" }, - eventTypeListItem: { - backgroundColor: "#D3D3D3", - }, - enabledDateButton: { - backgroundColor: "#D3D3D3", - }, - disabledDateButton: { - backgroundColor: "lightslategray", - }, - }, + } }, ] ); @@ -243,6 +272,11 @@ { elementOrSelector: "#cal-booking-place-third .place", calLink: "pro/30min", + config: { + iframeAttrs: { + id: "cal-booking-place-third-iframe" + }, + } }, ], [ @@ -280,7 +314,12 @@ "inline", { elementOrSelector: "#cal-booking-place-fourth .place", - calLink: "team/test-team", + calLink: "team/seeded-team", + config: { + iframeAttrs: { + id: "cal-booking-place-fourth-iframe" + }, + } }, ], [ @@ -307,7 +346,30 @@ callback, }); } - + if (!only || only === "ns:fifth") { + Cal("init", "fifth", { + debug: 1, + origin: "http://localhost:3000", + }); + Cal.ns.fifth( + [ + "inline", + { + elementOrSelector: "#cal-booking-place-fifth .place", + calLink: "team/seeded-team/collective-seeded-team-event", + config: { + iframeAttrs: { + id: "cal-booking-place-fifth-iframe" + }, + } + }, + ], + ); + Cal.ns.fifth("on", { + action: "*", + callback, + }); + } if (!only || only === "prerender-test") { Cal('init', 'prerendertestLightTheme', { debug: 1, @@ -321,6 +383,10 @@ debug: 1, origin: "http://localhost:3000", }) + Cal('init', 'popupAutoTheme', { + debug: 1, + origin: "http://localhost:3000", + }) Cal('init', 'popupTeamLinkLightTheme', { debug: 1, origin: "http://localhost:3000", @@ -330,7 +396,17 @@ origin: "http://localhost:3000", }) - Cal('init', 'upcomingBookings', { + Cal('init', 'popupTeamLinkDarkTheme', { + debug: 1, + origin: "http://localhost:3000", + }) + + Cal('init', 'popupTeamLinksList', { + debug: 1, + origin: "http://localhost:3000", + }) + + Cal('init', 'popupPaidEvent', { debug: 1, origin: "http://localhost:3000", }) diff --git a/packages/embeds/embed-core/package.json b/packages/embeds/embed-core/package.json index dc8896990c..946397950a 100644 --- a/packages/embeds/embed-core/package.json +++ b/packages/embeds/embed-core/package.json @@ -7,13 +7,30 @@ "build": "vite build", "build:cal": "NEXT_PUBLIC_WEBSITE_URL='https://cal.com' yarn build", "vite": "vite", - "dev": "run-p 'build --watch' 'vite --port 3002 --strict-port --open'", + "tailwind": "yarn tailwindcss -i ./src/styles.css -o ./src/tailwind.generated.css --watch", + "buildWatchAndServer": "run-p 'build --watch' 'vite --port 3100 --strict-port --open'", + "dev": "run-p 'tailwind' 'buildWatchAndServer'", "type-check": "tsc --pretty --noEmit", "lint": "eslint --ext .ts,.js src", - "test-playwright": "yarn playwright test --config=playwright/config/playwright.config.ts" + "embed-tests": "yarn playwright test --config=playwright/config/playwright.config.ts", + "embed-tests-quick": "QUICK=true yarn embed-tests" + }, + "postcss": { + "map": false, + "plugins": { + "tailwindcss": {}, + "autoprefixer": {} + } }, "devDependencies": { - "vite": "^2.8.6", - "eslint": "^8.10.0" + "autoprefixer": "^10.4.4", + "eslint": "^8.10.0", + "postcss": "^8.4.12", + "vite": "^2.8.6" + }, + "dependencies": { + "tailwindcss": "^3.0.24", + "tsc": "^2.0.4", + "typescript": "^4.6.3" } } diff --git a/packages/embeds/embed-core/playwright/config/playwright.config.ts b/packages/embeds/embed-core/playwright/config/playwright.config.ts index 2b8b443f5f..7ccb45ffc3 100644 --- a/packages/embeds/embed-core/playwright/config/playwright.config.ts +++ b/packages/embeds/embed-core/playwright/config/playwright.config.ts @@ -3,10 +3,10 @@ import * as path from "path"; const outputDir = path.join("../results"); const testDir = path.join("../tests"); - +const quickMode = process.env.QUICK === "true"; const config: PlaywrightTestConfig = { forbidOnly: !!process.env.CI, - retries: 1, + retries: quickMode ? 0 : 1, workers: 1, timeout: 60_000, reporter: [ @@ -19,15 +19,21 @@ const config: PlaywrightTestConfig = { ], globalSetup: require.resolve("./globalSetup"), outputDir, + expect: { + toMatchSnapshot: { + // Opacity transitions can cause small differences + maxDiffPixels: 50, + }, + }, webServer: { // Start App Server manually - Can't be handled here. See https://github.com/microsoft/playwright/issues/8206 command: "yarn workspace @calcom/embed-core dev", - port: 3002, + port: 3100, timeout: 60_000, reuseExistingServer: !process.env.CI, }, use: { - baseURL: "http://localhost:3002", + baseURL: "http://localhost:3100", locale: "en-US", trace: "retain-on-failure", headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS, @@ -38,16 +44,20 @@ const config: PlaywrightTestConfig = { testDir, use: { ...devices["Desktop Chrome"] }, }, - { - name: "firefox", - testDir, - use: { ...devices["Desktop Firefox"] }, - }, - { - name: "webkit", - testDir, - use: { ...devices["Desktop Safari"] }, - }, + quickMode + ? {} + : { + name: "firefox", + testDir, + use: { ...devices["Desktop Firefox"] }, + }, + quickMode + ? {} + : { + name: "webkit", + testDir, + use: { ...devices["Desktop Safari"] }, + }, ], }; export type ExpectedUrlDetails = { @@ -123,9 +133,16 @@ expect.extend({ } } - const iframeReadyEventDetail = await getActionFiredDetails({ - calNamespace, - actionType: "__iframeReady", + const iframeReadyEventDetail = await new Promise(async (resolve) => { + setInterval(async () => { + const iframeReadyEventDetail = await getActionFiredDetails({ + calNamespace, + actionType: "linkReady", + }); + if (iframeReadyEventDetail) { + resolve(iframeReadyEventDetail); + } + }, 500); }); if (!iframeReadyEventDetail) { diff --git a/packages/embeds/embed-core/playwright/fixtures/fixtures.ts b/packages/embeds/embed-core/playwright/fixtures/fixtures.ts index a4a1ac45c3..bc456b8473 100644 --- a/packages/embeds/embed-core/playwright/fixtures/fixtures.ts +++ b/packages/embeds/embed-core/playwright/fixtures/fixtures.ts @@ -11,18 +11,26 @@ export const test = base.extend<Fixtures>({ ({ calNamespace }: { calNamespace: string }) => { //@ts-ignore window.eventsFiredStoreForPlaywright = window.eventsFiredStoreForPlaywright || {}; - document.addEventListener("DOMContentLoaded", () => { + document.addEventListener("DOMContentLoaded", function tryAddingListener() { if (parent !== window) { // Firefox seems to execute this snippet for iframe as well. Avoid that. It must be executed only for parent frame. return; } - console.log("PlaywrightTest:", "Adding listener for __iframeReady"); //@ts-ignore let api = window.Cal; + + if (!api) { + setTimeout(tryAddingListener, 500); + return; + } if (calNamespace) { //@ts-ignore api = window.Cal.ns[calNamespace]; } + console.log("PlaywrightTest:", "Adding listener for __iframeReady"); + if (!api) { + throw new Error(`namespace "${calNamespace}" not found`); + } api("on", { action: "*", callback: (e: any) => { @@ -41,13 +49,15 @@ export const test = base.extend<Fixtures>({ }, getActionFiredDetails: async ({ page }, use) => { await use(async ({ calNamespace, actionType }) => { - return await page.evaluate( - ({ actionType, calNamespace }) => { - //@ts-ignore - return window.eventsFiredStoreForPlaywright[`${actionType}-${calNamespace}`]; - }, - { actionType, calNamespace } - ); + if (!page.isClosed()) { + return await page.evaluate( + ({ actionType, calNamespace }) => { + //@ts-ignore + return window.eventsFiredStoreForPlaywright[`${actionType}-${calNamespace}`]; + }, + { actionType, calNamespace } + ); + } }); }, }); diff --git a/packages/embeds/embed-core/playwright/lib/testUtils.ts b/packages/embeds/embed-core/playwright/lib/testUtils.ts index ce719c462f..62797e4fb3 100644 --- a/packages/embeds/embed-core/playwright/lib/testUtils.ts +++ b/packages/embeds/embed-core/playwright/lib/testUtils.ts @@ -1,8 +1,35 @@ -import { Page, test } from "@playwright/test"; +import { Page, Frame, test, expect } from "@playwright/test"; + +import prisma from "@lib/prisma"; export function todo(title: string) { test.skip(title, () => {}); } +export const deleteAllBookingsByEmail = async (email: string) => + await prisma.booking.deleteMany({ + where: { + attendees: { + some: { + email: email, + }, + }, + }, + }); + +export const getBooking = async (bookingId: string) => { + const booking = await prisma.booking.findUnique({ + where: { + uid: bookingId, + }, + include: { + attendees: true, + }, + }); + if (!booking) { + throw new Error("Booking not found"); + } + return booking; +}; export const getEmbedIframe = async ({ page, pathname }: { page: Page; pathname: string }) => { // FIXME: Need to wait for the iframe to be properly added to shadow dom. There should be a no time boundation way to do it. @@ -19,3 +46,48 @@ export const getEmbedIframe = async ({ page, pathname }: { page: Page; pathname: } return null; }; + +async function selectFirstAvailableTimeSlotNextMonth(frame: Frame, page: Page) { + await frame.click('[data-testid="incrementMonth"]'); + // @TODO: Find a better way to make test wait for full month change render to end + // so it can click up on the right day, also when resolve remove other todos + // Waiting for full month increment + await frame.waitForTimeout(1000); + expect(await page.screenshot()).toMatchSnapshot("availability-page-2.png"); + // TODO: Find out why the first day is always booked on tests + await frame.locator('[data-testid="day"][data-disabled="false"]').nth(1).click(); + await frame.click('[data-testid="time"]'); +} + +export async function bookFirstEvent(username: string, frame: Frame, page: Page) { + // Click first event type + await frame.click('[data-testid="event-type-link"]'); + await frame.waitForNavigation({ + url(url) { + return !!url.pathname.match(new RegExp(`/${username}/.*$`)); + }, + }); + expect(await page.screenshot()).toMatchSnapshot("availability-page-1.png"); + await selectFirstAvailableTimeSlotNextMonth(frame, page); + await frame.waitForNavigation({ + url(url) { + return url.pathname.includes(`/${username}/book`); + }, + }); + expect(await page.screenshot()).toMatchSnapshot("booking-page.png"); + // --- fill form + await frame.fill('[name="name"]', "Embed User"); + await frame.fill('[name="email"]', "embed-user@example.com"); + await frame.press('[name="email"]', "Enter"); + const response = await page.waitForResponse("**/api/book/event"); + const responseObj = await response.json(); + const bookingId = responseObj.uid; + // Make sure we're navigated to the success page + await frame.waitForNavigation({ + url(url) { + return url.pathname.endsWith("/success"); + }, + }); + expect(await page.screenshot()).toMatchSnapshot("success-page.png"); + return bookingId; +} diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts b/packages/embeds/embed-core/playwright/tests/action-based.test.ts index 559c199de2..d6a2c53a4c 100644 --- a/packages/embeds/embed-core/playwright/tests/action-based.test.ts +++ b/packages/embeds/embed-core/playwright/tests/action-based.test.ts @@ -1,9 +1,15 @@ import { expect } from "@playwright/test"; import { test } from "../fixtures/fixtures"; -import { todo, getEmbedIframe } from "../lib/testUtils"; +import { todo, getEmbedIframe, bookFirstEvent, getBooking, deleteAllBookingsByEmail } from "../lib/testUtils"; + +test("should open embed iframe on click - Configured with light theme", async ({ + page, + addEmbedListeners, + getActionFiredDetails, +}) => { + await deleteAllBookingsByEmail("embed-user@example.com"); -test("should open embed iframe on click", async ({ page, addEmbedListeners, getActionFiredDetails }) => { const calNamespace = "prerendertestLightTheme"; await addEmbedListeners(calNamespace); await page.goto("/?only=prerender-test"); @@ -17,6 +23,15 @@ test("should open embed iframe on click", async ({ page, addEmbedListeners, getA expect(embedIframe).toBeEmbedCalLink(calNamespace, getActionFiredDetails, { pathname: "/free", }); + expect(await page.screenshot()).toMatchSnapshot("event-types-list.png"); + if (!embedIframe) { + throw new Error("Embed iframe not found"); + } + const bookingId = await bookFirstEvent("free", embedIframe, page); + const booking = await getBooking(bookingId); + + expect(booking.attendees.length).toBe(1); + await deleteAllBookingsByEmail("embed-user@example.com"); }); todo("Floating Button Test with Dark Theme"); diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-chromium-darwin.png new file mode 100644 index 0000000000..f298b30a58 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-firefox-darwin.png new file mode 100644 index 0000000000..c08be36131 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-webkit-darwin.png new file mode 100644 index 0000000000..dc82183634 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-1-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-chromium-darwin.png new file mode 100644 index 0000000000..159f47fb86 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-firefox-darwin.png new file mode 100644 index 0000000000..3d4e9b15b0 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-webkit-darwin.png new file mode 100644 index 0000000000..3f2346ebac Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/availability-page-2-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-chromium-darwin.png new file mode 100644 index 0000000000..82be5124ca Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-firefox-darwin.png new file mode 100644 index 0000000000..39fd4db557 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-webkit-darwin.png new file mode 100644 index 0000000000..218b70b085 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/booking-page-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-chromium-darwin.png new file mode 100644 index 0000000000..6dc4e850d8 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-firefox-darwin.png new file mode 100644 index 0000000000..be6246fd0d Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-webkit-darwin.png new file mode 100644 index 0000000000..7ffc42a15d Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/event-types-list-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-chromium-darwin.png new file mode 100644 index 0000000000..c0774d3594 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-firefox-darwin.png new file mode 100644 index 0000000000..b7acdc12a4 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-webkit-darwin.png new file mode 100644 index 0000000000..cde99813a8 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/action-based.test.ts-snapshots/success-page-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts b/packages/embeds/embed-core/playwright/tests/inline.test.ts index ac64910058..90430887e9 100644 --- a/packages/embeds/embed-core/playwright/tests/inline.test.ts +++ b/packages/embeds/embed-core/playwright/tests/inline.test.ts @@ -1,13 +1,14 @@ import { expect, Frame } from "@playwright/test"; import { test } from "../fixtures/fixtures"; -import { todo, getEmbedIframe } from "../lib/testUtils"; +import { todo, getEmbedIframe, bookFirstEvent, deleteAllBookingsByEmail } from "../lib/testUtils"; test("Inline Iframe - Configured with Dark Theme", async ({ page, getActionFiredDetails, addEmbedListeners, }) => { + await deleteAllBookingsByEmail("embed-user@example.com"); await addEmbedListeners(""); await page.goto("/?only=ns:default"); const embedIframe = await getEmbedIframe({ page, pathname: "/pro" }); @@ -17,6 +18,12 @@ test("Inline Iframe - Configured with Dark Theme", async ({ theme: "dark", }, }); + expect(await page.screenshot()).toMatchSnapshot("event-types-list.png"); + if (!embedIframe) { + throw new Error("Embed iframe not found"); + } + await bookFirstEvent("pro", embedIframe, page); + await deleteAllBookingsByEmail("embed-user@example.com"); }); todo( diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-chromium-darwin.png new file mode 100644 index 0000000000..14dbce416f Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-firefox-darwin.png new file mode 100644 index 0000000000..ec14f32353 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-webkit-darwin.png new file mode 100644 index 0000000000..69180b3237 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-1-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-chromium-darwin.png new file mode 100644 index 0000000000..581c984197 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-firefox-darwin.png new file mode 100644 index 0000000000..a8b9fa9c7f Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-webkit-darwin.png new file mode 100644 index 0000000000..8650202b9b Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/availability-page-2-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-chromium-darwin.png new file mode 100644 index 0000000000..88db0167eb Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-firefox-darwin.png new file mode 100644 index 0000000000..978fc961af Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-webkit-darwin.png new file mode 100644 index 0000000000..641bb71f40 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/booking-page-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-chromium-darwin.png new file mode 100644 index 0000000000..302b6f43d9 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-firefox-darwin.png new file mode 100644 index 0000000000..7ab768fe11 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-webkit-darwin.png new file mode 100644 index 0000000000..7f6a09430e Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/event-types-list-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-chromium-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-chromium-darwin.png new file mode 100644 index 0000000000..ba1b0681a2 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-chromium-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-firefox-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-firefox-darwin.png new file mode 100644 index 0000000000..488fbca4bb Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-firefox-darwin.png differ diff --git a/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-webkit-darwin.png b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-webkit-darwin.png new file mode 100644 index 0000000000..0125193127 Binary files /dev/null and b/packages/embeds/embed-core/playwright/tests/inline.test.ts-snapshots/success-page-webkit-darwin.png differ diff --git a/packages/embeds/embed-core/src/FloatingButton.ts b/packages/embeds/embed-core/src/FloatingButton.ts deleted file mode 100644 index 670a7008ad..0000000000 --- a/packages/embeds/embed-core/src/FloatingButton.ts +++ /dev/null @@ -1,33 +0,0 @@ -import tailwindCss from "./tailwind.css"; - -export class FloatingButton extends HTMLElement { - constructor() { - super(); - const buttonHtml = ` - <style> - ${tailwindCss} - </style> - <button - class="fixed bottom-4 right-4 flex h-16 origin-center transform cursor-pointer items-center rounded-full py-4 px-6 text-base outline-none drop-shadow-md transition transition-all focus:outline-none focus:ring-4 focus:ring-gray-600 focus:ring-opacity-50 active:scale-95 md:bottom-6 md:right-10" - style="background-color: rgb(255, 202, 0); color: rgb(20, 30, 47); z-index: 10001"> - <div class="mr-3 flex items-center justify-center"> - <svg - class="h-7 w-7" - fill="none" - stroke="currentColor" - viewBox="0 0 24 24" - xmlns="http://www.w3.org/2000/svg"> - <path - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> - </svg> - </div> - <div class="font-semibold leading-5 antialiased">Book my Cal</div> - </button>`; - this.attachShadow({ mode: "open" }); - - this.shadowRoot!.innerHTML = buttonHtml; - } -} diff --git a/packages/embeds/embed-core/src/FloatingButton/FloatingButton.ts b/packages/embeds/embed-core/src/FloatingButton/FloatingButton.ts new file mode 100644 index 0000000000..01d3e25b6d --- /dev/null +++ b/packages/embeds/embed-core/src/FloatingButton/FloatingButton.ts @@ -0,0 +1,12 @@ +import { CalWindow } from "@calcom/embed-snippet"; + +import floatingButtonHtml from "./FloatingButtonHtml"; + +export class FloatingButton extends HTMLElement { + constructor() { + super(); + const buttonHtml = `<style>${(window as CalWindow).Cal!.__css}</style> ${floatingButtonHtml}`; + this.attachShadow({ mode: "open" }); + this.shadowRoot!.innerHTML = buttonHtml; + } +} diff --git a/packages/embeds/embed-core/src/FloatingButton/FloatingButtonHtml.ts b/packages/embeds/embed-core/src/FloatingButton/FloatingButtonHtml.ts new file mode 100644 index 0000000000..792032d879 --- /dev/null +++ b/packages/embeds/embed-core/src/FloatingButton/FloatingButtonHtml.ts @@ -0,0 +1,22 @@ +const html = `<button class="fixed bottom-4 right-4 flex h-16 origin-center bg-red-50 transform cursor-pointer items-center +rounded-full py-4 px-6 text-base outline-none drop-shadow-md transition focus:outline-none fo +cus:ring-4 focus:ring-gray-600 focus:ring-opacity-50 active:scale-95 md:bottom-6 md:right-10" +style="background-color: rgb(255, 202, 0); color: rgb(20, 30, 47); z-index: 10001"> +<div class="mr-3 flex items-center justify-center"> + <svg + class="h-7 w-7" + fill="none" + stroke="currentColor" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg"> + <path + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="2" + d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path> + </svg> +</div> +<div class="font-semibold leading-5 antialiased">Book my Cal</div> +</button>`; + +export default html; diff --git a/packages/embeds/embed-core/src/inline.ts b/packages/embeds/embed-core/src/Inline/inline.ts similarity index 50% rename from packages/embeds/embed-core/src/inline.ts rename to packages/embeds/embed-core/src/Inline/inline.ts index 23e804f9a3..e17e929115 100644 --- a/packages/embeds/embed-core/src/inline.ts +++ b/packages/embeds/embed-core/src/Inline/inline.ts @@ -1,5 +1,7 @@ -import loaderCss from "./loader.css"; -import tailwindCss from "./tailwind.css"; +import { CalWindow } from "@calcom/embed-snippet"; + +import loaderCss from "../loader.css"; +import inlineHtml from "./inlineHtml"; export class Inline extends HTMLElement { //@ts-ignore @@ -14,14 +16,8 @@ export class Inline extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); - this.shadowRoot!.innerHTML = ` - <style> ${tailwindCss}${loaderCss}</style> - <div id="loader" style="left:0;right:0" class="absolute z-highest flex h-screen w-full items-center"> - <div class="loader border-brand dark:border-darkmodebrand"> - <span class="loader-inner bg-brand dark:bg-darkmodebrand"></span> - </div> - </div> -<slot></slot> -`; + this.shadowRoot!.innerHTML = `<style>${ + (window as CalWindow).Cal!.__css + }</style><style>${loaderCss}</style>${inlineHtml}`; } } diff --git a/packages/embeds/embed-core/src/Inline/inlineHtml.ts b/packages/embeds/embed-core/src/Inline/inlineHtml.ts new file mode 100644 index 0000000000..80a87b2afa --- /dev/null +++ b/packages/embeds/embed-core/src/Inline/inlineHtml.ts @@ -0,0 +1,7 @@ +const html = `<div id="loader" style="top:calc(50% - 30px); left:calc(50% - 30px)" class="absolute z-highest"> +<div class="loader border-brand dark:border-darkmodebrand"> + <span class="loader-inner bg-brand dark:bg-darkmodebrand"></span> +</div> +</div> +<slot></slot>`; +export default html; diff --git a/packages/embeds/embed-core/src/ModalBox.ts b/packages/embeds/embed-core/src/ModalBox.ts deleted file mode 100644 index ce56c8d10f..0000000000 --- a/packages/embeds/embed-core/src/ModalBox.ts +++ /dev/null @@ -1,126 +0,0 @@ -import loaderCss from "./loader.css"; -import tailwindCss from "./tailwind.css"; - -export class ModalBox extends HTMLElement { - static htmlOverflow: string; - //@ts-ignore - static get observedAttributes() { - return ["state"]; - } - - show(show: boolean) { - // We can't make it display none as that takes iframe width and height calculations to 0 - (this.shadowRoot!.host as unknown as any).style.visibility = show ? "visible" : "hidden"; - } - - close() { - this.show(false); - document.body.style.overflow = ModalBox.htmlOverflow; - } - - attributeChangedCallback(name: string, oldValue: string, newValue: string) { - if (name !== "state") { - return; - } - - if (newValue == "loaded") { - (this.shadowRoot!.querySelector("#loader")! as HTMLElement).style.display = "none"; - } else if (newValue === "started") { - this.show(true); - } - } - - connectedCallback() { - const closeEl = this.shadowRoot!.querySelector(".close") as HTMLElement; - - this.shadowRoot!.host.addEventListener("click", (e) => { - this.close(); - }); - - closeEl.onclick = () => { - this.close(); - }; - } - - constructor() { - super(); - //FIXME: this styling goes as is as it's a JS string. That's a lot of unnecessary whitespaces over the wire. - const modalHtml = ` - <style> ${tailwindCss} - .backdrop { - position:fixed; - width:100%; - height:100%; - top:0; - left:0; - z-index:99999999; - display:block; - background-color:rgb(5,5,5, 0.8) - } - - @media only screen and (min-width:600px) { - .modal-box { - margin:0 auto; - margin-top:20px; - margin-bottom:20px; - position:absolute; - width:50%; - top:50%; - left:50%; - transform: translateY(-50%) translateX(-50%); - overflow: scroll; - } - } - - @media only screen and (max-width:600px) { - .modal-box { - width: 100%; - height: 80%; - position:fixed; - top:50px; - left:0; - right: 0; - margin: 0; - } - } - - .header { - position: relative; - float:right; - top: 10px; - } - .close { - font-size: 30px; - left: -20px; - position: relative; - color:white; - cursor: pointer; - } - .loader { - --cal-brand-border-color: white; - --cal-brand-background-color: white; - } - ${loaderCss} - </style> - <div class="backdrop"> - <div class="header"> - <span class="close">×</span> - </div> - <div class="modal-box"> - <div class="body"> - <div id="loader" class="absolute z-highest flex h-screen w-full items-center"> - <div class="loader border-brand dark:border-darkmodebrand"> - <span class="loader-inner bg-brand dark:bg-darkmodebrand"></span> - </div> - </div> - <slot></slot> - </div> - </div> - </div> - `; - this.attachShadow({ mode: "open" }); - ModalBox.htmlOverflow = document.body.style.overflow; - document.body.style.overflow = "hidden"; - this.shadowRoot!.innerHTML = modalHtml; - } -} diff --git a/packages/embeds/embed-core/src/ModalBox/ModalBox.ts b/packages/embeds/embed-core/src/ModalBox/ModalBox.ts new file mode 100644 index 0000000000..a6e6a2b2df --- /dev/null +++ b/packages/embeds/embed-core/src/ModalBox/ModalBox.ts @@ -0,0 +1,71 @@ +import { CalWindow } from "@calcom/embed-snippet"; + +import loaderCss from "../loader.css"; +import modalBoxHtml from "./ModalBoxHtml"; + +export class ModalBox extends HTMLElement { + static htmlOverflow: string; + //@ts-ignore + static get observedAttributes() { + return ["state"]; + } + + show(show: boolean) { + // We can't make it display none as that takes iframe width and height calculations to 0 + (this.shadowRoot!.host as unknown as any).style.visibility = show ? "visible" : "hidden"; + if (!show) { + document.body.style.overflow = ModalBox.htmlOverflow; + } + } + + close() { + this.show(false); + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name !== "state") { + return; + } + + if (newValue == "loaded") { + (this.shadowRoot!.querySelector("#loader")! as HTMLElement).style.display = "none"; + } else if (newValue === "started") { + this.show(true); + } else if (newValue == "closed") { + this.show(false); + } + } + + connectedCallback() { + const closeEl = this.shadowRoot!.querySelector(".close") as HTMLElement; + document.addEventListener( + "keydown", + (e) => { + if (e.key === "Escape") { + this.close(); + } + }, + { + once: true, + } + ); + this.shadowRoot!.host.addEventListener("click", (e) => { + this.close(); + }); + + closeEl.onclick = () => { + this.close(); + }; + } + + constructor() { + super(); + const modalHtml = `<style>${ + (window as CalWindow).Cal!.__css + }</style><style>${loaderCss}</style>${modalBoxHtml}`; + this.attachShadow({ mode: "open" }); + ModalBox.htmlOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + this.shadowRoot!.innerHTML = modalHtml; + } +} diff --git a/packages/embeds/embed-core/src/ModalBox/ModalBoxHtml.ts b/packages/embeds/embed-core/src/ModalBox/ModalBoxHtml.ts new file mode 100644 index 0000000000..6222e91abf --- /dev/null +++ b/packages/embeds/embed-core/src/ModalBox/ModalBoxHtml.ts @@ -0,0 +1,72 @@ +const html = `<style> +.my-backdrop { + position:fixed; + width:100%; + height:100%; + top:0; + left:0; + z-index:99999999; + display:block; + background-color:rgb(5,5,5, 0.8) +} + +@media only screen and (min-width:600px) { + .modal-box { + margin:0 auto; + margin-top:20px; + margin-bottom:20px; + position:absolute; + width:100%; + top:50%; + left:50%; + transform: translateY(-50%) translateX(-50%); + overflow: scroll; + } +} + +@media only screen and (max-width:600px) { + .modal-box { + width: 100%; + height: 80%; + position:fixed; + top:50px; + left:0; + right: 0; + margin: 0; + } +} + +.header { + position: relative; + float:right; + top: 10px; +} +.close { + font-size: 30px; + left: -20px; + position: relative; + color:white; + cursor: pointer; +} +/*Modal background is black only, so hardcode white */ +.loader { + --cal-brand-color:white; +} +</style> +<div class="my-backdrop"> +<div class="header"> + <span class="close">×</span> +</div> +<div class="modal-box"> + <div class="body"> + <div id="loader" class="z-[999999999999] absolute flex w-full items-center"> + <div class="loader modal-loader border-brand dark:border-darkmodebrand"> + <span class="loader-inner bg-brand dark:bg-darkmodebrand"></span> + </div> + </div> + <slot></slot> + </div> +</div> +</div>`; + +export default html; diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index c41ef8c1c2..7ae474dfc7 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -12,6 +12,7 @@ const embedStore = { // Store all embed styles here so that as and when new elements are mounted, styles can be applied to it. styles: {}, namespace: null, + embedType: undefined, theme: null, // Store all React State setters here. reactStylesStateSetters: {}, @@ -21,6 +22,7 @@ const embedStore = { styles: UiConfig["styles"]; namespace: string | null; theme: string | null; + embedType: undefined | null | string; reactStylesStateSetters: any; parentInformedAboutContentHeight: boolean; windowLoadEventFired: boolean; @@ -84,7 +86,9 @@ interface EmbedStyles { disabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">; availabilityDatePicker?: Pick<CSSProperties, "background" | "color" | "backgroundColor">; } -interface EmbedStylesBranding { +interface EmbedNonStylesConfig { + /** Default would be center */ + align: "left"; branding?: { brandColor?: string; lightColor?: string; @@ -97,7 +101,7 @@ interface EmbedStylesBranding { }; } -type ReactEmbedStylesSetter = React.Dispatch<React.SetStateAction<EmbedStyles | EmbedStylesBranding>>; +type ReactEmbedStylesSetter = React.Dispatch<React.SetStateAction<EmbedStyles | EmbedNonStylesConfig>>; const setEmbedStyles = (stylesConfig: UiConfig["styles"]) => { embedStore.styles = stylesConfig; @@ -111,14 +115,14 @@ const setEmbedStyles = (stylesConfig: UiConfig["styles"]) => { } }; -const registerNewSetter = (elementName: keyof EmbedStyles | keyof EmbedStylesBranding, setStyles: any) => { +const registerNewSetter = (elementName: keyof EmbedStyles | keyof EmbedNonStylesConfig, setStyles: any) => { embedStore.reactStylesStateSetters[elementName] = setStyles; // It's possible that 'ui' instruction has already been processed and the registration happened due to some action by the user in iframe. // So, we should call the setter immediately with available embedStyles setStyles(embedStore.styles); }; -const removeFromEmbedStylesSetterMap = (elementName: keyof EmbedStyles | keyof EmbedStylesBranding) => { +const removeFromEmbedStylesSetterMap = (elementName: keyof EmbedStyles | keyof EmbedNonStylesConfig) => { delete embedStore.reactStylesStateSetters[elementName]; }; @@ -128,6 +132,12 @@ function isValidNamespace(ns: string | null | undefined) { export const useEmbedTheme = () => { const router = useRouter(); + useEffect(() => { + router.events.on("routeChangeComplete", () => { + sdkActionManager?.fire("__routeChanged", {}); + }); + }, [router.events]); + if (embedStore.theme) { return embedStore.theme; } @@ -151,8 +161,8 @@ export const useEmbedStyles = (elementName: keyof EmbedStyles) => { return styles[elementName] || {}; }; -export const useEmbedBranding = (elementName: keyof EmbedStylesBranding) => { - const [styles, setStyles] = useState({} as EmbedStylesBranding); +export const useEmbedNonStylesConfig = (elementName: keyof EmbedNonStylesConfig) => { + const [styles, setStyles] = useState({} as EmbedNonStylesConfig); useEffect(() => { registerNewSetter(elementName, setStyles); @@ -171,7 +181,7 @@ export const useIsBackgroundTransparent = () => { // TODO: Background should be read as ui.background and not ui.body.background const bodyEmbedStyles = useEmbedStyles("body"); - if (bodyEmbedStyles?.background === "transparent") { + if (bodyEmbedStyles.background === "transparent") { isBackgroundTransparent = true; } return isBackgroundTransparent; @@ -179,8 +189,8 @@ export const useIsBackgroundTransparent = () => { export const useBrandColors = () => { // TODO: Branding shouldn't be part of ui.styles. It should exist as ui.branding. - const brandingColors = useEmbedBranding("branding"); - return brandingColors; + const brandingColors = useEmbedNonStylesConfig("branding") as EmbedNonStylesConfig["branding"]; + return brandingColors || {}; }; function getNamespace() { @@ -196,6 +206,17 @@ function getNamespace() { } } +function getEmbedType() { + if (embedStore.embedType) { + return embedStore.embedType; + } + if (isBrowser) { + const url = new URL(document.URL); + const embedType = (embedStore.embedType = url.searchParams.get("embedType")); + return embedType; + } +} + const isEmbed = () => { const namespace = getNamespace(); const _isValidNamespace = isValidNamespace(namespace); @@ -218,6 +239,14 @@ export const useIsEmbed = () => { return _isEmbed; }; +export const useEmbedType = () => { + const [state, setState] = useState<string | null | undefined>(null); + useEffect(() => { + setState(getEmbedType()); + }, []); + return state; +}; + function unhideBody() { document.body.style.display = "block"; } @@ -300,9 +329,13 @@ function keepParentInformedAboutDimensionChanges() { embedStore.windowLoadEventFired = true; // Use the dimensions of main element as in most places there is max-width restriction on it and we just want to show the main content. // It avoids the unwanted padding outside main tag. - const mainElement = document.getElementsByTagName("main")[0] || document.documentElement; + const mainElement = + (document.getElementsByClassName("main")[0] as HTMLElement) || + document.getElementsByTagName("main")[0] || + document.documentElement; const documentScrollHeight = document.documentElement.scrollHeight; const documentScrollWidth = document.documentElement.scrollWidth; + const contentHeight = mainElement.offsetHeight; const contentWidth = mainElement.offsetWidth; @@ -331,10 +364,6 @@ function keepParentInformedAboutDimensionChanges() { // Parent Counterpart would change the dimension of iframe and thus page's dimension would be impacted which is recursive. // It should stop ideally by reaching a hiddenHeight value of 0. // FIXME: If 0 can't be reached we need to just abandon our quest for perfect iframe and let scroll be there. Such case can be logged in the wild and fixed later on. - if (numDimensionChanges > 50) { - console.warn("Too many dimension changes detected."); - return; - } runAsap(informAboutScroll); }); } @@ -361,10 +390,16 @@ if (isBrowser) { // Because on cal-iframe we set explicty width to make it look inline and part of page, there is never space available for content to automatically expand // This is a HACK to quickly tell iframe to go full width and let iframe content adapt to that and set new width. sdkActionManager?.on("__refreshWidth", () => { - sdkActionManager?.fire("__dimensionChanged", { - iframeWidth: 100, - __unit: "%", - }); + // sdkActionManager?.fire("__dimensionChanged", { + // iframeWidth: 100, + // __unit: "%", + // }); + // runAsap(() => { + // sdkActionManager?.fire("__dimensionChanged", { + // iframeWidth: 100, + // __unit: "%", + // }); + // }); }); window.addEventListener("message", (e) => { @@ -378,6 +413,19 @@ if (isBrowser) { } }); + document.addEventListener("click", (e) => { + if (!e.target) { + return; + } + const mainElement = + (document.getElementsByClassName("main")[0] as HTMLElement) || + document.getElementsByTagName("main")[0] || + document.documentElement; + if ((e.target as HTMLElement).contains(mainElement)) { + sdkActionManager?.fire("__closeIframe", {}); + } + }); + if (!pageStatus || pageStatus == "200") { keepParentInformedAboutDimensionChanges(); sdkActionManager?.fire("__iframeReady", {}); diff --git a/packages/embeds/embed-core/src/embed.css b/packages/embeds/embed-core/src/embed.css index 08b796dcf5..0792fdb415 100644 --- a/packages/embeds/embed-core/src/embed.css +++ b/packages/embeds/embed-core/src/embed.css @@ -4,4 +4,6 @@ .cal-embed { border: 0px; min-height: 300px; + margin: 0 auto; + width: 100%; } diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index aa2383112a..77f2909679 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -1,26 +1,29 @@ import type { CalWindow } from "@calcom/embed-snippet"; -import { FloatingButton } from "./FloatingButton"; -import { ModalBox } from "./ModalBox"; +import { FloatingButton } from "./FloatingButton/FloatingButton"; +import { Inline } from "./Inline/inline"; +import { ModalBox } from "./ModalBox/ModalBox"; import { methods, UiConfig } from "./embed-iframe"; import css from "./embed.css"; -import { Inline } from "./inline"; import { SdkActionManager } from "./sdk-action-manager"; +import allCss from "./tailwind.generated.css"; + +customElements.define("cal-modal-box", ModalBox); +customElements.define("cal-floating-button", FloatingButton); +customElements.define("cal-inline", Inline); declare module "*.css"; - type Namespace = string; type Config = { origin: string; - debug: 1; + debug?: boolean; }; const globalCal = (window as CalWindow).Cal; - if (!globalCal || !globalCal.q) { throw new Error("Cal is not defined. This shouldn't happen"); } - +globalCal.__css = allCss; document.head.appendChild(document.createElement("style")).innerHTML = css; function log(...args: any[]) { @@ -75,7 +78,7 @@ export type InstructionQueue = Instruction[]; export class Cal { iframe?: HTMLIFrameElement; - __config: any; + __config: Config; modalBox!: Element; @@ -96,7 +99,7 @@ export class Cal { return { ...config, // guests is better for API but Booking Page accepts guest. So do the mapping - guest: config.guests ?? "", + guest: config.guests ?? undefined, }; } @@ -141,27 +144,35 @@ export class Cal { queryObject = {}, }: { calLink: string; - queryObject?: Record<string, string | string[]>; + queryObject?: Record<string, string | string[] | Record<string, string>>; }) { const iframe = (this.iframe = document.createElement("iframe")); iframe.className = "cal-embed"; iframe.name = "cal-embed"; const config = this.getConfig(); + const { iframeAttrs, ...restQueryObject } = queryObject; + + if (iframeAttrs && typeof iframeAttrs !== "string" && !(iframeAttrs instanceof Array)) { + iframe.setAttribute("id", iframeAttrs.id); + } // Prepare searchParams from config const searchParams = new URLSearchParams(); - for (const [key, value] of Object.entries(queryObject)) { + for (const [key, value] of Object.entries(restQueryObject)) { + if (value === undefined) { + continue; + } if (value instanceof Array) { value.forEach((val) => searchParams.append(key, val)); } else { - searchParams.set(key, value); + searchParams.set(key, value as string); } } const urlInstance = new URL(`${config.origin}/${calLink}`); urlInstance.searchParams.set("embed", this.namespace); if (config.debug) { - urlInstance.searchParams.set("debug", config.debug); + urlInstance.searchParams.set("debug", "" + config.debug); } // Merge searchParams from config onto the URL which might have query params already @@ -219,6 +230,16 @@ export class Cal { }, }, }); + 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). + // Best solution might be to autoscroll only if the iframe is not fully visible, detection of full visibility might be tough + + // We need to keep in mind that autoscroll is meant to solve the problem when on a certain view(which is availability page right now), the height goes too high and then suddenly it becomes normal + (config as unknown as any).__autoScroll = !!(config as unknown as any).__autoScroll; + config.embedType = "inline"; const iframe = this.createIframe({ calLink, queryObject: Cal.getQueryObject(config) }); iframe.style.height = "100%"; iframe.style.width = "100%"; @@ -230,8 +251,9 @@ export class Cal { throw new Error("Element not found"); } const template = document.createElement("template"); - template.innerHTML = `<cal-inline style="max-height:inherit;height:inherit;min-height:inherit;display:block;position:relative"></cal-inline>`; + template.innerHTML = `<cal-inline style="max-height:inherit;height:inherit;min-height:inherit;display:flex;position:relative;flex-wrap:wrap"></cal-inline>`; this.inlineEl = template.content.children[0]; + (this.inlineEl as unknown as any).__CalAutoScroll = config.__autoScroll; this.inlineEl.appendChild(iframe); element.appendChild(template.content); } @@ -247,7 +269,7 @@ export class Cal { }, }); const template = document.createElement("template"); - template.innerHTML = `<cal-floating-button data-cal-namespace=${this.namespace} data-cal-link=${calLink}></cal-floating-button>`; + template.innerHTML = `<cal-floating-button data-cal-namespace="${this.namespace}" data-cal-link="${calLink}"></cal-floating-button>`; document.body.appendChild(template.content); } @@ -257,6 +279,7 @@ export class Cal { existingModalEl.setAttribute("state", "started"); return; } + config.embedType = "modal"; const iframe = this.createIframe({ calLink, queryObject: Cal.getQueryObject(config) }); iframe.style.borderRadius = "8px"; @@ -264,8 +287,12 @@ export class Cal { iframe.style.width = "100%"; const template = document.createElement("template"); template.innerHTML = `<cal-modal-box uid="${uid}"></cal-modal-box>`; + this.modalBox = template.content.children[0]; this.modalBox.appendChild(iframe); + this.actionManager.on("__closeIframe", () => { + this.modalBox.setAttribute("state", "closed"); + }); document.body.appendChild(template.content); } @@ -348,7 +375,7 @@ export class Cal { constructor(namespace: string, q: InstructionQueue) { this.__config = { // Keep cal.com hardcoded till the time embed.js deployment to cal.com/embed.js is automated. This is to prevent accidentally pushing of localhost domain to production - origin: /*import.meta.env.NEXT_PUBLIC_WEBSITE_URL || */ "https://cal.com", + origin: /*import.meta.env.NEXT_PUBLIC_WEBSITE_URL || */ "https://app.cal.com", }; this.namespace = namespace; this.actionManager = new SdkActionManager(namespace); @@ -377,9 +404,9 @@ export class Cal { iframe.style.height = data.iframeHeight + unit; } - if (data.iframeWidth) { - iframe.style.width = data.iframeWidth + unit; - } + // if (data.iframeWidth) { + // iframe.style.width = data.iframeWidth + unit; + // } if (this.modalBox) { // It ensures that if the iframe is so tall that it can't fit in the parent window without scroll. Then force the scroll by restricting the max-height to innerHeight @@ -399,6 +426,13 @@ export class Cal { this.doInIframe({ method, arg }); }); }); + + this.actionManager.on("__routeChanged", () => { + if (this.inlineEl && (this.inlineEl as unknown as any).__CalAutoScroll) { + this.inlineEl.scrollIntoView(); + } + }); + this.actionManager.on("linkReady", (e) => { this.modalBox?.setAttribute("state", "loaded"); this.inlineEl?.setAttribute("loading", "done"); @@ -455,13 +489,12 @@ document.addEventListener("click", (e) => { if (namespace) { api = globalCal.ns![namespace]; } + if (!api) { + throw new Error(`Namespace ${namespace} isn't defined`); + } api("modal", { calLink: path, config, uid: modalUniqueId, }); }); - -customElements.define("cal-modal-box", ModalBox); -customElements.define("cal-floating-button", FloatingButton); -customElements.define("cal-inline", Inline); diff --git a/packages/embeds/embed-core/src/loader.css b/packages/embeds/embed-core/src/loader.css index f22b9aa882..61e163d59e 100644 --- a/packages/embeds/embed-core/src/loader.css +++ b/packages/embeds/embed-core/src/loader.css @@ -53,10 +53,13 @@ display: block; width: 30px; height: 30px; - margin: 60px auto; position: relative; border-width: 4px; border-style: solid; -webkit-animation: loader 2s infinite ease; animation: loader 2s infinite ease; -} + } + +.loader.modal-loader { + margin: 60px auto; +} \ No newline at end of file diff --git a/packages/embeds/embed-core/src/styles.css b/packages/embeds/embed-core/src/styles.css new file mode 100644 index 0000000000..b785bf715b --- /dev/null +++ b/packages/embeds/embed-core/src/styles.css @@ -0,0 +1,22 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +@font-face { + font-family: 'Cal Sans'; + src: url("https://cal.com/cal.ttf"); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Cal Sans'; + font-weight: normal; + letter-spacing: normal; +} + +html, body, :host { + font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji +} \ No newline at end of file diff --git a/packages/embeds/embed-core/src/tailwind.css b/packages/embeds/embed-core/src/tailwind.css deleted file mode 100644 index a00ff918da..0000000000 --- a/packages/embeds/embed-core/src/tailwind.css +++ /dev/null @@ -1,203 +0,0 @@ -* { - -tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; -} - -.bg-gray-50 { - --tw-bg-opacity: 1; - background-color: rgb(248 248 248 / var(--tw-bg-opacity)); -} - -.justify-center { - justify-content: center; -} - -.items-center { - align-items: center; -} - -.antialiased { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.leading-5 { - line-height: 1.25rem; -} -.w-7 { - width: 1.75rem; -} - -.h-7 { - height: 1.75rem; -} - -.font-semibold { - font-weight: 600; -} -.flex { - display: flex; -} - -.antialiased { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.leading-5 { - line-height: 1.25rem; -} -.font-semibold { - font-weight: 600; -} -.mr-3 { - margin-right: 0.75rem; -} - -.items-center { - align-items: center; -} - -.w-full { - width: 100%; -} - -.h-screen { - height: 100%; -} -.flex { - display: flex; -} -.z-highest { - z-index: 500000000; -} -.absolute { - position: absolute; -} - -.border-brand { - border-color:var(--cal-brand-border-color); -} - -.bg-brand { - background-color: var(--cal-brand-background-color); -} - -@media (min-width: 768px) { - .md\:right-10 { - right: 2.5rem; - } - - .md\:bottom-6 { - bottom: 1.5rem; - } -} - -.transition-all { - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.transition { - transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, - filter, -webkit-text-decoration-color, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, - box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, - box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - -.drop-shadow-md { - --tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) - var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); -} - -.outline-none { - outline: 2px solid transparent; - outline-offset: 2px; -} - -.text-base { - font-size: 1rem; - line-height: 1.5rem; -} - -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} - -.px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; -} - -.rounded-full { - border-radius: 9999px; -} - -.items-center { - align-items: center; -} - -.cursor-pointer { - cursor: pointer; -} - -.transform { - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) - skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.origin-center { - transform-origin: center; -} - -.h-16 { - height: 4rem; -} - -.flex { - display: flex; -} - -.right-4 { - right: 1rem; -} - -.bottom-4 { - bottom: 1rem; -} - -.fixed { - position: fixed; -} -.relative { - position: relative; -} diff --git a/packages/embeds/embed-core/tailwind.config.js b/packages/embeds/embed-core/tailwind.config.js new file mode 100644 index 0000000000..753dac72c2 --- /dev/null +++ b/packages/embeds/embed-core/tailwind.config.js @@ -0,0 +1,17 @@ +const base = require("@calcom/config/tailwind-preset"); + +module.exports = { + ...base, + content: ["**/*Html.ts"], + theme: { + ...base.theme, + extend: { + ...base.theme.extend, + colors: { + ...base.theme.extend.colors, + // Set default as black + brand: "var(--cal-brand-color, black)", + }, + }, + }, +}; diff --git a/packages/embeds/embed-core/tsconfig.json b/packages/embeds/embed-core/tsconfig.json index e9b64ffd76..bd3b3e6f54 100644 --- a/packages/embeds/embed-core/tsconfig.json +++ b/packages/embeds/embed-core/tsconfig.json @@ -2,6 +2,9 @@ "extends": "@calcom/tsconfig/base.json", "compilerOptions": { "module": "esnext", + "paths": { + "@lib/*": ["../../../apps/web/lib/*"] + }, "moduleResolution": "Node", "baseUrl": "." }, diff --git a/packages/embeds/embed-core/vite.config.js b/packages/embeds/embed-core/vite.config.js index 6a47400aa3..8f4610ff4c 100644 --- a/packages/embeds/embed-core/vite.config.js +++ b/packages/embeds/embed-core/vite.config.js @@ -2,7 +2,6 @@ require("dotenv").config({ path: "../../../.env" }); const path = require("path"); const { defineConfig } = require("vite"); - module.exports = defineConfig({ envPrefix: "NEXT_PUBLIC_", build: { diff --git a/packages/embeds/embed-react/.gitignore b/packages/embeds/embed-react/.gitignore new file mode 100644 index 0000000000..e247097780 --- /dev/null +++ b/packages/embeds/embed-react/.gitignore @@ -0,0 +1,2 @@ +.turbo +dist \ No newline at end of file diff --git a/packages/embeds/embed-react/README.md b/packages/embeds/embed-react/README.md index 5463129da1..ab61cf6c47 100644 --- a/packages/embeds/embed-react/README.md +++ b/packages/embeds/embed-react/README.md @@ -2,4 +2,13 @@ Embed Cal Link as a React Component -To know how to use it, follow the steps at <https://docs.cal.com/integrations/embed> \ No newline at end of file +To know how to use it, follow the steps at <https://docs.cal.com/integrations/embed> + +TODO + +- Playwright tests. + - Need to what these tests should be as embed-core already have tests. We probably just need to verify that embed-core API is called appropriately. + - It would probably be better if Playwright tests exist at one place for all embeds. +- Distribution + - It would be better DX to serve the unbuilt version with JSX, instead of built version with React.createElement calls. But because of WebPack loaders not running on node_modules automatically, it doesn't work automatically. + - Right now if a typescript project uses the package, VSCode takes the user to .d.ts files instead of the functions definitions. How to solve it ? \ No newline at end of file diff --git a/packages/embeds/embed-react/package.json b/packages/embeds/embed-react/package.json index a3b430b6e7..0fc3bc5cf3 100644 --- a/packages/embeds/embed-react/package.json +++ b/packages/embeds/embed-react/package.json @@ -3,18 +3,42 @@ "version": "1.0.1", "description": "Embed Cal Link as a React Component", "scripts": { - "dev": "vite --port=3003 --open", - "build": "vite build", + "dev": "vite --port=3101 --open", + "tsc": "tsc", + "build": "vite build && yarn tsc --emitDeclarationOnly --declarationDir dist", "preview": "vite preview", + "prepare": "yarn build", "type-check": "tsc --pretty --noEmit", - "lint": "eslint --ext .ts,.js,.tsx,.jsx ./src" + "lint": "eslint --ext .ts,.js,.tsx,.jsx ./src", + "embed-tests": "yarn playwright test --config=./playwright/config/playwright.config.ts", + "embed-tests-quick": "QUICK=true yarn embed-tests" }, - "main": "src/Cal.tsx", - "dependencies": { - "@calcom/embed-snippet": "^1.0.0" + "main": "./dist/Cal.umd.js", + "module": "./dist/Cal.es.js", + "types": "./dist/src/index.d.ts", + "peerDependencies": { + "react": "^17.0.0", + "react-dom": "^17.0.0" + }, + "files": [ + "dist" + ], + "exports": { + ".": { + "import": "./dist/Cal.es.js", + "require": "./dist/Cal.umd.js" + } }, "devDependencies": { - "vite": "^2.8.6", - "eslint": "^8.10.0" + "@calcom/embed-snippet": "^1.0.0", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@vitejs/plugin-react": "^1.3.0", + "eslint": "^8.10.0", + "vite": "^2.9.5" + }, + "dependencies": { + "playwright": "^1.21.1", + "typescript": "^4.6.3" } } diff --git a/packages/embeds/embed-react/playwright/config/playwright.config.ts b/packages/embeds/embed-react/playwright/config/playwright.config.ts new file mode 100644 index 0000000000..f435c80018 --- /dev/null +++ b/packages/embeds/embed-react/playwright/config/playwright.config.ts @@ -0,0 +1,34 @@ +import { PlaywrightTestConfig, devices } from "@playwright/test"; +import path from "path"; + +//TODO: Move the common config to embed-playwright-config and let core and react use the base. Along with config there would be base fixtures and expect custom matchers as well. +import baseConfig from "@calcom/embed-core/playwright/config/playwright.config"; + +const testDir = path.join("../tests"); + +const projects = baseConfig.projects?.map((project) => { + if (!project.name) { + return {}; + } + return { + ...project, + testDir, + }; +}); + +const config: PlaywrightTestConfig = { + ...baseConfig, + webServer: { + // Start App Server manually - Can't be handled here. See https://github.com/microsoft/playwright/issues/8206 + command: "yarn workspace @calcom/embed-react dev", + port: 3101, + timeout: 60_000, + reuseExistingServer: true, + }, + use: { + ...baseConfig.use, + baseURL: "http://localhost:3101", + }, + projects, +}; +export default config; diff --git a/packages/embeds/embed-react/playwright/tests/basic.test.ts b/packages/embeds/embed-react/playwright/tests/basic.test.ts new file mode 100644 index 0000000000..b460016bff --- /dev/null +++ b/packages/embeds/embed-react/playwright/tests/basic.test.ts @@ -0,0 +1,18 @@ +import { expect } from "@playwright/test"; + +import { test } from "@calcom/embed-core/playwright/fixtures/fixtures"; +import { getEmbedIframe } from "@calcom/embed-core/playwright/lib/testUtils"; + +test("Inline Usage Snapshot", async ({ page, getActionFiredDetails, addEmbedListeners }) => { + //TODO: Do it with page.goto automatically + await addEmbedListeners(""); + await page.goto("/"); + const embedIframe = await getEmbedIframe({ page, pathname: "/pro" }); + expect(embedIframe).toBeEmbedCalLink("", getActionFiredDetails, { + pathname: "/pro", + searchParams: { + theme: "dark", + }, + }); + expect(await page.screenshot()).toMatchSnapshot("react-component-inline.png"); +}); diff --git a/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-chromium-darwin.png b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-chromium-darwin.png new file mode 100644 index 0000000000..249377ff61 Binary files /dev/null and b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-chromium-darwin.png differ diff --git a/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-darwin.png b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-darwin.png new file mode 100644 index 0000000000..d17880cf33 Binary files /dev/null and b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-darwin.png differ diff --git a/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-firefox-darwin.png b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-firefox-darwin.png new file mode 100644 index 0000000000..a96682c6fd Binary files /dev/null and b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-firefox-darwin.png differ diff --git a/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-webkit-darwin.png b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-webkit-darwin.png new file mode 100644 index 0000000000..e50464ce78 Binary files /dev/null and b/packages/embeds/embed-react/playwright/tests/basic.test.ts-snapshots/react-component-inline-webkit-darwin.png differ diff --git a/packages/embeds/embed-react/src/Cal.tsx b/packages/embeds/embed-react/src/Cal.tsx index 08f91ddb49..af49c77541 100644 --- a/packages/embeds/embed-react/src/Cal.tsx +++ b/packages/embeds/embed-react/src/Cal.tsx @@ -13,12 +13,17 @@ export default function Cal({ config?: any; embedJsUrl?: string; }) { + if (!calLink) { + throw new Error("calLink is required"); + } + const initializedRef = useRef(false); const Cal = useEmbed(embedJsUrl); const ref = useRef<HTMLDivElement>(null); useEffect(() => { - if (!Cal) { + if (!Cal || initializedRef.current) { return; } + initializedRef.current = true; const element = ref.current; let initConfig = {}; if (calOrigin) { @@ -30,9 +35,6 @@ export default function Cal({ calLink, config, }); - return () => { - element?.querySelector(".cal-embed")?.remove(); - }; }, [Cal, calLink, config, calOrigin]); if (!Cal) { diff --git a/packages/embeds/embed-react/src/index.ts b/packages/embeds/embed-react/src/index.ts new file mode 100644 index 0000000000..85e2f4efc3 --- /dev/null +++ b/packages/embeds/embed-react/src/index.ts @@ -0,0 +1,3 @@ +import Cal from "./Cal"; + +export default Cal; diff --git a/packages/embeds/embed-react/test-cal.tsx b/packages/embeds/embed-react/test-cal.tsx index 9bc56e5c56..c5d8ae29f4 100644 --- a/packages/embeds/embed-react/test-cal.tsx +++ b/packages/embeds/embed-react/test-cal.tsx @@ -1,8 +1,15 @@ +import { useEffect } from "react"; +import { useState } from "react"; import ReactDom from "react-dom"; -import Cal from "@calcom/embed-react"; +import Cal from "./src/index"; function App() { + const [loaded, setLoaded] = useState(false); + useEffect(() => { + // Simulate state change causing config object to change, causing rerender of Cal + setTimeout(setLoaded.bind(true), 1000); + }, []); return ( <> <h1> @@ -10,7 +17,7 @@ function App() { </h1> <Cal calOrigin="http://localhost:3000" - embedJsUrl="//localhost:3002/dist/embed.umd.js" + embedJsUrl="//localhost:3100/dist/embed.umd.js" calLink="pro" config={{ name: "John Doe", diff --git a/packages/embeds/embed-react/tsconfig.json b/packages/embeds/embed-react/tsconfig.json index 2674008254..bbff4fc368 100644 --- a/packages/embeds/embed-react/tsconfig.json +++ b/packages/embeds/embed-react/tsconfig.json @@ -4,8 +4,12 @@ "module": "ESNext", "moduleResolution": "Node", "baseUrl": ".", - "jsx": "preserve" + "declaration": true, + "jsx": "preserve", + "paths": { + "@lib/*": ["../../../apps/web/lib/*"] + }, }, "include": ["."], - "exclude": ["dist", "build", "node_modules"] + "exclude": ["dist", "build", "node_modules", "test-cal.tsx"] } diff --git a/packages/embeds/embed-react/vite.config.js b/packages/embeds/embed-react/vite.config.js new file mode 100644 index 0000000000..672ead9130 --- /dev/null +++ b/packages/embeds/embed-react/vite.config.js @@ -0,0 +1,28 @@ +import react from "@vitejs/plugin-react"; +import path from "path"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + build: { + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "Cal", + fileName: (format) => `Cal.${format}.js`, + }, + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: ["react", "react-dom"], + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: { + react: "React", + "react-dom": "ReactDOM", + }, + }, + }, + }, +}); diff --git a/packages/embeds/embed-snippet/src/index.ts b/packages/embeds/embed-snippet/src/index.ts index 6a6c47da2f..7b7b63a553 100644 --- a/packages/embeds/embed-snippet/src/index.ts +++ b/packages/embeds/embed-snippet/src/index.ts @@ -2,7 +2,7 @@ * As we want to keep control on the size of this snippet but we want some portion of it to be still readable. * So, write the code that you need directly but keep it short. */ -import { Cal as CalClass, Instruction, InstructionQueue } from "@calcom/embed-core/src/embed"; +import type { Cal as CalClass, InstructionQueue } from "@calcom/embed-core/src/embed"; export interface GlobalCal { (methodName: string, arg?: any): void; @@ -13,6 +13,7 @@ export interface GlobalCal { /** If user registers multiple namespaces, those are available here */ ns?: Record<string, GlobalCal>; instance?: CalClass; + __css?: string; } export interface CalWindow extends Window { diff --git a/turbo.json b/turbo.json index 30ed195bd2..45160abd0f 100644 --- a/turbo.json +++ b/turbo.json @@ -122,6 +122,12 @@ }, "postinstall": {}, "start": {}, + "embed-tests": { + "cache": false + }, + "embed-tests-quick": { + "cache": false + }, "test": { "dependsOn": ["^test"] }, diff --git a/yarn.lock b/yarn.lock index 2bd1058c45..5fb0e2e923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -117,7 +117,7 @@ json5 "^2.1.2" semver "^6.3.0" -"@babel/core@^7.7.2", "@babel/core@^7.8.0": +"@babel/core@^7.17.9", "@babel/core@^7.7.2", "@babel/core@^7.8.0": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== @@ -626,7 +626,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.12.13": +"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== @@ -891,6 +891,38 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-react-jsx-development@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" + integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.16.7" + +"@babel/plugin-transform-react-jsx-self@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.16.7.tgz#f432ad0cba14c4a1faf44f0076c69e42a4d4479e" + integrity sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-jsx-source@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.16.7.tgz#1879c3f23629d287cc6186a6c683154509ec70c0" + integrity sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-jsx@^7.16.7", "@babel/plugin-transform-react-jsx@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" + integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.16.7" + "@babel/types" "^7.17.0" + "@babel/plugin-transform-regenerator@^7.16.7": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz#0a33c3a61cf47f45ed3232903683a0afd2d3460c" @@ -1337,7 +1369,7 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@eslint/eslintrc@^1.2.0", "@eslint/eslintrc@^1.2.1": +"@eslint/eslintrc@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== @@ -1619,7 +1651,7 @@ resolved "https://registry.yarnpkg.com/@glidejs/glide/-/glide-3.5.2.tgz#7012c5920ecf202bbda44d8526fc979984b6dd54" integrity sha512-7jGciNJ2bQ4eZLSNlSZ+VAyW63kALf420CvkEpK4lEsUfWJq9odqimci0YCiyNyMUFB+pWHwLYyNc57dijYsCg== -"@headlessui/react@^1.4.1", "@headlessui/react@^1.5.0": +"@headlessui/react@^1.4.1": version "1.5.0" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.5.0.tgz#483b44ba2c8b8d4391e1d2c863898d7dd0cc0296" integrity sha512-aaRnYxBb3MU2FNJf3Ut9RMTUqqU3as0aI1lQhgo2n9Fa67wRu14iOGqx93xB+uMNVfNwZ5B3y/Ndm7qZGuFeMQ== @@ -2434,11 +2466,6 @@ dependencies: webpack-bundle-analyzer "4.3.0" -"@next/env@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.0.tgz#73713399399b34aa5a01771fb73272b55b22c314" - integrity sha512-nrIgY6t17FQ9xxwH3jj0a6EOiQ/WDHUos35Hghtr+SWN/ntHIQ7UpuvSi0vaLzZVHQWaDupKI+liO5vANcDeTQ== - "@next/env@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.4.tgz#5af629b43075281ecd7f87938802b7cf5b67e94b" @@ -2466,11 +2493,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.1.5.tgz#36729ab3dfd7743e82cfe536b43254dcb146620c" integrity sha512-SKnGTdYcoN04Y2DvE0/Y7/MjkA+ltsmbuH/y/hR7Ob7tsj+8ZdOYuk+YvW1B8dY20nDPHP58XgDTSm2nA8BzzA== -"@next/swc-android-arm64@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.0.tgz#865ba3a9afc204ff2bdeea49dd64d58705007a39" - integrity sha512-/280MLdZe0W03stA69iL+v6I+J1ascrQ6FrXBlXGCsGzrfMaGr7fskMa0T5AhQIVQD4nA/46QQWxG//DYuFBcA== - "@next/swc-android-arm64@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.4.tgz#f320d60639e19ecffa1f9034829f2d95502a9a51" @@ -2481,11 +2503,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.5.tgz#52578f552305c92d0b9b81d603c9643fb71e0835" integrity sha512-YXiqgQ/9Rxg1dXp6brXbeQM1JDx9SwUY/36JiE+36FXqYEmDYbxld9qkX6GEzkc5rbwJ+RCitargnzEtwGW0mw== -"@next/swc-darwin-arm64@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.0.tgz#08e8b411b8accd095009ed12efbc2f1d4d547135" - integrity sha512-R8vcXE2/iONJ1Unf5Ptqjk6LRW3bggH+8drNkkzH4FLEQkHtELhvcmJwkXcuipyQCsIakldAXhRbZmm3YN1vXg== - "@next/swc-darwin-arm64@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.4.tgz#fd578278312613eddcf3aee26910100509941b63" @@ -2496,11 +2513,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.5.tgz#3d5b53211484c72074f4975ba0ec2b1107db300e" integrity sha512-y8mhldb/WFZ6lFeowkGfi0cO/lBdiBqDk4T4LZLvCpoQp4Or/NzUN6P5NzBQZ5/b4oUHM/wQICEM+1wKA4qIVw== -"@next/swc-darwin-x64@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.0.tgz#fcd684497a76e8feaca88db3c394480ff0b007cd" - integrity sha512-ieAz0/J0PhmbZBB8+EA/JGdhRHBogF8BWaeqR7hwveb6SYEIJaDNQy0I+ZN8gF8hLj63bEDxJAs/cEhdnTq+ug== - "@next/swc-darwin-x64@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.4.tgz#ace5f80d8c8348efe194f6d7074c6213c52b3944" @@ -2511,11 +2523,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.5.tgz#adcabb732d226453777c0d37d58eaff9328b66fd" integrity sha512-wqJ3X7WQdTwSGi0kIDEmzw34QHISRIQ5uvC+VXmsIlCPFcMA+zM5723uh8NfuKGquDMiEMS31a83QgkuHMYbwQ== -"@next/swc-linux-arm-gnueabihf@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.0.tgz#9ec6380a27938a5799aaa6035c205b3c478468a7" - integrity sha512-njUd9hpl6o6A5d08dC0cKAgXKCzm5fFtgGe6i0eko8IAdtAPbtHxtpre3VeSxdZvuGFh+hb0REySQP9T1ttkog== - "@next/swc-linux-arm-gnueabihf@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.4.tgz#2bf2c83863635f19c71c226a2df936e001cce29c" @@ -2526,11 +2533,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.5.tgz#82a7cde67482b756bc65fbebf1dfa8a782074e93" integrity sha512-WnhdM5duONMvt2CncAl+9pim0wBxDS2lHoo7ub/o/i1bRbs11UTzosKzEXVaTDCUkCX2c32lIDi1WcN2ZPkcdw== -"@next/swc-linux-arm64-gnu@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.0.tgz#7f4196dff1049cea479607c75b81033ae2dbd093" - integrity sha512-OqangJLkRxVxMhDtcb7Qn1xjzFA3s50EIxY7mljbSCLybU+sByPaWAHY4px97ieOlr2y4S0xdPKkQ3BCAwyo6Q== - "@next/swc-linux-arm64-gnu@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.4.tgz#d577190f641c9b4b463719dd6b8953b6ba9be8d9" @@ -2541,11 +2543,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.5.tgz#f82ca014504950aab751e81f467492e9be0bad5d" integrity sha512-Jq2H68yQ4bLUhR/XQnbw3LDW0GMQn355qx6rU36BthDLeGue7YV7MqNPa8GKvrpPocEMW77nWx/1yI6w6J07gw== -"@next/swc-linux-arm64-musl@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.0.tgz#b445f767569cdc2dddee785ca495e1a88c025566" - integrity sha512-hB8cLSt4GdmOpcwRe2UzI5UWn6HHO/vLkr5OTuNvCJ5xGDwpPXelVkYW/0+C3g5axbDW2Tym4S+MQCkkH9QfWA== - "@next/swc-linux-arm64-musl@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.4.tgz#e70ffe70393d8f9242deecdb282ce5a8fd588b14" @@ -2556,11 +2553,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.5.tgz#f811ec9f4b12a978426c284c95ab2f515ddf7f9e" integrity sha512-KgPjwdbhDqXI7ghNN8V/WAiLquc9Ebe8KBrNNEL0NQr+yd9CyKJ6KqjayVkmX+hbHzbyvbui/5wh/p3CZQ9xcQ== -"@next/swc-linux-x64-gnu@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.0.tgz#67610e9be4fbc987de7535f1bcb17e45fe12f90e" - integrity sha512-OKO4R/digvrVuweSw/uBM4nSdyzsBV5EwkUeeG4KVpkIZEe64ZwRpnFB65bC6hGwxIBnTv5NMSnJ+0K/WmG78A== - "@next/swc-linux-x64-gnu@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.4.tgz#91498a130387fb1961902f2bee55863f8e910cff" @@ -2571,11 +2563,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.5.tgz#d44857257e6d20dc841998951d584ab1f25772c3" integrity sha512-O2ErUTvCJ6DkNTSr9pbu1n3tcqykqE/ebty1rwClzIYdOgpB3T2MfEPP+K7GhUR87wmN/hlihO9ch7qpVFDGKw== -"@next/swc-linux-x64-musl@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.0.tgz#ea19a23db08a9f2e34ac30401f774cf7d1669d31" - integrity sha512-JohhgAHZvOD3rQY7tlp7NlmvtvYHBYgY0x5ZCecUT6eCCcl9lv6iV3nfu82ErkxNk1H893fqH0FUpznZ/H3pSw== - "@next/swc-linux-x64-musl@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.4.tgz#78057b03c148c121553d41521ad38f6c732762ff" @@ -2586,11 +2573,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.5.tgz#3cc523abadc9a2a6de680593aff06e71cc29ecef" integrity sha512-1eIlZmlO/VRjxxzUBcVosf54AFU3ltAzHi+BJA+9U/lPxCYIsT+R4uO3QksRzRjKWhVQMRjEnlXyyq5SKJm7BA== -"@next/swc-win32-arm64-msvc@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.0.tgz#eadf054fc412085659b98e145435bbba200b5283" - integrity sha512-T/3gIE6QEfKIJ4dmJk75v9hhNiYZhQYAoYm4iVo1TgcsuaKLFa+zMPh4056AHiG6n9tn2UQ1CFE8EoybEsqsSw== - "@next/swc-win32-arm64-msvc@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.4.tgz#05bbaabacac23b8edf6caa99eb86b17550a09051" @@ -2601,11 +2583,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.5.tgz#c62232d869f1f9b22e8f24e4e7f05307c20f30ca" integrity sha512-oromsfokbEuVb0CBLLE7R9qX3KGXucZpsojLpzUh1QJjuy1QkrPJncwr8xmWQnwgtQ6ecMWXgXPB+qtvizT9Tw== -"@next/swc-win32-ia32-msvc@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.0.tgz#68faeae10c89f698bf9d28759172b74c9c21bda1" - integrity sha512-iwnKgHJdqhIW19H9PRPM9j55V6RdcOo6rX+5imx832BCWzkDbyomWnlzBfr6ByUYfhohb8QuH4hSGEikpPqI0Q== - "@next/swc-win32-ia32-msvc@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.4.tgz#8fd2fb48f04a2802e51fc320878bf6b411c1c866" @@ -2616,11 +2593,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.5.tgz#2bd9b28a9ba730d12a493e7d9d18e150fe89d496" integrity sha512-a/51L5KzBpeZSW9LbekMo3I3Cwul+V+QKwbEIMA+Qwb2qrlcn1L9h3lt8cHqNTFt2y72ce6aTwDTw1lyi5oIRA== -"@next/swc-win32-x64-msvc@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.0.tgz#d27e7e76c87a460a4da99c5bfdb1618dcd6cd064" - integrity sha512-aBvcbMwuanDH4EMrL2TthNJy+4nP59Bimn8egqv6GHMVj0a44cU6Au4PjOhLNqEh9l+IpRGBqMTzec94UdC5xg== - "@next/swc-win32-x64-msvc@12.1.4": version "12.1.4" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.4.tgz#a72ed44c9b1f850986a30fe36c59e01f8a79b5f3" @@ -3385,6 +3357,14 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + "@rushstack/eslint-patch@1.0.8": version "1.0.8" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64" @@ -4017,11 +3997,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708" integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== -"@types/node@17.0.21": - version "17.0.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" - integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== - "@types/node@^12.12.6": version "12.20.47" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.47.tgz#ca9237d51f2a2557419688511dab1c8daf475188" @@ -4087,6 +4062,13 @@ dependencies: "@types/react" "*" +"@types/react-dom@^17.0.0": + version "17.0.15" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.15.tgz#f2c8efde11521a4b7991e076cb9c70ba3bb0d156" + integrity sha512-Tr9VU9DvNoHDWlmecmcsE5ZZiUkYx+nKBzum4Oxe1K0yJVyBlfbq7H3eXjxXqJczBKqPGq3EgfTru4MgKb9+Yw== + dependencies: + "@types/react" "^17" + "@types/react-phone-number-input@^3.0.13": version "3.0.13" resolved "https://registry.yarnpkg.com/@types/react-phone-number-input/-/react-phone-number-input-3.0.13.tgz#4eb7dcd278dcf9eb2a8d2ce2cb304657cbf1b4e5" @@ -4143,10 +4125,10 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@17.0.40": - version "17.0.40" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.40.tgz#dc010cee6254d5239a138083f3799a16638e6bad" - integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ== +"@types/react@^17", "@types/react@^17.0.0": + version "17.0.44" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.44.tgz#c3714bd34dd551ab20b8015d9d0dbec812a51ec7" + integrity sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -4383,6 +4365,20 @@ clsx "^1.1.1" next-transpile-modules "^8.0.0" +"@vitejs/plugin-react@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-1.3.1.tgz#bf008adf33e713215cd4a6b94a75146dd6891975" + integrity sha512-qQS8Y2fZCjo5YmDUplEXl3yn+aueiwxB7BaoQ4nWYJYR+Ai8NXPVLlkLobVMs5+DeyFyg9Lrz6zCzdX1opcvyw== + dependencies: + "@babel/core" "^7.17.9" + "@babel/plugin-transform-react-jsx" "^7.17.3" + "@babel/plugin-transform-react-jsx-development" "^7.16.7" + "@babel/plugin-transform-react-jsx-self" "^7.16.7" + "@babel/plugin-transform-react-jsx-source" "^7.16.7" + "@rollup/pluginutils" "^4.2.0" + react-refresh "^0.12.0" + resolve "^1.22.0" + "@wojtekmaj/date-utils@^1.0.2", "@wojtekmaj/date-utils@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1" @@ -4798,7 +4794,7 @@ autolinker@^3.11.0: dependencies: tslib "^2.3.0" -autoprefixer@^10.3.4, autoprefixer@^10.4.0, autoprefixer@^10.4.2: +autoprefixer@^10.3.4, autoprefixer@^10.4.0, autoprefixer@^10.4.4: version "10.4.4" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== @@ -5627,11 +5623,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chart.js@^3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada" - integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA== - chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -7175,47 +7166,6 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d" - integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw== - dependencies: - "@eslint/eslintrc" "^1.2.0" - "@humanwhocodes/config-array" "^0.9.2" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^6.0.1" - globals "^13.6.0" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - eslint@^8.10.0, eslint@^8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37" @@ -12312,29 +12262,6 @@ next-validations@^0.1.11: resolved "https://registry.yarnpkg.com/next-validations/-/next-validations-0.1.11.tgz#fcc62dea5be8f9793d410de175f96e3fc1dac54d" integrity sha512-rdyRgZ3f3jwhLigdi9MC5R74BvRpB3cewa8LVnMHDiDRnSThvX0CdZ5KHK4t/SgrIGaVXiXOQ59KtvBqjcm5pA== -next@12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/next/-/next-12.1.0.tgz#c33d753b644be92fc58e06e5a214f143da61dd5d" - integrity sha512-s885kWvnIlxsUFHq9UGyIyLiuD0G3BUC/xrH0CEnH5lHEWkwQcHOORgbDF0hbrW9vr/7am4ETfX4A7M6DjrE7Q== - dependencies: - "@next/env" "12.1.0" - caniuse-lite "^1.0.30001283" - postcss "8.4.5" - styled-jsx "5.0.0" - use-subscription "1.5.1" - optionalDependencies: - "@next/swc-android-arm64" "12.1.0" - "@next/swc-darwin-arm64" "12.1.0" - "@next/swc-darwin-x64" "12.1.0" - "@next/swc-linux-arm-gnueabihf" "12.1.0" - "@next/swc-linux-arm64-gnu" "12.1.0" - "@next/swc-linux-arm64-musl" "12.1.0" - "@next/swc-linux-x64-gnu" "12.1.0" - "@next/swc-linux-x64-musl" "12.1.0" - "@next/swc-win32-arm64-msvc" "12.1.0" - "@next/swc-win32-ia32-msvc" "12.1.0" - "@next/swc-win32-x64-msvc" "12.1.0" - next@12.1.4, next@^12.1.0: version "12.1.4" resolved "https://registry.yarnpkg.com/next/-/next-12.1.4.tgz#597a9bdec7aec778b442c4f6d41afd2c64a54b23" @@ -12622,6 +12549,11 @@ object-hash@^2.0.1, object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" @@ -13374,6 +13306,37 @@ playwright-core@1.20.2: yauzl "2.10.0" yazl "2.5.1" +playwright-core@1.21.1: + version "1.21.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.21.1.tgz#2757be7921576f047c0a622194dc45f4e1962e17" + integrity sha512-SbK5dEsai9ZUKlxcinqegorBq4GnftXd4/GfW+pLsdQIQWrLCM/JNh6YQ2Rf2enVykXCejtoXW8L5vJXBBVSJQ== + dependencies: + colors "1.4.0" + commander "8.3.0" + debug "4.3.3" + extract-zip "2.0.1" + https-proxy-agent "5.0.0" + jpeg-js "0.4.3" + mime "3.0.0" + pixelmatch "5.2.1" + pngjs "6.0.0" + progress "2.0.3" + proper-lockfile "4.1.2" + proxy-from-env "1.1.0" + rimraf "3.0.2" + socks-proxy-agent "6.1.1" + stack-utils "2.0.5" + ws "8.4.2" + yauzl "2.10.0" + yazl "2.5.1" + +playwright@^1.21.1: + version "1.21.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.21.1.tgz#62bdefc0e8baba192d93d8daf0c0eb9213869d76" + integrity sha512-Of0h1XAvsqK1XfHVZ8sL2PjJVoQUu9gTmmMTtLS7MEyWMRD0kn8myeI90xj1ncJhUysQxGboH64S5v+lL2USrg== + dependencies: + playwright-core "1.21.1" + pngjs@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" @@ -13406,7 +13369,7 @@ postcss-js@^4.0.0: dependencies: camelcase-css "^2.0.1" -postcss-load-config@^3.1.0: +postcss-load-config@^3.1.0, postcss-load-config@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== @@ -13421,7 +13384,7 @@ postcss-nested@5.0.6: dependencies: postcss-selector-parser "^6.0.6" -postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: version "6.0.10" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== @@ -13443,7 +13406,7 @@ postcss@8.4.5: picocolors "^1.0.0" source-map-js "^1.0.1" -postcss@^8.3.6, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.6, postcss@^8.4.8: +postcss@^8.3.6, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.6: version "8.4.12" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== @@ -13868,11 +13831,6 @@ react-calendar@^3.3.1: merge-class-names "^1.1.1" prop-types "^15.6.0" -react-chartjs-2@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-4.1.0.tgz#2a123df16d3a987c54eb4e810ed766d3c03adf8d" - integrity sha512-AsUihxEp8Jm1oBhbEovE+w50m9PVNhz1sfwEIT4hZduRC0m14gHWHd0cUaxkFDb8HNkdMIGzsNlmVqKiOpU74g== - react-colorful@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.5.1.tgz#29d9c4e496f2ca784dd2bb5053a3a4340cfaf784" @@ -14068,6 +14026,11 @@ react-redux@^7.2.4: prop-types "^15.7.2" react-is "^17.0.2" +react-refresh@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.12.0.tgz#28ac0a2c30ef2bb3433d5fd0621e69a6d774c3a4" + integrity sha512-suLIhrU2IHKL5JEKR/fAwJv7bbeq4kJ+pJopf77jHwuR+HmJS/HbrPIGsTBUVfw7tXPOmYv7UJ7PCaN49e8x4A== + react-remove-scroll-bar@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd" @@ -15543,11 +15506,6 @@ style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" -styled-jsx@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.0.tgz#816b4b92e07b1786c6b7111821750e0ba4d26e77" - integrity sha512-qUqsWoBquEdERe10EW8vLp3jT25s/ssG1/qX5gZ4wu15OZpmSMFI2v+fWlRhLfykA5rFtlJ1ME8A8pm/peV4WA== - styled-jsx@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.1.tgz#78fecbbad2bf95ce6cd981a08918ce4696f5fc80" @@ -15712,11 +15670,6 @@ swarm-js@^0.1.40: tar "^4.0.2" xhr-request "^1.0.1" -swr@^1.2.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" - integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -15749,6 +15702,33 @@ tailwindcss@^3.0.23: quick-lru "^5.1.1" resolve "^1.22.0" +tailwindcss@^3.0.24: + version "3.0.24" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d" + integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig== + dependencies: + arg "^5.0.1" + chokidar "^3.5.3" + color-name "^1.1.4" + detective "^5.2.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.11" + glob-parent "^6.0.2" + is-glob "^4.0.3" + lilconfig "^2.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.12" + postcss-js "^4.0.0" + postcss-load-config "^3.1.4" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.10" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.0" + tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -16074,6 +16054,11 @@ ts-node@^10.6.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" +tsc@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/tsc/-/tsc-2.0.4.tgz#5f6499146abea5dca4420b451fa4f2f9345238f5" + integrity sha512-fzoSieZI5KKJVBYGvwbVZs/J5za84f2lSTLPYf6AGiIf43tZ3GNrI1QzTLcjtyDDP4aLxd46RTZq1nQxe7+k5Q== + tsconfig-paths@^3.11.0, tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" @@ -16631,13 +16616,6 @@ use-sidecar@^1.0.1: detect-node-es "^1.1.0" tslib "^1.9.3" -use-subscription@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" - integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== - dependencies: - object-assign "^4.1.1" - use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -16831,6 +16809,18 @@ vite@^2.8.6: optionalDependencies: fsevents "~2.3.2" +vite@^2.9.5: + version "2.9.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.5.tgz#08ef37ac7a6d879c96f328b791732c9a00ea25ea" + integrity sha512-dvMN64X2YEQgSXF1lYabKXw3BbN6e+BL67+P3Vy4MacnY+UzT1AfkHiioFSi9+uiDUiaDy7Ax/LQqivk6orilg== + dependencies: + esbuild "^0.14.27" + postcss "^8.4.12" + resolve "^1.22.0" + rollup "^2.59.0" + optionalDependencies: + fsevents "~2.3.2" + void-elements@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"