diff options
| author | Dhravya Shah <[email protected]> | 2025-09-19 16:05:12 -0700 |
|---|---|---|
| committer | Dhravya Shah <[email protected]> | 2025-09-19 16:05:12 -0700 |
| commit | f82104ecc8835bbf61d94b12e6eff8487c2f7a8a (patch) | |
| tree | d2fad0b6670b7ba3ccefe9046e1ec4edd9e05d76 /apps/web/components | |
| parent | fix: one more build error (diff) | |
| download | supermemory-f82104ecc8835bbf61d94b12e6eff8487c2f7a8a.tar.xz supermemory-f82104ecc8835bbf61d94b12e6eff8487c2f7a8a.zip | |
fix
Diffstat (limited to 'apps/web/components')
| -rw-r--r-- | apps/web/components/connect-ai-modal.tsx | 172 | ||||
| -rw-r--r-- | apps/web/components/create-project-dialog.tsx | 62 |
2 files changed, 119 insertions, 115 deletions
diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx index f3008796..6500ba23 100644 --- a/apps/web/components/connect-ai-modal.tsx +++ b/apps/web/components/connect-ai-modal.tsx @@ -1,9 +1,9 @@ -"use client"; +"use client" -import { $fetch } from "@lib/api"; -import { useForm } from "@tanstack/react-form"; -import { useMutation, useQuery } from "@tanstack/react-query"; -import { Button } from "@ui/components/button"; +import { $fetch } from "@lib/api" +import { useForm } from "@tanstack/react-form" +import { useMutation, useQuery } from "@tanstack/react-query" +import { Button } from "@ui/components/button" import { Dialog, DialogContent, @@ -11,22 +11,22 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@ui/components/dialog"; -import { Input } from "@ui/components/input"; +} from "@ui/components/dialog" +import { Input } from "@ui/components/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@ui/components/select"; -import { CopyableCell } from "@ui/copyable-cell"; -import { CopyIcon, ExternalLink, Loader2 } from "lucide-react"; -import Image from "next/image"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; -import { z } from "zod/v4"; -import { analytics } from "@/lib/analytics"; +} from "@ui/components/select" +import { CopyableCell } from "@ui/copyable-cell" +import { CopyIcon, ExternalLink, Loader2 } from "lucide-react" +import Image from "next/image" +import { useEffect, useState } from "react" +import { toast } from "sonner" +import { z } from "zod/v4" +import { analytics } from "@/lib/analytics" const clients = { cursor: "Cursor", @@ -39,7 +39,7 @@ const clients = { "roo-cline": "Roo Cline", witsy: "Witsy", enconvo: "Enconvo", -} as const; +} as const const mcpMigrationSchema = z.object({ url: z @@ -49,21 +49,21 @@ const mcpMigrationSchema = z.object({ /^https:\/\/mcp\.supermemory\.ai\/[^/]+\/sse$/, "Link must be in format: https://mcp.supermemory.ai/userId/sse", ), -}); +}) interface Project { - id: string; - name: string; - containerTag: string; - createdAt: string; - updatedAt: string; - isExperimental?: boolean; + id: string + name: string + containerTag: string + createdAt: string + updatedAt: string + isExperimental?: boolean } interface ConnectAIModalProps { - children: React.ReactNode; - open?: boolean; - onOpenChange?: (open: boolean) => void; + children: React.ReactNode + open?: boolean + onOpenChange?: (open: boolean) => void } export function ConnectAIModal({ @@ -73,103 +73,103 @@ export function ConnectAIModal({ }: ConnectAIModalProps) { const [selectedClient, setSelectedClient] = useState< keyof typeof clients | null - >(null); - const [internalIsOpen, setInternalIsOpen] = useState(false); - const isOpen = open !== undefined ? open : internalIsOpen; - const setIsOpen = onOpenChange || setInternalIsOpen; - const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false); - const [selectedProject, setSelectedProject] = useState<string | null>("none"); - const projectId = localStorage.getItem("selectedProject") ?? "default"; + >(null) + const [internalIsOpen, setInternalIsOpen] = useState(false) + const isOpen = open !== undefined ? open : internalIsOpen + const setIsOpen = onOpenChange || setInternalIsOpen + const [isMigrateDialogOpen, setIsMigrateDialogOpen] = useState(false) + const [selectedProject, setSelectedProject] = useState<string | null>("none") + const projectId = localStorage.getItem("selectedProject") ?? "default" useEffect(() => { - analytics.mcpViewOpened(); - }, []); + analytics.mcpViewOpened() + }, []) const { data: projects = [], isLoading: isLoadingProjects } = useQuery({ queryKey: ["projects"], queryFn: async () => { - const response = await $fetch("@get/projects"); + const response = await $fetch("@get/projects") if (response.error) { - throw new Error(response.error?.message || "Failed to load projects"); + throw new Error(response.error?.message || "Failed to load projects") } - return response.data?.projects || []; + return response.data?.projects || [] }, staleTime: 30 * 1000, - }); + }) const mcpMigrationForm = useForm({ defaultValues: { url: "" }, onSubmit: async ({ value, formApi }) => { - const userId = extractUserIdFromMCPUrl(value.url); + const userId = extractUserIdFromMCPUrl(value.url) if (userId) { - migrateMCPMutation.mutate({ userId, projectId }); - formApi.reset(); + migrateMCPMutation.mutate({ userId, projectId }) + formApi.reset() } }, validators: { onChange: mcpMigrationSchema, }, - }); + }) const extractUserIdFromMCPUrl = (url: string): string | null => { - const regex = /^https:\/\/mcp\.supermemory\.ai\/([^/]+)\/sse$/; - const match = url.trim().match(regex); - return match?.[1] || null; - }; + const regex = /^https:\/\/mcp\.supermemory\.ai\/([^/]+)\/sse$/ + const match = url.trim().match(regex) + return match?.[1] || null + } const migrateMCPMutation = useMutation({ mutationFn: async ({ userId, projectId, }: { - userId: string; - projectId: string; + userId: string + projectId: string }) => { - const response = await $fetch("@post/memories/migrate-mcp", { + const response = await $fetch("@post/documents/migrate-mcp", { body: { userId, projectId }, - }); + }) if (response.error) { throw new Error( response.error?.message || "Failed to migrate documents", - ); + ) } - return response.data; + return response.data }, onSuccess: (data) => { toast.success("Migration completed!", { description: `Successfully migrated ${data?.migratedCount} documents`, - }); - setIsMigrateDialogOpen(false); + }) + setIsMigrateDialogOpen(false) }, onError: (error) => { toast.error("Migration failed", { description: error instanceof Error ? error.message : "Unknown error", - }); + }) }, - }); + }) function generateInstallCommand() { - if (!selectedClient) return ""; + if (!selectedClient) return "" - let command = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${selectedClient} --oauth=yes`; + let command = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${selectedClient} --oauth=yes` if (selectedProject && selectedProject !== "none") { // Remove the "sm_project_" prefix from the containerTag - const projectIdForCommand = selectedProject.replace(/^sm_project_/, ""); - command += ` --project ${projectIdForCommand}`; + const projectIdForCommand = selectedProject.replace(/^sm_project_/, "") + command += ` --project ${projectIdForCommand}` } - return command; + return command } const copyToClipboard = () => { - const command = generateInstallCommand(); - navigator.clipboard.writeText(command); - analytics.mcpInstallCmdCopied(); - toast.success("Copied to clipboard!"); - }; + const command = generateInstallCommand() + navigator.clipboard.writeText(command) + analytics.mcpInstallCmdCopied() + toast.success("Copied to clipboard!") + } return ( <Dialog onOpenChange={setIsOpen} open={isOpen}> @@ -219,23 +219,27 @@ export function ConnectAIModal({ className="rounded object-contain text-white fill-white" height={20} onError={(e) => { - const target = e.target as HTMLImageElement; - target.style.display = "none"; - const parent = target.parentElement; + const target = e.target as HTMLImageElement + target.style.display = "none" + const parent = target.parentElement if ( parent && !parent.querySelector(".fallback-text") ) { - const fallback = document.createElement("span"); + const fallback = document.createElement("span") fallback.className = - "fallback-text text-sm font-bold text-white/40"; + "fallback-text text-sm font-bold text-white/40" fallback.textContent = clientName .substring(0, 2) - .toUpperCase(); - parent.appendChild(fallback); + .toUpperCase() + parent.appendChild(fallback) } }} - src={key === "mcp-url" ? "/mcp-icon.svg" : `/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png`} + src={ + key === "mcp-url" + ? "/mcp-icon.svg" + : `/mcp-supported-tools/${key === "claude-code" ? "claude" : key}.png` + } width={20} /> </div> @@ -275,9 +279,9 @@ export function ConnectAIModal({ onClick={() => { navigator.clipboard.writeText( "https://api.supermemory.ai/mcp", - ); - analytics.mcpInstallCmdCopied(); - toast.success("Copied to clipboard!"); + ) + analytics.mcpInstallCmdCopied() + toast.success("Copied to clipboard!") }} variant="ghost" > @@ -459,9 +463,9 @@ export function ConnectAIModal({ </DialogHeader> <form onSubmit={(e) => { - e.preventDefault(); - e.stopPropagation(); - mcpMigrationForm.handleSubmit(); + e.preventDefault() + e.stopPropagation() + mcpMigrationForm.handleSubmit() }} > <div className="grid gap-4"> @@ -500,8 +504,8 @@ export function ConnectAIModal({ <Button className="bg-white/5 hover:bg-white/10 border-white/10 text-white" onClick={() => { - setIsMigrateDialogOpen(false); - mcpMigrationForm.reset(); + setIsMigrateDialogOpen(false) + mcpMigrationForm.reset() }} type="button" variant="outline" @@ -532,5 +536,5 @@ export function ConnectAIModal({ </Dialog> )} </Dialog> - ); + ) } diff --git a/apps/web/components/create-project-dialog.tsx b/apps/web/components/create-project-dialog.tsx index 1672a689..9a65b894 100644 --- a/apps/web/components/create-project-dialog.tsx +++ b/apps/web/components/create-project-dialog.tsx @@ -1,6 +1,6 @@ -"use client"; +"use client" -import { Button } from "@repo/ui/components/button"; +import { Button } from "@repo/ui/components/button" import { Dialog, DialogContent, @@ -8,50 +8,50 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@repo/ui/components/dialog"; -import { Input } from "@repo/ui/components/input"; -import { Label } from "@repo/ui/components/label"; -import { Loader2 } from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; -import { useState } from "react"; -import { useProjectMutations } from "@/hooks/use-project-mutations"; +} from "@repo/ui/components/dialog" +import { Input } from "@repo/ui/components/input" +import { Label } from "@repo/ui/components/label" +import { Loader2 } from "lucide-react" +import { AnimatePresence, motion } from "motion/react" +import { useState } from "react" +import { useProjectMutations } from "@/hooks/use-project-mutations" interface CreateProjectDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; + open: boolean + onOpenChange: (open: boolean) => void } export function CreateProjectDialog({ open, onOpenChange, }: CreateProjectDialogProps) { - const [projectName, setProjectName] = useState(""); - const { createProjectMutation } = useProjectMutations(); + const [projectName, setProjectName] = useState("") + const { createProjectMutation } = useProjectMutations() const handleClose = () => { - onOpenChange(false); - setProjectName(""); - }; + onOpenChange(false) + setProjectName("") + } const handleCreate = () => { if (projectName.trim()) { createProjectMutation.mutate(projectName, { onSuccess: () => { - handleClose(); + handleClose() }, - }); + }) } - }; + } return ( <AnimatePresence> {open && ( - <Dialog open={open} onOpenChange={onOpenChange}> + <Dialog onOpenChange={onOpenChange} open={open}> <DialogContent className="sm:max-w-2xl bg-[#0f1419] backdrop-blur-xl border-white/10 text-white"> <motion.div - initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} + initial={{ opacity: 0, scale: 0.95 }} > <DialogHeader> <DialogTitle>Create New Project</DialogTitle> @@ -61,23 +61,23 @@ export function CreateProjectDialog({ </DialogHeader> <div className="grid gap-4 py-4"> <motion.div - initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} - transition={{ delay: 0.1 }} className="flex flex-col gap-2" + initial={{ opacity: 0, y: 10 }} + transition={{ delay: 0.1 }} > <Label htmlFor="projectName">Project Name</Label> <Input - id="projectName" className="bg-white/5 border-white/10 text-white" - placeholder="My Awesome Project" - value={projectName} + id="projectName" onChange={(e) => setProjectName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && projectName.trim()) { - handleCreate(); + handleCreate() } }} + placeholder="My Awesome Project" + value={projectName} /> <p className="text-xs text-white/50"> This will help you organize your memories @@ -90,10 +90,10 @@ export function CreateProjectDialog({ whileTap={{ scale: 0.95 }} > <Button - type="button" - variant="outline" className="bg-white/5 hover:bg-white/10 border-white/10 text-white" onClick={handleClose} + type="button" + variant="outline" > Cancel </Button> @@ -103,12 +103,12 @@ export function CreateProjectDialog({ whileTap={{ scale: 0.95 }} > <Button - type="button" className="bg-white/10 hover:bg-white/20 text-white border-white/20" disabled={ createProjectMutation.isPending || !projectName.trim() } onClick={handleCreate} + type="button" > {createProjectMutation.isPending ? ( <> @@ -126,5 +126,5 @@ export function CreateProjectDialog({ </Dialog> )} </AnimatePresence> - ); + ) } |