ifixkart-storefront/components/store/StoreHeader.tsx

119 lines
4.9 KiB
TypeScript

/**
* @component StoreHeader
* @purpose Storefront header matching TechShop reference UI with logo, category search bar, blue search button, wishlist counter, account login, and shopping cart counter.
* @dependencies cartStore, LiveSearch, lucide-react
*/
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { ShoppingBag, Heart, User, ShoppingCart } from 'lucide-react';
import { useCartStore } from '@/store/cartStore';
import { LiveSearch } from './LiveSearch';
import { formatCurrency } from '@/lib/utils';
import { storefrontService, StorefrontSettings } from '@/services/api/storefrontService';
import { getMediaUrl } from '@/services/api/client';
export function StoreHeader() {
const itemCount = useCartStore((state) => state.getItemCount());
const cartTotal = useCartStore((state) => state.getCartTotal());
const wishlistCount = useCartStore((state) => state.wishlist.length);
const setCartDrawerOpen = useCartStore((state) => state.setCartDrawerOpen);
const [settings, setSettings] = useState<StorefrontSettings | null>(null);
useEffect(() => {
storefrontService.getSettings().then((s) => {
setSettings(s);
if (s?.favicon_url && typeof window !== 'undefined') {
let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement;
if (!link) {
link = document.createElement('link');
link.rel = 'shortcut icon';
document.head.appendChild(link);
}
link.href = getMediaUrl(s.favicon_url);
}
}).catch(() => {});
}, []);
const logoUrl = settings?.logo_url || settings?.primary_wordmark_url;
const storeName = settings?.store_name || 'iFixKart';
const logoMediaUrl = getMediaUrl(logoUrl);
return (
<header className="w-full bg-white border-b border-gray-100 py-4 px-4 sticky top-0 z-40 shadow-xs">
<div className="max-w-7xl mx-auto flex items-center justify-between gap-4 md:gap-8">
{/* Dynamic Store Logo */}
<Link href="/" className="flex items-center gap-1.5 shrink-0 group">
{logoMediaUrl ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={logoMediaUrl}
alt={storeName}
className="h-9 md:h-10 object-contain max-w-[220px]"
onError={(e) => {
// If logo image fails to load, fall back to text
(e.target as HTMLElement).style.display = 'none';
}}
/>
) : (
<span className="font-extrabold text-2xl md:text-3xl text-gray-900 tracking-tight">
iFix<span className="text-[#1877f2]">Kart</span>
</span>
)}
</Link>
{/* Live Catalog Search Component */}
<LiveSearch />
{/* Action Counters */}
<div className="flex items-center gap-4 sm:gap-6 shrink-0">
{/* Wishlist */}
<Link
href="/wishlist"
className="flex items-center gap-1.5 text-gray-700 hover:text-[#1877f2] transition-colors relative"
>
<div className="relative">
<Heart className="w-6 h-6 stroke-1.5 text-gray-700" />
{wishlistCount > 0 && (
<span className="absolute -top-1.5 -right-2 bg-[#1877f2] text-white text-[10px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
{wishlistCount}
</span>
)}
</div>
</Link>
{/* Account */}
<Link
href="/login"
className="hidden sm:flex items-center gap-2 text-gray-700 hover:text-[#1877f2] transition-colors"
>
<User className="w-6 h-6 stroke-1.5 text-gray-700" />
<div className="flex flex-col leading-tight text-left">
<span className="text-[10px] text-gray-400 font-medium">My Account</span>
<span className="text-xs font-bold text-gray-900">Login</span>
</div>
</Link>
{/* Shopping Cart Drawer Trigger */}
<button
onClick={() => setCartDrawerOpen(true)}
className="flex items-center gap-2 text-gray-700 hover:text-[#1877f2] transition-colors cursor-pointer"
>
<div className="relative">
<ShoppingCart className="w-6 h-6 stroke-1.5 text-gray-700" />
<span className="absolute -top-1.5 -right-2 bg-[#1877f2] text-white text-[10px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
{itemCount}
</span>
</div>
<div className="hidden sm:flex flex-col leading-tight text-left">
<span className="text-[10px] text-gray-400 font-medium">My Cart</span>
<span className="text-xs font-bold text-gray-900">{formatCurrency(cartTotal)}</span>
</div>
</button>
</div>
</div>
</header>
);
}