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

View File

@@ -0,0 +1,77 @@
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Shopping Cart - Padmaaja Rasooi | Review Your Premium Rice Products',
description: 'Review your selected premium rice products in your shopping cart. Secure checkout for the finest quality rice varieties from Padmaaja Rasooi.',
keywords: ['shopping cart', 'padmaaja rasooi', 'rice products', 'checkout', 'premium rice', 'secure shopping', 'cart review', 'order summary'],
openGraph: {
title: 'Shopping Cart - Padmaaja Rasooi',
description: 'Review your selected premium rice products and proceed to secure checkout.',
type: 'website',
images: ['/cart-icon.png'],
},
twitter: {
card: 'summary',
title: 'Shopping Cart - Padmaaja Rasooi',
description: 'Review your selected premium rice products and proceed to secure checkout.',
},
robots: {
index: false, // Don't index cart pages
follow: true,
noarchive: true,
nosnippet: true,
},
alternates: {
canonical: '/cart',
},
other: {
'cart-page': 'true',
'secure-checkout': 'enabled',
'page-type': 'ecommerce-cart',
},
}
export default function CartLayout({
children,
}: {
children: React.ReactNode
}) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Shopping Cart - Padmaaja Rasooi",
"description": "Review your selected premium rice products and proceed to secure checkout.",
"url": "/cart",
"isPartOf": {
"@type": "WebSite",
"name": "Padmaaja Rasooi",
"url": "/"
},
"potentialAction": {
"@type": "CheckoutAction",
"target": "/checkout"
}
}
return (
<>
{/* Enhanced structured data for cart */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c')
}}
/>
<main role="main" aria-label="Shopping Cart">
<div className="cart-container" itemScope itemType="https://schema.org/ShoppingCart">
<header className="sr-only">
<h1>Shopping Cart</h1>
</header>
<section aria-label="Cart items">
{children}
</section>
</div>
</main>
</>
)
}

281
app/(public)/cart/page.tsx Normal file
View File

@@ -0,0 +1,281 @@
'use client'
import { useState, useEffect, useCallback } from 'react'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
import { ArrowLeft, ShoppingCart, Trash2, Plus, Minus, Heart } from 'lucide-react'
import { cartManager } from '@/lib/cart'
import { toast } from 'sonner'
import Link from 'next/link'
import Image from 'next/image'
import { motion } from 'framer-motion'
import PageHero from '@/components/sections/PageHero'
interface CartItem {
id: string
name: string
price: number
quantity: number
image: string | null
}
export default function CartPage() {
const router = useRouter()
const [cart, setCart] = useState<CartItem[]>([])
const [updating, setUpdating] = useState<string | null>(null)
const loadCart = useCallback(() => {
const cartItems = cartManager.getCart()
setCart(cartItems || [])
}, [])
useEffect(() => {
loadCart()
const handleCartUpdate = () => loadCart()
window.addEventListener('cartUpdated', handleCartUpdate)
return () => {
window.removeEventListener('cartUpdated', handleCartUpdate)
}
}, [loadCart])
const updateQuantity = async (productId: string, newQuantity: number) => {
setUpdating(productId)
cartManager.updateQuantity(productId, newQuantity)
loadCart()
setUpdating(null)
}
const removeItem = (productId: string) => {
cartManager.removeFromCart(productId)
toast.success('Item removed from cart')
loadCart()
}
const getTotalPrice = () => {
return cartManager.getTotalPrice()
}
const getTotalItems = () => {
return cart.reduce((sum, item) => sum + item.quantity, 0)
}
if (cart.length === 0) {
return (
<div className="min-h-screen bg-white">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-8">
<Button variant="ghost" asChild className="mb-6 text-gray-600 hover:text-gray-900">
<Link href="/products">
<ArrowLeft className="h-4 w-4 mr-2" />
Continue Shopping
</Link>
</Button>
<h1 className="text-4xl font-bold text-gray-900 mb-2">Shopping Cart</h1>
<p className="text-gray-600">Your cart is currently empty</p>
</div>
<div className="text-center py-16">
<div className="w-24 h-24 mx-auto mb-6 bg-gray-100 rounded-full flex items-center justify-center">
<ShoppingCart className="h-12 w-12 text-gray-400" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">Your cart is empty</h3>
<p className="text-gray-600 mb-8 max-w-md mx-auto">
Looks like you haven&apos;t added anything to your cart yet. Start shopping to fill it up!
</p>
<Button size="lg" asChild className="bg-black text-white hover:bg-gray-800">
<Link href="/products">Start Shopping</Link>
</Button>
</div>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="mb-8">
<Button variant="ghost" asChild className="mb-6 text-gray-600 hover:text-gray-900">
<Link href="/products">
<ArrowLeft className="h-4 w-4 mr-2" />
Continue Shopping
</Link>
</Button>
<div className="flex items-center justify-between">
<div>
<h1 className="text-4xl font-bold text-gray-900 mb-2">Shopping Cart</h1>
<p className="text-gray-600">{getTotalItems()} {getTotalItems() === 1 ? 'item' : 'items'} in your cart</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-12">
{/* Cart Items */}
<div className="xl:col-span-2 space-y-6">
{cart.map((item, index) => (
<motion.div
key={item.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: index * 0.05 }}
className="group"
>
<div className="bg-white border border-gray-200 rounded-2xl p-6 hover:border-gray-300 transition-colors duration-200">
<div className="flex items-start gap-6">
{/* Product Image */}
<div className="relative flex-shrink-0">
<Image
src={item.image || 'https://images.pexels.com/photos/3683107/pexels-photo-3683107.jpeg'}
alt={item.name}
width={120}
height={120}
className="rounded-xl object-cover bg-gray-100"
/>
</div>
{/* Product Details */}
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-1 line-clamp-2">
{item.name}
</h3>
<p className="text-2xl font-bold text-gray-900">
{(item.price || 0).toFixed(2)}
</p>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="text-gray-400 hover:text-gray-600 opacity-0 group-hover:opacity-100 transition-opacity"
>
<Heart className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => removeItem(item.id)}
className="text-gray-400 hover:text-red-600"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
{/* Quantity Controls */}
<div className="flex items-center justify-between">
<div className="flex items-center bg-gray-100 rounded-full">
<Button
variant="ghost"
size="sm"
onClick={() => updateQuantity(item.id, item.quantity - 1)}
disabled={item.quantity <= 1 || updating === item.id}
className="h-10 w-10 rounded-full hover:bg-gray-200"
>
<Minus className="h-4 w-4" />
</Button>
<span className="w-12 text-center font-semibold text-gray-900">
{updating === item.id ? '...' : item.quantity}
</span>
<Button
variant="ghost"
size="sm"
onClick={() => updateQuantity(item.id, item.quantity + 1)}
disabled={updating === item.id}
className="h-10 w-10 rounded-full hover:bg-gray-200"
>
<Plus className="h-4 w-4" />
</Button>
</div>
<div className="text-right">
<p className="text-lg font-bold text-gray-900">
{((item.price || 0) * item.quantity).toFixed(2)}
</p>
<p className="text-sm text-gray-500">
{(item.price || 0).toFixed(2)} each
</p>
</div>
</div>
</div>
</div>
</div>
</motion.div>
))}
</div>
{/* Order Summary */}
<div className="xl:col-span-1">
<div className="sticky top-8">
<div className="bg-gray-50 rounded-2xl p-8 border border-gray-200">
<h2 className="text-xl font-bold text-gray-900 mb-6">Order Summary</h2>
<div className="space-y-4 mb-6">
<div className="flex justify-between text-gray-600">
<span>Subtotal ({getTotalItems()} items)</span>
<span className="font-semibold">{getTotalPrice().toFixed(2)}</span>
</div>
<div className="flex justify-between text-gray-600">
<span>Shipping</span>
<span className="font-semibold text-green-600">Free</span>
</div>
<div className="flex justify-between text-gray-600">
<span>Tax</span>
<span className="font-semibold">Calculated at checkout</span>
</div>
<Separator className="my-4" />
<div className="flex justify-between items-center">
<span className="text-lg font-bold text-gray-900">Total</span>
<span className="text-2xl font-bold text-gray-900">{getTotalPrice().toFixed(2)}</span>
</div>
</div>
<div className="space-y-3">
<Button
className="w-full h-12 bg-black text-white hover:bg-gray-800 rounded-xl text-base font-semibold"
asChild
>
<Link href="/checkout">
Proceed to Checkout
</Link>
</Button>
<Button
variant="outline"
className="w-full h-12 border-gray-300 hover:bg-gray-50 rounded-xl text-base"
asChild
>
<Link href="/products">
Continue Shopping
</Link>
</Button>
</div>
{/* Security Badge */}
<div className="mt-6 pt-6 border-t border-gray-200">
<div className="flex items-center justify-center text-sm text-gray-500">
<div className="w-4 h-4 bg-green-500 rounded-full mr-2"></div>
Secure checkout powered by Razorpay
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}