- Sidebar: collapsible groups (Основное/Цены/Сервис/Управление/Настройки/Разработчикам) with chevron headers, state persisted in localStorage 'sidebarGroups' - Sidebar: Избранное group at top, star icon on nav item hover to add/remove favorites, stored in localStorage 'sidebarFavorites' - Sidebar: active route auto-opens its group on navigation - SettingsPage: 'Скрыть чат' toggle in Внешний вид section, stored in localStorage 'chatHidden' - AppLayout: conditionally renders ChatWidget based on chatHidden, listens for storage events for reactive updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import { Outlet, Navigate } from 'react-router-dom'
|
|
import { useState, useEffect } from 'react'
|
|
import { Sidebar } from '../components/layout/Sidebar'
|
|
import { Topbar } from '../components/layout/Topbar'
|
|
import { ChatWidget } from '../components/chat/ChatWidget'
|
|
import { useAuth } from '../contexts/AuthContext'
|
|
|
|
export function AppLayout() {
|
|
const { user } = useAuth()
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
const [chatHidden, setChatHidden] = useState(
|
|
() => localStorage.getItem('chatHidden') === 'true',
|
|
)
|
|
|
|
useEffect(() => {
|
|
const handleStorage = () => {
|
|
setChatHidden(localStorage.getItem('chatHidden') === 'true')
|
|
}
|
|
window.addEventListener('storage', handleStorage)
|
|
return () => window.removeEventListener('storage', handleStorage)
|
|
}, [])
|
|
|
|
if (!user) return <Navigate to="/login" replace />
|
|
|
|
return (
|
|
<div className="flex h-screen bg-slate-50 dark:bg-slate-900 overflow-hidden">
|
|
<Sidebar open={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
|
|
|
<div className="flex-1 flex flex-col min-w-0">
|
|
<Topbar onMenuToggle={() => setSidebarOpen(v => !v)} />
|
|
<main className="flex-1 overflow-auto">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
|
|
{!chatHidden && <ChatWidget />}
|
|
</div>
|
|
)
|
|
}
|