chore: added headless routing link to embed (#22921)

* added headless routing link to embed

* Update apps/web/public/static/locales/en/common.json

* Update apps/web/public/static/locales/en/common.json

* fix: add missing headless embed type handlers to resolve TypeScript errors

- Add headless handlers for react, react-atom, and HTML frameworks in EmbedCodes.tsx
- Add headless case handling in EmbedTabs.tsx getEmbedTypeSpecificString function
- Add embedCalOrigin parameter to HTML framework handlers for type consistency
- All handlers return documentation comments directing users to headless routing docs

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* temp: enable IS_CALCOM for localhost testing

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: change headless handlers to return null instead of documentation code

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* revert: remove temporary IS_CALCOM localhost change

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: remove unnecessary parameter destructuring from headless embed handlers

- Headless handlers now take no parameters since they only return null
- Simplified function signatures to avoid unused parameter warnings
- Maintains existing functionality while improving code clarity

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: hariom@cal.com <hariombalhara@gmail.com>
This commit is contained in:
Peer Richelsen
2025-08-08 06:18:59 +00:00
committed by GitHub
co-authored by hariom@cal.com Devin AI
parent 86f6e8bac3
commit 09b1643478
6 changed files with 82 additions and 6 deletions
@@ -2251,6 +2251,8 @@
"floating_button_trigger_modal": "Puts a floating button on your site that triggers a modal with your event type.",
"pop_up_element_click": "Pop up via element click",
"open_dialog_with_element_click": "Open your calendar as a dialog when someone clicks an element.",
"use_my_own_form": "Use my own form",
"use_our_headless_routing_api": "Use our headless routing API and use your own form for submissions.",
"need_help_embedding": "Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options.",
"book_my_cal": "Book my Cal",
"first_name": "First name",
+7 -3
View File
@@ -207,9 +207,13 @@ const ChooseEmbedTypesDialogContent = ({
key={index}
data-testid={embed.type}
onClick={() => {
gotoState({
embedType: embed.type as EmbedType,
});
if (embed.type === "headless") {
window.open("https://cal.com/help/routing/headless-routing", "_blank");
} else {
gotoState({
embedType: embed.type as EmbedType,
});
}
}}>
<div className="bg-default order-none box-border flex-none rounded-md border border-solid transition dark:bg-transparent dark:invert">
{embed.illustration}
+55 -1
View File
@@ -1,6 +1,8 @@
import type { ComponentProps } from "react";
import { EmbedDialog, EmbedButton } from "@calcom/features/embed/Embed";
import { IS_CALCOM } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { tabs } from "./lib/EmbedTabs";
@@ -8,11 +10,63 @@ import { useEmbedTypes } from "./lib/hooks";
export const RoutingFormEmbedDialog = () => {
const types = useEmbedTypes();
const { t } = useLocale();
const { data: user } = trpc.viewer.me.get.useQuery();
const routingFormTypes = types.filter((type) => type.type !== "email");
// Add the headless option specifically for routing forms
const headlessType = {
title: t("use_my_own_form"),
subtitle: t("use_our_headless_routing_api"),
type: "headless",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
<rect x="24" y="50.5" width="139" height="163" rx="6" fill="#F8F8F8" />
<g opacity="0.8">
<rect x="42" y="74" width="20" height="20" rx="2" fill="#374151" />
<path
d="M48 81L52 85L56 81"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<rect x="48" y="106" width="80" height="8" rx="6" fill="#F3F4F6" />
<rect x="48" y="118" width="48" height="4" rx="6" fill="#F3F4F6" />
<rect x="48" y="130" width="64" height="4" rx="6" fill="#F3F4F6" />
<rect x="48" y="142" width="32" height="4" rx="6" fill="#F3F4F6" />
<rect x="48" y="160" width="72" height="6" rx="3" fill="#292929" />
<rect x="176" y="50.5" width="108" height="164" rx="6" fill="#F3F4F6" />
<text x="220" y="120" textAnchor="middle" className="fill-gray-400" fontSize="8">
API
</text>
<rect x="200" y="125" width="40" height="2" rx="1" fill="#9CA3AF" />
<rect x="200" y="130" width="32" height="2" rx="1" fill="#9CA3AF" />
<rect x="200" y="135" width="36" height="2" rx="1" fill="#9CA3AF" />
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
</svg>
),
};
const routingFormTypesWithHeadless = IS_CALCOM
? [...routingFormTypes, headlessType]
: routingFormTypes;
return (
<EmbedDialog
types={routingFormTypes}
types={routingFormTypesWithHeadless}
tabs={tabs}
eventTypeHideOptionDisabled={true}
defaultBrandColor={user ? { brandColor: user.brandColor, darkBrandColor: user.darkBrandColor } : null}
+15 -1
View File
@@ -113,6 +113,9 @@ export default function MyApp() {
>Click me</button>;
};`;
},
headless: () => {
return null;
},
},
"react-atom": {
inline: ({
@@ -230,17 +233,22 @@ export default function Booker( props : BookerProps ) {
);
};`;
},
headless: () => {
return null;
},
},
HTML: {
inline: ({
calLink,
uiInstructionCode,
previewState,
embedCalOrigin,
namespace,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState["inline"];
embedCalOrigin: string;
namespace: string;
}) => {
return code`${getApiNameForVanillaJsSnippet({ namespace, mainApiName: "Cal" })}("inline", {
@@ -251,16 +259,17 @@ export default function Booker( props : BookerProps ) {
${uiInstructionCode}`;
},
"floating-popup": ({
calLink,
uiInstructionCode,
previewState,
embedCalOrigin,
namespace,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState["floatingPopup"];
embedCalOrigin: string;
namespace: string;
}) => {
const floatingButtonArg = JSON.stringify({
@@ -277,11 +286,13 @@ export default function Booker( props : BookerProps ) {
calLink,
uiInstructionCode,
previewState,
embedCalOrigin,
namespace,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState["elementClick"];
embedCalOrigin: string;
namespace: string;
}) => {
return code`
@@ -292,6 +303,9 @@ export default function Booker( props : BookerProps ) {
${uiInstructionCode}`;
},
headless: () => {
return null;
},
},
};
@@ -283,6 +283,8 @@ const getEmbedTypeSpecificString = ({
...codeGeneratorInput,
previewState: previewState.elementClick,
});
} else if (embedType === "headless") {
return frameworkCodes[embedType]();
}
return "";
};
+1 -1
View File
@@ -3,7 +3,7 @@ import type { Brand } from "@calcom/types/utils";
import type { tabs } from "../lib/EmbedTabs";
import type { useEmbedTypes } from "../lib/hooks";
export type EmbedType = "inline" | "floating-popup" | "element-click" | "email";
export type EmbedType = "inline" | "floating-popup" | "element-click" | "email" | "headless";
type EmbedConfig = {
layout?: BookerLayouts;
theme?: Theme;