diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 574e2176..c2994942 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -573,11 +573,6 @@ router.post( import crypto from 'crypto'; -// Add these environment variables to your .env file -// AIRTABLE_CLIENT_ID=your_client_id -// AIRTABLE_CLIENT_SECRET=your_client_secret -// AIRTABLE_REDIRECT_URI=http://localhost:8080/auth/airtable/callback - // Airtable OAuth Routes router.get("/airtable", (req, res) => { const { robotId } = req.query; @@ -668,6 +663,7 @@ router.get("/airtable/callback", async (req, res) => { await robot.update({ airtable_access_token: tokens.access_token, airtable_refresh_token: tokens.refresh_token, + }); @@ -689,7 +685,7 @@ router.get("/airtable/callback", async (req, res) => { }); // Get Airtable bases -router.get("/airtable/bases", requireSignIn, async (req: AuthenticatedRequest, res) => { +router.get("/airtable/bases", async (req: AuthenticatedRequest, res) => { try { const { robotId } = req.query; if (!robotId) { @@ -697,7 +693,7 @@ router.get("/airtable/bases", requireSignIn, async (req: AuthenticatedRequest, r } const robot = await Robot.findOne({ - where: { "recording_meta.id": robotId }, + where: { "recording_meta.id": robotId.toString() }, raw: true, }); diff --git a/src/components/integration/IntegrationSettings.tsx b/src/components/integration/IntegrationSettings.tsx index 874b248d..1d0534d1 100644 --- a/src/components/integration/IntegrationSettings.tsx +++ b/src/components/integration/IntegrationSettings.tsx @@ -6,6 +6,7 @@ import { CircularProgress, Alert, AlertTitle, + Chip, } from "@mui/material"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; @@ -23,11 +24,12 @@ interface IntegrationProps { } export interface IntegrationSettings { - spreadsheetId: string; - spreadsheetName: string; - airtableBaseId: string; - airtableBaseName: string; + spreadsheetId?: string; + spreadsheetName?: string; + airtableBaseId?: string; + airtableBaseName?: string; data: string; + integrationType: "googleSheets" | "airtable"; } // Helper functions to replace js-cookie functionality @@ -50,123 +52,126 @@ export const IntegrationSettingsModal = ({ handleClose, }: IntegrationProps) => { const { t } = useTranslation(); + const [selectedIntegrationType, setSelectedIntegrationType] = useState< + "googleSheets" | "airtable" | null + >(null); const [settings, setSettings] = useState({ spreadsheetId: "", spreadsheetName: "", airtableBaseId: "", airtableBaseName: "", data: "", + integrationType: "googleSheets", }); - 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 [loading, setLoading] = useState(false); const [error, setError] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); const [recording, setRecording] = useState(null); - // Authenticate with Google const authenticateWithGoogle = () => { - const redirectUri = `${window.location.origin}/google/callback`; - window.location.href = `${apiUrl}/auth/google?robotId=${recordingId}&redirect_uri=${encodeURIComponent(redirectUri)}`; + window.location.href = `${apiUrl}/auth/google?robotId=${recordingId}`; }; - // Authenticate with Airtable const authenticateWithAirtable = () => { - const redirectUri = `${window.location.origin}/airtable/callback`; - window.location.href = `${apiUrl}/auth/airtable?robotId=${recordingId}&redirect_uri=${encodeURIComponent(redirectUri)}`; + window.location.href = `${apiUrl}/auth/airtable?robotId=${recordingId}`; }; - // Handle Google OAuth callback - const handleGoogleCallback = async () => { + const handleIntegrationType = (type: "googleSheets" | "airtable") => { + setSelectedIntegrationType(type); + setSettings({ + ...settings, + integrationType: type, + }); + }; + + const fetchAirtableTables = async (baseId: string) => { try { - const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get("code"); - - if (!code) { - setError(t("integration_settings.errors.no_auth_code")); - return; - } - const response = await axios.get( - `${apiUrl}/auth/google/callback?code=${code}&robotId=${recordingId}` + `${apiUrl}/auth/airtable/tables?baseId=${baseId}&robotId=${recordingId}`, + { + withCredentials: true, + } + ); + setAirtableTables(response.data); + } catch (error: any) { + console.error( + "Error fetching Airtable tables:", + error.response?.data?.message || error.message + ); + notify( + "error", + t("integration_settings.errors.fetch_error", { + message: error.response?.data?.message || error.message, + }) ); - - if (response.data.accessToken) { - notify("success", t("integration_settings.notifications.google_auth_success")); - await fetchSpreadsheetFiles(); - } - - // Clear URL parameters - window.history.replaceState({}, document.title, window.location.pathname); - } catch (error) { - setError(t("integration_settings.errors.google_auth_error")); } }; - // Handle Airtable OAuth callback - const handleAirtableCallback = async () => { - try { - const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get("code"); - - if (!code) { - setError(t("integration_settings.errors.no_auth_code")); - return; - } - - const response = await axios.get( - `${apiUrl}/auth/airtable/callback?code=${code}&robotId=${recordingId}` - ); - - if (response.data.accessToken) { - notify("success", t("integration_settings.notifications.airtable_auth_success")); - await fetchAirtableBases(); - } - - // Clear URL parameters - window.history.replaceState({}, document.title, window.location.pathname); - } catch (error) { - setError(t("integration_settings.errors.airtable_auth_error")); - } - }; - - // Fetch Google Sheets const fetchSpreadsheetFiles = async () => { try { - setLoading(true); - const response = await axios.get(`${apiUrl}/auth/gsheets/files?robotId=${recordingId}`, { - withCredentials: true, - }); + const response = await axios.get( + `${apiUrl}/auth/gsheets/files?robotId=${recordingId}`, + { + withCredentials: true, + } + ); setSpreadsheets(response.data); } catch (error: any) { - console.error("Error fetching spreadsheet files:", error.response?.data?.message || error.message); - notify("error", t("integration_settings.errors.fetch_error", { message: error.response?.data?.message || error.message })); - } finally { - setLoading(false); + console.error( + "Error fetching spreadsheet files:", + error.response?.data?.message || error.message + ); + notify( + "error", + t("integration_settings.errors.fetch_error", { + message: error.response?.data?.message || error.message, + }) + ); } }; - // Fetch Airtable Bases + console.log("recordingId", recordingId); + const fetchAirtableBases = async () => { try { - setLoading(true); - const response = await axios.get(`${apiUrl}/auth/airtable/bases?robotId=${recordingId}`, { - withCredentials: true, - }); + const response = await axios.get( + `${apiUrl}/auth/airtable/bases?robotId=${recordingId}`, + { + withCredentials: true, + } + ); + setAirtableBases(response.data); + + console.log("Airtable bases:", response.data); + } catch (error: any) { - console.error("Error fetching Airtable bases:", error.response?.data?.message || error.message); - notify("error", t("integration_settings.errors.fetch_error", { message: error.response?.data?.message || error.message })); - } finally { - setLoading(false); + console.error( + "Error fetching Airtable bases:", + error.response?.data?.message || error.message + ); + notify( + "error", + t("integration_settings.errors.fetch_error", { + message: error.response?.data?.message || error.message, + }) + ); } }; - // Handle Google Sheet selection const handleSpreadsheetSelect = (e: React.ChangeEvent) => { - const selectedSheet = spreadsheets.find((sheet) => sheet.id === e.target.value); + const selectedSheet = spreadsheets.find( + (sheet) => sheet.id === e.target.value + ); if (selectedSheet) { setSettings({ ...settings, @@ -176,9 +181,10 @@ export const IntegrationSettingsModal = ({ } }; - // Handle Airtable Base selection const handleAirtableBaseSelect = (e: React.ChangeEvent) => { - const selectedBase = airtableBases.find((base) => base.id === e.target.value); + const selectedBase = airtableBases.find( + (base) => base.id === e.target.value + ); if (selectedBase) { setSettings({ ...settings, @@ -188,10 +194,8 @@ export const IntegrationSettingsModal = ({ } }; - // Update Google Sheet ID const updateGoogleSheetId = async () => { try { - setLoading(true); const response = await axios.post( `${apiUrl}/auth/gsheets/update`, { @@ -201,19 +205,18 @@ export const IntegrationSettingsModal = ({ }, { withCredentials: true } ); - notify("success", t("integration_settings.notifications.sheet_selected")); + notify(`success`, t("integration_settings.notifications.sheet_selected")); console.log("Google Sheet ID updated:", response.data); } catch (error: any) { - console.error("Error updating Google Sheet ID:", error.response?.data?.message || error.message); - } finally { - setLoading(false); + console.error( + "Error updating Google Sheet ID:", + error.response?.data?.message || error.message + ); } }; - // Update Airtable Base ID const updateAirtableBaseId = async () => { try { - setLoading(true); const response = await axios.post( `${apiUrl}/auth/airtable/update`, { @@ -223,21 +226,25 @@ export const IntegrationSettingsModal = ({ }, { withCredentials: true } ); - notify("success", t("integration_settings.notifications.base_selected")); + notify(`success`, t("integration_settings.notifications.base_selected")); console.log("Airtable Base ID updated:", response.data); } catch (error: any) { - console.error("Error updating Airtable Base ID:", error.response?.data?.message || error.message); - } finally { - setLoading(false); + console.error( + "Error updating Airtable Base ID:", + error.response?.data?.message || error.message + ); } }; - // Remove Integration const removeIntegration = async () => { try { - setLoading(true); + const endpoint = + selectedIntegrationType === "googleSheets" + ? "/auth/gsheets/remove" + : "/auth/airtable/remove"; + await axios.post( - `${apiUrl}/auth/gsheets/remove`, + `${apiUrl}${endpoint}`, { robotId: recordingId }, { withCredentials: true } ); @@ -245,204 +252,305 @@ export const IntegrationSettingsModal = ({ setRecording(null); setSpreadsheets([]); setAirtableBases([]); + setSelectedIntegrationType(null); setSettings({ spreadsheetId: "", spreadsheetName: "", airtableBaseId: "", airtableBaseName: "", data: "", + integrationType: "googleSheets", }); - notify("success", t("integration_settings.notifications.integration_removed")); } catch (error: any) { - console.error("Error removing integration:", error.response?.data?.message || error.message); - } finally { - setLoading(false); + console.error( + "Error removing integration:", + error.response?.data?.message || error.message + ); } }; useEffect(() => { - const checkAuthCallback = () => { - const path = window.location.pathname; - const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get("code"); + const status = getCookie("robot_auth_status"); + const message = getCookie("robot_auth_message"); - if (code) { - if (path.includes("/google/callback")) { - handleGoogleCallback(); - } else if (path.includes("/airtable/callback")) { - handleAirtableCallback(); - } - } - }; + if (status === "success" && message) { + notify("success", message); + removeCookie("robot_auth_status"); + removeCookie("robot_auth_message"); + } - checkAuthCallback(); + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get("code"); + if (code) { + // Determine which authentication callback to handle + // You'll need to implement similar callback logic for Airtable + } - // Cleanup function - return () => { - window.history.replaceState({}, document.title, window.location.pathname); - }; - }, []); - - useEffect(() => { const fetchRecordingInfo = async () => { if (!recordingId) return; const recording = await getStoredRecording(recordingId); if (recording) { setRecording(recording); + // Determine integration type based on existing integration + if (recording.google_sheet_id) { + setSelectedIntegrationType("googleSheets"); + } else if (recording.airtable_base_id) { + setSelectedIntegrationType("airtable"); + } } }; fetchRecordingInfo(); }, [recordingId]); + // Initial integration type selection + if (!selectedIntegrationType) { + return ( + +
+ + {t("integration_settings.title_select_integration")} + +
+ + +
+
+
+ ); + } + return ( -
- {t("integration_settings.title")} +
+ + {selectedIntegrationType === "googleSheets" + ? t("integration_settings.title_google") + : t("integration_settings.title_airtable")} + - {recording && (recording.google_sheet_id || recording.airtable_base_id) ? ( + {recording && + (recording.google_sheet_id || recording.airtable_base_id ? ( + <> + + + {t("integration_settings.alerts.success.title")} + + {selectedIntegrationType === "googleSheets" ? ( + <> + {t("integration_settings.alerts.success.content", { + sheetName: recording.google_sheet_name, + })} + + {t("integration_settings.alerts.success.here")} + + + ) : ( + <> + {t("integration_settings.alerts.success.content", { + sheetName: recording.airtable_base_name, + })} + + {t("integration_settings.alerts.success.here")} + + + )} +
+ + {t("integration_settings.alerts.success.note")} + {" "} + {t("integration_settings.alerts.success.sync_limitation")} +
+ + + ) : null)} + + {!recording?.[ + selectedIntegrationType === "googleSheets" + ? "google_sheet_email" + : "airtable_email" + ] ? ( <> - {recording.google_sheet_id && ( - - {t("integration_settings.alerts.success.title")} - {t("integration_settings.alerts.success.content", { sheetName: recording.google_sheet_name })} - - {t("integration_settings.alerts.success.here")} - . -
- {t("integration_settings.alerts.success.note")} {t("integration_settings.alerts.success.sync_limitation")} -
- )} - - {recording.airtable_base_id && ( - - {t("integration_settings.alerts.success.title")} - {t("integration_settings.alerts.success.content", { sheetName: recording.airtable_base_name })} - - {t("integration_settings.alerts.success.here")} - . -
- {t("integration_settings.alerts.success.note")} {t("integration_settings.alerts.success.sync_limitation")} -
- )} - - ) : ( <> - {!recording?.google_sheet_email && !recording?.airtable_email ? ( + {recording[ + selectedIntegrationType === "googleSheets" + ? "google_sheet_email" + : "airtable_email" + ] && ( + + {t("integration_settings.descriptions.authenticated_as", { + email: + recording[ + selectedIntegrationType === "googleSheets" + ? "google_sheet_email" + : "airtable_email" + ], + })} + + )} + + {loading ? ( + + ) : error ? ( + {error} + ) : (selectedIntegrationType === "googleSheets" + ? spreadsheets + : airtableBases + ).length === 0 ? ( <> -

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

- - +
+ + +
) : ( <> - {(recording.google_sheet_email || recording.airtable_email) && ( - - {t("integration_settings.descriptions.authenticated_as", { - email: recording.google_sheet_email || recording.airtable_email, + + {(selectedIntegrationType === "googleSheets" + ? spreadsheets + : airtableBases + ).map((item) => ( + + {item.name} + + ))} + + + {(selectedIntegrationType === "googleSheets" + ? settings.spreadsheetId + : settings.airtableBaseId) && ( + + {t("integration_settings.fields.selected_sheet", { + name: + selectedIntegrationType === "googleSheets" + ? spreadsheets.find( + (s) => s.id === settings.spreadsheetId + )?.name + : airtableBases.find( + (b) => b.id === settings.airtableBaseId + )?.name, + id: + selectedIntegrationType === "googleSheets" + ? settings.spreadsheetId + : settings.airtableBaseId, })} )} - {loading ? ( - - ) : error ? ( - {error} - ) : ( - <> - {recording.google_sheet_email && ( - <> - - {spreadsheets.map((sheet) => ( - - {sheet.name} - - ))} - - - {settings.spreadsheetId && ( - - {t("integration_settings.fields.selected_sheet", { - name: spreadsheets.find((s) => s.id === settings.spreadsheetId)?.name, - id: settings.spreadsheetId, - })} - - )} - - - - )} - - {recording.airtable_email && ( - <> - - {airtableBases.map((base) => ( - - {base.name} - - ))} - - - {settings.airtableBaseId && ( - - {t("integration_settings.fields.selected_base", { - name: airtableBases.find((b) => b.id === settings.airtableBaseId)?.name, - id: settings.airtableBaseId, - })} - - )} - - - - )} - - )} + )} @@ -462,4 +570,4 @@ export const modalStyle = { height: "fit-content", display: "block", padding: "20px", -}; \ No newline at end of file +};