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); 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!

); }