All files / varjoliitokauppa/components ProductCard.tsx

86.95% Statements 20/23
60.46% Branches 26/43
66.66% Functions 2/3
90.9% Lines 20/22

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                                5x 5x 5x 5x     5x     5x 5x 5x     5x       5x 2x 2x   2x 1x 1x     1x         1x 1x     5x                                                                                                                                               5x  
'use client';
 
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { Product } from '../types';
import { useShop } from '../context/ShopContext';
import { useToast } from '../context/ToastContext';
import { Plus, ArrowRight } from 'lucide-react';
 
interface ProductCardProps {
  product: Product;
}
 
// OPTIMIZATION: React.memo prevents re-renders if props (product) don't change
export const ProductCard = React.memo<ProductCardProps>(({ product }) => {
  const { addToCart, cart } = useShop();
  const { addToast } = useToast();
  const router = useRouter();
 
  // Detect if product has variants that require selection
  const hasVariants = (product.colors && product.colors.length > 0) || (product.sizes && product.sizes.length > 0);
 
  // Calculate if out of stock in cart
  const cartItem = cart.find(i => i.id === product.id);
  const currentQty = cartItem ? cartItem.quantity : 0;
  const isOutOfStock = !hasVariants && currentQty >= product.stock;
 
  // Get valid image or use placeholder
  const imageUrl = product.images?.[0] && product.images[0].trim() !== ''
    ? product.images[0]
    : '/placeholder.svg';
 
  const handleAddToCart = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
 
    if (hasVariants) {
      router.push(`/tuote/${product.id}`);
      return;
    }
 
    Iif (isOutOfStock) {
      addToast("Varastosaldo ei riitä", "error");
      return;
    }
 
    addToCart(product);
    addToast(`${product.name} lisätty ostoskoriin!`);
  };
 
  return (
    // Rounded-2xl for professional feel
    <div className="group flex flex-col h-full bg-white rounded-2xl overflow-hidden transition-all duration-500 hover:shadow-xl hover:shadow-black/5 hover:-translate-y-1 border border-gray-200 relative">
      {/* Aspect Square (1:1) for technical gear */}
      <div className="relative aspect-square bg-gray-50 overflow-hidden block">
 
        {/* Main Link covers the image area */}
        <Link href={`/tuote/${product.id}`} className="absolute inset-0 z-10">
            <Image
              src={imageUrl}
              alt={product.name}
              fill
              sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
              className={`object-cover transition-transform duration-700 ease-in-out ${isOutOfStock ? 'grayscale opacity-80' : 'group-hover:scale-105'}`}
              quality={85}
            />
        </Link>
 
        {/* Badges - Unified visual language */}
        <div className="absolute top-4 left-4 flex flex-row flex-wrap gap-2 z-20 pointer-events-none items-start max-w-[calc(100%-2rem)]">
          {product.isNew && (
            <span className="bg-black text-white text-[10px] uppercase tracking-widest font-bold px-3 py-1 rounded-full shadow-sm w-fit">Uutuus</span>
          )}
          {product.salePrice && (
            <span className="bg-red-600 text-white text-[10px] uppercase tracking-widest font-bold px-3 py-1 rounded-full shadow-sm w-fit">Ale</span>
          )}
          {product.isUsed && (
            <span className="bg-gray-600 text-white text-[10px] uppercase tracking-widest font-bold px-3 py-1 rounded-full shadow-sm w-fit">Käytetty</span>
          )}
          {product.stock <= 0 && !hasVariants && (
             <span className="bg-gray-200 text-gray-500 text-[10px] uppercase tracking-widest font-bold px-3 py-1 rounded-full shadow-sm w-fit">Loppu</span>
          )}
        </div>
 
        {/* Hover Action - Button sits ON TOP of the link (Z-30) */}
        <div className="absolute bottom-4 left-4 right-4 z-30 opacity-0 translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300 ease-out">
          <button
            onClick={handleAddToCart}
            disabled={isOutOfStock && !hasVariants}
            className={`w-full py-3.5 rounded-xl font-bold text-sm flex items-center justify-center gap-2 shadow-lg transition-all ${isOutOfStock && !hasVariants ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-black hover:bg-black hover:text-white hover:scale-[1.02] active:scale-95'}`}
          >
            {isOutOfStock && !hasVariants ? 'Loppu varastosta' : hasVariants ? <><ArrowRight size={18} strokeWidth={2.5} /> Valitse vaihtoehdot</> : <><Plus size={18} strokeWidth={2.5} /> Lisää koriin</>}
          </button>
        </div>
      </div>
 
      <div className="p-5 flex flex-col flex-grow relative bg-white z-10 border-t border-gray-100">
        <div className="mb-1 text-[10px] font-bold text-gray-400 uppercase tracking-widest group-hover:text-black transition-colors">{product.brand || "Varjoliitokauppa"}</div>
 
        <Link href={`/tuote/${product.id}`} className="block mb-2 z-20">
          <h3 className="font-bold text-lg text-black leading-tight group-hover:opacity-70 transition-opacity line-clamp-2">
            {product.name}
          </h3>
        </Link>
 
        <div className="mt-auto flex items-end justify-between pt-2">
          <div className="flex flex-col">
            {product.salePrice ? (
              <div className="flex items-baseline gap-2">
                <span className="text-lg font-bold text-red-600">{product.salePrice} €</span>
                <span className="text-sm text-gray-400 line-through font-medium">{product.price} €</span>
              </div>
            ) : (
              <span className="text-lg font-bold text-black">{product.price} €</span>
            )}
          </div>
        </div>
      </div>
    </div>
  );
});
 
ProductCard.displayName = "ProductCard";