ImageCard.tsx 5.33 KB
import React, { useState, useRef, useEffect } from 'react';
import { ImageItem } from '../types';
import { Download, Heart, Video, Hourglass } from 'lucide-react';

interface ImageCardProps {
  image: ImageItem;
  onClick: (image: ImageItem) => void;
  onLike: (image: ImageItem) => void;
  currentUser?: string;
  isVideo?: boolean;
}

const ImageCard: React.FC<ImageCardProps> = ({ image, onClick, onLike, currentUser, isVideo = false }) => {
  const [isLoaded, setIsLoaded] = useState(false);
  const videoRef = useRef<HTMLVideoElement>(null);

  const handleLike = (e: React.MouseEvent) => {
    e.stopPropagation();
    if (isVideo) return; // Liking is disabled for videos for now
    onLike(image);
  };
  
  const handleMouseEnter = () => {
    if (videoRef.current) {
      videoRef.current.play().catch(e => console.error("Video play failed", e));
    }
  }

  const handleMouseLeave = () => {
    if (videoRef.current) {
      videoRef.current.pause();
    }
  }

  return (
    <div 
      className="group relative mb-4 break-inside-avoid rounded-2xl overflow-hidden bg-gray-200 cursor-zoom-in shadow-sm hover:shadow-xl transition-all duration-300"
      onClick={() => onClick(image)}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      {/* Placeholder / Skeleton */}
      {!isLoaded && (
        <div className="absolute inset-0 bg-gray-200 animate-pulse min-h-[200px]" />
      )}

      {/* Main Content */}
      {isVideo ? (
        <video
          ref={videoRef}
          src={image.url}
          className={`w-full h-auto object-cover transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
          onLoadedData={() => setIsLoaded(true)}
          loop
          muted
          playsInline
          preload="metadata"
        />
      ) : (
        <img
          src={image.url}
          alt={image.prompt}
          className={`w-full h-auto object-cover transition-transform duration-700 ease-in-out group-hover:scale-105 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
          onLoad={() => setIsLoaded(true)}
          loading="lazy"
        />
      )}

      {/* Hover Overlay */}
      <div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col justify-end p-4 md:p-5">
        
        {/* Top Right: Download */}
        <div className="absolute top-4 right-4 translate-y-[-10px] opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300 delay-75">
          <button 
            className="p-2 bg-white/20 backdrop-blur-md hover:bg-white/40 rounded-full text-white transition-colors"
            title="Download"
            onClick={(e) => {
              e.stopPropagation();
              const link = document.createElement('a');
              link.href = image.url;
              link.download = `z-${isVideo ? 'video' : 'image'}-${image.id}.${isVideo ? 'mp4' : 'png'}`;
              document.body.appendChild(link);
              link.click();
              document.body.removeChild(link);
            }}
          >
            <Download size={16} />
          </button>
        </div>
        
        {isVideo && (
          <div className="absolute top-4 left-4">
            <Video size={16} className="text-white/80" />
          </div>
        )}

        {/* Content Info */}
        <div className="text-white transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300">
          <p className="font-medium text-sm line-clamp-2 mb-2 text-shadow-sm">
            {image.prompt}
          </p>
          
          {!isVideo ? (
            <div className="flex justify-between items-end">
               {/* Author Info */}
               <div className="text-xs text-gray-300 font-mono flex flex-col gap-1">
                  <span className="opacity-75">ID: {image.authorId || 'UNKNOWN'}</span>
                  <span className="bg-white/10 px-1.5 py-0.5 rounded w-fit">{image.width}x{image.height}</span>
               </div>

               {/* Like Button */}
               <button 
                 onClick={handleLike}
                 className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full backdrop-blur-md transition-all duration-200 ${image.isLikedByCurrentUser ? 'bg-red-500/80 text-white' : 'bg-white/20 text-white hover:bg-white/30'}`}
                 title={currentUser ? "Like this image" : "Login to like"}
               >
                 <Heart size={14} fill={image.isLikedByCurrentUser ? "currentColor" : "none"} />
                 <span className="text-xs font-bold">{image.likes}</span>
               </button>
            </div>
          ) : (
            <div className="flex justify-between items-end">
              <div className="text-xs text-gray-300 font-mono flex flex-col gap-1">
                <span className="opacity-75">ID: {image.authorId || 'UNKNOWN'}</span>
              </div>
              {image.generationTime && (
                <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white/10 backdrop-blur-md text-white">
                  <Hourglass size={12} />
                  <span className="text-xs font-bold">{image.generationTime.toFixed(1)}s</span>
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

export default ImageCard;