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.
This commit is contained in:
43
src/App.js
43
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();
|
||||
|
||||
@@ -91,7 +91,7 @@ class Header extends Component {
|
||||
</Box>
|
||||
</Container>
|
||||
</Toolbar>
|
||||
{(isHomePage || this.props.categoryId || isProfilePage || isAktionenPage || isFilialePage) && <CategoryList categoryId={209} activeCategoryId={this.props.categoryId}/>}
|
||||
{(isHomePage || this.props.categoryId || isProfilePage || isAktionenPage || isFilialePage || this.props.isArtikel) && <CategoryList categoryId={209} activeCategoryId={this.props.categoryId}/>}
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
<Header {...props} isHomePage={isHomePage} isProfilePage={isProfilePage} isAktionenPage={isAktionenPage} isFilialePage={isFilialePage} />
|
||||
<Header {...props} isHomePage={isHomePage} isArtikel={isArtikel} isProfilePage={isProfilePage} isAktionenPage={isAktionenPage} isFilialePage={isFilialePage} />
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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,6 +130,23 @@ 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 {
|
||||
id, name, price, available, manufacturer, seoName,
|
||||
@@ -253,15 +327,15 @@ class Product extends Component {
|
||||
)}
|
||||
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/Artikel/${seoName}`}
|
||||
onClick={this.handleProductClick}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit'
|
||||
color: 'inherit',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
@@ -420,4 +494,10 @@ class Product extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withI18n()(Product);
|
||||
// Wrapper component to provide navigate hook
|
||||
const ProductWithNavigation = (props) => {
|
||||
const navigate = useNavigate();
|
||||
return <Product {...props} navigate={navigate} />;
|
||||
};
|
||||
|
||||
export default withI18n()(ProductWithNavigation);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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__;
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -23,6 +23,7 @@ class CategoryList extends Component {
|
||||
mobileMenuOpen: false,
|
||||
activeCategoryId: null // Will be set properly after categories are loaded
|
||||
};
|
||||
this.productCategoryCheckInterval = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -69,60 +70,70 @@ 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) => {
|
||||
if (categoryTreeCache && categoryTreeCache.children) {
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
const result = findLevel1FromSeoName(category.children, targetSeoName, currentLevel1Id);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
level1CategoryId = findLevel1FromSeoName(categoryTreeCache.children, input);
|
||||
}
|
||||
|
||||
// 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);
|
||||
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) => (
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
Reference in New Issue
Block a user