feat/fix: checkout email tracing, UI tweaks for add-to-cart, cart page overflow fix, implement hide admin bar setting

This commit is contained in:
Dwindi Ramadhana
2026-02-27 23:15:10 +07:00
parent 687a2318b0
commit a62037d993
22 changed files with 2711 additions and 294 deletions

View File

@@ -30,43 +30,43 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
const { layout, elements, addToCart, saleBadge, isLoading } = useShopSettings();
const { isEnabled: wishlistEnabled, isInWishlist, toggleWishlist } = useWishlist();
const { isEnabled: isModuleEnabled } = useModules();
const showWishlist = isModuleEnabled('wishlist') && wishlistEnabled;
const inWishlist = showWishlist && isInWishlist(product.id);
const handleWishlistClick = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
await toggleWishlist(product.id);
};
// Aspect ratio classes
const aspectRatioClass = {
'square': 'aspect-square',
'portrait': 'aspect-[3/4]',
'landscape': 'aspect-[4/3]',
}[layout.aspect_ratio] || 'aspect-square';
const isVariable = product.type === 'variable';
const handleAddToCart = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
// Variable products need to go to product page for attribute selection
if (isVariable) {
navigate(`/product/${product.slug}`);
return;
}
onAddToCart?.(product);
};
// Calculate discount if on sale
const discount = product.on_sale && product.regular_price && product.sale_price
? formatDiscount(parseFloat(product.regular_price), parseFloat(product.sale_price))
: null;
// Show skeleton while settings are loading to prevent layout shift
if (isLoading) {
return (
@@ -77,22 +77,22 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</div>
);
}
// Determine button variant and position based on settings
const buttonVariant = addToCart.style === 'outline' ? 'outline' : addToCart.style === 'text' ? 'ghost' : 'default';
const showButtonOnHover = addToCart.position === 'overlay';
const buttonPosition = addToCart.position; // 'below', 'overlay', 'bottom'
const isTextOnly = addToCart.style === 'text';
// Card style variations - adapt to column count
const cardStyle = layout.card_style || 'card';
const gridCols = parseInt(layout.grid_columns) || 3;
// More columns = cleaner styling
const getCardClasses = () => {
if (cardStyle === 'minimal') {
return gridCols >= 4
? 'overflow-hidden hover:opacity-90 transition-opacity'
return gridCols >= 4
? 'overflow-hidden hover:opacity-90 transition-opacity'
: 'overflow-hidden hover:opacity-80 transition-opacity border-b border-gray-100 pb-4';
}
if (cardStyle === 'overlay') {
@@ -105,16 +105,16 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
? 'border border-gray-200 rounded-md overflow-hidden hover:shadow-md transition-shadow bg-white'
: 'border rounded-lg overflow-hidden hover:shadow-lg transition-shadow bg-white';
};
const cardClasses = getCardClasses();
// Text alignment class
const textAlignClass = {
'left': 'text-left',
'center': 'text-center',
'right': 'text-right',
}[layout.card_text_align || 'left'] || 'text-left';
// Classic Layout - Traditional card with border
if (isClassic) {
return (
@@ -133,34 +133,32 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
No Image
</div>
)}
{/* Sale Badge */}
{elements.sale_badges && product.on_sale && discount && (
<div
<div
className="absolute top-2 right-2 text-white text-xs font-bold px-2 py-1 rounded"
style={{ backgroundColor: saleBadge.color }}
>
{discount}
</div>
)}
{/* Wishlist Button */}
{showWishlist && (
<div className="absolute top-2 left-2 z-10">
<button
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
<Heart className={`w-4 h-4 block transition-all ${inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
{/* Hover/Overlay Button */}
{showButtonOnHover && (
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-300 flex items-center justify-center">
@@ -176,13 +174,13 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</div>
)}
</div>
{/* Content */}
<div className={`p-4 flex-1 flex flex-col ${textAlignClass}`}>
<h3 className="text-sm font-medium text-gray-900 mb-2 line-clamp-2 leading-snug group-hover:text-primary transition-colors">
{product.name}
</h3>
{/* Price */}
<div className={`flex items-center gap-2 mb-3 ${(layout.card_text_align || 'left') === 'center' ? 'justify-center' : (layout.card_text_align || 'left') === 'right' ? 'justify-end' : ''}`}>
{product.on_sale && product.regular_price ? (
@@ -200,7 +198,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</span>
)}
</div>
{/* Add to Cart Button - Below Image */}
{!showButtonOnHover && (
<Button
@@ -218,7 +216,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</Link>
);
}
// Modern Layout - Minimalist, clean
if (isModern) {
return (
@@ -237,34 +235,32 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
No Image
</div>
)}
{/* Sale Badge */}
{elements.sale_badges && product.on_sale && discount && (
<div
<div
className="absolute top-4 left-4 text-white text-xs font-medium px-3 py-1"
style={{ backgroundColor: saleBadge.color }}
>
{discount}
</div>
)}
{/* Wishlist Button */}
{showWishlist && (
<div className="absolute top-4 right-4 z-10">
<button
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
<Heart className={`w-4 h-4 block transition-all ${inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
{/* Hover Overlay - Only show if position is hover/overlay */}
{showButtonOnHover && (
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-300 flex items-center justify-center">
@@ -280,13 +276,13 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</div>
)}
</div>
{/* Content */}
<div className="text-center">
<h3 className="text-sm font-medium text-gray-900 mb-2 leading-snug group-hover:text-primary transition-colors">
{product.name}
</h3>
{/* Price */}
<div className="flex items-center justify-center gap-2 mb-3">
{product.on_sale && product.regular_price ? (
@@ -304,7 +300,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</span>
)}
</div>
{/* Add to Cart Button - Below or Bottom */}
{!showButtonOnHover && (
<div className="flex flex-col mt-auto">
@@ -316,10 +312,10 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
disabled={product.stock_status === 'outofstock'}
>
{!isTextOnly && addToCart.show_icon && <ShoppingCart className="w-4 h-4 mr-2" />}
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
{product.stock_status === 'outofstock' ? 'Out of Stock' : isVariable ? 'Select Options' : 'Add to Cart'}
</Button>
)}
{buttonPosition === 'bottom' && (
<Button
onClick={handleAddToCart}
@@ -328,7 +324,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
disabled={product.stock_status === 'outofstock'}
>
{!isTextOnly && addToCart.show_icon && <ShoppingCart className="w-4 h-4 mr-2" />}
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
{product.stock_status === 'outofstock' ? 'Out of Stock' : isVariable ? 'Select Options' : 'Add to Cart'}
</Button>
)}
</div>
@@ -338,7 +334,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</Link>
);
}
// Boutique Layout - Luxury, elegant
if (isBoutique) {
return (
@@ -357,41 +353,39 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
No Image
</div>
)}
{/* Sale Badge */}
{elements.sale_badges && product.on_sale && discount && (
<div
<div
className="absolute top-6 right-6 text-white text-xs font-medium px-4 py-2 tracking-wider"
style={{ backgroundColor: saleBadge.color }}
>
{discount}
</div>
)}
{/* Wishlist Button */}
{showWishlist && (
<div className="absolute top-6 left-6 z-10">
<button
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
<Heart className={`w-4 h-4 block transition-all ${inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
</div>
{/* Content */}
<div className="text-center font-serif">
<h3 className="text-sm font-medium text-gray-900 mb-3 tracking-wide leading-snug group-hover:text-primary transition-colors">
{product.name}
</h3>
{/* Price */}
<div className="flex items-center justify-center gap-3 mb-4">
{product.on_sale && product.regular_price ? (
@@ -409,7 +403,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</span>
)}
</div>
{/* Add to Cart Button */}
<Button
onClick={handleAddToCart}
@@ -424,7 +418,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
</Link>
);
}
// Launch Layout - Funnel optimized (shouldn't show product grid, but just in case)
return (
<Link to={`/product/${product.slug}`} className="group">
@@ -441,25 +435,23 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
No Image
</div>
)}
{/* Wishlist Button */}
{showWishlist && (
<div className="absolute top-3 right-3 z-10">
<button
<button
onClick={handleWishlistClick}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${
inWishlist ? 'bg-red-50' : 'bg-white'
}`}
className={`font-[inherit] p-2 rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center transition-all ${inWishlist ? 'bg-red-50' : 'bg-white'
}`}
title={inWishlist ? 'Remove from wishlist' : 'Add to wishlist'}
>
<Heart className={`w-4 h-4 block transition-all ${
inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
<Heart className={`w-4 h-4 block transition-all ${inWishlist ? 'fill-red-500 text-red-500' : ''
}`} />
</button>
</div>
)}
</div>
<div className="p-4 text-center">
<h3 className="text-sm font-medium text-gray-900 mb-2 leading-snug">{product.name}</h3>
<div className="text-lg font-bold mb-3" style={{ color: 'var(--color-primary)' }}>