Compare commits

...

2 Commits

Author SHA1 Message Date
sebseb7
7a8d07ffc3 feat: improve product data handling in ProductDetailPage for better localization support
- Updated ProductDetailPage to utilize translated product data when available, enhancing localization.
- Adjusted caching logic to store translated products and their attributes.
- Ensured that component images and related data are loaded from the correct product source, improving user experience.
2025-08-06 08:05:32 +02:00
sebseb7
09cd68c144 feat: enhance ProductDetailPage with language context support for product view requests
- Updated the ProductDetailPage to include the current language context when emitting product view requests via WebSocket.
- Added logic to determine the appropriate language setting, improving localization and user experience.
- Enhanced debugging output for better tracking of language-related issues.
2025-08-06 07:46:16 +02:00
2 changed files with 25 additions and 276 deletions

View File

@@ -294,17 +294,23 @@ class ProductDetailPage extends Component {
}));
console.log('loadKomponent', id, count);
const currentLanguage = this.props.languageContext?.currentLanguage
const currentLanguage2 = this.props.i18n?.language || 'de';
console.log('debuglanguage', currentLanguage, currentLanguage2);
window.socketManager.emit(
"getProductView",
{ articleId: id },
{ articleId: id, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true},
(res) => {
if (res.success) {
// Use translated product if available, otherwise use original product
const productData = res.translatedProduct || res.product;
// Cache the successful response
window.productDetailCache[id] = res.product;
window.productDetailCache[id] = productData;
// Load komponent image if available
if (res.product.pictureList) {
this.loadKomponentImage(id, res.product.pictureList);
if (productData.pictureList) {
this.loadKomponentImage(id, productData.pictureList);
}
// Update state with loaded data
@@ -312,7 +318,7 @@ class ProductDetailPage extends Component {
const newKomponentenData = {
...prevState.komponentenData,
[id]: {
...res.product,
...productData,
count: parseInt(count),
loading: false,
loaded: true
@@ -406,20 +412,25 @@ class ProductDetailPage extends Component {
loadProductData = () => {
console.log('loadProductData', this.props.seoName);
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
console.log('debuglanguage', currentLanguage);
window.socketManager.emit(
"getProductView",
{ seoName: this.props.seoName },
{ seoName: this.props.seoName, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true},
(res) => {
if (res.success) {
res.product.seoName = this.props.seoName;
// Use translated product if available, otherwise use original product
const productData = res.translatedProduct || res.product;
productData.seoName = this.props.seoName;
// Initialize cache if it doesn't exist
if (!window.productDetailCache) {
window.productDetailCache = {};
}
// Cache the complete response data (product + attributes)
window.productDetailCache[this.props.seoName] = res;
// Cache the complete response data (product + attributes) - cache the response with translated product
const cacheData = { ...res, product: productData };
window.productDetailCache[this.props.seoName] = cacheData;
// Clean up prerender fallback since we now have real data
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
@@ -428,15 +439,15 @@ class ProductDetailPage extends Component {
}
const komponenten = [];
if(res.product.komponenten) {
for(const komponent of res.product.komponenten.split(",")) {
if(productData.komponenten) {
for(const komponent of productData.komponenten.split(",")) {
// Handle both "x" and "×" as separators
const [id, count] = komponent.split(/[x×]/);
komponenten.push({id: id.trim(), count: count.trim()});
}
}
this.setState({
product: res.product,
product: productData,
loading: false,
upgrading: false, // Clear upgrading state since we now have complete data
error: null,

View File

@@ -396,15 +396,7 @@ export default {
headers: {
'Cache-Control': 'public, max-age=3600',
},
static: [
{
directory: path.resolve(__dirname, 'dist'),
},
{
directory: path.resolve(__dirname, 'public'),
publicPath: '/',
}
],
// Add proxy configuration for socket.io and API
proxy: [
{
@@ -423,266 +415,12 @@ export default {
secure: proxyTarget.startsWith('https')
}
],
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) throw new Error('webpack-dev-server is not defined');
// Middleware to serve prerendered files as HTML
devServer.app.use((req, res, next) => {
// Check if this is a request for a prerendered file
if (req.url.startsWith('/Kategorie/') || req.url.startsWith('/Artikel/')) {
const filePath = path.resolve(__dirname, 'public', req.url.slice(1));
// Check if the prerendered file exists
if (fs.existsSync(filePath)) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Cache-Control', 'public, max-age=3600');
return res.sendFile(filePath);
}
}
// Handle root index file
if (req.url === '/' || req.url.startsWith('/index.html')) {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
}
next();
});
// Add middleware to handle /404 route BEFORE webpack-dev-server processing
middlewares.unshift({
name: 'handle-404-route',
middleware: async (req, res, next) => {
if (req.url === '/404') {
// Set up prerender environment
const { createRequire } = await import('module');
const require = createRequire(import.meta.url);
require('@babel/register')({
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-react'
],
extensions: ['.js', '.jsx'],
ignore: [/node_modules/]
});
// Import React first and make it globally available
const React = require('react');
global.React = React; // Make React available globally for components that don't import it
// Set up minimal globals for prerender
if (!global.window) {
global.window = {};
}
if (!global.navigator) {
global.navigator = { userAgent: 'node.js' };
}
if (!global.URL) {
global.URL = require('url').URL;
}
if (!global.Blob) {
global.Blob = class MockBlob {
constructor(data, options) {
this.data = data;
this.type = options?.type || '';
}
};
}
// Mock browser storage APIs
const mockStorage = {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
clear: () => {},
key: () => null,
length: 0
};
if (!global.localStorage) {
global.localStorage = mockStorage;
}
if (!global.sessionStorage) {
global.sessionStorage = mockStorage;
}
// Also add to window object for components that access it via window
global.window.localStorage = mockStorage;
global.window.sessionStorage = mockStorage;
// Import the dedicated prerender component
const PrerenderNotFound = require('./src/PrerenderNotFound.js').default;
// Create the prerender component
const component = React.createElement(PrerenderNotFound);
// Get only the essential bundles (not lazy-loaded chunks)
let jsBundles = [];
try {
const outputFileSystem = devServer.compiler.outputFileSystem;
const outputPath = devServer.compiler.outputPath;
const jsPath = path.join(outputPath, 'js');
if (outputFileSystem.existsSync && outputFileSystem.existsSync(jsPath)) {
const jsFiles = outputFileSystem.readdirSync(jsPath);
// Only include essential bundles in correct dependency order
const essentialBundles = [];
// 1. Runtime bundle (webpack runtime - must be first)
const runtimeFile = jsFiles.find(f => f.startsWith('runtime.') && f.endsWith('.bundle.js'));
if (runtimeFile) essentialBundles.push('/js/' + runtimeFile);
// 2. Vendor bundles (libraries that main depends on)
const reactFile = jsFiles.find(f => f.startsWith('react.') && f.endsWith('.bundle.js'));
if (reactFile) essentialBundles.push('/js/' + reactFile);
const emotionFile = jsFiles.find(f => f.startsWith('emotion.') && f.endsWith('.bundle.js'));
if (emotionFile) essentialBundles.push('/js/' + emotionFile);
const muiIconsCommonFile = jsFiles.find(f => f.startsWith('mui-icons-common.') && f.endsWith('.bundle.js'));
if (muiIconsCommonFile) essentialBundles.push('/js/' + muiIconsCommonFile);
const muiCoreFile = jsFiles.find(f => f.startsWith('mui-core.') && f.endsWith('.bundle.js'));
if (muiCoreFile) essentialBundles.push('/js/' + muiCoreFile);
const vendorFile = jsFiles.find(f => f.startsWith('vendor.') && f.endsWith('.bundle.js'));
if (vendorFile) essentialBundles.push('/js/' + vendorFile);
// 3. Common shared code
const commonFile = jsFiles.find(f => f.startsWith('common.') && f.endsWith('.chunk.js'));
if (commonFile) essentialBundles.push('/js/' + commonFile);
// 4. Main bundle (your app code - must be last)
const mainFile = jsFiles.find(f => f.startsWith('main.') && f.endsWith('.bundle.js'));
if (mainFile) essentialBundles.push('/js/' + mainFile);
jsBundles = essentialBundles;
}
} catch (error) {
console.warn('Could not read webpack output filesystem:', error.message);
}
// Fallback if we can't read the filesystem
if (jsBundles.length === 0) {
jsBundles = ['/js/runtime.bundle.js', '/js/main.bundle.js'];
}
// Render the page in memory only (no file writing in dev mode)
const ReactDOMServer = require('react-dom/server');
const { StaticRouter } = require('react-router');
const { CacheProvider } = require('@emotion/react');
const { ThemeProvider } = require('@mui/material/styles');
const createEmotionCache = require('./createEmotionCache.js').default;
const theme = require('./src/theme.js').default;
const createEmotionServer = require('@emotion/server/create-instance').default;
// Create fresh Emotion cache for this page
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
// Wrap with StaticRouter to provide React Router context for Logo's Link components
const routedComponent = React.createElement(
StaticRouter,
{ location: '/404' },
component
);
const pageElement = React.createElement(
CacheProvider,
{ value: cache },
React.createElement(ThemeProvider, { theme: theme }, routedComponent)
);
// Render to string
const renderedMarkup = ReactDOMServer.renderToString(pageElement);
const emotionChunks = extractCriticalToChunks(renderedMarkup);
// Build the full HTML page
const templatePath = path.resolve(__dirname, 'public', 'index.html');
let template = fs.readFileSync(templatePath, 'utf8');
// Add JavaScript bundles
let scriptTags = '';
jsBundles.forEach(jsFile => {
scriptTags += `<script src="${jsFile}"></script>`;
});
// Add global CSS from src/index.css
let globalCss = '';
try {
globalCss = fs.readFileSync(path.resolve(__dirname, 'src', 'index.css'), 'utf8');
// Fix relative font paths for prerendered HTML (remove ../public to make them relative to public root)
globalCss = globalCss.replace(/url\('\.\.\/public/g, "url('");
} catch (error) {
console.warn('Could not read src/index.css:', error.message);
}
// Add inline CSS from emotion
let emotionCss = '';
if (emotionChunks.styles.length > 0) {
emotionChunks.styles.forEach(style => {
if (style.css) {
emotionCss += style.css;
}
});
}
// Combine all CSS
const inlineCss = globalCss + emotionCss;
// Use the rendered markup as-is (no regex replacements)
let processedMarkup = renderedMarkup;
// Replace placeholders in template
const finalHtml = template
.replace('<div id="root"></div>', `<div id="root">${processedMarkup}</div>`)
.replace('</head>', `<style>${inlineCss}</style></head>`)
.replace('</body>', `
<script>
window.__PRERENDER_FALLBACK__ = {path: '/404', content: ${JSON.stringify(processedMarkup)}, timestamp: ${Date.now()}};
</script>
${scriptTags}
</body>`);
// Serve the prerendered HTML with 404 status
res.status(404);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
return res.send(finalHtml);
// If we get here, prerender failed - let the error bubble up
throw new Error('404 prerender failed completely');
} else {
next();
}
}
});
return middlewares;
},
hot: true,
port: 9500,
open: false,
historyApiFallback: {
index: '/index.html',
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
rewrites: [
// Exclude prerendered routes from SPA fallback
{ from: /^\/Kategorie\//, to: function(context) {
return context.parsedUrl.pathname;
}},
{ from: /^\/Artikel\//, to: function(context) {
return context.parsedUrl.pathname;
}},
// All other routes should fallback to React SPA
{ from: /^\/(?!api|socket\.io|assets|js|css|favicon\.ico).*$/, to: '/index.html' }
]
index: '/index.html'
},
client: {
logging: 'verbose',