chore: add client/src/pages/cart.tsx

This commit is contained in:
notshop 2026-04-26 16:36:08 +00:00
parent 5ad1e8a6df
commit d1b0b0fd7b

82
client/src/pages/cart.tsx Normal file
View file

@ -0,0 +1,82 @@
import { Link } from "wouter";
import { ShoppingBag, Trash2 } from "lucide-react";
import { useCart } from "../hooks/use-cart";
export default function CartPage() {
const { items, updateQuantity, removeItem, subtotal } = useCart();
if (items.length === 0) {
return (
<div className="max-w-3xl mx-auto px-4 py-20 text-center" data-testid="cart-empty">
<ShoppingBag size={48} className="mx-auto mb-4 text-muted-foreground/30" />
<h1 className="text-2xl font-bold mb-3">Your cart is empty</h1>
<Link href="/" className="inline-block bg-primary text-primary-foreground px-6 py-3 rounded-md font-medium hover:bg-primary/90 transition-colors">
Browse products
</Link>
</div>
);
}
return (
<div className="max-w-4xl mx-auto px-4 py-10" data-testid="cart-page">
<h1 className="text-2xl font-bold mb-8">Shopping cart ({items.length})</h1>
<div className="grid md:grid-cols-3 gap-8">
<div className="md:col-span-2 space-y-4">
{items.map((item) => (
<div key={item.id} className="flex gap-4 border border-border rounded-xl p-4" data-testid={`cart-row-${item.id}`}>
{item.imageUrl && (
<img src={item.imageUrl} alt={item.title} className="w-20 h-20 object-cover rounded-md border border-border flex-shrink-0" />
)}
<div className="flex-1 min-w-0">
<Link href={`/products/${item.productHandle}`} className="font-medium hover:text-primary transition-colors line-clamp-2">
{item.title}
</Link>
{item.variantTitle && item.variantTitle !== "Default Title" && (
<p className="text-sm text-muted-foreground">{item.variantTitle}</p>
)}
<p className="text-sm font-semibold mt-1">${parseFloat(item.price).toFixed(2)}</p>
</div>
<div className="flex flex-col items-end gap-3">
<button onClick={() => removeItem(item.id)} className="text-muted-foreground hover:text-destructive transition-colors" data-testid={`remove-item-${item.id}`}>
<Trash2 size={15} />
</button>
<div className="flex items-center border border-border rounded-md overflow-hidden">
<button onClick={() => updateQuantity(item.id, item.quantity - 1)} className="px-3 py-1.5 text-sm hover:bg-muted transition-colors" data-testid={`decrease-${item.id}`}></button>
<span className="px-3 py-1.5 text-sm border-x border-border min-w-[2.5rem] text-center" data-testid={`qty-${item.id}`}>{item.quantity}</span>
<button onClick={() => updateQuantity(item.id, item.quantity + 1)} className="px-3 py-1.5 text-sm hover:bg-muted transition-colors" data-testid={`increase-${item.id}`}>+</button>
</div>
<span className="text-sm font-semibold">${(parseFloat(item.price) * item.quantity).toFixed(2)}</span>
</div>
</div>
))}
</div>
<div>
<div className="border border-border rounded-xl p-5 sticky top-24">
<h2 className="font-semibold mb-4">Order summary</h2>
<div className="space-y-2 mb-4 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Subtotal</span>
<span>${subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Shipping</span>
<span className="text-muted-foreground">Calculated at checkout</span>
</div>
</div>
<div className="flex justify-between font-semibold border-t border-border pt-4 mb-4">
<span>Total</span>
<span data-testid="cart-page-total">${subtotal.toFixed(2)}</span>
</div>
<Link href="/checkout" className="w-full bg-primary text-primary-foreground py-3 rounded-md font-semibold block text-center hover:bg-primary/90 transition-colors" data-testid="link-to-checkout">
Proceed to checkout
</Link>
<Link href="/" className="w-full text-sm text-muted-foreground hover:text-foreground block text-center mt-3 transition-colors">
Continue shopping
</Link>
</div>
</div>
</div>
</div>
);
}