first commit

This commit is contained in:
2026-01-17 14:17:42 +05:30
commit 0f194eb9e7
328 changed files with 73544 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View 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>
)
}