- 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.
144 lines
5.8 KiB
TypeScript
144 lines
5.8 KiB
TypeScript
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useCart } from '@/contexts/cart-context';
|
|
import { formatRupiah } from '@/lib/rupiah';
|
|
import { formatWhatsAppPhone } from '@/lib/format';
|
|
import type { Product } from '@/types/homepage';
|
|
import { ShoppingBag, ShoppingCart } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
|
|
type ProductCardProps = {
|
|
product: Product;
|
|
onQuickView: (product: Product) => void;
|
|
contactPhone?: string | null;
|
|
};
|
|
|
|
const FALLBACK_IMAGE = 'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80';
|
|
|
|
function getProductImage(product: Product): string {
|
|
for (const variant of product.product_variants) {
|
|
if (variant.photo_urls && variant.photo_urls.length > 0) {
|
|
return variant.photo_urls[0];
|
|
}
|
|
}
|
|
return FALLBACK_IMAGE;
|
|
}
|
|
|
|
function getProductPriceRange(product: Product): string {
|
|
const prices = product.product_variants.flatMap((v) =>
|
|
v.product_prices
|
|
.filter((p) => p.type === 'retail')
|
|
.map((p) => p.price),
|
|
);
|
|
|
|
if (prices.length === 0) {
|
|
const fallbackPrices = product.product_variants.flatMap((v) =>
|
|
v.product_prices.map((p) => p.price),
|
|
);
|
|
|
|
if (fallbackPrices.length === 0) {
|
|
return 'Rp 0';
|
|
}
|
|
|
|
const min = Math.min(...fallbackPrices);
|
|
const max = Math.max(...fallbackPrices);
|
|
|
|
return min === max
|
|
? `Rp ${formatRupiah(min)}`
|
|
: `Rp ${formatRupiah(min)} - Rp ${formatRupiah(max)}`;
|
|
}
|
|
|
|
const min = Math.min(...prices);
|
|
const max = Math.max(...prices);
|
|
|
|
return min === max
|
|
? `Rp ${formatRupiah(min)}`
|
|
: `Rp ${formatRupiah(min)} - Rp ${formatRupiah(max)}`;
|
|
}
|
|
|
|
export default function ProductCard({ product, onQuickView, contactPhone }: ProductCardProps) {
|
|
const { addItem } = useCart();
|
|
|
|
console.log(contactPhone)
|
|
|
|
const handleAddToCart = () => {
|
|
const variant = product.product_variants[0];
|
|
if (!variant) return;
|
|
|
|
const retail = variant.product_prices.find((p) => p.type === 'retail');
|
|
const wholesale = variant.product_prices.find((p) => p.type === 'wholesale');
|
|
const price = retail?.price ?? wholesale?.price ?? 0;
|
|
|
|
addItem({
|
|
productId: product.id,
|
|
productName: product.name,
|
|
variantId: variant.id,
|
|
variantName: variant.name,
|
|
priceType: 'retail',
|
|
price,
|
|
imageUrl: getProductImage(product),
|
|
});
|
|
toast.success(`${product.name} ditambahkan ke keranjang`);
|
|
};
|
|
|
|
const variant = product.product_variants[0];
|
|
const retail = variant?.product_prices.find((p) => p.type === 'retail');
|
|
const price = retail?.price ?? 0;
|
|
const waMessage = encodeURIComponent(
|
|
`Halo min, saya mau order, berikut pesanannya.\n\n*${product.name}*\n* Varian: ${variant?.name ?? '-'}\n* Harga: Rp ${formatRupiah(price)}\n* QTY : 1\n* Subtotal : Rp ${formatRupiah(price)}\n\nTotal: Rp ${formatRupiah(price)}\n\nMohon diinformasikan detail pembayaran dan proses selanjutnya ya, Kak.\n\nTerimakasih`
|
|
);
|
|
const waUrl = `https://wa.me/${formatWhatsAppPhone(contactPhone || '')}?text=${waMessage}`;
|
|
|
|
return (
|
|
<div className="cursor-pointer" onClick={() => onQuickView(product)}>
|
|
<div className="relative aspect-3/4 rounded-2xl overflow-hidden shadow-md border-2 border-white transition-all duration-300 hover:shadow-xl hover:-translate-y-1">
|
|
<img
|
|
src={getProductImage(product)}
|
|
alt={product.name}
|
|
className="w-full h-full object-cover transition-transform duration-700 hover:scale-110"
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-4 space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
{product.categories.slice(0, 2).map((cat) => (
|
|
<Badge key={cat.id} variant="secondary" className="bg-amber-50 text-amber-600 uppercase tracking-wider font-semibold px-2 py-0.5">
|
|
{cat.name}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
<h4 className="text-sm font-semibold text-slate-800 line-clamp-1">
|
|
{product.name}
|
|
</h4>
|
|
<p className="text-sm font-bold text-amber-600">
|
|
{getProductPriceRange(product)}
|
|
</p>
|
|
<div className="flex items-center gap-2 pt-1">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (product.product_variants.length === 1) {
|
|
handleAddToCart();
|
|
} else {
|
|
onQuickView(product);
|
|
}
|
|
}}
|
|
className="w-9 h-9 rounded-full bg-amber-50 text-amber-600 hover:bg-amber-100 border-amber-200"
|
|
aria-label="Tambah ke Keranjang"
|
|
>
|
|
<ShoppingBag className="w-4 h-4" />
|
|
</Button>
|
|
<Button asChild className="flex-1 bg-amber-500 hover:bg-amber-600 text-white text-[11px] font-semibold uppercase tracking-wider py-2 rounded-lg transition-colors">
|
|
<a href={contactPhone ? waUrl : '#'} target="_blank" rel="noreferrer">
|
|
<ShoppingCart className="w-4 h-4" />
|
|
Beli Sekarang
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|