← back to Wine Finder Next

components/LazyWineCard.tsx

70 lines

'use client';

import dynamic from 'next/dynamic';
import { useState, useEffect, useRef } from 'react';

// Lazy load the actual WineCard component
const WineCard = dynamic(() => import('./WineCard'), {
  loading: () => <WineCardSkeleton />,
  ssr: false,
});

// Skeleton loader
function WineCardSkeleton() {
  return (
    <div className="bg-white rounded-xl p-4 shadow-md animate-pulse">
      <div className="w-full h-48 bg-gray-300 rounded-lg mb-4"></div>
      <div className="h-6 bg-gray-300 rounded mb-2"></div>
      <div className="h-4 bg-gray-300 rounded mb-2 w-3/4"></div>
      <div className="h-4 bg-gray-300 rounded mb-4 w-1/2"></div>
      <div className="h-10 bg-gray-300 rounded"></div>
    </div>
  );
}

interface LazyWineCardProps {
  wine: any;
  onPriceCheck?: (wine: any) => void;
}

export default function LazyWineCard({ wine, onPriceCheck }: LazyWineCardProps) {
  const [isVisible, setIsVisible] = useState(false);
  const cardRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            setIsVisible(true);
            observer.unobserve(entry.target);
          }
        });
      },
      {
        rootMargin: '100px',
        threshold: 0.01,
      }
    );

    if (cardRef.current) {
      observer.observe(cardRef.current);
    }

    return () => {
      if (cardRef.current) {
        observer.unobserve(cardRef.current);
      }
    };
  }, []);

  return (
    <div ref={cardRef}>
      {isVisible ? (
        <WineCard wine={wine} onPriceCheck={onPriceCheck} />
      ) : (
        <WineCardSkeleton />
      )}
    </div>
  );
}