← back to Handbag Auth Nextjs
src/components/BagDetailModal.tsx
398 lines
'use client'
import { useState, useEffect } from 'react'
import type { MarketplaceBag } from '@/types/marketplace'
interface BagDetailModalProps {
bag: MarketplaceBag | null
isOpen: boolean
onClose: () => void
}
export function BagDetailModal({ bag, isOpen, onClose }: BagDetailModalProps) {
const [selectedTab, setSelectedTab] = useState<'overview' | 'chart' | 'details'>('overview')
// Close modal on Escape key
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
if (isOpen) {
document.addEventListener('keydown', handleEscape)
document.body.style.overflow = 'hidden'
}
return () => {
document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = 'unset'
}
}, [isOpen, onClose])
if (!isOpen || !bag) return null
// Calculate chart dimensions
const chartWidth = 600
const chartHeight = 300
const padding = 40
const maxPrice = Math.max(...bag.priceHistory.map(p => p.price))
const minPrice = Math.min(...bag.priceHistory.map(p => p.price))
const priceRange = maxPrice - minPrice
// Generate SVG path for price chart
const points = bag.priceHistory.map((point, index) => {
const x = padding + (index / (bag.priceHistory.length - 1)) * (chartWidth - padding * 2)
const y = chartHeight - padding - ((point.price - minPrice) / priceRange) * (chartHeight - padding * 2)
return { x, y, ...point }
})
const pathD = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ')
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className="relative w-full max-w-4xl max-h-[90vh] overflow-y-auto bg-gradient-to-br from-gray-900 to-black border border-amber-500/30 rounded-2xl shadow-2xl shadow-amber-500/20 m-4">
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 w-10 h-10 flex items-center justify-center bg-white/10 hover:bg-white/20 rounded-full transition-colors"
>
<span className="text-2xl text-white">×</span>
</button>
{/* Header */}
<div className="p-8 border-b border-white/10">
<div className="flex items-start gap-6">
{/* Image */}
<div className="w-48 h-48 rounded-xl overflow-hidden flex-shrink-0 bg-gray-800">
<img
src={bag.image}
alt={`${bag.brand} ${bag.model}`}
className="w-full h-full object-cover"
/>
</div>
{/* Title & Price */}
<div className="flex-1">
<div className="text-sm text-amber-400 font-semibold mb-2">{bag.brand}</div>
<h2 className="text-3xl font-bold mb-3">{bag.model}</h2>
<p className="text-gray-400 mb-4">{bag.material} • {bag.hardware} • {bag.color}</p>
<div className="flex items-center gap-6 mb-4">
<div>
<div className="text-sm text-gray-400">Total Value</div>
<div className="text-3xl font-bold text-amber-400">${bag.totalValue.toLocaleString()}</div>
</div>
<div>
<div className="text-sm text-gray-400">Price per Share</div>
<div className="text-2xl font-bold">${bag.pricePerShare}</div>
</div>
<div>
<div className="text-sm text-gray-400">Appreciation</div>
<div className="text-2xl font-bold text-green-400">+{bag.appreciation}%</div>
</div>
</div>
{/* Live Link to Last Sale */}
{bag.productUrl && (
<a
href={bag.productUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white font-semibold transition-colors"
>
<span>🔗</span>
View Original Listing on Yahoo Japan
<span className="text-xs opacity-75">↗</span>
</a>
)}
</div>
</div>
</div>
{/* Tabs */}
<div className="flex gap-1 px-8 pt-6 border-b border-white/10">
<button
onClick={() => setSelectedTab('overview')}
className={`px-6 py-3 font-semibold transition-all ${
selectedTab === 'overview'
? 'text-amber-400 border-b-2 border-amber-400'
: 'text-gray-400 hover:text-white'
}`}
>
Overview
</button>
<button
onClick={() => setSelectedTab('chart')}
className={`px-6 py-3 font-semibold transition-all ${
selectedTab === 'chart'
? 'text-amber-400 border-b-2 border-amber-400'
: 'text-gray-400 hover:text-white'
}`}
>
Price Chart
</button>
<button
onClick={() => setSelectedTab('details')}
className={`px-6 py-3 font-semibold transition-all ${
selectedTab === 'details'
? 'text-amber-400 border-b-2 border-amber-400'
: 'text-gray-400 hover:text-white'
}`}
>
Data Source
</button>
</div>
{/* Content */}
<div className="p-8">
{/* Overview Tab */}
{selectedTab === 'overview' && (
<div className="space-y-6">
{/* Last Sale Info */}
{bag.lastSale && (
<div className="bg-black/30 rounded-xl p-6 border border-green-500/30">
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
<span>💰</span> Last Sale Information
</h3>
<div className="grid grid-cols-3 gap-4">
<div>
<div className="text-sm text-gray-400">Date</div>
<div className="text-lg font-semibold">
{new Date(bag.lastSale.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</div>
</div>
<div>
<div className="text-sm text-gray-400">Price</div>
<div className="text-lg font-semibold text-amber-400">
${bag.lastSale.price.toLocaleString()}
</div>
</div>
<div>
<div className="text-sm text-gray-400">Source</div>
<div className="text-lg font-semibold">{bag.lastSale.source}</div>
</div>
</div>
</div>
)}
{/* Investment Details */}
<div className="bg-black/30 rounded-xl p-6 border border-amber-500/30">
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
<span>📊</span> Investment Details
</h3>
<div className="grid grid-cols-2 gap-4">
<div className="flex items-center justify-between p-4 bg-white/5 rounded-lg">
<span className="text-gray-400">Total Shares</span>
<span className="font-bold">{bag.totalShares}</span>
</div>
<div className="flex items-center justify-between p-4 bg-white/5 rounded-lg">
<span className="text-gray-400">Available</span>
<span className="font-bold text-green-400">{bag.sharesAvailable}</span>
</div>
<div className="flex items-center justify-between p-4 bg-white/5 rounded-lg">
<span className="text-gray-400">Min Investment</span>
<span className="font-bold">${bag.pricePerShare}</span>
</div>
<div className="flex items-center justify-between p-4 bg-white/5 rounded-lg">
<span className="text-gray-400">Total Value</span>
<span className="font-bold">${bag.totalValue.toLocaleString()}</span>
</div>
</div>
</div>
</div>
)}
{/* Chart Tab */}
{selectedTab === 'chart' && (
<div className="space-y-6">
<h3 className="text-2xl font-bold flex items-center gap-2">
<span>📈</span> Price History (2015-2025)
</h3>
{/* Interactive Stock Chart */}
<div className="bg-black/30 rounded-xl p-6 border border-blue-500/30">
<svg viewBox={`0 0 ${chartWidth} ${chartHeight}`} className="w-full">
{/* Grid lines */}
{[0, 0.25, 0.5, 0.75, 1].map((fraction) => {
const y = chartHeight - padding - fraction * (chartHeight - padding * 2)
const price = minPrice + fraction * priceRange
return (
<g key={fraction}>
<line
x1={padding}
y1={y}
x2={chartWidth - padding}
y2={y}
stroke="rgba(255,255,255,0.1)"
strokeWidth="1"
/>
<text
x={padding - 10}
y={y + 5}
fill="#9ca3af"
fontSize="12"
textAnchor="end"
>
${Math.round(price).toLocaleString()}
</text>
</g>
)
})}
{/* Area under curve */}
<defs>
<linearGradient id="chartGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#f59e0b" stopOpacity="0.3" />
<stop offset="100%" stopColor="#f59e0b" stopOpacity="0" />
</linearGradient>
</defs>
<path
d={`${pathD} L ${chartWidth - padding} ${chartHeight - padding} L ${padding} ${chartHeight - padding} Z`}
fill="url(#chartGradient)"
/>
{/* Price line */}
<path
d={pathD}
fill="none"
stroke="#f59e0b"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
{/* Data points */}
{points.map((point, index) => (
<g key={index}>
<circle
cx={point.x}
cy={point.y}
r="5"
fill="#f59e0b"
stroke="#000"
strokeWidth="2"
/>
<text
x={point.x}
y={chartHeight - 10}
fill="#9ca3af"
fontSize="12"
textAnchor="middle"
>
{point.year}
</text>
</g>
))}
</svg>
{/* Price change summary */}
<div className="mt-6 grid grid-cols-3 gap-4">
<div className="text-center p-4 bg-white/5 rounded-lg">
<div className="text-sm text-gray-400">Starting Price (2015)</div>
<div className="text-xl font-bold">
${bag.priceHistory[0]?.price.toLocaleString()}
</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-lg">
<div className="text-sm text-gray-400">Current Price (2025)</div>
<div className="text-xl font-bold text-amber-400">
${bag.priceHistory[bag.priceHistory.length - 1]?.price.toLocaleString()}
</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-lg">
<div className="text-sm text-gray-400">Total Growth</div>
<div className="text-xl font-bold text-green-400">
+{Math.round(
((bag.priceHistory[bag.priceHistory.length - 1]?.price - bag.priceHistory[0]?.price) /
bag.priceHistory[0]?.price) *
100
)}%
</div>
</div>
</div>
</div>
</div>
)}
{/* Data Source Tab */}
{selectedTab === 'details' && (
<div className="space-y-6">
<h3 className="text-2xl font-bold flex items-center gap-2">
<span>🔍</span> Data Transparency
</h3>
{/* Data Source */}
<div className="bg-green-900/20 rounded-xl p-6 border border-green-500/30">
<div className="flex items-start gap-3 mb-4">
<span className="text-2xl">✅</span>
<div className="flex-1">
<h4 className="text-lg font-bold text-green-400 mb-2">100% Real Data</h4>
<p className="text-gray-300 mb-3">{bag.dataSource}</p>
<p className="text-sm text-gray-400">{bag.methodology}</p>
</div>
</div>
{/* Confidence Score */}
<div className="mt-4 pt-4 border-t border-green-500/20">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-semibold text-gray-300">Data Confidence</span>
<span className="text-sm font-bold text-green-400">{bag.confidence}%</span>
</div>
<div className="w-full h-3 bg-gray-800 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-green-500 to-emerald-500"
style={{ width: `${bag.confidence}%` }}
/>
</div>
</div>
</div>
{/* Product URL */}
{bag.productUrl && (
<div className="bg-blue-900/20 rounded-xl p-6 border border-blue-500/30">
<h4 className="text-lg font-bold mb-3 flex items-center gap-2">
<span>🔗</span> Original Listing
</h4>
<p className="text-sm text-gray-400 mb-3">
This bag is currently listed on Yahoo Japan Auctions. Click below to view the original listing with full details and photos.
</p>
<a
href={bag.productUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg text-white font-semibold transition-all transform hover:scale-105"
>
<span>🌐</span>
Open Yahoo Japan Listing
<span className="text-xs opacity-75">↗</span>
</a>
<p className="text-xs text-gray-500 mt-3">
Link: {bag.productUrl.substring(0, 60)}...
</p>
</div>
)}
</div>
)}
</div>
{/* Footer CTA */}
<div className="p-8 border-t border-white/10 bg-gradient-to-r from-amber-900/20 to-orange-900/20">
<button className="w-full px-6 py-4 bg-gradient-to-r from-yellow-400 to-amber-500 rounded-xl text-black font-bold text-lg hover:shadow-2xl hover:shadow-amber-500/50 transition-all transform hover:scale-105">
Invest in This Bag - Start from ${bag.pricePerShare}
</button>
</div>
</div>
</div>
)
}