import { ArrowLeftIcon, ChevronRightIcon, CodeIcon, EyeIcon, SunIcon } from "@heroicons/react/solid"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible"; import classNames from "classnames"; import { useRouter } from "next/router"; import { useRef, useState } from "react"; import { components, ControlProps } from "react-select"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import showToast from "@calcom/lib/notification"; import { EventType } from "@calcom/prisma/client"; import { Button, Switch } from "@calcom/ui"; import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog"; import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields"; import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants"; import { trpc } from "@lib/trpc"; import NavTabs from "@components/NavTabs"; import ColorPicker from "@components/ui/colorpicker"; import Select from "@components/ui/form/Select"; type EmbedType = "inline" | "floating-popup" | "element-click"; const queryParamsForDialog = ["embedType", "tabName", "eventTypeId"]; const embeds: { illustration: React.ReactElement; title: string; subtitle: string; type: EmbedType; }[] = [ { title: "Inline Embed", subtitle: "Loads your Cal scheduling page directly inline with your other website content", type: "inline", illustration: ( {/* */} ), }, { title: "Floating pop-up button", subtitle: "Adds a floating button on your site that launches Cal in a dialog.", type: "floating-popup", illustration: ( {/* */} ), }, { title: "Pop up via element click", subtitle: "Open your Cal dialog when someone clicks an element.", type: "element-click", illustration: ( {/* */} ), }, ]; function getEmbedSnippetString() { // TODO: Import this string from @calcom/embed-snippet return ` (function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; 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 () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar); return; } p(cal, ar); }; })(window, "${EMBED_LIB_URL}", "init"); Cal("init", {origin:"${WEBAPP_URL}"}); `; } const EmbedNavBar = () => { const { t } = useLocale(); const tabs = [ { name: t("Embed"), tabName: "embed-code", icon: CodeIcon, }, { name: t("Preview"), tabName: "embed-preview", icon: EyeIcon, }, ]; return ; }; const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: string; label: string }, false>) => { return ( {children} ); }; const ChooseEmbedTypesDialogContent = () => { const { t } = useLocale(); const router = useRouter(); return (

{t("choose_ways_put_cal_site")}

{embeds.map((embed, index) => ( ))}
); }; const EmbedTypeCodeAndPreviewDialogContent = ({ eventTypeId, embedType, }: { eventTypeId: EventType["id"]; embedType: EmbedType; }) => { const { t } = useLocale(); const router = useRouter(); const iframeRef = useRef(null); const embedCode = useRef(null); const embed = embeds.find((embed) => embed.type === embedType); const { data: eventType, isLoading } = trpc.useQuery([ "viewer.eventTypes.get", { id: +eventTypeId, }, ]); const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true); const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true); const [previewState, setPreviewState] = useState({ inline: { width: "100%", height: "100%", }, theme: "auto", floatingPopup: {}, elementClick: {}, palette: { brandColor: "#000000", }, }); const close = () => { const noPopupQuery = { ...router.query, }; delete noPopupQuery.dialog; queryParamsForDialog.forEach((queryParam) => { delete noPopupQuery[queryParam]; }); router.push({ query: noPopupQuery, }); }; // Use embed-code as default tab if (!router.query.tabName) { router.query.tabName = "embed-code"; router.push({ query: { ...router.query, }, }); } if (isLoading) { return null; } if (!embed || !eventType) { close(); return null; } const calLink = `${eventType.team ? `team/${eventType.team.slug}` : eventType.users[0].username}/${ eventType.slug }`; // TODO: Not sure how to make these template strings look better formatted. // This exact formatting is required to make the code look nicely formatted together. const getEmbedUIInstructionString = () => `Cal("ui", { ${getThemeForSnippet() ? 'theme: "' + previewState.theme + '",\n ' : ""}styles: { branding: ${JSON.stringify(previewState.palette)} } })`; const getEmbedTypeSpecificString = () => { if (embedType === "inline") { return ` Cal("inline", { elementOrSelector:"#my-cal-inline", calLink: "${calLink}" }); ${getEmbedUIInstructionString().trim()}`; } else if (embedType === "floating-popup") { const floatingButtonArg = { calLink, ...previewState.floatingPopup, }; return ` Cal("floatingButton", ${JSON.stringify(floatingButtonArg)}); ${getEmbedUIInstructionString().trim()}`; } else if (embedType === "element-click") { return `//Important: Also, add data-cal-link="${calLink}" attribute to the element you want to open Cal on click ${getEmbedUIInstructionString().trim()}`; } return ""; }; const getThemeForSnippet = () => { return previewState.theme !== "auto" ? previewState.theme : null; }; const getDimension = (dimension: string) => { if (dimension.match(/^\d+$/)) { dimension = `${dimension}%`; } return dimension; }; const addToPalette = (update: typeof previewState["palette"]) => { setPreviewState((previewState) => { return { ...previewState, palette: { ...previewState.palette, ...update, }, }; }); }; const previewInstruction = (instruction: { name: string; arg: unknown }) => { iframeRef.current?.contentWindow?.postMessage( { mode: "cal:preview", type: "instruction", instruction, }, "*" ); }; const inlineEmbedDimensionUpdate = ({ width, height }: { width: string; height: string }) => { iframeRef.current?.contentWindow?.postMessage( { mode: "cal:preview", type: "inlineEmbedDimensionUpdate", data: { width: getDimension(width), height: getDimension(height), }, }, "*" ); }; previewInstruction({ name: "ui", arg: { theme: previewState.theme, styles: { branding: { ...previewState.palette, }, }, }, }); if (embedType === "floating-popup") { previewInstruction({ name: "floatingButton", arg: { attributes: { id: "my-floating-button", }, ...previewState.floatingPopup, }, }); } if (embedType === "inline") { inlineEmbedDimensionUpdate({ width: previewState.inline.width, height: previewState.inline.height, }); } const ThemeOptions = [ { value: "auto", label: "Auto Theme" }, { value: "dark", label: "Dark Theme" }, { value: "light", label: "Light Theme" }, ]; const FloatingPopupPositionOptions = [ { value: "bottom-right", label: "Bottom Right", }, { value: "bottom-left", label: "Bottom Left", }, ]; return (

setIsEmbedCustomizationOpen((val) => !val)}>
{embedType === "inline" ? "Inline Embed Customization" : embedType === "floating-popup" ? "Floating Popup Customization" : "Element Click Customization"}
{/*TODO: Add Auto/Fixed toggle from Figma */}
Embed Window Sizing
{ setPreviewState((previewState) => { const width = e.target.value || "100%"; return { ...previewState, inline: { ...previewState.inline, width, }, }; }); }} addOnLeading={W} /> x { const height = e.target.value || "100%"; setPreviewState((previewState) => { return { ...previewState, inline: { ...previewState.inline, height, }, }; }); }} addOnLeading={H} />
Button Text
{/* Default Values should come from preview iframe */} { setPreviewState((previewState) => { return { ...previewState, floatingPopup: { ...previewState.floatingPopup, buttonText: e.target.value, }, }; }); }} defaultValue="Book my Cal" required />
Display Calendar Icon Button
{ setPreviewState((previewState) => { return { ...previewState, floatingPopup: { ...previewState.floatingPopup, hideButtonIcon: !checked, }, }; }); }}>
Position of Button
Button Color
{ setPreviewState((previewState) => { return { ...previewState, floatingPopup: { ...previewState.floatingPopup, buttonColor: color, }, }; }); }}>
Text Color
{ setPreviewState((previewState) => { return { ...previewState, floatingPopup: { ...previewState.floatingPopup, buttonTextColor: color, }, }; }); }}>
{/*
Button Color on Hover
{ addToPalette({ "floating-popup-button-color-hover": color, }); }}>
*/}

setIsBookingCustomizationOpen((val) => !val)}>
Cal Booking Customization
{[ { name: "brandColor", title: "Brand Color" }, // { name: "lightColor", title: "Light Color" }, // { name: "lighterColor", title: "Lighter Color" }, // { name: "lightestColor", title: "Lightest Color" }, // { name: "highlightColor", title: "Highlight Color" }, // { name: "medianColor", title: "Median Color" }, ].map((palette) => ( ))}
{t("place_where_cal_widget_appear")}

{t( "Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options." )}