diff --git a/src/components/robot/pages/RobotIntegrationPage.tsx b/src/components/robot/pages/RobotIntegrationPage.tsx index 723b5986..ea8295c0 100644 --- a/src/components/robot/pages/RobotIntegrationPage.tsx +++ b/src/components/robot/pages/RobotIntegrationPage.tsx @@ -64,19 +64,6 @@ export interface IntegrationSettings { integrationType: "googleSheets" | "airtable" | "webhook"; } -const getCookie = (name: string): string | null => { - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) { - return parts.pop()?.split(";").shift() || null; - } - return null; -}; - -const removeCookie = (name: string): void => { - document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; -}; - export const RobotIntegrationPage = ({ handleStart, robotPath = "robots", @@ -85,14 +72,12 @@ export const RobotIntegrationPage = ({ const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); - const params = useParams(); - // Extract robotId and integrationType from URL manually since there's no specific route param defined + // Extract robotId and integrationType from URL const pathSegments = location.pathname.split('/'); const robotsIndex = pathSegments.findIndex(segment => segment === 'robots' || segment === 'prebuilt-robots'); const integrateIndex = pathSegments.findIndex(segment => segment === 'integrate'); - // Extract robotId from URL (more reliable than global store) const robotIdFromUrl = robotsIndex !== -1 && robotsIndex + 1 < pathSegments.length ? pathSegments[robotsIndex + 1] : null; @@ -101,7 +86,6 @@ export const RobotIntegrationPage = ({ ? pathSegments[integrateIndex + 1] as "googleSheets" | "airtable" | "webhook" : preSelectedIntegrationType || null; - const [settings, setSettings] = useState({ spreadsheetId: "", spreadsheetName: "", @@ -114,15 +98,9 @@ export const RobotIntegrationPage = ({ integrationType: integrationType || "airtable", }); - const [spreadsheets, setSpreadsheets] = useState< - { id: string; name: string }[] - >([]); - const [airtableBases, setAirtableBases] = useState< - { id: string; name: string }[] - >([]); - const [airtableTables, setAirtableTables] = useState< - { id: string; name: string }[] - >([]); + const [spreadsheets, setSpreadsheets] = useState<{ id: string; name: string }[]>([]); + const [airtableBases, setAirtableBases] = useState<{ id: string; name: string }[]>([]); + const [airtableTables, setAirtableTables] = useState<{ id: string; name: string }[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [isLoading, setIsLoadingAction] = useState(false); @@ -139,10 +117,8 @@ export const RobotIntegrationPage = ({ const { recordingId: recordingIdFromStore, notify, setRerenderRobots, setRecordingId } = useGlobalInfoStore(); - // Use robotId from URL as primary source, fallback to global store const recordingId = robotIdFromUrl || recordingIdFromStore; - // Update global store if we extracted recordingId from URL useEffect(() => { if (robotIdFromUrl && robotIdFromUrl !== recordingIdFromStore) { setRecordingId(robotIdFromUrl); @@ -154,17 +130,28 @@ export const RobotIntegrationPage = ({ "googleSheets" | "airtable" | "webhook" | null >(integrationType); + // --- AUTHENTICATION --- + const authenticateWithGoogle = () => { + if (!recordingId) { + console.error("Cannot authenticate: recordingId is null"); + return; + } + const basePath = robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; + const redirectUrl = `${window.location.origin}${basePath}/${recordingId}/integrate/googleSheets`; + window.location.href = `${apiUrl}/auth/google?robotId=${recordingId}&redirectUrl=${encodeURIComponent(redirectUrl)}`; + }; + const authenticateWithAirtable = () => { if (!recordingId) { console.error("Cannot authenticate: recordingId is null"); return; } - const basePath = robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; const redirectUrl = `${window.location.origin}${basePath}/${recordingId}/integrate/airtable`; window.location.href = `${apiUrl}/auth/airtable?robotId=${recordingId}&redirectUrl=${encodeURIComponent(redirectUrl)}`; }; + // --- WEBHOOKS --- const validateWebhookData = ( url: string, events: string[], @@ -174,28 +161,23 @@ export const RobotIntegrationPage = ({ setUrlError("Please provide webhook URL"); return false; } - try { new URL(url); } catch { setUrlError("Please provide a valid URL"); return false; } - const existingWebhook = settings.webhooks?.find( (webhook) => webhook.url === url && webhook.id !== excludeId ); - if (existingWebhook) { setUrlError("This webhook URL is already in use"); return false; } - if (!events || events.length === 0) { setUrlError("Please select at least one event"); return false; } - setUrlError(null); return true; }; @@ -204,14 +186,9 @@ export const RobotIntegrationPage = ({ try { setLoading(true); if (!recordingId) return; - const response = await getWebhooks(recordingId); - if (response.ok && response.webhooks) { - setSettings((prev) => ({ - ...prev, - webhooks: response.webhooks, - })); + setSettings((prev) => ({ ...prev, webhooks: response.webhooks })); } setLoading(false); } catch (error: any) { @@ -222,37 +199,18 @@ export const RobotIntegrationPage = ({ const addWebhookSetting = async () => { if (!validateWebhookData(newWebhook.url, newWebhook.events)) { - if (!newWebhook.url) { - notify("error", "Please provide webhook URL"); - } else if (!newWebhook.events || newWebhook.events.length === 0) { - notify("error", "Please select at least one event"); - } + if (!newWebhook.url) notify("error", "Please provide webhook URL"); + else if (!newWebhook.events || newWebhook.events.length === 0) notify("error", "Please select at least one event"); return; } - if (!recordingId) return; - try { setLoading(true); - const webhookWithId = { - ...newWebhook, - id: uuid(), - }; - + const webhookWithId = { ...newWebhook, id: uuid() }; const response = await addWebhook(webhookWithId, recordingId); - if (response.ok) { - setSettings((prev) => ({ - ...prev, - webhooks: [...(prev.webhooks || []), webhookWithId], - })); - - setNewWebhook({ - id: "", - url: "", - events: ["run_completed"], - active: true, - }); + setSettings((prev) => ({ ...prev, webhooks: [...(prev.webhooks || []), webhookWithId] })); + setNewWebhook({ id: "", url: "", events: ["run_completed"], active: true }); setShowWebhookForm(false); notify("success", "Webhook added successfully"); } else { @@ -267,41 +225,19 @@ export const RobotIntegrationPage = ({ }; const updateWebhookSetting = async () => { - if ( - !validateWebhookData( - newWebhook.url, - newWebhook.events, - editingWebhook || undefined - ) - ) { - return; - } - + if (!validateWebhookData(newWebhook.url, newWebhook.events, editingWebhook || undefined)) return; if (!recordingId || !editingWebhook) return; - try { setLoading(true); - const response = await updateWebhook( - { ...newWebhook, id: editingWebhook }, - recordingId - ); - + const response = await updateWebhook({ ...newWebhook, id: editingWebhook }, recordingId); if (response.ok) { setSettings((prev) => ({ ...prev, webhooks: (prev.webhooks || []).map((webhook) => - webhook.id === editingWebhook - ? { ...newWebhook, id: editingWebhook } - : webhook + webhook.id === editingWebhook ? { ...newWebhook, id: editingWebhook } : webhook ), })); - - setNewWebhook({ - id: "", - url: "", - events: ["run_completed"], - active: true, - }); + setNewWebhook({ id: "", url: "", events: ["run_completed"], active: true }); setEditingWebhook(null); setShowWebhookForm(false); notify("success", "Webhook updated successfully"); @@ -318,26 +254,16 @@ export const RobotIntegrationPage = ({ const deleteWebhookSetting = async (webhookId: string) => { if (!recordingId) return; - try { setLoading(true); const response = await removeWebhook(webhookId, recordingId); - if (response.ok) { - setSettings((prev) => ({ - ...prev, - webhooks: (prev.webhooks || []).filter( - (webhook) => webhook.id !== webhookId - ), - })); - - // Refresh recording data + setSettings((prev) => ({ ...prev, webhooks: (prev.webhooks || []).filter((webhook) => webhook.id !== webhookId) })); if (recordingId) { const updatedRecording = await getStoredRecording(recordingId); setRecording(updatedRecording); } setRerenderRobots(true); - notify("success", "Webhook removed successfully"); } else { notify("error", response.error || "Failed to remove webhook"); @@ -352,14 +278,11 @@ export const RobotIntegrationPage = ({ const testWebhookSetting = async (webhookId: string) => { if (!recordingId) return; - const webhook = settings.webhooks?.find(w => w.id === webhookId); if (!webhook) return; - try { setLoading(true); const response = await testWebhook(webhook, recordingId); - if (response.ok) { notify("success", "Test webhook sent successfully"); } else { @@ -373,74 +296,119 @@ export const RobotIntegrationPage = ({ } }; + // --- DATA FETCHING & STATE MANAGEMENT --- useEffect(() => { setSelectedIntegrationType(integrationType); - setSettings(prev => ({ - ...prev, - integrationType: integrationType || "airtable" - })); + setSettings(prev => ({ ...prev, integrationType: integrationType || "airtable" })); }, [integrationType]); useEffect(() => { const fetchRecording = async () => { if (recordingId) { try { - const recording = await getStoredRecording(recordingId); - setRecording(recording); + const recordingData = await getStoredRecording(recordingId); + setRecording(recordingData); } catch (error) { console.error("Failed to fetch recording:", error); } } }; - fetchRecording(); if (selectedIntegrationType === "webhook") { fetchWebhooks(); } }, [recordingId, selectedIntegrationType]); + // --- NAVIGATION & ACTIONS --- const handleTabChange = (event: React.SyntheticEvent, newValue: string) => { - if (!recordingId) { - console.error("Cannot navigate: recordingId is null"); - return; - } - + if (!recordingId) return; const newIntegrationType = newValue as "googleSheets" | "airtable" | "webhook"; setSelectedIntegrationType(newIntegrationType); - const basePath = - robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; + const basePath = robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; navigate(`${basePath}/${recordingId}/integrate/${newValue}`); }; const handleCancel = () => { - const basePath = - robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; + const basePath = robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; navigate(basePath); }; - const handleSave = async () => { - setIsLoadingAction(true); + // --- GOOGLE SHEETS --- + const fetchSpreadsheetFiles = async () => { try { - await handleStart(settings); - const basePath = - robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; - navigate(basePath); - } catch (error) { - notify("error", "Failed to save integration settings"); - } finally { - setIsLoadingAction(false); + setLoading(true); + const response = await axios.get(`${apiUrl}/auth/gsheets/files?robotId=${recordingId}`, { withCredentials: true }); + setSpreadsheets(response.data); + setLoading(false); + } catch (error: any) { + setLoading(false); + console.error("Error fetching spreadsheet files:", error); + notify("error", t("integration_settings.google.errors.fetch_error", { + message: error.response?.data?.message || error.message, + })); } }; + const handleSpreadsheetSelect = (e: React.ChangeEvent) => { + const selectedSheet = spreadsheets.find((sheet) => sheet.id === e.target.value); + if (selectedSheet) { + setSettings({ ...settings, spreadsheetId: selectedSheet.id, spreadsheetName: selectedSheet.name }); + } + }; - // Fetch Airtable bases + const updateGoogleSheetId = async () => { + try { + setLoading(true); + await axios.post(`${apiUrl}/auth/gsheets/update`, { + spreadsheetId: settings.spreadsheetId, + spreadsheetName: settings.spreadsheetName, + robotId: recordingId, + }, { withCredentials: true }); + if (recordingId) { + const updatedRecording = await getStoredRecording(recordingId); + setRecording(updatedRecording); + } + setRerenderRobots(true); + notify("success", t("integration_settings.google.notifications.sheet_selected")); + setLoading(false); + } catch (error: any) { + setLoading(false); + console.error("Error updating Google Sheet ID:", error); + notify("error", t("integration_settings.google.errors.update_error", { + message: error.response?.data?.message || error.message, + })); + } + }; + + const removeGoogleSheetsIntegration = async () => { + try { + setLoading(true); + await axios.post(`${apiUrl}/auth/gsheets/remove`, { robotId: recordingId }, { withCredentials: true }); + setSpreadsheets([]); + setSettings({ ...settings, spreadsheetId: "", spreadsheetName: "" }); + if (recordingId) { + const updatedRecording = await getStoredRecording(recordingId); + setRecording(updatedRecording); + } + setRerenderRobots(true); + notify("success", t("integration_settings.google.notifications.integration_removed")); + setLoading(false); + } catch (error: any) { + setLoading(false); + console.error("Error removing Google Sheets integration:", error); + notify("error", t("integration_settings.google.errors.remove_error", { + message: error.response?.data?.message || error.message, + })); + } + }; + + // --- AIRTABLE --- const fetchAirtableBases = async () => { try { - const response = await axios.get( - `${apiUrl}/auth/airtable/bases?robotId=${recordingId}`, - { withCredentials: true } - ); + setLoading(true); + const response = await axios.get(`${apiUrl}/auth/airtable/bases?robotId=${recordingId}`, { withCredentials: true }); setAirtableBases(response.data); + setLoading(false); } catch (error: any) { setLoading(false); console.error("Error fetching Airtable bases:", error); @@ -452,11 +420,10 @@ export const RobotIntegrationPage = ({ const fetchAirtableTables = async (baseId: string, recordingId: string) => { try { - const response = await axios.get( - `${apiUrl}/auth/airtable/tables?robotId=${recordingId}&baseId=${baseId}`, - { withCredentials: true } - ); + setLoading(true); + const response = await axios.get(`${apiUrl}/auth/airtable/tables?robotId=${recordingId}&baseId=${baseId}`, { withCredentials: true }); setAirtableTables(response.data); + setLoading(false); } catch (error: any) { setLoading(false); console.error("Error fetching Airtable tables:", error); @@ -466,59 +433,36 @@ export const RobotIntegrationPage = ({ } }; - // Handle Airtable base selection const handleAirtableBaseSelect = async (e: React.ChangeEvent) => { const selectedBase = airtableBases.find((base) => base.id === e.target.value); - if (selectedBase) { - setSettings((prevSettings) => ({ - ...prevSettings, - airtableBaseId: selectedBase.id, - airtableBaseName: selectedBase.name, - })); - - if (recordingId) { - await fetchAirtableTables(selectedBase.id, recordingId); - } else { - console.error("Recording ID is null"); - } + setSettings((prevSettings) => ({ ...prevSettings, airtableBaseId: selectedBase.id, airtableBaseName: selectedBase.name })); + if (recordingId) await fetchAirtableTables(selectedBase.id, recordingId); } }; const handleAirtabletableSelect = (e: React.ChangeEvent) => { const selectedTable = airtableTables.find((table) => table.id === e.target.value); if (selectedTable) { - setSettings((prevSettings) => ({ - ...prevSettings, - airtableTableId: e.target.value, - airtableTableName: selectedTable?.name || "", - })); + setSettings((prevSettings) => ({ ...prevSettings, airtableTableId: e.target.value, airtableTableName: selectedTable?.name || "" })); } }; - // Update Airtable integration const updateAirtableBase = async () => { try { setLoading(true); - await axios.post( - `${apiUrl}/auth/airtable/update`, - { - baseId: settings.airtableBaseId, - baseName: settings.airtableBaseName, - robotId: recordingId, - tableName: settings.airtableTableName, - tableId: settings.airtableTableId, - }, - { withCredentials: true } - ); - - // Refresh recording data + await axios.post(`${apiUrl}/auth/airtable/update`, { + baseId: settings.airtableBaseId, + baseName: settings.airtableBaseName, + robotId: recordingId, + tableName: settings.airtableTableName, + tableId: settings.airtableTableId, + }, { withCredentials: true }); if (recordingId) { const updatedRecording = await getStoredRecording(recordingId); setRecording(updatedRecording); } setRerenderRobots(true); - notify("success", t("integration_settings.airtable.notifications.base_selected")); setLoading(false); } catch (error: any) { @@ -530,33 +474,18 @@ export const RobotIntegrationPage = ({ } }; - // Remove Airtable integration const removeAirtableIntegration = async () => { try { setLoading(true); - await axios.post( - `${apiUrl}/auth/airtable/remove`, - { robotId: recordingId }, - { withCredentials: true } - ); - + await axios.post(`${apiUrl}/auth/airtable/remove`, { robotId: recordingId }, { withCredentials: true }); setAirtableBases([]); setAirtableTables([]); - setSettings({ - ...settings, - airtableBaseId: "", - airtableBaseName: "", - airtableTableName: "", - airtableTableId: "", - }); - - // Refresh recording data + setSettings({ ...settings, airtableBaseId: "", airtableBaseName: "", airtableTableName: "", airtableTableId: "" }); if (recordingId) { const updatedRecording = await getStoredRecording(recordingId); setRecording(updatedRecording); } setRerenderRobots(true); - notify("success", t("integration_settings.airtable.notifications.integration_removed")); setLoading(false); } catch (error: any) { @@ -568,36 +497,79 @@ export const RobotIntegrationPage = ({ } }; + // --- RENDER METHODS --- + const renderGoogleSheetsIntegration = () => ( + <> + + {t("integration_settings.google.title")} + + {recording?.google_sheet_id ? ( + <> + + {t("integration_settings.google.alerts.success.title")} + {t("integration_settings.google.alerts.success.content", { sheetName: recording.google_sheet_name })} + + {t("integration_settings.google.alerts.success.here")} + + + + + ) : ( + <> + {!recording?.google_sheet_email ? ( + <> +

{t("integration_settings.google.descriptions.sync_info")}

+ + + ) : ( + <> + + {t("integration_settings.google.descriptions.authenticated_as", { email: recording.google_sheet_email })} + + {loading ? ( + + ) : error ? ( + {error} + ) : spreadsheets.length === 0 ? ( + + ) : ( + <> + + {spreadsheets.map((sheet) => ({sheet.name}))} + + + + )} + + )} + + )} + + ); + const renderAirtableIntegration = () => ( <> {t("integration_settings.airtable.title")} - {recording?.airtable_base_id ? ( <> {t("integration_settings.airtable.alerts.success.title")} - {t("integration_settings.airtable.alerts.success.content", { - baseName: recording.airtable_base_name, - tableName: recording.airtable_table_name, - })} - + {t("integration_settings.airtable.alerts.success.content", { baseName: recording.airtable_base_name, tableName: recording.airtable_table_name })} + {t("integration_settings.airtable.alerts.success.here")} - @@ -606,12 +578,7 @@ export const RobotIntegrationPage = ({ {!recording?.airtable_access_token ? ( <>

{t("integration_settings.airtable.descriptions.sync_info")}

- @@ -626,62 +593,22 @@ export const RobotIntegrationPage = ({ {error} ) : airtableBases.length === 0 ? ( - - ) : ( <> - - {airtableBases.map((base) => ( - - {base.name} - - ))} + + {airtableBases.map((base) => ({base.name}))} - - {airtableTables.map((table) => ( - - {table.name} - - ))} + + {airtableTables.map((table) => ({table.name}))} - @@ -694,136 +621,38 @@ export const RobotIntegrationPage = ({ ); const renderWebhookIntegration = () => ( - - - Webhook Integration - - - Configure webhooks to receive real-time notifications about robot events - - + + Webhook Integration + Configure webhooks to receive real-time notifications about robot events {!showWebhookForm && ( - + )} - {showWebhookForm && ( - - {editingWebhook ? "Edit Webhook" : "Add New Webhook"} - - - setNewWebhook((prev) => ({ ...prev, url: e.target.value })) - } - error={!!urlError} - helperText={urlError} - sx={{ mb: 2 }} - /> - - setNewWebhook((prev) => ({ - ...prev, - active: e.target.checked, - })) - } - /> - } - label="Active" - /> + {editingWebhook ? "Edit Webhook" : "Add New Webhook"} + setNewWebhook((prev) => ({ ...prev, url: e.target.value }))} error={!!urlError} helperText={urlError} sx={{ mb: 2 }}/> + setNewWebhook((prev) => ({ ...prev, active: e.target.checked }))}/>} label="Active"/> - - + + )} - {settings.webhooks && settings.webhooks.length > 0 && ( - - - URL - Status - Actions - - + URLStatusActions {settings.webhooks.map((webhook) => ( {webhook.url} + - - - - { - setNewWebhook(webhook); - setEditingWebhook(webhook.id); - setShowWebhookForm(true); - }} - size="small" - > - - - testWebhookSetting(webhook.id)} - size="small" - disabled={loading} - > - - - deleteWebhookSetting(webhook.id)} - size="small" - color="error" - > - - + { setNewWebhook(webhook); setEditingWebhook(webhook.id); setShowWebhookForm(true); }} size="small"> + testWebhookSetting(webhook.id)} size="small" disabled={loading}> + deleteWebhookSetting(webhook.id)} size="small" color="error"> ))} @@ -836,6 +665,8 @@ export const RobotIntegrationPage = ({ const getIntegrationTitle = () => { switch (selectedIntegrationType) { + case "googleSheets": + return "Google Sheets Integration"; case "airtable": return "Airtable Integration"; case "webhook": @@ -852,12 +683,7 @@ export const RobotIntegrationPage = ({ }; const resetWebhookForm = () => { - setNewWebhook({ - id: "", - url: "", - events: ["run_completed"], - active: true, - }); + setNewWebhook({ id: "", url: "", events: ["run_completed"], active: true }); setShowWebhookForm(false); setEditingWebhook(null); setUrlError(null); @@ -865,32 +691,20 @@ export const RobotIntegrationPage = ({ const toggleWebhookStatusSetting = async (webhookId: string) => { if (!recordingId) return; - try { const webhook = settings.webhooks?.find((w) => w.id === webhookId); if (!webhook) return; - const updatedWebhook = { ...webhook, active: !webhook.active }; - const response = await updateWebhook(updatedWebhook, recordingId); - if (response.ok) { - const updatedWebhooks = (settings.webhooks || []).map((w) => - w.id === webhookId ? updatedWebhook : w - ); + const updatedWebhooks = (settings.webhooks || []).map((w) => w.id === webhookId ? updatedWebhook : w); setSettings({ ...settings, webhooks: updatedWebhooks }); - - // Refresh recording data if (recordingId) { const updatedRecording = await getStoredRecording(recordingId); setRecording(updatedRecording); } setRerenderRobots(true); - - notify( - "success", - `Webhook ${updatedWebhook.active ? "enabled" : "disabled"}` - ); + notify("success", `Webhook ${updatedWebhook.active ? "enabled" : "disabled"}`); } else { notify("error", response.message || "Failed to update webhook"); } @@ -902,464 +716,163 @@ export const RobotIntegrationPage = ({ const formatEventName = (event: string) => { switch (event) { - case "run_completed": - return "Run finished"; - case "run_failed": - return "Run failed"; - default: - return event; + case "run_completed": return "Run finished"; + case "run_failed": return "Run failed"; + default: return event; } }; const formatLastCalled = (lastCalledAt?: string | null) => { - if (!lastCalledAt) { - return "Not called yet"; - } - + if (!lastCalledAt) return "Not called yet"; const date = new Date(lastCalledAt); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffMinutes = Math.floor(diffMs / (1000 * 60)); - - if (diffMinutes < 1) { - return "Just now"; - } else if (diffMinutes < 60) { - return `${diffMinutes} minute${diffMinutes === 1 ? "" : "s"} ago`; - } else if (diffHours < 24) { - return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`; - } else if (diffDays < 7) { - return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`; - } else { - return date.toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - }); - } + if (diffMinutes < 1) return "Just now"; + else if (diffMinutes < 60) return `${diffMinutes} minute${diffMinutes === 1 ? "" : "s"} ago`; + else if (diffHours < 24) return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`; + else if (diffDays < 7) return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`; + else return date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }); }; - // Show integration selection if no type is selected + // --- MAIN RENDER --- if (!selectedIntegrationType && !integrationType) { return ( - -
-
+ +
+
- - - - - - -
- + + +
-
); } const handleBack = () => { - if (!recordingId) { - console.error("Cannot navigate: recordingId is null"); - return; - } - + if (!recordingId) return; setSelectedIntegrationType(null); - setSettings({ ...settings, integrationType: "airtable" }); const basePath = robotPath === "prebuilt-robots" ? "/prebuilt-robots" : "/robots"; navigate(`${basePath}/${recordingId}/integrate`); }; return ( - -
+ +
+ {(selectedIntegrationType === "googleSheets" || integrationType === "googleSheets") && ( + <>{renderGoogleSheetsIntegration()} + )} + {(selectedIntegrationType === "airtable" || integrationType === "airtable") && ( <>{renderAirtableIntegration()} )} {(selectedIntegrationType === "webhook" || integrationType === "webhook") && ( - <> - - Integrate using Webhooks - - - {settings.webhooks && settings.webhooks.length > 0 && ( - -
- - - - Webhook URL - - - Call when - - - Last called - - - Status - - - Actions - - - - - {settings.webhooks.map((webhook) => ( - - {webhook.url} - - - {webhook.events.map((event) => ( - - ))} - - - - {formatLastCalled(webhook.lastCalledAt)} - - - toggleWebhookStatusSetting(webhook.id)} - size="small" - /> - - - - testWebhookSetting(webhook.id)} - disabled={loading || !webhook.active} - title="Test" - > - - - editWebhookSetting(webhook)} - disabled={loading} - title="Edit" - > - - - deleteWebhookSetting(webhook.id)} - disabled={loading} - title="Delete" - > - - - - + <> + Integrate using Webhooks + {settings.webhooks && settings.webhooks.length > 0 && ( + +
+ + + Webhook URL + Call when + Last called + Status + Actions - ))} - -
-
- )} - - {!showWebhookForm && ( - - - { - setNewWebhook({ ...newWebhook, url: e.target.value }); - if (urlError) setUrlError(null); - }} - error={!!urlError} - helperText={urlError} - required - aria-describedby="webhook-url-help" - /> - - setNewWebhook({ - ...newWebhook, - events: [e.target.value], - }) - } - sx={{ minWidth: "200px" }} - required - > - Run finished - Run failed - + + + {settings.webhooks.map((webhook) => ( + + {webhook.url} + {webhook.events.map((event) => ())} + {formatLastCalled(webhook.lastCalledAt)} + toggleWebhookStatusSetting(webhook.id)} size="small"/> + + testWebhookSetting(webhook.id)} disabled={loading || !webhook.active} title="Test"> + editWebhookSetting(webhook)} disabled={loading} title="Edit"> + deleteWebhookSetting(webhook.id)} disabled={loading} title="Delete"> + + + ))} + + + + )} + {!showWebhookForm && ( + + + { setNewWebhook({ ...newWebhook, url: e.target.value }); if (urlError) setUrlError(null); }} error={!!urlError} helperText={urlError} required aria-describedby="webhook-url-help"/> + setNewWebhook({ ...newWebhook, events: [e.target.value] })} sx={{ minWidth: "200px" }} required> + Run finished + Run failed + + + + Refer to the API documentation for examples and details. + + - - - - Refer to the{" "} - - API documentation - {" "} - for examples and details. - - - - - )} - - {showWebhookForm && ( - - - - {editingWebhook ? "Edit Webhook" : "Add New Webhook"} - - - { - setNewWebhook({ ...newWebhook, url: e.target.value }); - if (urlError) setUrlError(null); - }} - sx={{ marginBottom: "15px" }} - placeholder="https://your-api.com/webhook/endpoint" - required - error={!!urlError} - helperText={urlError} - /> - - - setNewWebhook({ - ...newWebhook, - events: - typeof e.target.value === "string" - ? [e.target.value] - : e.target.value, - }) - } - SelectProps={{ - multiple: true, - renderValue: (selected) => ( - - {(selected as string[]).map((value) => ( - - ))} - - ), - }} - sx={{ marginBottom: "20px" }} - required - > - Run finished - Run failed - - - - setNewWebhook({ - ...newWebhook, - active: e.target.checked, - }) - } - /> - } - label="Active" - sx={{ marginBottom: "10px" }} - /> - - - - - - - - )} - + )} + {showWebhookForm && ( + + + {editingWebhook ? "Edit Webhook" : "Add New Webhook"} + { setNewWebhook({ ...newWebhook, url: e.target.value }); if (urlError) setUrlError(null); }} sx={{ marginBottom: "15px" }} placeholder="https://your-api.com/webhook/endpoint" required error={!!urlError} helperText={urlError}/> + setNewWebhook({ ...newWebhook, events: typeof e.target.value === "string" ? [e.target.value] : e.target.value })} + SelectProps={{ multiple: true, renderValue: (selected) => ({(selected as string[]).map((value) => ())}),}} sx={{ marginBottom: "20px" }} required> + Run finished + Run failed + + setNewWebhook({ ...newWebhook, active: e.target.checked })}/>} label="Active" sx={{ marginBottom: "10px" }}/> + + + + + + + )} + )}