feat: add Categories page with refined layout and translation support
This commit is contained in:
@@ -159,6 +159,7 @@ const Batteriegesetzhinweise =
|
|||||||
const Widerrufsrecht = require("./src/pages/Widerrufsrecht.js").default;
|
const Widerrufsrecht = require("./src/pages/Widerrufsrecht.js").default;
|
||||||
const Sitemap = require("./src/pages/Sitemap.js").default;
|
const Sitemap = require("./src/pages/Sitemap.js").default;
|
||||||
const PrerenderSitemap = require("./src/PrerenderSitemap.js").default;
|
const PrerenderSitemap = require("./src/PrerenderSitemap.js").default;
|
||||||
|
const PrerenderCategoriesPage = require("./src/PrerenderCategoriesPage.js").default;
|
||||||
const AGB = require("./src/pages/AGB.js").default;
|
const AGB = require("./src/pages/AGB.js").default;
|
||||||
const NotFound404 = require("./src/pages/NotFound404.js").default;
|
const NotFound404 = require("./src/pages/NotFound404.js").default;
|
||||||
|
|
||||||
@@ -465,6 +466,13 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
description: "Sitemap page",
|
description: "Sitemap page",
|
||||||
needsCategoryData: true,
|
needsCategoryData: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: PrerenderCategoriesPage,
|
||||||
|
path: "/Kategorien",
|
||||||
|
filename: "Kategorien",
|
||||||
|
description: "Categories page",
|
||||||
|
needsCategoryData: true,
|
||||||
|
},
|
||||||
{ component: AGB, path: "/agb", filename: "agb", description: "AGB page" },
|
{ component: AGB, path: "/agb", filename: "agb", description: "AGB page" },
|
||||||
{ component: NotFound404, path: "/404", filename: "404", description: "404 Not Found page" },
|
{ component: NotFound404, path: "/404", filename: "404", description: "404 Not Found page" },
|
||||||
{
|
{
|
||||||
@@ -559,8 +567,7 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
try {
|
try {
|
||||||
productData = await fetchCategoryProducts(socket, category.id);
|
productData = await fetchCategoryProducts(socket, category.id);
|
||||||
console.log(
|
console.log(
|
||||||
` ✅ Found ${
|
` ✅ Found ${productData.products ? productData.products.length : 0
|
||||||
productData.products ? productData.products.length : 0
|
|
||||||
} products`
|
} products`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -849,7 +856,7 @@ const fetchCategoryDataAndRender = () => {
|
|||||||
|
|
||||||
const socket = io(socketUrl, {
|
const socket = io(socketUrl, {
|
||||||
path: "/socket.io/",
|
path: "/socket.io/",
|
||||||
transports: [ "websocket"],
|
transports: ["websocket"],
|
||||||
reconnection: false,
|
reconnection: false,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
});
|
});
|
||||||
|
|||||||
30
src/App.js
30
src/App.js
@@ -50,6 +50,7 @@ const Datenschutz = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/D
|
|||||||
const AGB = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/AGB.js"));
|
const AGB = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/AGB.js"));
|
||||||
//const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js")); <Route path="/404" element={<NotFound404 />} />
|
//const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js")); <Route path="/404" element={<NotFound404 />} />
|
||||||
const Sitemap = lazy(() => import(/* webpackChunkName: "sitemap" */ "./pages/Sitemap.js"));
|
const Sitemap = lazy(() => import(/* webpackChunkName: "sitemap" */ "./pages/Sitemap.js"));
|
||||||
|
const CategoriesPage = lazy(() => import(/* webpackChunkName: "categories" */ "./pages/CategoriesPage.js"));
|
||||||
const Impressum = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Impressum.js"));
|
const Impressum = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Impressum.js"));
|
||||||
const Batteriegesetzhinweise = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Batteriegesetzhinweise.js"));
|
const Batteriegesetzhinweise = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Batteriegesetzhinweise.js"));
|
||||||
const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Widerrufsrecht.js"));
|
const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Widerrufsrecht.js"));
|
||||||
@@ -260,19 +261,19 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
{/* Category page - Render Content in parallel */}
|
{/* Category page - Render Content in parallel */}
|
||||||
<Route
|
<Route
|
||||||
path="/Kategorie/:categoryId"
|
path="/Kategorie/:categoryId"
|
||||||
element={<Content/>}
|
element={<Content />}
|
||||||
/>
|
/>
|
||||||
{/* Single product page */}
|
{/* Single product page */}
|
||||||
<Route
|
<Route
|
||||||
path="/Artikel/:seoName"
|
path="/Artikel/:seoName"
|
||||||
element={<ProductDetail/>}
|
element={<ProductDetail />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Search page - Render Content in parallel */}
|
{/* Search page - Render Content in parallel */}
|
||||||
<Route path="/search" element={<Content/>} />
|
<Route path="/search" element={<Content />} />
|
||||||
|
|
||||||
{/* Profile page */}
|
{/* Profile page */}
|
||||||
<Route path="/profile" element={<ProfilePage/>} />
|
<Route path="/profile" element={<ProfilePage />} />
|
||||||
|
|
||||||
{/* Payment success page for Mollie redirects */}
|
{/* Payment success page for Mollie redirects */}
|
||||||
<Route path="/payment/success" element={<PaymentSuccess />} />
|
<Route path="/payment/success" element={<PaymentSuccess />} />
|
||||||
@@ -280,22 +281,23 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
{/* Reset password page */}
|
{/* Reset password page */}
|
||||||
<Route
|
<Route
|
||||||
path="/resetPassword"
|
path="/resetPassword"
|
||||||
element={<ResetPassword/>}
|
element={<ResetPassword />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Admin page */}
|
{/* Admin page */}
|
||||||
<Route path="/admin" element={<AdminPage/>} />
|
<Route path="/admin" element={<AdminPage />} />
|
||||||
|
|
||||||
{/* Admin Users page */}
|
{/* Admin Users page */}
|
||||||
<Route path="/admin/users" element={<UsersPage/>} />
|
<Route path="/admin/users" element={<UsersPage />} />
|
||||||
|
|
||||||
{/* Admin Server Logs page */}
|
{/* Admin Server Logs page */}
|
||||||
<Route path="/admin/logs" element={<ServerLogsPage/>} />
|
<Route path="/admin/logs" element={<ServerLogsPage />} />
|
||||||
|
|
||||||
{/* Legal pages */}
|
{/* Legal pages */}
|
||||||
<Route path="/datenschutz" element={<Datenschutz />} />
|
<Route path="/datenschutz" element={<Datenschutz />} />
|
||||||
<Route path="/agb" element={<AGB />} />
|
<Route path="/agb" element={<AGB />} />
|
||||||
<Route path="/sitemap" element={<Sitemap />} />
|
<Route path="/sitemap" element={<Sitemap />} />
|
||||||
|
<Route path="/Kategorien" element={<CategoriesPage />} />
|
||||||
<Route path="/impressum" element={<Impressum />} />
|
<Route path="/impressum" element={<Impressum />} />
|
||||||
<Route
|
<Route
|
||||||
path="/batteriegesetzhinweise"
|
path="/batteriegesetzhinweise"
|
||||||
@@ -304,7 +306,7 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
|
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
|
||||||
|
|
||||||
{/* Grow Tent Configurator */}
|
{/* Grow Tent Configurator */}
|
||||||
<Route path="/Konfigurator" element={<GrowTentKonfigurator/>} />
|
<Route path="/Konfigurator" element={<GrowTentKonfigurator />} />
|
||||||
|
|
||||||
{/* Separate pages that are truly different */}
|
{/* Separate pages that are truly different */}
|
||||||
<Route path="/presseverleih" element={<PresseverleihPage />} />
|
<Route path="/presseverleih" element={<PresseverleihPage />} />
|
||||||
@@ -457,11 +459,11 @@ const App = () => {
|
|||||||
<ProductContextProvider>
|
<ProductContextProvider>
|
||||||
<CategoryContextProvider>
|
<CategoryContextProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppContent
|
<AppContent
|
||||||
currentTheme={currentTheme}
|
currentTheme={currentTheme}
|
||||||
dynamicTheme={dynamicTheme}
|
dynamicTheme={dynamicTheme}
|
||||||
onThemeChange={handleThemeChange}
|
onThemeChange={handleThemeChange}
|
||||||
/>
|
/>
|
||||||
</CategoryContextProvider>
|
</CategoryContextProvider>
|
||||||
</ProductContextProvider>
|
</ProductContextProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
118
src/PrerenderCategoriesPage.js
Normal file
118
src/PrerenderCategoriesPage.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import LegalPage from './pages/LegalPage.js';
|
||||||
|
import CategoryBox from './components/CategoryBox.js';
|
||||||
|
|
||||||
|
const PrerenderCategoriesPage = ({ categoryData }) => {
|
||||||
|
// Helper function to recursively collect all categories from the tree
|
||||||
|
const collectAllCategories = (categoryNode, categories = [], level = 0) => {
|
||||||
|
if (!categoryNode) return categories;
|
||||||
|
|
||||||
|
// Add current category (skip root category 209)
|
||||||
|
if (categoryNode.id !== 209 && categoryNode.seoName) {
|
||||||
|
categories.push({
|
||||||
|
id: categoryNode.id,
|
||||||
|
name: categoryNode.name,
|
||||||
|
seoName: categoryNode.seoName,
|
||||||
|
level: level
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add children
|
||||||
|
if (categoryNode.children) {
|
||||||
|
for (const child of categoryNode.children) {
|
||||||
|
collectAllCategories(child, categories, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The categoryData passed prop is the root tree (id: 209)
|
||||||
|
const rootTree = categoryData;
|
||||||
|
|
||||||
|
const renderLevel1Section = (l1Node) => {
|
||||||
|
// Collect all descendants (excluding the L1 node itself, which collectAllCategories would include first)
|
||||||
|
const descendants = collectAllCategories(l1Node).slice(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
key={l1Node.id}
|
||||||
|
elevation={1}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
mb: 3,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
|
alignItems: { xs: 'flex-start', md: 'flex-start' },
|
||||||
|
gap: 3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Level 1 Header/Box */}
|
||||||
|
<Box sx={{
|
||||||
|
minWidth: '150px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1
|
||||||
|
}}>
|
||||||
|
<CategoryBox
|
||||||
|
id={l1Node.id}
|
||||||
|
name={l1Node.name}
|
||||||
|
seoName={l1Node.seoName}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 4,
|
||||||
|
width: '150px',
|
||||||
|
height: '150px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Descendants area */}
|
||||||
|
<Box sx={{ flex: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 2
|
||||||
|
}}>
|
||||||
|
{descendants.map((cat) => (
|
||||||
|
<CategoryBox
|
||||||
|
key={cat.id}
|
||||||
|
id={cat.id}
|
||||||
|
name={cat.name}
|
||||||
|
seoName={cat.seoName}
|
||||||
|
sx={{
|
||||||
|
width: '100px',
|
||||||
|
height: '100px',
|
||||||
|
minWidth: '100px',
|
||||||
|
minHeight: '100px',
|
||||||
|
boxShadow: 1,
|
||||||
|
fontSize: '0.9rem'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Box>
|
||||||
|
<Box>
|
||||||
|
{rootTree && rootTree.children && rootTree.children.map((child) => (
|
||||||
|
renderLevel1Section(child)
|
||||||
|
))}
|
||||||
|
{(!rootTree || !rootTree.children || rootTree.children.length === 0) && (
|
||||||
|
<Typography>Keine Kategorien gefunden.</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <LegalPage title="Kategorien" content={content} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrerenderCategoriesPage;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
@@ -60,9 +61,9 @@ class SharedCarousel extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
||||||
if(prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
if (prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
||||||
this.setState({ categories: [] },() => {
|
this.setState({ categories: [] }, () => {
|
||||||
window.categoryService.get(209,this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
window.categoryService.get(209, this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
if (response.children && response.children.length > 0) {
|
if (response.children && response.children.length > 0) {
|
||||||
this.originalCategories = response.children;
|
this.originalCategories = response.children;
|
||||||
@@ -268,25 +269,41 @@ class SharedCarousel extends React.Component {
|
|||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
const { categories } = this.state;
|
const { categories } = this.state;
|
||||||
|
|
||||||
if(!categories || categories.length === 0) {
|
if (!categories || categories.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
<Typography
|
<Box
|
||||||
variant="h4"
|
component={Link}
|
||||||
component="h1"
|
to="/Kategorien"
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
display: "flex",
|
||||||
fontFamily: "SwashingtonCP",
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textDecoration: "none",
|
||||||
color: "primary.main",
|
color: "primary.main",
|
||||||
textAlign: "center",
|
mb: 2,
|
||||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateX(5px)",
|
||||||
|
color: "primary.dark"
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('navigation.categories')}
|
<Typography
|
||||||
</Typography>
|
variant="h4"
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "SwashingtonCP",
|
||||||
|
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('navigation.categories')}
|
||||||
|
</Typography>
|
||||||
|
<ChevronRight sx={{ fontSize: "2.5rem", ml: 1 }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="carousel-wrapper"
|
className="carousel-wrapper"
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ class CategoryList extends Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
||||||
if(prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
if (prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
||||||
this.setState({
|
this.setState({
|
||||||
categories: [],
|
categories: [],
|
||||||
activeCategoryId: null
|
activeCategoryId: null
|
||||||
},() => {
|
}, () => {
|
||||||
window.categoryService.get(209,this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
window.categoryService.get(209, this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
if (response.children && response.children.length > 0) {
|
if (response.children && response.children.length > 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -69,14 +69,14 @@ class CategoryList extends Component {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
|
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
|
||||||
this.setLevel1CategoryId(this.props.activeCategoryId);
|
this.setLevel1CategoryId(this.props.activeCategoryId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setLevel1CategoryId = (input) => {
|
setLevel1CategoryId = (input) => {
|
||||||
if(input) {
|
if (input) {
|
||||||
const language = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
const language = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
||||||
const categoryTreeCache = window.categoryService.getSync(209, language);
|
const categoryTreeCache = window.categoryService.getSync(209, language);
|
||||||
|
|
||||||
@@ -173,141 +173,141 @@ class CategoryList extends Component {
|
|||||||
py: 0.5, // Add vertical padding to prevent border clipping
|
py: 0.5, // Add vertical padding to prevent border clipping
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/"
|
to="/"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Zur Startseite"
|
aria-label="Zur Startseite"
|
||||||
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
mx: isMobile ? 0 : 0.5,
|
mx: isMobile ? 0 : 0.5,
|
||||||
my: 0.25,
|
my: 0.25,
|
||||||
minWidth: isMobile ? "100%" : "auto",
|
minWidth: isMobile ? "100%" : "auto",
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
justifyContent: isMobile ? "flex-start" : "center",
|
justifyContent: isMobile ? "flex-start" : "center",
|
||||||
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",
|
||||||
...(activeCategoryId === null && {
|
...(activeCategoryId === null && {
|
||||||
bgcolor: "#fff",
|
bgcolor: "#fff",
|
||||||
textShadow: "none",
|
textShadow: "none",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
}),
|
}),
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
bgcolor: "#fff",
|
bgcolor: "#fff",
|
||||||
textShadow: "none",
|
textShadow: "none",
|
||||||
"& .MuiSvgIcon-root": {
|
"& .MuiSvgIcon-root": {
|
||||||
color: "#2e7d32 !important",
|
color: "#2e7d32 !important",
|
||||||
},
|
|
||||||
"& .bold-text": {
|
|
||||||
color: "#2e7d32 !important",
|
|
||||||
},
|
|
||||||
"& .thin-text": {
|
|
||||||
color: "transparent !important",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
"& .bold-text": {
|
||||||
>
|
color: "#2e7d32 !important",
|
||||||
<HomeIcon sx={{
|
},
|
||||||
fontSize: "1rem",
|
"& .thin-text": {
|
||||||
mr: isMobile ? 1 : 0,
|
color: "transparent !important",
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
},
|
||||||
}} />
|
},
|
||||||
{isMobile && (
|
}}
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
>
|
||||||
{/* Bold text (always rendered to set width) */}
|
<HomeIcon sx={{
|
||||||
<Box
|
fontSize: "1rem",
|
||||||
className="bold-text"
|
mr: isMobile ? 1 : 0,
|
||||||
sx={{
|
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
||||||
fontWeight: "bold",
|
}} />
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
{isMobile && (
|
||||||
position: "relative",
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
zIndex: 2,
|
{/* Bold text (always rendered to set width) */}
|
||||||
}}
|
<Box
|
||||||
>
|
className="bold-text"
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
sx={{
|
||||||
</Box>
|
fontWeight: "bold",
|
||||||
{/* Thin text (positioned on top) */}
|
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
||||||
<Box
|
position: "relative",
|
||||||
className="thin-text"
|
zIndex: 2,
|
||||||
sx={{
|
}}
|
||||||
fontWeight: "400",
|
>
|
||||||
color: activeCategoryId === null ? "transparent" : "inherit",
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
{/* Thin text (positioned on top) */}
|
||||||
</Button>
|
<Box
|
||||||
|
className="thin-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "400",
|
||||||
|
color: activeCategoryId === null ? "transparent" : "inherit",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/Kategorie/neu"
|
to="/Kategorie/neu"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Neuheiten"
|
aria-label="Neuheiten"
|
||||||
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
mx: isMobile ? 0 : 0.5,
|
mx: isMobile ? 0 : 0.5,
|
||||||
my: 0.25,
|
my: 0.25,
|
||||||
minWidth: isMobile ? "100%" : "auto",
|
minWidth: isMobile ? "100%" : "auto",
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
justifyContent: isMobile ? "flex-start" : "center",
|
justifyContent: isMobile ? "flex-start" : "center",
|
||||||
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"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FiberNewIcon sx={{
|
<FiberNewIcon sx={{
|
||||||
fontSize: "1rem",
|
fontSize: "1rem",
|
||||||
mr: isMobile ? 1 : 0
|
mr: isMobile ? 1 : 0
|
||||||
}} />
|
}} />
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
{/* Bold text (always rendered to set width) */}
|
{/* Bold text (always rendered to set width) */}
|
||||||
<Box
|
<Box
|
||||||
className="bold-text"
|
className="bold-text"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: "transparent",
|
color: "transparent",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
||||||
</Box>
|
|
||||||
{/* Thin text (positioned on top) */}
|
|
||||||
<Box
|
|
||||||
className="thin-text"
|
|
||||||
sx={{
|
|
||||||
fontWeight: "400",
|
|
||||||
color: "inherit",
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
{/* Thin text (positioned on top) */}
|
||||||
</Button>
|
<Box
|
||||||
|
className="thin-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "400",
|
||||||
|
color: "inherit",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
{categories.length > 0 ? (
|
{categories.length > 0 ? (
|
||||||
@@ -385,100 +385,100 @@ class CategoryList extends Component {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
) : ( !isMobile && (
|
) : (!isMobile && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
color="inherit"
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "33px", // Match small button height
|
|
||||||
px: 1,
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
opacity: 0.9,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
to="/Konfigurator"
|
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
|
||||||
aria-label="Zur Startseite"
|
|
||||||
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
|
||||||
sx={{
|
sx={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "33px", // Match small button height
|
||||||
|
px: 1,
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
textTransform: "none",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
mx: isMobile ? 0 : 0.5,
|
|
||||||
my: 0.25,
|
|
||||||
minWidth: isMobile ? "100%" : "auto",
|
|
||||||
borderRadius: 1,
|
|
||||||
justifyContent: isMobile ? "flex-start" : "center",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
|
||||||
position: "relative",
|
|
||||||
...(activeCategoryId === null && {
|
|
||||||
bgcolor: "#fff",
|
|
||||||
textShadow: "none",
|
|
||||||
opacity: 1,
|
|
||||||
}),
|
|
||||||
"&:hover": {
|
|
||||||
opacity: 1,
|
|
||||||
bgcolor: "#fff",
|
|
||||||
textShadow: "none",
|
|
||||||
"& .MuiSvgIcon-root": {
|
|
||||||
color: "#2e7d32 !important",
|
|
||||||
},
|
|
||||||
"& .bold-text": {
|
|
||||||
color: "#2e7d32 !important",
|
|
||||||
},
|
|
||||||
"& .thin-text": {
|
|
||||||
color: "transparent !important",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SettingsIcon sx={{
|
|
||||||
fontSize: "1rem",
|
</Typography>
|
||||||
mr: isMobile ? 1 : 0,
|
)
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
)}
|
||||||
}} />
|
<Button
|
||||||
{isMobile && (
|
component={Link}
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
to="/Konfigurator"
|
||||||
{/* Bold text (always rendered to set width) */}
|
color="inherit"
|
||||||
<Box
|
size="small"
|
||||||
className="bold-text"
|
aria-label="Zur Startseite"
|
||||||
sx={{
|
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
||||||
fontWeight: "bold",
|
sx={{
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
fontSize: "0.75rem",
|
||||||
position: "relative",
|
textTransform: "none",
|
||||||
zIndex: 2,
|
whiteSpace: "nowrap",
|
||||||
}}
|
opacity: 0.9,
|
||||||
>
|
mx: isMobile ? 0 : 0.5,
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
my: 0.25,
|
||||||
</Box>
|
minWidth: isMobile ? "100%" : "auto",
|
||||||
{/* Thin text (positioned on top) */}
|
borderRadius: 1,
|
||||||
<Box
|
justifyContent: isMobile ? "flex-start" : "center",
|
||||||
className="thin-text"
|
transition: "all 0.2s ease",
|
||||||
sx={{
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
||||||
fontWeight: "400",
|
position: "relative",
|
||||||
color: activeCategoryId === null ? "transparent" : "inherit",
|
...(activeCategoryId === null && {
|
||||||
position: "absolute",
|
bgcolor: "#fff",
|
||||||
top: 0,
|
textShadow: "none",
|
||||||
left: 0,
|
opacity: 1,
|
||||||
zIndex: 1,
|
}),
|
||||||
}}
|
"&:hover": {
|
||||||
>
|
opacity: 1,
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
bgcolor: "#fff",
|
||||||
</Box>
|
textShadow: "none",
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
color: "#2e7d32 !important",
|
||||||
|
},
|
||||||
|
"& .bold-text": {
|
||||||
|
color: "#2e7d32 !important",
|
||||||
|
},
|
||||||
|
"& .thin-text": {
|
||||||
|
color: "transparent !important",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsIcon sx={{
|
||||||
|
fontSize: "1rem",
|
||||||
|
mr: isMobile ? 1 : 0,
|
||||||
|
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
||||||
|
}} />
|
||||||
|
{isMobile && (
|
||||||
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
|
{/* Bold text (always rendered to set width) */}
|
||||||
|
<Box
|
||||||
|
className="bold-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
{/* Thin text (positioned on top) */}
|
||||||
</Button>
|
<Box
|
||||||
|
className="thin-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "400",
|
||||||
|
color: activeCategoryId === null ? "transparent" : "inherit",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -545,7 +545,7 @@ class CategoryList extends Component {
|
|||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)"
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)"
|
||||||
}}>
|
}}>
|
||||||
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
|
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
||||||
|
|||||||
231
src/pages/CategoriesPage.js
Normal file
231
src/pages/CategoriesPage.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import LegalPage from './LegalPage.js';
|
||||||
|
import CategoryBox from '../components/CategoryBox.js';
|
||||||
|
import { withI18n } from '../i18n/withTranslation.js';
|
||||||
|
|
||||||
|
// Helper function to recursively collect all categories from the tree
|
||||||
|
const collectAllCategories = (categoryNode, categories = [], level = 0) => {
|
||||||
|
if (!categoryNode) return categories;
|
||||||
|
|
||||||
|
// Add current category (skip root category 209)
|
||||||
|
if (categoryNode.id !== 209 && categoryNode.seoName) {
|
||||||
|
categories.push({
|
||||||
|
id: categoryNode.id,
|
||||||
|
name: categoryNode.name,
|
||||||
|
seoName: categoryNode.seoName,
|
||||||
|
level: level
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add children
|
||||||
|
if (categoryNode.children) {
|
||||||
|
for (const child of categoryNode.children) {
|
||||||
|
collectAllCategories(child, categories, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for cached data - handle both browser and prerender environments
|
||||||
|
const getProductCache = () => {
|
||||||
|
if (typeof window !== "undefined" && window.productCache) {
|
||||||
|
return window.productCache;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof global !== "undefined" &&
|
||||||
|
global.window &&
|
||||||
|
global.window.productCache
|
||||||
|
) {
|
||||||
|
return global.window.productCache;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize categories from cache if available (for prerendering)
|
||||||
|
const initializeCategoryTree = (language = 'de') => {
|
||||||
|
// Try synchronous get from service first if available
|
||||||
|
if (typeof window !== "undefined" && window.categoryService) {
|
||||||
|
const syncData = window.categoryService.getSync(209, language);
|
||||||
|
if (syncData) return syncData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const productCache = getProductCache();
|
||||||
|
// Fallback to productCache checks (mostly for prerender context if service isn't init)
|
||||||
|
const cacheKey = `categoryTree_209_${language}`; // Note: Service uses simpler keys, might mismatch if strictly relying on this
|
||||||
|
|
||||||
|
// Check old style cache just in case
|
||||||
|
if (productCache && productCache[cacheKey]) {
|
||||||
|
const cached = productCache[cacheKey];
|
||||||
|
if (cached.categoryTree) return cached.categoryTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CategoriesPage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Use languageContext if available, otherwise fallback to i18n or 'de'
|
||||||
|
const currentLanguage = props.languageContext?.currentLanguage || props.i18n?.language || 'de';
|
||||||
|
const initialTree = initializeCategoryTree(currentLanguage);
|
||||||
|
|
||||||
|
console.log("CategoriesPage constructor: currentLanguage =", currentLanguage);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
categoryTree: initialTree,
|
||||||
|
loading: !initialTree
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
|
||||||
|
|
||||||
|
// If we don't have data yet, or if we want to ensure freshness/socket connection
|
||||||
|
if (!this.state.categoryTree) {
|
||||||
|
this.fetchCategories(currentLanguage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
|
||||||
|
const prevLanguage = prevProps.languageContext?.currentLanguage || prevProps.i18n?.language || 'de';
|
||||||
|
|
||||||
|
if (currentLanguage !== prevLanguage) {
|
||||||
|
console.log(`CategoriesPage: Language changed from ${prevLanguage} to ${currentLanguage}. Refetching.`);
|
||||||
|
this.setState({ loading: true, categoryTree: [] }); // Clear tree to force re-render/loading state
|
||||||
|
this.fetchCategories(currentLanguage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCategories = (language) => {
|
||||||
|
// Use categoryService which handles caching and translated vs untranslated responses correctly
|
||||||
|
console.log(`CategoriesPage: Fetching categories for ${language} using categoryService`);
|
||||||
|
|
||||||
|
window.categoryService.get(209, language).then((tree) => {
|
||||||
|
if (tree) {
|
||||||
|
this.setState({
|
||||||
|
categoryTree: tree,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch categories via service');
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Error in categoryService:", err);
|
||||||
|
this.setState({ loading: false });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderLevel1Section = (l1Node) => {
|
||||||
|
// Collect all descendants (excluding the L1 node itself, which collectAllCategories would include first)
|
||||||
|
const descendants = collectAllCategories(l1Node).slice(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
key={l1Node.id}
|
||||||
|
elevation={1}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
mb: 3,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
|
alignItems: { xs: 'flex-start', md: 'flex-start' },
|
||||||
|
gap: 3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Level 1 Header/Box */}
|
||||||
|
<Box sx={{
|
||||||
|
minWidth: '150px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1
|
||||||
|
}}>
|
||||||
|
<CategoryBox
|
||||||
|
id={l1Node.id}
|
||||||
|
name={l1Node.name}
|
||||||
|
seoName={l1Node.seoName}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 4,
|
||||||
|
width: '150px',
|
||||||
|
height: '150px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
display: { xs: 'block', md: 'none' } // Only show text below box on mobile if needed, or rely on box text
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Box already has text, so maybe no extra text needed here */}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Descendants area */}
|
||||||
|
<Box sx={{ flex: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 2
|
||||||
|
}}>
|
||||||
|
{descendants.map((cat) => (
|
||||||
|
<CategoryBox
|
||||||
|
key={cat.id}
|
||||||
|
id={cat.id}
|
||||||
|
name={cat.name}
|
||||||
|
seoName={cat.seoName}
|
||||||
|
sx={{
|
||||||
|
width: '100px',
|
||||||
|
height: '100px',
|
||||||
|
minWidth: '100px',
|
||||||
|
minHeight: '100px',
|
||||||
|
boxShadow: 1,
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
'&:hover': { transform: 'scale(1.05)', boxShadow: 3 },
|
||||||
|
fontSize: '0.9rem' // Smaller text for smaller boxes
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
const { categoryTree, loading } = this.state;
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Box>
|
||||||
|
{loading ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
{categoryTree && categoryTree.children && categoryTree.children.map((child) => (
|
||||||
|
this.renderLevel1Section(child)
|
||||||
|
))}
|
||||||
|
{(!categoryTree || !categoryTree.children || categoryTree.children.length === 0) && (
|
||||||
|
<Typography>Keine Kategorien gefunden.</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <LegalPage title={t ? t('navigation.categories') : 'Kategorien'} content={content} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(CategoriesPage);
|
||||||
Reference in New Issue
Block a user