App.tsx (3266B)
1 import { useEffect, useCallback, useState } from "react"; 2 import { useAuthStore } from "./store/auth"; 3 import { useThemeStore } from "./store/theme"; 4 import { Login } from "./components/Login/Login"; 5 import { Setup } from "./components/Setup/Setup"; 6 import { ChatView } from "./components/Chat/ChatView"; 7 import { Sidebar } from "./components/Sidebar/Sidebar"; 8 import { SearchModal } from "./components/Search/SearchModal"; 9 import { ThemeToggle } from "./components/Theme/ThemeToggle"; 10 11 export function App() { 12 const { authenticated, configured, loading, checkStatus } = useAuthStore(); 13 const { theme } = useThemeStore(); 14 const [searchOpen, setSearchOpen] = useState(false); 15 const [sidebarOpen, setSidebarOpen] = useState(false); 16 17 useEffect(() => { 18 checkStatus(); 19 }, [checkStatus]); 20 21 const toggleSearch = useCallback(() => { 22 setSearchOpen((prev) => !prev); 23 }, []); 24 25 const closeSearch = useCallback(() => { 26 setSearchOpen(false); 27 }, []); 28 29 const toggleSidebar = useCallback(() => { 30 setSidebarOpen((prev) => !prev); 31 }, []); 32 33 const closeSidebar = useCallback(() => { 34 setSidebarOpen(false); 35 }, []); 36 37 const handleSelectSession = useCallback((_sessionId: string) => { 38 setSidebarOpen(false); 39 }, []); 40 41 useEffect(() => { 42 const handleKeyDown = (e: KeyboardEvent) => { 43 if ((e.ctrlKey || e.metaKey) && e.key === "k") { 44 e.preventDefault(); 45 toggleSearch(); 46 } 47 }; 48 document.addEventListener("keydown", handleKeyDown); 49 return () => document.removeEventListener("keydown", handleKeyDown); 50 }, [toggleSearch]); 51 52 const bgClass = theme === "dark" ? "bg-gray-950" : "bg-gray-100"; 53 const textClass = theme === "dark" ? "text-gray-100" : "text-gray-900"; 54 55 if (loading) { 56 return ( 57 <div className={`flex h-screen items-center justify-center ${bgClass} ${textClass}`}> 58 Loading... 59 </div> 60 ); 61 } 62 63 if (!configured) { 64 return <Setup />; 65 } 66 67 if (!authenticated) { 68 return <Login />; 69 } 70 71 return ( 72 <div className={`flex h-screen overflow-hidden ${bgClass} ${textClass}`}> 73 {/* Mobile hamburger */} 74 <button 75 type="button" 76 onClick={toggleSidebar} 77 className="fixed left-4 top-4 z-30 rounded-lg bg-gray-800 p-2 text-gray-300 hover:bg-gray-700 md:hidden" 78 aria-label="Toggle sidebar" 79 > 80 ☰ 81 </button> 82 83 {/* Sidebar */} 84 <div 85 className={`fixed inset-y-0 left-0 z-20 w-64 transition-transform duration-200 md:translate-x-0 ${ 86 sidebarOpen ? "translate-x-0" : "-translate-x-full" 87 }`} 88 > 89 <Sidebar onSelectSession={handleSelectSession} /> 90 </div> 91 92 {/* Mobile overlay */} 93 {sidebarOpen && ( 94 <div 95 className="fixed inset-0 z-10 bg-black/50 md:hidden" 96 onClick={closeSidebar} 97 aria-hidden="true" 98 /> 99 )} 100 101 {/* Main content */} 102 <main className="flex min-w-0 min-h-0 flex-1 flex-col md:ml-64"> 103 <div className="flex items-center justify-end p-2"> 104 <ThemeToggle /> 105 </div> 106 <ChatView /> 107 </main> 108 109 <SearchModal 110 isOpen={searchOpen} 111 onClose={closeSearch} 112 onSelectSession={handleSelectSession} 113 /> 114 </div> 115 ); 116 }