| import { useState, useEffect, useRef, useCallback } from "react" |
| import { Link, NavLink, useNavigate, useLocation } from "react-router-dom" |
| import Search from "./Search" |
| import NotificationCenter from "./NotificationCenter" |
| import { useSelector, useDispatch } from "react-redux" |
| import { logout, setUser } from "../store/slices/authSlice" |
| import { useGetWishlistsQuery } from "../store/slices/userApiSlice"; |
| import { useGetCartsQuery } from "../store/slices/userApiSlice"; |
| import { toast } from "react-toastify" |
| import { motion, AnimatePresence } from "framer-motion" |
| import { ShoppingCart, User, LogOut, Package, Menu, X, Heart, Gift, CreditCard } from "lucide-react" |
| import "./Header.css" |
| import { useLogoutAPIMutation } from "../store/slices/userApiSlice"; |
| import { apiSlice } from "../store/slices/apiSlice"; |
| import { performLogout } from "../utils/authUtils"; |
|
|
| export default function Header({ onOpenLiveChat, onOpenAIAssistant }) { |
| const { isLoggedIn, userData } = useSelector((state) => state.auth) |
| const [isScrolled, setIsScrolled] = useState(false) |
| const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) |
| const [showUserDropdown, setShowUserDropdown] = useState(false) |
| |
| const shouldSkipQueries = !isLoggedIn || !userData || !localStorage.getItem('token'); |
|
|
| const { data: wishlistData, refetch: refetchWishlist, error: wishlistError } = useGetWishlistsQuery(undefined, { |
| skip: shouldSkipQueries, |
| refetchOnMountOrArgChange: false, |
| pollingInterval: 0, |
| }); |
| const { data: cartsData, refetch: refetchCarts, error: cartError } = useGetCartsQuery(undefined, { |
| skip: shouldSkipQueries, |
| refetchOnMountOrArgChange: false, |
| pollingInterval: 0, |
| }); |
| |
| const [logoutAPI] = useLogoutAPIMutation(); |
| const dispatch = useDispatch() |
| const navigate = useNavigate() |
| const location = useLocation() |
|
|
| useEffect(() => { |
| const initializeAuth = () => { |
| try { |
| const storedUserData = localStorage.getItem('userData'); |
| const storedToken = localStorage.getItem('token'); |
|
|
| if (storedUserData && storedToken) { |
| try { |
| const userData = JSON.parse(storedUserData); |
| |
| if (userData._id && userData.email) { |
| dispatch(setUser(userData)); |
| } else { |
| |
| localStorage.removeItem('userData'); |
| localStorage.removeItem('token'); |
| } |
| } catch (error) { |
| console.error('Error parsing stored user data:', error); |
| |
| localStorage.removeItem('userData'); |
| localStorage.removeItem('token'); |
| } |
| } |
| |
| } catch (error) { |
| console.error('Error initializing auth:', error); |
| |
| localStorage.removeItem('userData'); |
| localStorage.removeItem('token'); |
| } |
| }; |
|
|
| initializeAuth(); |
| }, [dispatch]); |
|
|
| const wishlistCount = Array.isArray(wishlistData?.wishlist) |
| ? new Set(wishlistData.wishlist.map(item => item.productId || item._id)).size |
| : 0; |
| const cartItems = cartsData?.carts?.[0]?.cartItems || []; |
|
|
| useEffect(() => { |
| const handleScroll = () => { |
| setIsScrolled(window.scrollY > 50) |
| } |
| window.addEventListener("scroll", handleScroll) |
| return () => window.removeEventListener("scroll", handleScroll) |
| }, []) |
|
|
| useEffect(() => { |
| const handleClick = (e) => { |
| if (!e.target.closest('.user-menu')) setShowUserDropdown(false); |
| }; |
| if (showUserDropdown) document.addEventListener('mousedown', handleClick); |
| return () => document.removeEventListener('mousedown', handleClick); |
| }, [showUserDropdown]); |
|
|
| useEffect(() => { |
| const handleCartUpdated = () => { |
| if (isLoggedIn && userData) { |
| refetchCarts(); |
| } |
| }; |
| window.addEventListener('cart-updated', handleCartUpdated); |
| return () => window.removeEventListener('cart-updated', handleCartUpdated); |
| }, [isLoggedIn, userData, refetchCarts]); |
|
|
| useEffect(() => { |
| const handleWishlistUpdated = () => { |
| if (isLoggedIn && userData) { |
| refetchWishlist(); |
| } |
| }; |
| window.addEventListener('wishlist-updated', handleWishlistUpdated); |
| return () => window.removeEventListener('wishlist-updated', handleWishlistUpdated); |
| }, [isLoggedIn, userData, refetchWishlist]); |
|
|
| const logoutHandler = useCallback(async () => { |
| await performLogout(dispatch, logoutAPI, logout, apiSlice.util.resetApiState); |
| }, [logoutAPI, dispatch]); |
|
|
| return ( |
| <motion.header |
| initial={{ y: -100 }} |
| animate={{ y: 0 }} |
| transition={{ duration: 0.8, type: "spring", stiffness: 100 }} |
| className={`Premium-header ${isScrolled ? "scrolled" : ""}`} |
| > |
| <nav className="Premium-navbar"> |
| <div className="navbar-container"> |
| {} |
| <motion.div className="brand-container" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}> |
| <Link |
| className="brand-link" |
| to="/" |
| onClick={(e) => { |
| |
| if (location.pathname === '/') { |
| e.preventDefault(); |
| window.scrollTo({ top: 0, behavior: 'smooth' }); |
| } else { |
| |
| setTimeout(() => { |
| window.scrollTo({ top: 0, behavior: 'smooth' }); |
| }, 100); |
| } |
| }} |
| > |
| <div className="logo-container"> |
| <img src="MKCart-head.png" alt="MKCart Logo" className="logo-img" /> |
| <div className="logo-glow"></div> |
| </div> |
| </Link> |
| </motion.div> |
| |
| {} |
| <div className="desktop-nav"> |
| {} |
| <div className="search-wrapper"> |
| <Search /> |
| </div> |
| |
| {} |
| <div className="nav-items"> |
| {} |
| <motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}> |
| <NavLink className="nav-item-Premium" to="/productlist"> |
| <Package size={20} /> |
| <span>Products</span> |
| <div className="nav-glow"></div> |
| </NavLink> |
| </motion.div> |
| |
| {} |
| <NotificationCenter /> |
| |
| {} |
| <div className="wishlit-container"> |
| <motion.button |
| className={`wishlit-btn${wishlistCount > 0 ? ' has-wishlist' : ''}`} |
| whileHover={{ scale: 1.1 }} |
| whileTap={{ scale: 0.9 }} |
| onClick={() => { |
| if (!isLoggedIn) { |
| toast.error("You must be logged in to view wishlist!", { autoClose: 2000 }); |
| setTimeout(() => navigate("/login"), 1500); |
| return; |
| } |
| navigate('/wishlist'); |
| }} |
| > |
| <Heart size={20} color={wishlistCount > 0 ? '#ff4757' : undefined} fill={wishlistCount > 0 ? '#ff4757' : 'none'} /> |
| {wishlistCount > 0 && ( |
| <span className="wishlit-badge">{wishlistCount}</span> |
| )} |
| </motion.button> |
| </div> |
| |
| {} |
| {isLoggedIn && userData ? ( |
| <div className="user-menu" style={{ position: 'relative' }}> |
| <span className="user-name-gradient" style={{ marginRight: 16 }}>{userData.name}</span> |
| <button |
| className="user-avatar-btn" |
| style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer' }} |
| onClick={() => setShowUserDropdown((prev) => !prev)} |
| > |
| <div className="user-avagtar" style={{ |
| width: 48, |
| height: 48, |
| borderRadius: '50%', |
| background: 'rgba(255,255,255,0.18)', |
| boxShadow: '0 4px 18px rgba(31,38,135,0.10)', |
| backdropFilter: 'blur(12px) saturate(180%)', |
| WebkitBackdropFilter: 'blur(12px) saturate(180%)', |
| border: '1.5px solid rgba(255,255,255,0.18)', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| }}> |
| <User size={28} /> |
| </div> |
| <div className="user-glow"></div> |
| </button> |
| {showUserDropdown && ( |
| <div className="custom-user-dropdown" style={{ |
| position: 'absolute', |
| top: 'calc(100% + 8px)', |
| right: 0, |
| minWidth: 200, |
| background: 'rgba(130, 162, 183, 0.29)', |
| borderRadius: 16, |
| boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.12)', |
| backdropFilter: 'blur(18px) saturate(180%)', |
| WebkitBackdropFilter: 'blur(18px) saturate(180%)', |
| border: '1.5px solid rgba(255, 255, 255, 0.18)', |
| zIndex: 100 |
| }}> |
| <Link to="/profile" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => setShowUserDropdown(false)}> |
| <User size={16} style={{ marginRight: 8 }} /> |
| <span>Profile</span> |
| </Link> |
| <Link to="/wishlist" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => setShowUserDropdown(false)}> |
| <Heart size={16} style={{ marginRight: 8 }} /> |
| <span>Wishlist</span> |
| </Link> |
| <Link to="/orders" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => setShowUserDropdown(false)}> |
| <Package size={16} style={{ marginRight: 8 }} /> |
| <span>Orders</span> |
| </Link> |
| <Link to="/cart" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => setShowUserDropdown(false)}> |
| <ShoppingCart size={16} style={{ marginRight: 8 }} /> |
| <span>Cart</span> |
| </Link> |
| <Link to="/checkout" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => setShowUserDropdown(false)}> |
| <CreditCard size={16} style={{ marginRight: 8 }} /> |
| <span>Checkout</span> |
| </Link> |
| <div className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', cursor: 'pointer' }} onClick={() => { onOpenLiveChat && onOpenLiveChat(); setShowUserDropdown(false); }}> |
| <Gift size={16} style={{ marginRight: 8 }} /> |
| <span>Chat With Us</span> |
| </div> |
| <div className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', cursor: 'pointer' }} onClick={() => { onOpenAIAssistant && onOpenAIAssistant(); setShowUserDropdown(false); }}> |
| <User size={16} style={{ marginRight: 8 }} /> |
| <span>Assistant</span> |
| </div> |
| <div style={{ borderTop: '1px solid #444', margin: '4px 0' }} /> |
| <div className="dropdown-item-Premium logout" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#ff4757', cursor: 'pointer' }} onClick={() => { logoutHandler(); setShowUserDropdown(false); }}> |
| <LogOut size={16} style={{ marginRight: 8 }} /> |
| <span>Logout</span> |
| </div> |
| </div> |
| )} |
| </div> |
| ) : ( |
| <motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}> |
| <NavLink className="login-btn-Premium" to="/login"> |
| <User size={18} /> |
| <span>Login</span> |
| <div className="btn-shimmer"></div> |
| </NavLink> |
| </motion.div> |
| )} |
| |
| {} |
| <motion.div className="cart-wrapper" whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}> |
| <div |
| className="cart-link-Premium" |
| style={{ cursor: 'pointer' }} |
| onClick={() => { |
| if (!isLoggedIn) { |
| toast.error("You must be logged in to view cart!", { autoClose: 2000 }); |
| setTimeout(() => navigate("/login"), 1500); |
| return; |
| } |
| navigate('/cart'); |
| }} |
| > |
| <div className="cart-icon-container"> |
| <ShoppingCart size={24} className="cart-icon" /> |
| <AnimatePresence> |
| {cartItems.length > 0 && ( |
| <motion.span |
| className="cart-badge-Premium" |
| key={cartItems.length} |
| initial={{ scale: 0, rotate: 180 }} |
| animate={{ scale: 1, rotate: 0 }} |
| exit={{ scale: 0, rotate: -180 }} |
| transition={{ type: "spring", stiffness: 500 }} |
| > |
| {cartItems.length} |
| </motion.span> |
| )} |
| </AnimatePresence> |
| <div className="cart-pulse"></div> |
| </div> |
| </div> |
| </motion.div> |
| </div> |
| </div> |
| |
| {} |
| {} |
| </div> |
| |
| {} |
| <AnimatePresence> |
| {isMobileMenuOpen && ( |
| <motion.div |
| className="mobile-menu" |
| initial={{ opacity: 0, height: 0 }} |
| animate={{ opacity: 1, height: "auto" }} |
| exit={{ opacity: 0, height: 0 }} |
| transition={{ duration: 0.3 }} |
| > |
| <div className="mobile-menu-content"> |
| <div className="mobile-search"> |
| <Search /> |
| </div> |
| |
| <div className="mobile-nav-items"> |
| <Link to="/productlist" className="mobile-nav-item" onClick={() => setIsMobileMenuOpen(false)}> |
| <Package size={20} /> |
| <span>All Products</span> |
| </Link> |
| |
| {isLoggedIn ? ( |
| <> |
| <Link to="/profile" className="mobile-nav-item" onClick={() => setIsMobileMenuOpen(false)}> |
| <User size={20} /> |
| <span>Profile</span> |
| </Link> |
| <button |
| className="mobile-nav-item" |
| onClick={() => { |
| logoutHandler() |
| setIsMobileMenuOpen(false) |
| }} |
| > |
| <LogOut size={20} /> |
| <span>Logout</span> |
| </button> |
| </> |
| ) : ( |
| <Link to="/login" className="mobile-nav-item" onClick={() => setIsMobileMenuOpen(false)}> |
| <User size={20} /> |
| <span>Login</span> |
| </Link> |
| )} |
| |
| <div |
| className="mobile-nav-item" |
| style={{ cursor: 'pointer' }} |
| onClick={() => { |
| if (!isLoggedIn) { |
| toast.error("You must be logged in to view cart!", { autoClose: 2000 }); |
| setTimeout(() => navigate("/login"), 1500); |
| setIsMobileMenuOpen(false); |
| return; |
| } |
| navigate('/cart'); |
| setIsMobileMenuOpen(false); |
| }} |
| > |
| <ShoppingCart size={20} /> |
| <span>Cart ({cartItems.length})</span> |
| </div> |
| </div> |
| </div> |
| </motion.div> |
| )} |
| </AnimatePresence> |
| {} |
| <div className="mobile-header-controls" style={{ display: 'flex', alignItems: 'center', gap: 8 }}> |
| {isLoggedIn && userData && isMobileMenuOpen && ( |
| <div className="user-menu"> |
| <button |
| className="user-avatar-btn" |
| style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer' }} |
| onClick={() => setShowUserDropdown((prev) => !prev)} |
| > |
| <div className="user-avagtar" style={{ |
| width: 40, |
| height: 40, |
| borderRadius: '50%', |
| background: 'rgba(90, 139, 158, 0.27)', |
| boxShadow: '0 4px 18px rgba(31,38,135,0.10)', |
| backdropFilter: 'blur(12px) saturate(180%)', |
| WebkitBackdropFilter: 'blur(12px) saturate(180%)', |
| border: '1.5px solid rgba(255,255,255,0.18)', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| }}> |
| <User size={26} /> |
| </div> |
| <div className="user-glow"></div> |
| </button> |
| {showUserDropdown && ( |
| <div className="custom-user-dropdown" style={{ |
| position: 'absolute', |
| top: 'calc(100% + 8px)', |
| right: 0, |
| minWidth: 200, |
| background: 'rgba(130, 162, 183, 0.29)', |
| borderRadius: 16, |
| boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.12)', |
| backdropFilter: 'blur(18px) saturate(180%)', |
| WebkitBackdropFilter: 'blur(18px) saturate(180%)', |
| border: '1.5px solid rgba(255, 255, 255, 0.18)', |
| zIndex: 100 |
| }}> |
| <Link to="/profile" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => { setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <User size={16} style={{ marginRight: 8 }} /> |
| <span>Profile</span> |
| </Link> |
| <Link to="/wishlist" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => { setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <Heart size={16} style={{ marginRight: 8 }} /> |
| <span>Wishlist</span> |
| </Link> |
| <Link to="/orders" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => { setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <Package size={16} style={{ marginRight: 8 }} /> |
| <span>Orders</span> |
| </Link> |
| <Link to="/cart" className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', textDecoration: 'none' }} onClick={() => { setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <ShoppingCart size={16} style={{ marginRight: 8 }} /> |
| <span>Cart</span> |
| </Link> |
| <div className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', cursor: 'pointer' }} onClick={() => { onOpenLiveChat && onOpenLiveChat(); setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <Gift size={16} style={{ marginRight: 8 }} /> |
| <span>Chat With Us</span> |
| </div> |
| <div className="dropdown-item-Premium" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#fff', cursor: 'pointer' }} onClick={() => { onOpenAIAssistant && onOpenAIAssistant(); setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <User size={16} style={{ marginRight: 8 }} /> |
| <span>Assistant</span> |
| </div> |
| <div style={{ borderTop: '1px solid #444', margin: '4px 0' }} /> |
| <div className="dropdown-item-Premium logout" style={{ display: 'flex', alignItems: 'center', padding: '10px 18px', color: '#ff4757', cursor: 'pointer' }} onClick={() => { logoutHandler(); setShowUserDropdown(false); setIsMobileMenuOpen(false); }}> |
| <LogOut size={16} style={{ marginRight: 8 }} /> |
| <span>Logout</span> |
| </div> |
| </div> |
| )} |
| </div> |
| )} |
| <motion.button |
| className="mobile-menu-toggle" |
| onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} |
| whileHover={{ scale: 1.1 }} |
| whileTap={{ scale: 0.9 }} |
| > |
| <AnimatePresence mode="wait"> |
| {isMobileMenuOpen ? ( |
| <motion.div |
| key="close" |
| initial={{ rotate: -90, opacity: 0 }} |
| animate={{ rotate: 0, opacity: 1 }} |
| exit={{ rotate: 90, opacity: 0 }} |
| transition={{ duration: 0.2 }} |
| > |
| <X size={24} /> |
| </motion.div> |
| ) : ( |
| <motion.div |
| key="menu" |
| initial={{ rotate: 90, opacity: 0 }} |
| animate={{ rotate: 0, opacity: 1 }} |
| exit={{ rotate: -90, opacity: 0 }} |
| transition={{ duration: 0.2 }} |
| > |
| <Menu size={24} /> |
| </motion.div> |
| )} |
| </AnimatePresence> |
| </motion.button> |
| </div> |
| </nav> |
| </motion.header> |
| ) |
| } |