← back to Wine Finder Next
app/mobile-page.tsx
296 lines
'use client'
import { useState, useEffect } from 'react';
import { Wine } from '@/lib/types/wine';
import MobileWineCard from '@/components/mobile/MobileWineCard';
import MobileSearchBar from '@/components/mobile/MobileSearchBar';
import FilterSheet, { FilterState } from '@/components/mobile/FilterSheet';
export default function MobilePage() {
const [wines, setWines] = useState<Wine[]>([]);
const [filteredWines, setFilteredWines] = useState<Wine[]>([]);
const [loading, setLoading] = useState(false);
const [searching, setSearching] = useState(false);
const [showFilters, setShowFilters] = useState(false);
const [filters, setFilters] = useState<FilterState>({});
const [searchQuery, setSearchQuery] = useState('');
const [activeTab, setActiveTab] = useState<'search' | 'featured' | 'deals'>('featured');
// Load featured wines on mount
useEffect(() => {
loadFeaturedWines();
}, []);
// Apply filters when they change
useEffect(() => {
applyFilters();
}, [wines, filters]);
const loadFeaturedWines = async () => {
try {
setLoading(true);
const response = await fetch('/api/featured');
const data = await response.json();
const allWines = [
...(data.featured || []),
...(data.newVintages || []),
...(data.deals || [])
];
setWines(allWines);
} catch (error) {
console.error('Error loading featured wines:', error);
} finally {
setLoading(false);
}
};
const handleSearch = async (query: string) => {
setSearchQuery(query);
setSearching(true);
setActiveTab('search');
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
setWines(data.wines || []);
} catch (error) {
console.error('Search error:', error);
} finally {
setSearching(false);
}
};
const applyFilters = () => {
let filtered = [...wines];
// Price filter
if (filters.priceMin !== undefined) {
filtered = filtered.filter(w => {
const price = typeof w.price === 'string' ? parseFloat(w.price) : w.price;
return price >= filters.priceMin!;
});
}
if (filters.priceMax !== undefined) {
filtered = filtered.filter(w => {
const price = typeof w.price === 'string' ? parseFloat(w.price) : w.price;
return price <= filters.priceMax!;
});
}
// Rating filter
if (filters.ratingMin !== undefined) {
filtered = filtered.filter(w => w.rating && w.rating >= filters.ratingMin!);
}
// Type filter
if (filters.type && filters.type.length > 0) {
filtered = filtered.filter(w => w.type && filters.type!.includes(w.type));
}
// Source filter
if (filters.sources && filters.sources.length > 0) {
filtered = filtered.filter(w => filters.sources!.includes(w.source));
}
// In stock filter
if (filters.inStockOnly) {
filtered = filtered.filter(w =>
w.availability?.toLowerCase().includes('stock')
);
}
// Sort
if (filters.sortBy) {
filtered.sort((a, b) => {
const priceA = typeof a.price === 'string' ? parseFloat(a.price) : a.price;
const priceB = typeof b.price === 'string' ? parseFloat(b.price) : b.price;
switch (filters.sortBy) {
case 'price-asc':
return priceA - priceB;
case 'price-desc':
return priceB - priceA;
case 'rating':
return (b.rating || 0) - (a.rating || 0);
case 'name':
return a.name.localeCompare(b.name);
default:
return 0;
}
});
}
setFilteredWines(filtered);
};
const handlePullToRefresh = () => {
if (activeTab === 'featured' || activeTab === 'deals') {
loadFeaturedWines();
} else if (searchQuery) {
handleSearch(searchQuery);
}
};
const displayWines = filteredWines.length > 0 ? filteredWines : wines;
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="sticky top-0 z-30 bg-gradient-to-r from-purple-900 via-burgundy-800 to-pink-900 text-white shadow-xl">
<div className="px-4 py-4">
{/* Logo & Title */}
<div className="mb-4">
<h1 className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-yellow-200 via-pink-200 to-purple-200">
Red Thunder Wine
</h1>
<p className="text-sm text-purple-200">
500K+ wines • Best prices
</p>
</div>
{/* Search Bar */}
<MobileSearchBar
onSearch={handleSearch}
placeholder="Search wines, wineries, regions..."
/>
{/* Filter Button */}
<div className="flex gap-2 mt-3">
<button
onClick={() => setShowFilters(true)}
className="flex-1 py-3 px-4 bg-white/10 backdrop-blur rounded-xl font-bold hover:bg-white/20 transition-all flex items-center justify-center gap-2"
>
<span>🔧 Filters</span>
{Object.keys(filters).length > 0 && (
<span className="bg-pink-500 text-white text-xs px-2 py-1 rounded-full">
{Object.keys(filters).length}
</span>
)}
</button>
</div>
</div>
{/* Tabs */}
<div className="flex border-t border-white/10">
<button
onClick={() => {
setActiveTab('featured');
loadFeaturedWines();
}}
className={`flex-1 py-3 font-bold text-sm transition-all ${
activeTab === 'featured'
? 'bg-white/20 text-white border-b-2 border-yellow-300'
: 'text-purple-200'
}`}
>
⭐ Featured
</button>
<button
onClick={() => setActiveTab('search')}
className={`flex-1 py-3 font-bold text-sm transition-all ${
activeTab === 'search'
? 'bg-white/20 text-white border-b-2 border-yellow-300'
: 'text-purple-200'
}`}
>
🔍 Search
</button>
<button
onClick={() => {
setActiveTab('deals');
loadFeaturedWines();
}}
className={`flex-1 py-3 font-bold text-sm transition-all ${
activeTab === 'deals'
? 'bg-white/20 text-white border-b-2 border-yellow-300'
: 'text-purple-200'
}`}
>
🔥 Deals
</button>
</div>
</div>
{/* Content */}
<div className="px-4 py-4">
{/* Results Count */}
{displayWines.length > 0 && (
<div className="mb-4 flex items-center justify-between">
<p className="text-sm text-gray-600 font-medium">
{displayWines.length} wine{displayWines.length !== 1 ? 's' : ''} found
</p>
{searchQuery && (
<button
onClick={() => {
setSearchQuery('');
setActiveTab('featured');
loadFeaturedWines();
}}
className="text-purple-600 text-sm font-bold"
>
Clear Search
</button>
)}
</div>
)}
{/* Loading State */}
{(loading || searching) && (
<div className="text-center py-20">
<div className="inline-block animate-spin rounded-full h-16 w-16 border-4 border-purple-600 border-t-transparent"></div>
<p className="mt-4 text-gray-600 font-medium">
{searching ? 'Searching...' : 'Loading wines...'}
</p>
</div>
)}
{/* Wine Grid */}
{!loading && !searching && displayWines.length > 0 && (
<div className="grid grid-cols-1 gap-4 pb-20">
{displayWines.map((wine, index) => (
<MobileWineCard key={index} wine={wine} />
))}
</div>
)}
{/* Empty State */}
{!loading && !searching && displayWines.length === 0 && (
<div className="text-center py-20">
<div className="text-6xl mb-4">🍷</div>
<h3 className="text-xl font-bold text-gray-900 mb-2">
No wines found
</h3>
<p className="text-gray-600 mb-6">
Try adjusting your search or filters
</p>
<button
onClick={handlePullToRefresh}
className="px-6 py-3 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-xl font-bold hover:from-purple-700 hover:to-pink-700 transition-all"
>
Browse Featured Wines
</button>
</div>
)}
</div>
{/* Filter Sheet */}
<FilterSheet
isOpen={showFilters}
onClose={() => setShowFilters(false)}
onApply={(newFilters) => setFilters(newFilters)}
initialFilters={filters}
/>
{/* Affiliate Disclosure Footer */}
<div className="fixed bottom-0 inset-x-0 bg-white border-t border-gray-200 px-4 py-2 z-20">
<p className="text-xs text-gray-500 text-center">
Affiliate Disclosure: We may earn a commission from purchases made through our links.
</p>
</div>
</div>
);
}