feat(F-102): completed feature
This commit is contained in:
@@ -185,6 +185,7 @@ export default async function ProductPage({ params }: Props) {
|
||||
priceCents={grossCents}
|
||||
imageUrl={product.images?.[0]?.url}
|
||||
available={true}
|
||||
minPurchaseQty={product.minPurchaseQty ?? 1}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
|
||||
@@ -9,18 +9,20 @@ interface Props {
|
||||
priceCents: number;
|
||||
imageUrl?: string;
|
||||
available?: boolean;
|
||||
minPurchaseQty?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function AddToCartButton({
|
||||
variantId, productId, productName, priceCents, imageUrl, available = true, className = '',
|
||||
variantId, productId, productName, priceCents, imageUrl, available = true, minPurchaseQty = 1, className = '',
|
||||
}: Props) {
|
||||
const { addItem, itemCount } = useCart();
|
||||
const [added, setAdded] = useState(false);
|
||||
const qty = Math.max(1, minPurchaseQty);
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!available) return;
|
||||
addItem({ variantId, productId, productName, quantity: 1, priceCents, imageUrl });
|
||||
addItem({ variantId, productId, productName, quantity: qty, priceCents, imageUrl, minPurchaseQty: qty });
|
||||
setAdded(true);
|
||||
setTimeout(() => setAdded(false), 2000);
|
||||
};
|
||||
@@ -42,11 +44,16 @@ export default function AddToCartButton({
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className={`px-8 py-3.5 bg-[#70ad47] hover:bg-[#5a9040] text-white font-semibold rounded-xl transition-colors shadow-lg ${className}`}
|
||||
>
|
||||
Añadir al carrito
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
className={`px-8 py-3.5 bg-[#70ad47] hover:bg-[#5a9040] text-white font-semibold rounded-xl transition-colors shadow-lg ${className}`}
|
||||
>
|
||||
Añadir al carrito{qty > 1 ? ` (${qty} uds.)` : ''}
|
||||
</button>
|
||||
{qty > 1 && (
|
||||
<p className="mt-2 text-xs text-gray-500">Compra mínima: {qty} unidades.</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ function CartItemRow({ item }: { item: CartItem }) {
|
||||
<div className="flex items-center border border-gray-300 rounded-lg">
|
||||
<button
|
||||
onClick={() => changeQuantity(item.variantId, item.quantity - 1)}
|
||||
className="w-8 h-8 flex items-center justify-center text-gray-600 hover:text-[#70ad47] transition-colors"
|
||||
disabled={item.quantity <= (item.minPurchaseQty ?? 1)}
|
||||
title={item.quantity <= (item.minPurchaseQty ?? 1) && (item.minPurchaseQty ?? 1) > 1 ? `Compra mínima: ${item.minPurchaseQty} uds.` : undefined}
|
||||
className="w-8 h-8 flex items-center justify-center text-gray-600 hover:text-[#70ad47] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
|
||||
@@ -9,9 +9,10 @@ interface Props {
|
||||
priceCents: number;
|
||||
imageUrl?: string;
|
||||
available: boolean;
|
||||
minPurchaseQty?: number;
|
||||
}
|
||||
|
||||
export default function ProductAddToCart({ variantId, productId, productName, priceCents, imageUrl, available }: Props) {
|
||||
export default function ProductAddToCart({ variantId, productId, productName, priceCents, imageUrl, available, minPurchaseQty }: Props) {
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<AddToCartButton
|
||||
@@ -21,6 +22,7 @@ export default function ProductAddToCart({ variantId, productId, productName, pr
|
||||
priceCents={priceCents}
|
||||
imageUrl={imageUrl}
|
||||
available={available}
|
||||
minPurchaseQty={minPurchaseQty}
|
||||
className="w-full sm:w-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface CartItem {
|
||||
quantity: number;
|
||||
priceCents: number;
|
||||
imageUrl?: string;
|
||||
/** Cantidad mínima de compra del producto (F-102). */
|
||||
minPurchaseQty?: number;
|
||||
}
|
||||
|
||||
interface CartContextValue {
|
||||
@@ -39,7 +41,15 @@ export function CartProvider({ children }: { children: React.ReactNode }) {
|
||||
setItems((prev) => {
|
||||
const existing = prev.find((i) => i.variantId === item.variantId);
|
||||
const next = existing
|
||||
? prev.map((i) => i.variantId === item.variantId ? { ...i, quantity: i.quantity + item.quantity } : i)
|
||||
? prev.map((i) =>
|
||||
i.variantId === item.variantId
|
||||
? {
|
||||
...i,
|
||||
quantity: i.quantity + item.quantity,
|
||||
minPurchaseQty: Math.max(i.minPurchaseQty ?? 1, item.minPurchaseQty ?? 1),
|
||||
}
|
||||
: i,
|
||||
)
|
||||
: [...prev, item];
|
||||
localStorage.setItem('mdv_cart', JSON.stringify(next));
|
||||
return next;
|
||||
@@ -56,9 +66,14 @@ export function CartProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const changeQuantity = useCallback((variantId: string, quantity: number) => {
|
||||
setItems((prev) => {
|
||||
const next = quantity <= 0
|
||||
? prev.filter((i) => i.variantId !== variantId)
|
||||
: prev.map((i) => i.variantId === variantId ? { ...i, quantity } : i);
|
||||
const next =
|
||||
quantity <= 0
|
||||
? prev.filter((i) => i.variantId !== variantId)
|
||||
: prev.map((i) =>
|
||||
i.variantId === variantId
|
||||
? { ...i, quantity: Math.max(quantity, i.minPurchaseQty ?? 1) }
|
||||
: i,
|
||||
);
|
||||
localStorage.setItem('mdv_cart', JSON.stringify(next));
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface Product {
|
||||
priceCents?: number; // populated via separate pricing lookup
|
||||
brand?: { id: string; name: string; slug: string };
|
||||
imageUrl?: string;
|
||||
unitWeightKg?: number;
|
||||
minPurchaseQty?: number;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
|
||||
Reference in New Issue
Block a user