This commit is contained in:
Your Name committed 2026-06-07 17:17:01 +07:00
1 parent d325cd8763
commit c6bf2f70ac
45 files changed
+5195 -338

No files matched your search

+79 -12
View File
@@ -4,7 +4,8 @@ import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
import EmptyState from '../components/EmptyState'
import { API_BASE, type Category, type Product } from '../types'
import DynamicPageRenderer from '../components/DynamicPageRenderer'
import { API_BASE, type Category, type Product, type PageSection } from '../types'
export default function ShopPage() {
const [categories, setCategories] = useState<Category[]>([])
@@ -22,6 +23,7 @@ export default function ShopPage() {
const [loading, setLoading] = useState(true)
const [productsLoading, setProductsLoading] = useState(false)
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
// Handle coupon from URL
useEffect(() => {
@@ -45,6 +47,23 @@ export default function ShopPage() {
.catch(console.error)
}, [])
// Fetch CMS sections for shop-layout
useEffect(() => {
fetch(`${API_BASE}/pages/shop-layout`)
.then((r) => {
if (!r.ok) throw new Error('Page not found');
return r.json();
})
.then((data) => {
if (data && data.sections) {
setCmsSections(data.sections);
}
})
.catch((err) => {
console.log('Shop dynamic layout sections not loaded:', err.message);
});
}, [])
// Debounce search input
useEffect(() => {
const timer = setTimeout(() => {
@@ -95,22 +114,62 @@ export default function ShopPage() {
return () => observer.disconnect()
}, [products.length, displayCount, productsLoading])
const announcementSections = cmsSections.filter(s => s.isActive && s.type.toUpperCase() === 'ANNOUNCEMENT_BAR');
const heroSections = cmsSections.filter(s => s.isActive && (s.type.toUpperCase() === 'HERO' || s.type.toUpperCase() === 'BANNER'));
const bottomSections = cmsSections.filter(s => s.isActive && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR' && s.type.toUpperCase() !== 'HERO' && s.type.toUpperCase() !== 'BANNER');
return (
<div className="min-h-screen">
{announcementSections.length > 0 && (
<DynamicPageRenderer
sections={announcementSections}
categories={categories}
categoriesLoading={loading}
/>
)}
<Navbar categories={categories} />
{/* Page Header */}
<div className="pt-32 pb-12 px-6 text-center space-y-4 border-b border-spa-stone/10 bg-spa-light">
<p className="text-spa-forest uppercase tracking-[0.4em] text-xs font-bold opacity-80">
All Products
</p>
<h1 className="text-5xl font-bold tracking-tight text-pop-black">The Collection</h1>
<p className="text-spa-stone max-w-md mx-auto">
Browse our full range of premium aroma products — crafted to elevate every space.
</p>
{heroSections.length > 0 ? (
<div className="pt-20">
<DynamicPageRenderer
sections={heroSections}
categories={categories}
categoriesLoading={loading}
/>
</div>
) : (
<div className="pt-32 pb-12 px-6 text-center space-y-4 border-b border-spa-stone/10 bg-spa-light">
<p className="text-spa-forest uppercase tracking-[0.4em] text-xs font-bold opacity-80">
All Products
</p>
<h1 className="text-5xl font-bold tracking-tight text-pop-black">The Collection</h1>
<p className="text-spa-stone max-w-md mx-auto">
Browse our full range of premium aroma products — crafted to elevate every space.
</p>
{/* Search */}
<div className="max-w-md mx-auto mt-6">
{/* Search */}
<div className="max-w-md mx-auto mt-6">
<div className="flex bg-white border border-spa-stone/20 rounded-xl overflow-hidden shadow-sm focus-within:border-spa-forest focus-within:ring-1 focus-within:ring-spa-forest transition-all">
<svg className="w-4 h-4 text-spa-stone m-3.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
type="text"
placeholder="Search by scent or category..."
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
className="bg-transparent border-none outline-none text-sm p-2 w-full text-pop-black placeholder-spa-stone/60"
/>
</div>
</div>
</div>
)}
{/* Render Search bar below Hero block if Hero is configured */}
{heroSections.length > 0 && (
<div className="max-w-md mx-auto mt-8 px-6">
<div className="flex bg-white border border-spa-stone/20 rounded-xl overflow-hidden shadow-sm focus-within:border-spa-forest focus-within:ring-1 focus-within:ring-spa-forest transition-all">
<svg className="w-4 h-4 text-spa-stone m-3.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
@@ -124,7 +183,7 @@ export default function ShopPage() {
/>
</div>
</div>
</div>
)}
<div className="max-w-7xl mx-auto px-6 py-12 space-y-10">
{/* Category Filter Tabs */}
@@ -210,6 +269,14 @@ export default function ShopPage() {
)}
</div>
{bottomSections.length > 0 && (
<DynamicPageRenderer
sections={bottomSections}
categories={categories}
categoriesLoading={loading}
/>
)}
<Footer categories={categories} />
</div>
)