feat: implement header/footer visibility controls for checkout and thankyou pages
- Created LayoutWrapper component to conditionally render header/footer based on route - Created MinimalHeader component (logo only) - Created MinimalFooter component (trust badges + policy links) - Created usePageVisibility hook to get visibility settings per page - Wrapped ClassicLayout with LayoutWrapper for conditional rendering - Header/footer visibility now controlled directly in React SPA - Settings: show/minimal/hide for both header and footer - Background color support for checkout and thankyou pages
This commit is contained in:
@@ -4,6 +4,7 @@ import { ShoppingCart, Heart } from 'lucide-react';
|
||||
import { formatPrice, formatDiscount } from '@/lib/currency';
|
||||
import { Button } from './ui/button';
|
||||
import { useLayout } from '@/contexts/ThemeContext';
|
||||
import { useShopSettings } from '@/hooks/useAppearanceSettings';
|
||||
|
||||
interface ProductCardProps {
|
||||
product: {
|
||||
@@ -22,6 +23,14 @@ interface ProductCardProps {
|
||||
|
||||
export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
const { isClassic, isModern, isBoutique, isLaunch } = useLayout();
|
||||
const { layout, elements, addToCart, saleBadge, isLoading } = useShopSettings();
|
||||
|
||||
// Aspect ratio classes
|
||||
const aspectRatioClass = {
|
||||
'square': 'aspect-square',
|
||||
'portrait': 'aspect-[3/4]',
|
||||
'landscape': 'aspect-[4/3]',
|
||||
}[layout.aspect_ratio] || 'aspect-square';
|
||||
|
||||
const handleAddToCart = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -34,28 +43,79 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
? formatDiscount(parseFloat(product.regular_price), parseFloat(product.sale_price))
|
||||
: null;
|
||||
|
||||
// Show skeleton while settings are loading to prevent layout shift
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="animate-pulse">
|
||||
<div className="bg-gray-200 aspect-square rounded-lg mb-4" />
|
||||
<div className="h-4 bg-gray-200 rounded mb-2" />
|
||||
<div className="h-4 bg-gray-200 rounded w-2/3" />
|
||||
</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'
|
||||
: 'overflow-hidden hover:opacity-80 transition-opacity border-b border-gray-100 pb-4';
|
||||
}
|
||||
if (cardStyle === 'overlay') {
|
||||
return gridCols >= 4
|
||||
? 'relative overflow-hidden group-hover:shadow-lg transition-all rounded-md'
|
||||
: 'relative overflow-hidden group-hover:shadow-xl transition-all rounded-lg bg-white';
|
||||
}
|
||||
// Default 'card' style
|
||||
return gridCols >= 4
|
||||
? '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 (
|
||||
<Link to={`/product/${product.slug}`} className="group">
|
||||
<div className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow bg-white">
|
||||
<Link to={`/product/${product.slug}`} className="group h-full">
|
||||
<div className={`${cardClasses} h-full flex flex-col`}>
|
||||
{/* Image */}
|
||||
<div className="relative w-full h-64 overflow-hidden bg-gray-100" style={{ fontSize: 0 }}>
|
||||
<div className={`relative w-full overflow-hidden bg-gray-100 ${aspectRatioClass}`}>
|
||||
{product.image ? (
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="block w-full !h-full object-cover object-center group-hover:scale-105 transition-transform duration-300"
|
||||
className="absolute inset-0 w-full !h-full object-cover object-center group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full !h-full flex items-center justify-center text-gray-400" style={{ fontSize: '1rem' }}>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-gray-400">
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sale Badge */}
|
||||
{product.on_sale && discount && (
|
||||
<div className="absolute top-2 right-2 bg-red-500 text-white text-xs font-bold px-2 py-1 rounded">
|
||||
{elements.sale_badges && product.on_sale && discount && (
|
||||
<div
|
||||
className="absolute top-2 right-2 text-white text-xs font-bold px-2 py-1 rounded"
|
||||
style={{ backgroundColor: saleBadge.color }}
|
||||
>
|
||||
{discount}
|
||||
</div>
|
||||
)}
|
||||
@@ -66,16 +126,31 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<Heart className="w-4 h-4 block" />
|
||||
</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">
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
variant={buttonVariant}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
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'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4">
|
||||
<div className={`p-4 flex-1 flex flex-col ${textAlignClass}`}>
|
||||
<h3 className="font-semibold text-gray-900 mb-2 line-clamp-2 group-hover:text-primary transition-colors">
|
||||
{product.name}
|
||||
</h3>
|
||||
|
||||
{/* Price */}
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<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 ? (
|
||||
<>
|
||||
<span className="text-lg font-bold" style={{ color: 'var(--color-primary)' }}>
|
||||
@@ -92,15 +167,18 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add to Cart Button */}
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
className="w-full"
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
<ShoppingCart className="w-4 h-4 mr-2" />
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
|
||||
</Button>
|
||||
{/* Add to Cart Button - Below Image */}
|
||||
{!showButtonOnHover && (
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
variant={buttonVariant}
|
||||
className={`w-full mt-auto ${isTextOnly ? 'border-0 shadow-none hover:bg-transparent hover:underline' : ''}`}
|
||||
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'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -113,36 +191,43 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<Link to={`/product/${product.slug}`} className="group">
|
||||
<div className="overflow-hidden">
|
||||
{/* Image */}
|
||||
<div className="relative w-full h-64 mb-4 overflow-hidden bg-gray-50" style={{ fontSize: 0 }}>
|
||||
<div className={`relative w-full mb-4 overflow-hidden bg-gray-50 ${aspectRatioClass}`} style={{ fontSize: 0 }}>
|
||||
{product.image ? (
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="block w-full h-full object-cover object-center group-hover:scale-105 transition-transform duration-500"
|
||||
className="block w-full !h-full object-cover object-center group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-gray-300" style={{ fontSize: '1rem' }}>
|
||||
<div className="w-full !h-full flex items-center justify-center text-gray-300" style={{ fontSize: '1rem' }}>
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sale Badge */}
|
||||
{product.on_sale && discount && (
|
||||
<div className="absolute top-4 left-4 bg-black text-white text-xs font-medium px-3 py-1">
|
||||
{elements.sale_badges && product.on_sale && discount && (
|
||||
<div
|
||||
className="absolute top-4 left-4 text-white text-xs font-medium px-3 py-1"
|
||||
style={{ backgroundColor: saleBadge.color }}
|
||||
>
|
||||
{discount}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hover Overlay */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-300 flex items-center justify-center">
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
|
||||
</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">
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
variant={buttonVariant}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
{addToCart.show_icon && <ShoppingCart className="w-4 h-4 mr-2" />}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
@@ -152,7 +237,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
</h3>
|
||||
|
||||
{/* Price */}
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="flex items-center justify-center gap-2 mb-3">
|
||||
{product.on_sale && product.regular_price ? (
|
||||
<>
|
||||
<span className="font-semibold" style={{ color: 'var(--color-primary)' }}>
|
||||
@@ -168,6 +253,35 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add to Cart Button - Below or Bottom */}
|
||||
{!showButtonOnHover && (
|
||||
<div className="flex flex-col mt-auto">
|
||||
{buttonPosition === 'below' && (
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
variant={buttonVariant}
|
||||
className="w-full"
|
||||
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'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{buttonPosition === 'bottom' && (
|
||||
<Button
|
||||
onClick={handleAddToCart}
|
||||
variant={buttonVariant}
|
||||
className="w-full"
|
||||
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'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -180,22 +294,25 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<Link to={`/product/${product.slug}`} className="group">
|
||||
<div className="overflow-hidden">
|
||||
{/* Image */}
|
||||
<div className="relative w-full h-80 mb-6 overflow-hidden bg-gray-50" style={{ fontSize: 0 }}>
|
||||
<div className={`relative w-full mb-6 overflow-hidden bg-gray-50 ${aspectRatioClass}`} style={{ fontSize: 0 }}>
|
||||
{product.image ? (
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="block w-full h-full object-cover object-center group-hover:scale-110 transition-transform duration-700"
|
||||
className="block w-full !h-full object-cover object-center group-hover:scale-110 transition-transform duration-700"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-gray-300 font-serif" style={{ fontSize: '1rem' }}>
|
||||
<div className="w-full !h-full flex items-center justify-center text-gray-300 font-serif" style={{ fontSize: '1rem' }}>
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sale Badge */}
|
||||
{product.on_sale && discount && (
|
||||
<div className="absolute top-6 right-6 bg-white text-black text-xs font-medium px-4 py-2 tracking-wider">
|
||||
{elements.sale_badges && product.on_sale && discount && (
|
||||
<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>
|
||||
)}
|
||||
@@ -249,10 +366,10 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="block w-full h-full object-cover object-center"
|
||||
className="block w-full !h-full object-cover object-center"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center text-gray-400" style={{ fontSize: '1rem' }}>
|
||||
<div className="w-full !h-full flex items-center justify-center text-gray-400" style={{ fontSize: '1rem' }}>
|
||||
No Image
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user