From 0c92591d3244385db9e48c4412d350f2de161cec Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Sun, 16 Nov 2025 07:34:39 +0100 Subject: [PATCH] feat(navigation): enhance article category handling and product navigation - Introduce state management for article categories in App component to track active categories. - Implement logic to clear article category state when navigating away from article pages. - Update Product component to navigate to article pages with associated category information in the state. - Modify Header and CategoryList components to accommodate new category handling logic. - Ensure ProductCarousel and ProductDetailPage components receive and utilize category IDs for improved product organization. --- src/App.js | 43 +++++++++- src/components/Header.js | 7 +- src/components/Product.js | 100 +++++++++++++++++++--- src/components/ProductCarousel.js | 1 + src/components/ProductDetailPage.js | 14 ++-- src/components/ProductList.js | 1 + src/components/header/CategoryList.js | 115 ++++++++++++++------------ 7 files changed, 206 insertions(+), 75 deletions(-) diff --git a/src/App.js b/src/App.js index 5b6db1a..3bc21c6 100644 --- a/src/App.js +++ b/src/App.js @@ -5,7 +5,7 @@ import { Route, Navigate, useLocation, - useNavigate, + useNavigate } from "react-router-dom"; import { ThemeProvider } from "@mui/material/styles"; import CssBaseline from "@mui/material/CssBaseline"; @@ -82,6 +82,9 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => { const [authVersion, setAuthVersion] = useState(0); // @note Theme customizer state for development mode const [isThemeCustomizerOpen, setThemeCustomizerOpen] = useState(false); + // State to track active category for article pages + const [articleCategoryId, setArticleCategoryId] = useState(null); + // Remove duplicate theme state since it's passed as prop // const [dynamicTheme, setDynamicTheme] = useState(createTheme(defaultTheme)); @@ -112,10 +115,44 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => { }; }, []); - // Extract categoryId from pathname if on category route + // Clear article category when navigating away from article pages + useEffect(() => { + const isArticlePage = location.pathname.startsWith('/Artikel/'); + const isCategoryPage = location.pathname.startsWith('/Kategorie/'); + const isHomePage = location.pathname === '/'; + + // Only clear article category when navigating to non-article pages + // (but keep it when going from category to article) + if (!isArticlePage && !isCategoryPage && !isHomePage) { + setArticleCategoryId(null); + } + }, [location.pathname]); + + // Read article category from navigation state (when coming from product click) + useEffect(() => { + if (location.state && location.state.articleCategoryId !== undefined) { + if (location.state.articleCategoryId !== null) { + setArticleCategoryId(location.state.articleCategoryId); + } + // Clear the state so it doesn't persist on page refresh + navigate(location.pathname, { replace: true, state: {} }); + } + }, [location.state, navigate, location.pathname]); + + // Extract categoryId from pathname if on category route, or use article category const getCategoryId = () => { const match = location.pathname.match(/^\/Kategorie\/(.+)$/); - return match ? match[1] : null; + if (match) { + return match[1]; + } + + // For article pages, use the article category if available + const isArticlePage = location.pathname.startsWith('/Artikel/'); + if (isArticlePage && articleCategoryId) { + return articleCategoryId; + } + + return null; }; const categoryId = getCategoryId(); diff --git a/src/components/Header.js b/src/components/Header.js index 6661c78..84b4231 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -91,7 +91,7 @@ class Header extends Component { - {(isHomePage || this.props.categoryId || isProfilePage || isAktionenPage || isFilialePage) && } + {(isHomePage || this.props.categoryId || isProfilePage || isAktionenPage || isFilialePage || this.props.isArtikel) && } ); } @@ -104,10 +104,11 @@ const HeaderWithContext = (props) => { const isProfilePage = location.pathname === '/profile'; const isAktionenPage = location.pathname === '/aktionen'; const isFilialePage = location.pathname === '/filiale'; - + const isArtikel = location.pathname.startsWith('/Artikel/'); + return ( -
+
); }; diff --git a/src/components/Product.js b/src/components/Product.js index 9eee0d1..a72c99c 100644 --- a/src/components/Product.js +++ b/src/components/Product.js @@ -7,10 +7,67 @@ import Typography from '@mui/material/Typography'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; import AddToCartButton from './AddToCartButton.js'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { withI18n } from '../i18n/withTranslation.js'; import ZoomInIcon from '@mui/icons-material/ZoomIn'; +// Helper function to find level 1 category ID from any category ID +const findLevel1CategoryId = (categoryId) => { + try { + const currentLanguage = 'de'; // Default to German + const categoryTreeCache = window.categoryService?.getSync(209, currentLanguage); + + if (!categoryTreeCache || !categoryTreeCache.children) { + return null; + } + + // Helper function to find category by ID and get its level 1 parent + const findCategoryAndLevel1 = (categories, targetId) => { + for (const category of categories) { + if (category.id === targetId) { + // Found the category, now find its level 1 parent + return findLevel1Parent(categoryTreeCache.children, category); + } + + if (category.children && category.children.length > 0) { + const result = findCategoryAndLevel1(category.children, targetId); + if (result) return result; + } + } + return null; + }; + + // Helper function to find the level 1 parent (direct child of root category 209) + const findLevel1Parent = (level1Categories, category) => { + // If this category's parent is 209, it's already level 1 + if (category.parentId === 209) { + return category.id; + } + + // Otherwise, find the parent and check if it's level 1 + for (const level1Category of level1Categories) { + if (level1Category.id === category.parentId) { + return level1Category.id; + } + + // If parent has children, search recursively + if (level1Category.children && level1Category.children.length > 0) { + const result = findLevel1Parent(level1Category.children, category); + if (result) return result; + } + } + + return null; + }; + + return findCategoryAndLevel1(categoryTreeCache.children, parseInt(categoryId)); + + } catch (error) { + console.error('Error finding level 1 category:', error); + return null; + } +}; + class Product extends Component { constructor(props) { super(props); @@ -73,8 +130,25 @@ class Product extends Component { // In a real app, this would update a cart state in a parent component or Redux store } + handleProductClick = (e) => { + e.preventDefault(); + + const { categoryId } = this.props; + + // Find the level 1 category for this product + const level1CategoryId = categoryId ? findLevel1CategoryId(categoryId) : null; + + // Navigate to the product page WITH the category information in the state + const navigate = this.props.navigate; + if (navigate) { + navigate(`/Artikel/${this.props.seoName}`, { + state: { articleCategoryId: level1CategoryId } + }); + } + } + render() { - const { + const { id, name, price, available, manufacturer, seoName, currency, vat, cGrundEinheit, fGrundPreis, thc, floweringWeeks,incoming, neu, weight, versandklasse, availableSupplier, komponenten @@ -253,15 +327,15 @@ class Product extends Component { )} { + const navigate = useNavigate(); + return ; +}; + +export default withI18n()(ProductWithNavigation); diff --git a/src/components/ProductCarousel.js b/src/components/ProductCarousel.js index cd1a1b8..b6eb753 100644 --- a/src/components/ProductCarousel.js +++ b/src/components/ProductCarousel.js @@ -413,6 +413,7 @@ class ProductCarousel extends React.Component { availableSupplier={product.availableSupplier} komponenten={product.komponenten} rebate={product.rebate} + categoryId={product.kategorien ? product.kategorien.split(',')[0] : undefined} priority={index < 6 ? 'high' : 'auto'} t={t} /> diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js index 1837241..32444b0 100644 --- a/src/components/ProductDetailPage.js +++ b/src/components/ProductDetailPage.js @@ -62,6 +62,7 @@ class ProductDetailPage extends Component { if (cachedData) { // Complete cached data found + // Clean up prerender fallback since we have cached data if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) { delete window.__PRERENDER_FALLBACK__; @@ -110,7 +111,7 @@ class ProductDetailPage extends Component { } else if (partialProduct && isUpgrading) { // Partial product data found - enter upgrading state console.log("ProductDetailPage: Found partial product data, entering upgrading state"); - + // Clean up prerender fallback since we have some data if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) { delete window.__PRERENDER_FALLBACK__; @@ -581,7 +582,7 @@ class ProductDetailPage extends Component { // Cache the complete response data (product + attributes) - cache the response with translated product const cacheData = { ...res, product: productData }; window.productDetailCache[cacheKey] = cacheData; - + // Clean up prerender fallback since we now have real data if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) { delete window.__PRERENDER_FALLBACK__; @@ -900,10 +901,10 @@ class ProductDetailPage extends Component { mb: 2, position: ["-webkit-sticky", "sticky"], // Provide both prefixed and standard top: { - xs: "80px", - sm: "80px", - md: "80px", - lg: "80px", + xs: "110px", + sm: "110px", + md: "110px", + lg: "110px", } /* Offset to sit below the header 120 mith menu for md and lg*/, left: 0, width: "100%", @@ -1681,6 +1682,7 @@ class ProductDetailPage extends Component { availableSupplier={product.availableSupplier} komponenten={product.komponenten} rebate={product.rebate} + categoryId={product.kategorien ? product.kategorien.split(',')[0] : undefined} priority={index < 6 ? 'high' : 'auto'} t={this.props.t} /> diff --git a/src/components/ProductList.js b/src/components/ProductList.js index 4f7ad7a..eec3e2d 100644 --- a/src/components/ProductList.js +++ b/src/components/ProductList.js @@ -475,6 +475,7 @@ class ProductList extends Component { availableSupplier={product.availableSupplier} komponenten={product.komponenten} rebate={product.rebate} + categoryId={product.kategorien ? product.kategorien.split(',')[0] : undefined} priority={index < 6 ? 'high' : 'auto'} t={this.props.t} /> diff --git a/src/components/header/CategoryList.js b/src/components/header/CategoryList.js index d7107df..262aa2a 100644 --- a/src/components/header/CategoryList.js +++ b/src/components/header/CategoryList.js @@ -23,6 +23,7 @@ class CategoryList extends Component { mobileMenuOpen: false, activeCategoryId: null // Will be set properly after categories are loaded }; + this.productCategoryCheckInterval = null; } componentDidMount() { @@ -42,7 +43,7 @@ class CategoryList extends Component { if (response.children && response.children.length > 0) { console.log("Setting categories with", response.children.length, "items"); console.log("First category name:", response.children[0]?.name); - this.setState({ + this.setState({ categories: response.children, activeCategoryId: this.setLevel1CategoryId(this.props.activeCategoryId) }); @@ -68,61 +69,71 @@ class CategoryList extends Component { }); }); } - if (prevProps.activeCategoryId !== this.props.activeCategoryId) { - //detect path here - console.log("activeCategoryId updated", this.props.activeCategoryId); - - this.setLevel1CategoryId(this.props.activeCategoryId); - - } + if (prevProps.activeCategoryId !== this.props.activeCategoryId) { + this.setLevel1CategoryId(this.props.activeCategoryId); + } } - setLevel1CategoryId = (seoName) => { - console.log("setLevel1CategoryId called with seoName:", seoName); - if(seoName) { + setLevel1CategoryId = (input) => { + if(input) { const language = this.props.languageContext?.currentLanguage || this.props.i18n.language; - console.log("setLevel1CategoryId - using language:", language); - console.log("setLevel1CategoryId - languageContext:", this.props.languageContext); - console.log("setLevel1CategoryId - i18n.language:", this.props.i18n?.language); const categoryTreeCache = window.categoryService.getSync(209, language); - console.log("setLevel1CategoryId - categoryTreeCache (language: " + language + "):", categoryTreeCache, seoName); - - // Helper function to recursively search for seoName in category tree - const findLevel1CategoryId = (categories, targetSeoName, level1Id = null) => { - for (const category of categories) { - // If we're at level 1 (direct children of root), set this as potential level1Id - const currentLevel1Id = level1Id || category.id; - - // Check if current category matches the seoName - if (category.seoName === targetSeoName) { - return currentLevel1Id; - } - - // If category has children, search recursively - if (category.children && category.children.length > 0) { - const result = findLevel1CategoryId(category.children, targetSeoName, currentLevel1Id); - if (result) { - return result; - } - } - } - }; - - // Search in the children of the root category (209) + if (categoryTreeCache && categoryTreeCache.children) { - const level1CategoryId = findLevel1CategoryId(categoryTreeCache.children, seoName); - console.log("Found level1CategoryId:", level1CategoryId, "for seoName:", seoName); + let level1CategoryId = null; + + // Check if input is already a numeric level 1 category ID + const inputAsNumber = parseInt(input); + if (!isNaN(inputAsNumber)) { + // Check if this is already a level 1 category ID + const level1Category = categoryTreeCache.children.find(cat => cat.id === inputAsNumber); + if (level1Category) { + console.log("Input is already a level 1 category ID:", inputAsNumber); + level1CategoryId = inputAsNumber; + } else { + // It's a category ID, find its level 1 parent + const findLevel1FromId = (categories, targetId) => { + for (const category of categories) { + if (category.id === targetId) { + return category.parentId === 209 ? category.id : findLevel1FromId(categoryTreeCache.children, category.parentId); + } + if (category.children && category.children.length > 0) { + const result = findLevel1FromId(category.children, targetId); + if (result) return result; + } + } + return null; + }; + level1CategoryId = findLevel1FromId(categoryTreeCache.children, inputAsNumber); + } + } else { + // It's an SEO name, find the level 1 category + const findLevel1FromSeoName = (categories, targetSeoName, level1Id = null) => { + for (const category of categories) { + const currentLevel1Id = level1Id || category.id; + + if (category.seoName === targetSeoName) { + return currentLevel1Id; + } + + if (category.children && category.children.length > 0) { + const result = findLevel1FromSeoName(category.children, targetSeoName, currentLevel1Id); + if (result) return result; + } + } + return null; + }; + level1CategoryId = findLevel1FromSeoName(categoryTreeCache.children, input); + } + this.setState({ activeCategoryId: level1CategoryId }); return; } - this.setState({ activeCategoryId: null }); - - }else{ - this.setState({ activeCategoryId: null }); } + this.setState({ activeCategoryId: null }); } @@ -140,18 +151,16 @@ class CategoryList extends Component { }); }; + componentWillUnmount() { + if (this.productCategoryCheckInterval) { + clearInterval(this.productCategoryCheckInterval); + this.productCategoryCheckInterval = null; + } + } + render() { const { categories, mobileMenuOpen, activeCategoryId } = this.state; - console.log("RENDER DEBUG - About to render categories:"); - console.log(" categories.length:", categories.length); - if (categories.length > 0) { - console.log(" First category name:", categories[0].name); - console.log(" First category id:", categories[0].id); - } - console.log(" Current language context:", this.props.languageContext?.currentLanguage); - console.log(" Current i18n language:", this.props.i18n?.language); - const renderCategoryRow = (categories, isMobile = false) => (