195 lines
9.3 KiB
JavaScript
195 lines
9.3 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import { Link, useNavigate } from 'react-router-dom';
|
|
import { Trash2, Plus, Minus, ArrowRight, ShieldCheck, ShoppingBag, Truck } from 'lucide-react';
|
|
import { fetchCart, updateCartItemAsync, removeCartItemAsync, applyCouponAsync } from '../../store/slices/cartSlice';
|
|
import { showToast } from '../../store/slices/uiSlice';
|
|
import { formatINR } from '../../utils/formatCurrency';
|
|
|
|
const CartPage = () => {
|
|
const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
|
|
const { items, coupon } = useSelector((state) => state.cart);
|
|
const [couponCode, setCouponCode] = useState('');
|
|
|
|
useEffect(() => {
|
|
dispatch(fetchCart());
|
|
}, [dispatch]);
|
|
|
|
const subtotal = items.reduce((acc, item) => {
|
|
const itemPrice = item.product?.salePrice || item.product?.price || item.price;
|
|
return acc + itemPrice * item.quantity;
|
|
}, 0);
|
|
|
|
const discountAmount = coupon ? (coupon.discountType === 'percentage' ? (subtotal * coupon.discountValue) / 100 : coupon.discountValue) : 0;
|
|
const freeShippingThreshold = 5000; // ₹5,000 threshold for India
|
|
const shippingFee = subtotal >= freeShippingThreshold || subtotal === 0 ? 0 : 499;
|
|
const totalAmount = Math.max(0, subtotal - discountAmount + shippingFee);
|
|
|
|
const freeShippingProgress = Math.min(100, (subtotal / freeShippingThreshold) * 100);
|
|
|
|
const handleApplyCoupon = (e) => {
|
|
e.preventDefault();
|
|
if (couponCode.trim()) {
|
|
dispatch(applyCouponAsync(couponCode.trim()))
|
|
.unwrap()
|
|
.then(() => dispatch(showToast({ message: 'Coupon applied successfully!', type: 'success' })))
|
|
.catch((err) => dispatch(showToast({ message: err || 'Invalid coupon', type: 'error' })));
|
|
}
|
|
};
|
|
|
|
if (items.length === 0) {
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 py-20 text-center space-y-4 bg-slate-50">
|
|
<div className="w-16 h-16 bg-white border border-slate-200 rounded-full flex items-center justify-center mx-auto text-slate-400 shadow-sm">
|
|
<ShoppingBag className="w-8 h-8" />
|
|
</div>
|
|
<h2 className="text-2xl font-bold text-slate-900 font-serif">Your Shopping Cart is Empty</h2>
|
|
<p className="text-xs text-slate-500">Discover hand-crafted custom shop electric, acoustic, and bass guitars.</p>
|
|
<Link to="/shop" className="inline-block py-3 px-8 bg-amber-500 hover:bg-amber-600 text-white font-bold rounded-2xl text-xs shadow-sm">
|
|
Explore Guitar Catalog
|
|
</Link>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10 space-y-8 bg-slate-50 text-slate-900">
|
|
<h1 className="text-3xl font-extrabold text-slate-900 font-serif">Your Shopping Cart ({items.length} Items)</h1>
|
|
|
|
{/* Free Shipping Progress Bar */}
|
|
<div className="p-4 bg-white border border-slate-200 rounded-2xl space-y-2 shadow-sm">
|
|
<div className="flex items-center justify-between text-xs font-semibold">
|
|
<span className="text-slate-700 flex items-center gap-2">
|
|
<Truck className="w-4 h-4 text-amber-600" />
|
|
{subtotal >= freeShippingThreshold
|
|
? '🎉 You qualify for FREE Express Delivery in India!'
|
|
: `Add ${formatINR(freeShippingThreshold - subtotal)} more to unlock FREE Delivery`}
|
|
</span>
|
|
<span className="text-amber-600 font-bold">{Math.round(freeShippingProgress)}%</span>
|
|
</div>
|
|
<div className="h-2 w-full bg-slate-100 rounded-full overflow-hidden">
|
|
<div className="h-full bg-amber-500 transition-all duration-500" style={{ width: `${freeShippingProgress}%` }} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
|
|
{/* Cart Items Table */}
|
|
<div className="lg:col-span-2 space-y-4">
|
|
{items.map((item) => {
|
|
const product = item.product || {};
|
|
const itemPrice = product.salePrice > 0 ? product.salePrice : (product.price || item.price);
|
|
|
|
return (
|
|
<div key={item._id} className="p-4 bg-white border border-slate-200 rounded-2xl flex flex-col sm:flex-row items-center justify-between gap-4 shadow-sm">
|
|
<div className="flex items-center gap-4 w-full sm:w-auto">
|
|
<img
|
|
src={product.images?.[0] || 'https://images.unsplash.com/photo-1564186763535-ebb21ef5277f?w=300'}
|
|
alt={product.name}
|
|
className="w-20 h-20 object-cover rounded-xl border border-slate-200"
|
|
/>
|
|
<div>
|
|
<span className="text-[10px] font-bold text-amber-600 uppercase">{product.brand?.name}</span>
|
|
<h3 className="text-sm font-bold text-slate-900 line-clamp-1">{product.name}</h3>
|
|
{item.variantColor && (
|
|
<div className="text-xs text-slate-500">Variant: {item.variantColor} ({item.variantFinish})</div>
|
|
)}
|
|
<div className="text-xs font-bold text-slate-900 mt-1">{formatINR(itemPrice)} each</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between sm:justify-end gap-6 w-full sm:w-auto">
|
|
{/* Quantity selector */}
|
|
<div className="flex items-center bg-slate-100 border border-slate-200 rounded-xl p-1">
|
|
<button
|
|
onClick={() => dispatch(updateCartItemAsync({ itemId: item._id, quantity: item.quantity - 1 }))}
|
|
className="p-1.5 text-slate-500 hover:text-slate-900"
|
|
>
|
|
<Minus className="w-3.5 h-3.5" />
|
|
</button>
|
|
<span className="w-8 text-center text-xs font-bold text-slate-900">{item.quantity}</span>
|
|
<button
|
|
onClick={() => dispatch(updateCartItemAsync({ itemId: item._id, quantity: item.quantity + 1 }))}
|
|
className="p-1.5 text-slate-500 hover:text-slate-900"
|
|
>
|
|
<Plus className="w-3.5 h-3.5" />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="text-sm font-extrabold text-amber-700">{formatINR(itemPrice * item.quantity)}</div>
|
|
|
|
<button
|
|
onClick={() => dispatch(removeCartItemAsync(item._id))}
|
|
className="p-2 text-slate-400 hover:text-rose-600"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Order Summary Column */}
|
|
<div className="space-y-6">
|
|
<div className="p-6 bg-white border border-slate-200 rounded-3xl space-y-6 shadow-sm">
|
|
<h3 className="text-lg font-bold text-slate-900 font-serif">Order Summary</h3>
|
|
|
|
<div className="space-y-3 text-xs border-b border-slate-100 pb-4">
|
|
<div className="flex justify-between text-slate-600">
|
|
<span>Subtotal</span>
|
|
<span className="font-bold text-slate-900">{formatINR(subtotal)}</span>
|
|
</div>
|
|
{coupon && (
|
|
<div className="flex justify-between text-emerald-700">
|
|
<span>Discount ({coupon.code})</span>
|
|
<span className="font-bold">-{formatINR(discountAmount)}</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between text-slate-600">
|
|
<span>Estimated Shipping</span>
|
|
<span className="font-bold text-slate-900">{shippingFee === 0 ? 'FREE' : formatINR(shippingFee)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Coupon Code Input */}
|
|
<form onSubmit={handleApplyCoupon} className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
placeholder="Coupon (e.g. WELCOME10)"
|
|
value={couponCode}
|
|
onChange={(e) => setCouponCode(e.target.value)}
|
|
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-3 py-2 text-xs text-slate-900 uppercase focus:outline-none focus:border-amber-500"
|
|
/>
|
|
<button type="submit" className="py-2 px-4 bg-slate-900 hover:bg-slate-800 text-white font-bold rounded-xl text-xs">
|
|
Apply
|
|
</button>
|
|
</form>
|
|
|
|
<div className="flex justify-between items-baseline pt-2">
|
|
<span className="text-sm font-bold text-slate-900">Total Payable</span>
|
|
<span className="text-2xl font-extrabold text-amber-600">{formatINR(totalAmount)}</span>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => navigate('/checkout')}
|
|
className="w-full py-4 bg-amber-500 hover:bg-amber-600 text-white font-bold rounded-2xl text-xs sm:text-sm flex items-center justify-center gap-2 shadow-sm transition-all"
|
|
>
|
|
Proceed to Secure Checkout <ArrowRight className="w-4 h-4" />
|
|
</button>
|
|
|
|
<div className="flex items-center justify-center gap-2 text-xs text-slate-500 pt-2">
|
|
<ShieldCheck className="w-4 h-4 text-emerald-600" /> 256-Bit Encrypted Payment
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CartPage;
|