- 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
279 lines
10 KiB
TypeScript
279 lines
10 KiB
TypeScript
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 { toast } from 'sonner';
|
|
import { api } from '@/lib/api';
|
|
|
|
export default function AppearanceProduct() {
|
|
const [loading, setLoading] = useState(true);
|
|
const [imagePosition, setImagePosition] = useState('left');
|
|
const [galleryStyle, setGalleryStyle] = useState('thumbnails');
|
|
const [stickyAddToCart, setStickyAddToCart] = useState(false);
|
|
|
|
const [elements, setElements] = useState({
|
|
breadcrumbs: true,
|
|
related_products: true,
|
|
reviews: true,
|
|
share_buttons: false,
|
|
product_meta: true,
|
|
});
|
|
|
|
const [reviewSettings, setReviewSettings] = useState({
|
|
placement: 'product_page',
|
|
hide_if_empty: true,
|
|
});
|
|
|
|
const [relatedProductsTitle, setRelatedProductsTitle] = useState('You May Also Like');
|
|
|
|
useEffect(() => {
|
|
const loadSettings = async () => {
|
|
try {
|
|
const response = await api.get('/appearance/settings');
|
|
const product = response.data?.pages?.product;
|
|
|
|
if (product) {
|
|
if (product.layout) {
|
|
if (product.layout.image_position) setImagePosition(product.layout.image_position);
|
|
if (product.layout.gallery_style) setGalleryStyle(product.layout.gallery_style);
|
|
if (product.layout.sticky_add_to_cart !== undefined) setStickyAddToCart(product.layout.sticky_add_to_cart);
|
|
}
|
|
if (product.elements) {
|
|
setElements({
|
|
breadcrumbs: product.elements.breadcrumbs ?? true,
|
|
related_products: product.elements.related_products ?? true,
|
|
reviews: product.elements.reviews ?? true,
|
|
share_buttons: product.elements.share_buttons ?? false,
|
|
product_meta: product.elements.product_meta ?? true,
|
|
});
|
|
}
|
|
if (product.related_products) {
|
|
setRelatedProductsTitle(product.related_products.title ?? 'You May Also Like');
|
|
}
|
|
if (product.reviews) {
|
|
setReviewSettings({
|
|
placement: product.reviews.placement ?? 'product_page',
|
|
hide_if_empty: product.reviews.hide_if_empty ?? 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/product', {
|
|
layout: {
|
|
image_position: imagePosition,
|
|
gallery_style: galleryStyle,
|
|
sticky_add_to_cart: stickyAddToCart
|
|
},
|
|
elements,
|
|
related_products: {
|
|
title: relatedProductsTitle,
|
|
},
|
|
reviews: reviewSettings,
|
|
});
|
|
toast.success('Product page settings saved successfully');
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
toast.error('Failed to save settings');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<SettingsLayout
|
|
title="Product Page Settings"
|
|
onSave={handleSave}
|
|
isLoading={loading}
|
|
>
|
|
{/* Layout */}
|
|
<SettingsCard
|
|
title="Layout"
|
|
description="Configure product page layout and gallery"
|
|
>
|
|
<SettingsSection label="Image Position" htmlFor="image-position">
|
|
<Select value={imagePosition} onValueChange={setImagePosition}>
|
|
<SelectTrigger id="image-position">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="left">Left</SelectItem>
|
|
<SelectItem value="right">Right</SelectItem>
|
|
<SelectItem value="top">Top</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection label="Gallery Style" htmlFor="gallery-style">
|
|
<Select value={galleryStyle} onValueChange={setGalleryStyle}>
|
|
<SelectTrigger id="gallery-style">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="thumbnails">Thumbnails</SelectItem>
|
|
<SelectItem value="dots">Dots</SelectItem>
|
|
<SelectItem value="slider">Slider</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</SettingsSection>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="sticky-cart">Sticky Add to Cart</Label>
|
|
<p className="text-sm text-muted-foreground">
|
|
Keep add to cart button visible when scrolling
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
id="sticky-cart"
|
|
checked={stickyAddToCart}
|
|
onCheckedChange={setStickyAddToCart}
|
|
/>
|
|
</div>
|
|
</SettingsCard>
|
|
|
|
{/* Elements */}
|
|
<SettingsCard
|
|
title="Elements"
|
|
description="Choose which elements to display on the product page"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="element-breadcrumbs" className="cursor-pointer">
|
|
Show breadcrumbs
|
|
</Label>
|
|
<Switch
|
|
id="element-breadcrumbs"
|
|
checked={elements.breadcrumbs}
|
|
onCheckedChange={() => toggleElement('breadcrumbs')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="element-related-products" className="cursor-pointer">
|
|
Show related products
|
|
</Label>
|
|
<Switch
|
|
id="element-related-products"
|
|
checked={elements.related_products}
|
|
onCheckedChange={() => toggleElement('related_products')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="element-reviews" className="cursor-pointer">
|
|
Show reviews
|
|
</Label>
|
|
<Switch
|
|
id="element-reviews"
|
|
checked={elements.reviews}
|
|
onCheckedChange={() => toggleElement('reviews')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="element-share-buttons" className="cursor-pointer">
|
|
Show share buttons
|
|
</Label>
|
|
<Switch
|
|
id="element-share-buttons"
|
|
checked={elements.share_buttons}
|
|
onCheckedChange={() => toggleElement('share_buttons')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="element-product-meta" className="cursor-pointer">
|
|
Show product meta
|
|
</Label>
|
|
<p className="text-sm text-muted-foreground">
|
|
SKU, categories, tags
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
id="element-product-meta"
|
|
checked={elements.product_meta}
|
|
onCheckedChange={() => toggleElement('product_meta')}
|
|
/>
|
|
</div>
|
|
</SettingsCard>
|
|
|
|
{/* Related Products Settings */}
|
|
<SettingsCard
|
|
title="Related Products"
|
|
description="Configure related products section"
|
|
>
|
|
<SettingsSection label="Section Title" htmlFor="related-products-title">
|
|
<input
|
|
id="related-products-title"
|
|
type="text"
|
|
value={relatedProductsTitle}
|
|
onChange={(e) => setRelatedProductsTitle(e.target.value)}
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
placeholder="You May Also Like"
|
|
/>
|
|
<p className="text-sm text-muted-foreground mt-2">
|
|
This heading appears above the related products grid
|
|
</p>
|
|
</SettingsSection>
|
|
</SettingsCard>
|
|
|
|
{/* Review Settings */}
|
|
<SettingsCard
|
|
title="Review Settings"
|
|
description="Configure how and where reviews are displayed"
|
|
>
|
|
<SettingsSection label="Review Placement" htmlFor="review-placement">
|
|
<Select value={reviewSettings.placement} onValueChange={(value) => setReviewSettings({ ...reviewSettings, placement: value })}>
|
|
<SelectTrigger id="review-placement">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="product_page">Product Page (Traditional)</SelectItem>
|
|
<SelectItem value="order_details">Order Details Only (Marketplace Style)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-sm text-muted-foreground mt-2">
|
|
{reviewSettings.placement === 'product_page'
|
|
? 'Reviews appear on product page. Users can submit reviews directly on the product.'
|
|
: 'Reviews only appear in order details after purchase. Ensures verified purchases only.'}
|
|
</p>
|
|
</SettingsSection>
|
|
|
|
{reviewSettings.placement === 'product_page' && (
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="hide-if-empty" className="cursor-pointer">
|
|
Hide reviews section if empty
|
|
</Label>
|
|
<p className="text-sm text-muted-foreground">
|
|
Only show reviews section when product has at least one review
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
id="hide-if-empty"
|
|
checked={reviewSettings.hide_if_empty}
|
|
onCheckedChange={(checked) => setReviewSettings({ ...reviewSettings, hide_if_empty: checked })}
|
|
/>
|
|
</div>
|
|
)}
|
|
</SettingsCard>
|
|
</SettingsLayout>
|
|
);
|
|
}
|