Spaces:
Running
Running
| import { useState } from 'react' | |
| import { Outlet, NavLink, useNavigate, Link } from 'react-router-dom' | |
| import { useAuth } from '../context/AuthContext' | |
| import { useTheme } from '../context/ThemeContext' | |
| const NAV_MAIN = [ | |
| { to: '/app/dashboard', icon: 'β', label: 'Dashboard' }, | |
| { to: '/app/assess', icon: 'β', label: 'New Assessment' }, | |
| { to: '/app/history', icon: 'β·', label: 'History' }, | |
| ] | |
| const NAV_WELLNESS = [ | |
| { to: '/app/breathe', icon: 'π«', label: 'Breathe Hub' }, | |
| { to: '/app/gratitude', icon: 'βοΈ', label: 'Gratitude' }, | |
| { to: '/app/todo', icon: 'β ', label: 'Daily To-Do' }, | |
| ] | |
| export default function Layout() { | |
| const { user, logout } = useAuth() | |
| const { theme, toggle } = useTheme() | |
| const navigate = useNavigate() | |
| const [open, setOpen] = useState(false) // mobile sidebar | |
| async function handleLogout() { | |
| await logout() | |
| navigate('/auth') | |
| } | |
| const initials = user?.username?.slice(0, 2).toUpperCase() || 'U' | |
| return ( | |
| <div className="app-shell"> | |
| {/* Mobile top bar */} | |
| <header className="mobile-bar"> | |
| <button className="hamburger" onClick={() => setOpen(o => !o)}>β°</button> | |
| <Link to="/app/breathe" className="mobile-brand-link"> | |
| <img src="/logo.svg" alt="BREATHE" className="mobile-brand-logo" /> | |
| </Link> | |
| <button className="mobile-theme-toggle" onClick={toggle} aria-label="Toggle theme"> | |
| {theme === 'dark' ? 'βοΈ' : 'π'} | |
| </button> | |
| </header> | |
| {/* Backdrop (mobile) */} | |
| {open && <div className="sidebar-backdrop" onClick={() => setOpen(false)} />} | |
| <aside className={`sidebar ${open ? 'sidebar--open' : ''}`}> | |
| <div className="sidebar-top"> | |
| <div className="sidebar-brand"> | |
| <Link to="/app/breathe" className="sidebar-brand-link" onClick={() => setOpen(false)}> | |
| <img src="/logo.svg" alt="BREATHE" className="brand-logo" /> | |
| <div className="brand-sub">Stress Intelligence</div> | |
| </Link> | |
| </div> | |
| <nav className="sidebar-nav"> | |
| <div className="nav-section-label">Overview</div> | |
| {NAV_MAIN.map(n => ( | |
| <NavLink | |
| key={n.to} | |
| to={n.to} | |
| className={({ isActive }) => `nav-link ${isActive ? 'nav-link--active' : ''}`} | |
| onClick={() => setOpen(false)} | |
| > | |
| <span className="nav-icon">{n.icon}</span> | |
| <span>{n.label}</span> | |
| </NavLink> | |
| ))} | |
| <div className="nav-section-label" style={{ marginTop: '16px' }}>Wellness</div> | |
| {NAV_WELLNESS.map(n => ( | |
| <NavLink | |
| key={n.to} | |
| to={n.to} | |
| className={({ isActive }) => `nav-link ${isActive ? 'nav-link--active' : ''}`} | |
| onClick={() => setOpen(false)} | |
| > | |
| <span className="nav-icon">{n.icon}</span> | |
| <span>{n.label}</span> | |
| </NavLink> | |
| ))} | |
| </nav> | |
| </div> | |
| <div className="sidebar-bottom"> | |
| <div className="user-chip"> | |
| <Link to="/app/profile" className="user-chip-link" onClick={() => setOpen(false)}> | |
| {user?.avatar | |
| ? <img src={user.avatar} alt="avatar" className="user-avatar user-avatar--img" /> | |
| : <div className="user-avatar">{initials}</div> | |
| } | |
| <div className="user-meta"> | |
| <div className="user-name">{user?.display_name || user?.username}</div> | |
| <div className="user-email">{user?.email}</div> | |
| </div> | |
| </Link> | |
| </div> | |
| <button className="theme-toggle" onClick={toggle}> | |
| <span className="theme-toggle-icon">{theme === 'dark' ? 'βοΈ' : 'π'}</span> | |
| {theme === 'dark' ? 'Light mode' : 'Dark mode'} | |
| </button> | |
| <button className="logout-btn" onClick={handleLogout}> | |
| β© Log out | |
| </button> | |
| </div> | |
| </aside> | |
| <main className="main-area"> | |
| <Outlet /> | |
| </main> | |
| </div> | |
| ) | |
| } | |