import { $fetch } from "@lib/api" import { authClient } from "@lib/auth" import { useAuth } from "@lib/auth-context" import { generateId } from "@lib/generate-id" import { ADD_MEMORY_SHORTCUT_URL, SEARCH_MEMORY_SHORTCUT_URL, } from "@repo/lib/constants" import { fetchConnectionsFeature } from "@repo/lib/queries" import { Button } from "@repo/ui/components/button" import { Dialog, DialogContent, DialogHeader, DialogPortal, DialogTitle, } from "@repo/ui/components/dialog" 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 { Check, Copy, Smartphone, Trash2 } from "lucide-react" import { motion } from "motion/react" import Image from "next/image" import { useEffect, useId, useState } from "react" import { toast } from "sonner" import type { z } from "zod" import { analytics } from "@/lib/analytics" import { useProject } from "@/stores" type Connection = z.infer 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 const ChromeIcon = ({ className }: { className?: string }) => ( Google Chrome Icon ) export function IntegrationsView() { const { org } = useAuth() const queryClient = useQueryClient() const { selectedProject } = useProject() const autumn = useCustomer() const [showApiKeyModal, setShowApiKeyModal] = useState(false) const [apiKey, setApiKey] = useState("") const [copied, setCopied] = useState(false) const [isProUser, setIsProUser] = useState(false) const [selectedShortcutType, setSelectedShortcutType] = useState< "add" | "search" | null >(null) const apiKeyId = useId() const handleUpgrade = async () => { try { await autumn.attach({ productId: "consumer_pro", successUrl: "https://app.supermemory.ai/", }) window.location.reload() } catch (error) { console.error(error) } } useEffect(() => { if (!autumn.isLoading) { setIsProUser( autumn.customer?.products.some( (product) => product.id === "consumer_pro", ) ?? false, ) } }, [autumn.isLoading, autumn.customer]) const { data: connectionsCheck } = fetchConnectionsFeature( autumn, !autumn.isLoading, ) const connectionsUsed = connectionsCheck?.balance ?? 0 const connectionsLimit = connectionsCheck?.included_usage ?? 0 const canAddConnection = connectionsUsed < connectionsLimit const { data: connections = [], isLoading: connectionsLoading, 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, }) useEffect(() => { if (connectionsError) { toast.error("Failed to load connections", { description: connectionsError instanceof Error ? connectionsError.message : "Unknown error", }) } }, [connectionsError]) 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, provider) => { analytics.connectionAdded(provider) analytics.connectionAuthStarted() 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", }) }, }) 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 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 createApiKeyMutation = useMutation({ mutationFn: async () => { const res = await authClient.apiKey.create({ metadata: { organizationId: org?.id, type: "ios-shortcut", }, name: `ios-${generateId().slice(0, 8)}`, prefix: `sm_${org?.id}_`, }) return res.key }, onSuccess: (apiKey) => { setApiKey(apiKey) setShowApiKeyModal(true) setCopied(false) handleCopyApiKey() }, onError: (error) => { toast.error("Failed to create API key", { description: error instanceof Error ? error.message : "Unknown error", }) }, }) const handleShortcutClick = (shortcutType: "add" | "search") => { setSelectedShortcutType(shortcutType) createApiKeyMutation.mutate() } const handleCopyApiKey = async () => { try { await navigator.clipboard.writeText(apiKey) setCopied(true) toast.success("API key copied to clipboard!") setTimeout(() => setCopied(false), 2000) } catch { toast.error("Failed to copy API key") } } const handleOpenShortcut = () => { if (!selectedShortcutType) { toast.error("No shortcut type selected") return } if (selectedShortcutType === "add") { window.open(ADD_MEMORY_SHORTCUT_URL, "_blank") } else if (selectedShortcutType === "search") { window.open(SEARCH_MEMORY_SHORTCUT_URL, "_blank") } } const handleDialogClose = (open: boolean) => { setShowApiKeyModal(open) if (!open) { // Reset state when dialog closes setSelectedShortcutType(null) setApiKey("") setCopied(false) } } return (
{/* iOS Shortcuts */}

Apple shortcuts

Add memories directly from iPhone, iPad or Mac.

{/* Chrome Extension */}

Chrome Extension

Save any webpage to supermemory

Import All your Twitter Bookmarks

Bring all your chatGPT memories to Supermemory

{/* Connections Section */}
Connection Link Icon

Connections

Connect your accounts to sync document.

{!isProUser && (

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.

)} {/* All Connections with Status */} {connectionsLoading ? (
{Object.keys(CONNECTORS).map((_, i) => ( ))}
) : (
{Object.entries(CONNECTORS).map(([provider, config], index) => { const Icon = config.icon const connection = connections.find( (conn) => conn.provider === provider, ) const isConnected = !!connection return (

{config.title}

{isConnected ? (
Connected
) : (
Disconnected
)}

{config.description}

{connection?.email && (

{connection.email}

)}
{isConnected ? ( ) : (
Disconnected
)}
) })}
)}

More integrations are coming soon! Have a suggestion? Share it with us on{" "} X .

{/* API Key Modal */} Setup{" "} {selectedShortcutType === "add" ? "Add Memory" : selectedShortcutType === "search" ? "Search Memory" : "iOS"}{" "} Shortcut
{/* API Key Section */}
{/* Steps */}

Follow these steps:

1

Click "Add to Shortcuts" below to open the shortcut

2

Paste your API key when prompted

3

Start using your shortcut!

) }