| import React, { useState, useEffect } from 'react'; |
| import { useDispatch, useSelector } from 'react-redux'; |
| import { useNavigate } from 'react-router-dom'; |
| import { logoutUser } from '../../store/reducers/authSlice'; |
| import './Header.css'; |
|
|
| const Header = ({ onMenuToggle, isMenuOpen: propIsMenuOpen, isMobile = false }) => { |
| const dispatch = useDispatch(); |
| const navigate = useNavigate(); |
| const { user } = useSelector(state => state.auth); |
| |
| const isMenuOpen = propIsMenuOpen; |
| |
|
|
| |
| |
| const [isLoading, setIsLoading] = useState(false); |
|
|
| const handleLogout = async () => { |
| setIsLoading(true); |
| try { |
| await dispatch(logoutUser()).unwrap(); |
| navigate('/login'); |
| } catch (error) { |
| console.error('Logout failed:', error); |
| } finally { |
| setIsLoading(false); |
| } |
| }; |
|
|
| const toggleMenu = () => { |
| |
| if (onMenuToggle) { |
| onMenuToggle(!isMenuOpen); |
| } |
| }; |
|
|
| |
|
|
| |
| useEffect(() => { |
| const handleClickOutside = (event) => { |
| if (isMenuOpen && event.target.closest('.header-menu') === null) { |
| |
| if (onMenuToggle) { |
| onMenuToggle(false); |
| } |
| } |
| }; |
|
|
| document.addEventListener('mousedown', handleClickOutside); |
| return () => { |
| document.removeEventListener('mousedown', handleClickOutside); |
| }; |
| }, [isMenuOpen, onMenuToggle]); |
|
|
| |
| useEffect(() => { |
| const handleGlobalKeyDown = (e) => { |
| |
| if (e.key === 'Escape' && isMenuOpen) { |
| |
| if (onMenuToggle) { |
| onMenuToggle(false); |
| } |
| } |
| }; |
|
|
| document.addEventListener('keydown', handleGlobalKeyDown); |
| return () => document.removeEventListener('keydown', handleGlobalKeyDown); |
| }, [isMenuOpen, onMenuToggle]); |
|
|
| const handleNavigation = (path) => { |
| console.log("Attempting to navigate to:", path); |
| navigate(path); |
| console.log("Navigation called, now closing menu"); |
| |
| if (onMenuToggle) { |
| onMenuToggle(false); |
| } |
| }; |
|
|
| return ( |
| <header className="header fixed top-0 left-0 right-0 z-50 bg-white/90 backdrop-blur-md border-b border-gray-200/50 shadow-sm animate-fade-down"> |
| <div className="header-content max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| {/* Desktop layout - flex for better control */} |
| <div className="hidden lg:flex items-center justify-between h-16"> |
| {/* Logo and App Title - Aligned to the left */} |
| <div className="flex items-center space-x-3"> |
| <div className="flex-shrink-0"> |
| <div className="w-10 h-10 bg-gradient-to-br from-primary-600 to-primary-800 rounded-lg flex items-center justify-center transform transition-transform hover:scale-105 active:scale-95 touch-manipulation"> |
| <span className="text-white font-bold text-xl">L</span> |
| </div> |
| </div> |
| <h1 className="app-title text-display text-2xl font-bold text-gray-900 tracking-tight whitespace-nowrap"> |
| Lin |
| </h1> |
| </div> |
| |
| {/* Desktop Navigation - Empty for now */} |
| <div className="flex-1 flex items-center justify-center"> |
| {/* Navigation items can go here if needed */} |
| </div> |
| |
| {/* User Profile and Logout - Aligned to the far right */} |
| {user && ( |
| <div className="flex items-center space-x-4"> |
| <div className="flex items-center space-x-3"> |
| <div className="w-10 h-10 bg-gradient-to-br from-accent-400 to-accent-600 rounded-full flex items-center justify-center"> |
| <span className="text-white font-medium text-sm"> |
| {user.email ? user.email.charAt(0).toUpperCase() : 'U'} |
| </span> |
| </div> |
| <div className="hidden sm:block"> |
| <p className="text-sm font-medium text-gray-900"> |
| Welcome back |
| </p> |
| <p className="text-xs text-gray-500 truncate max-w-[150px]"> |
| {user.email || 'user@example.com'} |
| </p> |
| </div> |
| <button |
| onClick={handleLogout} |
| disabled={isLoading} |
| className="btn btn-primary flex items-center space-x-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed touch-manipulation active:scale-95" |
| aria-label="Logout" |
| aria-busy={isLoading} |
| > |
| {isLoading ? ( |
| <> |
| <svg className="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
| <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> |
| <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
| </svg> |
| <span className="text-sm">Logging out...</span> |
| </> |
| ) : ( |
| <> |
| <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> |
| </svg> |
| <span className="text-sm">Logout</span> |
| </> |
| )} |
| </button> |
| </div> |
| </div> |
| )} |
| </div> |
| |
| {} |
| <div className="lg:hidden grid grid-cols-[1fr_auto] items-center h-16 gap-4"> |
| {} |
| <div className="flex items-center space-x-3"> |
| <div className="flex-shrink-0"> |
| <div className="w-10 h-10 bg-gradient-to-br from-primary-600 to-primary-800 rounded-lg flex items-center justify-center transform transition-transform hover:scale-105 active:scale-95 touch-manipulation"> |
| <span className="text-white font-bold text-xl">L</span> |
| </div> |
| </div> |
| <h1 className="app-title text-display text-2xl font-bold text-gray-900 tracking-tight whitespace-nowrap"> |
| Lin |
| </h1> |
| </div> |
| |
| {} |
| <div className="flex items-center justify-self-end space-x-2"> |
| {user ? ( |
| <div className="flex items-center space-x-2"> |
| <div className="w-8 h-8 bg-gradient-to-br from-accent-400 to-accent-600 rounded-full flex items-center justify-center"> |
| <span className="text-white font-medium text-xs"> |
| {user.email ? user.email.charAt(0).toUpperCase() : 'U'} |
| </span> |
| </div> |
| <button |
| onClick={toggleMenu} |
| className="mobile-menu-button inline-flex items-center justify-center p-2.5 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 transition-colors duration-200 touch-manipulation active:scale-95" |
| aria-expanded={isMenuOpen} |
| aria-label="Open user menu" |
| aria-controls="mobile-menu" |
| > |
| <span className={`hamburger-line block w-5 h-0.5 bg-current transition-all duration-300 ease-in-out ${isMenuOpen ? 'rotate-45 translate-y-1.5' : ''}`}></span> |
| <span className={`hamburger-line block w-5 h-0.5 bg-current transition-all duration-300 ease-in-out my-1 ${isMenuOpen ? 'opacity-0' : ''}`}></span> |
| <span className={`hamburger-line block w-5 h-0.5 bg-current transition-all duration-300 ease-in-out ${isMenuOpen ? '-rotate-45 -translate-y-1.5' : ''}`}></span> |
| </button> |
| </div> |
| ) : ( |
| |
| <button |
| onClick={toggleMenu} |
| className="mobile-menu-button inline-flex items-center justify-center p-2.5 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 transition-colors duration-200 touch-manipulation active:scale-95" |
| aria-expanded={isMenuOpen} |
| aria-label="Open navigation menu" |
| aria-controls="mobile-menu" |
| > |
| <span className={`hamburger-line block w-6 h-0.5 bg-current transition-all duration-300 ease-in-out ${isMenuOpen ? 'rotate-45 translate-y-1.5' : ''}`}></span> |
| <span className={`hamburger-line block w-6 h-0.5 bg-current transition-all duration-300 ease-in-out my-1 ${isMenuOpen ? 'opacity-0' : ''}`}></span> |
| <span className={`hamburger-line block w-6 h-0.5 bg-current transition-all duration-300 ease-in-out ${isMenuOpen ? '-rotate-45 -translate-y-1.5' : ''}`}></span> |
| </button> |
| )} |
| </div> |
| </div> {/* End of mobile grid layout */} |
|
|
| {} |
| {user && isMenuOpen && ( |
| <div className="lg:hidden fixed inset-0 z-50 bg-black bg-opacity-50 header-menu" id="mobile-menu"> |
| <div className="fixed inset-y-0 right-0 max-w-full flex"> |
| <div className="relative w-64 bg-white shadow-xl mobile-menu-content"> |
| <div className="flex flex-col min-h-screen"> |
| {/* Mobile menu header */} |
| <div className="flex items-center justify-between px-4 py-3 border-b"> |
| <div className="flex items-center space-x-3"> |
| <div className="w-10 h-10 bg-gradient-to-br from-accent-400 to-accent-600 rounded-full flex items-center justify-center"> |
| <span className="text-white font-medium text-sm"> |
| {user.email ? user.email.charAt(0).toUpperCase() : 'U'} |
| </span> |
| </div> |
| <div> |
| <p className="text-sm font-semibold text-gray-900"> |
| {user.name || 'User'} |
| </p> |
| <p className="text-xs text-gray-500"> |
| {user.email || 'user@example.com'} |
| </p> |
| </div> |
| </div> |
| <button |
| onClick={toggleMenu} |
| className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500" |
| aria-label="Close menu" |
| > |
| <svg className="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> |
| </svg> |
| </button> |
| </div> |
| |
| {/* Mobile menu body - flex-1 to take available space */} |
| <div className="flex-1 overflow-y-auto"> |
| <nav className="px-2 py-3"> |
| <div className="space-y-1"> |
| <button |
| className="flex items-center w-full px-3 py-2 text-base font-medium text-gray-700 rounded-md hover:bg-gray-100 hover:text-primary-600" |
| onClick={() => { |
| handleNavigation('/dashboard'); |
| }} |
| > |
| <svg className="mr-3 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /> |
| </svg> |
| Dashboard |
| </button> |
| <button |
| className="flex items-center w-full px-3 py-2 text-base font-medium text-gray-700 rounded-md hover:bg-gray-100 hover:text-primary-600" |
| onClick={() => { |
| handleNavigation('/posts'); |
| }} |
| > |
| <svg className="mr-3 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /> |
| </svg> |
| Posts |
| </button> |
| <button |
| className="flex items-center w-full px-3 py-2 text-base font-medium text-gray-700 rounded-md hover:bg-gray-100 hover:text-primary-600" |
| onClick={() => { |
| console.log("Schedule button clicked"); // Add log |
| handleNavigation('/schedule'); |
| }} |
| > |
| <svg className="mr-3 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> |
| </svg> |
| Schedule |
| </button> |
| <button |
| className="flex items-center w-full px-3 py-2 text-base font-medium text-gray-700 rounded-md hover:bg-gray-100 hover:text-primary-600" |
| onClick={() => { |
| handleNavigation('/sources'); |
| }} |
| > |
| <svg className="mr-3 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /> |
| </svg> |
| Sources |
| </button> |
| <button |
| className="flex items-center w-full px-3 py-2 text-base font-medium text-gray-700 rounded-md hover:bg-gray-100 hover:text-primary-600" |
| onClick={() => { |
| handleNavigation('/accounts'); |
| }} |
| > |
| <svg className="mr-3 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> |
| </svg> |
| Accounts |
| </button> |
| </div> |
| </nav> |
| </div> |
| |
| {/* Mobile menu footer */} |
| <div className="border-t border-gray-200 p-4"> |
| <button |
| onClick={handleLogout} |
| disabled={isLoading} |
| className="w-full flex items-center justify-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-primary-600 shadow-sm hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed" |
| aria-label="Logout" |
| aria-busy={isLoading} |
| > |
| {isLoading ? ( |
| <> |
| <svg className="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
| <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> |
| <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
| </svg> |
| Logging out... |
| </> |
| ) : ( |
| <> |
| <svg className="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> |
| </svg> |
| Logout |
| </> |
| )} |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| )} |
| </div> |
| </header> |
| ); |
| }; |
|
|
| export default Header; |