chore: add ansible backend docker frontend-prep

This commit is contained in:
2026-01-21 13:05:13 +01:00
parent 5a280b6b01
commit 943fe4de7d
14930 changed files with 2341433 additions and 0 deletions
@@ -0,0 +1,223 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useCart } from "../context/CartContext";
import "./ProductCard.css";
interface ProductCardProps {
id: number;
name: string;
price: number;
unit: string;
image: string;
stock: number;
category: string;
prices?: Array<{ quantity: number; price: number }>;
hasVideo?: boolean;
videoUrl?: string; // ✨ Nouveau prop pour l'URL de la vidéo
}
function ProductCard({
id,
name,
price,
image,
stock,
category = "autre",
prices,
hasVideo = false,
videoUrl,
}: ProductCardProps) {
const navigate = useNavigate();
const { addToCart } = useCart();
const [showQuantitySelect, setShowQuantitySelect] = useState(false);
const [selectedQuantity, setSelectedQuantity] = useState<number | null>(
null,
);
const [showSuccess, setShowSuccess] = useState(false);
const [showVideo, setShowVideo] = useState(false); // ✨ État pour afficher/masquer la vidéo
const isOutOfStock = stock === 0;
const normalizedCategory = (category || "autre").toLowerCase().trim();
const handleDetailsClick = (e: React.MouseEvent) => {
e.stopPropagation();
navigate(`/user/product/${id}`);
};
// ✨ Handler pour afficher/masquer la vidéo
const handleVideoToggle = (e: React.MouseEvent) => {
e.stopPropagation();
setShowVideo(!showVideo);
};
// ✨ Handler pour fermer la vidéo
const handleCloseVideo = (e: React.MouseEvent) => {
e.stopPropagation();
setShowVideo(false);
};
const handleQuickAddClick = (e: React.MouseEvent) => {
e.stopPropagation();
console.log("🔘 Bouton cliqué", {
id,
name,
category: normalizedCategory,
isOutOfStock,
prices: prices?.length || 0,
showQuantitySelect,
});
if (!isOutOfStock && prices && prices.length > 0) {
setShowQuantitySelect(!showQuantitySelect);
}
};
const handleQuantitySelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedGrams = Number(e.target.value);
setSelectedQuantity(selectedGrams);
const priceOption = prices?.find((p) => p.quantity === selectedGrams);
if (priceOption) {
console.log("💾 [ProductCard] Ajout au panier:", {
id,
name,
category: normalizedCategory,
quantity: selectedGrams,
price: priceOption.price,
});
addToCart({
product_id: id,
name_product: name,
price: priceOption.price,
quantity: selectedGrams,
category: normalizedCategory,
image: image,
});
setShowSuccess(true);
setTimeout(() => {
setShowSuccess(false);
setShowQuantitySelect(false);
setSelectedQuantity(null);
}, 1500);
}
};
return (
<div className={`product-card ${isOutOfStock ? "out-of-stock" : ""}`}>
<div className="product-card-content">
<div className="product-image-container">
<img
src={image}
alt={name}
className="product-image3"
loading="lazy"
/>
{/* ✨ Icône caméra cliquable */}
{hasVideo && !isOutOfStock && videoUrl && (
<button
className="media-indicator"
onClick={handleVideoToggle}
aria-label="Voir la vidéo du produit"
>
<i className="fas fa-camera"></i>
</button>
)}
{/* ✨ Bouton "Détails" */}
<button
className="details-button"
onClick={handleDetailsClick}
aria-label="Voir les détails du produit"
>
<i className="fas fa-info-circle"></i>
<span>Détails</span>
</button>
{isOutOfStock && (
<div className="sold-out-overlay">SOLD OUT</div>
)}
</div>
<div className="product-info">
<h3 className="product-name">{name}</h3>
<p className="product-price">
{price > 0
? `${price.toFixed(2)}`
: "Prix non disponible"}
</p>
</div>
</div>
<div
className="quick-add-section"
onClick={(e) => e.stopPropagation()}
>
{showSuccess ? (
<div className="success-message"> Ajouté !</div>
) : (
<>
<button
className={`quick-add-btn ${isOutOfStock ? "disabled" : ""}`}
onClick={handleQuickAddClick}
disabled={isOutOfStock}
>
{isOutOfStock
? "Rupture de stock"
: "Ajouter rapidement"}
</button>
{showQuantitySelect && prices && prices.length > 0 && (
<select
className="quantity-select"
value={selectedQuantity ?? ""}
onChange={handleQuantitySelect}
onClick={(e) => e.stopPropagation()}
>
<option value="">Choisir une quantité</option>
{prices.map((priceOption) => (
<option
key={priceOption.quantity}
value={priceOption.quantity}
>
{priceOption.quantity}g -{" "}
{priceOption.price.toFixed(2)}
</option>
))}
</select>
)}
</>
)}
</div>
{/* ✨ Modal vidéo */}
{showVideo && videoUrl && (
<div className="video-modal" onClick={handleCloseVideo}>
<div
className="video-modal-content"
onClick={(e) => e.stopPropagation()}
>
<button
className="video-close-btn"
onClick={handleCloseVideo}
aria-label="Fermer la vidéo"
>
<i className="fas fa-times"></i>
</button>
<video
src={videoUrl}
controls
autoPlay
className="video-player"
>
Votre navigateur ne supporte pas la lecture de
vidéos.
</video>
</div>
</div>
)}
</div>
);
}
export default ProductCard;