From 43e67ee4c4c35ea19a56e6d2c531721d6d903b85 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Wed, 19 Nov 2025 09:25:21 +0100 Subject: [PATCH] feat(Context): integrate Product and Category context providers into App - Wrapped AppContent with ProductContextProvider and CategoryContextProvider to manage product and category states. - Added TitleUpdater component for dynamic title management. - Enhanced Content and ProductDetailPage components to utilize the new context for setting and clearing current product and category states. - Updated ProductDetailWithSocket to pass setCurrentProduct function from context. --- .cursor/rules/devserver.mdc | 5 ++ src/App.js | 20 +++++--- src/components/Content.js | 56 ++++++++++++++++++++++- src/components/ProductDetailPage.js | 35 +++++++++++++- src/components/ProductDetailWithSocket.js | 11 ++++- src/components/TitleUpdater.js | 53 +++++++++++++++++++++ src/context/CategoryContext.js | 31 +++++++++++++ src/context/ProductContext.js | 31 +++++++++++++ 8 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 .cursor/rules/devserver.mdc create mode 100644 src/components/TitleUpdater.js create mode 100644 src/context/CategoryContext.js create mode 100644 src/context/ProductContext.js diff --git a/.cursor/rules/devserver.mdc b/.cursor/rules/devserver.mdc new file mode 100644 index 0000000..60548c8 --- /dev/null +++ b/.cursor/rules/devserver.mdc @@ -0,0 +1,5 @@ +--- +alwaysApply: false +--- +never run your own dev sever, it can be restarted with ```pm2 restart dev_seedheads_fron``` +get logoutput lioke this ```pm2 log dev_seedheads_fron --lines 20 --nostream``` \ No newline at end of file diff --git a/src/App.js b/src/App.js index 3bc21c6..c287595 100644 --- a/src/App.js +++ b/src/App.js @@ -18,6 +18,9 @@ import PaletteIcon from "@mui/icons-material/Palette"; import ScienceIcon from "@mui/icons-material/Science"; import { CarouselProvider } from "./contexts/CarouselContext.js"; +import { ProductContextProvider } from "./context/ProductContext.js"; +import { CategoryContextProvider } from "./context/CategoryContext.js"; +import TitleUpdater from "./components/TitleUpdater.js"; import config from "./config.js"; import ScrollToTop from "./components/ScrollToTop.js"; @@ -222,6 +225,7 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => { bgcolor: "background.default", }} > +
@@ -450,12 +454,16 @@ const App = () => { return ( - - + + + + + + ); diff --git a/src/components/Content.js b/src/components/Content.js index caa2151..0357cf3 100644 --- a/src/components/Content.js +++ b/src/components/Content.js @@ -14,6 +14,7 @@ import CategoryBox from './CategoryBox.js'; import { useParams, useSearchParams } from 'react-router-dom'; import { getAllSettingsWithPrefix } from '../utils/sessionStorage.js'; import { withI18n } from '../i18n/withTranslation.js'; +import { withCategory } from '../context/CategoryContext.js'; const isNew = (neu) => neu && (new Date().getTime() - new Date(neu).getTime() < 30 * 24 * 60 * 60 * 1000); @@ -221,6 +222,11 @@ class Content extends Component { const searchChanged = this.props.searchParams?.get('q') && (prevProps.searchParams?.get('q') !== this.props.searchParams?.get('q')); if(categoryChanged) { + // Clear context for new category loading + if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) { + this.props.categoryContext.setCurrentCategory(null); + } + window.currentSearchQuery = null; this.setState({loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage}, () => { this.fetchCategoryData(this.props.params.categoryId); @@ -299,6 +305,26 @@ class Content extends Component { attributes: response.attributes, childCategories: response.childCategories || [], loaded: true + }, () => { + console.log('Content: processData finished', { + hasContext: !!this.props.categoryContext, + categoryName: response.categoryName, + name: response.name + }); + + if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) { + if (response.categoryName || response.name) { + console.log('Content: Setting category context'); + this.props.categoryContext.setCurrentCategory({ + id: this.props.params.categoryId, + name: response.categoryName || response.name + }); + } else { + console.log('Content: No category name found to set in context'); + } + } else { + console.warn('Content: categoryContext prop is missing!'); + } }); } @@ -375,6 +401,27 @@ class Content extends Component { ...response, childCategories }; + + // Attempt to set category name from the tree if missing in response + if (!enhancedResponse.categoryName && !enhancedResponse.name) { + // Try to find name in the tree using the ID or SEO name + try { + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const categoryTreeCache = window.categoryService.getSync(209, currentLanguage); + + if (categoryTreeCache) { + const targetCategory = typeof categoryId === 'string' + ? this.findCategoryBySeoName(categoryTreeCache, categoryId) + : this.findCategoryById(categoryTreeCache, categoryId); + + if (targetCategory && targetCategory.name) { + enhancedResponse.categoryName = targetCategory.name; + } + } + } catch (err) { + console.error('Error finding category name in tree:', err); + } + } this.processData(enhancedResponse); } @@ -452,6 +499,12 @@ class Content extends Component { return category ? category.id : null; } + componentWillUnmount() { + if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) { + this.props.categoryContext.setCurrentCategory(null); + } + } + renderParentCategoryNavigation = () => { const currentCategoryId = this.getCurrentCategoryId(); if (!currentCategoryId) return null; @@ -490,6 +543,7 @@ class Content extends Component { } render() { + // console.log('Content props:', this.props); // Check if we should show category boxes instead of product list const showCategoryBoxes = this.state.loaded && this.state.unfilteredProducts.length === 0 && @@ -780,4 +834,4 @@ class Content extends Component { } } -export default withRouter(withI18n()(Content)); \ No newline at end of file +export default withRouter(withI18n()(withCategory(Content))); \ No newline at end of file diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js index 81e9dae..27a6ce0 100644 --- a/src/components/ProductDetailPage.js +++ b/src/components/ProductDetailPage.js @@ -201,6 +201,17 @@ class ProductDetailPage extends Component { } componentDidMount() { + // Update context with cached product if available + if (this.state.product && this.props.setCurrentProduct) { + console.log('ProductDetailPage: Setting product context from cache', this.state.product.name); + this.props.setCurrentProduct({ + name: this.state.product.name, + categoryId: this.state.product.kategorien ? this.state.product.kategorien.split(',')[0] : undefined + }); + } else if (this.state.product) { + console.warn('ProductDetailPage: setCurrentProduct prop is missing despite having product'); + } + // Load product data if we have no product or if we're in upgrading state if (!this.state.product || this.state.upgrading) { this.loadProductData(); @@ -221,6 +232,11 @@ class ProductDetailPage extends Component { componentDidUpdate(prevProps) { // Check for seoName changes if (prevProps.seoName !== this.props.seoName) { + // Clear context when navigating to new product + if (this.props.setCurrentProduct) { + this.props.setCurrentProduct(null); + } + this.setState( { product: null, loading: true, upgrading: false, error: null, imageDialogOpen: false, similarProducts: [] }, this.loadProductData @@ -617,6 +633,17 @@ class ProductDetailPage extends Component { komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded similarProducts: res.similarProducts || [] }, () => { + // Update context + if (this.props.setCurrentProduct) { + console.log('ProductDetailPage: Setting product context from fetch', productData.name); + this.props.setCurrentProduct({ + name: productData.name, + categoryId: productData.kategorien ? productData.kategorien.split(',')[0] : undefined + }); + } else { + console.warn('ProductDetailPage: setCurrentProduct prop is missing after fetch'); + } + if(komponenten.length > 0) { for(const komponent of komponenten) { this.loadKomponent(komponent.id, komponent.count); @@ -1034,7 +1061,13 @@ class ProductDetailPage extends Component { }); }; - render() { + componentWillUnmount() { + if (this.props.setCurrentProduct) { + this.props.setCurrentProduct(null); + } + } + + render() { const { product, loading, upgrading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings, shareAnchorEl, sharePopperOpen, snackbarOpen, snackbarMessage, snackbarSeverity } = this.state; diff --git a/src/components/ProductDetailWithSocket.js b/src/components/ProductDetailWithSocket.js index b5fad1e..a108bfa 100644 --- a/src/components/ProductDetailWithSocket.js +++ b/src/components/ProductDetailWithSocket.js @@ -1,15 +1,22 @@ import React from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; import ProductDetailPage from './ProductDetailPage.js'; +import { useProduct } from '../context/ProductContext.js'; const ProductDetailWithSocket = () => { const { seoName } = useParams(); const navigate = useNavigate(); const location = useLocation(); + const { setCurrentProduct } = useProduct(); return ( - + ); }; -export default ProductDetailWithSocket; \ No newline at end of file +export default ProductDetailWithSocket; diff --git a/src/components/TitleUpdater.js b/src/components/TitleUpdater.js new file mode 100644 index 0000000..800eed3 --- /dev/null +++ b/src/components/TitleUpdater.js @@ -0,0 +1,53 @@ +import React, { Component } from 'react'; +import { withProduct } from '../context/ProductContext.js'; +import { withCategory } from '../context/CategoryContext.js'; + +// Utility function to clean product names (duplicated from ProductDetailPage to ensure consistency) +const cleanProductName = (name) => { + if (!name) return ""; + // Remove patterns like " (1)", " (3)", " (10)" at the end of the string + return name.replace(/\s*\(\d+\)\s*$/, "").trim(); +}; + +class TitleUpdater extends Component { + componentDidMount() { + this.updateTitle(); + } + + componentDidUpdate(prevProps) { + console.log('TitleUpdater: Update triggered', { + prevProduct: prevProps.productContext.currentProduct, + currProduct: this.props.productContext.currentProduct, + prevCategory: prevProps.categoryContext.currentCategory, + currCategory: this.props.categoryContext.currentCategory + }); + if ( + prevProps.productContext.currentProduct !== this.props.productContext.currentProduct || + prevProps.categoryContext.currentCategory !== this.props.categoryContext.currentCategory + ) { + this.updateTitle(); + } + } + + updateTitle() { + const { currentProduct } = this.props.productContext; + const { currentCategory } = this.props.categoryContext; + + console.log('TitleUpdater: Updating title with', { currentProduct, currentCategory }); + + if (currentProduct && currentProduct.name) { + document.title = `GrowHeads.de - ${cleanProductName(currentProduct.name)}`; + } else if (currentCategory && currentCategory.name) { + document.title = `GrowHeads.de - ${currentCategory.name}`; + } else { + document.title = 'GrowHeads.de'; + } + } + + render() { + return null; + } +} + +export default withCategory(withProduct(TitleUpdater)); + diff --git a/src/context/CategoryContext.js b/src/context/CategoryContext.js new file mode 100644 index 0000000..2a4e169 --- /dev/null +++ b/src/context/CategoryContext.js @@ -0,0 +1,31 @@ +import React, { createContext, useState, useContext } from 'react'; + +const CategoryContext = createContext({ + currentCategory: null, + setCurrentCategory: () => {} +}); + +export const useCategory = () => useContext(CategoryContext); + +export const withCategory = (Component) => { + return (props) => { + const categoryContext = useCategory(); + return ; + }; +}; + +export const CategoryContextProvider = ({ children }) => { + const [currentCategory, setCurrentCategory] = useState(null); + + const setCurrentCategoryWithLog = (category) => { + console.log('CategoryContext: Setting current category to:', category); + setCurrentCategory(category); + }; + + return ( + + {children} + + ); +}; + diff --git a/src/context/ProductContext.js b/src/context/ProductContext.js new file mode 100644 index 0000000..7957f04 --- /dev/null +++ b/src/context/ProductContext.js @@ -0,0 +1,31 @@ +import React, { createContext, useState, useContext } from 'react'; + +const ProductContext = createContext({ + currentProduct: null, + setCurrentProduct: () => {} +}); + +export const useProduct = () => useContext(ProductContext); + +export const withProduct = (Component) => { + return (props) => { + const productContext = useProduct(); + return ; + }; +}; + +export const ProductContextProvider = ({ children }) => { + const [currentProduct, setCurrentProduct] = useState(null); + + const setCurrentProductWithLog = (product) => { + console.log('ProductContext: Setting current product to:', product); + setCurrentProduct(product); + }; + + return ( + + {children} + + ); +}; +