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.
This commit is contained in:
5
.cursor/rules/devserver.mdc
Normal file
5
.cursor/rules/devserver.mdc
Normal file
@@ -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```
|
||||
20
src/App.js
20
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",
|
||||
}}
|
||||
>
|
||||
<TitleUpdater />
|
||||
<ScrollToTop />
|
||||
<Header active categoryId={categoryId} key={authVersion} />
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
@@ -450,12 +454,16 @@ const App = () => {
|
||||
return (
|
||||
<LanguageProvider i18n={i18n}>
|
||||
<ThemeProvider theme={dynamicTheme}>
|
||||
<CssBaseline />
|
||||
<AppContent
|
||||
currentTheme={currentTheme}
|
||||
dynamicTheme={dynamicTheme}
|
||||
onThemeChange={handleThemeChange}
|
||||
/>
|
||||
<ProductContextProvider>
|
||||
<CategoryContextProvider>
|
||||
<CssBaseline />
|
||||
<AppContent
|
||||
currentTheme={currentTheme}
|
||||
dynamicTheme={dynamicTheme}
|
||||
onThemeChange={handleThemeChange}
|
||||
/>
|
||||
</CategoryContextProvider>
|
||||
</ProductContextProvider>
|
||||
</ThemeProvider>
|
||||
</LanguageProvider>
|
||||
);
|
||||
|
||||
@@ -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));
|
||||
export default withRouter(withI18n()(withCategory(Content)));
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<ProductDetailPage seoName={seoName} navigate={navigate} location={location} />
|
||||
<ProductDetailPage
|
||||
seoName={seoName}
|
||||
navigate={navigate}
|
||||
location={location}
|
||||
setCurrentProduct={setCurrentProduct}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductDetailWithSocket;
|
||||
export default ProductDetailWithSocket;
|
||||
|
||||
53
src/components/TitleUpdater.js
Normal file
53
src/components/TitleUpdater.js
Normal file
@@ -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));
|
||||
|
||||
31
src/context/CategoryContext.js
Normal file
31
src/context/CategoryContext.js
Normal file
@@ -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 <Component {...props} categoryContext={categoryContext} />;
|
||||
};
|
||||
};
|
||||
|
||||
export const CategoryContextProvider = ({ children }) => {
|
||||
const [currentCategory, setCurrentCategory] = useState(null);
|
||||
|
||||
const setCurrentCategoryWithLog = (category) => {
|
||||
console.log('CategoryContext: Setting current category to:', category);
|
||||
setCurrentCategory(category);
|
||||
};
|
||||
|
||||
return (
|
||||
<CategoryContext.Provider value={{ currentCategory, setCurrentCategory: setCurrentCategoryWithLog }}>
|
||||
{children}
|
||||
</CategoryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
31
src/context/ProductContext.js
Normal file
31
src/context/ProductContext.js
Normal file
@@ -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 <Component {...props} productContext={productContext} />;
|
||||
};
|
||||
};
|
||||
|
||||
export const ProductContextProvider = ({ children }) => {
|
||||
const [currentProduct, setCurrentProduct] = useState(null);
|
||||
|
||||
const setCurrentProductWithLog = (product) => {
|
||||
console.log('ProductContext: Setting current product to:', product);
|
||||
setCurrentProduct(product);
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductContext.Provider value={{ currentProduct, setCurrentProduct: setCurrentProductWithLog }}>
|
||||
{children}
|
||||
</ProductContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user