"use client"
import { dmSans125ClassName } from "@/lib/fonts"
import { analytics } from "@/lib/analytics"
import { cn } from "@lib/utils"
import { authClient } from "@lib/auth"
import { useAuth } from "@lib/auth-context"
import { generateId } from "@lib/generate-id"
import {
ADD_MEMORY_SHORTCUT_URL,
RAYCAST_EXTENSION_URL,
SEARCH_MEMORY_SHORTCUT_URL,
} from "@repo/lib/constants"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogPortal,
} from "@ui/components/dialog"
import { useMutation } from "@tanstack/react-query"
import { Check, Copy, Download, Key, Loader, Plus, Search } from "lucide-react"
import Image from "next/image"
import { useSearchParams } from "next/navigation"
import { useEffect, useId, useState } from "react"
import { toast } from "sonner"
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
function IntegrationCard({
children,
id,
}: {
children: React.ReactNode
id?: string
}) {
return (
{children}
)
}
function PillButton({
children,
onClick,
className,
disabled,
}: {
children: React.ReactNode
onClick?: () => void
className?: string
disabled?: boolean
}) {
return (
)
}
function FeatureItem({ text }: { text: string }) {
return (
{text}
)
}
function ChromeIcon({ className }: { className?: string }) {
return (
)
}
function AppleShortcutsIcon() {
return (
)
}
function RaycastIcon({ className }: { className?: string }) {
return (
)
}
export default function Integrations() {
const { org } = useAuth()
const searchParams = useSearchParams()
// iOS Shortcuts state
const [showApiKeyModal, setShowApiKeyModal] = useState(false)
const [apiKey, setApiKey] = useState("")
const [copied, setCopied] = useState(false)
const [selectedShortcutType, setSelectedShortcutType] = useState<
"add" | "search" | null
>(null)
const apiKeyId = useId()
// Raycast state
const [showRaycastApiKeyModal, setShowRaycastApiKeyModal] = useState(false)
const [raycastApiKey, setRaycastApiKey] = useState("")
const [raycastCopied, setRaycastCopied] = useState(false)
const [hasTriggeredRaycast, setHasTriggeredRaycast] = useState(false)
const raycastApiKeyId = useId()
const handleCopyApiKey = async (key: string, isRaycast = false) => {
try {
await navigator.clipboard.writeText(key)
if (isRaycast) {
setRaycastCopied(true)
setTimeout(() => setRaycastCopied(false), 2000)
} else {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
toast.success("API key copied to clipboard!")
} catch {
toast.error("Failed to copy API key")
}
}
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: (key) => {
setApiKey(key)
setShowApiKeyModal(true)
setCopied(false)
handleCopyApiKey(key)
},
onError: (error) => {
toast.error("Failed to create API key", {
description: error instanceof Error ? error.message : "Unknown error",
})
},
})
const createRaycastApiKeyMutation = useMutation({
mutationFn: async () => {
if (!org?.id) {
throw new Error("Organization ID is required")
}
const res = await authClient.apiKey.create({
metadata: {
organizationId: org.id,
type: "raycast-extension",
},
name: `raycast-${generateId().slice(0, 8)}`,
prefix: `sm_${org.id}_`,
})
return res.key
},
onSuccess: (key) => {
setRaycastApiKey(key)
setShowRaycastApiKeyModal(true)
setRaycastCopied(false)
handleCopyApiKey(key, true)
},
onError: (error) => {
toast.error("Failed to create Raycast API key", {
description: error instanceof Error ? error.message : "Unknown error",
})
},
})
useEffect(() => {
const qParam = searchParams.get("q")
if (
qParam === "raycast" &&
!hasTriggeredRaycast &&
!createRaycastApiKeyMutation.isPending &&
org?.id
) {
setHasTriggeredRaycast(true)
createRaycastApiKeyMutation.mutate()
}
}, [searchParams, hasTriggeredRaycast, createRaycastApiKeyMutation, org])
const handleChromeInstall = () => {
window.open(
"https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc",
"_blank",
"noopener,noreferrer",
)
analytics.onboardingChromeExtensionClicked({ source: "settings" })
analytics.extensionInstallClicked()
}
const handleShortcutClick = (shortcutType: "add" | "search") => {
setSelectedShortcutType(shortcutType)
createApiKeyMutation.mutate()
}
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 handleRaycastClick = () => {
createRaycastApiKeyMutation.mutate()
}
const handleRaycastInstall = () => {
window.open(RAYCAST_EXTENSION_URL, "_blank")
analytics.onboardingChromeExtensionClicked({ source: "settings" })
analytics.extensionInstallClicked()
}
const handleDialogClose = (open: boolean) => {
setShowApiKeyModal(open)
if (!open) {
setSelectedShortcutType(null)
setApiKey("")
setCopied(false)
}
}
const handleRaycastDialogClose = (open: boolean) => {
setShowRaycastApiKeyModal(open)
if (!open) {
setRaycastApiKey("")
setRaycastCopied(false)
}
}
return (
Integrations
handleShortcutClick("add")}
disabled={createApiKeyMutation.isPending}
>
{createApiKeyMutation.isPending &&
selectedShortcutType === "add" ? (
) : (
)}
{createApiKeyMutation.isPending &&
selectedShortcutType === "add"
? "Creating..."
: "Add memory shortcut"}
handleShortcutClick("search")}
disabled={createApiKeyMutation.isPending}
>
{createApiKeyMutation.isPending &&
selectedShortcutType === "search" ? (
) : (
)}
{createApiKeyMutation.isPending &&
selectedShortcutType === "search"
? "Creating..."
: "Search memory shortcut"}
{createRaycastApiKeyMutation.isPending ? (
) : (
)}
{createRaycastApiKeyMutation.isPending
? "Generating..."
: "Get API key"}
Install extension
)
}