Files
WooNooW/admin-spa/src/routes/Appearance/Product.tsx
Dwindi Ramadhana 9ac09582d2 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
2025-12-25 22:20:48 +07:00

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