diff options
Diffstat (limited to 'apps/web/components/menu.tsx')
| -rw-r--r-- | apps/web/components/menu.tsx | 211 |
1 files changed, 106 insertions, 105 deletions
diff --git a/apps/web/components/menu.tsx b/apps/web/components/menu.tsx index db012ab7..1d42c8d8 100644 --- a/apps/web/components/menu.tsx +++ b/apps/web/components/menu.tsx @@ -1,28 +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 { 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" +} from "@repo/lib/queries"; +import { Button } from "@repo/ui/components/button"; +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 { ConnectAIModal } from "./connect-ai-modal"; +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 ( @@ -37,13 +37,13 @@ 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 router = useRouter() - const searchParams = useSearchParams() - const openParam = searchParams.get("open") + const router = useRouter(); + const searchParams = useSearchParams(); + const openParam = searchParams.get("open"); // Valid view names that can be opened via URL parameter const validViews = [ @@ -52,52 +52,52 @@ function Menu({ id }: { id?: string }) { "projects", "profile", "integrations", - ] as const - type ValidView = (typeof validViews)[number] + ] as const; + type ValidView = (typeof validViews)[number]; - const [isHovered, setIsHovered] = useState(false) + 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]); // 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 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 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> = { @@ -105,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 = [ { @@ -138,70 +138,70 @@ 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) + setIsOpen(true); + setIsMobileMenuOpen(false); if (isMobile) { - setActivePanel("chat") + setActivePanel("chat"); } } else if (openParam === "mcp") { // Open ConnectAIModal directly for MCP - setIsMobileMenuOpen(false) - setExpandedView(null) - setShowConnectAIModal(true) + setIsMobileMenuOpen(false); + setExpandedView(null); + setShowConnectAIModal(true); } else if (openParam === "addUrl") { - setShowAddMemoryView(true) - setExpandedView(null) + setShowAddMemoryView(true); + setExpandedView(null); if (isMobile) { - setIsMobileMenuOpen(true) - setActivePanel("menu") + setIsMobileMenuOpen(true); + setActivePanel("menu"); } } else if (validViews.includes(openParam as ValidView)) { // For other valid views like "profile", "integrations" - setExpandedView(openParam as ValidView) + setExpandedView(openParam as ValidView); if (isMobile) { - setIsMobileMenuOpen(true) - setActivePanel("menu") + setIsMobileMenuOpen(true); + setActivePanel("menu"); } } // Clear the parameter from URL after performing any action - clearOpenParam() + clearOpenParam(); } }, [ openParam, @@ -210,30 +210,31 @@ function Menu({ id }: { id?: string }) { 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 ( <> @@ -442,8 +443,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" @@ -479,14 +480,14 @@ function Menu({ id }: { id?: string }) { {/* Mobile Menu with Vaul Drawer */} {isMobile && ( <Drawer.Root - open={isMobileMenuOpen || !!expandedView} onOpenChange={(open) => { if (!open) { - setIsMobileMenuOpen(false) - setExpandedView(null) - setActivePanel(null) + setIsMobileMenuOpen(false); + setExpandedView(null); + setActivePanel(null); } }} + open={isMobileMenuOpen || !!expandedView} > {/* Menu Trigger Button */} {!isMobileMenuOpen && !expandedView && ( @@ -497,8 +498,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, @@ -594,13 +595,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" @@ -664,8 +665,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" @@ -707,7 +708,7 @@ function Menu({ id }: { id?: string }) { <Button className="hidden">Connect AI Assistant</Button> </ConnectAIModal> </> - ) + ); } -export default Menu +export default Menu; |