- 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.
118 lines
4.6 KiB
TypeScript
118 lines
4.6 KiB
TypeScript
import { formatRupiah } from '@/lib/rupiah';
|
|
import type { Product } from '@/types/homepage';
|
|
|
|
type ProductCardProps = {
|
|
product: Product;
|
|
onQuickView: (product: Product) => void;
|
|
};
|
|
|
|
function getProductImage(product: Product): string {
|
|
for (const variant of product.product_variants) {
|
|
const prices = variant.product_prices;
|
|
if (prices.length > 0) {
|
|
return `https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80`;
|
|
}
|
|
}
|
|
|
|
const fallbacks: Record<string, string[]> = {
|
|
'daster': [
|
|
'https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80',
|
|
],
|
|
'setelan-celana': [
|
|
'https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80',
|
|
],
|
|
'atasan': [
|
|
'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80',
|
|
],
|
|
'bawahan': [
|
|
'https://images.unsplash.com/photo-1583496661160-fb4886b36ca7?w=800&auto=format&fit=crop&q=80',
|
|
],
|
|
};
|
|
|
|
const firstCat = product.categories?.[0]?.slug || '';
|
|
const categoryUrls = fallbacks[firstCat] || [
|
|
'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?w=800&auto=format&fit=crop&q=80',
|
|
];
|
|
|
|
return categoryUrls[0];
|
|
}
|
|
|
|
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 }: ProductCardProps) {
|
|
return (
|
|
<div
|
|
className="group 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 group-hover:scale-110"
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
|
<div className="absolute bottom-0 left-0 right-0 p-4 translate-y-full group-hover:translate-y-0 transition-transform duration-300">
|
|
<span className="text-white text-xs font-semibold uppercase tracking-widest">
|
|
Lihat Detail
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 space-y-1">
|
|
<div className="flex items-center gap-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>
|
|
<h4 className="text-sm font-semibold text-slate-800 line-clamp-1">
|
|
{product.name}
|
|
</h4>
|
|
<p className="text-xs text-slate-500 line-clamp-2 font-light">
|
|
{product.description || 'Koleksi fashion berkualitas'}
|
|
</p>
|
|
<p className="text-sm font-bold text-amber-600">
|
|
{getProductPriceRange(product)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|