diff --git a/apps/docs/pages/integrations/embed.mdx b/apps/docs/pages/integrations/embed.mdx index 652ead23..40cde9d9 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 6adcfe08..fa549c75 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 dd22a8fb..78805642 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 bed4afd9..08606bdd 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 fbcec078..9af36630 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 1218612f..ddf754b0 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 9360d3aa..a927bd5f 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 f39a496f..65cda6e2 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 12013b3c..1e2160d4 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 c29a9667..7ffb1e8f 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 00000000..faea1947 --- /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 9234286d..23795f3b 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 6f6a53a8..cf7842da 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 dc889699..94639795 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 2b8b443f..7ccb45ff 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 a4a1ac45..bc456b84 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 ce719c46..62797e4f 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 559c199d..d6a2c53a 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 00000000..f298b30a 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 00000000..c08be361 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 00000000..dc821836 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 00000000..159f47fb 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 00000000..3d4e9b15 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 00000000..3f2346eb 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 00000000..82be5124 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 00000000..39fd4db5 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 00000000..218b70b0 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 00000000..6dc4e850 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 00000000..be6246fd 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 00000000..7ffc42a1 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 00000000..c0774d35 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 00000000..b7acdc12 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 00000000..cde99813 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 ac649100..90430887 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 00000000..14dbce41 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 00000000..ec14f323 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 00000000..69180b32 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 00000000..581c9841 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 00000000..a8b9fa9c 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 00000000..8650202b 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 00000000..88db0167 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 00000000..978fc961 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 00000000..641bb71f 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 00000000..302b6f43 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 00000000..7ab768fe 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 00000000..7f6a0943 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 00000000..ba1b0681 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 00000000..488fbca4 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 00000000..01251931 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 670a7008..00000000 --- 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 00000000..01d3e25b --- /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 00000000..792032d8 --- /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 23e804f9..e17e9291 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 00000000..80a87b2a --- /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 ce56c8d1..00000000 --- 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 00000000..a6e6a2b2 --- /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 00000000..6222e91a --- /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 c41ef8c1..7ae474df 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 08b796dc..0792fdb4 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 aa238311..77f29096 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 f22b9aa8..61e163d5 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 00000000..b785bf71 --- /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 a00ff918..00000000 --- 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 00000000..753dac72 --- /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 e9b64ffd..bd3b3e6f 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 6a47400a..8f4610ff 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 00000000..e2470977 --- /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 5463129d..ab61cf6c 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 a3b430b6..0fc3bc5c 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 00000000..f435c800 --- /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 00000000..b460016b --- /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 00000000..249377ff 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 00000000..d17880cf 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 00000000..a96682c6 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 00000000..e50464ce 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 08f91ddb..af49c775 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 00000000..85e2f4ef --- /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 9bc56e5c..c5d8ae29 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 26740082..bbff4fc3 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 00000000..672ead91 --- /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 6a6c47da..7b7b63a5 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 30ed195b..45160abd 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 2bd1058c..5fb0e2e9 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"