diff --git a/apps/web/app/(app)/settings/integrations/page.tsx b/apps/web/app/(app)/settings/integrations/page.tsx
new file mode 100644
index 00000000..3984fb52
--- /dev/null
+++ b/apps/web/app/(app)/settings/integrations/page.tsx
@@ -0,0 +1,16 @@
+"use client"
+
+import { useEffect } from "react"
+import { useRouter, useSearchParams } from "next/navigation"
+
+export default function SettingsIntegrationsPage() {
+ const router = useRouter()
+ const searchParams = useSearchParams()
+
+ useEffect(() => {
+ const qs = searchParams.toString()
+ router.replace(`/settings${qs ? `?${qs}` : ""}#integrations`)
+ }, [router, searchParams])
+
+ return null
+}
diff --git a/apps/web/components/integrations/raycast-detail.tsx b/apps/web/components/integrations/raycast-detail.tsx
index 94769cb5..7557471c 100644
--- a/apps/web/components/integrations/raycast-detail.tsx
+++ b/apps/web/components/integrations/raycast-detail.tsx
@@ -7,17 +7,11 @@ import { authClient } from "@lib/auth"
import { useAuth } from "@lib/auth-context"
import { generateId } from "@lib/generate-id"
import { RAYCAST_EXTENSION_URL } from "@lib/constants"
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogPortal,
-} from "@ui/components/dialog"
import { useMutation } from "@tanstack/react-query"
-import { Check, Copy, Download, Key, Loader } from "lucide-react"
-import { useId, useState } from "react"
+import { Download, Key, Loader } from "lucide-react"
+import { useState } from "react"
import { toast } from "sonner"
+import { RaycastSetupModal } from "./raycast-setup-modal"
function PillButton({
children,
@@ -52,19 +46,6 @@ export function RaycastDetail() {
const { org } = useAuth()
const [showModal, setShowModal] = useState(false)
const [apiKey, setApiKey] = useState("")
- const [copied, setCopied] = useState(false)
- const apiKeyId = useId()
-
- const handleCopy = async (key: string) => {
- try {
- await navigator.clipboard.writeText(key)
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
- toast.success("API key copied to clipboard!")
- } catch {
- toast.error("Failed to copy API key")
- }
- }
const createKeyMutation = useMutation({
mutationFn: async () => {
@@ -74,13 +55,14 @@ export function RaycastDetail() {
name: `raycast-${generateId().slice(0, 8)}`,
prefix: `sm_${org.id}_`,
})
- return res.key
+ if (res.error)
+ throw new Error(res.error.message ?? "Failed to create API key")
+ if (!res.data?.key) throw new Error("API key missing from response")
+ return res.data.key
},
onSuccess: (key) => {
setApiKey(key)
setShowModal(true)
- setCopied(false)
- handleCopy(key)
},
onError: (error) => {
toast.error("Failed to create API key", {
@@ -146,111 +128,14 @@ export function RaycastDetail() {
-
+ apiKey={apiKey}
+ />
>
)
}
diff --git a/apps/web/components/integrations/raycast-setup-modal.tsx b/apps/web/components/integrations/raycast-setup-modal.tsx
new file mode 100644
index 00000000..3d4a2a66
--- /dev/null
+++ b/apps/web/components/integrations/raycast-setup-modal.tsx
@@ -0,0 +1,132 @@
+"use client"
+
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { Download, X } from "lucide-react"
+import { cn } from "@lib/utils"
+import { dmSans125ClassName } from "@/lib/fonts"
+import { RAYCAST_EXTENSION_URL } from "@lib/constants"
+import { RaycastIcon } from "@/components/integration-icons"
+import { Dialog, DialogContent, DialogTitle } from "@ui/components/dialog"
+import type { InstallStep } from "@/lib/plugin-catalog"
+import { INSET, InstallSteps } from "./install-steps"
+
+const RAYCAST_STEPS: InstallStep[] = [
+ {
+ title: "Copy your API key",
+ description: "You won't be able to see it again — store it somewhere safe.",
+ code: "sm_...",
+ copyLabel: "API key",
+ secret: true,
+ },
+ {
+ title: "Install the Raycast extension",
+ description: "Open the Supermemory extension page in the Raycast Store.",
+ },
+ {
+ title: "Paste your key in Raycast preferences",
+ description:
+ "Open Raycast preferences → Extensions → Supermemory, then paste the key above.",
+ },
+ {
+ title: 'Run "Add Memory" or "Search Memories"',
+ description: "Trigger Raycast and start using Supermemory from anywhere.",
+ },
+]
+
+function RaycastIconBox() {
+ return (
+
+
+
+ )
+}
+
+export function RaycastSetupModal({
+ open,
+ onOpenChange,
+ apiKey,
+}: {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ apiKey: string
+}) {
+ return (
+
+ )
+}
diff --git a/apps/web/components/settings/integrations.tsx b/apps/web/components/settings/integrations.tsx
index dc3e5818..de72ad16 100644
--- a/apps/web/components/settings/integrations.tsx
+++ b/apps/web/components/settings/integrations.tsx
@@ -30,6 +30,7 @@ import {
AppleShortcutsIcon,
RaycastIcon,
} from "@/components/integration-icons"
+import { RaycastSetupModal } from "@/components/integrations/raycast-setup-modal"
function SectionTitle({ children }: { children: React.ReactNode }) {
return (
@@ -128,20 +129,13 @@ export default function Integrations() {
// 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) => {
+ const handleCopyApiKey = async (key: string) => {
try {
await navigator.clipboard.writeText(key)
- if (isRaycast) {
- setRaycastCopied(true)
- setTimeout(() => setRaycastCopied(false), 2000)
- } else {
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
- }
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
toast.success("API key copied to clipboard!")
} catch {
toast.error("Failed to copy API key")
@@ -187,13 +181,14 @@ export default function Integrations() {
name: `raycast-${generateId().slice(0, 8)}`,
prefix: `sm_${org.id}_`,
})
- return res.key
+ if (res.error)
+ throw new Error(res.error.message ?? "Failed to create API key")
+ if (!res.data?.key) throw new Error("API key missing from response")
+ return res.data.key
},
onSuccess: (key) => {
setRaycastApiKey(key)
setShowRaycastApiKeyModal(true)
- setRaycastCopied(false)
- handleCopyApiKey(key, true)
},
onError: (error) => {
toast.error("Failed to create Raycast API key", {
@@ -260,10 +255,7 @@ export default function Integrations() {
const handleRaycastDialogClose = (open: boolean) => {
setShowRaycastApiKeyModal(open)
- if (!open) {
- setRaycastApiKey("")
- setRaycastCopied(false)
- }
+ if (!open) setRaycastApiKey("")
}
return (
@@ -564,134 +556,11 @@ export default function Integrations() {
-
+ apiKey={raycastApiKey}
+ />
)
}