68 lines
4.2 KiB
TypeScript
68 lines
4.2 KiB
TypeScript
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||
import { Link, useNavigate } from 'react-router-dom'
|
||
import { Trash2, Flower2, ShoppingCart } from 'lucide-react'
|
||
import { getCart, updateCartItem, removeCartItem } from '../api/client'
|
||
import { money, useShop } from '../store/shop'
|
||
|
||
export default function Cart() {
|
||
const qc = useQueryClient()
|
||
const nav = useNavigate()
|
||
const { custToken, setCartCount } = useShop()
|
||
const { data: items } = useQuery({ queryKey: ['cart'], queryFn: getCart, enabled: !!custToken })
|
||
|
||
const refresh = () => qc.invalidateQueries({ queryKey: ['cart'] })
|
||
const setQty = async (id: number, q: number) => { if (q < 1) return; await updateCartItem(id, q); refresh() }
|
||
const remove = async (id: number) => { await removeCartItem(id); refresh(); setCartCount((c: number) => Math.max(0, c - 1)) }
|
||
|
||
if (!custToken) return (
|
||
<div className="max-w-3xl mx-auto px-4 py-20 text-center">
|
||
<ShoppingCart className="mx-auto text-bloom/40 mb-3" size={48} />
|
||
<p className="text-gray-500 mb-4">Please sign in to view your cart.</p>
|
||
<Link to="/account" className="bg-bloom text-white px-6 py-2.5 rounded-full font-semibold">Sign In / Register</Link>
|
||
</div>
|
||
)
|
||
|
||
const list = items || []
|
||
const subtotal = list.reduce((s: number, i: any) => s + (i.price || (i.unitPrice || 0) * (i.quantity || 1)), 0)
|
||
|
||
return (
|
||
<div className="max-w-4xl mx-auto px-4 py-8">
|
||
<h1 className="font-serif text-2xl font-bold mb-6">Cart</h1>
|
||
{!list.length ? (
|
||
<div className="text-center text-gray-400 py-16">Your cart is empty. <Link to="/category" className="text-bloom">Start Shopping →</Link></div>
|
||
) : (
|
||
<div className="grid md:grid-cols-3 gap-6">
|
||
<div className="md:col-span-2 space-y-3">
|
||
{list.map((i: any) => (
|
||
<div key={i.id} className="bg-white rounded-2xl border border-blush-100/60 p-4 flex gap-4">
|
||
<div className="w-20 h-20 bg-petal rounded-xl flex items-center justify-center overflow-hidden shrink-0">
|
||
{i.thumbnail ? <img src={i.thumbnail} className="w-full h-full object-cover" /> : <Flower2 className="text-bloom/30" size={32} />}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="font-medium text-sm">{i.productName || `Product #${i.productId}`}</div>
|
||
<div className="text-xs text-gray-500">{i.sizeCode}{i.cardMessage ? ` · Card: ${i.cardMessage.slice(0, 20)}` : ''}</div>
|
||
<div className="flex items-center gap-3 mt-2">
|
||
<div className="flex items-center border border-blush-100 rounded-full text-sm">
|
||
<button onClick={() => setQty(i.id, (i.quantity || 1) - 1)} className="px-2.5 py-1 text-bloom2">−</button>
|
||
<span className="px-1 w-6 text-center">{i.quantity || 1}</span>
|
||
<button onClick={() => setQty(i.id, (i.quantity || 1) + 1)} className="px-2.5 py-1 text-bloom2">+</button>
|
||
</div>
|
||
<button onClick={() => remove(i.id)} className="text-blush-400 hover:text-blush-600"><Trash2 size={16} /></button>
|
||
</div>
|
||
</div>
|
||
<div className="font-bold text-bloom2 text-sm">{money(i.price || (i.unitPrice || 0) * (i.quantity || 1))}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="bg-white rounded-2xl border border-blush-100/60 p-5 h-fit">
|
||
<div className="flex justify-between text-sm mb-2"><span className="text-gray-500">Subtotal</span><span className="font-medium">{money(subtotal)}</span></div>
|
||
<div className="flex justify-between text-sm mb-3"><span className="text-gray-500">Shipping/Tax</span><span className="text-gray-400">Calculated at checkout</span></div>
|
||
<div className="border-t border-blush-100/60 pt-3 flex justify-between font-bold"><span>Total</span><span className="text-bloom2">{money(subtotal)}</span></div>
|
||
<button onClick={() => nav('/checkout')} className="w-full mt-4 bg-bloom text-white font-semibold py-3 rounded-full hover:bg-bloom2">Checkout</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|