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:
@@ -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)' }}>
|
||||
|
||||
47
customer-spa/src/lib/sectionStyles.ts
Normal file
47
customer-spa/src/lib/sectionStyles.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Shared utility to compute background styles from section styles.
|
||||
* Used by all customer SPA section components.
|
||||
*/
|
||||
export function getSectionBackground(styles?: Record<string, any>): {
|
||||
style: React.CSSProperties;
|
||||
hasOverlay: boolean;
|
||||
overlayOpacity: number;
|
||||
backgroundImage?: string;
|
||||
} {
|
||||
if (!styles) {
|
||||
return { style: {}, hasOverlay: false, overlayOpacity: 0 };
|
||||
}
|
||||
|
||||
const bgType = styles.backgroundType || 'solid';
|
||||
let style: React.CSSProperties = {};
|
||||
let hasOverlay = false;
|
||||
let overlayOpacity = 0;
|
||||
let backgroundImage: string | undefined;
|
||||
|
||||
switch (bgType) {
|
||||
case 'gradient':
|
||||
style.background = `linear-gradient(${styles.gradientAngle ?? 135}deg, ${styles.gradientFrom || '#9333ea'}, ${styles.gradientTo || '#3b82f6'})`;
|
||||
break;
|
||||
case 'image':
|
||||
if (styles.backgroundImage) {
|
||||
backgroundImage = styles.backgroundImage;
|
||||
overlayOpacity = (styles.backgroundOverlay || 0) / 100;
|
||||
hasOverlay = overlayOpacity > 0;
|
||||
}
|
||||
break;
|
||||
case 'solid':
|
||||
default:
|
||||
if (styles.backgroundColor) {
|
||||
style.backgroundColor = styles.backgroundColor;
|
||||
}
|
||||
// Legacy: if backgroundImage exists without explicit type
|
||||
if (!styles.backgroundType && styles.backgroundImage) {
|
||||
backgroundImage = styles.backgroundImage;
|
||||
overlayOpacity = (styles.backgroundOverlay || 0) / 100;
|
||||
hasOverlay = overlayOpacity > 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return { style, hasOverlay, overlayOpacity, backgroundImage };
|
||||
}
|
||||
@@ -173,55 +173,84 @@ export default function Cart() {
|
||||
{cart.items.map((item: CartItem) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex gap-4 p-4 border rounded-lg bg-white"
|
||||
className="flex flex-col sm:flex-row gap-4 p-4 border rounded-lg bg-white relative"
|
||||
>
|
||||
{/* Product Image */}
|
||||
{elements.product_images && (
|
||||
<div className="relative w-24 h-24 flex-shrink-0 rounded-lg overflow-hidden bg-gray-100">
|
||||
{item.image ? (
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.name}
|
||||
className="block w-full !h-full object-cover object-center"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full !h-full flex items-center justify-center text-gray-400 text-xs">
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Product Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-lg mb-1 truncate">
|
||||
{item.name}
|
||||
</h3>
|
||||
|
||||
{/* Variation Attributes */}
|
||||
{item.attributes && Object.keys(item.attributes).length > 0 && (
|
||||
<div className="text-sm text-gray-500 mb-1">
|
||||
{Object.entries(item.attributes).map(([key, value]) => {
|
||||
// Format attribute name: capitalize first letter
|
||||
const formattedKey = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
return (
|
||||
<span key={key} className="mr-3">
|
||||
{formattedKey}: <span className="font-medium">{value}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<div className="flex gap-4 sm:flex-1">
|
||||
{/* Product Image */}
|
||||
{elements.product_images && (
|
||||
<div className="relative w-20 h-20 sm:w-24 sm:h-24 flex-shrink-0 rounded-lg overflow-hidden bg-gray-100">
|
||||
{item.image ? (
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.name}
|
||||
className="block w-full !h-full object-cover object-center"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full !h-full flex items-center justify-center text-gray-400 text-xs">
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-gray-600 mb-2">
|
||||
{formatPrice(item.price)}
|
||||
</p>
|
||||
{/* Product Info */}
|
||||
<div className="flex-1 min-w-0 pr-8 sm:pr-0">
|
||||
<h3 className="font-semibold text-base sm:text-lg mb-1 truncate">
|
||||
{item.name}
|
||||
</h3>
|
||||
|
||||
{/* Quantity Controls */}
|
||||
{/* Variation Attributes */}
|
||||
{item.attributes && Object.keys(item.attributes).length > 0 && (
|
||||
<div className="text-xs sm:text-sm text-gray-500 mb-1 flex flex-wrap gap-x-3 gap-y-1">
|
||||
{Object.entries(item.attributes).map(([key, value]) => {
|
||||
const formattedKey = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
return (
|
||||
<span key={key}>
|
||||
{formattedKey}: <span className="font-medium">{value}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-gray-600 mb-2 font-medium">
|
||||
{formatPrice(item.price)}
|
||||
</p>
|
||||
|
||||
{/* Quantity Controls - Desktop */}
|
||||
<div className="hidden sm:flex items-center gap-2 mt-2">
|
||||
<button
|
||||
onClick={() => handleUpdateQuantity(item.key, item.quantity - 1)}
|
||||
className="font-[inherit] p-1 hover:bg-gray-100 rounded"
|
||||
>
|
||||
<Minus className="h-4 w-4" />
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
value={item.quantity}
|
||||
onChange={(e) =>
|
||||
handleUpdateQuantity(item.key, parseInt(e.target.value) || 1)
|
||||
}
|
||||
className="w-16 text-center border rounded py-1"
|
||||
min="1"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleUpdateQuantity(item.key, item.quantity + 1)}
|
||||
className="font-[inherit] p-1 hover:bg-gray-100 rounded"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Quantity & Item Total Container */}
|
||||
<div className="flex items-center justify-between sm:hidden pt-3 border-t border-gray-100">
|
||||
{/* Quantity Controls - Mobile */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleUpdateQuantity(item.key, item.quantity - 1)}
|
||||
className="font-[inherit] p-1 hover:bg-gray-100 rounded"
|
||||
className="font-[inherit] p-1 hover:bg-gray-100 rounded border"
|
||||
>
|
||||
<Minus className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -231,27 +260,30 @@ export default function Cart() {
|
||||
onChange={(e) =>
|
||||
handleUpdateQuantity(item.key, parseInt(e.target.value) || 1)
|
||||
}
|
||||
className="w-16 text-center border rounded py-1"
|
||||
className="w-12 text-center border rounded py-1"
|
||||
min="1"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleUpdateQuantity(item.key, item.quantity + 1)}
|
||||
className="font-[inherit] p-1 hover:bg-gray-100 rounded"
|
||||
className="font-[inherit] p-1 hover:bg-gray-100 rounded border"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="font-bold text-base">
|
||||
{formatPrice(item.price * item.quantity)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Item Total & Remove */}
|
||||
<div className="flex flex-col items-end justify-between">
|
||||
{/* Desktop Item Total & Remove + Mobile Absolute Remove */}
|
||||
<div className="absolute top-2 right-2 sm:static sm:flex sm:flex-col sm:items-end sm:justify-between">
|
||||
<button
|
||||
onClick={() => handleRemoveItem(item.key)}
|
||||
className="font-[inherit] text-red-600 hover:text-red-700 p-2"
|
||||
className="font-[inherit] text-gray-400 hover:text-red-600 p-2 sm:p-1"
|
||||
>
|
||||
<Trash2 className="h-5 w-5" />
|
||||
</button>
|
||||
<p className="font-bold text-lg">
|
||||
<p className="hidden sm:block font-bold text-lg">
|
||||
{formatPrice(item.price * item.quantity)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getSectionBackground } from '@/lib/sectionStyles';
|
||||
|
||||
interface HeroSectionProps {
|
||||
id: string;
|
||||
@@ -28,7 +29,8 @@ export function HeroSection({
|
||||
const isImageRight = layout === 'hero-right-image' || layout === 'image-right';
|
||||
const isCentered = layout === 'centered' || layout === 'default';
|
||||
|
||||
const hasCustomBackground = !!styles?.backgroundColor || !!styles?.backgroundImage;
|
||||
const hasCustomBackground = !!styles?.backgroundColor || !!styles?.backgroundImage || styles?.backgroundType === 'gradient';
|
||||
const sectionBg = getSectionBackground(styles);
|
||||
|
||||
// Helper to get text styles (including font family)
|
||||
const getTextStyles = (elementName: string) => {
|
||||
@@ -71,8 +73,9 @@ export function HeroSection({
|
||||
const heightClasses = heightMap[heightPreset] || 'py-12 md:py-24';
|
||||
|
||||
// Helper to get background style for dynamic schemes
|
||||
const getBackgroundStyle = () => {
|
||||
if (hasCustomBackground) return undefined;
|
||||
const getBackgroundStyle = (): React.CSSProperties | undefined => {
|
||||
// If user set custom bg via Design tab, use that
|
||||
if (hasCustomBackground) return sectionBg.style;
|
||||
if (colorScheme === 'gradient') {
|
||||
return { backgroundImage: 'linear-gradient(135deg, var(--wn-gradient-start, #9333ea), var(--wn-gradient-end, #3b82f6))' };
|
||||
}
|
||||
|
||||
@@ -909,8 +909,8 @@ export default function Product() {
|
||||
{/* Sticky CTA Bar */}
|
||||
{layout.sticky_add_to_cart && stockStatus === 'instock' && (
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-white border-t-2 border-gray-200 p-3 shadow-2xl z-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="max-w-6xl mx-auto flex items-center justify-between gap-3 px-2">
|
||||
<div className="flex-1 flex flex-col justify-center min-w-0">
|
||||
{/* Show selected variation for variable products */}
|
||||
{product.type === 'variable' && Object.keys(selectedAttributes).length > 0 && (
|
||||
<div className="text-xs text-gray-600 mb-1 flex items-center gap-1 flex-wrap">
|
||||
|
||||
Reference in New Issue
Block a user