Spaces:
Configuration error
Configuration error
| import React, { useState, useEffect, useRef } from 'react'; | |
| import { Link, useLocation } from 'react-router-dom'; | |
| import { Menu, X, Sun, Moon, LogOut, User as UserIcon } from 'lucide-react'; | |
| import { auth } from '../firebase'; | |
| import { onAuthStateChanged, User as FirebaseUser, signOut } from 'firebase/auth'; | |
| const Header: React.FC = () => { | |
| const [isMenuOpen, setIsMenuOpen] = useState(false); | |
| const [darkMode, setDarkMode] = useState(false); | |
| const [user, setUser] = useState<FirebaseUser | null>(null); | |
| const [profilePic, setProfilePic] = useState<string | null>(null); | |
| const [isProfileOpen, setIsProfileOpen] = useState(false); | |
| const [showSignOutConfirm, setShowSignOutConfirm] = useState(false); // NEW | |
| const profileRef = useRef<HTMLDivElement>(null); | |
| const location = useLocation(); | |
| const navLinks = [ | |
| { path: '/', label: 'Humanizer X' }, | |
| { path: '/gpt-checker', label: 'GPT Checker' }, | |
| { path: '/word-counter', label: 'Word Counter' }, // <-- added | |
| { path: '/about', label: 'About' }, | |
| { path: '/contact', label: 'Contact' }, | |
| { path: '/blogs', label: 'Blog' }, | |
| ]; | |
| // Theme handling | |
| useEffect(() => { | |
| const savedTheme = localStorage.getItem('theme'); | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | |
| if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { | |
| setDarkMode(true); | |
| document.documentElement.classList.add('dark'); | |
| } else { | |
| setDarkMode(false); | |
| document.documentElement.classList.remove('dark'); | |
| } | |
| }, []); | |
| const toggleDarkMode = () => { | |
| const newDark = !darkMode; | |
| setDarkMode(newDark); | |
| if (newDark) { | |
| document.documentElement.classList.add('dark'); | |
| localStorage.setItem('theme', 'dark'); | |
| } else { | |
| document.documentElement.classList.remove('dark'); | |
| localStorage.setItem('theme', 'light'); | |
| } | |
| }; | |
| // Auth listener | |
| useEffect(() => { | |
| const unsubscribe = onAuthStateChanged(auth, (currentUser) => { | |
| setUser(currentUser); | |
| setProfilePic(currentUser?.photoURL || null); | |
| }); | |
| return () => unsubscribe(); | |
| }, []); | |
| // Close profile dropdown when clicking outside | |
| useEffect(() => { | |
| const handleClickOutside = (e: MouseEvent) => { | |
| if (profileRef.current && !profileRef.current.contains(e.target as Node)) { | |
| setIsProfileOpen(false); | |
| } | |
| }; | |
| document.addEventListener('mousedown', handleClickOutside); | |
| return () => document.removeEventListener('mousedown', handleClickOutside); | |
| }, []); | |
| const isActive = (path: string) => | |
| location.pathname === path || location.pathname.startsWith(path + "/"); | |
| // Sign out function | |
| const handleSignOut = async () => { | |
| try { | |
| await signOut(auth); | |
| setIsProfileOpen(false); | |
| setIsMenuOpen(false); | |
| setShowSignOutConfirm(false); // Close modal | |
| } catch (err) { | |
| console.error('Sign out error:', err); | |
| } | |
| }; | |
| return ( | |
| <header className="bg-gray-900 dark:bg-gray-950 text-white py-2 shadow-lg transition-colors duration-300"> | |
| <div className="container mx-auto px-4"> | |
| <div className="flex items-center justify-between h-16"> | |
| {/* Logo */} | |
| <Link to="/" className="flex items-center gap-2"> | |
| <img | |
| src="https://ucarecdn.com/baca8c8c-4a8b-4020-82e6-1382cdd3782a/-/format/auto/" | |
| alt="Humanizer X - AI Text Humanization Platform Logo" | |
| title="Humanizer X - Navigate to homepage" | |
| className="w-10 h-10 object-contain" | |
| /> | |
| <span className="text-2xl font-bold bg-gradient-to-r from-indigo-500 to-indigo-700 bg-clip-text text-transparent"> | |
| Humanizer X | |
| </span> | |
| </Link> | |
| {/* Desktop Navigation */} | |
| <nav className="hidden md:flex items-center space-x-8"> | |
| {navLinks.map((link) => ( | |
| <Link | |
| key={link.path} | |
| to={link.path} | |
| className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${ | |
| isActive(link.path) | |
| ? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20' | |
| : 'text-gray-300 hover:text-blue-600 dark:hover:text-blue-400' | |
| }`} | |
| > | |
| {link.label} | |
| </Link> | |
| ))} | |
| </nav> | |
| {/* Right side controls */} | |
| <div className="flex items-center gap-4 relative"> | |
| {/* Profile/Auth Section */} | |
| {!user ? ( | |
| <div className="hidden md:flex items-center gap-3"> | |
| <Link | |
| to="/login" | |
| className="px-4 py-2 text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors" | |
| > | |
| Sign In | |
| </Link> | |
| <Link | |
| to="/signup" | |
| className="px-4 py-2 bg-gradient-to-r from-indigo-500 to-indigo-700 text-white rounded-lg hover:from-blue-700 hover:to-purple-700 transition-all" | |
| > | |
| Get Started | |
| </Link> | |
| </div> | |
| ) : ( | |
| <div className="relative" ref={profileRef}> | |
| <button | |
| onClick={() => setIsProfileOpen(!isProfileOpen)} | |
| className="w-10 h-10 rounded-full border-2 border-gray-300 dark:border-gray-600 overflow-hidden flex items-center justify-center bg-gray-200 dark:bg-gray-700" | |
| > | |
| {profilePic ? ( | |
| <img src={profilePic} alt="Profile" className="w-full h-full object-cover" /> | |
| ) : ( | |
| <UserIcon size={20} className="text-black dark:text-white" /> | |
| )} | |
| </button> | |
| {isProfileOpen && ( | |
| <div className="absolute right-0 mt-2 w-80 bg-white dark:bg-gray-800 rounded-2xl shadow-xl border border-gray-200 dark:border-gray-700 p-4 z-50"> | |
| <div className="flex items-center gap-3"> | |
| {profilePic ? ( | |
| <img src={profilePic} alt="Profile" className="w-12 h-12 rounded-full object-cover" /> | |
| ) : ( | |
| <UserIcon size={40} className="text-black dark:text-white" /> | |
| )} | |
| <div> | |
| <h4 className="text-gray-900 dark:text-gray-100 font-semibold"> | |
| {user.displayName || 'User'} | |
| </h4> | |
| <p className="text-sm text-gray-500 dark:text-gray-400">{user.email}</p> | |
| </div> | |
| </div> | |
| <hr className="my-3 border-gray-200 dark:border-gray-700" /> | |
| <Link | |
| to="/profile" | |
| className="flex items-center gap-2 text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors py-2" | |
| > | |
| <UserIcon size={18} /> My Profile | |
| </Link> | |
| <button | |
| onClick={() => setShowSignOutConfirm(true)} // show modal | |
| className="flex items-center gap-2 text-gray-700 dark:text-gray-300 hover:text-red-600 dark:hover:text-red-400 transition-colors py-2 w-full text-left" | |
| > | |
| <LogOut size={18} /> Sign Out | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* Mobile menu button */} | |
| <button | |
| onClick={() => setIsMenuOpen(!isMenuOpen)} | |
| className="md:hidden p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300" | |
| aria-label={isMenuOpen ? "Close menu" : "Open menu"} | |
| > | |
| {isMenuOpen ? <X size={20} /> : <Menu size={20} />} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Mobile Navigation */} | |
| {isMenuOpen && ( | |
| <div className="md:hidden border-t border-gray-200 dark:border-gray-700 py-4"> | |
| <nav className="flex flex-col space-y-2"> | |
| {navLinks.map((link) => ( | |
| <Link | |
| key={link.path} | |
| to={link.path} | |
| onClick={() => setIsMenuOpen(false)} | |
| className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${ | |
| isActive(link.path) | |
| ? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20' | |
| : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400' | |
| }`} | |
| > | |
| {link.label} | |
| </Link> | |
| ))} | |
| {/* Signed-in user: Show Sign Out */} | |
| {user ? ( | |
| <button | |
| onClick={() => { | |
| setIsMenuOpen(false); | |
| setShowSignOutConfirm(true); | |
| }} | |
| className="px-3 py-2 rounded-md text-left text-gray-700 dark:text-gray-300 hover:text-red-600 dark:hover:text-red-400 transition-colors font-medium" | |
| > | |
| <LogOut size={16} className="inline mr-2" /> Sign Out | |
| </button> | |
| ) : ( | |
| <> | |
| {/* Show Sign In if not signed in */} | |
| <Link | |
| to="/signin" | |
| onClick={() => setIsMenuOpen(false)} | |
| className="px-3 py-2 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors" | |
| > | |
| Sign In | |
| </Link> | |
| {/* Get Started Button */} | |
| <Link | |
| to="/get-started" | |
| onClick={() => setIsMenuOpen(false)} | |
| className="px-3 py-2 rounded-md text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 transition-colors text-center" | |
| > | |
| Get Started | |
| </Link> | |
| </> | |
| )} | |
| </nav> | |
| </div> | |
| )} | |
| </div> | |
| {/* Sign Out Confirmation Modal */} | |
| {showSignOutConfirm && ( | |
| <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div className="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-xl w-80 text-center"> | |
| <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4"> | |
| Confirm Sign Out | |
| </h2> | |
| <p className="text-gray-700 dark:text-gray-300 mb-6"> | |
| Are you sure you want to sign out? | |
| </p> | |
| <div className="flex justify-center gap-4"> | |
| <button | |
| onClick={handleSignOut} | |
| className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition" | |
| > | |
| Yes, Sign Out | |
| </button> | |
| <button | |
| onClick={() => setShowSignOutConfirm(false)} | |
| className="px-4 py-2 bg-gray-300 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-600 transition" | |
| > | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </header> | |
| ); | |
| }; | |
| export default Header; |