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": [ "skipFiles": [
"<node_internals>/**" "<node_internals>/**"
] ]
},{ },
{
"name": "Start", "name": "Start",
"type": "node-terminal", "type": "node-terminal",
"request": "launch", "request": "launch",
@@ -28,6 +29,50 @@
"skipFiles": [ "skipFiles": [
"<node_internals>/**" "<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) { constructor(props) {
super(props); super(props);
if ( // First try to find cached data by seoName (complete data)
window.productDetailCache && let cachedData = null;
window.productDetailCache[this.props.seoName] let partialProduct = null;
) { let isUpgrading = false;
const cachedData = window.productDetailCache[this.props.seoName];
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 // Clean up prerender fallback since we have cached data
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) { if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
delete window.__PRERENDER_FALLBACK__; delete window.__PRERENDER_FALLBACK__;
@@ -46,6 +68,7 @@ class ProductDetailPage extends Component {
this.state = { this.state = {
product: cachedData.product, product: cachedData.product,
loading: false, loading: false,
upgrading: false,
error: null, error: null,
attributeImages: {}, attributeImages: {},
attributes: cachedData.attributes || [], attributes: cachedData.attributes || [],
@@ -62,10 +85,52 @@ class ProductDetailPage extends Component {
showRatingForm: false, showRatingForm: false,
showAvailabilityForm: 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 { } else {
// No cached data found - full loading state
this.state = { this.state = {
product: null, product: null,
loading: true, loading: true,
upgrading: false,
error: null, error: null,
attributeImages: {}, attributeImages: {},
attributes: [], attributes: [],
@@ -86,8 +151,8 @@ class ProductDetailPage extends Component {
} }
componentDidMount() { componentDidMount() {
// Only load product data if not already cached // Load product data if we have no product or if we're in upgrading state
if (!this.state.product) { if (!this.state.product || this.state.upgrading) {
this.loadProductData(); this.loadProductData();
} else { } else {
// Product is cached, but we still need to load komponenten if they exist // Product is cached, but we still need to load komponenten if they exist
@@ -102,7 +167,7 @@ class ProductDetailPage extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (prevProps.seoName !== this.props.seoName) if (prevProps.seoName !== this.props.seoName)
this.setState( this.setState(
{ product: null, loading: true, error: null, imageDialogOpen: false }, { product: null, loading: true, upgrading: false, error: null, imageDialogOpen: false },
this.loadProductData this.loadProductData
); );
} }
@@ -373,6 +438,7 @@ class ProductDetailPage extends Component {
this.setState({ this.setState({
product: res.product, product: res.product,
loading: false, loading: false,
upgrading: false, // Clear upgrading state since we now have complete data
error: null, error: null,
imageDialogOpen: false, imageDialogOpen: false,
attributes: res.attributes, attributes: res.attributes,
@@ -526,14 +592,15 @@ class ProductDetailPage extends Component {
}; };
render() { 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; this.state;
// Debug alerts removed // 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 // Check if prerender fallback is available
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) { if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
return ( return (
@@ -977,7 +1044,7 @@ class ProductDetailPage extends Component {
</Box> </Box>
{/* Product full description */} {/* Product full description */}
{product.description && ( {(product.description || upgrading) && (
<Box <Box
sx={{ sx={{
mt: 4, mt: 4,
@@ -995,7 +1062,15 @@ class ProductDetailPage extends Component {
"& strong": { fontWeight: 600 }, "& 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>
</Box> </Box>
)} )}

View File

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

View File

@@ -1,5 +1,6 @@
export default { export default {
"loading": "Loading product...", // Produkt wird geladen... "loading": "Loading product...", // Produkt wird geladen...
"loadingDescription": "Loading product description...", // Produktbeschreibung wird geladen...
"notFound": "Product not found", // Produkt nicht gefunden "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. "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 "backToHome": "Back to homepage", // Zurück zur Startseite

View File

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