"use client" import { $fetch } from "@lib/api" import { fetchConnectionsFeature } from "@repo/lib/queries" import type { ConnectionResponseSchema } from "@repo/validation/api" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { GoogleDrive, Notion, OneDrive } from "@ui/assets/icons" import { useCustomer } from "autumn-js/react" import { Check, Loader, Trash2, Zap } from "lucide-react" import { useEffect, useState } from "react" import { toast } from "sonner" import type { z } from "zod" import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" type Connection = z.infer type ConnectorProvider = "google-drive" | "notion" | "onedrive" const CONNECTORS: Record< ConnectorProvider, { title: string description: string icon: React.ComponentType<{ className?: string }> } > = { "google-drive": { title: "Google Drive", description: "Connect your Google docs, sheets and slides", icon: GoogleDrive, }, notion: { title: "Notion", description: "Import your Notion pages and databases", icon: Notion, }, onedrive: { title: "OneDrive", description: "Access your Microsoft Office documents", icon: OneDrive, }, } as const interface ConnectContentProps { selectedProject: string } export function ConnectContent({ selectedProject }: ConnectContentProps) { const queryClient = useQueryClient() const autumn = useCustomer() const [isProUser, setIsProUser] = useState(false) const [connectingProvider, setConnectingProvider] = useState(null) const [isUpgrading, setIsUpgrading] = useState(false) // Check Pro status useEffect(() => { if (!autumn.isLoading) { setIsProUser( autumn.customer?.products.some( (product) => product.id === "consumer_pro", ) ?? false, ) } }, [autumn.isLoading, autumn.customer]) const handleUpgrade = async () => { setIsUpgrading(true) try { await autumn.attach({ productId: "consumer_pro", successUrl: window.location.href, }) } catch (error) { console.error("Upgrade error:", error) toast.error("Failed to start upgrade process") setIsUpgrading(false) } } // Check connections feature limits const { data: connectionsCheck } = fetchConnectionsFeature( autumn, !autumn.isLoading, ) const connectionsUsed = connectionsCheck?.balance ?? 0 const connectionsLimit = connectionsCheck?.included_usage ?? 0 const canAddConnection = connectionsUsed < connectionsLimit // Fetch connections const { data: connections = [], 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, refetchIntervalInBackground: true, }) // Handle connection errors useEffect(() => { if (connectionsError) { toast.error("Failed to load connections", { description: connectionsError instanceof Error ? connectionsError.message : "Unknown error", }) } }, [connectionsError]) // Connect mutation const addConnectionMutation = useMutation({ mutationFn: async (provider: ConnectorProvider) => { if (!canAddConnection && !isProUser) { throw new Error( "Free plan doesn't include connections. Upgrade to Pro for unlimited connections.", ) } const response = await $fetch("@post/connections/:provider", { params: { provider }, body: { redirectUrl: window.location.href, containerTags: [selectedProject], }, }) // biome-ignore lint/style/noNonNullAssertion: its fine if ("data" in response && !("error" in response.data!)) { return response.data } throw new Error(response.error?.message || "Failed to connect") }, onSuccess: (data) => { if (data?.authLink) { window.location.href = data.authLink } }, onError: (error) => { setConnectingProvider(null) toast.error("Failed to connect", { description: error instanceof Error ? error.message : "Unknown error", }) }, }) // Disconnect mutation const deleteConnectionMutation = useMutation({ mutationFn: async (connectionId: string) => { await $fetch(`@delete/connections/${connectionId}`) }, onSuccess: () => { toast.success( "Connection removal has started. supermemory will permanently delete all documents related to the connection 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", }) }, }) const handleConnect = (provider: ConnectorProvider) => { setConnectingProvider(provider) addConnectionMutation.mutate(provider) } const handleDisconnect = (connectionId: string) => { deleteConnectionMutation.mutate(connectionId) } const hasConnections = connections.length > 0 // Helper function to format connection subtext safely const getConnectionSubtext = (connection: Connection): string => { if (connection.email) { return connection.email } return "Connected" } return (

Supermemory Connections

PRO
{/* Connector section - conditional layout based on hasConnections */} {hasConnections ? (
{Object.entries(CONNECTORS).map(([provider, config]) => { const Icon = config.icon const isConnecting = connectingProvider === provider || (addConnectionMutation.isPending && addConnectionMutation.variables === provider) return ( ) })}
) : (
{Object.entries(CONNECTORS).map(([provider, config]) => { const Icon = config.icon const connection = connections.find( (conn) => conn.provider === provider, ) const isConnected = !!connection const isConnecting = connectingProvider === provider || (addConnectionMutation.isPending && addConnectionMutation.variables === provider) return (

{config.title}

{isConnected && ( {connection.metadata?.syncInProgress ? "Syncing..." : "Connected"} )}

{config.description}

{isConnected ? ( ) : ( )}
) })}
)} {/* Connected list panel - only when hasConnections */} {hasConnections && (

Connected to Supermemory

{connectionsLimit > 0 && (

{connections.length}/{connectionsLimit} connections used

)}
{connections.map((connection) => { const config = CONNECTORS[connection.provider as ConnectorProvider] if (!config) return null const Icon = config.icon const subtext = getConnectionSubtext(connection) return (

{config.title}

{subtext}

) })}
)} {/* Empty state panel - only when !hasConnections */} {!hasConnections && (
{!isProUser ? ( <>

{isUpgrading || autumn.isLoading ? ( Upgrading... ) : ( <> {" "} to get
Supermemory Connections )}

Unlimited memories
10 connections
Advanced search
Priority support
) : (

No connections yet

Choose a service above to import your knowledge

)}
)}
) }