← back to Handbag Auth Nextjs

src/components/ListingGrid.tsx

225 lines

'use client'

import Image from 'next/image'
import Link from 'next/link'
import { useState } from 'react'

interface ListingGridProps {
  listings: any[]
  loading: boolean
}

export function ListingGrid({ listings, loading }: ListingGridProps) {
  const [imageErrors, setImageErrors] = useState<Set<number>>(new Set())
  const [loadedImages, setLoadedImages] = useState<Set<number>>(new Set())

  const handleImageError = (id: number) => {
    setImageErrors(prev => new Set(prev).add(id))
  }

  const handleImageLoad = (id: number) => {
    setLoadedImages(prev => new Set(prev).add(id))
  }

  if (loading) {
    return (
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
        {[...Array(6)].map((_, i) => (
          <div key={i} className="bg-white rounded-2xl shadow-lg overflow-hidden animate-pulse">
            <div className="w-full h-64 sm:h-80 bg-gradient-to-br from-gray-200 to-gray-300"></div>
            <div className="p-4 sm:p-5 space-y-3">
              <div className="h-4 bg-gray-300 rounded-full w-3/4"></div>
              <div className="h-4 bg-gray-300 rounded-full w-1/2"></div>
              <div className="h-8 bg-gray-300 rounded-lg w-full"></div>
            </div>
          </div>
        ))}
      </div>
    )
  }

  if (listings.length === 0) {
    return (
      <div className="text-center py-16 px-4">
        <div className="w-20 h-20 mx-auto mb-4 bg-gradient-to-br from-purple-100 to-blue-100 rounded-full flex items-center justify-center">
          <svg
            className="w-10 h-10 text-purple-600"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
            />
          </svg>
        </div>
        <h3 className="text-xl font-bold text-gray-900 mb-2">No listings found</h3>
        <p className="text-gray-500 mb-6">Try adjusting your filters or search terms</p>
        <button
          onClick={() => window.location.reload()}
          className="px-6 py-3 bg-gradient-to-r from-purple-600 to-blue-500 text-white rounded-lg font-semibold hover:shadow-lg transition-all"
          style={{ fontSize: '16px', minHeight: '48px' }}
        >
          Reset Filters
        </button>
      </div>
    )
  }

  return (
    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
      {listings.map((listing: any, index: number) => (
        <div
          key={listing.id}
          className="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transition-all duration-200 transform hover:-translate-y-1 active:scale-98 cursor-pointer"
          style={{
            animation: `fadeIn 0.3s ease-out ${index * 0.05}s both`
          }}
        >
          {/* Image Section */}
          <div className="relative w-full h-64 sm:h-80 bg-gradient-to-br from-gray-100 to-gray-200 overflow-hidden">
            {(listing.imageUrl || listing.thumbnailUrl) && !imageErrors.has(listing.id) ? (
              <>
                <div
                  className={`absolute inset-0 bg-gradient-to-br from-purple-100 to-blue-100 transition-opacity duration-500 ${
                    loadedImages.has(listing.id) ? 'opacity-0' : 'opacity-100'
                  }`}
                  style={{
                    filter: 'blur(20px)',
                    transform: 'scale(1.1)'
                  }}
                />
                <Image
                  src={listing.imageUrl || listing.thumbnailUrl}
                  alt={listing.title || 'Handbag'}
                  fill
                  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
                  className={`object-cover transition-all duration-500 ${
                    loadedImages.has(listing.id) ? 'opacity-100 scale-100' : 'opacity-0 scale-105'
                  }`}
                  unoptimized
                  loading={index < 3 ? 'eager' : 'lazy'}
                  onLoad={() => handleImageLoad(listing.id)}
                  onError={() => handleImageError(listing.id)}
                />
              </>
            ) : (
              <div className="w-full h-full flex items-center justify-center">
                <svg
                  className="w-16 h-16 text-gray-300"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke="currentColor"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth={1.5}
                    d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
                  />
                </svg>
              </div>
            )}

            {/* Deal Badge */}
            {listing.dealAnalysis?.isDeal === 1 && listing.dealAnalysis?.dealPercentage && (
              <div className="absolute top-3 right-3 bg-gradient-to-r from-red-500 to-pink-500 text-white px-3 py-1.5 rounded-full text-sm font-bold shadow-lg animate-pulse">
                {Math.round(listing.dealAnalysis.dealPercentage)}% OFF
              </div>
            )}

            {/* Brand Badge */}
            <div className="absolute bottom-3 left-3 bg-white/95 backdrop-blur-sm px-3 py-1.5 rounded-full shadow-lg">
              <span className="text-xs font-bold text-purple-600 uppercase tracking-wide">{listing.brand}</span>
            </div>
          </div>

          {/* Content Section */}
          <div className="p-4 sm:p-5">
            {/* Title & Condition */}
            <div className="flex items-start justify-between gap-2 mb-3">
              <h3 className="text-base sm:text-lg font-bold text-gray-900 line-clamp-2 flex-1">
                {listing.title}
              </h3>
              {listing.condition && (
                <span className="text-xs text-gray-700 bg-gray-100 px-2.5 py-1 rounded-full font-medium whitespace-nowrap">
                  {listing.condition}
                </span>
              )}
            </div>

            {/* Model */}
            {listing.model && (
              <p className="text-sm text-gray-600 mb-3 font-medium line-clamp-1">
                {listing.model}
              </p>
            )}

            {/* Price Section - Mobile Optimized */}
            <div className="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl p-3 sm:p-4 mb-4">
              <div className="flex items-center justify-between">
                <div>
                  <p className="text-2xl sm:text-3xl font-bold text-gray-900">
                    ${listing.priceUsd?.toLocaleString() || 'N/A'}
                  </p>
                  {listing.priceJpy && (
                    <p className="text-xs sm:text-sm text-gray-600 mt-1">
                      ¥{listing.priceJpy.toLocaleString()}
                    </p>
                  )}
                </div>

                {listing.dealAnalysis?.avgUsPrice && (
                  <div className="text-right">
                    <p className="text-xs sm:text-sm text-gray-500 line-through">
                      ${listing.dealAnalysis.avgUsPrice.toLocaleString()}
                    </p>
                    <p className="text-xs sm:text-sm font-bold text-green-600">
                      Save ${(listing.dealAnalysis.avgUsPrice - listing.priceUsd).toLocaleString()}
                    </p>
                  </div>
                )}
              </div>
            </div>

            {/* Quick Info - Simplified for Mobile */}
            <div className="grid grid-cols-2 gap-2 mb-4 text-xs">
              {listing.listingType && (
                <div className="bg-gray-50 rounded-lg p-2">
                  <p className="text-gray-500 uppercase tracking-wide font-medium mb-0.5">Type</p>
                  <p className={`font-bold ${listing.listingType === 'auction' ? 'text-yellow-700' : 'text-blue-700'}`}>
                    {listing.listingType.toUpperCase()}
                  </p>
                </div>
              )}

              {listing.auctionEndDate && (
                <div className="bg-gray-50 rounded-lg p-2">
                  <p className="text-gray-500 uppercase tracking-wide font-medium mb-0.5">Ends</p>
                  <p className="text-gray-900 font-bold">{listing.auctionEndDate}</p>
                </div>
              )}
            </div>

            {/* Action Button - Mobile Optimized */}
            {listing.productUrl && (
              <a
                href={listing.productUrl}
                target="_blank"
                rel="noopener noreferrer"
                className="block w-full bg-gradient-to-r from-purple-600 to-blue-600 text-white text-center px-4 py-3 sm:py-3.5 rounded-xl font-bold text-sm sm:text-base hover:from-purple-700 hover:to-blue-700 transition-all shadow-md hover:shadow-xl active:scale-95"
                style={{ fontSize: '16px', minHeight: '48px' }}
              >
                View Listing →
              </a>
            )}
          </div>
        </div>
      ))}
    </div>
  )
}