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