130 lines
4.6 KiB
React
130 lines
4.6 KiB
React
import React, { useState, useRef, useEffect } from 'react';
|
|
import { Menu, Search, Bell, User as UserIcon, LogOut, ShieldCheck, ChevronDown } from 'lucide-react';
|
|
import { useAuth } from '../../context/AuthContext';
|
|
import { ThemeToggle } from './ThemeToggle';
|
|
import './layout.css';
|
|
|
|
export function Header({ setMobileOpen }) {
|
|
const { user, logout, isDemoMode } = useAuth();
|
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
const dropdownRef = useRef(null);
|
|
|
|
// Close dropdown on click outside
|
|
useEffect(() => {
|
|
function handleClickOutside(event) {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
setDropdownOpen(false);
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
return (
|
|
<header className="dashboard-header">
|
|
{/* Left: Mobile hamburger & Search */}
|
|
<div className="header-left">
|
|
<button
|
|
className="mobile-menu-btn"
|
|
onClick={() => setMobileOpen(true)}
|
|
aria-label="Open navigation menu"
|
|
>
|
|
<Menu size={22} />
|
|
</button>
|
|
|
|
<div className="header-search">
|
|
<Search size={18} className="header-search-icon" />
|
|
<input
|
|
type="text"
|
|
className="header-search-input"
|
|
placeholder="Search workspaces, logs, users..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right: Actions, Theme, User */}
|
|
<div className="header-right">
|
|
{/* Backend Connectivity Status Badge */}
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '6px',
|
|
fontSize: '0.8rem',
|
|
padding: '4px 10px',
|
|
borderRadius: 'var(--radius-full)',
|
|
background: isDemoMode ? 'var(--color-warning-bg)' : 'var(--color-success-bg)',
|
|
color: isDemoMode ? 'var(--color-warning)' : 'var(--color-success)',
|
|
border: `1px solid ${isDemoMode ? 'rgba(245, 158, 11, 0.3)' : 'rgba(16, 185, 129, 0.3)'}`,
|
|
}}
|
|
title={isDemoMode ? 'Running in Standalone Demo Mode' : 'Connected to Spring Boot API'}
|
|
>
|
|
<span
|
|
style={{
|
|
width: 7,
|
|
height: 7,
|
|
borderRadius: '50%',
|
|
backgroundColor: isDemoMode ? 'var(--color-warning)' : 'var(--color-success)',
|
|
}}
|
|
/>
|
|
<span>{isDemoMode ? 'Demo Sandbox' : 'Backend Live'}</span>
|
|
</div>
|
|
|
|
{/* Theme Switcher */}
|
|
<ThemeToggle />
|
|
|
|
{/* Notifications */}
|
|
<button className="header-btn" title="Recent security alerts">
|
|
<Bell size={18} />
|
|
<span className="header-badge-dot" />
|
|
</button>
|
|
|
|
{/* User Dropdown */}
|
|
<div className="profile-dropdown-wrapper" ref={dropdownRef}>
|
|
<button
|
|
className="profile-trigger-btn"
|
|
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
aria-expanded={dropdownOpen}
|
|
>
|
|
<div className="sidebar-user-avatar" style={{ width: 30, height: 30, fontSize: '0.75rem' }}>
|
|
{(user?.firstName?.[0] || 'A')}
|
|
</div>
|
|
<span style={{ fontSize: '0.85rem', fontWeight: 600 }}>
|
|
{user?.firstName || 'User'}
|
|
</span>
|
|
<ChevronDown size={14} color="var(--text-muted)" />
|
|
</button>
|
|
|
|
{dropdownOpen && (
|
|
<div className="profile-dropdown-menu animate-fade-in">
|
|
<div style={{ padding: '8px 12px', borderBottom: '1px solid var(--border-subtle)' }}>
|
|
<p style={{ fontSize: '0.825rem', fontWeight: 700 }}>{user?.fullname || user?.firstName}</p>
|
|
<p style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{user?.email}</p>
|
|
</div>
|
|
|
|
<div className="dropdown-item" onClick={() => setDropdownOpen(false)}>
|
|
<UserIcon size={16} />
|
|
<span>Profile Settings</span>
|
|
</div>
|
|
<div className="dropdown-item" onClick={() => setDropdownOpen(false)}>
|
|
<ShieldCheck size={16} />
|
|
<span>Security Token Info</span>
|
|
</div>
|
|
<div
|
|
className="dropdown-item danger"
|
|
onClick={() => {
|
|
setDropdownOpen(false);
|
|
logout();
|
|
}}
|
|
>
|
|
<LogOut size={16} />
|
|
<span>Sign Out</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|