← back to Wine Finder Next
components/WineCard.tsx
263 lines
'use client'
import { useState, useEffect } from 'react'
import Image from 'next/image'
import PriceChart from './PriceChart'
interface WineCardProps {
wine: {
name: string
vintage?: string
price: string
rating?: number
image?: string
source: string
url?: string
badge?: 'new' | 'deal' | 'featured'
availability?: string
winery?: string
region?: string
country?: string
type?: string
varietal?: string
alcohol?: string
description?: string
reviews?: number
priceHistory?: {
previousPrice?: string
priceChange?: string
}
}
onPriceCheck?: (wine: any) => void
}
export default function WineCard({ wine }: WineCardProps) {
const [showChart, setShowChart] = useState(false)
const [chartData, setChartData] = useState<any>(null)
const [loadingChart, setLoadingChart] = useState(false)
const badgeClass = wine.badge === 'new' ? 'wine-badge-new' :
wine.badge === 'deal' ? 'wine-badge-deal' :
'wine-badge-featured'
const badgeText = wine.badge === 'new' ? '🆕 New Vintage' :
wine.badge === 'deal' ? '🔥 Hot Deal' :
'⭐ Featured'
const isInStock = wine.availability?.toLowerCase().includes('stock')
const loadPriceHistory = async () => {
if (chartData || loadingChart) return
setLoadingChart(true)
try {
const response = await fetch(
`/api/price-history?wine=${encodeURIComponent(wine.name)}&vintage=${wine.vintage || ''}`
)
const data = await response.json()
// Only set chart data if we have real historical data
if (data.success && data.pricePoints && data.pricePoints.length > 0) {
setChartData(data)
} else {
setChartData({
success: false,
error: data.error || 'No historical price data available for this wine yet',
pricePoints: []
})
}
} catch (error) {
console.error('Failed to load price history:', error)
setChartData({
success: false,
error: 'Failed to load price history',
pricePoints: []
})
} finally {
setLoadingChart(false)
}
}
const toggleChart = () => {
if (!showChart && !chartData) {
loadPriceHistory()
}
setShowChart(!showChart)
}
return (
<div className="wine-card group">
<div className="relative h-72 bg-gradient-to-br from-purple-100 to-pink-100">
{wine.image ? (
<Image
src={wine.image}
alt={wine.name}
fill
className="object-contain p-4 group-hover:scale-110 transition-transform duration-300"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
) : (
<div className="flex items-center justify-center h-full text-6xl">
🍷
</div>
)}
{wine.badge && (
<div className="absolute top-4 right-4">
<span className={badgeClass}>{badgeText}</span>
</div>
)}
{/* Price Change Indicator */}
{wine.priceHistory?.priceChange && (
<div className="absolute bottom-4 left-4 bg-black/70 text-white px-3 py-1 rounded-full text-xs font-semibold">
{wine.priceHistory.priceChange.startsWith('-') ? '📉' : '📈'} {wine.priceHistory.priceChange}
</div>
)}
</div>
<div className="p-5 space-y-3">
{/* Wine Name */}
<h3 className="font-bold text-lg text-gray-900 mb-1 line-clamp-2 group-hover:text-purple-700 transition-colors leading-tight">
{wine.name}
</h3>
{/* Winery */}
{wine.winery && (
<p className="text-sm font-medium text-purple-600 flex items-center gap-1">
🏛️ {wine.winery}
</p>
)}
{/* Region & Country */}
{(wine.region || wine.country) && (
<p className="text-sm text-gray-600 flex items-center gap-1">
📍 {wine.region}{wine.region && wine.country ? ', ' : ''}{wine.country}
</p>
)}
{/* Vintage & Type */}
<div className="flex items-center gap-2 text-sm">
{wine.vintage && (
<span className="bg-purple-100 text-purple-800 px-2 py-1 rounded-md font-semibold">
🗓️ {wine.vintage}
</span>
)}
{wine.type && (
<span className="bg-pink-100 text-pink-800 px-2 py-1 rounded-md font-medium">
{wine.type}
</span>
)}
</div>
{/* Varietal */}
{wine.varietal && (
<p className="text-sm text-gray-700 font-medium flex items-center gap-1">
🍇 {wine.varietal}
</p>
)}
{/* Alcohol Content */}
{wine.alcohol && (
<p className="text-xs text-gray-500">
Alcohol: {wine.alcohol}
</p>
)}
{/* Description */}
{wine.description && (
<p className="text-sm text-gray-600 line-clamp-3 italic border-l-2 border-purple-300 pl-3">
"{wine.description}"
</p>
)}
{/* Price & Rating Section */}
<div className="pt-2 border-t border-gray-200">
<div className="flex items-center justify-between mb-2">
<div>
<div className="text-3xl font-bold text-purple-700">
${wine.price}
</div>
{wine.priceHistory?.previousPrice && (
<div className="text-xs text-gray-500 line-through">
${wine.priceHistory.previousPrice}
</div>
)}
</div>
{wine.rating && (
<div className="text-right">
<div className="flex items-center gap-1 bg-yellow-50 px-3 py-2 rounded-lg border border-yellow-200">
<span className="text-yellow-500 text-lg">⭐</span>
<span className="text-lg font-bold text-gray-800">{wine.rating}</span>
</div>
{wine.reviews && (
<div className="text-xs text-gray-500 mt-1">
{wine.reviews.toLocaleString()} reviews
</div>
)}
</div>
)}
</div>
</div>
{/* Source & Availability */}
<div className="flex items-center justify-between text-sm pt-2">
<div className="flex items-center gap-1">
<span className="text-xs bg-gray-100 text-gray-700 px-2 py-1 rounded-full font-medium">
📦 {wine.source}
</span>
</div>
{wine.availability && (
<span className={`text-xs font-semibold px-2 py-1 rounded-full ${isInStock ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'}`}>
{isInStock ? '✓' : '○'} {wine.availability}
</span>
)}
</div>
{/* Price History Chart Toggle */}
<button
onClick={toggleChart}
className="mt-3 w-full py-2 px-4 bg-gradient-to-r from-blue-500 to-indigo-500 text-white rounded-lg font-semibold text-sm hover:from-blue-600 hover:to-indigo-600 transition-all duration-300 flex items-center justify-center gap-2"
>
{showChart ? '📊 Hide' : '📈 Show'} Price History
{loadingChart && <div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>}
</button>
{/* Price Chart */}
{showChart && chartData && (
<div className="mt-3 animate-fadeIn">
{chartData.success && chartData.pricePoints && chartData.pricePoints.length > 0 ? (
<PriceChart
data={chartData.pricePoints}
currentPrice={parseFloat(wine.price)}
trend={chartData.stats?.trend || 'stable'}
dealScore={chartData.stats?.dealScore || 50}
/>
) : (
<div className="bg-gray-100 border border-gray-200 rounded-lg p-4 text-center">
<div className="text-gray-600 mb-2">📊 No Price History Available</div>
<div className="text-sm text-gray-500">
{chartData.error || 'Historical price data not yet available for this wine.'}
</div>
<div className="text-xs text-gray-400 mt-2">
Price tracking will begin once this wine is searched.
</div>
</div>
)}
</div>
)}
{/* CTA Button */}
{wine.url && (
<a
href={wine.url}
target="_blank"
rel="noopener noreferrer"
className="mt-4 block w-full text-center bg-gradient-to-r from-purple-600 to-pink-600 text-white py-3 px-4 rounded-lg font-bold hover:from-purple-700 hover:to-pink-700 transition-all duration-300 hover:shadow-xl transform hover:scale-105"
>
🛒 View Full Details →
</a>
)}
</div>
</div>
)
}