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