aboutsummaryrefslogtreecommitdiff
path: root/apps/web/components
diff options
context:
space:
mode:
authorCodeWithShreyans <[email protected]>2025-09-04 16:46:47 +0000
committerCodeWithShreyans <[email protected]>2025-09-04 16:46:47 +0000
commit84fea4a6981385145dd84a58a610b4782caec047 (patch)
tree3d3f84d0ce15a096086c28e86e863ea99d25006b /apps/web/components
parentfeat: openai python sdk (#409) (diff)
downloadsupermemory-84fea4a6981385145dd84a58a610b4782caec047.tar.xz
supermemory-84fea4a6981385145dd84a58a610b4782caec047.zip
feat: add mcp migrate route (#410)shreyans/09-03-feat_add_mcp_migrate_route
Diffstat (limited to 'apps/web/components')
-rw-r--r--apps/web/components/menu.tsx222
-rw-r--r--apps/web/components/views/profile.tsx80
2 files changed, 183 insertions, 119 deletions
diff --git a/apps/web/components/menu.tsx b/apps/web/components/menu.tsx
index 6eb81128..db012ab7 100644
--- a/apps/web/components/menu.tsx
+++ b/apps/web/components/menu.tsx
@@ -1,27 +1,28 @@
-"use client";
+"use client"
-import { useIsMobile } from "@hooks/use-mobile";
+import { useIsMobile } from "@hooks/use-mobile"
import {
fetchConsumerProProduct,
fetchMemoriesFeature,
-} from "@repo/lib/queries";
-import { Button } from "@repo/ui/components/button";
-import { ConnectAIModal } from "./connect-ai-modal";
-import { HeadingH2Bold } from "@repo/ui/text/heading/heading-h2-bold";
-import { GlassMenuEffect } from "@ui/other/glass-effect";
-import { useCustomer } from "autumn-js/react";
-import { MessageSquareMore, Plus, Puzzle, User, X } from "lucide-react";
-import { AnimatePresence, LayoutGroup, motion } from "motion/react";
-import { useEffect, useState } from "react";
-import { Drawer } from "vaul";
-import { useMobilePanel } from "@/lib/mobile-panel-context";
-import { TOUR_STEP_IDS } from "@/lib/tour-constants";
-import { useChatOpen } from "@/stores";
-import { ProjectSelector } from "./project-selector";
-import { useTour } from "./tour";
-import { AddMemoryExpandedView, AddMemoryView } from "./views/add-memory";
-import { IntegrationsView } from "./views/integrations";
-import { ProfileView } from "./views/profile";
+} from "@repo/lib/queries"
+import { Button } from "@repo/ui/components/button"
+import { ConnectAIModal } from "./connect-ai-modal"
+import { HeadingH2Bold } from "@repo/ui/text/heading/heading-h2-bold"
+import { GlassMenuEffect } from "@ui/other/glass-effect"
+import { useCustomer } from "autumn-js/react"
+import { MessageSquareMore, Plus, Puzzle, User, X } from "lucide-react"
+import { AnimatePresence, LayoutGroup, motion } from "motion/react"
+import { useRouter, useSearchParams } from "next/navigation"
+import { useCallback, useEffect, useState } from "react"
+import { Drawer } from "vaul"
+import { useMobilePanel } from "@/lib/mobile-panel-context"
+import { TOUR_STEP_IDS } from "@/lib/tour-constants"
+import { useChatOpen } from "@/stores"
+import { ProjectSelector } from "./project-selector"
+import { useTour } from "./tour"
+import { AddMemoryExpandedView, AddMemoryView } from "./views/add-memory"
+import { IntegrationsView } from "./views/integrations"
+import { ProfileView } from "./views/profile"
const MCPIcon = ({ className }: { className?: string }) => {
return (
@@ -36,45 +37,67 @@ const MCPIcon = ({ className }: { className?: string }) => {
<path d="M15.688 2.343a2.588 2.588 0 00-3.61 0l-9.626 9.44a.863.863 0 01-1.203 0 .823.823 0 010-1.18l9.626-9.44a4.313 4.313 0 016.016 0 4.116 4.116 0 011.204 3.54 4.3 4.3 0 013.609 1.18l.05.05a4.115 4.115 0 010 5.9l-8.706 8.537a.274.274 0 000 .393l1.788 1.754a.823.823 0 010 1.18.863.863 0 01-1.203 0l-1.788-1.753a1.92 1.92 0 010-2.754l8.706-8.538a2.47 2.47 0 000-3.54l-.05-.049a2.588 2.588 0 00-3.607-.003l-7.172 7.034-.002.002-.098.097a.863.863 0 01-1.204 0 .823.823 0 010-1.18l7.273-7.133a2.47 2.47 0 00-.003-3.537z" />
<path d="M14.485 4.703a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a4.115 4.115 0 000 5.9 4.314 4.314 0 006.016 0l7.12-6.982a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a2.588 2.588 0 01-3.61 0 2.47 2.47 0 010-3.54l7.12-6.982z" />
</svg>
- );
-};
+ )
+}
function Menu({ id }: { id?: string }) {
- const [isHovered, setIsHovered] = useState(false);
+ const router = useRouter()
+ const searchParams = useSearchParams()
+ const openParam = searchParams.get("open")
+
+ // Valid view names that can be opened via URL parameter
+ const validViews = [
+ "addUrl",
+ "mcp",
+ "projects",
+ "profile",
+ "integrations",
+ ] as const
+ type ValidView = (typeof validViews)[number]
+
+ const [isHovered, setIsHovered] = useState(false)
const [expandedView, setExpandedView] = useState<
"addUrl" | "mcp" | "projects" | "profile" | "integrations" | null
- >(null);
- const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
- const [isCollapsing, setIsCollapsing] = useState(false);
- const [showAddMemoryView, setShowAddMemoryView] = useState(false);
- const [showConnectAIModal, setShowConnectAIModal] = useState(false);
- const isMobile = useIsMobile();
- const { activePanel, setActivePanel } = useMobilePanel();
- const { setMenuExpanded } = useTour();
- const autumn = useCustomer();
- const { setIsOpen } = useChatOpen();
+ >(null)
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
+ const [isCollapsing, setIsCollapsing] = useState(false)
+ const [showAddMemoryView, setShowAddMemoryView] = useState(false)
+ const [showConnectAIModal, setShowConnectAIModal] = useState(false)
+ const isMobile = useIsMobile()
+ const { activePanel, setActivePanel } = useMobilePanel()
+ const { setMenuExpanded } = useTour()
+ const autumn = useCustomer()
+ const { setIsOpen } = useChatOpen()
- const { data: memoriesCheck } = fetchMemoriesFeature(autumn);
+ const { data: memoriesCheck } = fetchMemoriesFeature(autumn)
- const memoriesUsed = memoriesCheck?.usage ?? 0;
- const memoriesLimit = memoriesCheck?.included_usage ?? 0;
+ const memoriesUsed = memoriesCheck?.usage ?? 0
+ const memoriesLimit = memoriesCheck?.included_usage ?? 0
- const { data: proCheck } = fetchConsumerProProduct(autumn);
+ const { data: proCheck } = fetchConsumerProProduct(autumn)
useEffect(() => {
if (memoriesCheck) {
- console.log({ memoriesCheck });
+ console.log({ memoriesCheck })
}
if (proCheck) {
- console.log({ proCheck });
+ console.log({ proCheck })
}
- }, [memoriesCheck, proCheck]);
+ }, [memoriesCheck, proCheck])
- const isProUser = proCheck?.allowed ?? false;
+ // Function to clear the 'open' parameter from URL
+ const clearOpenParam = useCallback(() => {
+ const newSearchParams = new URLSearchParams(searchParams.toString())
+ newSearchParams.delete("open")
+ const newUrl = `${window.location.pathname}${newSearchParams.toString() ? `?${newSearchParams.toString()}` : ""}`
+ router.replace(newUrl)
+ }, [searchParams, router])
+
+ const isProUser = proCheck?.allowed ?? false
const shouldShowLimitWarning =
- !isProUser && memoriesUsed >= memoriesLimit * 0.8;
+ !isProUser && memoriesUsed >= memoriesLimit * 0.8
// Map menu item keys to tour IDs
const menuItemTourIds: Record<string, string> = {
@@ -82,7 +105,7 @@ function Menu({ id }: { id?: string }) {
projects: TOUR_STEP_IDS.MENU_PROJECTS,
mcp: TOUR_STEP_IDS.MENU_MCP,
integrations: "", // No tour ID for integrations yet
- };
+ }
const menuItems = [
{
@@ -115,61 +138,102 @@ function Menu({ id }: { id?: string }) {
key: "profile" as const,
disabled: false,
},
- ];
+ ]
const handleMenuItemClick = (
key: "chat" | "addUrl" | "mcp" | "projects" | "profile" | "integrations",
) => {
if (key === "chat") {
- setIsOpen(true);
- setIsMobileMenuOpen(false);
+ setIsOpen(true)
+ setIsMobileMenuOpen(false)
if (isMobile) {
- setActivePanel("chat");
+ setActivePanel("chat")
}
} else if (key === "mcp") {
// Open ConnectAIModal directly for MCP
- setIsMobileMenuOpen(false);
- setExpandedView(null);
- setShowConnectAIModal(true);
+ setIsMobileMenuOpen(false)
+ setExpandedView(null)
+ setShowConnectAIModal(true)
} else {
if (expandedView === key) {
- setIsCollapsing(true);
- setExpandedView(null);
+ setIsCollapsing(true)
+ setExpandedView(null)
} else if (key === "addUrl") {
- setShowAddMemoryView(true);
- setExpandedView(null);
+ setShowAddMemoryView(true)
+ setExpandedView(null)
} else {
- setExpandedView(key);
+ setExpandedView(key)
}
if (isMobile) {
- setActivePanel("menu");
+ setActivePanel("menu")
}
}
- };
+ }
+
+ // Handle initial view opening based on URL parameter
+ useEffect(() => {
+ if (openParam) {
+ if (openParam === "chat") {
+ setIsOpen(true)
+ setIsMobileMenuOpen(false)
+ if (isMobile) {
+ setActivePanel("chat")
+ }
+ } else if (openParam === "mcp") {
+ // Open ConnectAIModal directly for MCP
+ setIsMobileMenuOpen(false)
+ setExpandedView(null)
+ setShowConnectAIModal(true)
+ } else if (openParam === "addUrl") {
+ setShowAddMemoryView(true)
+ setExpandedView(null)
+ if (isMobile) {
+ setIsMobileMenuOpen(true)
+ setActivePanel("menu")
+ }
+ } else if (validViews.includes(openParam as ValidView)) {
+ // For other valid views like "profile", "integrations"
+ setExpandedView(openParam as ValidView)
+ if (isMobile) {
+ setIsMobileMenuOpen(true)
+ setActivePanel("menu")
+ }
+ }
+
+ // Clear the parameter from URL after performing any action
+ clearOpenParam()
+ }
+ }, [
+ openParam,
+ isMobile,
+ setIsOpen,
+ setActivePanel,
+ validViews,
+ clearOpenParam,
+ ])
// Watch for active panel changes on mobile
useEffect(() => {
if (isMobile && activePanel !== "menu" && activePanel !== null) {
// Another panel became active, close the menu
- setIsMobileMenuOpen(false);
- setExpandedView(null);
+ setIsMobileMenuOpen(false)
+ setExpandedView(null)
}
- }, [isMobile, activePanel]);
+ }, [isMobile, activePanel])
// Notify tour provider about expansion state changes
useEffect(() => {
const isExpanded = isMobile
? isMobileMenuOpen || !!expandedView
- : isHovered || !!expandedView;
- setMenuExpanded(isExpanded);
- }, [isMobile, isMobileMenuOpen, isHovered, expandedView, setMenuExpanded]);
+ : isHovered || !!expandedView
+ setMenuExpanded(isExpanded)
+ }, [isMobile, isMobileMenuOpen, isHovered, expandedView, setMenuExpanded])
// Calculate width based on state
- const menuWidth = expandedView || isCollapsing ? 600 : isHovered ? 160 : 56;
+ const menuWidth = expandedView || isCollapsing ? 600 : isHovered ? 160 : 56
// Dynamic z-index for mobile based on active panel
- const mobileZIndex =
- isMobile && activePanel === "menu" ? "z-[70]" : "z-[100]";
+ const mobileZIndex = isMobile && activePanel === "menu" ? "z-[70]" : "z-[100]"
return (
<>
@@ -378,8 +442,8 @@ function Menu({ id }: { id?: string }) {
<Button
className="text-white/70 hover:text-white transition-colors duration-200"
onClick={() => {
- setIsCollapsing(true);
- setExpandedView(null);
+ setIsCollapsing(true)
+ setExpandedView(null)
}}
size="icon"
variant="ghost"
@@ -418,9 +482,9 @@ function Menu({ id }: { id?: string }) {
open={isMobileMenuOpen || !!expandedView}
onOpenChange={(open) => {
if (!open) {
- setIsMobileMenuOpen(false);
- setExpandedView(null);
- setActivePanel(null);
+ setIsMobileMenuOpen(false)
+ setExpandedView(null)
+ setActivePanel(null)
}
}}
>
@@ -433,8 +497,8 @@ function Menu({ id }: { id?: string }) {
className="w-14 h-14 flex items-center justify-center text-white rounded-full shadow-2xl"
initial={{ scale: 0.8, opacity: 0 }}
onClick={() => {
- setIsMobileMenuOpen(true);
- setActivePanel("menu");
+ setIsMobileMenuOpen(true)
+ setActivePanel("menu")
}}
transition={{
duration: 0.3,
@@ -530,13 +594,13 @@ function Menu({ id }: { id?: string }) {
initial={{ opacity: 0, y: 10 }}
layout
onClick={() => {
- handleMenuItemClick(item.key);
+ handleMenuItemClick(item.key)
if (
item.key !== "mcp" &&
item.key !== "profile" &&
item.key !== "integrations"
) {
- setIsMobileMenuOpen(false);
+ setIsMobileMenuOpen(false)
}
}}
type="button"
@@ -600,8 +664,8 @@ function Menu({ id }: { id?: string }) {
<Button
className="text-white/70 hover:text-white transition-colors duration-200"
onClick={() => {
- setIsCollapsing(true);
- setExpandedView(null);
+ setIsCollapsing(true)
+ setExpandedView(null)
}}
size="icon"
variant="ghost"
@@ -643,7 +707,7 @@ function Menu({ id }: { id?: string }) {
<Button className="hidden">Connect AI Assistant</Button>
</ConnectAIModal>
</>
- );
+ )
}
-export default Menu;
+export default Menu
diff --git a/apps/web/components/views/profile.tsx b/apps/web/components/views/profile.tsx
index 336b9416..b19706f7 100644
--- a/apps/web/components/views/profile.tsx
+++ b/apps/web/components/views/profile.tsx
@@ -1,15 +1,15 @@
-"use client";
+"use client"
-import { authClient } from "@lib/auth";
-import { useAuth } from "@lib/auth-context";
+import { authClient } from "@lib/auth"
+import { useAuth } from "@lib/auth-context"
import {
fetchConnectionsFeature,
fetchMemoriesFeature,
fetchSubscriptionStatus,
-} from "@lib/queries";
-import { Button } from "@repo/ui/components/button";
-import { HeadingH3Bold } from "@repo/ui/text/heading/heading-h3-bold";
-import { useCustomer } from "autumn-js/react";
+} from "@lib/queries"
+import { Button } from "@repo/ui/components/button"
+import { HeadingH3Bold } from "@repo/ui/text/heading/heading-h3-bold"
+import { useCustomer } from "autumn-js/react"
import {
CheckCircle,
CreditCard,
@@ -17,27 +17,27 @@ import {
LogOut,
User,
X,
-} from "lucide-react";
-import { motion } from "motion/react";
-import Link from "next/link";
-import { usePathname, useRouter } from "next/navigation";
-import { useState } from "react";
-import { analytics } from "@/lib/analytics";
+} from "lucide-react"
+import { motion } from "motion/react"
+import Link from "next/link"
+import { usePathname, useRouter } from "next/navigation"
+import { useState } from "react"
+import { analytics } from "@/lib/analytics"
export function ProfileView() {
- const router = useRouter();
- const pathname = usePathname();
- const { user: session, org } = useAuth();
- const organizations = org;
- const autumn = useCustomer();
- const [isLoading, setIsLoading] = useState(false);
+ const router = useRouter()
+ const pathname = usePathname()
+ const { user: session, org } = useAuth()
+ const organizations = org
+ const autumn = useCustomer()
+ const [isLoading, setIsLoading] = useState(false)
- const { data: memoriesCheck } = fetchMemoriesFeature(autumn as any);
- const memoriesUsed = memoriesCheck?.usage ?? 0;
- const memoriesLimit = memoriesCheck?.included_usage ?? 0;
+ const { data: memoriesCheck } = fetchMemoriesFeature(autumn as any)
+ const memoriesUsed = memoriesCheck?.usage ?? 0
+ const memoriesLimit = memoriesCheck?.included_usage ?? 0
- const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any);
- const connectionsUsed = connectionsCheck?.usage ?? 0;
+ const { data: connectionsCheck } = fetchConnectionsFeature(autumn as any)
+ const connectionsUsed = connectionsCheck?.usage ?? 0
// Fetch subscription status with React Query
const {
@@ -45,36 +45,36 @@ export function ProfileView() {
consumer_pro: null,
},
isLoading: isCheckingStatus,
- } = fetchSubscriptionStatus(autumn as any);
+ } = fetchSubscriptionStatus(autumn as any)
- const isPro = status.consumer_pro;
+ const isPro = status.consumer_pro
const handleLogout = () => {
- analytics.userSignedOut();
- authClient.signOut();
- router.push("/login");
- };
+ analytics.userSignedOut()
+ authClient.signOut()
+ router.push("/login")
+ }
const handleUpgrade = async () => {
- setIsLoading(true);
+ setIsLoading(true)
try {
await autumn.attach({
productId: "consumer_pro",
successUrl: "https://app.supermemory.ai/",
- });
- window.location.reload();
+ })
+ window.location.reload()
} catch (error) {
- console.error(error);
- setIsLoading(false);
+ console.error(error)
+ setIsLoading(false)
}
- };
+ }
// Handle manage billing
const handleManageBilling = async () => {
await autumn.openBillingPortal({
returnUrl: "https://app.supermemory.ai",
- });
- };
+ })
+ }
if (session?.isAnonymous) {
return (
@@ -99,7 +99,7 @@ export function ProfileView() {
</motion.div>
</motion.div>
</div>
- );
+ )
}
return (
@@ -282,5 +282,5 @@ export function ProfileView() {
Sign Out
</Button>
</div>
- );
+ )
}