← back to Handbag Auth Nextjs

src/app/marketplace/page.tsx

459 lines

'use client'

import { useState, useEffect } from 'react'
import Link from 'next/link'
import toast, { Toaster } from 'react-hot-toast'
import { DataAttribution } from '@/components/DataAttribution'
import { BagDetailModal } from '@/components/BagDetailModal'
import type { MarketplaceBag, MarketplaceStats } from '@/types/marketplace'

export default function Marketplace() {
  const [filter, setFilter] = useState('all')
  const [sort, setSort] = useState('best-deal')
  const [bags, setBags] = useState<MarketplaceBag[]>([])
  const [loading, setLoading] = useState(true)
  const [stats, setStats] = useState<MarketplaceStats | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [selectedBag, setSelectedBag] = useState<MarketplaceBag | null>(null)
  const [isModalOpen, setIsModalOpen] = useState(false)

  // Sort bags when sort option changes
  const sortBags = (bagsToSort: MarketplaceBag[], sortBy: string) => {
    const sorted = [...bagsToSort]

    switch (sortBy) {
      case 'best-deal':
        // Sort by highest appreciation (best deals first)
        return sorted.sort((a, b) => b.appreciation - a.appreciation)

      case 'price-low':
        return sorted.sort((a, b) => a.totalValue - b.totalValue)

      case 'price-high':
        return sorted.sort((a, b) => b.totalValue - a.totalValue)

      case 'appreciation':
        return sorted.sort((a, b) => b.appreciation - a.appreciation)

      case 'trending':
      default:
        // Keep original order (most recent/trending)
        return sorted
    }
  }

  // Re-sort when sort option changes
  useEffect(() => {
    setBags(currentBags => sortBags(currentBags, sort))
  }, [sort])

  // Load bags when filter changes (single useEffect - no duplicates)
  useEffect(() => {
    loadBags(filter)
  }, [filter])

  const handleBagClick = (bag: MarketplaceBag) => {
    setSelectedBag(bag)
    setIsModalOpen(true)
  }

  const loadBags = async (brand: string = 'all') => {
    setLoading(true)
    setError(null)

    const url = brand === 'all'
      ? '/api/marketplace/real'
      : `/api/marketplace/real?brand=${brand}`

    try {
      const res = await fetch(url)
      const data = await res.json()

      if (data.success) {
        // Sort bags by best deal (highest appreciation) by default
        const sortedBags = sortBags(data.bags, sort)
        setBags(sortedBags)
        setStats(data.stats)
      } else {
        throw new Error(data.error || 'Failed to load handbags')
      }
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Failed to load handbags'
      console.error('Failed to load real data:', err)
      setError(errorMessage)
      toast.error(errorMessage)
    } finally {
      setLoading(false)
    }
  }


  // Mini price chart component
  const MiniPriceChart = ({ data }: { data: { year: number; price: number }[] }) => {
    if (!data || data.length < 2) return null

    const maxPrice = Math.max(...data.map(d => d.price))
    const minPrice = Math.min(...data.map(d => d.price))
    const range = maxPrice - minPrice

    const points = data
      .map((d, i) => {
        const x = (i / (data.length - 1)) * 100
        const y = 100 - ((d.price - minPrice) / range) * 100
        return `${x},${y}`
      })
      .join(' ')

    return (
      <div className="relative h-20 mb-4">
        <svg viewBox="0 0 100 100" className="w-full h-full" preserveAspectRatio="none">
          <defs>
            <linearGradient id={`gradient-${data[0].year}`} x1="0%" y1="0%" x2="100%" y2="0%">
              <stop offset="0%" stopColor="#fbbf24" />
              <stop offset="100%" stopColor="#f59e0b" />
            </linearGradient>
          </defs>
          <polyline
            fill="none"
            stroke={`url(#gradient-${data[0].year})`}
            strokeWidth="2"
            points={points}
            vectorEffect="non-scaling-stroke"
          />
        </svg>
        <div className="absolute bottom-0 left-0 right-0 flex justify-between text-xs text-gray-500">
          <span>{data[0].year}</span>
          <span className="text-amber-400 font-bold">${(data[data.length - 1].price).toLocaleString()}</span>
          <span>{data[data.length - 1].year}</span>
        </div>
      </div>
    )
  }

  return (
    <div className="min-h-screen bg-black text-white">
      <Toaster position="top-right" />
      <BagDetailModal bag={selectedBag} isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />

      {/* Navigation */}
      <nav className="fixed top-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-xl border-b border-white/10">
        <div className="max-w-7xl mx-auto px-6 py-4">
          <div className="flex items-center justify-between">
            <Link href="/" className="flex items-center space-x-3">
              <div className="w-12 h-12 bg-gradient-to-br from-yellow-400 via-amber-500 to-orange-600 rounded-xl flex items-center justify-center shadow-2xl shadow-amber-500/50">
                <span className="text-2xl">💎</span>
              </div>
              <div>
                <div className="text-xl font-bold tracking-tight">LUXVAULT</div>
                <div className="text-xs text-gray-400 tracking-wide">Marketplace</div>
              </div>
            </Link>

            <div className="flex items-center space-x-4">
              <Link href="/admin" className="px-6 py-2.5 text-sm font-semibold text-white hover:text-gray-300 transition-colors">
                Admin
              </Link>
              <button className="px-6 py-2.5 bg-gradient-to-r from-yellow-400 to-amber-500 rounded-xl text-black font-bold text-sm hover:shadow-2xl hover:shadow-amber-500/50 transition-all transform hover:scale-105">
                Connect Wallet
              </button>
            </div>
          </div>
        </div>
      </nav>

      {/* Header */}
      <div className="pt-32 pb-12 bg-gradient-to-b from-gray-900 to-black">
        <div className="max-w-7xl mx-auto px-6">
          <h1 className="text-5xl md:text-6xl font-extrabold mb-4">
            Discover <span className="bg-gradient-to-r from-amber-400 to-orange-500 bg-clip-text text-transparent">Luxury</span>
          </h1>
          <p className="text-xl text-gray-400 mb-8">Browse authenticated luxury handbags with historical price data</p>

          {/* Filters */}
          <div className="flex flex-wrap gap-4 items-center justify-between">
            <div className="flex flex-wrap gap-3">
              <button
                onClick={() => setFilter('all')}
                className={`px-6 py-2.5 rounded-xl font-semibold text-sm transition-all ${
                  filter === 'all'
                    ? 'bg-amber-500 text-black'
                    : 'bg-white/10 text-gray-400 hover:bg-white/20'
                }`}
              >
                All Brands
              </button>
              <button
                onClick={() => setFilter('hermes')}
                className={`px-6 py-2.5 rounded-xl font-semibold text-sm transition-all ${
                  filter === 'hermes'
                    ? 'bg-amber-500 text-black'
                    : 'bg-white/10 text-gray-400 hover:bg-white/20'
                }`}
              >
                Hermès
              </button>
              <button
                onClick={() => setFilter('chanel')}
                className={`px-6 py-2.5 rounded-xl font-semibold text-sm transition-all ${
                  filter === 'chanel'
                    ? 'bg-amber-500 text-black'
                    : 'bg-white/10 text-gray-400 hover:bg-white/20'
                }`}
              >
                Chanel
              </button>
              <button
                onClick={() => setFilter('lv')}
                className={`px-6 py-2.5 rounded-xl font-semibold text-sm transition-all ${
                  filter === 'lv'
                    ? 'bg-amber-500 text-black'
                    : 'bg-white/10 text-gray-400 hover:bg-white/20'
                }`}
              >
                Louis Vuitton
              </button>
            </div>

            <select
              value={sort}
              onChange={(e) => setSort(e.target.value)}
              className="px-6 py-2.5 bg-white/10 border border-white/20 rounded-xl text-white font-semibold text-sm focus:outline-none focus:border-amber-500"
            >
              <option value="best-deal">🔥 Best Deals (Default)</option>
              <option value="appreciation">Highest Appreciation</option>
              <option value="price-high">Price: High to Low</option>
              <option value="price-low">Price: Low to High</option>
              <option value="trending">Trending</option>
            </select>
          </div>
        </div>
      </div>

      {/* Marketplace Grid */}
      <div className="py-12 bg-black">
        <div className="max-w-7xl mx-auto px-6">

          {/* Loading State */}
          {loading && (
            <div className="text-center py-20">
              <div className="inline-block animate-spin rounded-full h-16 w-16 border-t-4 border-b-4 border-amber-500 mb-4"></div>
              <p className="text-gray-400 text-lg">Loading REAL handbags from database...</p>
              <p className="text-sm text-gray-500 mt-2">Querying 27,927 listings from Yahoo Japan</p>
            </div>
          )}

          {/* Results */}
          {!loading && bags.length === 0 && (
            <div className="text-center py-20">
              <p className="text-2xl text-gray-400">No bags found for this brand</p>
              <button
                onClick={() => setFilter('all')}
                className="mt-4 px-6 py-3 bg-amber-500 rounded-xl hover:bg-amber-600 transition"
              >
                Show All Brands
              </button>
            </div>
          )}

          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
            {!loading && bags.map((bag) => (
              <div
                key={bag.id}
                onClick={() => handleBagClick(bag)}
                className="group relative bg-gradient-to-br from-gray-900 to-black border border-white/10 rounded-3xl overflow-hidden hover:border-amber-500/50 transition-all duration-500 hover:shadow-2xl hover:shadow-amber-500/20 transform hover:-translate-y-2 cursor-pointer"
              >
                {/* Appreciation Badge */}
                <div className="absolute top-4 right-4 z-10 px-4 py-2 bg-green-500/90 backdrop-blur-sm rounded-full text-xs font-bold text-black">
                  +{bag.appreciation}%
                </div>

                {/* Image */}
                <div className="relative w-full h-64 bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center overflow-hidden">
                  <img
                    src={bag.image}
                    alt={`${bag.brand} ${bag.model}`}
                    className="w-full h-full object-cover"
                  />
                  <div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-black to-transparent"></div>
                </div>

                {/* Content */}
                <div className="p-6">
                  <div className="mb-4">
                    <div className="text-sm text-amber-400 font-semibold mb-1">{bag.brand}</div>
                    <h3 className="text-2xl font-bold mb-2">{bag.model}</h3>
                    <p className="text-sm text-gray-400">
                      {bag.material} • {bag.hardware} • {bag.color}
                    </p>
                  </div>

                  <div className="space-y-3 mb-6">
                    {/* COMMENTED OUT - SHARES FUNCTIONALITY
                    <div className="flex items-center justify-between">
                      <span className="text-gray-400 text-sm">Price per Share</span>
                      <span className="text-2xl font-bold text-amber-400">${bag.pricePerShare}</span>
                    </div>
                    */}
                    <div className="flex items-center justify-between">
                      <span className="text-gray-400 text-sm">Current Price</span>
                      <span className="text-2xl font-bold text-amber-400">${bag.totalValue.toLocaleString()}</span>
                    </div>
                    {/* COMMENTED OUT - SHARES FUNCTIONALITY
                    <div className="flex items-center justify-between">
                      <span className="text-gray-400 text-sm">Available Shares</span>
                      <span className="text-sm font-semibold">{bag.sharesAvailable.toLocaleString()} / {bag.totalShares.toLocaleString()}</span>
                    </div>
                    */}
                  </div>

                  {/* COMMENTED OUT - SHARES PROGRESS BAR
                  <div className="mb-6">
                    <div className="w-full h-2 bg-gray-800 rounded-full overflow-hidden">
                      <div
                        className="h-full bg-gradient-to-r from-amber-400 to-orange-500"
                        style={{ width: `${(bag.sharesAvailable / bag.totalShares) * 100}%` }}
                      ></div>
                    </div>
                    <p className="text-xs text-gray-500 mt-2">
                      {((bag.sharesAvailable / bag.totalShares) * 100).toFixed(0)}% available
                    </p>
                  </div>
                  */}

                  {/* Action Buttons */}
                  <div className="flex gap-3 mb-6">
                    {/* COMMENTED OUT - SHARES FUNCTIONALITY
                    <button className="flex-1 px-6 py-3 bg-gradient-to-r from-yellow-400 to-amber-500 rounded-xl text-black font-bold text-sm hover:shadow-2xl hover:shadow-amber-500/50 transition-all transform hover:scale-105">
                      Invest Now
                    </button>
                    */}
                    <button className="flex-1 px-6 py-3 bg-gradient-to-r from-yellow-400 to-amber-500 rounded-xl text-black font-bold text-sm hover:shadow-2xl hover:shadow-amber-500/50 transition-all transform hover:scale-105">
                      View Listing
                    </button>
                    <button className="px-6 py-3 bg-white/10 border border-white/20 rounded-xl text-white font-semibold text-sm hover:bg-white/20 transition-all">
                      Details
                    </button>
                  </div>

                  {/* Price History Chart */}
                  <div className="border-t border-white/10 pt-4 mb-4">
                    <h4 className="text-xs font-semibold text-gray-400 mb-3">PRICE HISTORY (2015-2025)</h4>
                    <MiniPriceChart data={bag.priceHistory} />
                  </div>

                  {/* Last Sale Info */}
                  {bag.lastSale && (
                    <div className="bg-black/30 rounded-xl p-4 mb-4">
                      <div className="flex items-center justify-between mb-2">
                        <span className="text-xs font-semibold text-gray-400">LAST SALE</span>
                        <span className="text-xs text-green-400">✓ Verified</span>
                      </div>
                      <div className="flex items-center justify-between">
                        <span className="text-sm text-gray-300">{new Date(bag.lastSale.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}</span>
                        <span className="text-lg font-bold text-amber-400">${bag.lastSale.price.toLocaleString()}</span>
                      </div>
                      <p className="text-xs text-gray-500 mt-1">Source: {bag.lastSale.source}</p>
                    </div>
                  )}

                  {/* Data Source Attribution */}
                  <div className="border-t border-white/10 pt-4">
                    <div className="space-y-2">
                      <div className="flex items-start gap-2">
                        <span className="text-sm">📊</span>
                        <div className="flex-1">
                          <p className="text-xs font-medium text-blue-400">{bag.dataSource}</p>
                          <p className="text-xs text-gray-500 mt-1">{bag.methodology}</p>
                        </div>
                      </div>
                      <div className="flex items-center gap-2">
                        <span className="text-sm">✅</span>
                        <div className="flex-1">
                          <p className="text-xs text-gray-400">Confidence: {bag.confidence}%</p>
                          <div className="w-full h-1.5 bg-gray-800 rounded-full overflow-hidden mt-1">
                            <div
                              className="h-full bg-gradient-to-r from-green-500 to-emerald-500"
                              style={{ width: `${bag.confidence}%` }}
                            />
                          </div>
                        </div>
                      </div>
                      <div className="flex items-center gap-2">
                        <span className="text-sm">⏰</span>
                        <p className="text-xs text-gray-400">Updated: Static estimate (not live pricing)</p>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Overall Data Attribution Section - REAL DATABASE */}
      <div className="py-16 bg-gradient-to-b from-black to-gray-900">
        <div className="max-w-7xl mx-auto px-6">
          <h2 className="text-3xl font-bold mb-8 text-center">Data Transparency - REAL DATABASE</h2>

          {stats && (
            <div className="bg-black/40 rounded-2xl p-8 mb-8 border border-green-500">
              <h3 className="text-2xl font-bold text-green-400 mb-6">✅ 100% REAL DATA FROM YAHOO JAPAN AUCTIONS</h3>
              <div className="grid grid-cols-2 md:grid-cols-4 gap-6">
                <div className="text-center">
                  <div className="text-4xl font-bold text-white">{stats.totalListings?.toLocaleString()}</div>
                  <div className="text-sm text-gray-400 mt-2">Total Real Listings</div>
                </div>
                <div className="text-center">
                  <div className="text-4xl font-bold text-white">{stats.totalBrands}</div>
                  <div className="text-sm text-gray-400 mt-2">Luxury Brands</div>
                </div>
                <div className="text-center">
                  <div className="text-4xl font-bold text-white">${stats.avgPrice?.toLocaleString()}</div>
                  <div className="text-sm text-gray-400 mt-2">Average Price</div>
                </div>
                <div className="text-center">
                  <div className="text-4xl font-bold text-green-400">100%</div>
                  <div className="text-sm text-gray-400 mt-2">Data Confidence</div>
                </div>
              </div>
              <div className="mt-6 pt-6 border-t border-white/10">
                <p className="text-sm text-gray-300">📅 Last Crawl: {stats.lastCrawl}</p>
                <p className="text-sm text-gray-300">🗄️ Database: /root/Projects/handbag-auth-nextjs/data/handbags.db</p>
                <p className="text-sm text-gray-300">🔗 Source: Yahoo Japan Auctions (Nov 8-14, 2025)</p>
              </div>
            </div>
          )}

          <DataAttribution
            sources={[
              {
                type: 'database',
                name: 'REAL Yahoo Japan Auction Database',
                description: `${stats?.totalListings.toLocaleString()} live luxury handbag listings with actual prices, images, and product URLs`
              },
              {
                type: 'api',
                name: 'Live Crawler System',
                description: 'Automated scraping from Yahoo Japan specialist luxury sellers'
              }
            ]}
            lastUpdated={`Real-time from database (last crawl: ${stats?.lastCrawl || 'Loading...'})`}
            confidence={100}
            showPremiumUpsell={false}
          />
        </div>
      </div>

      {/* Footer */}
      <footer className="relative py-12 bg-black border-t border-white/10">
        <div className="max-w-7xl mx-auto px-6 text-center">
          <p className="text-gray-500 text-sm">
            © 2025 LuxVault. All rights reserved. Securities offered through regulated broker-dealer.
          </p>
        </div>
      </footer>
    </div>
  )
}