feat: enhance ProductDetailPage to support partial data loading and improve user feedback with loading descriptions

This commit is contained in:
sebseb7
2025-08-05 15:48:56 +02:00
parent 0a7f7e653b
commit 9f707737b4
5 changed files with 138 additions and 16 deletions

47
.vscode/launch.json vendored
View File

@@ -16,7 +16,8 @@
"skipFiles": [
"<node_internals>/**"
]
},{
},
{
"name": "Start",
"type": "node-terminal",
"request": "launch",
@@ -28,6 +29,50 @@
"skipFiles": [
"<node_internals>/**"
]
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome - Debug React App",
"url": "https://dev.seedheads.de",
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack://reactshop/./src/*": "${webRoot}/*",
"webpack://reactshop/src/*": "${webRoot}/*",
"webpack:///src/*": "${webRoot}/*",
"webpack:///./src/*": "${webRoot}/*",
"webpack:///./*": "${workspaceFolder}/*",
"webpack:///./~/*": "${workspaceFolder}/node_modules/*",
"webpack://*": "${workspaceFolder}/*"
},
"smartStep": true,
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**",
"${workspaceFolder}/dist/**"
]
},
{
"type": "chrome",
"request": "attach",
"name": "Attach to Chrome - Debug React App",
"port": 9222,
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack://reactshop/./src/*": "${webRoot}/*",
"webpack://reactshop/src/*": "${webRoot}/*",
"webpack:///src/*": "${webRoot}/*",
"webpack:///./src/*": "${webRoot}/*",
"webpack:///./*": "${workspaceFolder}/*",
"webpack:///./~/*": "${workspaceFolder}/node_modules/*",
"webpack://*": "${workspaceFolder}/*"
},
"smartStep": true,
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**",
"${workspaceFolder}/dist/**"
]
}
]
}

View File

@@ -21,12 +21,34 @@ class ProductDetailPage extends Component {
constructor(props) {
super(props);
if (
window.productDetailCache &&
window.productDetailCache[this.props.seoName]
) {
const cachedData = window.productDetailCache[this.props.seoName];
// First try to find cached data by seoName (complete data)
let cachedData = null;
let partialProduct = null;
let isUpgrading = false;
if (window.productDetailCache && window.productDetailCache[this.props.seoName]) {
cachedData = window.productDetailCache[this.props.seoName];
} else if (window.productDetailCache) {
// If not found by seoName, search for partial data by checking all cached products
// Look for a product where the seoName matches this.props.seoName
for (const key in window.productDetailCache) {
const cached = window.productDetailCache[key];
if (cached && cached.seoName === this.props.seoName) {
partialProduct = cached;
isUpgrading = true;
break;
}
// Also check if cached is a product object directly (from category cache)
if (cached && typeof cached === 'object' && cached.seoName === this.props.seoName) {
partialProduct = cached;
isUpgrading = true;
break;
}
}
}
if (cachedData) {
// Complete cached data found
// Clean up prerender fallback since we have cached data
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
delete window.__PRERENDER_FALLBACK__;
@@ -46,6 +68,7 @@ class ProductDetailPage extends Component {
this.state = {
product: cachedData.product,
loading: false,
upgrading: false,
error: null,
attributeImages: {},
attributes: cachedData.attributes || [],
@@ -62,10 +85,52 @@ class ProductDetailPage extends Component {
showRatingForm: false,
showAvailabilityForm: false
};
} else if (partialProduct && isUpgrading) {
// Partial product data found - enter upgrading state
console.log("ProductDetailPage: Found partial product data, entering upgrading state");
// Clean up prerender fallback since we have some data
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
delete window.__PRERENDER_FALLBACK__;
console.log("ProductDetailPage: Cleaned up prerender fallback using partial product data");
}
// Initialize komponenten from partial product data if available
const komponenten = [];
if(partialProduct.komponenten) {
for(const komponent of partialProduct.komponenten.split(",")) {
// Handle both "x" and "×" as separators
const [id, count] = komponent.split(/[x×]/);
komponenten.push({id: id.trim(), count: count.trim()});
}
}
this.state = {
product: partialProduct,
loading: false,
upgrading: true, // This indicates we have partial data and are loading complete data
error: null,
attributeImages: {},
attributes: [], // Will be loaded when upgrading
isSteckling: false,
imageDialogOpen: false,
komponenten: komponenten,
komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded
komponentenData: {}, // Store individual komponent data with loading states
komponentenImages: {}, // Store tiny pictures for komponenten
totalKomponentenPrice: 0,
totalSavings: 0,
// Collapsible sections state
showQuestionForm: false,
showRatingForm: false,
showAvailabilityForm: false
};
} else {
// No cached data found - full loading state
this.state = {
product: null,
loading: true,
upgrading: false,
error: null,
attributeImages: {},
attributes: [],
@@ -86,8 +151,8 @@ class ProductDetailPage extends Component {
}
componentDidMount() {
// Only load product data if not already cached
if (!this.state.product) {
// Load product data if we have no product or if we're in upgrading state
if (!this.state.product || this.state.upgrading) {
this.loadProductData();
} else {
// Product is cached, but we still need to load komponenten if they exist
@@ -102,7 +167,7 @@ class ProductDetailPage extends Component {
componentDidUpdate(prevProps) {
if (prevProps.seoName !== this.props.seoName)
this.setState(
{ product: null, loading: true, error: null, imageDialogOpen: false },
{ product: null, loading: true, upgrading: false, error: null, imageDialogOpen: false },
this.loadProductData
);
}
@@ -373,6 +438,7 @@ class ProductDetailPage extends Component {
this.setState({
product: res.product,
loading: false,
upgrading: false, // Clear upgrading state since we now have complete data
error: null,
imageDialogOpen: false,
attributes: res.attributes,
@@ -526,14 +592,15 @@ class ProductDetailPage extends Component {
};
render() {
const { product, loading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings } =
const { product, loading, upgrading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings } =
this.state;
// Debug alerts removed
if (loading) {
if (loading && !upgrading) {
// Only show full loading screen when we have no product data at all
// Check if prerender fallback is available
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
return (
@@ -977,7 +1044,7 @@ class ProductDetailPage extends Component {
</Box>
{/* Product full description */}
{product.description && (
{(product.description || upgrading) && (
<Box
sx={{
mt: 4,
@@ -995,7 +1062,15 @@ class ProductDetailPage extends Component {
"& strong": { fontWeight: 600 },
}}
>
{parse(product.description)}
{product.description ? (
parse(product.description)
) : upgrading ? (
<Box sx={{ textAlign: "center", py: 2 }}>
<Typography variant="body1" color="text.secondary">
{this.props.t ? this.props.t('product.loadingDescription') : 'Produktbeschreibung wird geladen...'}
</Typography>
</Box>
) : null}
</Box>
</Box>
)}

View File

@@ -1,5 +1,6 @@
export default {
"loading": "Produkt wird geladen...",
"loadingDescription": "Produktbeschreibung wird geladen...",
"notFound": "Produkt nicht gefunden",
"notFoundDescription": "Das gesuchte Produkt existiert nicht oder wurde entfernt.",
"backToHome": "Zurück zur Startseite",

View File

@@ -1,5 +1,6 @@
export default {
"loading": "Loading product...", // Produkt wird geladen...
"loadingDescription": "Loading product description...", // Produktbeschreibung wird geladen...
"notFound": "Product not found", // Produkt nicht gefunden
"notFoundDescription": "The product you are looking for does not exist or has been removed.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
"backToHome": "Back to homepage", // Zurück zur Startseite

View File

@@ -4,8 +4,8 @@ import { io } from 'socket.io-client';
class SocketManager {
constructor() {
this.socket = io('', {
transports: ["websocket"],
autoConnect: false
transports: ["websocket", "polling"],
autoConnect: false
});
this.emit = this.emit.bind(this);