"use client" import { $fetch } from "@lib/api" import { Button } from "@repo/ui/components/button" import { Skeleton } from "@repo/ui/components/skeleton" 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 { Trash2 } from "lucide-react" import { AnimatePresence, motion } from "motion/react" import { useEffect, useState } from "react" import { toast } from "sonner" import type { z } from "zod" import { analytics } from "@/lib/analytics" import { useProject } from "@/stores" // Define types type Connection = z.infer // Connector configurations const CONNECTORS = { "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 type ConnectorProvider = keyof typeof CONNECTORS export function ConnectionsTabContent() { const queryClient = useQueryClient() const { selectedProject } = useProject() const autumn = useCustomer() const [isProUser, setIsProUser] = useState(false) const handleUpgrade = async () => { try { await autumn.attach({ productId: "consumer_pro", successUrl: "https://app.supermemory.ai/", }) window.location.reload() } catch (error) { console.error(error) } } // Set pro user status when autumn data loads useEffect(() => { if (!autumn.isLoading) { setIsProUser( autumn.customer?.products.some( (product) => product.id === "consumer_pro", ) ?? false, ) } }, [autumn.isLoading, autumn.customer]) // Get connections data directly from autumn customer const connectionsFeature = autumn.customer?.features?.connections const connectionsUsed = connectionsFeature?.usage ?? 0 const connectionsLimit = connectionsFeature?.included_usage ?? 0 const canAddConnection = connectionsUsed < connectionsLimit // Fetch connections const { data: connections = [], isLoading, error, } = 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, }) // Show error toast if connections fail to load useEffect(() => { if (error) { toast.error("Failed to load connections", { description: error instanceof Error ? error.message : "Unknown error", }) } }, [error]) // Add connection mutation const addConnectionMutation = useMutation({ mutationFn: async (provider: ConnectorProvider) => { // Check if user can add connections 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, provider) => { analytics.connectionAdded(provider) analytics.connectionAuthStarted() autumn.track({ featureId: "connections", value: 1, }) if (data?.authLink) { window.location.href = data.authLink } }, onError: (error, provider) => { analytics.connectionAuthFailed() toast.error(`Failed to connect ${provider}`, { description: error instanceof Error ? error.message : "Unknown error", }) }, }) // 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", }) }, }) const getProviderIcon = (provider: string) => { const connector = CONNECTORS[provider as ConnectorProvider] if (connector) { const Icon = connector.icon return } return 📎 } return (

Connect your favorite services to import documents

{isProUser && !autumn.isLoading && (

{connectionsUsed} of {connectionsLimit} connections used

)} {!isProUser && !autumn.isLoading && (

Connections require a Pro subscription

)}
{/* Show upgrade prompt for free users */} {!autumn.isLoading && !isProUser && (

🔌 Connections are a Pro feature

Connect Google Drive, Notion, OneDrive and more to automatically sync your documents.

)} {isLoading ? (
{[...Array(2)].map((_, i) => ( ))}
) : connections.length === 0 ? (

No connections yet

Choose a service below to connect

) : ( {connections.map((connection, index) => (
{getProviderIcon(connection.provider)}

{connection.provider.replace("-", " ")}

{connection.email && (

{connection.email}

)}
))}
)} {/* Available Connections Section */}

Available Connections

{Object.entries(CONNECTORS).map(([provider, config], index) => { const Icon = config.icon return ( ) })}
) }