← back to Handbag Auth Nextjs

src/components/DealsGrid.tsx

288 lines

'use client'

import { useState, useEffect } from 'react'
import Image from 'next/image'

interface Deal {
  type: 'listing' | 'auction' | 'arbitrage'
  id: string | number
  title: string
  description?: string
  price: number
  originalPrice?: number
  currency: string
  savings: number
  savingsPercent: number
  url?: string
  imageUrl?: string
  brand?: string
  model?: string
  condition?: string
  source: string
  metadata: Record<string, any>
}

interface DealsGridProps {
  limit?: number
  autoRefresh?: boolean
  refreshInterval?: number
  filterType?: 'all' | 'listing' | 'auction' | 'arbitrage'
}

export function DealsGrid({ limit = 10, autoRefresh = false, refreshInterval = 60000, filterType = 'all' }: DealsGridProps) {
  const [deals, setDeals] = useState<Deal[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [stats, setStats] = useState<any>(null)

  const fetchDeals = async () => {
    try {
      setLoading(true)
      const response = await fetch(`/api/top-deals?limit=${limit}`)
      const data = await response.json()

      if (data.success) {
        setDeals(data.deals)
        setStats(data.stats)
        setError(null)
      } else {
        setError(data.error || 'Failed to load deals')
      }
    } catch (err) {
      setError('Failed to load deals')
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    fetchDeals()

    if (autoRefresh) {
      const interval = setInterval(fetchDeals, refreshInterval)
      return () => clearInterval(interval)
    }
  }, [limit, autoRefresh, refreshInterval, filterType])

  const getTypeColor = (type: string) => {
    switch (type) {
      case 'listing':
        return 'bg-blue-500'
      case 'auction':
        return 'bg-purple-500'
      case 'arbitrage':
        return 'bg-green-500'
      default:
        return 'bg-gray-500'
    }
  }

  const getTypeIcon = (type: string) => {
    switch (type) {
      case 'listing':
        return '🛍️'
      case 'auction':
        return '🔨'
      case 'arbitrage':
        return '💰'
      default:
        return '📦'
    }
  }

  const formatPrice = (price: number, currency: string = 'USD') => {
    const symbol = currency === 'JPY' ? '¥' : currency === 'HKD' ? 'HK$' : '$'
    return `${symbol}${price.toLocaleString()}`
  }

  if (loading && deals.length === 0) {
    return (
      <div className="flex items-center justify-center py-12">
        <div className="text-center">
          <div className="animate-spin h-12 w-12 border-4 border-purple-500 border-t-transparent rounded-full mx-auto mb-4"></div>
          <p className="text-gray-600">Loading top deals...</p>
        </div>
      </div>
    )
  }

  if (error) {
    return (
      <div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
        <p className="text-red-700">{error}</p>
        <button
          onClick={fetchDeals}
          className="mt-4 px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
        >
          Retry
        </button>
      </div>
    )
  }

  return (
    <div>
      {/* Stats Bar */}
      {stats && (
        <div className="mb-6 bg-gradient-to-r from-purple-50 to-pink-50 rounded-lg p-4 border border-purple-200">
          <div className="flex flex-wrap items-center justify-between gap-4">
            <div className="flex items-center gap-6">
              <div>
                <span className="text-sm text-gray-600">Total Deals</span>
                <p className="text-2xl font-bold text-purple-600">{deals.length}</p>
              </div>
              <div>
                <span className="text-sm text-gray-600">Avg Savings</span>
                <p className="text-2xl font-bold text-green-600">{stats.avgSavings?.toFixed(1)}%</p>
              </div>
            </div>
            <div className="flex gap-3 text-sm">
              {stats.listings > 0 && (
                <span className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full">
                  🛍️ {stats.listings} Listings
                </span>
              )}
              {stats.auctions > 0 && (
                <span className="px-3 py-1 bg-purple-100 text-purple-800 rounded-full">
                  🔨 {stats.auctions} Auctions
                </span>
              )}
              {stats.arbitrage > 0 && (
                <span className="px-3 py-1 bg-green-100 text-green-800 rounded-full">
                  💰 {stats.arbitrage} Arbitrage
                </span>
              )}
            </div>
          </div>
        </div>
      )}

      {/* Deals Grid */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-6">
        {deals.filter(deal => filterType === 'all' || deal.type === filterType).map((deal, index) => (
          <div
            key={`${deal.type}-${deal.id}`}
            className="group bg-white rounded-xl shadow-md hover:shadow-2xl transition-all duration-300 overflow-hidden border border-gray-200 hover:border-purple-500 cursor-pointer transform hover:-translate-y-1"
            onClick={() => {
              if (deal.url) {
                window.open(deal.url, '_blank')
              }
            }}
          >
            {/* Deal Rank Badge */}
            <div className="absolute top-2 left-2 z-10">
              <div className="bg-gradient-to-r from-yellow-400 to-orange-500 text-white font-bold text-sm px-3 py-1 rounded-full shadow-lg">
                #{index + 1}
              </div>
            </div>

            {/* Type Badge */}
            <div className="absolute top-2 right-2 z-10">
              <div className={`${getTypeColor(deal.type)} text-white text-xs px-2 py-1 rounded-full shadow-lg`}>
                {getTypeIcon(deal.type)} {deal.type.toUpperCase()}
              </div>
            </div>

            {/* Image */}
            <div className="relative h-48 bg-gray-100 overflow-hidden">
              {deal.imageUrl ? (
                <img
                  src={deal.imageUrl}
                  alt={deal.title}
                  className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"
                  loading="lazy"
                />
              ) : (
                <div className="flex items-center justify-center h-full bg-gradient-to-br from-purple-100 to-pink-100">
                  <span className="text-6xl">{getTypeIcon(deal.type)}</span>
                </div>
              )}

              {/* Savings Badge - Overlaid on image */}
              <div className="absolute bottom-2 left-2 right-2">
                <div className="bg-red-600 text-white font-bold text-center py-2 rounded-lg shadow-lg">
                  <div className="text-2xl">{deal.savingsPercent.toFixed(0)}% OFF</div>
                  <div className="text-xs">Save {formatPrice(deal.savings)}</div>
                </div>
              </div>
            </div>

            {/* Content */}
            <div className="p-4">
              {/* Brand */}
              {deal.brand && (
                <div className="text-xs font-semibold text-purple-600 uppercase tracking-wide mb-1">
                  {deal.brand}
                </div>
              )}

              {/* Title */}
              <h3 className="font-bold text-gray-900 mb-2 line-clamp-2 min-h-[48px] group-hover:text-purple-600 transition-colors">
                {deal.title}
              </h3>

              {/* Description */}
              {deal.description && (
                <p className="text-xs text-gray-600 mb-3 line-clamp-2 min-h-[32px]">
                  {deal.description}
                </p>
              )}

              {/* Price Info */}
              <div className="space-y-1 mb-3">
                <div className="flex items-baseline gap-2">
                  <span className="text-2xl font-bold text-purple-600">
                    {formatPrice(deal.price, deal.currency)}
                  </span>
                  {deal.originalPrice && (
                    <span className="text-sm text-gray-500 line-through">
                      {formatPrice(deal.originalPrice, deal.currency)}
                    </span>
                  )}
                </div>
              </div>

              {/* Metadata */}
              <div className="flex flex-wrap gap-1 mb-3">
                {deal.condition && (
                  <span className="text-xs bg-gray-100 text-gray-700 px-2 py-1 rounded">
                    {deal.condition}
                  </span>
                )}
                {deal.metadata.verified && (
                  <span className="text-xs bg-green-100 text-green-700 px-2 py-1 rounded">
                    ✓ Verified
                  </span>
                )}
              </div>

              {/* Source */}
              <div className="text-xs text-gray-500 border-t pt-2">
                {deal.source}
              </div>

              {/* CTA */}
              <button className="w-full mt-3 bg-gradient-to-r from-purple-600 to-pink-600 text-white font-semibold py-2 rounded-lg hover:from-purple-700 hover:to-pink-700 transition-all transform group-hover:scale-105">
                View Deal →
              </button>
            </div>
          </div>
        ))}
      </div>

      {/* Empty State */}
      {deals.length === 0 && !loading && (
        <div className="text-center py-12 bg-gray-50 rounded-lg">
          <p className="text-gray-600 text-lg mb-4">No deals available at the moment</p>
          <button
            onClick={fetchDeals}
            className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
          >
            Refresh
          </button>
        </div>
      )}
    </div>
  )
}