| | 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; |