"use client" import { dmSans125ClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { $fetch } from "@lib/api" import { fetchSubscriptionStatus } from "@lib/queries" import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons" import { useCustomer } from "autumn-js/react" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Check, Plus, Trash2, Zap } from "lucide-react" import { useEffect, useState } from "react" import { toast } from "sonner" import type { ConnectionResponseSchema } from "@repo/validation/api" import type { z } from "zod" import { analytics } from "@/lib/analytics" import { ConnectAIModal } from "@/components/connect-ai-modal" import { AddDocumentModal } from "@/components/new/add-document" import { DEFAULT_PROJECT_ID } from "@repo/lib/constants" import type { Project } from "@repo/lib/types" type Connection = z.infer const CONNECTORS = { "google-drive": { title: "Google Drive", description: "Connect your Google Docs, Sheets, and Slides", icon: GoogleDrive, documentLabel: "documents", }, notion: { title: "Notion", description: "Import your Notion pages and databases", icon: Notion, documentLabel: "pages", }, onedrive: { title: "OneDrive", description: "Access your Microsoft Office documents", icon: OneDrive, documentLabel: "documents", }, } as const type ConnectorProvider = keyof typeof CONNECTORS function SectionTitle({ children, badge, }: { children: React.ReactNode badge?: React.ReactNode }) { return (

{children}

{badge}
) } function ProBadge() { return ( PRO ) } function ConnectionsCard({ children, className, }: { children: React.ReactNode className?: string }) { return (
{children}
) } function PillButton({ children, onClick, disabled, className, }: { children: React.ReactNode onClick?: () => void disabled?: boolean className?: string }) { return ( ) } function ConnectionStatusBadge({ connected }: { connected: boolean }) { return (
{connected ? "Connected" : "Disconnected"}
) } function ConnectionRow({ connection, onDelete, isDeleting, disabled, projects, }: { connection: Connection onDelete: () => void isDeleting: boolean disabled?: boolean projects: Project[] }) { const config = CONNECTORS[connection.provider as ConnectorProvider] if (!config) return null const Icon = config.icon // Check if connection is active: if expiresAt exists and is in the future, or if no expiresAt const isConnected = !connection.expiresAt || new Date(connection.expiresAt) > new Date() // Format relative time const formatRelativeTime = (date: string | null | undefined) => { if (!date) return "Never" const d = new Date(date) const now = new Date() const diffMs = now.getTime() - d.getTime() const diffHours = Math.floor(diffMs / (1000 * 60 * 60)) const diffDays = Math.floor(diffHours / 24) if (diffHours < 1) return "Just now" if (diffHours < 24) return `${diffHours}h ago` if (diffDays === 1) return "Yesterday" if (diffDays < 7) return `${diffDays} days ago` return d.toLocaleDateString() } const getProjectDisplayName = (containerTag: string): string => { if (containerTag === DEFAULT_PROJECT_ID) return "Default Project" const found = projects.find((p) => p.containerTag === containerTag) if (found) return found.name return containerTag.replace(/^sm_project_/, "") // if cached project is not found, remove the prefix } const documentCount = (connection.metadata?.documentCount as number) ?? 0 const containerTags = ( connection as Connection & { containerTags?: string[] } ).containerTags const projectName = containerTags && containerTags.length > 0 && containerTags[0] ? getProjectDisplayName(containerTags[0]) : null return (
{/* Main row */}
{config.title}
{connection.email || "Unknown"}
{/* Meta row */}
{projectName && ( <> Project: {projectName}
)} Added: {formatRelativeTime(connection.createdAt)}
{documentCount} {config.documentLabel} connected
) } function UpgradeOverlay({ onUpgrade }: { onUpgrade: () => void }) { return (

{" "} to get Supermemory Connections

) } function FeatureItem({ text }: { text: string }) { return (
{text}
) } export default function ConnectionsMCP() { const queryClient = useQueryClient() const autumn = useCustomer() const [isAddDocumentOpen, setIsAddDocumentOpen] = useState(false) const [mcpModalOpen, setMcpModalOpen] = useState(false) const projects = (queryClient.getQueryData(["projects"]) || []) as Project[] // Billing data const { data: status = { consumer_pro: { allowed: false, status: null }, }, isLoading: isCheckingStatus, } = fetchSubscriptionStatus(autumn, !autumn.isLoading) const hasProProduct = status.consumer_pro?.status !== null // Get connections data directly from autumn customer const connectionsFeature = autumn.customer?.features?.connections const connectionsUsed = connectionsFeature?.usage ?? 0 const connectionsLimit = connectionsFeature?.included_usage ?? 10 const canAddConnection = connectionsUsed < connectionsLimit // Fetch connections const { data: connections = [], isLoading: isLoadingConnections, error: connectionsError, } = useQuery({ queryKey: ["connections"], queryFn: async () => { const response = await $fetch("@post/connections/list", { body: { containerTags: [], }, }) if (response.error) { throw new Error(response.error?.message || "Failed to load connections") } return response.data as Connection[] }, staleTime: 30 * 1000, refetchInterval: 60 * 1000, enabled: hasProProduct, }) useEffect(() => { if (connectionsError) { toast.error("Failed to load connections", { description: connectionsError instanceof Error ? connectionsError.message : "Unknown error", }) } }, [connectionsError]) // Delete connection mutation const deleteConnectionMutation = useMutation({ mutationFn: async (connectionId: string) => { await $fetch(`@delete/connections/${connectionId}`) }, onSuccess: () => { analytics.connectionDeleted() toast.success( "Connection removal has started. Supermemory will permanently delete the documents in the next few minutes.", ) queryClient.invalidateQueries({ queryKey: ["connections"] }) }, onError: (error) => { toast.error("Failed to remove connection", { description: error instanceof Error ? error.message : "Unknown error", }) }, }) // Upgrade handler const handleUpgrade = async () => { try { await autumn.attach({ productId: "consumer_pro", successUrl: "https://app.supermemory.ai/new/settings#connections", }) window.location.reload() } catch (error) { console.error(error) } } const isLoading = autumn.isLoading || isCheckingStatus return (
{/* Supermemory Connections Section */}
}> Supermemory Connections {/* Blur overlay for free users */} {!hasProProduct && !isLoading && ( <>
)}
Connected to Supermemory {connections.length}/{connectionsLimit} connections used
{isLoadingConnections ? (
) : connections.length > 0 ? ( connections.map((connection) => ( deleteConnectionMutation.mutate(connection.id) } isDeleting={deleteConnectionMutation.isPending} disabled={!hasProProduct} projects={projects} /> )) ) : (

No connections yet

Connect a service below to import your knowledge

)}
setIsAddDocumentOpen(true)} disabled={!hasProProduct || !canAddConnection} > Connect knowledge bases
{/* Supermemory MCP Section */}
Supermemory MCP

Connect your AI to create and use your memories via MCP.{" "} Learn more

setMcpModalOpen(true)}> Connect your AI to Supermemory
{/* Add Document Modal */} setIsAddDocumentOpen(false)} defaultTab="connect" />
) }