[Feature]Booking Embed (#2227)
This commit is contained in:
parent
4e9c3be598
commit
4a58da62d6
39 changed files with 1740 additions and 22 deletions
|
@ -5,13 +5,15 @@ import dayjsBusinessTime from "dayjs-business-time";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import { memoize } from "lodash";
|
import { memoize } from "lodash";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { useEmbedStyles } from "@calcom/embed-core";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
import { timeZone } from "@lib/clock";
|
import { timeZone } from "@lib/clock";
|
||||||
import { weekdayNames } from "@lib/core/i18n/weekday";
|
import { weekdayNames } from "@lib/core/i18n/weekday";
|
||||||
import { doWorkAsync } from "@lib/doWorkAsync";
|
import { doWorkAsync } from "@lib/doWorkAsync";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
|
||||||
import getSlots from "@lib/slots";
|
import getSlots from "@lib/slots";
|
||||||
import { WorkingHours } from "@lib/types/schedule";
|
import { WorkingHours } from "@lib/types/schedule";
|
||||||
|
|
||||||
|
@ -85,6 +87,8 @@ function DatePicker({
|
||||||
}: DatePickerProps): JSX.Element {
|
}: DatePickerProps): JSX.Element {
|
||||||
const { i18n } = useLocale();
|
const { i18n } = useLocale();
|
||||||
const [browsingDate, setBrowsingDate] = useState<Dayjs | null>(date);
|
const [browsingDate, setBrowsingDate] = useState<Dayjs | null>(date);
|
||||||
|
const enabledDateButtonEmbedStyles = useEmbedStyles("enabledDateButton");
|
||||||
|
const disabledDateButtonEmbedStyles = useEmbedStyles("disabledDateButton");
|
||||||
|
|
||||||
const [month, setMonth] = useState<string>("");
|
const [month, setMonth] = useState<string>("");
|
||||||
const [year, setYear] = useState<string>("");
|
const [year, setYear] = useState<string>("");
|
||||||
|
@ -274,6 +278,9 @@ function DatePicker({
|
||||||
<button
|
<button
|
||||||
onClick={() => onDatePicked(browsingDate.date(day.date))}
|
onClick={() => onDatePicked(browsingDate.date(day.date))}
|
||||||
disabled={day.disabled}
|
disabled={day.disabled}
|
||||||
|
style={
|
||||||
|
day.disabled ? { ...disabledDateButtonEmbedStyles } : { ...enabledDateButtonEmbedStyles }
|
||||||
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"absolute top-0 left-0 right-0 bottom-0 mx-auto w-full rounded-sm text-center",
|
"absolute top-0 left-0 right-0 bottom-0 mx-auto w-full rounded-sm text-center",
|
||||||
"hover:border-brand hover:border dark:hover:border-white",
|
"hover:border-brand hover:border dark:hover:border-white",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Control } from "react-hook-form";
|
|
||||||
import BasePhoneInput, { Props } from "react-phone-number-input/react-hook-form";
|
import BasePhoneInput, { Props } from "react-phone-number-input/react-hook-form";
|
||||||
import "react-phone-number-input/style.css";
|
import "react-phone-number-input/style.css";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Maybe } from "@trpc/server";
|
import { Maybe } from "@trpc/server";
|
||||||
|
@ -27,6 +28,10 @@ function applyThemeAndAddListener(theme: string) {
|
||||||
// makes sure the ui doesn't flash
|
// makes sure the ui doesn't flash
|
||||||
export default function useTheme(theme?: Maybe<string>) {
|
export default function useTheme(theme?: Maybe<string>) {
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Embed UI configuration takes more precedence over App Configuration
|
||||||
|
theme = (router.query.theme as string | null) || theme;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: isReady doesn't seem required now. This is also impacting PSI Score for pages which are using isReady.
|
// TODO: isReady doesn't seem required now. This is also impacting PSI Score for pages which are using isReady.
|
||||||
|
|
|
@ -8,6 +8,7 @@ const withTM = require("next-transpile-modules")([
|
||||||
"@calcom/prisma",
|
"@calcom/prisma",
|
||||||
"@calcom/stripe",
|
"@calcom/stripe",
|
||||||
"@calcom/ui",
|
"@calcom/ui",
|
||||||
|
"@calcom/embed-core",
|
||||||
]);
|
]);
|
||||||
const { i18n } = require("./next-i18next.config");
|
const { i18n } = require("./next-i18next.config");
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ import React, { useState } from "react";
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
import { JSONObject } from "superjson/dist/types";
|
import { JSONObject } from "superjson/dist/types";
|
||||||
|
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { sdkActionManager, useEmbedStyles } from "@calcom/embed-core";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|
||||||
import useTheme from "@lib/hooks/useTheme";
|
import useTheme from "@lib/hooks/useTheme";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
@ -30,6 +32,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
const { user, eventTypes } = props;
|
const { user, eventTypes } = props;
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem");
|
||||||
const query = { ...router.query };
|
const query = { ...router.query };
|
||||||
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
||||||
|
|
||||||
|
@ -48,9 +51,9 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
<div className="h-screen dark:bg-neutral-900">
|
<div className="h-screen dark:bg-neutral-900">
|
||||||
<main className="mx-auto max-w-3xl px-4 py-24">
|
<main className="mx-auto max-w-3xl px-4 py-24">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername}></AvatarSSR>
|
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} />
|
||||||
<h1 className="font-cal mb-1 text-3xl text-neutral-900 dark:text-white">
|
<h1 className="font-cal mb-1 text-3xl text-neutral-900 dark:text-white">
|
||||||
{nameOrUsername}
|
<span>{nameOrUsername}</span>
|
||||||
{user.verified && (
|
{user.verified && (
|
||||||
<BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
|
<BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
|
||||||
)}
|
)}
|
||||||
|
@ -71,7 +74,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
eventTypes.map((type) => (
|
eventTypes.map((type) => (
|
||||||
<div
|
<div
|
||||||
key={type.id}
|
key={type.id}
|
||||||
style={{ display: "flex" }}
|
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
|
||||||
className="hover:border-brand group relative rounded-sm border border-neutral-200 bg-white hover:bg-gray-50 dark:border-neutral-700 dark:bg-gray-800 dark:hover:border-neutral-600">
|
className="hover:border-brand group relative rounded-sm border border-neutral-200 bg-white hover:bg-gray-50 dark:border-neutral-700 dark:bg-gray-800 dark:hover:border-neutral-600">
|
||||||
<ArrowRightIcon className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
|
<ArrowRightIcon className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
|
||||||
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
|
||||||
|
@ -91,6 +94,10 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
"You must verify a wallet with a token belonging to the specified smart contract first",
|
"You must verify a wallet with a token belonging to the specified smart contract first",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
sdkActionManager?.fire("eventTypeSelected", {
|
||||||
|
eventType: type,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="block w-full px-6 py-4"
|
className="block w-full px-6 py-4"
|
||||||
|
|
|
@ -3,6 +3,8 @@ import Head from "next/head";
|
||||||
// import { ReactQueryDevtools } from "react-query/devtools";
|
// import { ReactQueryDevtools } from "react-query/devtools";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
|
import "@calcom/embed-core/src/embed-iframe";
|
||||||
|
|
||||||
import AppProviders, { AppProps } from "@lib/app-providers";
|
import AppProviders, { AppProps } from "@lib/app-providers";
|
||||||
import { seoConfig } from "@lib/config/next-seo.config";
|
import { seoConfig } from "@lib/config/next-seo.config";
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,12 @@ type Props = Record<string, unknown> & DocumentProps;
|
||||||
class MyDocument extends Document<Props> {
|
class MyDocument extends Document<Props> {
|
||||||
static async getInitialProps(ctx: DocumentContext) {
|
static async getInitialProps(ctx: DocumentContext) {
|
||||||
const initialProps = await Document.getInitialProps(ctx);
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
return { ...initialProps };
|
const isEmbed = ctx.req?.url?.includes("embed");
|
||||||
|
return { ...initialProps, isEmbed };
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const props = this.props;
|
||||||
const { locale } = this.props.__NEXT_DATA__;
|
const { locale } = this.props.__NEXT_DATA__;
|
||||||
const dir = locale === "ar" || locale === "he" ? "rtl" : "ltr";
|
const dir = locale === "ar" || locale === "he" ? "rtl" : "ltr";
|
||||||
|
|
||||||
|
@ -23,7 +25,9 @@ class MyDocument extends Document<Props> {
|
||||||
<meta name="msapplication-TileColor" content="#ff0000" />
|
<meta name="msapplication-TileColor" content="#ff0000" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
</Head>
|
</Head>
|
||||||
<body className="bg-gray-100 dark:bg-neutral-900">
|
|
||||||
|
{/* 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" } : {}}>
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -10,12 +10,13 @@ import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { sdkActionManager } from "@calcom/embed-core";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import Button from "@calcom/ui/Button";
|
import Button from "@calcom/ui/Button";
|
||||||
import { EmailInput } from "@calcom/ui/form/fields";
|
import { EmailInput } from "@calcom/ui/form/fields";
|
||||||
|
|
||||||
import { asStringOrThrow, asStringOrNull } from "@lib/asStringOrNull";
|
import { asStringOrThrow, asStringOrNull } from "@lib/asStringOrNull";
|
||||||
import { getEventName } from "@lib/event";
|
import { getEventName } from "@lib/event";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
|
||||||
import useTheme from "@lib/hooks/useTheme";
|
import useTheme from "@lib/hooks/useTheme";
|
||||||
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
|
@ -40,11 +41,7 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
|
|
||||||
const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date)));
|
const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date)));
|
||||||
const { isReady, Theme } = useTheme(props.profile.theme);
|
const { isReady, Theme } = useTheme(props.profile.theme);
|
||||||
|
const { eventType } = props;
|
||||||
useEffect(() => {
|
|
||||||
setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()));
|
|
||||||
setIs24h(!!localStorage.getItem("timeOption.is24hClock"));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const attendeeName = typeof name === "string" ? name : "Nameless";
|
const attendeeName = typeof name === "string" ? name : "Nameless";
|
||||||
|
|
||||||
|
@ -57,6 +54,26 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventName = getEventName(eventNameObject);
|
const eventName = getEventName(eventNameObject);
|
||||||
|
const needsConfirmation = eventType.requiresConfirmation && reschedule != "true";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const users = eventType.users;
|
||||||
|
// TODO: We should probably make it consistent with Webhook payload. Some data is not available here, as and when requirement comes we can add
|
||||||
|
sdkActionManager!.fire("bookingSuccessful", {
|
||||||
|
eventType,
|
||||||
|
date: date.toString(),
|
||||||
|
duration: eventType.length,
|
||||||
|
organizer: {
|
||||||
|
name: users[0].name || "Nameless",
|
||||||
|
email: users[0].email || "Email-less",
|
||||||
|
timeZone: users[0].timeZone,
|
||||||
|
},
|
||||||
|
confirmed: !needsConfirmation,
|
||||||
|
// TODO: Add payment details
|
||||||
|
});
|
||||||
|
setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()));
|
||||||
|
setIs24h(!!localStorage.getItem("timeOption.is24hClock"));
|
||||||
|
}, [eventType, needsConfirmation]);
|
||||||
|
|
||||||
function eventLink(): string {
|
function eventLink(): string {
|
||||||
const optional: { location?: string } = {};
|
const optional: { location?: string } = {};
|
||||||
|
@ -87,8 +104,6 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
return encodeURIComponent(event.value ? event.value : false);
|
return encodeURIComponent(event.value ? event.value : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsConfirmation = props.eventType.requiresConfirmation && reschedule != "true";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(isReady && (
|
(isReady && (
|
||||||
<div className="h-screen bg-neutral-100 dark:bg-neutral-900" data-testid="success-page">
|
<div className="h-screen bg-neutral-100 dark:bg-neutral-900" data-testid="success-page">
|
||||||
|
@ -322,6 +337,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
theme: true,
|
theme: true,
|
||||||
brandColor: true,
|
brandColor: true,
|
||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
|
email: true,
|
||||||
|
timeZone: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
team: {
|
team: {
|
||||||
|
@ -351,6 +368,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
theme: true,
|
theme: true,
|
||||||
brandColor: true,
|
brandColor: true,
|
||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
|
email: true,
|
||||||
|
timeZone: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
"packages/embeds/*",
|
||||||
"packages/app-store/*"
|
"packages/app-store/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
8
packages/embeds/README.md
Normal file
8
packages/embeds/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Embeds
|
||||||
|
|
||||||
|
This folder contains all the various flavours of embeds.
|
||||||
|
|
||||||
|
`core` contains the core library written in vanilla JS that manages the embed.
|
||||||
|
`snippet` contains the Vanilla JS Code Snippet that can be installed on any website and would automatically fetch the `core` library.
|
||||||
|
|
||||||
|
Please see the respective folder READMEs for details on them.
|
95
packages/embeds/embed-core/README.md
Normal file
95
packages/embeds/embed-core/README.md
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# embed-core
|
||||||
|
|
||||||
|
See [index.html](index.html) to understand how it can be used.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- The Embed SDK can be added asynchronously
|
||||||
|
- You can add it through any tag manager like GTM if you like[Need to Test]
|
||||||
|
- Available configurations are
|
||||||
|
- `theme`
|
||||||
|
- Prefilling of
|
||||||
|
- `name`
|
||||||
|
- `email`
|
||||||
|
- `notes`
|
||||||
|
- `guests`
|
||||||
|
|
||||||
|
## How to use embed on any webpage no matter what framework.
|
||||||
|
|
||||||
|
- _Step-1._ Install the snippet
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
(function (C, A, L) {
|
||||||
|
let d = C.document;
|
||||||
|
C.Cal =
|
||||||
|
C.Cal ||
|
||||||
|
function () {
|
||||||
|
let cal = C.Cal;
|
||||||
|
let ar = arguments;
|
||||||
|
if (!cal.loaded) {
|
||||||
|
cal.ns = {};
|
||||||
|
cal.q = cal.q || [];
|
||||||
|
d.head.appendChild(d.createElement("script")).src = A;
|
||||||
|
cal.loaded = true;
|
||||||
|
}
|
||||||
|
if (ar[0] === L) {
|
||||||
|
const api = function () {
|
||||||
|
api.q.push(arguments);
|
||||||
|
};
|
||||||
|
const namespace = arguments[1];
|
||||||
|
api.q = api.q || [];
|
||||||
|
namespace ? (cal.ns[namespace] = api) : null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cal.q.push(ar);
|
||||||
|
};
|
||||||
|
})(window, "https://cal.com/embed.js", "init");
|
||||||
|
```
|
||||||
|
|
||||||
|
- _Step-2_. Give `init` instruction to it. It creates a queue so that even without embed.js being fetched, you can give instructions to embed.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Cal("init) // Creates default instance. Give instruction to it as Cal("instruction")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Optionally** if you want to install another instance of embed you can do
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Cal("init", "NAME_YOUR_OTHER_INSTANCE"); // Creates a named instance. Give instructions to it as Cal.ns.NAME_YOUR_OTHER_INSTANCE("instruction")
|
||||||
|
```
|
||||||
|
|
||||||
|
- Step-1 and Step-2 must be followed in same order. After that you can give various instructions to embed as you like.
|
||||||
|
|
||||||
|
## Supported Instructions
|
||||||
|
|
||||||
|
Consider an instruction as a function with that name and that would be called with the given argument.
|
||||||
|
|
||||||
|
`inline` - Appends embed inline as the child of the element.
|
||||||
|
|
||||||
|
- `elementOrSelector` - Give it either a valid CSS selector or an HTMLElement instance directly
|
||||||
|
|
||||||
|
- `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>. It makes it easy to configure the calendar host once and use as many links you want with just usernames
|
||||||
|
|
||||||
|
`ui` - Configure UI for embed. Make it look part of your webpage.
|
||||||
|
|
||||||
|
- `styles` - It supports styling for `body` and `eventTypeListItem`. Right now we support just background on these two.
|
||||||
|
|
||||||
|
`preload` - If you want to open cal link on some action. Make it pop open instantly by preloading it.
|
||||||
|
|
||||||
|
- `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>
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Run the following command and then you can test the embed in the automatically opened page `http://localhost:3002`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shipping to Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
Make `dist/embed.umd.js` servable on URL http://cal.com/embed.js
|
9
packages/embeds/embed-core/env.d.ts
vendored
Normal file
9
packages/embeds/embed-core/env.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly NEXT_PUBLIC_WEBSITE_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
225
packages/embeds/embed-core/index.html
Normal file
225
packages/embeds/embed-core/index.html
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- <link rel="prerender" href="http://localhost:3000/free"> -->
|
||||||
|
<script>
|
||||||
|
if (!location.search.includes("nonResponsive")) {
|
||||||
|
document.write('<meta name="viewport" content="width=device-width"/>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
(function (C, A, L) {
|
||||||
|
let d = C.document;
|
||||||
|
C.Cal =
|
||||||
|
C.Cal ||
|
||||||
|
function () {
|
||||||
|
let cal = C.Cal;
|
||||||
|
let ar = arguments;
|
||||||
|
if (!cal.loaded) {
|
||||||
|
cal.ns = {};
|
||||||
|
cal.q = cal.q || [];
|
||||||
|
d.head.appendChild(d.createElement("script")).src = A;
|
||||||
|
cal.loaded = true;
|
||||||
|
}
|
||||||
|
if (ar[0] === L) {
|
||||||
|
const api = function () {
|
||||||
|
api.q.push(arguments);
|
||||||
|
};
|
||||||
|
const namespace = arguments[1];
|
||||||
|
api.q = api.q || [];
|
||||||
|
namespace ? (cal.ns[namespace] = api) : null;
|
||||||
|
}
|
||||||
|
cal.q.push(ar);
|
||||||
|
};
|
||||||
|
})(window, "//localhost:3002/dist/embed.umd.js", "init");
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
Cal("init");
|
||||||
|
|
||||||
|
// Create a namespace "second". It can be accessed as Cal.ns.second with the exact same API as Cal
|
||||||
|
Cal("init", "second");
|
||||||
|
|
||||||
|
// Create a namespace "third". It can be accessed as Cal.ns.second with the exact same API as Cal
|
||||||
|
Cal("init", "third");
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.debug {
|
||||||
|
/* border: 1px solid black; */
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>This page has a non responsive version accessible <a href="?nonResponsive">here</a></h3>
|
||||||
|
<h3>Pre-render test page available at <a href="?prerender-test">here</a></h3>
|
||||||
|
<div>
|
||||||
|
<button data-cal-link="free">Book with Free User</button>
|
||||||
|
<div>
|
||||||
|
<i
|
||||||
|
>Corresponding Cal Link is being preloaded. Assuming that it would take you some time to click this
|
||||||
|
as you are reading this text, it would open up super fast[If you are running a production build on
|
||||||
|
local]. Try switching to slow 3G or create a custom Network configuration which is impossibly
|
||||||
|
slow</i
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="namespaces-test">
|
||||||
|
<div class="debug">
|
||||||
|
<h2>Default Namespace(Cal)<i>[Black Theme][Guests(janedoe@gmail.com and test@gmail.com)]</i></h2>
|
||||||
|
<div>
|
||||||
|
<i><a href="?only=ns:default">Test in Zen Mode</a></i>
|
||||||
|
</div>
|
||||||
|
<i id="booking-status-"> You would see last Booking page action in my place </i>
|
||||||
|
<div id="cal-booking-place-" style="max-height: 30vh; overflow: scroll">
|
||||||
|
<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="loader" id="cal-booking-loader-">Loading .....</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="debug">
|
||||||
|
<h2>Namespace "second"(Cal.ns.second)[Custom Styling]</h2>
|
||||||
|
<div>
|
||||||
|
<i><a href="?only=ns:second">Test in Zen Mode</a></i>
|
||||||
|
</div>
|
||||||
|
<i id="booking-status-second">
|
||||||
|
<i>You would see last Booking page action in my place</i>
|
||||||
|
</i>
|
||||||
|
<div id="cal-booking-place-second">
|
||||||
|
<div>If you render booking embed in me, I won't restrict you. The entire page is yours.</div>
|
||||||
|
<button
|
||||||
|
onclick="(function () {Cal.ns.second('ui', {styles:{eventTypeListItem:{backgroundColor:'blue'}}})})()">
|
||||||
|
Change <code>eventTypeListItem</code> bg color
|
||||||
|
</button>
|
||||||
|
<button onclick="(function () {Cal.ns.second('ui', {styles:{body:{background:'red'}}})})()">
|
||||||
|
Change <code>body</code> bg color
|
||||||
|
</button>
|
||||||
|
<div class="loader" id="cal-booking-loader-second">Loading .....</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="debug">
|
||||||
|
<h2>Namespace "third"(Cal.ns.third)</h2>
|
||||||
|
<div>
|
||||||
|
<i><a href="?only=ns:third">Test in Zen Mode</a></i>
|
||||||
|
</div>
|
||||||
|
<i id="booking-status-third">
|
||||||
|
<i>You would see last Booking page action in my place</i>
|
||||||
|
</i>
|
||||||
|
<div id="cal-booking-place-third" style="width: 30%">
|
||||||
|
<div>If you render booking embed in me, I would not let you be more than 30% wide</div>
|
||||||
|
<div class="loader" id="cal-booking-loader-third">Loading .....</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const searchParams = new URL(document.URL).searchParams;
|
||||||
|
const only = searchParams.get("only");
|
||||||
|
// In prerender-test, we would want to test just the prerender case and nothing else.
|
||||||
|
if (searchParams.get("prerender-test") === null) {
|
||||||
|
if (!only || only === "ns:default") {
|
||||||
|
Cal("inline", {
|
||||||
|
elementOrSelector: "#cal-booking-place-",
|
||||||
|
calLink: "pro?case=1",
|
||||||
|
config: {
|
||||||
|
name: "John Doe",
|
||||||
|
email: "johndoe@gmail.com",
|
||||||
|
notes: "Test Meeting",
|
||||||
|
guests: ["janedoe@gmail.com", "test@gmail.com"],
|
||||||
|
theme: "dark",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!only || only === "ns:second") {
|
||||||
|
// Bulk API is supported - Keep all configuration at one place.
|
||||||
|
Cal.ns.second(
|
||||||
|
[
|
||||||
|
"inline",
|
||||||
|
{
|
||||||
|
elementOrSelector: "#cal-booking-place-second",
|
||||||
|
calLink: "pro?case=2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui",
|
||||||
|
{
|
||||||
|
styles: {
|
||||||
|
body: {
|
||||||
|
background: "white",
|
||||||
|
},
|
||||||
|
eventTypeListItem: {
|
||||||
|
backgroundColor: "#D3D3D3",
|
||||||
|
},
|
||||||
|
enabledDateButton: {
|
||||||
|
backgroundColor: "#D3D3D3",
|
||||||
|
},
|
||||||
|
disabledDateButton: {
|
||||||
|
backgroundColor: "lightslategray",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!only || only === "ns:third") {
|
||||||
|
Cal.ns.third(
|
||||||
|
[
|
||||||
|
"inline",
|
||||||
|
{
|
||||||
|
elementOrSelector: "#cal-booking-place-third",
|
||||||
|
calLink: "pro?case=3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ui",
|
||||||
|
{
|
||||||
|
styles: {
|
||||||
|
body: {
|
||||||
|
background: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById("namespaces-test").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
Cal("preload", {
|
||||||
|
calLink: "free",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const callback = function (e) {
|
||||||
|
const detail = e.detail;
|
||||||
|
const namespace = detail.namespace;
|
||||||
|
|
||||||
|
if (detail.type === "linkReady") {
|
||||||
|
document.getElementById("cal-booking-loader-" + namespace).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(`booking-status-${namespace}`).innerHTML = JSON.stringify(e.detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
Cal("on", {
|
||||||
|
action: "*",
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
Cal.ns.second("on", {
|
||||||
|
action: "*",
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
Cal.ns.third("on", {
|
||||||
|
action: "*",
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
packages/embeds/embed-core/index.ts
Normal file
2
packages/embeds/embed-core/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./src/embed-iframe";
|
||||||
|
export * from "./src/sdk-event";
|
17
packages/embeds/embed-core/package.json
Normal file
17
packages/embeds/embed-core/package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "@calcom/embed-core",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "The core script adds the booking embed",
|
||||||
|
"main": "./index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"vite": "vite",
|
||||||
|
"dev": "run-p 'build --watch' 'vite --port 3002 --strict-port --open'",
|
||||||
|
"type-check": "tsc --pretty --noEmit",
|
||||||
|
"lint": "eslint --ext .ts,.js src"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^2.8.6",
|
||||||
|
"eslint": "^8.10.0"
|
||||||
|
}
|
||||||
|
}
|
78
packages/embeds/embed-core/src/ModalBox.ts
Normal file
78
packages/embeds/embed-core/src/ModalBox.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
export class ModalBox extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
const closeEl = this.shadowRoot!.querySelector(".close") as HTMLElement;
|
||||||
|
closeEl.onclick = () => {
|
||||||
|
this.shadowRoot!.host.remove();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
.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%;
|
||||||
|
height: 80%;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="backdrop">
|
||||||
|
<div class="header">
|
||||||
|
<span class="close">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-box">
|
||||||
|
<div class="body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
this.attachShadow({ mode: "open" });
|
||||||
|
this.shadowRoot!.innerHTML = modalHtml;
|
||||||
|
}
|
||||||
|
}
|
152
packages/embeds/embed-core/src/embed-iframe.ts
Normal file
152
packages/embeds/embed-core/src/embed-iframe.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import { useState, useEffect, CSSProperties } from "react";
|
||||||
|
|
||||||
|
import { sdkActionManager } from "./sdk-event";
|
||||||
|
|
||||||
|
// Only allow certain styles to be modified so that when we make any changes to HTML, we know what all embed styles might be impacted.
|
||||||
|
// Keep this list to minimum, only adding those styles which are really needed.
|
||||||
|
interface EmbedStyles {
|
||||||
|
body?: Pick<CSSProperties, "background" | "backgroundColor">;
|
||||||
|
eventTypeListItem?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
|
||||||
|
enabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
|
||||||
|
disabledDateButton?: Pick<CSSProperties, "background" | "color" | "backgroundColor">;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElementName = keyof EmbedStyles;
|
||||||
|
|
||||||
|
type ReactEmbedStylesSetter = React.Dispatch<React.SetStateAction<EmbedStyles>>;
|
||||||
|
|
||||||
|
export interface UiConfig {
|
||||||
|
theme: string;
|
||||||
|
styles: EmbedStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
const embedStore = {
|
||||||
|
// Store all embed styles here so that as and when new elements are mounted, styles can be applied to it.
|
||||||
|
styles: {},
|
||||||
|
// Store all React State setters here.
|
||||||
|
reactStylesStateSetters: {} as Record<ElementName, ReactEmbedStylesSetter>,
|
||||||
|
};
|
||||||
|
|
||||||
|
const setEmbedStyles = (stylesConfig: UiConfig["styles"]) => {
|
||||||
|
embedStore.styles = stylesConfig;
|
||||||
|
for (let [, setEmbedStyle] of Object.entries(embedStore.reactStylesStateSetters)) {
|
||||||
|
setEmbedStyle((styles) => {
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
...stylesConfig,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerNewSetter = (elementName: ElementName, setStyles: ReactEmbedStylesSetter) => {
|
||||||
|
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: ElementName) => {
|
||||||
|
delete embedStore.reactStylesStateSetters[elementName];
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Make it usable as an attribute directly instead of styles value. It would allow us to go beyond styles e.g. for debugging we can add a special attribute indentifying the element on which UI config has been applied
|
||||||
|
export const useEmbedStyles = (elementName: ElementName) => {
|
||||||
|
const [styles, setStyles] = useState({} as EmbedStyles);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerNewSetter(elementName, setStyles);
|
||||||
|
// It's important to have an element's embed style be required in only one component. If due to any reason it is required in multiple components, we would override state setter.
|
||||||
|
return () => {
|
||||||
|
// Once the component is unmounted, we can remove that state setter.
|
||||||
|
removeFromEmbedStylesSetterMap(elementName);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return styles[elementName] || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// If you add a method here, give type safety to parent manually by adding it to embed.ts. Look for "parentKnowsIframeReady" in it
|
||||||
|
export const methods = {
|
||||||
|
ui: function style(uiConfig: UiConfig) {
|
||||||
|
// TODO: Create automatic logger for all methods. Useful for debugging.
|
||||||
|
console.log("Method: ui called", uiConfig);
|
||||||
|
const stylesConfig = uiConfig.styles;
|
||||||
|
|
||||||
|
// In case where parent gives instructions before setEmbedStyles is set.
|
||||||
|
if (!setEmbedStyles) {
|
||||||
|
return requestAnimationFrame(() => {
|
||||||
|
style(uiConfig);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// body can't be styled using React state hook as it is generated by _document.tsx which doesn't support hooks.
|
||||||
|
if (stylesConfig.body?.background) {
|
||||||
|
document.body.style.background = stylesConfig.body.background as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEmbedStyles(stylesConfig);
|
||||||
|
},
|
||||||
|
parentKnowsIframeReady: () => {
|
||||||
|
document.body.style.display = "block";
|
||||||
|
sdkActionManager?.fire("linkReady", {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageParent = (data: any) => {
|
||||||
|
parent.postMessage(
|
||||||
|
{
|
||||||
|
originator: "CAL",
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function keepParentInformedAboutDimensionChanges() {
|
||||||
|
let knownHiddenHeight: Number | null = null;
|
||||||
|
let numDimensionChanges = 0;
|
||||||
|
requestAnimationFrame(function informAboutScroll() {
|
||||||
|
// Because of scroll="no", this much is hidden from the user.
|
||||||
|
const hiddenHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
|
// TODO: Handle width as well.
|
||||||
|
if (knownHiddenHeight !== hiddenHeight) {
|
||||||
|
knownHiddenHeight = hiddenHeight;
|
||||||
|
numDimensionChanges++;
|
||||||
|
// FIXME: This event shouldn't be subscribable by the user. Only by the SDK.
|
||||||
|
sdkActionManager?.fire("dimension-changed", {
|
||||||
|
hiddenHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(informAboutScroll);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== "undefined" && !location.search.includes("prerender=true")) {
|
||||||
|
sdkActionManager?.on("*", (e) => {
|
||||||
|
const detail = e.detail;
|
||||||
|
//console.log(detail.fullType, detail.type, detail.data);
|
||||||
|
messageParent(detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("message", (e) => {
|
||||||
|
const data: Record<string, any> = e.data;
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const method: keyof typeof methods = data.method;
|
||||||
|
if (data.originator === "CAL" && typeof method === "string") {
|
||||||
|
methods[method]?.(data.arg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
keepParentInformedAboutDimensionChanges();
|
||||||
|
sdkActionManager?.fire("iframeReady", {});
|
||||||
|
}
|
6
packages/embeds/embed-core/src/embed.css
Normal file
6
packages/embeds/embed-core/src/embed.css
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
These styles are applied to the entire page, so the selectors need to be specific
|
||||||
|
*/
|
||||||
|
.cal-embed {
|
||||||
|
border: 0px;
|
||||||
|
}
|
382
packages/embeds/embed-core/src/embed.ts
Normal file
382
packages/embeds/embed-core/src/embed.ts
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
import type { CalWindow } from "@calcom/embed-snippet";
|
||||||
|
|
||||||
|
import { ModalBox } from "./ModalBox";
|
||||||
|
import { methods, UiConfig } from "./embed-iframe";
|
||||||
|
import css from "./embed.css";
|
||||||
|
import { SdkActionManager } from "./sdk-action-manager";
|
||||||
|
|
||||||
|
declare module "*.css";
|
||||||
|
|
||||||
|
type Namespace = string;
|
||||||
|
type Config = Record<"origin", "string">;
|
||||||
|
|
||||||
|
const globalCal = (window as CalWindow).Cal;
|
||||||
|
|
||||||
|
if (!globalCal || !globalCal.q) {
|
||||||
|
throw new Error("Cal is not defined. This shouldn't happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.head.appendChild(document.createElement("style")).innerHTML = css;
|
||||||
|
|
||||||
|
function log(...args: any[]) {
|
||||||
|
console.log(...args);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A very simple data validator written with intention of keeping payload size low.
|
||||||
|
* Extend the functionality of it as required by the embed.
|
||||||
|
* @param data
|
||||||
|
* @param schema
|
||||||
|
*/
|
||||||
|
function validate(data: any, schema: Record<"props" | "required", any>) {
|
||||||
|
function checkType(value: any, expectedType: any) {
|
||||||
|
if (typeof expectedType === "string") {
|
||||||
|
return typeof value == expectedType;
|
||||||
|
} else {
|
||||||
|
return value instanceof expectedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUndefined(data: any) {
|
||||||
|
return typeof data === "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.required && isUndefined(data)) {
|
||||||
|
throw new Error("Argument is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [prop, propSchema] of Object.entries<Record<"type" | "required", any>>(schema.props)) {
|
||||||
|
if (propSchema.required && isUndefined(data[prop])) {
|
||||||
|
throw new Error(`"${prop}" is required`);
|
||||||
|
}
|
||||||
|
let typeCheck = true;
|
||||||
|
if (propSchema.type && !isUndefined(data[prop])) {
|
||||||
|
if (propSchema.type instanceof Array) {
|
||||||
|
propSchema.type.forEach((type) => {
|
||||||
|
typeCheck = typeCheck || checkType(data[prop], type);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
typeCheck = checkType(data[prop], propSchema.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!typeCheck) {
|
||||||
|
throw new Error(`"${prop}" is of wrong type.Expected type "${propSchema.type}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Instruction = [method: string, argument: any] | [method: string, argument: any][];
|
||||||
|
export type InstructionQueue = Instruction[];
|
||||||
|
|
||||||
|
export class Cal {
|
||||||
|
iframe?: HTMLIFrameElement;
|
||||||
|
|
||||||
|
__config: any;
|
||||||
|
|
||||||
|
namespace: string;
|
||||||
|
|
||||||
|
actionManager: SdkActionManager;
|
||||||
|
|
||||||
|
iframeReady!: boolean;
|
||||||
|
|
||||||
|
iframeDoQueue: { method: keyof typeof methods; arg: any }[] = [];
|
||||||
|
|
||||||
|
static actionsManagers: Record<Namespace, SdkActionManager>;
|
||||||
|
|
||||||
|
static getQueryObject(config: Record<string, string>) {
|
||||||
|
config = config || {};
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
// guests is better for API but Booking Page accepts guest. So do the mapping
|
||||||
|
guest: config.guests ?? "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
processInstruction(instruction: Instruction) {
|
||||||
|
instruction = [].slice.call(instruction, 0);
|
||||||
|
if (instruction[0] instanceof Array) {
|
||||||
|
// It is an instruction
|
||||||
|
instruction.forEach((instruction) => {
|
||||||
|
this.processInstruction(instruction);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [method, ...args] = instruction;
|
||||||
|
if (!this[method]) {
|
||||||
|
// Instead of throwing error, log and move forward in the queue
|
||||||
|
log(`Instruction ${method} not FOUND`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
(this[method] as Function)(...args);
|
||||||
|
} catch (e) {
|
||||||
|
// Instead of throwing error, log and move forward in the queue
|
||||||
|
log(`Instruction couldn't be executed`, e);
|
||||||
|
}
|
||||||
|
return instruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
processQueue(queue: InstructionQueue) {
|
||||||
|
queue.forEach((instruction) => {
|
||||||
|
this.processInstruction(instruction);
|
||||||
|
});
|
||||||
|
|
||||||
|
queue.splice(0);
|
||||||
|
|
||||||
|
/** @ts-ignore */ // We changed the definition of push here.
|
||||||
|
queue.push = (instruction) => {
|
||||||
|
this.processInstruction(instruction);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createIframe({
|
||||||
|
calLink,
|
||||||
|
queryObject = {},
|
||||||
|
}: {
|
||||||
|
calLink: string;
|
||||||
|
queryObject?: Record<string, string | string[]>;
|
||||||
|
}) {
|
||||||
|
const iframe = (this.iframe = document.createElement("iframe"));
|
||||||
|
// FIXME: scrolling seems deprecated, though it works on Chrome. What's the recommended way to do it?
|
||||||
|
iframe.scrolling = "no";
|
||||||
|
iframe.className = "cal-embed";
|
||||||
|
const config = this.getConfig();
|
||||||
|
|
||||||
|
// Prepare searchParams from config
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
for (const [key, value] of Object.entries(queryObject)) {
|
||||||
|
if (value instanceof Array) {
|
||||||
|
value.forEach((val) => searchParams.append(key, val));
|
||||||
|
} else {
|
||||||
|
searchParams.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlInstance = new URL(`${config.origin}/${calLink}`);
|
||||||
|
urlInstance.searchParams.set("embed", this.namespace);
|
||||||
|
|
||||||
|
// Merge searchParams from config onto the URL which might have query params already
|
||||||
|
//@ts-ignore
|
||||||
|
for (let [key, value] of searchParams) {
|
||||||
|
urlInstance.searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
iframe.src = urlInstance.toString();
|
||||||
|
return iframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(namespaceOrConfig: string | Config, config: Config = {} as Config) {
|
||||||
|
if (namespaceOrConfig.hasOwnProperty("origin")) {
|
||||||
|
config = namespaceOrConfig as Config;
|
||||||
|
}
|
||||||
|
if (config?.origin) {
|
||||||
|
this.__config.origin = config.origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig() {
|
||||||
|
return this.__config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Maintain exposed methods in a separate namespace, so that unexpected methods don't become instructions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is an instruction that adds embed iframe inline as last child of the element
|
||||||
|
*/
|
||||||
|
inline({
|
||||||
|
calLink,
|
||||||
|
elementOrSelector,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
calLink: string;
|
||||||
|
elementOrSelector: string | HTMLElement;
|
||||||
|
config: Record<string, string>;
|
||||||
|
}) {
|
||||||
|
validate(arguments[0], {
|
||||||
|
required: true,
|
||||||
|
props: {
|
||||||
|
calLink: {
|
||||||
|
required: true,
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
elementOrSelector: {
|
||||||
|
required: true,
|
||||||
|
type: ["string", HTMLElement],
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
required: false,
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const iframe = this.createIframe({ calLink, queryObject: Cal.getQueryObject(config) });
|
||||||
|
iframe.style.height = "100%";
|
||||||
|
iframe.style.width = "100%";
|
||||||
|
let element =
|
||||||
|
elementOrSelector instanceof HTMLElement
|
||||||
|
? elementOrSelector
|
||||||
|
: document.querySelector(elementOrSelector);
|
||||||
|
if (!element) {
|
||||||
|
throw new Error("Element not found");
|
||||||
|
}
|
||||||
|
element.appendChild(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
modal({ calLink }: { calLink: string }) {
|
||||||
|
const iframe = this.createIframe({ calLink });
|
||||||
|
iframe.style.height = "100%";
|
||||||
|
iframe.style.width = "100%";
|
||||||
|
const template = document.createElement("template");
|
||||||
|
template.innerHTML = `<cal-modal-box></cal-modal-box>`;
|
||||||
|
template.content.children[0].appendChild(iframe);
|
||||||
|
document.body.appendChild(template.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
on({
|
||||||
|
action,
|
||||||
|
callback,
|
||||||
|
}: {
|
||||||
|
action: Parameters<SdkActionManager["on"]>[0];
|
||||||
|
callback: Parameters<SdkActionManager["on"]>[1];
|
||||||
|
}) {
|
||||||
|
validate(arguments[0], {
|
||||||
|
required: true,
|
||||||
|
props: {
|
||||||
|
action: {
|
||||||
|
required: true,
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
callback: {
|
||||||
|
required: true,
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.actionManager.on(action, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
preload({ calLink }: { calLink: string }) {
|
||||||
|
validate(arguments[0], {
|
||||||
|
required: true,
|
||||||
|
props: {
|
||||||
|
calLink: {
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const iframe = document.body.appendChild(document.createElement("iframe"));
|
||||||
|
const config = this.getConfig();
|
||||||
|
|
||||||
|
const urlInstance = new URL(`${config.origin}/${calLink}`);
|
||||||
|
urlInstance.searchParams.set("prerender", "true");
|
||||||
|
iframe.src = urlInstance.toString();
|
||||||
|
iframe.style.width = "0";
|
||||||
|
iframe.style.height = "0";
|
||||||
|
iframe.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
ui(uiConfig: UiConfig) {
|
||||||
|
validate(uiConfig, {
|
||||||
|
required: true,
|
||||||
|
props: {
|
||||||
|
theme: {
|
||||||
|
required: false,
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
required: false,
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.doInIframe({ method: "ui", arg: uiConfig });
|
||||||
|
}
|
||||||
|
|
||||||
|
doInIframe({
|
||||||
|
method,
|
||||||
|
arg,
|
||||||
|
}: // TODO: Need some TypeScript magic here to remove hardcoded types
|
||||||
|
| { method: "ui"; arg: Parameters<typeof methods["ui"]>[0] }
|
||||||
|
| { method: "parentKnowsIframeReady"; arg: undefined }) {
|
||||||
|
if (!this.iframeReady) {
|
||||||
|
this.iframeDoQueue.push({ method, arg });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: Ensure that origin is as defined by user. Generally it would be cal.com but in case of self hosting it can be anything.
|
||||||
|
this.iframe!.contentWindow!.postMessage({ originator: "CAL", method, arg }, "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(namespace: string, q: InstructionQueue) {
|
||||||
|
this.__config = {
|
||||||
|
origin: import.meta.env.NEXT_PUBLIC_WEBSITE_URL || "https://cal.com",
|
||||||
|
};
|
||||||
|
this.namespace = namespace;
|
||||||
|
this.actionManager = new SdkActionManager(namespace);
|
||||||
|
|
||||||
|
Cal.actionsManagers = Cal.actionsManagers || {};
|
||||||
|
Cal.actionsManagers[namespace] = this.actionManager;
|
||||||
|
|
||||||
|
this.processQueue(q);
|
||||||
|
|
||||||
|
// 1. Initial iframe width and height would be according to 100% value of the parent element
|
||||||
|
// 2. Once webpage inside iframe renders, it would tell how much iframe height should be increased so that my entire content is visible without iframe scroll
|
||||||
|
// 3. Parent window would check what iframe height can be set according to parent Element
|
||||||
|
this.actionManager.on("dimension-changed", (e) => {
|
||||||
|
const { data } = e.detail;
|
||||||
|
const iframe = this.iframe!;
|
||||||
|
if (!iframe) {
|
||||||
|
// Iframe might be pre-rendering
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let proposedHeightByIframeWebsite = parseFloat(getComputedStyle(iframe).height) + data.hiddenHeight;
|
||||||
|
iframe.style.height = proposedHeightByIframeWebsite;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.actionManager.on("iframeReady", (e) => {
|
||||||
|
this.iframeReady = true;
|
||||||
|
this.doInIframe({ method: "parentKnowsIframeReady", arg: undefined });
|
||||||
|
this.iframeDoQueue.forEach(({ method, arg }) => {
|
||||||
|
this.doInIframe({ method, arg });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCal.instance = new Cal("", globalCal.q!);
|
||||||
|
|
||||||
|
for (let [ns, api] of Object.entries(globalCal.ns!)) {
|
||||||
|
api.instance = new Cal(ns, api.q!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts all postmessages and fires action in corresponding actionManager
|
||||||
|
*/
|
||||||
|
window.addEventListener("message", (e) => {
|
||||||
|
const detail = e.data;
|
||||||
|
const fullType = detail.fullType;
|
||||||
|
const parsedAction = SdkActionManager.parseAction(fullType);
|
||||||
|
if (!parsedAction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const actionManager = Cal.actionsManagers[parsedAction.ns];
|
||||||
|
if (!actionManager) {
|
||||||
|
throw new Error("Unhandled Action" + parsedAction);
|
||||||
|
}
|
||||||
|
actionManager.fire(parsedAction.type, detail.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const htmlElement = e.target;
|
||||||
|
if (!(htmlElement instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = htmlElement.dataset.calLink;
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: Add an option to check which cal instance should be used for this.
|
||||||
|
globalCal("modal", {
|
||||||
|
calLink: path,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
customElements.define("cal-modal-box", ModalBox);
|
58
packages/embeds/embed-core/src/sdk-action-manager.ts
Normal file
58
packages/embeds/embed-core/src/sdk-action-manager.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
type Namespace = string;
|
||||||
|
type CustomEventDetail = Record<string, any>;
|
||||||
|
|
||||||
|
function _fireEvent(fullName: string, detail: CustomEventDetail) {
|
||||||
|
const event = new window.CustomEvent(fullName, {
|
||||||
|
detail: detail,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SdkActionManager {
|
||||||
|
namespace: Namespace;
|
||||||
|
|
||||||
|
static parseAction(fullType: string) {
|
||||||
|
if (!fullType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//FIXME: Ensure that any action if it has :, it is properly encoded.
|
||||||
|
const [cal, calNamespace, type] = fullType.split(":");
|
||||||
|
if (cal !== "CAL") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ns: calNamespace,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullActionName(name: string) {
|
||||||
|
return this.namespace ? `CAL:${this.namespace}:${name}` : `CAL::${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fire(name: string, data: CustomEventDetail) {
|
||||||
|
const fullName = this.getFullActionName(name);
|
||||||
|
const detail = {
|
||||||
|
type: name,
|
||||||
|
namespace: this.namespace,
|
||||||
|
fullType: fullName,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
_fireEvent(fullName, detail);
|
||||||
|
|
||||||
|
// Wildcard Event
|
||||||
|
_fireEvent(this.getFullActionName("*"), detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
on(name: string, callback: (arg0: CustomEvent<CustomEventDetail>) => void) {
|
||||||
|
const fullName = this.getFullActionName(name);
|
||||||
|
window.addEventListener(fullName, callback as EventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(ns: string | null) {
|
||||||
|
ns = ns || "";
|
||||||
|
this.namespace = ns;
|
||||||
|
}
|
||||||
|
}
|
10
packages/embeds/embed-core/src/sdk-event.ts
Normal file
10
packages/embeds/embed-core/src/sdk-event.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* This module is supposed to instantiate the SDK with appropriate namespace
|
||||||
|
*/
|
||||||
|
import { SdkActionManager } from "./sdk-action-manager";
|
||||||
|
|
||||||
|
export let sdkActionManager: SdkActionManager | null = null;
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
sdkActionManager = new SdkActionManager(new URL(document.URL).searchParams.get("embed"));
|
||||||
|
}
|
10
packages/embeds/embed-core/tsconfig.json
Normal file
10
packages/embeds/embed-core/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "@calcom/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"baseUrl": "."
|
||||||
|
},
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["dist", "build", "node_modules"]
|
||||||
|
}
|
15
packages/embeds/embed-core/vite.config.js
Normal file
15
packages/embeds/embed-core/vite.config.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
require("dotenv").config({ path: "../../../.env" });
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const { defineConfig } = require("vite");
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
envPrefix: "NEXT_PUBLIC_",
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: path.resolve(__dirname, "src/embed.ts"),
|
||||||
|
name: "embed",
|
||||||
|
fileName: (format) => `embed.${format}.js`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
215
packages/embeds/embed-core/yarn.lock
Normal file
215
packages/embeds/embed-core/yarn.lock
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
esbuild-android-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz#b868bbd9955a92309c69df628d8dd1945478b45c"
|
||||||
|
integrity sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==
|
||||||
|
|
||||||
|
esbuild-android-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz#e7d6430555e8e9c505fd87266bbc709f25f1825c"
|
||||||
|
integrity sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==
|
||||||
|
|
||||||
|
esbuild-darwin-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz#4dc7484127564e89b4445c0a560a3cb50b3d68e1"
|
||||||
|
integrity sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==
|
||||||
|
|
||||||
|
esbuild-darwin-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz#469e59c665f84a8ed323166624c5e7b9b2d22ac1"
|
||||||
|
integrity sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==
|
||||||
|
|
||||||
|
esbuild-freebsd-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz#895df03bf5f87094a56c9a5815bf92e591903d70"
|
||||||
|
integrity sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==
|
||||||
|
|
||||||
|
esbuild-freebsd-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz#0b72a41a6b8655e9a8c5608f2ec1afdcf6958441"
|
||||||
|
integrity sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==
|
||||||
|
|
||||||
|
esbuild-linux-32@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz#43b8ba3803b0bbe7f051869c6a8bf6de1e95de28"
|
||||||
|
integrity sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==
|
||||||
|
|
||||||
|
esbuild-linux-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz#dc8072097327ecfadba1735562824ce8c05dd0bd"
|
||||||
|
integrity sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==
|
||||||
|
|
||||||
|
esbuild-linux-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz#c52b58cbe948426b1559910f521b0a3f396f10b8"
|
||||||
|
integrity sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==
|
||||||
|
|
||||||
|
esbuild-linux-arm@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz#df869dbd67d4ee3a04b3c7273b6bd2b233e78a18"
|
||||||
|
integrity sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==
|
||||||
|
|
||||||
|
esbuild-linux-mips64le@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz#a2b646d9df368b01aa970a7b8968be6dd6b01d19"
|
||||||
|
integrity sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==
|
||||||
|
|
||||||
|
esbuild-linux-ppc64le@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz#9a21af766a0292578a3009c7408b8509cac7cefd"
|
||||||
|
integrity sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==
|
||||||
|
|
||||||
|
esbuild-linux-riscv64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz#344a27f91568056a5903ad5841b447e00e78d740"
|
||||||
|
integrity sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==
|
||||||
|
|
||||||
|
esbuild-linux-s390x@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz#73a7309bd648a07ef58f069658f989a5096130db"
|
||||||
|
integrity sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==
|
||||||
|
|
||||||
|
esbuild-netbsd-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz#482a587cdbd18a6c264a05136596927deb46c30a"
|
||||||
|
integrity sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==
|
||||||
|
|
||||||
|
esbuild-openbsd-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz#e99f8cdc63f1628747b63edd124d53cf7796468d"
|
||||||
|
integrity sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==
|
||||||
|
|
||||||
|
esbuild-sunos-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz#8611d825bcb8239c78d57452e83253a71942f45c"
|
||||||
|
integrity sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==
|
||||||
|
|
||||||
|
esbuild-windows-32@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz#c06374206d4d92dd31d4fda299b09f51a35e82f6"
|
||||||
|
integrity sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==
|
||||||
|
|
||||||
|
esbuild-windows-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz#756631c1d301dfc0d1a887deed2459ce4079582f"
|
||||||
|
integrity sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==
|
||||||
|
|
||||||
|
esbuild-windows-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz#ad7e187193dcd18768b16065a950f4441d7173f4"
|
||||||
|
integrity sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==
|
||||||
|
|
||||||
|
esbuild@^0.14.14:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.27.tgz#41fe0f1b6b68b9f77cac025009bc54bb96e616f1"
|
||||||
|
integrity sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==
|
||||||
|
optionalDependencies:
|
||||||
|
esbuild-android-64 "0.14.27"
|
||||||
|
esbuild-android-arm64 "0.14.27"
|
||||||
|
esbuild-darwin-64 "0.14.27"
|
||||||
|
esbuild-darwin-arm64 "0.14.27"
|
||||||
|
esbuild-freebsd-64 "0.14.27"
|
||||||
|
esbuild-freebsd-arm64 "0.14.27"
|
||||||
|
esbuild-linux-32 "0.14.27"
|
||||||
|
esbuild-linux-64 "0.14.27"
|
||||||
|
esbuild-linux-arm "0.14.27"
|
||||||
|
esbuild-linux-arm64 "0.14.27"
|
||||||
|
esbuild-linux-mips64le "0.14.27"
|
||||||
|
esbuild-linux-ppc64le "0.14.27"
|
||||||
|
esbuild-linux-riscv64 "0.14.27"
|
||||||
|
esbuild-linux-s390x "0.14.27"
|
||||||
|
esbuild-netbsd-64 "0.14.27"
|
||||||
|
esbuild-openbsd-64 "0.14.27"
|
||||||
|
esbuild-sunos-64 "0.14.27"
|
||||||
|
esbuild-windows-32 "0.14.27"
|
||||||
|
esbuild-windows-64 "0.14.27"
|
||||||
|
esbuild-windows-arm64 "0.14.27"
|
||||||
|
|
||||||
|
fsevents@~2.3.2:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
|
|
||||||
|
function-bind@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
|
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||||
|
|
||||||
|
has@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||||
|
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
|
is-core-module@^2.8.1:
|
||||||
|
version "2.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
|
||||||
|
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
|
||||||
|
dependencies:
|
||||||
|
has "^1.0.3"
|
||||||
|
|
||||||
|
nanoid@^3.3.1:
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
|
||||||
|
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
|
||||||
|
|
||||||
|
path-parse@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
|
picocolors@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
|
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||||
|
|
||||||
|
postcss@^8.4.6:
|
||||||
|
version "8.4.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
|
||||||
|
integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.1"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
resolve@^1.22.0:
|
||||||
|
version "1.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
|
||||||
|
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
|
||||||
|
dependencies:
|
||||||
|
is-core-module "^2.8.1"
|
||||||
|
path-parse "^1.0.7"
|
||||||
|
supports-preserve-symlinks-flag "^1.0.0"
|
||||||
|
|
||||||
|
rollup@^2.59.0:
|
||||||
|
version "2.70.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e"
|
||||||
|
integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
source-map-js@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
|
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
|
vite@^2.8.6:
|
||||||
|
version "2.8.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite/-/vite-2.8.6.tgz#32d50e23c99ca31b26b8ccdc78b1d72d4d7323d3"
|
||||||
|
integrity sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.14.14"
|
||||||
|
postcss "^8.4.6"
|
||||||
|
resolve "^1.22.0"
|
||||||
|
rollup "^2.59.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
14
packages/embeds/embed-react/README.md
Normal file
14
packages/embeds/embed-react/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# cal-react
|
||||||
|
|
||||||
|
Makes the embed available as a React component.
|
||||||
|
|
||||||
|
To add the embed on a webpage built using React. Follow the steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn add @calcom/embed-react
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import Cal from "@calcom/embed-react"
|
||||||
|
<Cal></Cal>
|
||||||
|
```
|
1
packages/embeds/embed-react/env.d.ts
vendored
Normal file
1
packages/embeds/embed-react/env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
10
packages/embeds/embed-react/index.html
Normal file
10
packages/embeds/embed-react/index.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
|
||||||
|
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
|
||||||
|
<script type="module" src="./test-cal.tsx"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
17
packages/embeds/embed-react/package.json
Normal file
17
packages/embeds/embed-react/package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "@calcom/embed-react",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Embed Cal Booking anywhere",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port=3003 --open",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"type-check": "tsc --pretty --noEmit",
|
||||||
|
"lint": "eslint --ext .ts,.js,.tsx,.jsx ./src"
|
||||||
|
},
|
||||||
|
"main": "src/Cal.tsx",
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^2.8.6",
|
||||||
|
"eslint": "^8.10.0"
|
||||||
|
}
|
||||||
|
}
|
33
packages/embeds/embed-react/src/Cal.tsx
Normal file
33
packages/embeds/embed-react/src/Cal.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import useEmbed from "./useEmbed";
|
||||||
|
|
||||||
|
export default function Cal({
|
||||||
|
calLink,
|
||||||
|
config,
|
||||||
|
embedJsUrl,
|
||||||
|
}: {
|
||||||
|
calLink: string;
|
||||||
|
config?: any;
|
||||||
|
embedJsUrl?: string;
|
||||||
|
}) {
|
||||||
|
const Cal = useEmbed(embedJsUrl);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Cal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Cal("init");
|
||||||
|
Cal("inline", {
|
||||||
|
elementOrSelector: ref.current,
|
||||||
|
calLink,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}, [Cal, calLink, config]);
|
||||||
|
|
||||||
|
if (!Cal) {
|
||||||
|
return <div>Loading {calLink}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div ref={ref}></div>;
|
||||||
|
}
|
13
packages/embeds/embed-react/src/useEmbed.ts
Normal file
13
packages/embeds/embed-react/src/useEmbed.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import EmbedSnippet from "@calcom/embed-snippet";
|
||||||
|
|
||||||
|
export default function useEmbed(embedJsUrl?: string) {
|
||||||
|
const [globalCal, setGlobalCal] = useState<ReturnType<typeof EmbedSnippet>>();
|
||||||
|
useEffect(() => {
|
||||||
|
setGlobalCal(() => {
|
||||||
|
return EmbedSnippet(embedJsUrl);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
return globalCal;
|
||||||
|
}
|
24
packages/embeds/embed-react/test-cal.tsx
Normal file
24
packages/embeds/embed-react/test-cal.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import ReactDom from "react-dom";
|
||||||
|
|
||||||
|
import Cal from "@calcom/embed-react";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>
|
||||||
|
There is <code>Cal</code> component below me
|
||||||
|
</h1>
|
||||||
|
<Cal
|
||||||
|
embedJsUrl="//localhost:3002/dist/embed.umd.js"
|
||||||
|
calLink="pro"
|
||||||
|
config={{
|
||||||
|
name: "John Doe",
|
||||||
|
email: "johndoe@gmail.com",
|
||||||
|
notes: "Test Meeting",
|
||||||
|
guests: ["janedoe@gmail.com"],
|
||||||
|
theme: "dark",
|
||||||
|
}}></Cal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ReactDom.render(<App></App>, document.getElementById("root"));
|
11
packages/embeds/embed-react/tsconfig.json
Normal file
11
packages/embeds/embed-react/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@calcom/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["dist", "build", "node_modules"]
|
||||||
|
}
|
11
packages/embeds/embed-snippet/README.md
Normal file
11
packages/embeds/embed-snippet/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# embed-snippet
|
||||||
|
|
||||||
|
This is the code snippet that is to be installed by the user on his website.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
`yarn build` will generate dist/snippet.es.js
|
||||||
|
|
||||||
|
- which can be used as `<script type="module" src=...`
|
||||||
|
- You can also copy the appropriate portion of the code and install it directly as `<script>CODE_SUGGESTED_TO_BE_COPIED</script>`
|
||||||
|
|
1
packages/embeds/embed-snippet/env.d.ts
vendored
Normal file
1
packages/embeds/embed-snippet/env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
15
packages/embeds/embed-snippet/package.json
Normal file
15
packages/embeds/embed-snippet/package.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "@calcom/embed-snippet",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port=3002",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"type-check": "tsc --pretty --noEmit",
|
||||||
|
"lint": "eslint --ext .ts,.js src"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.10.0"
|
||||||
|
}
|
||||||
|
}
|
58
packages/embeds/embed-snippet/src/index.ts
Normal file
58
packages/embeds/embed-snippet/src/index.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
export interface GlobalCal {
|
||||||
|
(methodName: string, arg?: any): void;
|
||||||
|
/** Marks that the embed.js is loaded. Avoids re-downloading it. */
|
||||||
|
loaded?: boolean;
|
||||||
|
/** Maintains a queue till the time embed.js isn't loaded */
|
||||||
|
q?: InstructionQueue;
|
||||||
|
/** If user registers multiple namespaces, those are available here */
|
||||||
|
ns?: Record<string, GlobalCal>;
|
||||||
|
instance?: CalClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalWindow extends Window {
|
||||||
|
Cal?: GlobalCal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EmbedSnippet(url = "https://cal.com/embed.js") {
|
||||||
|
/*! Copy the code below and paste it in script tag of your website */
|
||||||
|
(function (C: CalWindow, A, L) {
|
||||||
|
let d = C.document;
|
||||||
|
C.Cal =
|
||||||
|
C.Cal ||
|
||||||
|
function () {
|
||||||
|
let cal = C.Cal!;
|
||||||
|
let ar = arguments;
|
||||||
|
if (!cal.loaded) {
|
||||||
|
cal.ns = {};
|
||||||
|
cal.q = cal.q || [];
|
||||||
|
d.head.appendChild(d.createElement("script")).src = A;
|
||||||
|
cal.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ar[0] === L) {
|
||||||
|
const api: { (): void; q: any[] } = function () {
|
||||||
|
api.q.push(arguments);
|
||||||
|
};
|
||||||
|
const namespace = arguments[1];
|
||||||
|
api.q = api.q || [];
|
||||||
|
namespace ? (cal.ns![namespace] = api) : null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cal.q!.push(ar as unknown as Instruction);
|
||||||
|
};
|
||||||
|
})(
|
||||||
|
window,
|
||||||
|
//! Replace it with "https://cal.com/embed.js" or the URL where you have embed.js installed
|
||||||
|
url,
|
||||||
|
"init"
|
||||||
|
);
|
||||||
|
/*! Copying ends here. */
|
||||||
|
|
||||||
|
return (window as CalWindow).Cal;
|
||||||
|
}
|
9
packages/embeds/embed-snippet/tsconfig.json
Normal file
9
packages/embeds/embed-snippet/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "@calcom/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "esnext"
|
||||||
|
},
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["dist", "build", "node_modules"]
|
||||||
|
}
|
12
packages/embeds/embed-snippet/vite.config.js
Normal file
12
packages/embeds/embed-snippet/vite.config.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const path = require("path");
|
||||||
|
const { defineConfig } = require("vite");
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: path.resolve(__dirname, "index.ts"),
|
||||||
|
name: "snippet",
|
||||||
|
fileName: (format) => `snippet.${format}.js`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
172
yarn.lock
172
yarn.lock
|
@ -3357,11 +3357,16 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
"@stripe/stripe-js@^1.16.0", "@stripe/stripe-js@^1.17.1":
|
"@stripe/stripe-js@^1.16.0":
|
||||||
version "1.24.0"
|
version "1.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.24.0.tgz#d23977f364565981f8ab30b1b540e367f72abc5c"
|
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.24.0.tgz#d23977f364565981f8ab30b1b540e367f72abc5c"
|
||||||
integrity sha512-8CEILOpzoRhGwvgcf6y+BlPyEq1ZqxAv3gsX7LvokFYvbcyH72GRcHQMGXuZS3s7HqfYQuTSFrvZNL/qdkgA9Q==
|
integrity sha512-8CEILOpzoRhGwvgcf6y+BlPyEq1ZqxAv3gsX7LvokFYvbcyH72GRcHQMGXuZS3s7HqfYQuTSFrvZNL/qdkgA9Q==
|
||||||
|
|
||||||
|
"@stripe/stripe-js@^1.17.1":
|
||||||
|
version "1.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.26.0.tgz#45670924753c01e18d0544ea1f1067b474aaa96f"
|
||||||
|
integrity sha512-4R1vC75yKaCVFARW3bhelf9+dKt4NP4iZY/sIjGK7AAMBVvZ47eG74NvsAIUdUnhOXSWFMjdFWqv+etk5BDW4g==
|
||||||
|
|
||||||
"@szmarczak/http-timer@^1.1.2":
|
"@szmarczak/http-timer@^1.1.2":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
|
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
|
||||||
|
@ -5897,7 +5902,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@4, debug@4.3.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@~4.3.1:
|
debug@4, debug@4.3.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3:
|
||||||
version "4.3.3"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||||
|
@ -5911,6 +5916,13 @@ debug@^3.2.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
|
debug@~4.3.1:
|
||||||
|
version "4.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
decamelize@^1.2.0:
|
decamelize@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
|
@ -6453,6 +6465,132 @@ es6-symbol@^3.1.1, es6-symbol@^3.1.3:
|
||||||
d "^1.0.1"
|
d "^1.0.1"
|
||||||
ext "^1.1.2"
|
ext "^1.1.2"
|
||||||
|
|
||||||
|
esbuild-android-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz#b868bbd9955a92309c69df628d8dd1945478b45c"
|
||||||
|
integrity sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==
|
||||||
|
|
||||||
|
esbuild-android-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz#e7d6430555e8e9c505fd87266bbc709f25f1825c"
|
||||||
|
integrity sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==
|
||||||
|
|
||||||
|
esbuild-darwin-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz#4dc7484127564e89b4445c0a560a3cb50b3d68e1"
|
||||||
|
integrity sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==
|
||||||
|
|
||||||
|
esbuild-darwin-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz#469e59c665f84a8ed323166624c5e7b9b2d22ac1"
|
||||||
|
integrity sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==
|
||||||
|
|
||||||
|
esbuild-freebsd-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz#895df03bf5f87094a56c9a5815bf92e591903d70"
|
||||||
|
integrity sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==
|
||||||
|
|
||||||
|
esbuild-freebsd-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz#0b72a41a6b8655e9a8c5608f2ec1afdcf6958441"
|
||||||
|
integrity sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==
|
||||||
|
|
||||||
|
esbuild-linux-32@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz#43b8ba3803b0bbe7f051869c6a8bf6de1e95de28"
|
||||||
|
integrity sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==
|
||||||
|
|
||||||
|
esbuild-linux-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz#dc8072097327ecfadba1735562824ce8c05dd0bd"
|
||||||
|
integrity sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==
|
||||||
|
|
||||||
|
esbuild-linux-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz#c52b58cbe948426b1559910f521b0a3f396f10b8"
|
||||||
|
integrity sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==
|
||||||
|
|
||||||
|
esbuild-linux-arm@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz#df869dbd67d4ee3a04b3c7273b6bd2b233e78a18"
|
||||||
|
integrity sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==
|
||||||
|
|
||||||
|
esbuild-linux-mips64le@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz#a2b646d9df368b01aa970a7b8968be6dd6b01d19"
|
||||||
|
integrity sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==
|
||||||
|
|
||||||
|
esbuild-linux-ppc64le@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz#9a21af766a0292578a3009c7408b8509cac7cefd"
|
||||||
|
integrity sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==
|
||||||
|
|
||||||
|
esbuild-linux-riscv64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz#344a27f91568056a5903ad5841b447e00e78d740"
|
||||||
|
integrity sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==
|
||||||
|
|
||||||
|
esbuild-linux-s390x@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz#73a7309bd648a07ef58f069658f989a5096130db"
|
||||||
|
integrity sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==
|
||||||
|
|
||||||
|
esbuild-netbsd-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz#482a587cdbd18a6c264a05136596927deb46c30a"
|
||||||
|
integrity sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==
|
||||||
|
|
||||||
|
esbuild-openbsd-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz#e99f8cdc63f1628747b63edd124d53cf7796468d"
|
||||||
|
integrity sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==
|
||||||
|
|
||||||
|
esbuild-sunos-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz#8611d825bcb8239c78d57452e83253a71942f45c"
|
||||||
|
integrity sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==
|
||||||
|
|
||||||
|
esbuild-windows-32@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz#c06374206d4d92dd31d4fda299b09f51a35e82f6"
|
||||||
|
integrity sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==
|
||||||
|
|
||||||
|
esbuild-windows-64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz#756631c1d301dfc0d1a887deed2459ce4079582f"
|
||||||
|
integrity sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==
|
||||||
|
|
||||||
|
esbuild-windows-arm64@0.14.27:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz#ad7e187193dcd18768b16065a950f4441d7173f4"
|
||||||
|
integrity sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==
|
||||||
|
|
||||||
|
esbuild@^0.14.14:
|
||||||
|
version "0.14.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.27.tgz#41fe0f1b6b68b9f77cac025009bc54bb96e616f1"
|
||||||
|
integrity sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==
|
||||||
|
optionalDependencies:
|
||||||
|
esbuild-android-64 "0.14.27"
|
||||||
|
esbuild-android-arm64 "0.14.27"
|
||||||
|
esbuild-darwin-64 "0.14.27"
|
||||||
|
esbuild-darwin-arm64 "0.14.27"
|
||||||
|
esbuild-freebsd-64 "0.14.27"
|
||||||
|
esbuild-freebsd-arm64 "0.14.27"
|
||||||
|
esbuild-linux-32 "0.14.27"
|
||||||
|
esbuild-linux-64 "0.14.27"
|
||||||
|
esbuild-linux-arm "0.14.27"
|
||||||
|
esbuild-linux-arm64 "0.14.27"
|
||||||
|
esbuild-linux-mips64le "0.14.27"
|
||||||
|
esbuild-linux-ppc64le "0.14.27"
|
||||||
|
esbuild-linux-riscv64 "0.14.27"
|
||||||
|
esbuild-linux-s390x "0.14.27"
|
||||||
|
esbuild-netbsd-64 "0.14.27"
|
||||||
|
esbuild-openbsd-64 "0.14.27"
|
||||||
|
esbuild-sunos-64 "0.14.27"
|
||||||
|
esbuild-windows-32 "0.14.27"
|
||||||
|
esbuild-windows-64 "0.14.27"
|
||||||
|
esbuild-windows-arm64 "0.14.27"
|
||||||
|
|
||||||
escalade@^3.1.1:
|
escalade@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
|
@ -12954,7 +13092,12 @@ react-fit@^1.4.0:
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
tiny-warning "^1.0.0"
|
tiny-warning "^1.0.0"
|
||||||
|
|
||||||
react-hook-form@^7.16.2, react-hook-form@^7.20.4:
|
react-hook-form@^7.16.2:
|
||||||
|
version "7.29.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.29.0.tgz#5e7e41a483b70731720966ed8be52163ea1fecf1"
|
||||||
|
integrity sha512-NcJqWRF6el5HMW30fqZRt27s+lorvlCCDbTpAyHoodQeYWXgQCvZJJQLC1kRMKdrJknVH0NIg3At6TUzlZJFOQ==
|
||||||
|
|
||||||
|
react-hook-form@^7.20.4:
|
||||||
version "7.28.0"
|
version "7.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.28.0.tgz#40f385da1f31a3c26bb7491d7d77c111b6ad6909"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.28.0.tgz#40f385da1f31a3c26bb7491d7d77c111b6ad6909"
|
||||||
integrity sha512-mmLpT86BkMGPr0r6ca8zxV0WH4Y1FW5MKs7Rq1+uHLVeeg5pSWbF5Z/qLCnM5vPVblHNM6lRBRRotnfEAf0ALA==
|
integrity sha512-mmLpT86BkMGPr0r6ca8zxV0WH4Y1FW5MKs7Rq1+uHLVeeg5pSWbF5Z/qLCnM5vPVblHNM6lRBRRotnfEAf0ALA==
|
||||||
|
@ -13610,6 +13753,13 @@ rollup-plugin-polyfill-node@0.8.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@rollup/plugin-inject" "^4.0.0"
|
"@rollup/plugin-inject" "^4.0.0"
|
||||||
|
|
||||||
|
rollup@^2.59.0:
|
||||||
|
version "2.70.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e"
|
||||||
|
integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
rsvp@^4.8.4:
|
rsvp@^4.8.4:
|
||||||
version "4.8.5"
|
version "4.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||||
|
@ -15550,6 +15700,18 @@ vfile@^5.0.0:
|
||||||
unist-util-stringify-position "^3.0.0"
|
unist-util-stringify-position "^3.0.0"
|
||||||
vfile-message "^3.0.0"
|
vfile-message "^3.0.0"
|
||||||
|
|
||||||
|
vite@^2.8.6:
|
||||||
|
version "2.8.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite/-/vite-2.8.6.tgz#32d50e23c99ca31b26b8ccdc78b1d72d4d7323d3"
|
||||||
|
integrity sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.14.14"
|
||||||
|
postcss "^8.4.6"
|
||||||
|
resolve "^1.22.0"
|
||||||
|
rollup "^2.59.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
void-elements@3.1.0:
|
void-elements@3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||||
|
@ -16315,12 +16477,12 @@ zod-prisma@^0.5.4:
|
||||||
parenthesis "^3.1.8"
|
parenthesis "^3.1.8"
|
||||||
ts-morph "^13.0.2"
|
ts-morph "^13.0.2"
|
||||||
|
|
||||||
zod@^3.14.2:
|
zod@^3.14.2, zod@^3.9.5:
|
||||||
version "3.14.3"
|
version "3.14.3"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123"
|
||||||
integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ==
|
integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ==
|
||||||
|
|
||||||
zod@^3.8.2, zod@^3.9.5:
|
zod@^3.8.2:
|
||||||
version "3.13.4"
|
version "3.13.4"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.13.4.tgz#5d6fe03ef4824a637d7ef50b5441cf6ab3acede0"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.13.4.tgz#5d6fe03ef4824a637d7ef50b5441cf6ab3acede0"
|
||||||
integrity sha512-LZRucWt4j/ru5azOkJxCfpR87IyFDn8h2UODdqvXzZLb3K7bb9chUrUIGTy3BPsr8XnbQYfQ5Md5Hu2OYIo1mg==
|
integrity sha512-LZRucWt4j/ru5azOkJxCfpR87IyFDn8h2UODdqvXzZLb3K7bb9chUrUIGTy3BPsr8XnbQYfQ5Md5Hu2OYIo1mg==
|
||||||
|
|
Loading…
Reference in a new issue