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() { - { + onOpenChange={(open) => { setShowModal(open) - if (!open) { - setApiKey("") - setCopied(false) - } + if (!open) setApiKey("") }} - > - - - - - Setup Raycast Extension - - -
-
- -
- - -
-
-
-

- Follow these steps: -

-
- {[ - "Install the Raycast extension from the Raycast Store", - "Open Raycast preferences and paste your API key", - 'Use "Add Memory" or "Search Memories" commands!', - ].map((text, i) => ( -
-
- {i + 1} -
-

- {text} -

-
- ))} -
-
- -
-
-
-
+ 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 ( + + + Set up Raycast Extension + +
+ +
+

+ Set up Raycast Extension +

+

+ Copy your key and follow these steps to finish. +

+
+ + + +
+ +
+
+ +
+
+ +
+ +
+
+
+ ) +} 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() { - - - - - - Setup Raycast Extension - - - -
-
- -
- - -
-
- -
-

- Follow these steps: -

-
-
-
- 1 -
-

- Install the Raycast extension from the Raycast Store -

-
-
-
- 2 -
-

- Open Raycast preferences and paste your API key -

-
-
-
- 3 -
-

- Use "Add Memory" or "Search Memories" commands! -

-
-
-
- -
- -
-
-
-
-
+ apiKey={raycastApiKey} + /> ) }