import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible"; import classNames from "classnames"; import type { TFunction } from "next-i18next"; import type { NextRouter } from "next/router"; import { useRouter } from "next/router"; import type { MutableRefObject, RefObject } from "react"; import { createRef, forwardRef, useRef, useState } from "react"; import type { ControlProps } from "react-select"; import { components } from "react-select"; import type { BookerLayout } from "@calcom/features/bookings/Booker/types"; import { useFlagMap } from "@calcom/features/flags/context/provider"; import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { BookerLayouts } from "@calcom/prisma/zod-utils"; import { Button, Dialog, DialogClose, DialogContent, HorizontalTabs, Label, showToast, Switch, TextArea, TextField, ColorPicker, Select, } from "@calcom/ui"; import { Code, Trello, Sun, ArrowLeft } from "@calcom/ui/components/icon"; type EmbedType = "inline" | "floating-popup" | "element-click"; type EmbedFramework = "react" | "HTML"; const enum Theme { auto = "auto", light = "light", dark = "dark", } type PreviewState = { inline: { width: string; height: string; }; theme: Theme; floatingPopup: Record; elementClick: Record; palette: { brandColor: string; }; hideEventTypeDetails: boolean; layout: BookerLayouts; }; const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"]; const getDimension = (dimension: string) => { if (dimension.match(/^\d+$/)) { dimension = `${dimension}%`; } return dimension; }; const goto = (router: NextRouter, searchParams: Record) => { const newQuery = new URLSearchParams(router.asPath.split("?")[1]); Object.keys(searchParams).forEach((key) => { newQuery.set(key, searchParams[key]); }); router.push(`${router.asPath.split("?")[0]}?${newQuery.toString()}`, undefined, { shallow: true, }); }; const removeQueryParams = (router: NextRouter, queryParams: string[]) => { const params = new URLSearchParams(window.location.search); queryParams.forEach((param) => { params.delete(param); }); router.push(`${router.asPath.split("?")[0]}?${params.toString()}`); }; /** * It allows us to show code with certain reusable blocks indented according to the block variable placement * So, if you add a variable ${abc} with indentation of 4 spaces, it will automatically indent all newlines in `abc` with the same indent before constructing the final string * `A${var}C` with var = "B" -> partsWithoutBlock=['A','C'] blocksOrVariables=['B'] */ const code = (partsWithoutBlock: TemplateStringsArray, ...blocksOrVariables: string[]) => { const constructedCode: string[] = []; for (let i = 0; i < partsWithoutBlock.length; i++) { const partWithoutBlock = partsWithoutBlock[i]; // blocksOrVariables length would always be 1 less than partsWithoutBlock // So, last item should be concatenated as is. if (i >= blocksOrVariables.length) { constructedCode.push(partWithoutBlock); continue; } const block = blocksOrVariables[i]; const indentedBlock: string[] = []; let indent = ""; block.split("\n").forEach((line) => { indentedBlock.push(line); }); // non-null assertion is okay because we know that we are referencing last element. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const indentationMatch = partWithoutBlock .split("\n") .at(-1)! .match(/(^[\t ]*).*$/); if (indentationMatch) { indent = indentationMatch[1]; } constructedCode.push(partWithoutBlock + indentedBlock.join("\n" + indent)); } return constructedCode.join(""); }; const getInstructionString = ({ apiName, instructionName, instructionArg, }: { apiName: string; instructionName: string; instructionArg: Record; }) => { return `${apiName}("${instructionName}", ${JSON.stringify(instructionArg)});`; }; const getEmbedUIInstructionString = ({ apiName, theme, brandColor, hideEventTypeDetails, layout, }: { apiName: string; theme?: string; brandColor: string; hideEventTypeDetails: boolean; layout?: string; }) => { theme = theme !== "auto" ? theme : undefined; return getInstructionString({ apiName, instructionName: "ui", instructionArg: { theme, styles: { branding: { brandColor, }, }, hideEventTypeDetails: hideEventTypeDetails, layout, }, }); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const Codes: Record string>> = { react: { inline: ({ calLink, uiInstructionCode, previewState, }: { calLink: string; uiInstructionCode: string; previewState: PreviewState; }) => { const width = getDimension(previewState.inline.width); const height = getDimension(previewState.inline.height); return code` import Cal, { getCalApi } from "@calcom/embed-react"; import { useEffect } from "react"; function MyComponent() { useEffect(()=>{ (async function () { const cal = await getCalApi(); ${uiInstructionCode} })(); }, []) return ; };`; }, "floating-popup": ({ floatingButtonArg, uiInstructionCode, }: { floatingButtonArg: string; uiInstructionCode: string; }) => { return code` import Cal, { getCalApi } from "@calcom/embed-react"; import { useEffect } from "react"; function MyComponent() { useEffect(()=>{ (async function () { const cal = await getCalApi(); cal("floatingButton", ${floatingButtonArg}); ${uiInstructionCode} })(); }, []) };`; }, "element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => { return code` import Cal, { getCalApi } from "@calcom/embed-react"; import { useEffect } from "react"; function MyComponent() { useEffect(()=>{ (async function () { const cal = await getCalApi(); ${uiInstructionCode} })(); }, []) return