From 0ccb00db32b59d566db3228b02104387b050fb10 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Fri, 19 Dec 2025 12:46:49 +0100 Subject: [PATCH] feat: Add GPSR safety info translation and remove pre-order cutting button functionality. --- src/components/AddToCartButton.js | 16 +- src/components/ProductDetailPage.js | 708 ++++++++++++++-------------- src/i18n/locales/de/product.js | 1 + 3 files changed, 369 insertions(+), 356 deletions(-) diff --git a/src/components/AddToCartButton.js b/src/components/AddToCartButton.js index 45e369e..ac113a7 100644 --- a/src/components/AddToCartButton.js +++ b/src/components/AddToCartButton.js @@ -154,7 +154,7 @@ class AddToCartButton extends Component { }, }} > - {this.props.t ? this.props.t('cart.availableFrom', { + {this.props.t ? this.props.t('cart.availableFrom', { date: new Date(incoming).toLocaleDateString("de-DE", { year: "numeric", month: "long", @@ -168,7 +168,7 @@ class AddToCartButton extends Component { ); } - + // If availableSupplier is 1, handle both quantity cases if (availableSupplier === 1) { // If no items in cart, show simple "Add to Cart" button with yellowish green @@ -190,13 +190,13 @@ class AddToCartButton extends Component { }, }} > - {this.props.steckling ? - (this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") : + {/*this.props.steckling ? + (this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") : */ (this.props.t ? this.props.t('cart.addToCart') : "In den Korb")} ); } - + // If items are in cart, show quantity controls with yellowish green if (quantity > 0) { return ( @@ -305,7 +305,7 @@ class AddToCartButton extends Component { ); } } - + return ( ); diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js index d292331..4265e2c 100644 --- a/src/components/ProductDetailPage.js +++ b/src/components/ProductDetailPage.js @@ -68,17 +68,17 @@ class ProductDetailPage extends Component { delete window.__PRERENDER_FALLBACK__; console.log("ProductDetailPage: Cleaned up prerender fallback using cached product data"); } - + // Initialize komponenten from cached product data const komponenten = []; - if(cachedData.product.komponenten) { - for(const komponent of cachedData.product.komponenten.split(",")) { + if (cachedData.product.komponenten) { + for (const komponent of cachedData.product.komponenten.split(",")) { // Handle both "x" and "×" as separators const [id, count] = komponent.split(/[x×]/); - komponenten.push({id: id.trim(), count: count.trim()}); + komponenten.push({ id: id.trim(), count: count.trim() }); } } - + this.state = { product: cachedData.product, loading: false, @@ -120,17 +120,17 @@ class ProductDetailPage extends Component { 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(",")) { + 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()}); + komponenten.push({ id: id.trim(), count: count.trim() }); } } - + this.state = { product: partialProduct, loading: false, @@ -220,9 +220,9 @@ class ProductDetailPage extends Component { if (this.state.attributes && this.state.attributes.length > 0) { this.loadAttributeImages(this.state.attributes); } - + if (this.state.komponenten.length > 0 && !this.state.komponentenLoaded) { - for(const komponent of this.state.komponenten) { + for (const komponent of this.state.komponenten) { this.loadKomponent(komponent.id, komponent.count); } } @@ -243,26 +243,26 @@ class ProductDetailPage extends Component { ); return; } - + // Check for language changes const prevLanguage = prevProps.languageContext?.currentLanguage || prevProps.i18n?.language || 'de'; const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; - + if (prevLanguage !== currentLanguage) { console.log('Language changed from', prevLanguage, 'to', currentLanguage, '- reloading product data'); - + // Clear caches globally to force fresh socket calls with new language if (typeof window !== 'undefined') { window.productCache = {}; window.productDetailCache = {}; } - + // Reset component state and reload data this.setState( - { - loading: false, - upgrading: false, - error: null, + { + loading: false, + upgrading: false, + error: null, imageDialogOpen: false, komponentenData: {}, komponentenLoaded: false, @@ -288,7 +288,7 @@ class ProductDetailPage extends Component { // Get the first image ID from pictureList const bildId = pictureList.split(',')[0]; - + // Check if already cached if (window.smallPicCache[bildId]) { this.setState(prevState => ({ @@ -306,7 +306,7 @@ class ProductDetailPage extends Component { if (res.success) { // Cache the image window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' })); - + // Update state this.setState(prevState => ({ komponentenImages: { @@ -332,12 +332,12 @@ class ProductDetailPage extends Component { // Check if this komponent is already cached if (window.productDetailCache[cacheKey]) { const cachedProduct = window.productDetailCache[cacheKey]; - + // Load komponent image if available if (cachedProduct.pictureList) { this.loadKomponentImage(id, cachedProduct.pictureList); } - + // Update state with cached data this.setState(prevState => { const newKomponentenData = { @@ -348,16 +348,16 @@ class ProductDetailPage extends Component { loaded: true } }; - + // Check if all remaining komponenten are loaded - const allLoaded = prevState.komponenten.every(k => + const allLoaded = prevState.komponenten.every(k => newKomponentenData[k.id] && newKomponentenData[k.id].loaded ); - + // Calculate totals if all loaded let totalKomponentenPrice = 0; let totalSavings = 0; - + if (allLoaded) { totalKomponentenPrice = prevState.komponenten.reduce((sum, k) => { const komponentData = newKomponentenData[k.id]; @@ -366,15 +366,15 @@ class ProductDetailPage extends Component { } return sum; }, 0); - + // Calculate savings (difference between buying individually vs as set) const setPrice = prevState.product ? prevState.product.price : 0; totalSavings = Math.max(0, totalKomponentenPrice - setPrice); } - + console.log("Cached komponent loaded:", id, "data:", newKomponentenData[id]); console.log("All loaded (cached):", allLoaded); - + return { komponentenData: newKomponentenData, komponentenLoaded: allLoaded, @@ -382,7 +382,7 @@ class ProductDetailPage extends Component { totalSavings }; }); - + return; } @@ -403,7 +403,7 @@ class ProductDetailPage extends Component { window.socketManager.emit( "getProductView", - { articleId: id, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true}, + { articleId: id, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true }, (res) => { if (res.success) { // Use translated product if available, otherwise use original product @@ -411,12 +411,12 @@ class ProductDetailPage extends Component { // Cache the successful response window.productDetailCache[cacheKey] = productData; - + // Load komponent image if available if (productData.pictureList) { this.loadKomponentImage(id, productData.pictureList); } - + // Update state with loaded data this.setState(prevState => { const newKomponentenData = { @@ -428,16 +428,16 @@ class ProductDetailPage extends Component { loaded: true } }; - - // Check if all remaining komponenten are loaded - const allLoaded = prevState.komponenten.every(k => - newKomponentenData[k.id] && newKomponentenData[k.id].loaded - ); - + + // Check if all remaining komponenten are loaded + const allLoaded = prevState.komponenten.every(k => + newKomponentenData[k.id] && newKomponentenData[k.id].loaded + ); + // Calculate totals if all loaded let totalKomponentenPrice = 0; let totalSavings = 0; - + if (allLoaded) { totalKomponentenPrice = prevState.komponenten.reduce((sum, k) => { const komponentData = newKomponentenData[k.id]; @@ -446,15 +446,15 @@ class ProductDetailPage extends Component { } return sum; }, 0); - + // Calculate savings (difference between buying individually vs as set) const setPrice = prevState.product ? prevState.product.price : 0; totalSavings = Math.max(0, totalKomponentenPrice - setPrice); } - + console.log("Updated komponentenData for", id, ":", newKomponentenData[id]); console.log("All loaded:", allLoaded); - + return { komponentenData: newKomponentenData, komponentenLoaded: allLoaded, @@ -462,28 +462,28 @@ class ProductDetailPage extends Component { totalSavings }; }); - + console.log("getProductView (komponent)", res); } else { console.error("Error loading komponent:", res.error || "Unknown error", res); - + // Remove failed komponent from the list and check if all remaining are loaded this.setState(prevState => { const newKomponenten = prevState.komponenten.filter(k => k.id !== id); const newKomponentenData = { ...prevState.komponentenData }; - + // Remove failed komponent from data delete newKomponentenData[id]; - + // Check if all remaining komponenten are loaded - const allLoaded = newKomponenten.length === 0 || newKomponenten.every(k => + const allLoaded = newKomponenten.length === 0 || newKomponenten.every(k => newKomponentenData[k.id] && newKomponentenData[k.id].loaded ); - + // Calculate totals if all loaded let totalKomponentenPrice = 0; let totalSavings = 0; - + if (allLoaded && newKomponenten.length > 0) { totalKomponentenPrice = newKomponenten.reduce((sum, k) => { const komponentData = newKomponentenData[k.id]; @@ -492,15 +492,15 @@ class ProductDetailPage extends Component { } return sum; }, 0); - + // Calculate savings (difference between buying individually vs as set) const setPrice = this.state.product ? this.state.product.price : 0; totalSavings = Math.max(0, totalKomponentenPrice - setPrice); } - + console.log("Removed failed komponent:", id, "remaining:", newKomponenten.length); console.log("All loaded after removal:", allLoaded); - + return { komponenten: newKomponenten, komponentenData: newKomponentenData, @@ -592,7 +592,7 @@ class ProductDetailPage extends Component { window.socketManager.emit( "getProductView", - { seoName: this.props.seoName, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true}, + { seoName: this.props.seoName, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true }, (res) => { if (res.success) { // Use translated product if available, otherwise use original product @@ -616,13 +616,13 @@ class ProductDetailPage extends Component { delete window.__PRERENDER_FALLBACK__; console.log("ProductDetailPage: Cleaned up prerender fallback after loading product data"); } - + const komponenten = []; - if(productData.komponenten) { - for(const komponent of productData.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()}); + komponenten.push({ id: id.trim(), count: count.trim() }); } } this.setState({ @@ -636,22 +636,22 @@ class ProductDetailPage extends Component { komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded similarProducts: res.similarProducts || [] }, () => { - // Update context - if (this.props.setCurrentProduct) { - console.log('ProductDetailPage: Setting product context from fetch', productData.name); - this.props.setCurrentProduct({ - name: productData.name, - categoryId: productData.kategorien ? productData.kategorien.split(',')[0] : undefined - }); - } else { - console.warn('ProductDetailPage: setCurrentProduct prop is missing after fetch'); - } + // Update context + if (this.props.setCurrentProduct) { + console.log('ProductDetailPage: Setting product context from fetch', productData.name); + this.props.setCurrentProduct({ + name: productData.name, + categoryId: productData.kategorien ? productData.kategorien.split(',')[0] : undefined + }); + } else { + console.warn('ProductDetailPage: setCurrentProduct prop is missing after fetch'); + } - if(komponenten.length > 0) { - for(const komponent of komponenten) { - this.loadKomponent(komponent.id, komponent.count); - } + if (komponenten.length > 0) { + for (const komponent of komponenten) { + this.loadKomponent(komponent.id, komponent.count); } + } }); console.log("getProductView", res); @@ -683,7 +683,7 @@ class ProductDetailPage extends Component { }; toggleQuestionForm = () => { - this.setState(prevState => ({ + this.setState(prevState => ({ showQuestionForm: !prevState.showQuestionForm, showRatingForm: false, showAvailabilityForm: false @@ -695,7 +695,7 @@ class ProductDetailPage extends Component { }; toggleRatingForm = () => { - this.setState(prevState => ({ + this.setState(prevState => ({ showRatingForm: !prevState.showRatingForm, showQuestionForm: false, showAvailabilityForm: false @@ -707,7 +707,7 @@ class ProductDetailPage extends Component { }; toggleAvailabilityForm = () => { - this.setState(prevState => ({ + this.setState(prevState => ({ showAvailabilityForm: !prevState.showAvailabilityForm, showQuestionForm: false, showRatingForm: false @@ -721,7 +721,7 @@ class ProductDetailPage extends Component { scrollToSection = (sectionId) => { const element = document.getElementById(sectionId); if (element) { - element.scrollIntoView({ + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); @@ -786,8 +786,8 @@ class ProductDetailPage extends Component { handleWhatsAppShare = () => { const url = this.getProductUrl(); const productName = cleanProductName(this.state.product.name); - const text = this.props.t - ? this.props.t("productDialogs.shareWhatsAppText", { name: productName }) + const text = this.props.t + ? this.props.t("productDialogs.shareWhatsAppText", { name: productName }) : `Schau dir dieses Produkt an: ${productName}`; const whatsappUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`; window.open(whatsappUrl, '_blank'); @@ -804,8 +804,8 @@ class ProductDetailPage extends Component { handleTelegramShare = () => { const url = this.getProductUrl(); const productName = cleanProductName(this.state.product.name); - const text = this.props.t - ? this.props.t("productDialogs.shareTelegramText", { name: productName }) + const text = this.props.t + ? this.props.t("productDialogs.shareTelegramText", { name: productName }) : `Schau dir dieses Produkt an: ${productName}`; const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`; window.open(telegramUrl, '_blank'); @@ -815,17 +815,17 @@ class ProductDetailPage extends Component { handleEmailShare = () => { const url = this.getProductUrl(); const productName = cleanProductName(this.state.product.name); - const subject = this.props.t + const subject = this.props.t ? `${this.props.t("productDialogs.shareEmailSubject")}: ${productName}` : `Produktempfehlung: ${productName}`; - + const body = this.props.t - ? this.props.t("productDialogs.shareEmailBody", { - name: productName, - url: url - }) + ? this.props.t("productDialogs.shareEmailBody", { + name: productName, + url: url + }) : `Hallo,\n\nich möchte dir dieses Produkt empfehlen:\n\n${productName}\n${url}\n\nViele Grüße`; - + const emailUrl = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; window.location.href = emailUrl; this.handleShareClose(); @@ -855,19 +855,19 @@ class ProductDetailPage extends Component { // Render embedded product from tag in description renderEmbeddedProduct = (articleNr) => { console.log('renderEmbeddedProduct called with articleNr:', articleNr); - + // Check if we already have this product data in state const embeddedProducts = this.state.embeddedProducts || {}; const productData = embeddedProducts[articleNr]; - + console.log('Embedded product data:', productData); // If there was an error loading, show error message (don't retry infinitely) if (productData && productData.error) { return ( - { console.log('loadEmbeddedProduct', articleNr); - + // Mark as loading this.setState(prevState => ({ embeddedProducts: { @@ -1010,19 +1010,19 @@ class ProductDetailPage extends Component { const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; // Fetch product data from API using getProductView (same as komponenten) - window.socketManager.emit('getProductView', { - articleNr: articleNr, - language: currentLanguage, + window.socketManager.emit('getProductView', { + articleNr: articleNr, + language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true }, (response) => { console.log('loadEmbeddedProduct response:', articleNr, response); - + if (response.success && response.product) { // Use translated product if available, otherwise use original product const product = response.translatedProduct || response.product; - + console.log('Successfully loaded embedded product:', articleNr, product.name); - + // Update state with loaded product data this.setState(prevState => ({ embeddedProducts: { @@ -1046,8 +1046,8 @@ class ProductDetailPage extends Component { this.setState(prevState => ({ embeddedProducts: { ...prevState.embeddedProducts, - [articleNr]: { - loading: false, + [articleNr]: { + loading: false, loaded: false, error: true, errorMessage: response.error || 'Unknown error' @@ -1061,10 +1061,10 @@ class ProductDetailPage extends Component { // Load embedded product image loadEmbeddedProductImage = (articleNr, bildId) => { console.log('loadEmbeddedProductImage', articleNr, bildId); - + window.socketManager.emit('getPic', { bildId, size: 'small' }, (res) => { console.log('loadEmbeddedProductImage response:', articleNr, res.success); - + if (res.success) { const imageUrl = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' })); this.setState(prevState => { @@ -1080,19 +1080,19 @@ class ProductDetailPage extends Component { }); }; - componentWillUnmount() { + componentWillUnmount() { if (this.props.setCurrentProduct) { this.props.setCurrentProduct(null); } } render() { - const { product, loading, upgrading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings, shareAnchorEl, sharePopperOpen, snackbarOpen, snackbarMessage, snackbarSeverity } = + const { product, loading, upgrading, error, attributeImages, /*isSteckling,*/ attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings, shareAnchorEl, sharePopperOpen, snackbarOpen, snackbarMessage, snackbarSeverity } = this.state; // Debug alerts removed - + if (loading && !upgrading) { // Only show full loading screen when we have no product data at all @@ -1106,7 +1106,7 @@ class ProductDetailPage extends Component { /> ); } - + // Fallback to blank page if no prerender content return
; } @@ -1296,7 +1296,7 @@ class ProductDetailPage extends Component { )} {/* Attribute images and chips with action buttons */} - + {(attributes.some(attr => attributeImages[attr.kMerkmalWert]) || attributes.some(attr => !attributeImages[attr.kMerkmalWert])) && ( @@ -1305,7 +1305,7 @@ class ProductDetailPage extends Component { .map((attribute) => { const key = attribute.kMerkmalWert; return ( - + )} - + {/* Right-aligned action buttons */}