first commit
This commit is contained in:
1153
app/(public)/products/[slug]/page.tsx
Normal file
1153
app/(public)/products/[slug]/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
193
app/(public)/products/page.tsx
Normal file
193
app/(public)/products/page.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Search, Loader2 } from 'lucide-react'
|
||||
import ProductCard from '@/components/shop/ProductCard'
|
||||
import StructuredData from '@/components/StructuredData'
|
||||
import { generateProductListJsonLd, generateBreadcrumbJsonLd } from '@/lib/structured-data'
|
||||
import { Product, Category } from '@/types'
|
||||
import PageHero from '@/components/sections/PageHero'
|
||||
|
||||
export default function ProductsPage() {
|
||||
const [products, setProducts] = useState<Product[]>([])
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const [selectedCategory, setSelectedCategory] = useState('all')
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [totalProducts, setTotalProducts] = useState(0)
|
||||
|
||||
const fetchProducts = useCallback(async (currentPage = 1, isLoadMore = false) => {
|
||||
try {
|
||||
if (!isLoadMore) {
|
||||
setLoading(true)
|
||||
} else {
|
||||
setLoadingMore(true)
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage.toString(),
|
||||
limit: '12',
|
||||
})
|
||||
|
||||
if (search) params.append('search', search)
|
||||
if (selectedCategory && selectedCategory !== 'all') params.append('category', selectedCategory)
|
||||
|
||||
const response = await fetch(`/api/products?${params}`)
|
||||
const data = await response.json()
|
||||
|
||||
if (isLoadMore) {
|
||||
setProducts(prev => [...prev, ...data.products])
|
||||
setHasMore(data.products.length === 12)
|
||||
} else {
|
||||
setProducts(data.products)
|
||||
setHasMore(data.products.length === 12)
|
||||
}
|
||||
|
||||
setTotalProducts(data.total || 0)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching products:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
setLoadingMore(false)
|
||||
}
|
||||
}, [search, selectedCategory])
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories()
|
||||
fetchProducts(1, false)
|
||||
}, [fetchProducts])
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1)
|
||||
fetchProducts(1, false)
|
||||
}, [search, selectedCategory, fetchProducts])
|
||||
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/categories')
|
||||
const data = await response.json()
|
||||
// Handle both old format (array) and new format (object with categories)
|
||||
const categoriesArray = Array.isArray(data) ? data : data.categories || []
|
||||
setCategories(categoriesArray)
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLoadMore = () => {
|
||||
const nextPage = page + 1
|
||||
setPage(nextPage)
|
||||
fetchProducts(nextPage, true)
|
||||
}
|
||||
|
||||
// Generate structured data
|
||||
const baseUrl = process.env.NEXT_PUBLIC_URL || 'https://padmaajarasooi.com'
|
||||
const productListData = generateProductListJsonLd(products, baseUrl)
|
||||
const breadcrumbData = generateBreadcrumbJsonLd([
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Products', url: '/products' }
|
||||
], baseUrl)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Structured Data */}
|
||||
<StructuredData data={productListData} id="product-list-data" />
|
||||
<StructuredData data={breadcrumbData} id="breadcrumb-data" />
|
||||
|
||||
{/* Hero Section */}
|
||||
<PageHero
|
||||
title="Products"
|
||||
subtitle="Our"
|
||||
description="Discover our premium collection of authentic food products, carefully crafted to bring traditional flavors to your kitchen."
|
||||
badge={{
|
||||
text: "Premium • Authentic • Quality"
|
||||
}}
|
||||
alignment="left"
|
||||
/>
|
||||
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
|
||||
{/* Filters */}
|
||||
<div className="mb-8 flex flex-col sm:flex-row gap-4">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Search products..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
||||
<SelectTrigger className="w-full sm:w-48">
|
||||
<SelectValue placeholder="All Categories" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Categories</SelectItem>
|
||||
{categories.map((category) => (
|
||||
<SelectItem key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Products Grid */}
|
||||
{loading ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div key={i} className="animate-pulse">
|
||||
<div className="bg-gray-200 aspect-square rounded-lg mb-4"></div>
|
||||
<div className="h-4 bg-gray-200 rounded mb-2"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{products.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{products.length === 0 && !loading && (
|
||||
<div className="text-center py-12">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No products found</h3>
|
||||
<p className="text-gray-500">Try adjusting your search or filter criteria</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Load More Button */}
|
||||
{!loading && hasMore && products.length > 0 && (
|
||||
<div className="flex justify-center mt-12">
|
||||
<Button
|
||||
onClick={handleLoadMore}
|
||||
disabled={loadingMore}
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="px-8 py-3"
|
||||
>
|
||||
{loadingMore ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Loading...
|
||||
</>
|
||||
) : (
|
||||
'Load More Products'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user