2
0

first commit

This commit is contained in:
2024-08-09 00:39:27 +02:00
commit 79688abe2e
5698 changed files with 497838 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
.turbo
dist

View File

@@ -0,0 +1,91 @@
# @calcom/embed-react
## 1.5.0
### Minor Changes
- Added namespacing support throughout
### Patch Changes
- Updated dependencies
- @calcom/embed-core@1.5.0
- @calcom/embed-snippet@1.3.0
## 1.4.0
### Minor Changes
- Added a few more events
### Patch Changes
- Updated dependencies
- @calcom/embed-core@1.4.0
- @calcom/embed-snippet@1.2.0
## 1.3.0
### Minor Changes
- Fix module import of the embed-react package
## 1.2.2
### Patch Changes
- Improve UI instruction layout typings
- Updated dependencies
- @calcom/embed-snippet@1.1.2
- @calcom/embed-core@1.3.2
## 1.2.1
### Patch Changes
- layout type fix as zod-utils can't be used in npm package
- Updated dependencies
- @calcom/embed-snippet@1.1.1
- @calcom/embed-core@1.3.1
## 1.2.0
### Minor Changes
- Supports new booker layout
### Patch Changes
- Updated dependencies
- @calcom/embed-core@1.3.0
- @calcom/embed-snippet@1.1.0
## 1.1.1
### Patch Changes
- Fix the build for embed-react
- Updated dependencies
- @calcom/embed-snippet@1.0.9
- @calcom/embed-core@1.2.1
## 1.1.0
### Minor Changes
- Fix missing types for @calcom/embed-react. Also, release support for floatingButton config parameter. Though the support is available using embed.js already, for users using getCalApi the TypeScript types would report that config isn't supported.
### Patch Changes
- Updated dependencies
- @calcom/embed-core@1.2.0
- @calcom/embed-snippet@1.0.8
## 1.0.12
### Patch Changes
- Add changesets. Use prepack instead of prePublish and prepublish only as that works with both yarn and npm
- Updated dependencies
- @calcom/embed-snippet@1.0.7
- @calcom/embed-core@1.1.5

View File

@@ -0,0 +1,42 @@
The Cal.com Commercial License (EE) license (the “EE License”)
Copyright (c) 2020-present Cal.com, Inc
With regard to the Cal.com Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cal.com Subscription Terms available
at https://cal.com/terms (the “EE Terms”), or other agreements governing
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
and otherwise have a valid Cal.com Commercial License subscription ("EE Subscription")
for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid EE Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This EE License applies only to the part of this Software that is not distributed under
the AGPLv3 license. Any part of this Software distributed under the AGPLv3 license or which
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
in part, is copyrighted under the AGPLv3 license. The full text of this EE License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Cal.com Software, those
components are licensed under the original license provided by the owner of the
applicable component.

View File

@@ -0,0 +1,25 @@
# cal-react
Embed Cal Link as a React Component
To know how to use it, follow the steps at <https://developer.cal.com/embed/install-with-react>
## Development
Following command starts a hot reloading server
`yarn dev`
If you are working with embed on website, don't forget to do `yarn build` after every change.
## Running Tests
Runs tests and updates the snapshots. Right now we don't care about snapshots
`yarn embed-tests-quick --update-snapshots`
TODO
- Playwright tests.
- Need to what these tests should be as embed-core already have tests. We probably just need to verify that embed-core API is called appropriately.
- It would probably be better if Playwright tests exist at one place for all embeds.
- Distribution
- It would be better DX to serve the unbuilt version with JSX, instead of built version with React.createElement calls. But because of WebPack loaders not running on node_modules automatically, it doesn't work automatically.
- Right now if a typescript project uses the package, VSCode takes the user to .d.ts files instead of the functions definitions. How to solve it ?

View 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="./element-click.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
/**
* @fileoverview This file is an example file and tells how to use the element-click popup embed in a React application. This is also used by playwright e2e
*/
import { useEffect } from "react";
import * as React from "react";
import ReactDom from "react-dom";
import { getCalApi } from "./src/index";
const calNamespace = "element-click";
function App() {
useEffect(() => {
(async function () {
const cal = await getCalApi({
embedJsUrl: "http://localhost:3000/embed/embed.js",
namespace: calNamespace,
});
cal("ui", { styles: { branding: { brandColor: "#000000" } }, hideEventTypeDetails: false });
})();
}, []);
return (
<button
data-cal-namespace={calNamespace}
data-cal-link="pro"
data-cal-config='{"layout":"month_view", "theme":"dark"}'>
Click me
</button>
);
}
ReactDom.render(<App />, document.getElementById("root"));

View File

@@ -0,0 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly EMBED_PUBLIC_WEBAPP_URL: string;
readonly EMBED_PUBLIC_VERCEL_URL: string;
readonly EMBED_PUBLIC_EMBED_LIB_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View 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="./floating.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,30 @@
/**
* @fileoverview This file is an example file and tells how to use floating popup button in a React application. This is also used by playwright e2e
*/
import { useEffect } from "react";
import * as React from "react";
import ReactDom from "react-dom";
import { getCalApi } from "./src/index";
function App() {
useEffect(() => {
(async function () {
const cal = await getCalApi({
namespace: "floating",
embedJsUrl: "http://localhost:3000/embed/embed.js",
});
cal("floatingButton", {
calLink: "pro",
calOrigin: "http://localhost:3000",
config: {
theme: "dark",
},
});
cal("ui", { styles: { branding: { brandColor: "#000000" } }, hideEventTypeDetails: false });
})();
}, []);
return null;
}
ReactDom.render(<App />, document.getElementById("root"));

View File

@@ -0,0 +1,16 @@
<html>
<body>
<h1>Playground has following demos</h1>
<ul>
<li>
<a href="./inline.html">Inline</a>
</li>
<li>
<a href="./floating.html">Floating Button Popup</a>
</li>
<li>
<a href="./element-click.html">Element Click Popup</a>
</li>
</ul>
</body>
</html>

View 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="./inline.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,77 @@
/**
* @fileoverview This file is an example file and tells how to use the Cal component in a React application. This is also used by playwright e2e
*/
import * as React from "react";
import { useEffect } from "react";
import { useState } from "react";
import ReactDom from "react-dom";
// Because we don't import from @calcom/embed-react, this file isn't able to test if the build is successful or not and thus npm package would work or not correctly.
// There are tests in test/built which verifiy that the types from built package are correctly generated and exported correctly.
import Cal, { getCalApi } from "./src/index";
const api = getCalApi({
namespace: "inline",
});
function App() {
const [, setLoaded] = useState(false);
useEffect(() => {
// Simulate state change causing config object to change, causing rerender of Cal
setTimeout(setLoaded.bind(true), 1000);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const callback = (event: any) => {
console.log(event.detail);
};
api.then((api) => {
api("on", {
action: "*",
callback,
});
api("ui", {
cssVarsPerTheme: {
light: {
"cal-border-booker": "red",
"cal-border-booker-width": "20px",
},
dark: {
"cal-border-booker": "red",
"cal-border-booker-width": "5px",
},
},
});
});
return () => {
api.then((api) => {
api("off", {
action: "*",
callback,
});
});
};
}, []);
return (
<>
<h1>
There is <code>Cal</code> component below me
</h1>
<Cal
calOrigin="http://localhost:3000"
embedJsUrl="//localhost:3000/embed/embed.js"
namespace="inline"
style={{ width: "100%", height: "100%", overflow: "scroll" }}
calLink="pro"
config={{
name: "John Doe",
email: "johndoe@gmail.com",
notes: "Test Meeting",
guests: ["janedoe@gmail.com"],
theme: "dark",
}}
/>
</>
);
}
ReactDom.render(<App />, document.getElementById("root"));

View File

@@ -0,0 +1,60 @@
{
"name": "@calcom/embed-react",
"sideEffects": false,
"version": "1.5.0",
"description": "Embed Cal Link as a React Component",
"license": "SEE LICENSE IN LICENSE",
"repository": {
"type": "git",
"url": "https://github.com/calcom/cal.com",
"directory": "packages/embeds/embed-react"
},
"scripts": {
"dev": "vite --port=3101 --open",
"build": "rm -rf dist && vite build && cp ./dist/Cal.es.js ./dist/Cal.es.mjs && tsc --emitDeclarationOnly --declarationDir dist",
"preview": "vite preview",
"type-check": "tsc --pretty --noEmit",
"type-check:ci": "tsc-absolute --pretty --noEmit",
"lint": "eslint --ext .ts,.js,.tsx,.jsx ./src",
"embed-tests": "yarn playwright test --config=./playwright/config/playwright.config.ts",
"embed-tests-quick": "QUICK=true yarn embed-tests",
"embed-tests-update-snapshots:ci": "yarn embed-tests-quick --update-snapshots",
"packaged:tests": "cd test/packaged && yarn tsc --noEmit && yarn run -T test -- --packaged-embed-tests-only",
"withEmbedPublishEnv": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn",
"prepack": "yarn ../../../ lint --filter='@calcom/embed-react' && yarn withEmbedPublishEnv build && yarn packaged:tests",
"embed-web-start": "yarn workspace @calcom/web start",
"embed-dev": "yarn workspace @calcom/embed-react dev",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"main": "./dist/Cal.umd.js",
"module": "./dist/Cal.es.mjs",
"types": "./dist/embed-react/src/index.d.ts",
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/embed-react/src/index.d.ts",
"import": "./dist/Cal.es.mjs",
"require": "./dist/Cal.umd.js"
}
},
"devDependencies": {
"@playwright/test": "^1.31.2",
"@types/react": "18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^2.2.0",
"eslint": "^8.34.0",
"npm-run-all": "^4.1.5",
"typescript": "^4.9.4",
"vite": "^4.5.2"
},
"dependencies": {
"@calcom/embed-core": "workspace:*",
"@calcom/embed-snippet": "workspace:*"
}
}

View File

@@ -0,0 +1,68 @@
import { expect } from "@playwright/test";
import { getEmbedIframe } from "@calcom/embed-core/playwright/lib/testUtils";
// eslint-disable-next-line no-restricted-imports
import { test } from "@calcom/web/playwright/lib/fixtures";
test.describe("React Embed", () => {
test.describe("Inline", () => {
test("should verify that the iframe got created with correct URL - namespaced", async ({
page,
embeds,
}) => {
const calNamespace = "inline";
await embeds.gotoPlayground({ url: "/inline.html", calNamespace });
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
expect(embedIframe).toBeEmbedCalLink("", embeds.getActionFiredDetails, {
pathname: "/pro",
searchParams: {
theme: "dark",
},
});
// expect(await page.screenshot()).toMatchSnapshot("react-component-inline.png");
});
});
test.describe("Floating button Popup", () => {
test("should verify that the iframe got created with correct URL - namespaced", async ({
page,
embeds,
}) => {
const calNamespace = "floating";
await page.waitForLoadState();
await embeds.gotoPlayground({ url: "/floating.html", calNamespace });
await page.click("text=Book my Cal");
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
pathname: "/pro",
searchParams: {
theme: "dark",
},
});
});
});
// TODO: This test is extremely flaky and has been failing a lot, blocking many PRs. Fix this.
// eslint-disable-next-line playwright/no-skipped-test
test.describe.skip("Element Click Popup", () => {
test("should verify that the iframe got created with correct URL - namespaced", async ({
page,
embeds,
}) => {
const calNamespace = "element-click";
await embeds.gotoPlayground({ url: "/element-click.html", calNamespace });
await page.waitForLoadState();
await page.click("text=Click me");
const embedIframe = await getEmbedIframe({ calNamespace, page, pathname: "/pro" });
expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, {
pathname: "/pro",
searchParams: {
theme: "dark",
},
});
});
});
});

View File

@@ -0,0 +1,64 @@
"use client";
import { useEffect, useRef } from "react";
import type { PrefillAndIframeAttrsConfig } from "@calcom/embed-core";
import useEmbed from "./useEmbed";
type CalProps = {
calOrigin?: string;
calLink: string;
initConfig?: {
debug?: boolean;
uiDebug?: boolean;
};
namespace?: string;
config?: PrefillAndIframeAttrsConfig;
embedJsUrl?: string;
} & React.HTMLAttributes<HTMLDivElement>;
const Cal = function Cal(props: CalProps) {
const { calLink, calOrigin, namespace = "", config, initConfig = {}, embedJsUrl, ...restProps } = props;
if (!calLink) {
throw new Error("calLink is required");
}
const initializedRef = useRef(false);
const Cal = useEmbed(embedJsUrl);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!Cal || initializedRef.current || !ref.current) {
return;
}
initializedRef.current = true;
const element = ref.current;
if (namespace) {
Cal("init", namespace, {
...initConfig,
origin: calOrigin,
});
Cal.ns[namespace]("inline", {
elementOrSelector: element,
calLink,
config,
});
} else {
Cal("init", {
...initConfig,
origin: calOrigin,
});
Cal("inline", {
elementOrSelector: element,
calLink,
config,
});
}
}, [Cal, calLink, config, namespace, calOrigin, initConfig]);
if (!Cal) {
return null;
}
return <div ref={ref} {...restProps} />;
};
export default Cal;

View File

@@ -0,0 +1,40 @@
"use client";
import type { GlobalCal, GlobalCalWithoutNs } from "@calcom/embed-core";
import EmbedSnippet from "@calcom/embed-snippet";
import Cal from "./Cal";
export function getCalApi(options?: {
embedJsUrl?: string;
namespace?: string;
}): Promise<GlobalCal | GlobalCalWithoutNs>;
export function getCalApi(embedJsUrl: string): Promise<GlobalCal | GlobalCalWithoutNs>;
export function getCalApi(
optionsOrEmbedJsUrl?:
| {
embedJsUrl?: string;
namespace?: string;
}
| string
): Promise<GlobalCal | GlobalCalWithoutNs> {
const options =
typeof optionsOrEmbedJsUrl === "string" ? { embedJsUrl: optionsOrEmbedJsUrl } : optionsOrEmbedJsUrl ?? {};
const { namespace = "", embedJsUrl } = options;
return new Promise(function tryReadingFromWindow(resolve) {
const globalCal = EmbedSnippet(embedJsUrl);
globalCal("init", namespace);
const api = namespace ? globalCal.ns[namespace as keyof typeof globalCal.ns] : globalCal;
if (!api) {
setTimeout(() => {
tryReadingFromWindow(resolve);
}, 50);
return;
}
resolve(api);
});
}
export default Cal;

View File

@@ -0,0 +1,16 @@
"use client";
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);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return globalCal;
}

View File

@@ -0,0 +1,6 @@
# Packaged
The tests in this file are run on the packaged code that is published to npm. The packaged code is different from the source code in atleast the following ways
- Not all files go to packaged code.If package.json -> files field is specified then only the files that are specified there would be published. So, one might accidentally miss an important file that's available otherwise.
- The packaged code doesn't have .ts files. Those files are actually converted to .js files and .d.ts files are generated separately for TypeScript support. It allows the package to work in both TypeScript and non TypeScript environments.

View File

@@ -0,0 +1,25 @@
/**
* @fileoverview
* This file tests two things in 2 ways
* - It is a vitest test file and thus it tests if the code executes without any error. Thus, it tests that package.json->main/module fields are correctly defined. It obviously verifies the assertions as well.
* - It is also validates for it's types and thus verifies that @calcom/embed-react has correctly specified it's types in package.json->types field.
*/
import { expect, test } from "vitest";
// This import may show up as an error in your IDE, but it's fine because typings are available only after embed-react is built.
import { getCalApi } from "@calcom/embed-react";
const api = getCalApi();
test("Check that the API is available", async () => {
expect(api).toBeDefined();
const awaitedApi = await api;
awaitedApi("floatingButton", {
calLink: "free",
config: {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error We are intentionaly testing invalid value
layout: "wrongview",
},
});
});

View File

@@ -0,0 +1,13 @@
{
"extends": "@calcom/tsconfig/base.json",
"compilerOptions": {
"module": "ESNext",
"target": "ES2015",
"moduleResolution": "Node",
"baseUrl": ".",
"declaration": true,
"jsx": "preserve",
"outDir": "dist"
},
"include": ["**/*.ts"]
}

View File

@@ -0,0 +1,19 @@
{
"extends": "@calcom/tsconfig/base.json",
"compilerOptions": {
"module": "ESNext",
"target": "ES2015",
"moduleResolution": "Node",
"baseUrl": ".",
"declaration": true,
"jsx": "preserve",
"outDir": "dist",
"paths": {
"@calcom/embed-core": ["../embed-core/src"],
"@calcom/embed-snippet": ["../embed-snippet/src"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "env.d.ts"],
// Exclude "test" because that has `api.test.ts` which imports @calcom/embed-react which needs it to be built using this tsconfig.json first. Excluding it here prevents type-check from validating test folder
"exclude": ["node_modules", "test"]
}

View File

@@ -0,0 +1,32 @@
import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig } from "vite";
import viteBaseConfig from "../vite.config";
// https://vitejs.dev/config/
export default defineConfig({
...viteBaseConfig,
plugins: [react()],
build: {
lib: {
entry: path.resolve(__dirname, "src/index.ts"),
name: "Cal",
fileName: (format) => `Cal.${format}.js`,
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ["react", "react-dom"],
output: {
exports: "named",
// Provide global variables to use in the UMD build
// for externalized deps
globals: {
react: "React",
"react-dom": "ReactDOM",
},
},
},
},
});