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:
302
admin-spa/src/routes/Appearance/Shop.tsx
Normal file
302
admin-spa/src/routes/Appearance/Shop.tsx
Normal file
@@ -0,0 +1,302 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { SettingsLayout } from '@/routes/Settings/components/SettingsLayout';
|
||||
import { SettingsCard } from '@/routes/Settings/components/SettingsCard';
|
||||
import { SettingsSection } from '@/routes/Settings/components/SettingsSection';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
export default function AppearanceShop() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [gridColumns, setGridColumns] = useState('3');
|
||||
const [gridStyle, setGridStyle] = useState('standard');
|
||||
const [cardStyle, setCardStyle] = useState('card');
|
||||
const [aspectRatio, setAspectRatio] = useState('square');
|
||||
|
||||
const [elements, setElements] = useState({
|
||||
category_filter: true,
|
||||
search_bar: true,
|
||||
sort_dropdown: true,
|
||||
sale_badges: true,
|
||||
});
|
||||
|
||||
const [saleBadgeColor, setSaleBadgeColor] = useState('#ef4444');
|
||||
const [cardTextAlign, setCardTextAlign] = useState('left');
|
||||
|
||||
const [addToCartPosition, setAddToCartPosition] = useState('below');
|
||||
const [addToCartStyle, setAddToCartStyle] = useState('solid');
|
||||
const [showCartIcon, setShowCartIcon] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const response = await api.get('/appearance/settings');
|
||||
const shop = response.data?.pages?.shop;
|
||||
|
||||
if (shop) {
|
||||
setGridColumns(shop.layout?.grid_columns || '3');
|
||||
setGridStyle(shop.layout?.grid_style || 'standard');
|
||||
setCardStyle(shop.layout?.card_style || 'card');
|
||||
setAspectRatio(shop.layout?.aspect_ratio || 'square');
|
||||
setCardTextAlign(shop.layout?.card_text_align || 'left');
|
||||
|
||||
if (shop.elements) {
|
||||
setElements(shop.elements);
|
||||
}
|
||||
|
||||
setSaleBadgeColor(shop.sale_badge?.color || '#ef4444');
|
||||
|
||||
setAddToCartPosition(shop.add_to_cart?.position || 'below');
|
||||
setAddToCartStyle(shop.add_to_cart?.style || 'solid');
|
||||
setShowCartIcon(shop.add_to_cart?.show_icon ?? true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
const toggleElement = (key: keyof typeof elements) => {
|
||||
setElements({ ...elements, [key]: !elements[key] });
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await api.post('/appearance/pages/shop', {
|
||||
layout: {
|
||||
grid_columns: gridColumns,
|
||||
grid_style: gridStyle,
|
||||
card_style: cardStyle,
|
||||
aspect_ratio: aspectRatio,
|
||||
card_text_align: cardTextAlign
|
||||
},
|
||||
elements: {
|
||||
category_filter: elements.category_filter,
|
||||
search_bar: elements.search_bar,
|
||||
sort_dropdown: elements.sort_dropdown,
|
||||
sale_badges: elements.sale_badges,
|
||||
},
|
||||
sale_badge: {
|
||||
color: saleBadgeColor
|
||||
},
|
||||
add_to_cart: {
|
||||
position: addToCartPosition,
|
||||
style: addToCartStyle,
|
||||
show_icon: showCartIcon
|
||||
},
|
||||
});
|
||||
toast.success('Shop page settings saved successfully');
|
||||
} catch (error) {
|
||||
console.error('Save error:', error);
|
||||
toast.error('Failed to save settings');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsLayout
|
||||
title="Shop Page Settings"
|
||||
onSave={handleSave}
|
||||
isLoading={loading}
|
||||
>
|
||||
{/* Layout */}
|
||||
<SettingsCard
|
||||
title="Layout"
|
||||
description="Configure shop page layout and product display"
|
||||
>
|
||||
<SettingsSection label="Grid Columns" htmlFor="grid-columns">
|
||||
<Select value={gridColumns} onValueChange={setGridColumns}>
|
||||
<SelectTrigger id="grid-columns">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="2">2 Columns</SelectItem>
|
||||
<SelectItem value="3">3 Columns</SelectItem>
|
||||
<SelectItem value="4">4 Columns</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Grid Style" htmlFor="grid-style" description="Masonry creates a Pinterest-like layout with varying heights">
|
||||
<Select value={gridStyle} onValueChange={setGridStyle}>
|
||||
<SelectTrigger id="grid-style">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="standard">Standard - Equal heights</SelectItem>
|
||||
<SelectItem value="masonry">Masonry - Dynamic heights</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Product Card Style" htmlFor="card-style" description="Visual style adapts to column count - more columns = cleaner style">
|
||||
<Select value={cardStyle} onValueChange={setCardStyle}>
|
||||
<SelectTrigger id="card-style">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="card">Card - Bordered with shadow</SelectItem>
|
||||
<SelectItem value="minimal">Minimal - Clean, no border</SelectItem>
|
||||
<SelectItem value="overlay">Overlay - Shadow on hover</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Image Aspect Ratio" htmlFor="aspect-ratio">
|
||||
<Select value={aspectRatio} onValueChange={setAspectRatio}>
|
||||
<SelectTrigger id="aspect-ratio">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="square">Square (1:1)</SelectItem>
|
||||
<SelectItem value="portrait">Portrait (3:4)</SelectItem>
|
||||
<SelectItem value="landscape">Landscape (4:3)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Card Text Alignment" htmlFor="card-text-align" description="Align product title and price">
|
||||
<Select value={cardTextAlign} onValueChange={setCardTextAlign}>
|
||||
<SelectTrigger id="card-text-align">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="left">Left</SelectItem>
|
||||
<SelectItem value="center">Center</SelectItem>
|
||||
<SelectItem value="right">Right</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Sale Badge Color" htmlFor="sale-badge-color">
|
||||
<input
|
||||
type="color"
|
||||
id="sale-badge-color"
|
||||
value={saleBadgeColor}
|
||||
onChange={(e) => setSaleBadgeColor(e.target.value)}
|
||||
className="h-10 w-full rounded-md border border-input cursor-pointer"
|
||||
/>
|
||||
</SettingsSection>
|
||||
</SettingsCard>
|
||||
|
||||
{/* Elements */}
|
||||
<SettingsCard
|
||||
title="Elements"
|
||||
description="Choose which elements to display on the shop page"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="element-category_filter" className="cursor-pointer">
|
||||
Show category filter
|
||||
</Label>
|
||||
<Switch
|
||||
id="element-category_filter"
|
||||
checked={elements.category_filter}
|
||||
onCheckedChange={() => toggleElement('category_filter')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="element-search_bar" className="cursor-pointer">
|
||||
Show search bar
|
||||
</Label>
|
||||
<Switch
|
||||
id="element-search_bar"
|
||||
checked={elements.search_bar}
|
||||
onCheckedChange={() => toggleElement('search_bar')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="element-sort_dropdown" className="cursor-pointer">
|
||||
Show sort dropdown
|
||||
</Label>
|
||||
<Switch
|
||||
id="element-sort_dropdown"
|
||||
checked={elements.sort_dropdown}
|
||||
onCheckedChange={() => toggleElement('sort_dropdown')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="element-sale_badges" className="cursor-pointer">
|
||||
Show sale badges
|
||||
</Label>
|
||||
<Switch
|
||||
id="element-sale_badges"
|
||||
checked={elements.sale_badges}
|
||||
onCheckedChange={() => toggleElement('sale_badges')}
|
||||
/>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
|
||||
{/* Add to Cart Button */}
|
||||
<SettingsCard
|
||||
title="Add to Cart Button"
|
||||
description="Configure add to cart button appearance and behavior"
|
||||
>
|
||||
<SettingsSection label="Position">
|
||||
<RadioGroup value={addToCartPosition} onValueChange={setAddToCartPosition}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="below" id="position-below" />
|
||||
<Label htmlFor="position-below" className="cursor-pointer">
|
||||
Below image
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="overlay" id="position-overlay" />
|
||||
<Label htmlFor="position-overlay" className="cursor-pointer">
|
||||
On hover overlay
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="bottom" id="position-bottom" />
|
||||
<Label htmlFor="position-bottom" className="cursor-pointer">
|
||||
Bottom of card
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection label="Style">
|
||||
<RadioGroup value={addToCartStyle} onValueChange={setAddToCartStyle}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="solid" id="style-solid" />
|
||||
<Label htmlFor="style-solid" className="cursor-pointer">
|
||||
Solid
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="outline" id="style-outline" />
|
||||
<Label htmlFor="style-outline" className="cursor-pointer">
|
||||
Outline
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="text" id="style-text" />
|
||||
<Label htmlFor="style-text" className="cursor-pointer">
|
||||
Text only
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</SettingsSection>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="show-cart-icon" className="cursor-pointer">
|
||||
Show cart icon
|
||||
</Label>
|
||||
<Switch
|
||||
id="show-cart-icon"
|
||||
checked={showCartIcon}
|
||||
onCheckedChange={setShowCartIcon}
|
||||
/>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user