fix: add error handling for cal.com embeds in credentialless environments

- Add 5-second timeout to detect embed load failures
- Show error message with fallback direct booking link
- Handle COEP/Cross-Origin Embedder Policy blocks gracefully

Fixes #7457
This commit is contained in:
NOX Ventures
2026-03-15 00:17:27 +07:00
parent 2dc5c50f4d
commit 0729798537
@@ -1,5 +1,5 @@
import snippet from "@calcom/embed-snippet";
import { useEffect, useMemo } from "preact/hooks";
import { useEffect, useMemo, useState } from "preact/hooks";
import { type TSurveyCalElement } from "@formbricks/types/surveys/elements";
import { cn } from "@/lib/utils";
@@ -9,6 +9,9 @@ interface CalEmbedProps {
}
export function CalEmbed({ element, onSuccessfulBooking }: CalEmbedProps) {
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const cal = useMemo(() => {
const calInline = snippet("https://cal.com/embed.js");
@@ -46,16 +49,57 @@ export function CalEmbed({ element, onSuccessfulBooking }: CalEmbedProps) {
document.querySelectorAll("cal-inline").forEach((el) => {
el.remove();
});
setError(false);
setErrorMessage("");
cal("init", { calOrigin: element.calHost ? `https://${element.calHost}` : "https://cal.com" });
cal("inline", {
elementOrSelector: "#cal-embed",
calLink: element.calUserName,
});
// Set up error detection via MutationObserver for COEP/credentialless failures
const timer = setTimeout(() => {
const embedContainer = document.getElementById("cal-embed");
if (embedContainer) {
const iframe = embedContainer.querySelector("iframe");
if (!iframe) {
setError(true);
setErrorMessage("Failed to load booking widget. Your environment may be blocking cross-origin resources.");
}
}
}, 5000);
return () => clearTimeout(timer);
}, [cal, element.calHost, element.calUserName]);
if (error) {
return (
<div className="relative mt-4 overflow-auto">
<div className="border-border rounded-input border p-4 text-center">
<p className="text-sm text-red-600">
{errorMessage}
</p>
<p className="text-muted-foreground mt-2 text-xs">
Try opening the booking page directly at{" "}
<a
href={`https://${element.calHost || "cal.com"}/${element.calUserName}`}
target="_blank"
rel="noopener noreferrer"
className="underline">
{element.calHost || "cal.com"}/{element.calUserName}
</a>
</p>
</div>
</div>
);
}
return (
<div className="relative mt-4 overflow-auto">
<div id="cal-embed" className={cn("border-border rounded-input border")} />
<div id="cal-embed" className={cn("border-border rounded-input border")}>
{/* The cal.com widget renders here. If COEP/credentialless blocks the iframe,
the timeout above will trigger an error state with a fallback link. */}
</div>
</div>
);
}