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:
Dwindi Ramadhana
2025-12-25 22:20:48 +07:00
parent c37ecb8e96
commit 9ac09582d2
104 changed files with 14801 additions and 1213 deletions

View 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>
);
}