feat: add prerendering support and improve component imports

- Introduced a new PrerenderHome component for development testing.
- Updated App.js to conditionally render the PrerenderHome based on the route.
- Refactored several components to use ES6 import/export syntax for consistency.
- Enhanced AppContent to manage dynamic theming and added a development-only FAB for prerender testing.
- Minor adjustments to props in PrerenderCategory for clarity.
This commit is contained in:
sebseb7
2025-08-31 06:04:55 +02:00
parent 2ac9baada0
commit 6a144f7441
7 changed files with 388 additions and 53 deletions

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, lazy, Suspense } from "react";
import { createTheme } from "@mui/material/styles";
import {
Routes,
Route,
@@ -14,6 +15,7 @@ import Fab from "@mui/material/Fab";
import Tooltip from "@mui/material/Tooltip";
import SmartToyIcon from "@mui/icons-material/SmartToy";
import PaletteIcon from "@mui/icons-material/Palette";
import ScienceIcon from "@mui/icons-material/Science";
import { CarouselProvider } from "./contexts/CarouselContext.js";
import config from "./config.js";
@@ -60,11 +62,13 @@ const ThcTestPage = lazy(() => import(/* webpackChunkName: "thc-test" */ "./page
// Lazy load payment success page
const PaymentSuccess = lazy(() => import(/* webpackChunkName: "payment" */ "./components/PaymentSuccess.js"));
// Lazy load prerender component (development testing only)
const PrerenderHome = lazy(() => import(/* webpackChunkName: "prerender-home" */ "./PrerenderHome.js"));
// Import theme from separate file to reduce main bundle size
import defaultTheme from "./theme.js";
// Lazy load theme customizer for development only
const ThemeCustomizerDialog = lazy(() => import(/* webpackChunkName: "theme-customizer" */ "./components/ThemeCustomizerDialog.js"));
import { createTheme } from "@mui/material/styles";
const deleteMessages = () => {
console.log("Deleting messages");
@@ -72,13 +76,16 @@ const deleteMessages = () => {
};
const AppContent = ({ currentTheme, onThemeChange }) => {
const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
// State to manage chat visibility
const [isChatOpen, setChatOpen] = useState(false);
const [authVersion, setAuthVersion] = useState(0);
// @note Theme customizer state for development mode
const [isThemeCustomizerOpen, setThemeCustomizerOpen] = useState(false);
// Remove duplicate theme state since it's passed as prop
// const [dynamicTheme, setDynamicTheme] = useState(createTheme(defaultTheme));
// Get current location
const location = useLocation();
const navigate = useNavigate();
@@ -138,6 +145,35 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
// Check if we're in development mode
const isDevelopment = process.env.NODE_ENV === "development";
// Check if current route is a prerender test route
const isPrerenderTestRoute = isDevelopment && location.pathname === "/prerenderTest/home";
// If it's a prerender test route, render it standalone without app layout
if (isPrerenderTestRoute) {
return (
<LanguageProvider i18n={i18n}>
<ThemeProvider theme={dynamicTheme}>
<CssBaseline />
<Suspense fallback={
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minHeight: "100vh",
}}
>
<CircularProgress color="primary" />
</Box>
}>
<PrerenderHome />
</Suspense>
</ThemeProvider>
</LanguageProvider>
);
}
// Regular app layout for all other routes
return (
<Box
sx={{
@@ -312,6 +348,25 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
</Tooltip>
)}
{/* Development-only Prerender Test FAB */}
{isDevelopment && (
<Tooltip title="Test Prerender Home" placement="left">
<Fab
color="warning"
aria-label="prerender test"
size="small"
sx={{
position: "fixed",
bottom: 31,
right: 75,
}}
onClick={() => navigate('/prerenderTest/home')}
>
<ScienceIcon sx={{ fontSize: "1.2rem" }} />
</Fab>
</Tooltip>
)}
{/* Development-only Theme Customizer Dialog */}
{isDevelopment && isThemeCustomizerOpen && (
<Suspense fallback={
@@ -361,6 +416,7 @@ const App = () => {
<CssBaseline />
<AppContent
currentTheme={currentTheme}
dynamicTheme={dynamicTheme}
onThemeChange={handleThemeChange}
/>
</ThemeProvider>

View File

@@ -3,7 +3,7 @@ import { Box, AppBar, Toolbar, Container, Typography, Grid, Card, CardMedia, Car
import Footer from './components/Footer.js';
import { Logo, SearchBar, CategoryList } from './components/header/index.js';
const PrerenderCategory = ({ categoryId, categoryName, categorySeoName, productData }) => {
const PrerenderCategory = ({ categoryId, categoryName, categorySeoName: _categorySeoName, productData }) => {
const products = productData?.products || [];
return (

View File

@@ -1,13 +1,12 @@
const React = require('react');
const {
import React from 'react';
import {
Box,
AppBar,
Toolbar,
Container
} = require('@mui/material');
const Footer = require('./components/Footer.js').default;
const { Logo, CategoryList } = require('./components/header/index.js');
const MainPageLayout = require('./components/MainPageLayout.js').default;
} from '@mui/material';
import Footer from './components/Footer.js';
import { Logo, CategoryList } from './components/header/index.js';
class PrerenderHome extends React.Component {
@@ -65,4 +64,4 @@ class PrerenderHome extends React.Component {
}
}
module.exports = { default: PrerenderHome };
export default PrerenderHome;

View File

@@ -1,13 +1,13 @@
const React = require('react');
const {
import React from 'react';
import {
Box,
AppBar,
Toolbar,
Container
} = require('@mui/material');
const Footer = require('./components/Footer.js').default;
const { Logo } = require('./components/header/index.js');
const NotFound404 = require('./pages/NotFound404.js').default;
} from '@mui/material';
import Footer from './components/Footer.js';
import { Logo } from './components/header/index.js';
import NotFound404 from './pages/NotFound404.js';
class PrerenderNotFound extends React.Component {
render() {
@@ -89,4 +89,4 @@ class PrerenderNotFound extends React.Component {
}
}
module.exports = { default: PrerenderNotFound };
export default PrerenderNotFound;

View File

@@ -1,20 +1,17 @@
const React = require('react');
const {
import React from 'react';
import {
Container,
Typography,
Card,
CardMedia,
Grid,
Box,
Chip,
Stack,
AppBar,
Toolbar,
Button
} = require('@mui/material');
const Footer = require('./components/Footer.js').default;
const { Logo } = require('./components/header/index.js');
const ProductImage = require('./components/ProductImage.js').default;
} from '@mui/material';
import Footer from './components/Footer.js';
import { Logo } from './components/header/index.js';
import ProductImage from './components/ProductImage.js';
// Utility function to clean product names by removing trailing number in parentheses
const cleanProductName = (name) => {
@@ -46,9 +43,6 @@ class PrerenderProduct extends React.Component {
const product = productData.product;
const attributes = productData.attributes || [];
const mainImage = product.pictureList && product.pictureList.trim()
? `/assets/images/prod${product.pictureList.split(',')[0].trim()}.jpg`
: '/assets/images/nopicture.jpg';
// Format price with tax
const priceWithTax = new Intl.NumberFormat("de-DE", {
@@ -563,4 +557,4 @@ class PrerenderProduct extends React.Component {
}
}
module.exports = { default: PrerenderProduct };
export default PrerenderProduct;

View File

@@ -1,17 +1,11 @@
const React = require('react');
const {
Box,
AppBar,
Toolbar,
Container,
import React from 'react';
import {
Typography,
List,
ListItem,
ListItemText
} = require('@mui/material');
const Footer = require('./components/Footer.js').default;
const { Logo, CategoryList } = require('./components/header/index.js');
const LegalPage = require('./pages/LegalPage.js').default;
} from '@mui/material';
import LegalPage from './pages/LegalPage.js';
const PrerenderSitemap = ({ categoryData }) => {
// Process category data to flatten the hierarchy
@@ -134,4 +128,4 @@ const PrerenderSitemap = ({ categoryData }) => {
return React.createElement(LegalPage, { title: 'Sitemap', content: content });
};
module.exports = { default: PrerenderSitemap };
export default PrerenderSitemap;

View File

@@ -0,0 +1,292 @@
import React, { useState, lazy, Suspense } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import {
Box,
Container,
Typography,
Paper,
Button,
Grid,
Card,
CardContent,
CardActions,
Chip,
Alert,
CircularProgress
} from '@mui/material';
import { styled } from '@mui/material/styles';
import ScienceIcon from '@mui/icons-material/Science';
// Lazy load prerender components for testing
const PrerenderHome = lazy(() => import('../PrerenderHome.js'));
const PrerenderCategory = lazy(() => import('../PrerenderCategory.js'));
const PrerenderProduct = lazy(() => import('../PrerenderProduct.js'));
const PrerenderKonfigurator = lazy(() => import('../PrerenderKonfigurator.js'));
const PrerenderProfile = lazy(() => import('../PrerenderProfile.js'));
const PrerenderSitemap = lazy(() => import('../PrerenderSitemap.js'));
const StyledPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(3),
margin: theme.spacing(2, 0),
backgroundColor: theme.palette.background.paper,
}));
const ComponentCard = styled(Card)(({ theme }) => ({
height: '100%',
display: 'flex',
flexDirection: 'column',
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: theme.shadows[4],
},
}));
const PrerenderTestPage = () => {
const { componentName } = useParams();
const navigate = useNavigate();
const [selectedComponent, setSelectedComponent] = useState(componentName || 'home');
const [componentProps, setComponentProps] = useState({});
// Available components for testing - memoized to prevent re-renders
const availableComponents = React.useMemo(() => ({
// Prerender components
home: {
component: PrerenderHome,
name: 'Home Page',
description: 'Main homepage component',
category: 'Prerender',
props: {}
},
category: {
component: PrerenderCategory,
name: 'Category Page',
description: 'Category listing with products',
category: 'Prerender',
props: {
categoryId: 209,
categoryName: 'Test Category',
categorySeoName: 'test-category',
productData: null
}
},
product: {
component: PrerenderProduct,
name: 'Product Page',
description: 'Individual product detail page',
category: 'Prerender',
props: {
productData: {
product: {
id: 1,
name: 'Test Product',
seoName: 'test-product',
description: 'This is a test product for prerender testing',
price: 99.99
}
}
}
},
konfigurator: {
component: PrerenderKonfigurator,
name: 'Konfigurator',
description: 'Grow tent configurator',
category: 'Prerender',
props: {}
},
profile: {
component: PrerenderProfile,
name: 'Profile Page',
description: 'User profile page',
category: 'Prerender',
props: {}
},
sitemap: {
component: PrerenderSitemap,
name: 'Sitemap',
description: 'Site navigation map',
category: 'Prerender',
props: {
categoryData: null
}
}
}), []);
React.useEffect(() => {
if (componentName && availableComponents[componentName]) {
setSelectedComponent(componentName);
setComponentProps(availableComponents[componentName].props);
}
}, [componentName, availableComponents]);
const handleComponentChange = (componentKey) => {
setSelectedComponent(componentKey);
setComponentProps(availableComponents[componentKey].props);
navigate(`/prerenderTest/${componentKey}`);
};
const renderComponent = () => {
const componentConfig = availableComponents[selectedComponent];
if (!componentConfig) return null;
const Component = componentConfig.component;
return (
<Suspense fallback={
<Box sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '200px'
}}>
<CircularProgress />
</Box>
}>
<Component {...componentProps} />
</Suspense>
);
};
const getCategories = () => {
const categories = {};
Object.keys(availableComponents).forEach(key => {
const category = availableComponents[key].category;
if (!categories[category]) {
categories[category] = [];
}
categories[category].push(key);
});
return categories;
};
const categories = getCategories();
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
<Typography variant="h3" component="h1" gutterBottom align="center">
Prerender Preview Environment
</Typography>
<Typography variant="body1" align="center" sx={{ mb: 4, color: 'text.secondary' }}>
Preview prerender components dynamically during development
</Typography>
<Alert severity="info" sx={{ mb: 3 }}>
This page allows you to preview prerender components in a development environment.
The actual prerender components remain unchanged and are used for static generation.
</Alert>
{/* Component Selection */}
<StyledPaper elevation={2}>
<Typography variant="h5" gutterBottom>
Select Component to Preview
</Typography>
<Grid container spacing={2} sx={{ mb: 3 }}>
{Object.keys(categories).map(category => (
<Grid item xs={12} key={category}>
<Typography variant="h6" sx={{ mb: 2, mt: 2 }}>
{category} Components
</Typography>
<Grid container spacing={2}>
{categories[category].map(componentKey => {
const config = availableComponents[componentKey];
return (
<Grid item xs={12} sm={6} md={4} lg={3} key={componentKey}>
<ComponentCard
onClick={() => handleComponentChange(componentKey)}
sx={{
cursor: 'pointer',
border: selectedComponent === componentKey ? '2px solid' : '1px solid',
borderColor: selectedComponent === componentKey ? 'primary.main' : 'divider'
}}
>
<CardContent sx={{ flexGrow: 1 }}>
<Typography variant="h6" component="div" gutterBottom>
{config.name}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{config.description}
</Typography>
<Chip
label={category}
size="small"
color={category === 'Prerender' ? 'primary' : 'secondary'}
variant="outlined"
/>
</CardContent>
<CardActions>
<Button
size="small"
variant={selectedComponent === componentKey ? 'contained' : 'outlined'}
fullWidth
startIcon={<ScienceIcon />}
>
{selectedComponent === componentKey ? 'Previewing' : 'Preview'}
</Button>
</CardActions>
</ComponentCard>
</Grid>
);
})}
</Grid>
</Grid>
))}
</Grid>
{/* Current Selection Info */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom>
Currently Previewing: {availableComponents[selectedComponent]?.name}
</Typography>
<Typography variant="body2" color="text.secondary">
Component: {selectedComponent} | Category: {availableComponents[selectedComponent]?.category}
</Typography>
</Box>
</StyledPaper>
{/* Component Preview Area */}
<StyledPaper elevation={1}>
<Typography variant="h5" gutterBottom>
Component Preview
</Typography>
<Alert severity="warning" sx={{ mb: 3 }}>
The component below is rendered dynamically for development preview.
In production, this would be prerendered as static HTML.
</Alert>
<Box sx={{
border: '1px solid',
borderColor: 'divider',
borderRadius: 1,
minHeight: '400px',
backgroundColor: 'background.default',
p: 2,
overflow: 'auto'
}}>
{renderComponent()}
</Box>
</StyledPaper>
{/* Navigation */}
<Box sx={{ mt: 4, textAlign: 'center' }}>
<Button
variant="outlined"
onClick={() => navigate('/')}
sx={{ mr: 2 }}
>
Back to Home
</Button>
<Button
variant="contained"
onClick={() => window.location.reload()}
>
Refresh Preview
</Button>
</Box>
</Container>
);
};
export default PrerenderTestPage;