aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/app')
-rw-r--r--apps/web/app/api/emails/welcome/route.tsx2
-rw-r--r--apps/web/app/global-error.tsx30
-rw-r--r--apps/web/app/layout.tsx9
-rw-r--r--apps/web/app/page.tsx326
-rw-r--r--apps/web/app/ref/[code]/page.tsx9
-rw-r--r--apps/web/app/upgrade-mcp/page.tsx104
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>
- )
+ );
}