"use client" import { useState, useEffect } from "react" import { useSearchParams } from "next/navigation" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Loader2, PlugZap, CheckCircle, XCircle, RefreshCw, FileText, Download, AlertCircle } from "lucide-react" import { useAuth } from "@/contexts/auth-context" import { useTask } from "@/contexts/task-context" import { ProtectedRoute } from "@/components/protected-route" interface Connector { id: string name: string description: string icon: React.ReactNode status: "not_connected" | "connecting" | "connected" | "error" type: string connectionId?: string // Store the active connection ID for syncing access_token?: string // For connectors that use OAuth } interface ConnectorStatus { authenticated: boolean status: string connections: Array<{ connection_id: string name: string is_active: boolean created_at: string last_sync?: string }> } interface SyncResult { processed?: number; added?: number; skipped?: number; errors?: number; error?: string; message?: string; // For sync started messages isStarted?: boolean; // For sync started state } function ConnectorsPage() { const { user, isAuthenticated } = useAuth() const { addTask, refreshTasks } = useTask() const searchParams = useSearchParams() const [connectors, setConnectors] = useState([]) const [isConnecting, setIsConnecting] = useState(null) const [isSyncing, setIsSyncing] = useState(null) const [syncResults, setSyncResults] = useState<{[key: string]: SyncResult | null}>({}) const [syncProgress, setSyncProgress] = useState<{[key: string]: number | null}>({}) const [maxFiles, setMaxFiles] = useState(10) const [isLoading, setIsLoading] = useState(true) // Function definitions first const checkConnectorStatuses = async () => { // Initialize connectors list setConnectors([ { id: "google_drive", name: "Google Drive", description: "Connect your Google Drive to automatically sync documents", icon:
G
, status: "not_connected", type: "google_drive" }, ]) try { // Check status for each connector type const connectorTypes = ["google_drive"] for (const connectorType of connectorTypes) { const response = await fetch(`/api/connectors/${connectorType}/status`) if (response.ok) { const data = await response.json() const connections = data.connections || [] const activeConnection = connections.find((conn: any) => conn.is_active) const isConnected = activeConnection !== undefined setConnectors(prev => prev.map(c => c.type === connectorType ? { ...c, status: isConnected ? "connected" : "not_connected", connectionId: activeConnection?.connection_id } : c )) } } } catch (error) { console.error('Failed to check connector statuses:', error) } finally { setIsLoading(false) } } const refreshConnectorStatus = async (connector: Connector) => { try { const response = await fetch(`/api/connectors/${connector.type}/status`) if (response.ok) { const data = await response.json() const connections = data.connections || [] const activeConnection = connections.find((conn: any) => conn.is_active) const isConnected = activeConnection !== undefined setConnectors(prev => prev.map(c => c.id === connector.id ? { ...c, status: isConnected ? "connected" : "not_connected", connectionId: activeConnection?.connection_id } : c )) } } catch (error) { console.error(`Failed to refresh connector status for ${connector.name}:`, error) } } const handleConnect = async (connector: Connector) => { setIsConnecting(connector.id) setConnectors(prev => prev.map(c => c.id === connector.id ? { ...c, status: "connecting" } : c )) try { // Use the shared auth callback URL, not a separate connectors callback const redirectUri = `${window.location.origin}/auth/callback` const response = await fetch('/api/auth/init', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ provider: connector.type.replace('_drive', ''), // "google_drive" -> "google" purpose: "data_source", name: `${connector.name} Connection`, redirect_uri: redirectUri }), }) const result = await response.json() if (response.ok) { // Store connector ID for callback localStorage.setItem('connecting_connector_id', result.connection_id) localStorage.setItem('connecting_connector_type', connector.type) // Handle client-side OAuth with Google's library if (result.oauth_config) { // Use the redirect URI provided by the backend const authUrl = `${result.oauth_config.authorization_endpoint}?` + `client_id=${result.oauth_config.client_id}&` + `response_type=code&` + `scope=${result.oauth_config.scopes.join(' ')}&` + `redirect_uri=${encodeURIComponent(result.oauth_config.redirect_uri)}&` + `access_type=offline&` + `prompt=consent&` + `state=${result.connection_id}` window.location.href = authUrl } } else { throw new Error(result.error || 'Failed to initialize OAuth') } } catch (error) { console.error('OAuth initialization failed:', error) setConnectors(prev => prev.map(c => c.id === connector.id ? { ...c, status: "error" } : c )) } finally { setIsConnecting(null) } } const handleSync = async (connector: Connector) => { if (!connector.connectionId) { console.error('No connection ID available for connector') return } setIsSyncing(connector.id) setSyncProgress(prev => ({ ...prev, [connector.id]: null })) // Clear any existing progress setSyncResults(prev => ({ ...prev, [connector.id]: null })) try { const response = await fetch(`/api/connectors/${connector.type}/sync`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ max_files: maxFiles }), }) const result = await response.json() if (response.status === 201 && result.task_id) { // Task-based sync, use centralized tracking addTask(result.task_id) console.log(`Sync task ${result.task_id} added to central tracking for connector ${connector.id}`) // Immediately refresh task notifications to show the new task await refreshTasks() // Show sync started message setSyncResults(prev => ({ ...prev, [connector.id]: { message: "Check task notification panel for progress", isStarted: true } })) setIsSyncing(null) } else if (response.ok) { // Direct sync result - still show "sync started" message setSyncResults(prev => ({ ...prev, [connector.id]: { message: "Check task notification panel for progress", isStarted: true } })) setIsSyncing(null) } else { throw new Error(result.error || 'Sync failed') } } catch (error) { console.error('Sync failed:', error) setSyncResults(prev => ({ ...prev, [connector.id]: { error: error instanceof Error ? error.message : 'Sync failed' } })) setIsSyncing(null) } } const handleDisconnect = async (connector: Connector) => { // This would call a disconnect endpoint when implemented setConnectors(prev => prev.map(c => c.id === connector.id ? { ...c, status: "not_connected", connectionId: undefined } : c )) setSyncResults(prev => ({ ...prev, [connector.id]: null })) } const getStatusIcon = (status: Connector['status']) => { switch (status) { case "connected": return case "connecting": return case "error": return default: return } } const getStatusBadge = (status: Connector['status']) => { switch (status) { case "connected": return Connected case "connecting": return Connecting... case "error": return Error default: return Not Connected } } // Check connector status on mount and when returning from OAuth useEffect(() => { if (isAuthenticated) { checkConnectorStatuses() } // If we just returned from OAuth, clear the URL parameter if (searchParams.get('oauth_success') === 'true') { // Clear the URL parameter without causing a page reload const url = new URL(window.location.href) url.searchParams.delete('oauth_success') window.history.replaceState({}, '', url.toString()) } }, [searchParams, isAuthenticated]) return (

Connectors

Connect external services to automatically sync and index your documents

{/* Sync Settings */} Sync Settings Configure how many files to sync when manually triggering a sync
setMaxFiles(parseInt(e.target.value) || 10)} className="w-24" min="1" max="100" /> (Leave blank or set to 0 for unlimited)
{/* Connectors Grid */}
{connectors.map((connector) => (
{connector.icon}
{connector.name}
{getStatusIcon(connector.status)} {getStatusBadge(connector.status)}
{connector.description}
{connector.status === "not_connected" && ( )} {connector.status === "connected" && ( <> )} {connector.status === "error" && ( )}
{/* Sync Results */} {syncResults[connector.id] && (
{syncResults[connector.id]?.isStarted && (
Task initiated:
{syncResults[connector.id]?.message}
)} {syncResults[connector.id]?.error && (
Sync Failed
{syncResults[connector.id]?.error}
)}
)}
))}
{/* Coming Soon Section */} Coming Soon Additional connectors are in development
D
Dropbox
File storage
O
OneDrive
Microsoft cloud storage
B
Box
Enterprise file sharing
) } export default function ProtectedConnectorsPage() { return ( ) }