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 ( - {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")} ); 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 */} {this.props.t ? this.props.t('product.inclVat', { vat: product.vat }) : `inkl. ${product.vat}% MwSt.`} {product.cGrundEinheit && product.fGrundPreis && ( - <>; {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(product.fGrundPreis)}/{product.cGrundEinheit}> + <>; {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(product.fGrundPreis)}/{product.cGrundEinheit}> )} @@ -1458,39 +1458,39 @@ class ProductDetailPage extends Component { {/* Savings comparison - positioned between price and cart button */} - {product.komponenten && komponentenLoaded && totalKomponentenPrice > product.price && - (totalKomponentenPrice - product.price >= 2 && - (totalKomponentenPrice - product.price) / product.price >= 0.02) && ( - - - - {this.props.t ? this.props.t('product.youSave', { - amount: new Intl.NumberFormat("de-DE", { + {product.komponenten && komponentenLoaded && totalKomponentenPrice > product.price && + (totalKomponentenPrice - product.price >= 2 && + (totalKomponentenPrice - product.price) / product.price >= 0.02) && ( + + + + {this.props.t ? this.props.t('product.youSave', { + amount: new Intl.NumberFormat("de-DE", { + style: "currency", + currency: "EUR", + }).format(totalKomponentenPrice - product.price) + }) : `Sie sparen: ${new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", - }).format(totalKomponentenPrice - product.price) - }) : `Sie sparen: ${new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(totalKomponentenPrice - product.price)}`} - - - {this.props.t ? this.props.t('product.cheaperThanIndividual') : 'Günstiger als Einzelkauf'} - + }).format(totalKomponentenPrice - product.price)}`} + + + {this.props.t ? this.props.t('product.cheaperThanIndividual') : 'Günstiger als Einzelkauf'} + + - - )} + )} - {isSteckling && product.available == 1 && ( + {/*isSteckling && product.available == 1 && ( - )} + )*/} - - {product.id.toString().endsWith("steckling") ? + {product.id.toString().endsWith("steckling") ? (this.props.t ? this.props.t('delivery.times.cutting14Days') : "Lieferzeit: 14 Tage") : - product.available == 1 ? - (this.props.t ? this.props.t('delivery.times.standard2to3Days') : "Lieferzeit: 2-3 Tage") : - product.availableSupplier == 1 ? + product.available == 1 ? + (this.props.t ? this.props.t('delivery.times.standard2to3Days') : "Lieferzeit: 2-3 Tage") : + product.availableSupplier == 1 ? (this.props.t ? this.props.t('delivery.times.supplier7to9Days') : "Lieferzeit: 7-9 Tage") : ""} @@ -1637,7 +1637,7 @@ class ProductDetailPage extends Component { }, disallowedTagsMode: 'discard' }); - + // Parse with custom replace function to handle tags return parse(sanitized, { replace: (domNode) => { @@ -1663,6 +1663,18 @@ class ProductDetailPage extends Component { ) : null} + + {/* GPSR Information */} + {product.gpsr && ( + + + {this.props.t ? this.props.t("product.gpsrSafetyInfo") : "GPSR Informationen zur Produktsicherheit:"} + + + {product.gpsr} + + + )} )} @@ -1728,7 +1740,7 @@ class ProductDetailPage extends Component { {/* Article Question Form */} - @@ -1738,7 +1750,7 @@ class ProductDetailPage extends Component { {/* Article Rating Form */} - @@ -1748,7 +1760,7 @@ class ProductDetailPage extends Component { {/* Article Availability Form - only show for out of stock items */} {(product.available !== 1 && product.availableSupplier !== 1) && ( - @@ -1759,26 +1771,175 @@ class ProductDetailPage extends Component { {this.props.t ? this.props.t('product.consistsOf') : 'Bestehend aus:'} - - {(console.log("komponentenLoaded:", komponentenLoaded), komponentenLoaded) ? ( - <> - {console.log("Rendering loaded komponenten:", this.state.komponenten.length, "komponentenData:", Object.keys(komponentenData).length)} - {this.state.komponenten.map((komponent, index) => { - const komponentData = komponentenData[komponent.id]; - console.log(`Rendering komponent ${komponent.id}:`, komponentData); - - // Don't show border on last item (pricing section has its own top border) - const isLastItem = index === this.state.komponenten.length - 1; - const showBorder = !isLastItem; - - if (!komponentData || !komponentData.loaded) { + + {(console.log("komponentenLoaded:", komponentenLoaded), komponentenLoaded) ? ( + <> + {console.log("Rendering loaded komponenten:", this.state.komponenten.length, "komponentenData:", Object.keys(komponentenData).length)} + {this.state.komponenten.map((komponent, index) => { + const komponentData = komponentenData[komponent.id]; + console.log(`Rendering komponent ${komponent.id}:`, komponentData); + + // Don't show border on last item (pricing section has its own top border) + const isLastItem = index === this.state.komponenten.length - 1; + const showBorder = !isLastItem; + + if (!komponentData || !komponentData.loaded) { + return ( + + + + {/* Empty placeholder for image */} + + + + {index + 1}. Lädt... + + + {komponent.count}x + + + + + - + + + ); + } + + const itemPrice = komponentData.price * parseInt(komponent.count); + const formattedPrice = new Intl.NumberFormat("de-DE", { + style: "currency", + currency: "EUR", + }).format(itemPrice); + return ( - + + + {komponentenImages[komponent.id] ? ( + + ) : ( + + )} + + + + {index + 1}. {cleanProductName(komponentData.name)} + + + {komponent.count}x à {new Intl.NumberFormat("de-DE", { + style: "currency", + currency: "EUR", + }).format(komponentData.price)} + + + + + {formattedPrice} + + + ); + })} + + {/* Total price and savings display - only show when prices differ meaningfully */} + {totalKomponentenPrice > product.price && + (totalKomponentenPrice - product.price >= 2 && + (totalKomponentenPrice - product.price) / product.price >= 0.02) && ( + + + + {this.props.t ? this.props.t('product.individualPriceTotal') : 'Einzelpreis gesamt:'} + + + {new Intl.NumberFormat("de-DE", { + style: "currency", + currency: "EUR", + }).format(totalKomponentenPrice)} + + + + + {this.props.t ? this.props.t('product.setPrice') : 'Set-Preis:'} + + + {new Intl.NumberFormat("de-DE", { + style: "currency", + currency: "EUR", + }).format(product.price)} + + + {totalSavings > 0 && ( + + + {this.props.t ? this.props.t('product.yourSavings') : 'Ihre Ersparnis:'} + + + {new Intl.NumberFormat("de-DE", { + style: "currency", + currency: "EUR", + }).format(totalSavings)} + + + )} + + )} + > + ) : ( + // Loading state + + {this.state.komponenten.map((komponent, index) => { + // For loading state, we don't know if pricing will be shown, so show all borders + return ( + @@ -1787,7 +1948,7 @@ class ProductDetailPage extends Component { - {index + 1}. Lädt... + {this.props.t ? this.props.t('product.loadingComponentDetails', { index: index + 1 }) : `${index + 1}. Lädt Komponent-Details...`} {komponent.count}x @@ -1799,158 +1960,9 @@ class ProductDetailPage extends Component { ); - } - - const itemPrice = komponentData.price * parseInt(komponent.count); - const formattedPrice = new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(itemPrice); - - return ( - - - - {komponentenImages[komponent.id] ? ( - - ) : ( - - )} - - - - {index + 1}. {cleanProductName(komponentData.name)} - - - {komponent.count}x à {new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(komponentData.price)} - - - - - {formattedPrice} - - - ); - })} - - {/* Total price and savings display - only show when prices differ meaningfully */} - {totalKomponentenPrice > product.price && - (totalKomponentenPrice - product.price >= 2 && - (totalKomponentenPrice - product.price) / product.price >= 0.02) && ( - - - - {this.props.t ? this.props.t('product.individualPriceTotal') : 'Einzelpreis gesamt:'} - - - {new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(totalKomponentenPrice)} - - - - - {this.props.t ? this.props.t('product.setPrice') : 'Set-Preis:'} - - - {new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(product.price)} - - - {totalSavings > 0 && ( - - - {this.props.t ? this.props.t('product.yourSavings') : 'Ihre Ersparnis:'} - - - {new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(totalSavings)} - - - )} - - )} - > - ) : ( - // Loading state - - {this.state.komponenten.map((komponent, index) => { - // For loading state, we don't know if pricing will be shown, so show all borders - return ( - - - - {/* Empty placeholder for image */} - - - - {this.props.t ? this.props.t('product.loadingComponentDetails', { index: index + 1 }) : `${index + 1}. Lädt Komponent-Details...`} - - - {komponent.count}x - - - - - - - - - ); - })} - - )} + })} + + )} )} @@ -1961,15 +1973,15 @@ class ProductDetailPage extends Component { {this.props.t ? this.props.t('product.similarProducts') : 'Ähnliche Produkte'} - {this.state.similarProducts.map((similarProductData, index) => { const product = similarProductData.translatedProduct || similarProductData.product; @@ -2014,8 +2026,8 @@ class ProductDetailPage extends Component { onClose={this.handleSnackbarClose} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} > - diff --git a/src/i18n/locales/de/product.js b/src/i18n/locales/de/product.js index 18ed1a4..052ac13 100644 --- a/src/i18n/locales/de/product.js +++ b/src/i18n/locales/de/product.js @@ -24,6 +24,7 @@ export default { "youSave": "Sie sparen: {{amount}}", "cheaperThanIndividual": "Günstiger als Einzelkauf", "pickupPrice": "Abholpreis: 19,90 € pro Steckling.", + "gpsrSafetyInfo": "GPSR Informationen zur Produktsicherheit:", "consistsOf": "Bestehend aus:", "loadingComponentDetails": "{{index}}. Lädt Komponent-Details...", "loadingProduct": "Produkt wird geladen...",