import { useCallback, useEffect, useState } from "react"; import { Alert, AlertDescription, type BlockEditorProps, Button, type ReactNode, Card, CardContent, CardDescription, CardHeader, CardTitle, Tabs, TabsContent, TabsList, TabsTrigger, Input, Label, Textarea, Switch, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Settings, Edit3, CheckCircle2, XCircle, Loader2, Key, AlertCircle, ExternalLink, RefreshCw, t, } from "@block-ninja/ui"; type EventTypeSummary = { id: number; slug: string; title: string; lengthInMinutes: number; }; function renderConnectedAccount(apiKeyConfigured: boolean | null, calcomUsername: string) { if (apiKeyConfigured === false) { return

{t("calcom.editor.connectedAs.noKey", "Configure the API key in plugin settings to connect an account.")}

; } if (calcomUsername) { return

{calcomUsername}

; } return

{t("calcom.editor.connectedAs.resolving", "Resolving account from API key…")}

; } function CalcomBlockEditor({ content, onChange }: BlockEditorProps) { const [activeTab, setActiveTab] = useState("config"); const [apiKeyConfigured, setApiKeyConfigured] = useState(null); const [calcomUsername, setCalcomUsername] = useState(""); const [eventTypes, setEventTypes] = useState([]); const [loadingEventTypes, setLoadingEventTypes] = useState(false); const [fetchFailed, setFetchFailed] = useState(false); const [retryCounter, setRetryCounter] = useState(0); const handleFieldChange = useCallback( (field: string, value: unknown) => { onChange({ ...content, [field]: value }); }, [content, onChange], ); const getString = (field: string, defaultValue = "") => { return (content[field] as string) || defaultValue; }; const getNumber = (field: string, defaultValue = 0) => { const val = content[field]; if (typeof val === "number") return val; if (typeof val === "string") return parseInt(val, 10) || defaultValue; return defaultValue; }; const getBool = (field: string, defaultValue = false) => { const val = content[field]; if (typeof val === "boolean") return val; return defaultValue; }; const eventTypeSlug = getString("eventTypeSlug"); useEffect(() => { fetch(`/api/plugins/calcomblock/settings`) .then((r) => r.json()) .then((d: { api_key_configured?: boolean; username?: string }) => { setApiKeyConfigured(!!d.api_key_configured); setCalcomUsername(d.username ?? ""); }) .catch(() => setApiKeyConfigured(false)); }, []); // Stamp the resolved Cal.com username into block content so the public // render path (block.go BookingConfig.Username) keeps working without any // backend rewiring. Existing blocks with a stale username get refreshed // automatically the next time the editor opens. useEffect(() => { if (calcomUsername && content.username !== calcomUsername) { onChange({ ...content, username: calcomUsername }); } }, [calcomUsername, content, onChange]); useEffect(() => { if (!apiKeyConfigured) { setEventTypes([]); setFetchFailed(false); return; } const timer = setTimeout(() => { setLoadingEventTypes(true); setFetchFailed(false); fetch(`/api/plugins/calcomblock/event-types?username=${encodeURIComponent(calcomUsername)}`) .then((r) => r.json()) .then((d: { success?: boolean; event_types?: EventTypeSummary[] }) => { if (d.success) { setEventTypes(d.event_types ?? []); } else { setEventTypes([]); setFetchFailed(true); } }) .catch(() => { setEventTypes([]); setFetchFailed(true); }) .finally(() => setLoadingEventTypes(false)); }, 300); return () => clearTimeout(timer); }, [apiKeyConfigured, calcomUsername, retryCounter]); const handleRetry = useCallback(() => { setRetryCounter((n) => n + 1); }, []); const selectPlaceholder = (() => { if (apiKeyConfigured === false) { return ( {t("calcom.editor.eventType.placeholder.noApiKey", "Configure API key first")} ); } if (loadingEventTypes) { return ( {t("calcom.editor.eventType.placeholder.loading", "Loading…")} ); } if (!calcomUsername) { return ( {t("calcom.editor.eventType.placeholder.resolvingUser", "Resolving account…")} ); } if (eventTypes.length === 0) { return ( {t("calcom.editor.eventType.placeholder.noEventTypes", "No event types found")} ); } return t("calcom.editor.eventType.placeholder.select", "Select event type"); })(); const apiKeyReady = apiKeyConfigured === true; const usernameReady = calcomUsername.length > 0; const eventTypeReady = eventTypeSlug.length > 0; const canOpenInCalcom = usernameReady && eventTypeReady; const renderStatusRow = (ok: boolean, label: string, action?: ReactNode) => (
{ok ?
); return (
{t("calcom.editor.tab.configuration", "Configuration")} {t("calcom.editor.tab.content", "Content")} {/* Configuration Tab */} {apiKeyConfigured === false && ( {t("calcom.editor.alert.apiKeyMissing.prefix", "Configure your Cal.com API key in")}{" "} {t("calcom.editor.alert.apiKeyMissing.linkLabel", "plugin settings")} {" "} {t("calcom.editor.alert.apiKeyMissing.suffix", "to enable the event-type dropdown.")} )} {t("calcom.editor.section.config.title", "Cal.com Settings")} {t("calcom.editor.section.config.description", "Connect to your Cal.com account and select an event type")}
{renderConnectedAccount(apiKeyConfigured, calcomUsername)}
{fetchFailed ? <>
handleFieldChange("eventTypeSlug", e.target.value)} placeholder={t("calcom.editor.eventType.fallbackPlaceholder", "30min")} className="flex-1" />
: <>

{t("calcom.editor.eventType.help", "Fetched from your Cal.com account")}

}

{t("calcom.editor.weeksToShow.help", "How many weeks of dates to show in the calendar")}

{t("calcom.editor.showTimezone.help", "Show a label above the date grid indicating which timezone times are displayed in.")}

handleFieldChange("showTimezone", v)} aria-label={t("calcom.editor.showTimezone.label", "Show Timezone Selector")} />

{t("calcom.editor.captchaEnabled.help", "Require visitors to solve a privacy-friendly proof-of-work captcha before booking (spam protection).")}

handleFieldChange("captchaEnabled", v)} aria-label={t("calcom.editor.captchaEnabled.label", "Enable captcha")} />
{t("calcom.editor.status.title", "Configuration status")} {t("calcom.editor.status.description", "Quick check of the values needed before this block renders bookings.")} {renderStatusRow( apiKeyReady, t("calcom.editor.status.apiKey", "API key configured"), !apiKeyReady ? {t("calcom.editor.status.apiKey.configureLink", "Configure")} : null, )} {renderStatusRow(usernameReady, t("calcom.editor.status.username", "Account resolved"))} {renderStatusRow(eventTypeReady, t("calcom.editor.status.eventType", "Event type selected"))}
· {t("calcom.editor.status.eventTypesAvailable", "{count} event types available").replace("{count}", String(eventTypes.length))}
{/* Content Tab */} {t("calcom.editor.section.content.title", "Widget Content")} {t("calcom.editor.section.content.description", "Customize the text shown in the booking widget")}
handleFieldChange("title", e.target.value)} placeholder={t("calcom.editor.title.placeholder", "Book a Meeting")} />

{t("calcom.editor.title.help", "Main heading shown above the calendar")}