← back to Wine Finder Next
components/mobile/WineDetailModal.tsx
290 lines
'use client'
import { useState, useEffect } from 'react';
import Image from 'next/image';
import { Wine } from '@/lib/types/wine';
import PriceChart from '../PriceChart';
import AffiliateButton from './AffiliateButton';
import PriceComparisonSheet from './PriceComparisonSheet';
interface WineDetailModalProps {
wine: Wine | null;
isOpen: boolean;
onClose: () => void;
}
export default function WineDetailModal({
wine,
isOpen,
onClose
}: WineDetailModalProps) {
const [showPriceHistory, setShowPriceHistory] = useState(false);
const [showComparison, setShowComparison] = useState(false);
const [chartData, setChartData] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [imageIndex, setImageIndex] = useState(0);
useEffect(() => {
if (isOpen && wine) {
// Reset state
setImageIndex(0);
setShowPriceHistory(false);
setChartData(null);
// Prevent body scroll
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen, wine]);
const loadPriceHistory = async () => {
if (!wine || loading || chartData) return;
setLoading(true);
try {
const response = await fetch(
`/api/price-history?wine=${encodeURIComponent(wine.name)}&vintage=${wine.vintage || ''}`
);
const data = await response.json();
setChartData(data);
} catch (error) {
console.error('Failed to load price history:', error);
} finally {
setLoading(false);
}
};
if (!isOpen || !wine) return null;
const isInStock = wine.availability?.toLowerCase().includes('stock');
return (
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/70 z-50 animate-fadeIn"
onClick={onClose}
/>
{/* Modal */}
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center pointer-events-none">
<div
className="bg-white w-full max-w-2xl max-h-[95vh] rounded-t-3xl sm:rounded-3xl overflow-hidden shadow-2xl pointer-events-auto animate-slideUp"
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
onClick={onClose}
className="absolute top-4 right-4 z-10 w-10 h-10 bg-white/90 backdrop-blur rounded-full flex items-center justify-center text-gray-700 hover:bg-white shadow-lg transition-all"
>
✕
</button>
{/* Scrollable Content */}
<div className="overflow-y-auto max-h-[95vh]">
{/* Image Section */}
<div className="relative h-80 bg-gradient-to-br from-purple-100 to-pink-100">
{wine.image ? (
<Image
src={wine.image}
alt={wine.name}
fill
className="object-contain p-8"
sizes="(max-width: 768px) 100vw, 50vw"
priority
/>
) : (
<div className="flex items-center justify-center h-full text-8xl">
🍷
</div>
)}
{/* Badge */}
{wine.badge && (
<div className="absolute top-4 left-4 bg-gradient-to-r from-purple-600 to-pink-600 text-white px-4 py-2 rounded-full font-bold text-sm shadow-lg">
{wine.badge === 'new' && '🆕 New Vintage'}
{wine.badge === 'deal' && '🔥 Hot Deal'}
{wine.badge === 'featured' && '⭐ Featured'}
</div>
)}
</div>
{/* Content */}
<div className="p-6 space-y-4">
{/* Title */}
<div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">
{wine.name}
</h2>
{wine.winery && (
<p className="text-lg text-purple-600 font-medium">
🏛️ {wine.winery}
</p>
)}
</div>
{/* Price & Rating */}
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl">
<div>
<div className="text-4xl font-bold text-purple-700">
${wine.price}
</div>
{wine.priceHistory?.previousPrice && (
<div className="text-sm text-gray-500 line-through">
${wine.priceHistory.previousPrice}
</div>
)}
</div>
{wine.rating && (
<div className="text-right">
<div className="flex items-center gap-2 bg-yellow-50 px-4 py-3 rounded-xl border-2 border-yellow-200">
<span className="text-yellow-500 text-2xl">⭐</span>
<span className="text-2xl 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>
{/* Details Grid */}
<div className="grid grid-cols-2 gap-3">
{wine.vintage && (
<div className="bg-purple-50 p-3 rounded-lg">
<div className="text-xs text-gray-600">Vintage</div>
<div className="font-bold text-purple-800">🗓️ {wine.vintage}</div>
</div>
)}
{wine.region && (
<div className="bg-pink-50 p-3 rounded-lg">
<div className="text-xs text-gray-600">Region</div>
<div className="font-bold text-pink-800">📍 {wine.region}</div>
</div>
)}
{wine.type && (
<div className="bg-indigo-50 p-3 rounded-lg">
<div className="text-xs text-gray-600">Type</div>
<div className="font-bold text-indigo-800">{wine.type}</div>
</div>
)}
{wine.varietal && (
<div className="bg-green-50 p-3 rounded-lg">
<div className="text-xs text-gray-600">Varietal</div>
<div className="font-bold text-green-800">🍇 {wine.varietal}</div>
</div>
)}
</div>
{/* Description */}
{wine.description && (
<div className="border-l-4 border-purple-500 pl-4 py-2 bg-gray-50 rounded-r-lg">
<p className="text-gray-700 italic">"{wine.description}"</p>
</div>
)}
{/* Availability */}
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<span className="text-sm text-gray-600">Availability</span>
<span className={`text-sm font-bold px-3 py-1 rounded-full ${
isInStock
? 'bg-green-100 text-green-700'
: 'bg-gray-200 text-gray-600'
}`}>
{isInStock ? '✓' : '○'} {wine.availability || 'Check retailer'}
</span>
</div>
{/* Price History Toggle */}
<button
onClick={() => {
if (!showPriceHistory && !chartData) {
loadPriceHistory();
}
setShowPriceHistory(!showPriceHistory);
}}
className="w-full py-3 px-4 bg-gradient-to-r from-blue-500 to-indigo-500 text-white rounded-xl font-bold flex items-center justify-center gap-2 hover:from-blue-600 hover:to-indigo-600 transition-all"
>
{showPriceHistory ? '📊 Hide' : '📈 Show'} Price History
{loading && <div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>}
</button>
{/* Price Chart */}
{showPriceHistory && chartData && (
<div className="animate-fadeIn">
{chartData.success && chartData.pricePoints?.length > 0 ? (
<PriceChart
data={chartData.pricePoints}
currentPrice={parseFloat(wine.price.toString())}
trend={chartData.stats?.trend || 'stable'}
dealScore={chartData.stats?.dealScore || 50}
/>
) : (
<div className="bg-gray-100 border border-gray-200 rounded-xl p-6 text-center">
<div className="text-gray-600 mb-2">📊 No Price History</div>
<div className="text-sm text-gray-500">
Historical data not yet available for this wine.
</div>
</div>
)}
</div>
)}
{/* CTA Buttons */}
<div className="space-y-3 pt-4 border-t border-gray-200">
<AffiliateButton
wine={wine}
partnerId={wine.source.toLowerCase().replace(/[^a-z]/g, '-')}
partnerName={wine.source}
variant="primary"
fullWidth
showPrice
/>
<button
onClick={() => setShowComparison(true)}
className="w-full py-3 px-4 border-2 border-purple-600 text-purple-600 rounded-xl font-bold hover:bg-purple-50 transition-all"
>
💰 Compare Prices from 5+ Retailers
</button>
</div>
{/* Affiliate Disclosure */}
<p className="text-xs text-gray-500 text-center pt-2">
We may earn a commission from purchases made through our links.
</p>
</div>
</div>
</div>
</div>
{/* Price Comparison Sheet */}
<PriceComparisonSheet
wine={wine}
isOpen={showComparison}
onClose={() => setShowComparison(false)}
/>
<style jsx>{`
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
`}</style>
</>
);
}