refactor: enhance category data management in CategoryList and CategoryService by integrating async-mutex for improved concurrency control and simplifying state handling
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"@mui/material": "^7.1.1",
|
"@mui/material": "^7.1.1",
|
||||||
"@stripe/react-stripe-js": "^3.7.0",
|
"@stripe/react-stripe-js": "^3.7.0",
|
||||||
"@stripe/stripe-js": "^7.3.1",
|
"@stripe/stripe-js": "^7.3.1",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
"country-flag-icons": "^1.5.19",
|
"country-flag-icons": "^1.5.19",
|
||||||
"html-react-parser": "^5.2.5",
|
"html-react-parser": "^5.2.5",
|
||||||
@@ -4200,6 +4201,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/async-mutex": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@@ -11829,7 +11839,6 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"@mui/material": "^7.1.1",
|
"@mui/material": "^7.1.1",
|
||||||
"@stripe/react-stripe-js": "^3.7.0",
|
"@stripe/react-stripe-js": "^3.7.0",
|
||||||
"@stripe/stripe-js": "^7.3.1",
|
"@stripe/stripe-js": "^7.3.1",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
"country-flag-icons": "^1.5.19",
|
"country-flag-icons": "^1.5.19",
|
||||||
"html-react-parser": "^5.2.5",
|
"html-react-parser": "^5.2.5",
|
||||||
|
|||||||
@@ -11,311 +11,129 @@ import CloseIcon from "@mui/icons-material/Close";
|
|||||||
import { withI18n } from "../../i18n/withTranslation.js";
|
import { withI18n } from "../../i18n/withTranslation.js";
|
||||||
|
|
||||||
class CategoryList extends Component {
|
class CategoryList extends Component {
|
||||||
findCategoryById = (category, targetId) => {
|
|
||||||
if (!category) return null;
|
|
||||||
|
|
||||||
if (category.seoName === targetId) {
|
|
||||||
return category;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (category.children) {
|
|
||||||
for (let child of category.children) {
|
|
||||||
const found = this.findCategoryById(child, targetId);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
getPathToCategory = (category, targetId, currentPath = []) => {
|
|
||||||
if (!category) return null;
|
|
||||||
|
|
||||||
const newPath = [...currentPath, category];
|
|
||||||
|
|
||||||
if (category.seoName === targetId) {
|
|
||||||
return newPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (category.children) {
|
|
||||||
for (let child of category.children) {
|
|
||||||
const found = this.getPathToCategory(child, targetId, newPath);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
//const { i18n } = props;
|
||||||
|
const categories = window.categoryService.getSync(209);
|
||||||
|
|
||||||
// Get current language from props (provided by withI18n HOC)
|
this.state = {
|
||||||
const currentLanguage = props.languageContext?.currentLanguage || 'de';
|
categories: categories && categories.children && categories.children.length > 0 ? categories.children : [],
|
||||||
|
mobileMenuOpen: false,
|
||||||
// Check for cached data during SSR/initial render
|
activeCategoryId: null // Will be set properly after categories are loaded
|
||||||
let initialState = {
|
|
||||||
categoryTree: null,
|
|
||||||
level1Categories: [], // Children of category 209 (Home) - always shown
|
|
||||||
level2Categories: [], // Children of active level 1 category
|
|
||||||
level3Categories: [], // Children of active level 2 category
|
|
||||||
activePath: [], // Array of active category objects for each level
|
|
||||||
fetchedCategories: false,
|
|
||||||
mobileMenuOpen: false, // State for mobile collapsible menu
|
|
||||||
currentLanguage: currentLanguage,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to get cached data for SSR
|
|
||||||
try {
|
|
||||||
// @note Check both global.window (SSR) and window (browser) for cache
|
|
||||||
const productCache = (typeof global !== "undefined" && global.window && global.window.productCache) ||
|
|
||||||
(typeof window !== "undefined" && window.productCache);
|
|
||||||
|
|
||||||
if (productCache) {
|
|
||||||
const cacheKey = `categoryTree_209_${currentLanguage}`;
|
|
||||||
const cachedData = productCache[cacheKey];
|
|
||||||
if (cachedData && cachedData.categoryTree) {
|
|
||||||
const { categoryTree, timestamp } = cachedData;
|
|
||||||
const cacheAge = Date.now() - timestamp;
|
|
||||||
const tenMinutes = 10 * 60 * 1000;
|
|
||||||
|
|
||||||
// Use cached data if it's fresh
|
|
||||||
if (cacheAge < tenMinutes) {
|
|
||||||
initialState.categoryTree = categoryTree;
|
|
||||||
initialState.fetchedCategories = true;
|
|
||||||
|
|
||||||
// Process category tree to set up navigation
|
|
||||||
const level1Categories =
|
|
||||||
categoryTree && categoryTree.id === 209
|
|
||||||
? categoryTree.children || []
|
|
||||||
: [];
|
|
||||||
initialState.level1Categories = level1Categories;
|
|
||||||
|
|
||||||
// Process active category path if needed
|
|
||||||
if (props.activeCategoryId) {
|
|
||||||
const activeCategory = this.findCategoryById(
|
|
||||||
categoryTree,
|
|
||||||
props.activeCategoryId
|
|
||||||
);
|
|
||||||
if (activeCategory) {
|
|
||||||
const pathToActive = this.getPathToCategory(
|
|
||||||
categoryTree,
|
|
||||||
props.activeCategoryId
|
|
||||||
);
|
|
||||||
initialState.activePath = pathToActive
|
|
||||||
? pathToActive.slice(1)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (initialState.activePath.length >= 1) {
|
|
||||||
const level1Category = initialState.activePath[0];
|
|
||||||
initialState.level2Categories = level1Category.children || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialState.activePath.length >= 2) {
|
|
||||||
const level2Category = initialState.activePath[1];
|
|
||||||
initialState.level3Categories = level2Category.children || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error reading cache in constructor:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = initialState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchCategories();
|
if (!this.state.categories || this.state.categories.length === 0) {
|
||||||
|
window.categoryService.get(209).then((response) => {
|
||||||
|
console.log("response", response);
|
||||||
|
if (response.children && response.children.length > 0) {
|
||||||
|
this.setState({
|
||||||
|
categories: response.children,
|
||||||
|
activeCategoryId: this.getLevel1CategoryId(this.props.activeCategoryId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Categories are already loaded, set the initial activeCategoryId
|
||||||
|
this.setState({
|
||||||
|
activeCategoryId: this.getLevel1CategoryId(this.props.activeCategoryId)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
// Handle language changes
|
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
|
||||||
const currentLanguage = this.props.languageContext?.currentLanguage || 'de';
|
//detect path here
|
||||||
const prevLanguage = prevProps.languageContext?.currentLanguage || 'de';
|
console.log("activeCategoryId updated", this.props.activeCategoryId);
|
||||||
|
|
||||||
if (currentLanguage !== prevLanguage) {
|
// Get the active category ID of level 1 when prop is seoName
|
||||||
// Language changed, need to refetch categories
|
const level1CategoryId = this.getLevel1CategoryId(this.props.activeCategoryId);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
currentLanguage: currentLanguage,
|
activeCategoryId: level1CategoryId
|
||||||
fetchedCategories: false,
|
|
||||||
categoryTree: null,
|
|
||||||
level1Categories: [],
|
|
||||||
level2Categories: [],
|
|
||||||
level3Categories: [],
|
|
||||||
activePath: [],
|
|
||||||
}, () => {
|
|
||||||
this.fetchCategories();
|
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If activeCategoryId changes, update subcategories
|
|
||||||
if (
|
|
||||||
prevProps.activeCategoryId !== this.props.activeCategoryId &&
|
|
||||||
this.state.categoryTree
|
|
||||||
) {
|
|
||||||
this.processCategoryTree(this.state.categoryTree);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCategories = () => {
|
// Helper method to get level 1 category ID from seoName or numeric ID
|
||||||
if (this.state.fetchedCategories) {
|
getLevel1CategoryId = (categoryIdOrSeoName) => {
|
||||||
console.log('Categories already fetched, skipping');
|
if (!categoryIdOrSeoName) return null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentLanguage = this.state.currentLanguage || 'de';
|
|
||||||
const windowObj = (typeof global !== "undefined" && global.window) ||
|
|
||||||
(typeof window !== "undefined" && window);
|
|
||||||
|
|
||||||
// Ensure cache exists
|
// If it's already a numeric ID, check if it's a level 1 category
|
||||||
windowObj.productCache = windowObj.productCache || {};
|
if (typeof categoryIdOrSeoName === 'number') {
|
||||||
|
// Check if this ID is directly under Home (209)
|
||||||
// The cache is PRERENDERED - always use it first!
|
const level1Category = this.state.categories.find(cat => cat.id === categoryIdOrSeoName);
|
||||||
console.log('CategoryList: Checking prerendered cache', windowObj.productCache);
|
return level1Category ? categoryIdOrSeoName : this.findLevel1ParentId(categoryIdOrSeoName);
|
||||||
|
|
||||||
// Use either language-specific or default cache
|
|
||||||
const cacheKey = `categoryTree_209_${currentLanguage}`;
|
|
||||||
const defaultCacheKey = "categoryTree_209";
|
|
||||||
|
|
||||||
// Try language-specific cache first, then fall back to default
|
|
||||||
const categoryTree =
|
|
||||||
windowObj.productCache[cacheKey]?.categoryTree ||
|
|
||||||
windowObj.productCache[defaultCacheKey]?.categoryTree;
|
|
||||||
|
|
||||||
if (categoryTree) {
|
|
||||||
console.log('CategoryList: Using prerendered cache');
|
|
||||||
this.processCategoryTree(categoryTree);
|
|
||||||
this.setState({ fetchedCategories: true });
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only fetch if no prerendered cache exists
|
// If it's a string (seoName), find the category and get its level 1 parent
|
||||||
console.log('CategoryList: No prerendered cache, fetching from socket');
|
if (typeof categoryIdOrSeoName === 'string') {
|
||||||
windowObj.productCache[cacheKey] = { fetching: true, timestamp: Date.now() };
|
const categoryTreeCache = window.productCache && window.productCache['categoryTree_209'];
|
||||||
this.setState({ fetchedCategories: true });
|
if (!categoryTreeCache || !categoryTreeCache.categoryTree) {
|
||||||
window.socketManager.emit("categoryList", { categoryId: 209, language: currentLanguage, requestTranslation: true }, (response) => {
|
return null;
|
||||||
if (response && response.success) {
|
|
||||||
// Use translated data if available, otherwise fall back to original
|
|
||||||
const categoryTreeToUse = response.translation || response.categoryTree;
|
|
||||||
|
|
||||||
if (categoryTreeToUse) {
|
|
||||||
// Store in global cache with timestamp
|
|
||||||
try {
|
|
||||||
const cacheKey = `categoryTree_209_${currentLanguage}`;
|
|
||||||
if (windowObj && windowObj.productCache) {
|
|
||||||
windowObj.productCache[cacheKey] = {
|
|
||||||
categoryTree: categoryTreeToUse,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
fetching: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error writing to cache:", err);
|
|
||||||
}
|
|
||||||
this.processCategoryTree(categoryTreeToUse);
|
|
||||||
} else {
|
|
||||||
console.error('No category tree found in response');
|
|
||||||
// Clear cache on error
|
|
||||||
try {
|
|
||||||
const cacheKey = `categoryTree_209_${currentLanguage}`;
|
|
||||||
if (windowObj && windowObj.productCache) {
|
|
||||||
windowObj.productCache[cacheKey] = {
|
|
||||||
categoryTree: null,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
fetching: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error writing to cache:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
categoryTree: null,
|
|
||||||
level1Categories: [],
|
|
||||||
level2Categories: [],
|
|
||||||
level3Categories: [],
|
|
||||||
activePath: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Failed to fetch categories:', response);
|
|
||||||
try {
|
|
||||||
const cacheKey = `categoryTree_209_${currentLanguage}`;
|
|
||||||
if (windowObj && windowObj.productCache) {
|
|
||||||
windowObj.productCache[cacheKey] = {
|
|
||||||
categoryTree: null,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
fetching: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error writing to cache:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
categoryTree: null,
|
|
||||||
level1Categories: [],
|
|
||||||
level2Categories: [],
|
|
||||||
level3Categories: [],
|
|
||||||
activePath: [],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
const category = this.findCategoryBySeoName(categoryTreeCache.categoryTree, categoryIdOrSeoName);
|
||||||
|
if (!category) return null;
|
||||||
|
|
||||||
|
// If the found category is already level 1 (direct child of Home)
|
||||||
|
if (category.parentId === 209) {
|
||||||
|
return category.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the level 1 parent of this category
|
||||||
|
return this.findLevel1ParentId(category.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
processCategoryTree = (categoryTree) => {
|
// Helper method to find category by seoName (similar to Content.js)
|
||||||
// Level 1 categories are always the children of category 209 (Home)
|
findCategoryBySeoName = (categoryNode, seoName) => {
|
||||||
const level1Categories =
|
if (!categoryNode) return null;
|
||||||
categoryTree && categoryTree.id === 209
|
|
||||||
? categoryTree.children || []
|
if (categoryNode.seoName === seoName) {
|
||||||
: [];
|
return categoryNode;
|
||||||
|
}
|
||||||
// Build the navigation path and determine what to show at each level
|
|
||||||
let level2Categories = [];
|
if (categoryNode.children) {
|
||||||
let level3Categories = [];
|
for (const child of categoryNode.children) {
|
||||||
let activePath = [];
|
const found = this.findCategoryBySeoName(child, seoName);
|
||||||
|
if (found) return found;
|
||||||
if (this.props.activeCategoryId) {
|
|
||||||
const activeCategory = this.findCategoryById(
|
|
||||||
categoryTree,
|
|
||||||
this.props.activeCategoryId
|
|
||||||
);
|
|
||||||
if (activeCategory) {
|
|
||||||
// Build the path from root to active category
|
|
||||||
const pathToActive = this.getPathToCategory(
|
|
||||||
categoryTree,
|
|
||||||
this.props.activeCategoryId
|
|
||||||
);
|
|
||||||
activePath = pathToActive.slice(1); // Remove root (209) from path
|
|
||||||
|
|
||||||
// Determine what to show at each level based on the path depth
|
|
||||||
if (activePath.length >= 1) {
|
|
||||||
// Show children of the level 1 category
|
|
||||||
const level1Category = activePath[0];
|
|
||||||
level2Categories = level1Category.children || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activePath.length >= 2) {
|
|
||||||
// Show children of the level 2 category
|
|
||||||
const level2Category = activePath[1];
|
|
||||||
level3Categories = level2Category.children || [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
// Helper method to find the level 1 parent category ID
|
||||||
categoryTree,
|
findLevel1ParentId = (categoryId) => {
|
||||||
level1Categories,
|
const categoryTreeCache = window.productCache && window.productCache['categoryTree_209'];
|
||||||
level2Categories,
|
if (!categoryTreeCache || !categoryTreeCache.categoryTree) {
|
||||||
level3Categories,
|
return null;
|
||||||
activePath,
|
}
|
||||||
fetchedCategories: true,
|
|
||||||
});
|
const findParentPath = (node, targetId, path = []) => {
|
||||||
};
|
if (node.id === targetId) {
|
||||||
|
return [...path, node.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
const result = findParentPath(child, targetId, [...path, node.id]);
|
||||||
|
if (result) return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = findParentPath(categoryTreeCache.categoryTree, categoryId);
|
||||||
|
if (!path || path.length < 3) return null; // path should be [209, level1Id, ...]
|
||||||
|
|
||||||
|
return path[1]; // Return the level 1 category ID (second in path after Home/209)
|
||||||
|
}
|
||||||
|
|
||||||
handleMobileMenuToggle = () => {
|
handleMobileMenuToggle = () => {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
@@ -331,10 +149,9 @@ class CategoryList extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { level1Categories, activePath, mobileMenuOpen } =
|
const { categories, mobileMenuOpen, activeCategoryId } = this.state;
|
||||||
this.state;
|
|
||||||
|
|
||||||
const renderCategoryRow = (categories, level = 1, isMobile = false) => (
|
const renderCategoryRow = (categories, isMobile = false) => (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -351,7 +168,6 @@ class CategoryList extends Component {
|
|||||||
msOverflowStyle: "none",
|
msOverflowStyle: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{level === 1 && (
|
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/"
|
to="/"
|
||||||
@@ -372,7 +188,7 @@ class CategoryList extends Component {
|
|||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
...(this.props.activeCategoryId === null && {
|
...(activeCategoryId === null && {
|
||||||
bgcolor: "#fff",
|
bgcolor: "#fff",
|
||||||
textShadow: "none",
|
textShadow: "none",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
@@ -396,7 +212,7 @@ class CategoryList extends Component {
|
|||||||
<HomeIcon sx={{
|
<HomeIcon sx={{
|
||||||
fontSize: "1rem",
|
fontSize: "1rem",
|
||||||
mr: isMobile ? 1 : 0,
|
mr: isMobile ? 1 : 0,
|
||||||
color: this.props.activeCategoryId === null ? "#2e7d32" : "inherit"
|
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
||||||
}} />
|
}} />
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
@@ -405,7 +221,7 @@ class CategoryList extends Component {
|
|||||||
className="bold-text"
|
className="bold-text"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: this.props.activeCategoryId === null ? "#2e7d32" : "transparent",
|
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
@@ -417,7 +233,7 @@ class CategoryList extends Component {
|
|||||||
className="thin-text"
|
className="thin-text"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "400",
|
fontWeight: "400",
|
||||||
color: this.props.activeCategoryId === null ? "transparent" : "inherit",
|
color: activeCategoryId === null ? "transparent" : "inherit",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -429,14 +245,10 @@ class CategoryList extends Component {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
{this.state.fetchedCategories && categories.length > 0 ? (
|
{categories.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{categories.map((category) => {
|
{categories.map((category) => {
|
||||||
// Determine if this category is active at this level
|
|
||||||
const isActiveAtThisLevel =
|
|
||||||
activePath[level - 1] &&
|
|
||||||
activePath[level - 1].id === category.id;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -459,7 +271,7 @@ class CategoryList extends Component {
|
|||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
...(isActiveAtThisLevel && {
|
...(activeCategoryId === category.id && {
|
||||||
bgcolor: "#fff",
|
bgcolor: "#fff",
|
||||||
textShadow: "none",
|
textShadow: "none",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
@@ -483,7 +295,7 @@ class CategoryList extends Component {
|
|||||||
className="bold-text"
|
className="bold-text"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: isActiveAtThisLevel ? "#2e7d32" : "transparent",
|
color: activeCategoryId === category.id ? "#2e7d32" : "transparent",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
@@ -495,7 +307,7 @@ class CategoryList extends Component {
|
|||||||
className="thin-text"
|
className="thin-text"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "400",
|
fontWeight: "400",
|
||||||
color: isActiveAtThisLevel ? "transparent" : "inherit",
|
color: activeCategoryId === category.id ? "transparent" : "inherit",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -509,8 +321,7 @@ class CategoryList extends Component {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : ( !isMobile && (
|
||||||
level === 1 && !isMobile && (
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
@@ -549,25 +360,7 @@ class CategoryList extends Component {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container maxWidth="lg" sx={{ px: 2 }}>
|
<Container maxWidth="lg" sx={{ px: 2 }}>
|
||||||
{/* Level 1 Categories Row - Always shown */}
|
{renderCategoryRow(categories, false)}
|
||||||
{renderCategoryRow(level1Categories, 1, false)}
|
|
||||||
|
|
||||||
{/* Level 2 Categories Row - Show when level 1 is selected */}
|
|
||||||
{/* DISABLED FOR NOW
|
|
||||||
{level2Categories.length > 0 && (
|
|
||||||
<Box sx={{ mt: 0.5 }}>
|
|
||||||
{renderCategoryRow(level2Categories, 2, false)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Level 3 Categories Row - Show when level 2 is selected */}
|
|
||||||
{/* DISABLED FOR NOW
|
|
||||||
{level3Categories.length > 0 && (
|
|
||||||
<Box sx={{ mt: 0.5 }}>
|
|
||||||
{renderCategoryRow(level3Categories, 3, false)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
*/}
|
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -621,7 +414,7 @@ class CategoryList extends Component {
|
|||||||
<Collapse in={mobileMenuOpen}>
|
<Collapse in={mobileMenuOpen}>
|
||||||
<Box sx={{ pb: 2 }}>
|
<Box sx={{ pb: 2 }}>
|
||||||
{/* Level 1 Categories - Only level shown in mobile menu */}
|
{/* Level 1 Categories - Only level shown in mobile menu */}
|
||||||
{renderCategoryRow(level1Categories, 1, true)}
|
{renderCategoryRow(categories, true)}
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import { Mutex } from 'async-mutex';
|
||||||
|
|
||||||
|
const mutex = new Mutex();
|
||||||
|
|
||||||
|
|
||||||
class CategoryService {
|
class CategoryService {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.get = this.get.bind(this);
|
this.get = this.get.bind(this);
|
||||||
@@ -11,26 +16,37 @@ class CategoryService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(categoryId, language = "de") {
|
async get(categoryId, language = "de") {
|
||||||
const cacheKey = `${categoryId}_${language}`;
|
|
||||||
if (window.categoryCache && window.categoryCache[cacheKey]) {
|
return await mutex.runExclusive(async () => {
|
||||||
return Promise.resolve(window.categoryCache[cacheKey]);
|
console.log("mutex locked");
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
const cacheKey = `${categoryId}_${language}`;
|
||||||
window.socketManager.emit("categoryList", {categoryId: categoryId, language: language}, (response) => {
|
if (window.categoryCache && window.categoryCache[cacheKey]) {
|
||||||
console.log("CategoryService", cacheKey);
|
console.log("mutex unlocked and returning cached value");
|
||||||
if (response.categoryTree) {
|
return Promise.resolve(window.categoryCache[cacheKey]);
|
||||||
if (!window.categoryCache) {
|
}
|
||||||
window.categoryCache = {};
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
window.socketManager.emit("categoryList", {categoryId: categoryId, language: language}, (response) => {
|
||||||
|
console.log("CategoryService", cacheKey);
|
||||||
|
if (response.categoryTree) {
|
||||||
|
if (!window.categoryCache) {
|
||||||
|
window.categoryCache = {};
|
||||||
|
}
|
||||||
|
window.categoryCache[cacheKey] = response.categoryTree;
|
||||||
|
console.log("mutex unlocked and returning new value");
|
||||||
|
resolve(response.categoryTree);
|
||||||
|
} else {
|
||||||
|
console.log("mutex unlocked and returning null");
|
||||||
|
resolve(null);
|
||||||
}
|
}
|
||||||
window.categoryCache[cacheKey] = response.categoryTree;
|
});
|
||||||
resolve(response.categoryTree);
|
|
||||||
} else {
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user