Spaces:
Sleeping
Sleeping
import React, { useState, useEffect } from 'react'; | |
import { Toaster } from 'react-hot-toast'; | |
import ChatInterface from './components/ChatInterface'; | |
import Sidebar from './components/Sidebar'; | |
import WelcomeScreen from './components/WelcomeScreen'; | |
import { SunIcon, MoonIcon, HomeIcon, Bars3Icon } from '@heroicons/react/24/outline'; | |
import ConversationStorage from './utils/conversationStorage'; | |
function App() { | |
const [darkMode, setDarkMode] = useState(false); | |
const [sidebarOpen, setSidebarOpen] = useState(false); | |
const [chatStarted, setChatStarted] = useState(false); | |
const [conversations, setConversations] = useState([]); | |
const [activeConversationId, setActiveConversationId] = useState(null); | |
useEffect(() => { | |
// Check for saved theme preference or default to light mode | |
const savedTheme = localStorage.getItem('theme'); | |
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { | |
setDarkMode(true); | |
document.documentElement.classList.add('dark'); | |
} | |
// Load conversations from localStorage | |
const savedConversations = ConversationStorage.loadConversations(); | |
if (savedConversations.length > 0) { | |
setConversations(savedConversations); | |
setChatStarted(true); | |
// Set the most recent conversation as active | |
setActiveConversationId(savedConversations[0].id); | |
} | |
}, []); | |
const toggleDarkMode = () => { | |
setDarkMode(!darkMode); | |
if (darkMode) { | |
document.documentElement.classList.remove('dark'); | |
localStorage.setItem('theme', 'light'); | |
} else { | |
document.documentElement.classList.add('dark'); | |
localStorage.setItem('theme', 'dark'); | |
} | |
}; | |
const startNewChat = () => { | |
const newConversation = { | |
id: Date.now().toString(), | |
title: 'New Conversation', | |
messages: [], | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
}; | |
// Save to localStorage | |
ConversationStorage.addConversation(newConversation); | |
setConversations(prev => [newConversation, ...prev]); | |
setActiveConversationId(newConversation.id); | |
setChatStarted(true); | |
setSidebarOpen(false); | |
}; | |
const deleteConversation = (conversationId) => { | |
// Delete from localStorage | |
ConversationStorage.deleteConversation(conversationId); | |
setConversations(prev => prev.filter(conv => conv.id !== conversationId)); | |
// If the deleted conversation was active, switch to another one | |
if (activeConversationId === conversationId) { | |
const remainingConversations = conversations.filter(conv => conv.id !== conversationId); | |
if (remainingConversations.length > 0) { | |
setActiveConversationId(remainingConversations[0].id); | |
} else { | |
setActiveConversationId(null); | |
setChatStarted(false); | |
} | |
} | |
}; | |
const updateConversations = (updatedConversations) => { | |
setConversations(updatedConversations); | |
// Save to localStorage | |
ConversationStorage.saveConversations(updatedConversations); | |
}; | |
const handleFirstMessage = (message) => { | |
if (!chatStarted) { | |
startNewChat(); | |
} | |
}; | |
const goBackToHome = () => { | |
setActiveConversationId(null); | |
setChatStarted(false); | |
setSidebarOpen(false); | |
}; | |
return ( | |
<div className={`min-h-screen transition-colors duration-200 ${ | |
darkMode | |
? 'bg-gray-900 text-white' | |
: 'bg-gray-50 text-gray-900' | |
}`}> | |
{/* Header - Mobile optimized */} | |
<header className={`fixed top-0 left-0 right-0 z-50 ${ | |
darkMode | |
? 'bg-gray-800/95 border-gray-700' | |
: 'bg-white/95 border-gray-200' | |
} backdrop-blur-sm border-b shadow-sm`}> | |
<div className="flex items-center justify-between px-3 md:px-4 py-3 md:py-3"> | |
{/* Left side - Menu and Title */} | |
<div className="flex items-center space-x-2 md:space-x-4 flex-1 min-w-0"> | |
{/* Hamburger Menu Button - Larger touch target */} | |
<button | |
onClick={() => setSidebarOpen(!sidebarOpen)} | |
className={`p-2 md:p-2 rounded-lg md:rounded-xl transition-all duration-200 touch-manipulation ${ | |
darkMode | |
? 'hover:bg-gray-700 active:bg-gray-600 text-gray-300 hover:text-white' | |
: 'hover:bg-gray-100 active:bg-gray-200 text-gray-600 hover:text-gray-900' | |
} min-w-[44px] min-h-[44px] md:min-w-[40px] md:min-h-[40px] flex items-center justify-center`} | |
title="Toggle menu" | |
> | |
<Bars3Icon className="w-6 h-6 md:w-5 md:h-5" /> | |
</button> | |
{/* App Title - Mobile optimized */} | |
<button | |
onClick={goBackToHome} | |
className="flex-1 min-w-0 text-left group" | |
> | |
<h1 className="text-lg md:text-xl font-bold gradient-text hover:opacity-80 transition-opacity truncate"> | |
CA Study Assistant | |
</h1> | |
{/* Subtitle for larger screens */} | |
<p className={`text-xs hidden md:block ${ | |
darkMode ? 'text-gray-400' : 'text-gray-500' | |
}`}> | |
AI-powered study companion | |
</p> | |
</button> | |
</div> | |
{/* Right side - Action buttons */} | |
<div className="flex items-center space-x-1 md:space-x-2 flex-shrink-0"> | |
{/* Home Button - Only show in chat mode */} | |
{chatStarted && ( | |
<button | |
onClick={goBackToHome} | |
className={`p-2 md:p-2 rounded-lg md:rounded-xl transition-all duration-200 touch-manipulation ${ | |
darkMode | |
? 'hover:bg-gray-700 active:bg-gray-600 text-gray-300 hover:text-white' | |
: 'hover:bg-gray-100 active:bg-gray-200 text-gray-600 hover:text-gray-900' | |
} min-w-[44px] min-h-[44px] md:min-w-[40px] md:min-h-[40px] flex items-center justify-center`} | |
title="Back to Home" | |
> | |
<HomeIcon className="w-6 h-6 md:w-5 md:h-5" /> | |
</button> | |
)} | |
{/* Dark Mode Toggle - Larger touch target */} | |
<button | |
onClick={toggleDarkMode} | |
className={`p-2 md:p-2 rounded-lg md:rounded-xl transition-all duration-200 touch-manipulation ${ | |
darkMode | |
? 'hover:bg-gray-700 active:bg-gray-600 text-gray-300 hover:text-yellow-400' | |
: 'hover:bg-gray-100 active:bg-gray-200 text-gray-600 hover:text-yellow-600' | |
} min-w-[44px] min-h-[44px] md:min-w-[40px] md:min-h-[40px] flex items-center justify-center`} | |
title={darkMode ? 'Switch to light mode' : 'Switch to dark mode'} | |
> | |
{darkMode ? ( | |
<SunIcon className="w-6 h-6 md:w-5 md:h-5" /> | |
) : ( | |
<MoonIcon className="w-6 h-6 md:w-5 md:h-5" /> | |
)} | |
</button> | |
{/* Conversation count badge - Mobile optimized */} | |
{conversations.length > 0 && ( | |
<div className={`hidden sm:flex items-center px-2 md:px-3 py-1 md:py-1.5 rounded-full text-xs md:text-sm font-medium ${ | |
darkMode | |
? 'bg-primary-900/30 text-primary-300 border border-primary-700/50' | |
: 'bg-primary-50 text-primary-700 border border-primary-200' | |
}`}> | |
{conversations.length} chat{conversations.length !== 1 ? 's' : ''} | |
</div> | |
)} | |
</div> | |
</div> | |
{/* Mobile conversation indicator - Shows only on small screens */} | |
{conversations.length > 0 && ( | |
<div className="sm:hidden px-3 pb-2"> | |
<div className={`flex items-center justify-center px-3 py-1 rounded-full text-xs font-medium ${ | |
darkMode | |
? 'bg-primary-900/30 text-primary-300 border border-primary-700/50' | |
: 'bg-primary-50 text-primary-700 border border-primary-200' | |
}`}> | |
📚 {conversations.length} conversation{conversations.length !== 1 ? 's' : ''} saved | |
</div> | |
</div> | |
)} | |
</header> | |
{/* Sidebar */} | |
<Sidebar | |
open={sidebarOpen} | |
onClose={() => setSidebarOpen(false)} | |
conversations={conversations} | |
activeConversationId={activeConversationId} | |
onConversationSelect={setActiveConversationId} | |
onNewChat={startNewChat} | |
onDeleteConversation={deleteConversation} | |
onBackToHome={goBackToHome} | |
darkMode={darkMode} | |
/> | |
{/* Main Content - Mobile optimized */} | |
<main className={`transition-all duration-200 ${ | |
sidebarOpen ? 'md:ml-80' : 'ml-0' | |
} pt-16 md:pt-20 min-h-screen`}> | |
{chatStarted ? ( | |
<ChatInterface | |
conversationId={activeConversationId} | |
conversations={conversations} | |
setConversations={updateConversations} | |
darkMode={darkMode} | |
/> | |
) : ( | |
<WelcomeScreen | |
onStartChat={handleFirstMessage} | |
onNewChat={startNewChat} | |
darkMode={darkMode} | |
/> | |
)} | |
</main> | |
{/* Toast notifications - Mobile optimized */} | |
<Toaster | |
position="top-center" | |
containerStyle={{ | |
top: '80px', // Account for header height | |
}} | |
toastOptions={{ | |
duration: 4000, | |
style: { | |
background: darkMode ? '#374151' : '#ffffff', | |
color: darkMode ? '#f9fafb' : '#111827', | |
border: darkMode ? '1px solid #4b5563' : '1px solid #e5e7eb', | |
borderRadius: '12px', | |
padding: '12px 16px', | |
fontSize: '14px', | |
maxWidth: '90vw', | |
wordBreak: 'break-word', | |
}, | |
success: { | |
iconTheme: { | |
primary: '#10b981', | |
secondary: '#ffffff', | |
}, | |
}, | |
error: { | |
iconTheme: { | |
primary: '#ef4444', | |
secondary: '#ffffff', | |
}, | |
}, | |
}} | |
/> | |
{/* Global loading overlay for mobile (if needed) */} | |
{/* This can be used for app-wide loading states */} | |
{/* Safe area spacing for mobile devices */} | |
<style jsx global>{` | |
@supports (padding: max(0px)) { | |
.pb-safe { | |
padding-bottom: max(env(safe-area-inset-bottom), 1rem); | |
} | |
} | |
/* Prevent zoom on double-tap for iOS */ | |
button, input, select, textarea { | |
touch-action: manipulation; | |
} | |
/* Improve scrolling on mobile */ | |
* { | |
-webkit-overflow-scrolling: touch; | |
} | |
/* Custom gradient text */ | |
.gradient-text { | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
background-clip: text; | |
} | |
/* Mobile-specific touch improvements */ | |
@media (max-width: 768px) { | |
/* Increase minimum touch target size */ | |
button, [role="button"] { | |
min-height: 44px; | |
min-width: 44px; | |
} | |
/* Reduce motion for users who prefer it */ | |
@media (prefers-reduced-motion: reduce) { | |
* { | |
animation-duration: 0.01ms ; | |
animation-iteration-count: 1 ; | |
transition-duration: 0.01ms ; | |
} | |
} | |
} | |
`}</style> | |
</div> | |
); | |
} | |
export default App; |