dstpabuaran.com/resources/js/components/home/ProductModal.tsx
Yoga Pangestu 4733df5827 Add homepage types and update routing to use HomepageController
- Created a new TypeScript file for homepage-related types including Product, ProductVariant, ProductCategory, and HomepageData.
- Updated the index.ts to export the new homepage types.
- Refactored the web routes to replace the inline closure for the home route with the HomepageController.
2026-08-07 17:29:59 +07:00

222 lines
11 KiB
TypeScript

import { X, Star, Minus, Plus, Phone } from 'lucide-react';
import { useState } from 'react';
import { formatRupiah } from '@/lib/rupiah';
import type { Product, ProductVariant, ProductPrice } from '@/types/homepage';
type ProductModalProps = {
product: Product | null;
onClose: () => void;
};
function getProductImage(): string {
return 'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1000&auto=format&fit=crop&q=80';
}
function getRetailPrice(variant: ProductVariant): number {
const retail = variant.product_prices.find((p) => p.type === 'retail');
return retail?.price ?? 0;
}
function getWholesalePrice(variant: ProductVariant): number {
const wholesale = variant.product_prices.find((p) => p.type === 'wholesale');
return wholesale?.price ?? 0;
}
export default function ProductModal({ product, onClose }: ProductModalProps) {
const [selectedVariant, setSelectedVariant] = useState<ProductVariant | null>(null);
const [selectedPriceType, setSelectedPriceType] = useState<'retail' | 'wholesale'>('retail');
const [quantity, setQuantity] = useState(1);
if (!product) return null;
const variants = product.product_variants;
const activeVariant = selectedVariant ?? variants[0];
const retailPrice = activeVariant ? getRetailPrice(activeVariant) : 0;
const wholesalePrice = activeVariant ? getWholesalePrice(activeVariant) : 0;
const currentPrice = selectedPriceType === 'retail' ? retailPrice : wholesalePrice;
const maxStock = activeVariant?.stock ?? 0;
const handleWhatsAppOrder = () => {
if (!activeVariant) return;
const priceLabel = selectedPriceType === 'retail' ? 'Retail' : 'Grosir';
const message = `Halo, saya tertarik dengan produk:\n\n*${product.name}*\n- Varian: ${activeVariant.name}\n- Harga ${priceLabel}: Rp ${formatRupiah(currentPrice)}\n- Jumlah: ${quantity} pcs\n- Subtotal: Rp ${formatRupiah(currentPrice * quantity)}\n\nMohon info detail pesanan. Terima kasih!`;
const phone = '6281234567890';
const waUrl = `https://wa.me/${phone}?text=${encodeURIComponent(message)}`;
window.open(waUrl, '_blank');
};
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
onClick={onClose}
>
<div
className="bg-[#FDFDFC] rounded-3xl max-w-4xl w-full max-h-[90vh] overflow-y-auto shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex justify-between items-center p-6 border-b border-amber-100/30">
<h3 className="text-lg font-serif text-slate-800">Detail Produk</h3>
<button
onClick={onClose}
className="p-2 rounded-full hover:bg-slate-100 transition-colors"
>
<X className="w-5 h-5 text-slate-500" />
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6">
{/* Image */}
<div className="relative aspect-3/4 rounded-2xl overflow-hidden">
<img
src={getProductImage()}
alt={product.name}
className="w-full h-full object-cover"
/>
</div>
{/* Details */}
<div className="space-y-4">
<div>
<div className="flex items-center gap-2 mb-2">
{product.categories.slice(0, 2).map((cat) => (
<span
key={cat.id}
className="text-[10px] font-semibold uppercase tracking-wider text-amber-600 bg-amber-50 px-2 py-0.5 rounded-full"
>
{cat.name}
</span>
))}
</div>
<h2 className="text-2xl font-serif text-slate-800">{product.name}</h2>
</div>
<p className="text-sm text-slate-500 leading-relaxed font-light">
{product.description || 'Produk fashion berkualitas dengan desain elegan dan bahan nyaman.'}
</p>
{/* Rating */}
<div className="flex items-center gap-2">
<div className="flex text-amber-400">
<Star className="w-4 h-4 fill-current" />
<Star className="w-4 h-4 fill-current" />
<Star className="w-4 h-4 fill-current" />
<Star className="w-4 h-4 fill-current" />
<Star className="w-4 h-4 fill-current" />
</div>
<span className="text-xs text-slate-400">5.0 (120+)</span>
</div>
{/* Price */}
<div className="bg-amber-50/50 p-4 rounded-xl">
<p className="text-xs text-slate-500 mb-1">Harga</p>
<p className="text-2xl font-bold text-amber-600">
Rp {formatRupiah(currentPrice)}
</p>
{selectedPriceType === 'wholesale' && wholesalePrice > 0 && (
<p className="text-xs text-slate-400 mt-1">
Harga retail: Rp {formatRupiah(retailPrice)}
</p>
)}
</div>
{/* Variant Selection */}
{variants.length > 0 && (
<div>
<p className="text-xs font-semibold text-slate-700 mb-2">Varian</p>
<div className="flex flex-wrap gap-2">
{variants.map((variant) => (
<button
key={variant.id}
onClick={() => setSelectedVariant(variant)}
className={`px-4 py-2 text-xs font-semibold rounded-xl border transition-all ${
activeVariant?.id === variant.id
? 'bg-amber-600 text-white border-transparent'
: 'bg-white text-slate-600 border-slate-200 hover:border-amber-300'
}`}
>
{variant.name}
<span className="ml-1 text-[10px] opacity-70">
({variant.stock})
</span>
</button>
))}
</div>
</div>
)}
{/* Price Type */}
<div>
<p className="text-xs font-semibold text-slate-700 mb-2">Jenis Harga</p>
<div className="flex gap-2">
<button
onClick={() => setSelectedPriceType('retail')}
className={`px-4 py-2 text-xs font-semibold rounded-xl border transition-all ${
selectedPriceType === 'retail'
? 'bg-amber-600 text-white border-transparent'
: 'bg-white text-slate-600 border-slate-200 hover:border-amber-300'
}`}
>
Retail
</button>
<button
onClick={() => setSelectedPriceType('wholesale')}
className={`px-4 py-2 text-xs font-semibold rounded-xl border transition-all ${
selectedPriceType === 'wholesale'
? 'bg-amber-600 text-white border-transparent'
: 'bg-white text-slate-600 border-slate-200 hover:border-amber-300'
}`}
>
Grosir
</button>
</div>
</div>
{/* Quantity */}
<div>
<p className="text-xs font-semibold text-slate-700 mb-2">Jumlah</p>
<div className="flex items-center gap-3 border border-slate-200 rounded-xl px-3 py-2 w-fit">
<button
onClick={() => setQuantity(Math.max(1, quantity - 1))}
className="p-1 text-slate-500 hover:text-slate-800"
>
<Minus className="w-4 h-4" />
</button>
<span className="text-sm font-semibold w-8 text-center">{quantity}</span>
<button
onClick={() => setQuantity(Math.min(maxStock, quantity + 1))}
className="p-1 text-slate-500 hover:text-slate-800"
>
<Plus className="w-4 h-4" />
</button>
</div>
<p className="text-[10px] text-slate-400 mt-1">Stok tersedia: {maxStock}</p>
</div>
{/* Total */}
<div className="bg-slate-50 p-4 rounded-xl">
<div className="flex justify-between text-sm">
<span className="text-slate-500">Subtotal</span>
<span className="font-bold text-amber-600">
Rp {formatRupiah(currentPrice * quantity)}
</span>
</div>
</div>
{/* WhatsApp Order */}
<button
onClick={handleWhatsAppOrder}
className="w-full flex items-center justify-center bg-emerald-600 text-white hover:bg-emerald-500 py-3.5 text-xs font-bold uppercase tracking-widest rounded-xl transition-all shadow-md"
>
<Phone className="w-4 h-4 mr-2" />
Pesan Via WhatsApp
</button>
</div>
</div>
</div>
</div>
);
}