Initial: ecommerce template via tAI
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Minus, Plus, X, ShoppingBag, ArrowRight, ArrowLeft, Tag } from "lucide-react";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { useCart } from "@/lib/cart-context";
|
||||
|
||||
export default function CartPage() {
|
||||
const { items, removeItem, updateQuantity, total, itemCount, clearCart } = useCart();
|
||||
const [coupon, setCoupon] = useState("");
|
||||
const [couponApplied, setCouponApplied] = useState(false);
|
||||
|
||||
const shipping = total >= 100 ? 0 : 12.99;
|
||||
const tax = total * 0.08;
|
||||
const discount = couponApplied ? total * 0.1 : 0;
|
||||
const grandTotal = total + shipping + tax - discount;
|
||||
|
||||
const applyCoupon = () => {
|
||||
if (coupon.trim().length > 0) {
|
||||
setCouponApplied(true);
|
||||
toast.success("Coupon applied!");
|
||||
}
|
||||
};
|
||||
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-stone-900 antialiased">
|
||||
<Header />
|
||||
<div className="flex flex-col items-center justify-center py-40 px-6">
|
||||
<div className="h-20 w-20 rounded-full bg-stone-100 flex items-center justify-center mb-6">
|
||||
<ShoppingBag className="h-10 w-10 text-stone-400" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold mb-2">Your cart is empty</h1>
|
||||
<p className="text-stone-500 mb-8">Looks like you haven't added anything to your cart yet.</p>
|
||||
<Button asChild className="bg-stone-900 hover:bg-stone-800 rounded-full px-8">
|
||||
<Link to="/products">Continue Shopping</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-stone-900 antialiased">
|
||||
<Header />
|
||||
|
||||
<div className="mx-auto max-w-7xl px-6 pt-28 pb-20">
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2 animate-fade-up">Shopping Cart</h1>
|
||||
<p className="text-stone-500 mb-10">{itemCount} {itemCount === 1 ? "item" : "items"} in your cart</p>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-10">
|
||||
{/* Cart Items */}
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
{items.map((item) => (
|
||||
<Card key={item.id + item.size + item.color} className="border-stone-100">
|
||||
<CardContent className="p-4 sm:p-6">
|
||||
<div className="flex gap-4">
|
||||
<div className="h-24 w-20 sm:h-28 sm:w-24 rounded-lg bg-gradient-to-br from-rose-100 to-stone-200 flex items-center justify-center text-stone-400 text-xs shrink-0 relative overflow-hidden">
|
||||
<div className="absolute inset-0" style={{ backgroundImage: "radial-gradient(circle, rgba(0,0,0,0.06) 1px, transparent 1px)", backgroundSize: "12px 12px" }} />
|
||||
<div className="absolute top-1/4 -left-4 h-12 w-12 rounded-full bg-white/30 blur-xl" />
|
||||
<span className="relative">Image</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm sm:text-base">{item.name}</h3>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
{item.color && <Badge variant="secondary" className="text-xs">{item.color}</Badge>}
|
||||
{item.size && <Badge variant="secondary" className="text-xs">Size: {item.size}</Badge>}
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => removeItem(item.id)} className="text-stone-400 hover:text-stone-600 transition-colors">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-4">
|
||||
<div className="flex items-center border rounded-full">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-full active:scale-90 transition-transform" onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}>
|
||||
<Minus className="h-3 w-3" />
|
||||
</Button>
|
||||
<span className="w-8 text-center text-sm font-medium">{item.quantity}</span>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-full active:scale-90 transition-transform" onClick={() => updateQuantity(item.id, item.quantity + 1)}>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<span className="font-semibold">\${(item.price * item.quantity).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Order Summary */}
|
||||
<div>
|
||||
<Card className="border-stone-200 sticky top-28 backdrop-blur-xl bg-white/80 dark:bg-slate-900/80 border-white/20">
|
||||
<CardContent className="p-6">
|
||||
<h2 className="text-lg font-bold mb-6">Order Summary</h2>
|
||||
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-stone-500">Subtotal</span>
|
||||
<span className="font-medium">\${total.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-stone-500">Shipping</span>
|
||||
<span className={\`font-medium \${shipping === 0 ? "text-green-600" : ""}\`}>
|
||||
{shipping === 0 ? "Free" : \`\$\${shipping.toFixed(2)}\`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-stone-500">Tax</span>
|
||||
<span className="font-medium">\${tax.toFixed(2)}</span>
|
||||
</div>
|
||||
{couponApplied && (
|
||||
<div className="flex justify-between text-green-600">
|
||||
<span>Discount (10%)</span>
|
||||
<span>-\${discount.toFixed(2)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<div className="flex justify-between text-lg font-bold">
|
||||
<span>Total</span>
|
||||
<span>\${grandTotal.toFixed(2)}</span>
|
||||
</div>
|
||||
|
||||
{/* Coupon */}
|
||||
<div className="mt-6 flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Tag className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-stone-400" />
|
||||
<Input
|
||||
placeholder="Coupon code"
|
||||
value={coupon}
|
||||
onChange={(e) => setCoupon(e.target.value)}
|
||||
className="pl-9 rounded-full text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="rounded-full" onClick={applyCoupon}>
|
||||
Apply
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{shipping > 0 && (
|
||||
<p className="text-xs text-stone-400 mt-4 text-center">
|
||||
Add \${(100 - total).toFixed(2)} more for free shipping
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-6 space-y-3">
|
||||
<Button asChild size="lg" className="w-full bg-stone-900 hover:bg-stone-800 rounded-full active:scale-[0.98] transition-all">
|
||||
<Link to="/checkout">Checkout <ArrowRight className="ml-2 h-4 w-4" /></Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg" className="w-full rounded-full active:scale-[0.98] transition-all">
|
||||
<Link to="/products"><ArrowLeft className="mr-2 h-4 w-4" /> Continue Shopping</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user