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:
64
src/App.js
64
src/App.js
@@ -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={{
|
||||
@@ -208,10 +244,10 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
|
||||
|
||||
{/* Admin page */}
|
||||
<Route path="/admin" element={<AdminPage/>} />
|
||||
|
||||
|
||||
{/* Admin Users page */}
|
||||
<Route path="/admin/users" element={<UsersPage/>} />
|
||||
|
||||
|
||||
{/* Admin Server Logs page */}
|
||||
<Route path="/admin/logs" element={<ServerLogsPage/>} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
const React = require('react');
|
||||
const {
|
||||
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;
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Container
|
||||
} 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;
|
||||
@@ -1,13 +1,13 @@
|
||||
const React = require('react');
|
||||
const {
|
||||
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;
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Container
|
||||
} 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;
|
||||
@@ -1,20 +1,17 @@
|
||||
const React = require('react');
|
||||
const {
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardMedia,
|
||||
Grid,
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
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;
|
||||
@@ -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;
|
||||
292
src/pages/PrerenderTestPage.js
Normal file
292
src/pages/PrerenderTestPage.js
Normal 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;
|
||||
Reference in New Issue
Block a user