import { useEffect, useState } from "react"; import { useNavigate, useRouteLoaderData } from "@remix-run/react"; import { Button } from "../ui/button"; import { Card } from "../ui/card"; import { Dialog, DialogContent, DialogDescription, DialogTitle } from "../ui/dialog"; import { CSVUploadModal } from "./CSVUploadModal"; import { MarkdownUploadModal } from "./MarkdownUploadModal"; import { motion } from "framer-motion"; import { AlertCircle, BookIcon, CheckCircle, Clipboard, ClipboardCheckIcon, FileUpIcon, X, } from "lucide-react"; import { toast } from "sonner"; import { type IntegrationConfig, getIntegrations } from "~/config/integrations"; import { getChromeExtensionId } from "~/config/util"; import { cn } from "~/lib/utils"; import { loader } from "~/root"; function IntegrationButton({ integration, onClick, }: { integration: IntegrationConfig; onClick: () => void; }) { return (
{integration.icon && (
)}

{integration.name}

{integration.description}

); } function LoadingIntegration({ integration, onCancel, }: { integration: IntegrationConfig; onCancel: () => void; }) { const [extensionPresent, setExtensionPresent] = useState(false); const [importComplete, setImportComplete] = useState(false); useEffect(() => { if (integration.id === "xBookmarks") { try { // Listen for import complete message const handleMessage = (event: MessageEvent) => { if (event.data.type === "TWITTER_IMPORT_COMPLETE") { setImportComplete(true); // Auto close after 2 seconds setTimeout(() => { onCancel(); }, 2000); } }; window.addEventListener("message", handleMessage); // Check if extension is present chrome?.runtime.sendMessage(getChromeExtensionId(), { action: "ping" }, (response: any) => { setExtensionPresent(!chrome?.runtime.lastError); }); return () => { window.removeEventListener("message", handleMessage); }; } catch (e) { setExtensionPresent(false); } } }, [integration, onCancel]); return ( onCancel()}> {integration.id === "xBookmarks" && !extensionPresent ? (
Extension Required Please install the SuperMemory extension to connect with X
) : importComplete ? (
Import Complete! Your X bookmarks have been successfully imported.
) : (
{integration.icon && ( )} Importing from {integration.name}... The import has started. It will keep going in the background. You can close this modal and enjoy supermemory! (You might need to reload to see the changes!)
)}
); } function Integrations() { const navigate = useNavigate(); const data = useRouteLoaderData("root"); const env = data?.ENV || {}; const integrations = getIntegrations(env); const [loadingIntegration, setLoadingIntegration] = useState(null); const [apiKey, setApiKey] = useState(null); const [copied, setCopied] = useState(false); const [isCSVModalOpen, setIsCSVModalOpen] = useState(false); const [isMarkdownModalOpen, setIsMarkdownModalOpen] = useState(false); const handleIntegrationClick = (integration: IntegrationConfig) => { setLoadingIntegration(integration); if (integration.handleConnection) { integration.handleConnection(env, navigate); } else if (integration.getAuthUrl) { const authUrl = integration.getAuthUrl(env); // Small delay to show the loading state setTimeout(() => { if (authUrl.startsWith(window.location.origin)) { navigate(new URL(authUrl).pathname); } else { window.location.href = authUrl; } }, 500); } }; const getApiKey = async () => { const response = await fetch(`/backend/v1/user/key`, { credentials: "include", }); if (response.ok) { const data = (await response.json()) as { key: string }; setApiKey(data.key); } else { toast.error("Failed to fetch API key"); console.error("Failed to fetch API key", response); return null; } }; useEffect(() => { getApiKey(); }, []); useEffect(() => { if (copied) { const timeout = setTimeout(() => { setCopied(false); }, 2000); return () => clearTimeout(timeout); } }, [copied]); return (

Import your content

Connect your favorite platforms to import your content into Supermemory

Your API Key {apiKey ? ( ) : (
Loading...
)}
setIsMarkdownModalOpen(true)} >

Obsidian

Import notes from your Obsidian vault

setIsCSVModalOpen(true)} >

CSV Upload

Bulk import URLs from a CSV file

{Object.entries(integrations).map(([key, integration]) => ( handleIntegrationClick(integration)} /> ))}
{loadingIntegration && ( setLoadingIntegration(null)} /> )} setIsCSVModalOpen(false)} /> setIsMarkdownModalOpen(false)} />

More integrations coming soon.{" "} Have a suggestion?

); } type ImportStatus = "idle" | "loading" | "success" | "error"; export function IntegrationModals({ integrationId }: { integrationId: string }) { const [status, setStatus] = useState("idle"); const [progress, setProgress] = useState(0); const [isModalOpen, setIsModalOpen] = useState(true); const router = useNavigate(); useEffect(() => { if (integrationId === "notion") { handleNotionImport(); } }, [integrationId]); const handleNotionImport = async () => { setStatus("loading"); let toastId: string | number | undefined; try { const eventSource = new EventSource(`/backend/v1/integrations/notion/import`, { withCredentials: true, }); eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.progress) { setProgress(data.progress); if (data.page) { toastId = toast.loading(`Importing ${data.page}... ${data.progress}%`, { id: toastId, }); } else { toastId = toast.loading(`Importing from Notion: ${data.progress}%`, { id: toastId }); } } if (data.warning) { toast.warning(data.message); } if (data.error) { throw new Error(data.error); } if (data.progress === 100) { eventSource.close(); setStatus("success"); if (toastId) { toast.success("Successfully imported from Notion!", { id: toastId }); } else { toast.success("Successfully imported from Notion!"); } setTimeout(() => { router("/"); }, 1500); } } catch (e) { console.error("Error parsing SSE data:", e); eventSource.close(); setStatus("error"); if (toastId) { toast.error(e instanceof Error ? e.message : "Failed to import from Notion", { id: toastId, }); } else { toast.error(e instanceof Error ? e.message : "Failed to import from Notion"); } } }; eventSource.onerror = (error) => { console.error("EventSource error:", error); eventSource.close(); setStatus("error"); if (toastId) { toast.error("Failed to connect to import stream", { id: toastId }); } else { toast.error("Failed to connect to import stream"); } }; } catch (error) { console.error("Import error:", error); setStatus("error"); if (toastId) { toast.error(error instanceof Error ? error.message : "Failed to import from Notion", { id: toastId, }); } else { toast.error(error instanceof Error ? error.message : "Failed to import from Notion"); } } }; const handleClose = () => { setIsModalOpen(false); }; if (!integrationId || !isModalOpen) return null; return ( e.stopPropagation()} >
{status === "loading" && ( <>
{progress}%

Importing from Notion

This may take a few minutes. You can close this modal and let it run in the background.

)} {status === "success" && ( <>

Import Complete!

Redirecting you to your dashboard...

)} {status === "error" && ( <>

Import Failed

Please try again or contact support if the issue persists

)}
); } export default Integrations;