dstpabuaran.com/resources/js/components/home/CartDrawer.tsx
Yoga Pangestu 7d21a1e6db feat: add cart context and functionality, update welcome page with new components and styles
- Implemented CartContext for managing cart state, including adding, removing, and updating items.
- Added CartDrawer component to display cart items.
- Updated AppHeader to include cart item count.
- Enhanced welcome page with new layout, improved product image handling, and updated call-to-action buttons.
- Refactored homepage data types to accommodate new features and removed unused properties.
2026-08-13 17:19:56 +07:00

114 lines
6.8 KiB
TypeScript

import { Button } from '@/components/ui/button';
import { useCart } from '@/contexts/cart-context';
import { formatRupiah } from '@/lib/rupiah';
import { formatWhatsAppPhone } from '@/lib/format';
import { Minus, Plus, ShoppingBag, ShoppingCart, Trash2, X } from 'lucide-react';
type CartDrawerProps = {
open: boolean;
onClose: () => void;
contactPhone?: string | null;
};
export default function CartDrawer({ open, onClose, contactPhone }: CartDrawerProps) {
const { items, removeItem, updateQuantity, clearCart, totalItems, totalPrice } = useCart();
if (!open) return null;
const handleCheckout = () => {
if (items.length === 0 || !contactPhone) return;
const lines = items.map((item) => {
const subtotal = item.price * item.quantity;
return `*${item.productName}*\n* Varian: ${item.variantName}\n* Harga: Rp ${formatRupiah(item.price)}\n* QTY : ${item.quantity}\n* Subtotal : Rp ${formatRupiah(subtotal)}`;
});
const message = `Halo min, saya mau order, berikut pesanannya.\n\n${lines.join('\n\n')}\n\nTotal: Rp ${formatRupiah(totalPrice)}\n\nMohon diinformasikan detail pembayaran dan proses selanjutnya ya, Kak.\n\nTerimakasih`;
const waUrl = `https://wa.me/${formatWhatsAppPhone(contactPhone || '')}?text=${encodeURIComponent(message)}`;
window.open(waUrl, '_blank');
};
return (
<div className="fixed inset-0 z-50 flex justify-end" onClick={onClose}>
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
<div
className="relative w-full max-w-md bg-[#FDFDFC] shadow-2xl flex flex-col animate-in slide-in-from-right duration-300"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-5 border-b border-amber-100/30">
<div className="flex items-center gap-2">
<ShoppingBag className="w-5 h-5 text-amber-600" />
<h3 className="text-lg font-serif text-slate-800">Keranjang</h3>
<span className="text-xs text-slate-400">({totalItems} item)</span>
</div>
<div className="flex items-center gap-2">
{items.length > 0 && (
<Button variant="ghost" size="sm" onClick={clearCart} className="text-xs text-red-500 hover:text-red-600 hover:bg-red-50">
<Trash2 className="w-3.5 h-3.5 mr-1" />
Hapus Semua
</Button>
)}
<Button variant="ghost" size="icon" onClick={onClose} className="rounded-full hover:bg-slate-100">
<X className="w-5 h-5 text-slate-500" />
</Button>
</div>
</div>
{/* Items */}
<div className="flex-1 overflow-y-auto p-5 space-y-4">
{items.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center space-y-3">
<ShoppingBag className="w-12 h-12 text-slate-200" />
<p className="text-sm text-slate-400">Keranjang kosong</p>
<p className="text-xs text-slate-300">Tambahkan produk untuk mulai berbelanja</p>
</div>
) : (
items.map((item) => (
<div key={item.variantId} className="flex gap-3 p-3 bg-white rounded-xl border border-slate-100 shadow-sm">
<div className="w-16 h-20 rounded-lg overflow-hidden shrink-0">
<img src={item.imageUrl} alt={item.productName} className="w-full h-full object-cover" />
</div>
<div className="flex-1 min-w-0 space-y-1">
<p className="text-sm font-semibold text-slate-800 truncate">{item.productName}</p>
<p className="text-[10px] text-slate-400">{item.variantName} &middot; {item.priceType === 'retail' ? 'Retail' : 'Grosir'}</p>
<p className="text-sm font-bold text-amber-600">Rp {formatRupiah(item.price)}</p>
<div className="flex items-center justify-between pt-1">
<div className="flex items-center gap-2 border border-slate-200 rounded-lg px-2 py-0.5">
<Button variant="ghost" size="icon-xs" onClick={() => updateQuantity(item.variantId, item.quantity - 1)}>
<Minus className="w-3 h-3" />
</Button>
<span className="text-xs font-semibold w-5 text-center">{item.quantity}</span>
<Button variant="ghost" size="icon-xs" onClick={() => updateQuantity(item.variantId, item.quantity + 1)}>
<Plus className="w-3 h-3" />
</Button>
</div>
<Button variant="ghost" size="icon-xs" onClick={() => removeItem(item.variantId)} className="text-red-400 hover:text-red-500 hover:bg-red-50">
<Trash2 className="w-3.5 h-3.5" />
</Button>
</div>
</div>
</div>
))
)}
</div>
{/* Footer */}
{items.length > 0 && (
<div className="border-t border-amber-100/30 p-5 space-y-4">
<div className="flex justify-between items-center">
<span className="text-sm text-slate-500">Total</span>
<span className="text-xl font-bold text-amber-600">Rp {formatRupiah(totalPrice)}</span>
</div>
<Button asChild className="w-full bg-amber-600 text-white hover:bg-amber-500 py-3.5 text-xs font-bold uppercase tracking-widest rounded-xl transition-all shadow-md">
<a href="#" onClick={(e) => { e.preventDefault(); handleCheckout(); }}>
<ShoppingCart className="w-4 h-4 mr-2" />
Beli Sekarang
</a>
</Button>
</div>
)}
</div>
</div>
);
}