This commit is contained in:
Your Name committed 2026-06-13 23:10:33 -04:00
1 parent 684fd6907b
commit 310e48fe6a
17 files changed
+424 -139

No files matched your search

+9 -7
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import { useParams, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
@@ -13,6 +14,7 @@ export default function CategoryPage() {
const [category, setCategory] = useState<Category | null>(null)
const [products, setProducts] = useState<Product[]>([])
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
const { i18n } = useTranslation()
// Local progressive rendering
const [displayCount, setDisplayCount] = useState(24)
@@ -22,14 +24,14 @@ export default function CategoryPage() {
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch(`${API_BASE}/categories`)
fetch(`${API_BASE}/categories?lang=${i18n.language}`)
.then((r) => r.json())
.then(setCategories)
.catch(console.error)
}, [])
}, [i18n.language])
useEffect(() => {
fetch(`${API_BASE}/pages/category-layout`)
fetch(`${API_BASE}/pages/category-layout?lang=${i18n.language}`)
.then((r) => {
if (!r.ok) throw new Error('Page not found');
return r.json();
@@ -42,7 +44,7 @@ export default function CategoryPage() {
.catch((err) => {
console.log('Category dynamic layout sections not loaded:', err.message);
});
}, [])
}, [i18n.language])
// Reset state when category changes
useEffect(() => {
@@ -57,8 +59,8 @@ export default function CategoryPage() {
Promise.all([
// Fetch up to 1000 items (essentially all) in one go
fetch(`${API_BASE}/categories/${id}/products?limit=1000`),
fetch(`${API_BASE}/categories`),
fetch(`${API_BASE}/categories/${id}/products?limit=1000&lang=${i18n.language}`),
fetch(`${API_BASE}/categories?lang=${i18n.language}`),
])
.then(async ([catRes, allCatsRes]) => {
if (!catRes.ok) throw new Error('Category not found')
@@ -71,7 +73,7 @@ export default function CategoryPage() {
})
.catch((err) => setError(err.message))
.finally(() => setLoading(false))
}, [id])
}, [id, i18n.language])
// Intersection Observer for infinite scroll
useEffect(() => {
+5 -3
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
import DynamicPageRenderer from '../components/DynamicPageRenderer';
@@ -13,6 +14,7 @@ export default function CustomPage() {
const [loading, setLoading] = useState(true);
const [categoriesLoading, setCategoriesLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { i18n } = useTranslation();
const announcementSections = sections.filter(
(section) => section.isActive && section.type.toUpperCase() === 'ANNOUNCEMENT_BAR'
);
@@ -24,7 +26,7 @@ export default function CustomPage() {
useEffect(() => {
const fetchCategories = async () => {
try {
const res = await fetch(`${API_BASE}/categories`);
const res = await fetch(`${API_BASE}/categories?lang=${i18n.language}`);
const cats: Category[] = await res.json();
setCategories(cats);
} catch (err) {
@@ -39,7 +41,7 @@ export default function CustomPage() {
setLoading(true);
setError(null);
try {
const res = await fetch(`${API_BASE}/pages/${slug}`);
const res = await fetch(`${API_BASE}/pages/${slug}?lang=${i18n.language}`);
if (!res.ok) {
setError('Page not found');
return;
@@ -60,7 +62,7 @@ export default function CustomPage() {
fetchCategories();
fetchPageLayout();
}, [slug]);
}, [slug, i18n.language]);
return (
<div className="min-h-screen bg-[#FDFDF9] text-[#1F3D2D]">
+5 -3
View File
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import DynamicPageRenderer from '../components/DynamicPageRenderer'
@@ -9,11 +10,12 @@ export default function HomePage() {
const [sections, setSections] = useState<PageSection[]>([])
const [loading, setLoading] = useState(true)
const [categoriesLoading, setCategoriesLoading] = useState(true)
const { i18n } = useTranslation()
useEffect(() => {
const fetchCategories = async () => {
try {
const res = await fetch(`${API_BASE}/categories`)
const res = await fetch(`${API_BASE}/categories?lang=${i18n.language}`)
const cats: Category[] = await res.json()
setCategories(cats)
} catch (err) {
@@ -25,7 +27,7 @@ export default function HomePage() {
const fetchPageLayout = async () => {
try {
const res = await fetch(`${API_BASE}/pages/home`)
const res = await fetch(`${API_BASE}/pages/home?lang=${i18n.language}`)
const pageData = await res.json()
if (pageData && pageData.sections) {
setSections(pageData.sections)
@@ -39,7 +41,7 @@ export default function HomePage() {
fetchCategories()
fetchPageLayout()
}, [])
}, [i18n.language])
return (
<div className="min-h-screen bg-pop-bg">
+9 -7
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import { useParams, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
@@ -21,16 +22,17 @@ export default function ProductDetailPage() {
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
const { addToCart } = useCartStore()
const { i18n } = useTranslation()
useEffect(() => {
fetch(`${API_BASE}/categories`)
fetch(`${API_BASE}/categories?lang=${i18n.language}`)
.then((r) => r.json())
.then(setCategories)
.catch(console.error)
}, [])
}, [i18n.language])
useEffect(() => {
fetch(`${API_BASE}/pages/product-detail-layout`)
fetch(`${API_BASE}/pages/product-detail-layout?lang=${i18n.language}`)
.then((r) => {
if (!r.ok) throw new Error('Page not found');
return r.json();
@@ -43,7 +45,7 @@ export default function ProductDetailPage() {
.catch((err) => {
console.log('Product detail dynamic layout sections not loaded:', err.message);
});
}, [])
}, [i18n.language])
useEffect(() => {
if (!id) return
@@ -51,7 +53,7 @@ export default function ProductDetailPage() {
setError(null)
setSelectedVariant(0)
fetch(`${API_BASE}/products/${id}`)
fetch(`${API_BASE}/products/${id}?lang=${i18n.language}`)
.then(async (r) => {
if (!r.ok) throw new Error('Product not found')
return r.json()
@@ -59,14 +61,14 @@ export default function ProductDetailPage() {
.then(async (data: Product) => {
setProduct(data)
// Fetch related (same category, different product)
const relRes = await fetch(`${API_BASE}/products?categoryId=${data.category.id}`)
const relRes = await fetch(`${API_BASE}/products?categoryId=${data.category.id}&lang=${i18n.language}`)
const relJson = await relRes.json()
const relData: Product[] = relJson.data || []
setRelated(relData.filter((p) => p.id !== data.id).slice(0, 6))
})
.catch((err) => setError(err.message))
.finally(() => setLoading(false))
}, [id])
}, [id, i18n.language])
if (loading) {
return (
+6 -4
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import DynamicPageRenderer from '../components/DynamicPageRenderer'
@@ -9,14 +10,15 @@ export default function ScentGuidePage() {
const [categories, setCategories] = useState<Category[]>([])
const [articles, setArticles] = useState<Article[]>([])
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
const { i18n } = useTranslation()
useEffect(() => {
fetch(`${API_BASE}/categories`).then(r => r.json()).then(setCategories).catch(console.error)
fetch(`${API_BASE}/categories?lang=${i18n.language}`).then(r => r.json()).then(setCategories).catch(console.error)
fetch(`${API_BASE}/articles?publishedOnly=true`).then(r => r.json()).then(setArticles).catch(console.error)
}, [])
}, [i18n.language])
useEffect(() => {
fetch(`${API_BASE}/pages/scent-guide-layout`)
fetch(`${API_BASE}/pages/scent-guide-layout?lang=${i18n.language}`)
.then((r) => {
if (!r.ok) throw new Error('Page not found');
return r.json();
@@ -29,7 +31,7 @@ export default function ScentGuidePage() {
.catch((err) => {
console.log('Scent guide dynamic layout sections not loaded:', err.message);
});
}, [])
}, [i18n.language])
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'));
+8 -6
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { useSearchParams, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
@@ -17,13 +18,14 @@ export default function SearchPage() {
const [loading, setLoading] = useState(false)
const [searched, setSearched] = useState(false)
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
const { i18n } = useTranslation()
useEffect(() => {
fetch(`${API_BASE}/categories`).then(r => r.json()).then(setCategories).catch(console.error)
}, [])
fetch(`${API_BASE}/categories?lang=${i18n.language}`).then(r => r.json()).then(setCategories).catch(console.error)
}, [i18n.language])
useEffect(() => {
fetch(`${API_BASE}/pages/search-layout`)
fetch(`${API_BASE}/pages/search-layout?lang=${i18n.language}`)
.then((r) => {
if (!r.ok) throw new Error('Page not found');
return r.json();
@@ -36,14 +38,14 @@ export default function SearchPage() {
.catch((err) => {
console.log('Search dynamic layout sections not loaded:', err.message);
});
}, [])
}, [i18n.language])
const doSearch = useCallback(async (q: string) => {
if (!q.trim()) { setProducts([]); setSearched(false); return }
setLoading(true)
setSearched(true)
try {
const res = await fetch(`${API_BASE}/products?search=${encodeURIComponent(q)}`)
const res = await fetch(`${API_BASE}/products?search=${encodeURIComponent(q)}&lang=${i18n.language}`)
const paginated = await res.json()
setProducts(paginated.data || [])
} catch (err) {
@@ -51,7 +53,7 @@ export default function SearchPage() {
} finally {
setLoading(false)
}
}, [])
}, [i18n.language])
// Search when URL query changes
useEffect(() => {
+8 -5
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
@@ -24,6 +25,7 @@ export default function ShopPage() {
const [loading, setLoading] = useState(true)
const [productsLoading, setProductsLoading] = useState(false)
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
const { i18n } = useTranslation()
// Handle coupon from URL
useEffect(() => {
@@ -38,18 +40,18 @@ export default function ShopPage() {
// Fetch categories
useEffect(() => {
fetch(`${API_BASE}/categories`)
fetch(`${API_BASE}/categories?lang=${i18n.language}`)
.then((r) => r.json())
.then((data: Category[]) => {
setCategories(data)
setLoading(false)
})
.catch(console.error)
}, [])
}, [i18n.language])
// Fetch CMS sections for shop-layout
useEffect(() => {
fetch(`${API_BASE}/pages/shop-layout`)
fetch(`${API_BASE}/pages/shop-layout?lang=${i18n.language}`)
.then((r) => {
if (!r.ok) throw new Error('Page not found');
return r.json();
@@ -62,7 +64,7 @@ export default function ShopPage() {
.catch((err) => {
console.log('Shop dynamic layout sections not loaded:', err.message);
});
}, [])
}, [i18n.language])
// Debounce search input
useEffect(() => {
@@ -86,6 +88,7 @@ export default function ShopPage() {
params.append('limit', '1000') // fetch all matching items locally
if (activeCategory) params.append('categoryId', activeCategory.toString())
if (searchQuery.trim()) params.append('search', searchQuery.trim())
params.append('lang', i18n.language)
fetch(`${API_BASE}/products?${params.toString()}`)
.then((r) => r.json())
@@ -97,7 +100,7 @@ export default function ShopPage() {
})
.catch(console.error)
.finally(() => setProductsLoading(false))
}, [activeCategory, searchQuery])
}, [activeCategory, searchQuery, i18n.language])
// Intersection Observer for infinite scroll
useEffect(() => {