import { useState } from "react"; import { json } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { requireUserId, requireWorkpace } from "~/services/session.server"; import { getIntegrationDefinitions } from "~/services/integrationDefinition.server"; import { getIntegrationAccounts } from "~/services/integrationAccount.server"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "~/components/ui/card"; import { Button } from "~/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/ui/dialog"; import { Input } from "~/components/ui/input"; import { FormButtons } from "~/components/ui/FormButtons"; import { Plus, Search } from "lucide-react"; // Loader to fetch integration definitions and existing accounts export async function loader({ request }: LoaderFunctionArgs) { const userId = await requireUserId(request); const workspace = await requireWorkpace(request); const [integrationDefinitions, integrationAccounts] = await Promise.all([ getIntegrationDefinitions(workspace.id), getIntegrationAccounts(userId), ]); return json({ integrationDefinitions, integrationAccounts, userId, }); } export default function Integrations() { const { integrationDefinitions, integrationAccounts, userId } = useLoaderData(); const [selectedIntegration, setSelectedIntegration] = useState(null); const [apiKey, setApiKey] = useState(""); const [isLoading, setIsLoading] = useState(false); const [isConnecting, setIsConnecting] = useState(false); // Check if user has an active account for an integration const hasActiveAccount = (integrationDefinitionId: string) => { return integrationAccounts.some( (account) => account.integrationDefinitionId === integrationDefinitionId && account.isActive, ); }; // Handle connection with API key const handleApiKeyConnect = async () => { if (!selectedIntegration || !apiKey.trim()) return; setIsLoading(true); try { const response = await fetch("/api/v1/integration_account", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ integrationDefinitionId: selectedIntegration.id, apiKey, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to connect integration"); } // Refresh the page to show the new integration account window.location.reload(); } catch (error) { console.error("Error connecting integration:", error); // Handle error (could add error state and display message) } finally { setIsLoading(false); } }; // Handle OAuth connection const handleOAuthConnect = async () => { if (!selectedIntegration) return; setIsConnecting(true); try { const response = await fetch("/api/v1/oauth", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ integrationDefinitionId: selectedIntegration.id, userId, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to start OAuth flow"); } const { url } = await response.json(); // Redirect to OAuth authorization URL window.location.href = url; } catch (error) { console.error("Error starting OAuth flow:", error); // Handle error } finally { setIsConnecting(false); } }; return (

Connect your tools and services

{/* Integration cards grid */}
{integrationDefinitions.map((integration) => { const isConnected = hasActiveAccount(integration.id); return ( { if (open) { setSelectedIntegration(integration); setApiKey(""); } else { setSelectedIntegration(null); } }} >
{integration.icon ? ( {integration.name} ) : (
)}
{integration.name} {integration.description || "Connect to " + integration.name}
{isConnected ? ( Connected ) : ( Not connected )}
Connect to {integration.name} {integration.description || `Connect your ${integration.name} account to enable integration.`} {/* API Key Authentication */} {(() => { const specData = typeof integration.spec === "string" ? JSON.parse(integration.spec) : integration.spec; return specData?.auth?.api_key; })() && (
setApiKey(e.target.value)} /> {(() => { const specData = typeof integration.spec === "string" ? JSON.parse(integration.spec) : integration.spec; return specData?.auth?.api_key?.description; })() && (

{(() => { const specData = typeof integration.spec === "string" ? JSON.parse(integration.spec) : integration.spec; return specData?.auth?.api_key?.description; })()}

)}
{isLoading ? "Connecting..." : "Connect"} } >
)} {/* OAuth Authentication */} {(() => { const specData = typeof integration.spec === "string" ? JSON.parse(integration.spec) : integration.spec; return specData?.auth?.oauth2; })() && (
)} {/* No authentication method found */} {(() => { const specData = typeof integration.spec === "string" ? JSON.parse(integration.spec) : integration.spec; return !specData?.auth?.api_key && !specData?.auth?.oauth2; })() && (
This integration doesn't specify an authentication method.
)}
By connecting, you agree to the {integration.name} terms of service.
); })}
{/* Empty state */} {integrationDefinitions.length === 0 && (

No integrations found

)}
); }