All files / varjoliitokauppa/components Header.tsx

68.96% Statements 20/29
73.68% Branches 14/19
53.84% Functions 7/13
65.38% Lines 17/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170                    4x 6x 6x 6x 6x 6x     6x     6x 3x       6x 3x 3x                   3x 3x     6x               6x                                                     30x                                                                                                                                                                                        
'use client';
 
import React, { useState, useEffect, useMemo } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { ShoppingCart, Menu, X, User, Search, Home, Store, Tag, RotateCcw, Mail } from 'lucide-react';
import { useShop } from '../context/ShopContext';
import Image from 'next/image';
import { motion } from 'framer-motion';
 
export const Header: React.FC = () => {
  const { cart, isAuthenticated } = useShop();
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const [isScrolled, setIsScrolled] = useState(false);
  const [mounted, setMounted] = useState(false);
  const pathname = usePathname();
 
  // OPTIMIZATION: Memoize cart count calculation
  const cartCount = useMemo(() => cart.reduce((sum, item) => sum + item.quantity, 0), [cart]);
 
  // Fix hydration error by only rendering cart count after mount
  useEffect(() => {
    setMounted(true);
  }, []);
 
  // OPTIMIZATION: Throttle scroll event using requestAnimationFrame
  useEffect(() => {
    let ticking = false;
    const handleScroll = () => {
      if (!ticking) {
        window.requestAnimationFrame(() => {
          setIsScrolled(window.scrollY > 10);
          ticking = false;
        });
        ticking = true;
      }
    };
    // Use passive listener for better scroll performance
    window.addEventListener('scroll', handleScroll, { passive: true });
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);
 
  const navLinks = [
    { name: 'Etusivu', path: '/', icon: Home },
    { name: 'Kauppa', path: '/kauppa', icon: Store },
    { name: 'Tarjoukset', path: '/tarjoukset', icon: Tag },
    { name: 'Käytetyt', path: '/kaytetyt', icon: RotateCcw },
    { name: 'Yhteystiedot', path: '/yhteystiedot', icon: Mail },
  ];
 
  return (
    <header 
      className={`sticky top-0 z-50 transition-all duration-300 border-b ${
        isScrolled 
          ? 'bg-white/95 backdrop-blur-md border-gray-200 py-2 shadow-sm' 
          : 'bg-white border-transparent py-5'
      }`}
    >
      <div className="max-w-[100rem] mx-auto px-3 sm:px-4 lg:px-6 relative">
        <div className="flex justify-between items-center h-16 md:h-14">
        <div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-shrink-0">
            <Image
              src="/logo.webp"
              alt="Logo"
              width={35}
              height={35}
              priority
              className="rounded w-8 h-8 sm:w-10 sm:h-10 lg:w-11 lg:h-11 flex-shrink-0"
            />
            <span className="text-lg sm:text-xl lg:text-2xl font-bold tracking-tight truncate">
              Varjoliitokauppa
            </span>
          </div>
 
          {/* Desktop Nav */}
          <nav className="hidden md:flex gap-6 lg:gap-10 items-center">
            {navLinks.map(link => (
              <Link
                key={link.path}
                href={link.path}
                className="flex items-center text-sm xl:text-base font-medium text-gray-600 hover:text-black transition-colors duration-300 whitespace-nowrap"
              >
                <motion.div
                  whileHover={{
                    color: "hsl(0, 0%, 0%)",
                  }}
                  transition={{ type: "spring", stiffness: 300, damping: 15 }}
                  className="flex items-center"
                >
                  <link.icon
                    className={`mr-1 h-4 w-4 flex-shrink-0 ${
                      pathname === link.path ? "text-[#3d3d3d]" : ""
                    }`}
                    strokeWidth={pathname === link.path ? 2.5 : 2}
                  />
                  <span className={pathname === link.path ? "font-bold text-[#3d3d3d]" : "font-medium"}>
                    {link.name}
                  </span>
                </motion.div>
              </Link>
            ))}
          </nav>
 
          {/* Actions */}
          <div className="flex items-center gap-2 md:gap-4">
            {/* Search - Visible on Mobile now */}
            <Link href="/kauppa" className="p-3 md:p-2 text-gray-500 hover:text-black transition-colors rounded-full hover:bg-gray-100">
              <Search size={22} strokeWidth={2} />
            </Link>
 
            <Link href="/admin" className="p-3 md:p-2 text-gray-500 hover:text-black transition-colors rounded-full hover:bg-gray-100 hidden sm:block">
               <User size={22} strokeWidth={2} className={isAuthenticated ? "text-black" : ""} />
            </Link>
 
            <Link href="/kassa" className="relative group ml-1 md:ml-2">
              <div className="bg-black text-white px-5 md:px-6 py-3 rounded-full text-sm font-bold transition-transform hover:scale-105 active:scale-95 flex items-center gap-3 shadow-sm shadow-black/10 group-hover:bg-gray-900 duration-300 cursor-pointer">
                <ShoppingCart size={18} strokeWidth={2} />
                <span className="hidden sm:inline">Ostoskori</span>
                {mounted && cartCount > 0 && (
                  /* PERFECT CIRCLE FIX: w-5 h-5 flex center + pt-[1px] for optical alignment */
                  <span className="w-5 h-5 bg-white text-black text-[10px] font-extrabold rounded-full flex items-center justify-center leading-none pt-[1px]">
                    {cartCount}
                  </span>
                )}
              </div>
            </Link>
 
            {/* Mobile Menu Button */}
            <button 
              className="md:hidden p-3 text-black ml-1 rounded-full hover:bg-gray-100"
              onClick={() => setIsMenuOpen(!isMenuOpen)}
            >
              {isMenuOpen ? <X size={24} strokeWidth={2} /> : <Menu size={24} strokeWidth={2} />}
            </button>
          </div>
        </div>
      </div>
 
      {/* Mobile Menu - Positioned Absolutely to attach to header bottom */}
      {isMenuOpen && (
        <div className="md:hidden absolute top-full left-0 w-full bg-white border-b border-gray-200 shadow-2xl px-6 py-8 space-y-6 h-[calc(100vh-80px)] overflow-y-auto animate-slide-down z-40">
          {navLinks.map(link => (
            <Link
              key={link.path}
              href={link.path}
              className="block text-3xl font-extrabold text-black hover:opacity-70 transition-opacity"
              onClick={() => setIsMenuOpen(false)}
            >
              {link.name}
            </Link>
          ))}
          <div className="pt-8 mt-4 border-t border-gray-200">
             <Link
               href="/admin"
               className="flex items-center gap-3 text-lg font-bold text-gray-500 mb-6"
               onClick={() => setIsMenuOpen(false)}
             >
                <User size={20} /> Ylläpito
             </Link>
            <a href="https://varjoliitokoulu.fi" target="_blank" rel="noreferrer" className="flex items-center gap-2 text-base text-gray-500 font-bold hover:text-black transition-colors">
              Linkki Varjoliitokouluun 
              <span className="text-xl">→</span>
            </a>
          </div>
        </div>
      )}
    </header>
  );
};