diff options
Diffstat (limited to 'apps/web/app')
| -rw-r--r-- | apps/web/app/api/emails/welcome/route.tsx | 2 | ||||
| -rw-r--r-- | apps/web/app/global-error.tsx | 30 | ||||
| -rw-r--r-- | apps/web/app/layout.tsx | 9 | ||||
| -rw-r--r-- | apps/web/app/page.tsx | 326 | ||||
| -rw-r--r-- | apps/web/app/ref/[code]/page.tsx | 9 | ||||
| -rw-r--r-- | apps/web/app/upgrade-mcp/page.tsx | 104 |
6 files changed, 229 insertions, 251 deletions
diff --git a/apps/web/app/api/emails/welcome/route.tsx b/apps/web/app/api/emails/welcome/route.tsx index 48883d6b..69e3ae07 100644 --- a/apps/web/app/api/emails/welcome/route.tsx +++ b/apps/web/app/api/emails/welcome/route.tsx @@ -5,9 +5,9 @@ export async function GET() { return new ImageResponse( <div tw="w-full h-full flex flex-col justify-center items-center"> <img - src="https://pub-1be2b1df2c7e456f8e21149e972f4caf.r2.dev/bust.png" alt="Google Logo" height={367} + src="https://pub-1be2b1df2c7e456f8e21149e972f4caf.r2.dev/bust.png" width={369} /> </div>, diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx index 9bda5fee..786d6682 100644 --- a/apps/web/app/global-error.tsx +++ b/apps/web/app/global-error.tsx @@ -4,20 +4,24 @@ import * as Sentry from "@sentry/nextjs"; import NextError from "next/error"; import { useEffect } from "react"; -export default function GlobalError({ error }: { error: Error & { digest?: string } }) { - useEffect(() => { - Sentry.captureException(error); - }, [error]); +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); - return ( - <html> - <body> - {/* `NextError` is the default Next.js error page component. Its type + return ( + <html> + <body> + {/* `NextError` is the default Next.js error page component. Its type definition requires a `statusCode` prop. However, since the App Router does not expose status codes for errors, we simply pass 0 to render a generic error message. */} - <NextError statusCode={0} /> - </body> - </html> - ); -}
\ No newline at end of file + <NextError statusCode={0} /> + </body> + </html> + ); +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e6d9094d..629dced6 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { Inter, JetBrains_Mono } from "next/font/google"; +import { Inter, JetBrains_Mono, Space_Grotesk } from "next/font/google"; import "../globals.css"; import "@ui/globals.css"; import { AuthProvider } from "@lib/auth-context"; @@ -24,6 +24,11 @@ const mono = JetBrains_Mono({ variable: "--font-mono", }); +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + variable: "--font-space-grotesk", +}); + export const metadata: Metadata = { metadataBase: new URL("https://app.supermemory.ai"), description: "Your memories, wherever you are", @@ -38,7 +43,7 @@ export default function RootLayout({ return ( <html className="dark bg-sm-black" lang="en"> <body - className={`${sans.variable} ${mono.variable} antialiased bg-[#0f1419]`} + className={`${sans.variable} ${mono.variable} ${spaceGrotesk.variable} antialiased bg-[#0f1419]`} > <AutumnProvider backendUrl={ diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index d2dd6b4a..bc87a087 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,14 +1,14 @@ -"use client" - -import { useIsMobile } from "@hooks/use-mobile" -import { useAuth } from "@lib/auth-context" -import { $fetch } from "@repo/lib/api" -import { MemoryGraph } from "@repo/ui/memory-graph" -import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" -import { useInfiniteQuery, useQuery } from "@tanstack/react-query" -import { Logo, LogoFull } from "@ui/assets/Logo" -import { Button } from "@ui/components/button" -import { GlassMenuEffect } from "@ui/other/glass-effect" +"use client"; + +import { useIsMobile } from "@hooks/use-mobile"; +import { useAuth } from "@lib/auth-context"; +import { $fetch } from "@repo/lib/api"; +import { MemoryGraph } from "@repo/ui/memory-graph"; +import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"; +import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; +import { Logo, LogoFull } from "@ui/assets/Logo"; +import { Button } from "@ui/components/button"; +import { GlassMenuEffect } from "@ui/other/glass-effect"; import { HelpCircle, LayoutGrid, @@ -16,58 +16,59 @@ import { LoaderIcon, MessageSquare, Unplug, -} from "lucide-react" -import { AnimatePresence, motion } from "motion/react" -import Link from "next/link" -import { useCallback, useEffect, useMemo, useState } from "react" -import type { z } from "zod" -import { ConnectAIModal } from "@/components/connect-ai-modal" -import { InstallPrompt } from "@/components/install-prompt" -import { MemoryListView } from "@/components/memory-list-view" -import Menu from "@/components/menu" -import { ProjectSelector } from "@/components/project-selector" -import { ReferralUpgradeModal } from "@/components/referral-upgrade-modal" -import type { TourStep } from "@/components/tour" -import { TourAlertDialog, useTour } from "@/components/tour" -import { AddMemoryView } from "@/components/views/add-memory" -import { ChatRewrite } from "@/components/views/chat" -import { TOUR_STEP_IDS, TOUR_STORAGE_KEY } from "@/lib/tour-constants" -import { useViewMode } from "@/lib/view-mode-context" -import { useChatOpen, useProject } from "@/stores" -import { useGraphHighlights } from "@/stores/highlights" - -type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema> -type DocumentWithMemories = DocumentsResponse["documents"][0] +} from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import Link from "next/link"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import type { z } from "zod"; +import { ConnectAIModal } from "@/components/connect-ai-modal"; +import { GetStarted } from "@/components/get-started"; +import { InstallPrompt } from "@/components/install-prompt"; +import { MemoryListView } from "@/components/memory-list-view"; +import Menu from "@/components/menu"; +import { ProjectSelector } from "@/components/project-selector"; +import { ReferralUpgradeModal } from "@/components/referral-upgrade-modal"; +import type { TourStep } from "@/components/tour"; +import { TourAlertDialog, useTour } from "@/components/tour"; +import { AddMemoryView } from "@/components/views/add-memory"; +import { ChatRewrite } from "@/components/views/chat"; +import { TOUR_STEP_IDS, TOUR_STORAGE_KEY } from "@/lib/tour-constants"; +import { useViewMode } from "@/lib/view-mode-context"; +import { useChatOpen, useProject } from "@/stores"; +import { useGraphHighlights } from "@/stores/highlights"; + +type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>; +type DocumentWithMemories = DocumentsResponse["documents"][0]; const MemoryGraphPage = () => { - const { documentIds: allHighlightDocumentIds } = useGraphHighlights() - const isMobile = useIsMobile() - const { viewMode, setViewMode, isInitialized } = useViewMode() - const { selectedProject } = useProject() - const { setSteps, isTourCompleted } = useTour() - const { isOpen, setIsOpen } = useChatOpen() - const [injectedDocs, setInjectedDocs] = useState<DocumentWithMemories[]>([]) - const [showAddMemoryView, setShowAddMemoryView] = useState(false) - const [showReferralModal, setShowReferralModal] = useState(false) - const [showConnectAIModal, setShowConnectAIModal] = useState(false) - const [isHelpHovered, setIsHelpHovered] = useState(false) + const { documentIds: allHighlightDocumentIds } = useGraphHighlights(); + const isMobile = useIsMobile(); + const { viewMode, setViewMode, isInitialized } = useViewMode(); + const { selectedProject } = useProject(); + const { setSteps, isTourCompleted } = useTour(); + const { isOpen, setIsOpen } = useChatOpen(); + const [injectedDocs, setInjectedDocs] = useState<DocumentWithMemories[]>([]); + const [showAddMemoryView, setShowAddMemoryView] = useState(false); + const [showReferralModal, setShowReferralModal] = useState(false); + const [showConnectAIModal, setShowConnectAIModal] = useState(false); + const [isHelpHovered, setIsHelpHovered] = useState(false); // Fetch projects meta to detect experimental flag const { data: projectsMeta = [] } = useQuery({ queryKey: ["projects"], queryFn: async () => { - const response = await $fetch("@get/projects") - return response.data?.projects ?? [] + const response = await $fetch("@get/projects"); + return response.data?.projects ?? []; }, staleTime: 5 * 60 * 1000, - }) + }); const isCurrentProjectExperimental = !!projectsMeta.find( (p: any) => p.containerTag === selectedProject, - )?.isExperimental + )?.isExperimental; // Tour state - const [showTourDialog, setShowTourDialog] = useState(false) + const [showTourDialog, setShowTourDialog] = useState(false); // Define tour steps with useMemo to prevent recreation const tourSteps: TourStep[] = useMemo(() => { @@ -200,37 +201,37 @@ const MemoryGraphPage = () => { selectorId: TOUR_STEP_IDS.FLOATING_CHAT, position: "left", }, - ] - }, []) + ]; + }, []); // Check if tour has been completed before useEffect(() => { - const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true" + const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true"; if (!hasCompletedTour && !isTourCompleted) { const timer = setTimeout(() => { - setShowTourDialog(true) - setShowConnectAIModal(false) - }, 1000) // Show after 1 second - return () => clearTimeout(timer) + setShowTourDialog(true); + setShowConnectAIModal(false); + }, 1000); // Show after 1 second + return () => clearTimeout(timer); } - }, [isTourCompleted]) + }, [isTourCompleted]); // Set up tour steps useEffect(() => { - setSteps(tourSteps) - }, [setSteps, tourSteps]) + setSteps(tourSteps); + }, [setSteps, tourSteps]); // Save tour completion to localStorage useEffect(() => { if (isTourCompleted) { - localStorage.setItem(TOUR_STORAGE_KEY, "true") + localStorage.setItem(TOUR_STORAGE_KEY, "true"); } - }, [isTourCompleted]) + }, [isTourCompleted]); // Progressive loading via useInfiniteQuery - const IS_DEV = process.env.NODE_ENV === "development" - const PAGE_SIZE = IS_DEV ? 100 : 100 - const MAX_TOTAL = 1000 + const IS_DEV = process.env.NODE_ENV === "development"; + const PAGE_SIZE = IS_DEV ? 100 : 100; + const MAX_TOTAL = 1000; const { data, @@ -252,75 +253,76 @@ const MemoryGraphPage = () => { containerTags: selectedProject ? [selectedProject] : undefined, }, disableValidation: true, - }) + }); if (response.error) { - throw new Error(response.error?.message || "Failed to fetch documents") + throw new Error(response.error?.message || "Failed to fetch documents"); } - return response.data + return response.data; }, getNextPageParam: (lastPage, allPages) => { const loaded = allPages.reduce( (acc, p) => acc + (p.documents?.length ?? 0), 0, - ) - if (loaded >= MAX_TOTAL) return undefined + ); + if (loaded >= MAX_TOTAL) return undefined; - const { currentPage, totalPages } = lastPage.pagination + const { currentPage, totalPages } = lastPage.pagination; if (currentPage < totalPages) { - return currentPage + 1 + return currentPage + 1; } - return undefined + return undefined; }, staleTime: 5 * 60 * 1000, - }) + }); const baseDocuments = useMemo(() => { return ( data?.pages.flatMap((p: DocumentsResponse) => p.documents ?? []) ?? [] - ) - }, [data]) + ); + }, [data]); const allDocuments = useMemo(() => { - if (injectedDocs.length === 0) return baseDocuments - const byId = new Map<string, DocumentWithMemories>() - for (const d of injectedDocs) byId.set(d.id, d) - for (const d of baseDocuments) if (!byId.has(d.id)) byId.set(d.id, d) - return Array.from(byId.values()) - }, [baseDocuments, injectedDocs]) + if (injectedDocs.length === 0) return baseDocuments; + const byId = new Map<string, DocumentWithMemories>(); + for (const d of injectedDocs) byId.set(d.id, d); + for (const d of baseDocuments) if (!byId.has(d.id)) byId.set(d.id, d); + return Array.from(byId.values()); + }, [baseDocuments, injectedDocs]); - const totalLoaded = allDocuments.length - const hasMore = hasNextPage - const isLoadingMore = isFetchingNextPage + const totalLoaded = allDocuments.length; + const hasMore = hasNextPage; + const isLoadingMore = isFetchingNextPage; const loadMoreDocuments = useCallback(async (): Promise<void> => { if (hasNextPage && !isFetchingNextPage) { - await fetchNextPage() - return + await fetchNextPage(); + return; } - return - }, [hasNextPage, isFetchingNextPage, fetchNextPage]) + return; + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); // Reset injected docs when project changes useEffect(() => { - setInjectedDocs([]) - }, [selectedProject]) + setInjectedDocs([]); + }, [selectedProject]); // Surgical fetch of missing highlighted documents (customId-based IDs from search) useEffect(() => { - if (!isOpen) return - if (!allHighlightDocumentIds || allHighlightDocumentIds.length === 0) return - const present = new Set<string>() + if (!isOpen) return; + if (!allHighlightDocumentIds || allHighlightDocumentIds.length === 0) + return; + const present = new Set<string>(); for (const d of [...baseDocuments, ...injectedDocs]) { - if (d.id) present.add(d.id) - if ((d as any).customId) present.add((d as any).customId as string) + if (d.id) present.add(d.id); + if ((d as any).customId) present.add((d as any).customId as string); } const missing = allHighlightDocumentIds.filter( (id: string) => !present.has(id), - ) - if (missing.length === 0) return - let cancelled = false + ); + if (missing.length === 0) return; + let cancelled = false; const run = async () => { try { const resp = await $fetch("@post/memories/documents/by-ids", { @@ -330,32 +332,32 @@ const MemoryGraphPage = () => { containerTags: selectedProject ? [selectedProject] : undefined, }, disableValidation: true, - }) - if (cancelled || (resp as any)?.error) return + }); + if (cancelled || (resp as any)?.error) return; const extraDocs = (resp as any)?.data?.documents as | DocumentWithMemories[] - | undefined - if (!extraDocs || extraDocs.length === 0) return + | undefined; + if (!extraDocs || extraDocs.length === 0) return; setInjectedDocs((prev) => { const seen = new Set<string>([ ...prev.map((d) => d.id), ...baseDocuments.map((d) => d.id), - ]) - const merged = [...prev] + ]); + const merged = [...prev]; for (const doc of extraDocs) { if (!seen.has(doc.id)) { - merged.push(doc) - seen.add(doc.id) + merged.push(doc); + seen.add(doc.id); } } - return merged - }) + return merged; + }); } catch {} - } - void run() + }; + void run(); return () => { - cancelled = true - } + cancelled = true; + }; }, [ isOpen, allHighlightDocumentIds.join("|"), @@ -363,39 +365,39 @@ const MemoryGraphPage = () => { injectedDocs, selectedProject, $fetch, - ]) + ]); // Handle view mode change const handleViewModeChange = useCallback( (mode: "graph" | "list") => { - setViewMode(mode) + setViewMode(mode); }, [setViewMode], - ) + ); useEffect(() => { - const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true" + const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true"; if (hasCompletedTour && allDocuments.length === 0 && !showTourDialog) { - setShowConnectAIModal(true) + setShowConnectAIModal(true); } else if (showTourDialog) { - setShowConnectAIModal(false) + setShowConnectAIModal(false); } - }, [allDocuments.length, showTourDialog]) + }, [allDocuments.length, showTourDialog]); // Prevent body scrolling useEffect(() => { - document.body.style.overflow = "hidden" - document.body.style.height = "100vh" - document.documentElement.style.overflow = "hidden" - document.documentElement.style.height = "100vh" + document.body.style.overflow = "hidden"; + document.body.style.height = "100vh"; + document.documentElement.style.overflow = "hidden"; + document.documentElement.style.height = "100vh"; return () => { - document.body.style.overflow = "" - document.body.style.height = "" - document.documentElement.style.overflow = "" - document.documentElement.style.height = "" - } - }, []) + document.body.style.overflow = ""; + document.body.style.height = ""; + document.documentElement.style.overflow = ""; + document.documentElement.style.height = ""; + }; + }, []); return ( <div className="relative h-screen bg-[#0f1419] overflow-hidden touch-none"> @@ -526,9 +528,9 @@ const MemoryGraphPage = () => { <button className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline" onClick={(e) => { - e.stopPropagation() - setShowAddMemoryView(true) - setShowConnectAIModal(false) + e.stopPropagation(); + setShowAddMemoryView(true); + setShowConnectAIModal(false); }} type="button" > @@ -564,37 +566,7 @@ const MemoryGraphPage = () => { loadMoreDocuments={loadMoreDocuments} totalLoaded={totalLoaded} > - <div className="absolute inset-0 flex items-center justify-center"> - <ConnectAIModal - onOpenChange={setShowConnectAIModal} - open={showConnectAIModal} - > - <div className="rounded-xl overflow-hidden cursor-pointer hover:bg-white/5 transition-colors p-6"> - <div className="relative z-10 text-slate-200 text-center"> - <p className="text-lg font-medium mb-4"> - Get Started with supermemory - </p> - <div className="flex flex-col gap-3"> - <p className="text-sm text-blue-400 hover:text-blue-300 transition-colors"> - Click here to set up your AI connection - </p> - <p className="text-xs text-white/60">or</p> - <button - className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline" - onClick={(e) => { - e.stopPropagation() - setShowAddMemoryView(true) - setShowConnectAIModal(false) - }} - type="button" - > - Add your first memory - </button> - </div> - </div> - </div> - </ConnectAIModal> - </div> + <GetStarted /> </MemoryListView> </motion.div> )} @@ -735,35 +707,35 @@ const MemoryGraphPage = () => { onClose={() => setShowReferralModal(false)} /> </div> - ) -} + ); +}; // Wrapper component to handle auth and waitlist checks export default function Page() { - const { user, session } = useAuth() + const { user, session } = useAuth(); useEffect(() => { - const url = new URL(window.location.href) + const url = new URL(window.location.href); const authenticateChromeExtension = url.searchParams.get( "extension-auth-success", - ) + ); if (authenticateChromeExtension) { - const sessionToken = session?.token + const sessionToken = session?.token; const userData = { email: user?.email, name: user?.name, userId: user?.id, - } + }; if (sessionToken && userData?.email) { - const encodedToken = encodeURIComponent(sessionToken) - window.postMessage({ token: encodedToken, userData }, "*") - url.searchParams.delete("extension-auth-success") - window.history.replaceState({}, "", url.toString()) + const encodedToken = encodeURIComponent(sessionToken); + window.postMessage({ token: encodedToken, userData }, "*"); + url.searchParams.delete("extension-auth-success"); + window.history.replaceState({}, "", url.toString()); } } - }, [user, session]) + }, [user, session]); // Show loading state while checking authentication and waitlist status if (!user) { @@ -774,7 +746,7 @@ export default function Page() { <p className="text-white/60">Loading...</p> </div> </div> - ) + ); } // If we have a user and they have access, show the main component @@ -783,5 +755,5 @@ export default function Page() { <MemoryGraphPage /> <InstallPrompt /> </> - ) + ); } diff --git a/apps/web/app/ref/[code]/page.tsx b/apps/web/app/ref/[code]/page.tsx index 98afcfe5..f5633029 100644 --- a/apps/web/app/ref/[code]/page.tsx +++ b/apps/web/app/ref/[code]/page.tsx @@ -55,8 +55,6 @@ export default function ReferralPage() { checkReferral(); }, [referralCode]); - - const handleCopyLink = async () => { try { await navigator.clipboard.writeText(referralLink); @@ -146,11 +144,10 @@ export default function ReferralPage() { </p> </div> - <div className="text-center"> <Link - href="https://supermemory.ai" className="text-orange-500 hover:text-orange-400 text-sm underline" + href="https://supermemory.ai" > Learn more about supermemory </Link> @@ -178,10 +175,10 @@ export default function ReferralPage() { </p> </div> <Button + className="shrink-0 border-white/10 hover:bg-white/5" onClick={handleCopyLink} size="sm" variant="outline" - className="shrink-0 border-white/10 hover:bg-white/5" > {copiedLink ? ( <CheckIcon className="w-4 h-4" /> @@ -192,9 +189,9 @@ export default function ReferralPage() { </div> <Button + className="w-full border-white/10 text-white hover:bg-white/5" onClick={handleShare} variant="outline" - className="w-full border-white/10 text-white hover:bg-white/5" > <ShareIcon className="w-4 h-4" /> Share this link diff --git a/apps/web/app/upgrade-mcp/page.tsx b/apps/web/app/upgrade-mcp/page.tsx index d62b3769..47d643a1 100644 --- a/apps/web/app/upgrade-mcp/page.tsx +++ b/apps/web/app/upgrade-mcp/page.tsx @@ -1,107 +1,107 @@ -"use client" +"use client"; -import { $fetch } from "@lib/api" -import { useSession } from "@lib/auth" -import { useMutation } from "@tanstack/react-query" -import { Logo, LogoFull } from "@ui/assets/Logo" -import { Button } from "@ui/components/button" -import { Input } from "@ui/components/input" -import { GlassMenuEffect } from "@ui/other/glass-effect" -import { ArrowRight, CheckCircle, Upload, Zap } from "lucide-react" -import { AnimatePresence, motion } from "motion/react" -import Link from "next/link" -import { useRouter, useSearchParams } from "next/navigation" -import { useEffect, useState } from "react" -import { toast } from "sonner" -import { Spinner } from "@/components/spinner" +import { $fetch } from "@lib/api"; +import { useSession } from "@lib/auth"; +import { useMutation } from "@tanstack/react-query"; +import { Logo, LogoFull } from "@ui/assets/Logo"; +import { Button } from "@ui/components/button"; +import { Input } from "@ui/components/input"; +import { GlassMenuEffect } from "@ui/other/glass-effect"; +import { ArrowRight, CheckCircle, Upload, Zap } from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { Spinner } from "@/components/spinner"; interface MigrateMCPRequest { - userId: string - projectId: string + userId: string; + projectId: string; } interface MigrateMCPResponse { - success: boolean - migratedCount: number - message: string - documentIds?: string[] + success: boolean; + migratedCount: number; + message: string; + documentIds?: string[]; } export default function MigrateMCPPage() { - const router = useRouter() - const searchParams = useSearchParams() - const [mcpUrl, setMcpUrl] = useState("") - const [projectId, setProjectId] = useState("default") + const router = useRouter(); + const searchParams = useSearchParams(); + const [mcpUrl, setMcpUrl] = useState(""); + const [projectId, setProjectId] = useState("default"); - const session = useSession() + const session = useSession(); // Extract MCP URL from query parameter useEffect(() => { - const urlParam = searchParams.get("url") + const urlParam = searchParams.get("url"); if (urlParam) { - setMcpUrl(urlParam) + setMcpUrl(urlParam); } - }, [searchParams]) + }, [searchParams]); useEffect(() => { - console.log("session", session) + console.log("session", session); if (!session.isPending && !session.data) { - const redirectUrl = new URL("/login", window.location.href) - redirectUrl.searchParams.set("redirect", window.location.href) - router.push(redirectUrl.toString()) - return + const redirectUrl = new URL("/login", window.location.href); + redirectUrl.searchParams.set("redirect", window.location.href); + router.push(redirectUrl.toString()); + return; } - }, [session, router]) + }, [session, router]); // Extract userId from MCP URL const getUserIdFromUrl = (url: string) => { - return url.split("/").at(-2) || "" - } + return url.split("/").at(-2) || ""; + }; const migrateMutation = useMutation({ mutationFn: async (data: MigrateMCPRequest) => { const response = await $fetch("@post/documents/migrate-mcp", { body: data, - }) + }); if (response.error) { throw new Error( response.error?.message || "Failed to migrate documents", - ) + ); } - return response.data + return response.data; }, onSuccess: (data: MigrateMCPResponse) => { toast.success("Migration completed successfully", { description: data.message, - }) + }); // Redirect to home page after successful migration setTimeout(() => { - router.push("/?open=mcp") - }, 2000) // Wait 2 seconds to show the success message + router.push("/?open=mcp"); + }, 2000); // Wait 2 seconds to show the success message }, onError: (error: Error) => { toast.error("Migration failed", { description: error.message || "An unexpected error occurred", - }) + }); }, - }) + }); const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() + e.preventDefault(); - const userId = getUserIdFromUrl(mcpUrl) + const userId = getUserIdFromUrl(mcpUrl); if (!userId) { - toast.error("Please enter a valid MCP URL") - return + toast.error("Please enter a valid MCP URL"); + return; } migrateMutation.mutate({ userId, projectId: projectId.trim() || "default", - }) - } + }); + }; return ( <div className="min-h-screen bg-[#0f1419] overflow-hidden relative"> @@ -320,5 +320,5 @@ export default function MigrateMCPPage() { </motion.div> </div> </div> - ) + ); } |