feat(ui): add similar products section to ProductDetailPage

- Introduce a new section displaying similar products on the ProductDetailPage.
- Update state management to include similar products data.
- Enhance internationalization by adding translation keys for similar products in English, German, and Spanish.
This commit is contained in:
sebseb7
2025-10-08 06:26:00 +02:00
parent f8f2658653
commit 0e29ab2a61
4 changed files with 69 additions and 6 deletions

View File

@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
import parse from "html-react-parser";
import AddToCartButton from "./AddToCartButton.js";
import ProductImage from "./ProductImage.js";
import Product from "./Product.js";
import { withI18n } from "../i18n/withTranslation.js";
import ArticleQuestionForm from "./ArticleQuestionForm.js";
import ArticleRatingForm from "./ArticleRatingForm.js";
@@ -97,7 +98,9 @@ class ProductDetailPage extends Component {
// Snackbar state
snackbarOpen: false,
snackbarMessage: "",
snackbarSeverity: "success"
snackbarSeverity: "success",
// Similar products
similarProducts: cachedData.similarProducts || []
};
} else if (partialProduct && isUpgrading) {
// Partial product data found - enter upgrading state
@@ -144,7 +147,9 @@ class ProductDetailPage extends Component {
// Snackbar state
snackbarOpen: false,
snackbarMessage: "",
snackbarSeverity: "success"
snackbarSeverity: "success",
// Similar products
similarProducts: []
};
} else {
// No cached data found - full loading state
@@ -173,7 +178,9 @@ class ProductDetailPage extends Component {
// Snackbar state
snackbarOpen: false,
snackbarMessage: "",
snackbarSeverity: "success"
snackbarSeverity: "success",
// Similar products
similarProducts: []
};
}
}
@@ -196,7 +203,7 @@ class ProductDetailPage extends Component {
// Check for seoName changes
if (prevProps.seoName !== this.props.seoName) {
this.setState(
{ product: null, loading: true, upgrading: false, error: null, imageDialogOpen: false },
{ product: null, loading: true, upgrading: false, error: null, imageDialogOpen: false, similarProducts: [] },
this.loadProductData
);
return;
@@ -225,7 +232,8 @@ class ProductDetailPage extends Component {
komponentenData: {},
komponentenLoaded: false,
totalKomponentenPrice: 0,
totalSavings: 0
totalSavings: 0,
similarProducts: []
},
this.loadProductData
);
@@ -514,7 +522,8 @@ class ProductDetailPage extends Component {
imageDialogOpen: false,
attributes: res.attributes,
komponenten: komponenten,
komponentenLoaded: komponenten.length === 0 // If no komponenten, mark as loaded
komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded
similarProducts: res.similarProducts || []
}, () => {
if(komponenten.length > 0) {
for(const komponent of komponenten) {
@@ -1599,6 +1608,57 @@ class ProductDetailPage extends Component {
</Box>
)}
{/* Similar Products Section */}
{this.state.similarProducts && this.state.similarProducts.length > 0 && (
<Box sx={{ mt: 4, p: 4, background: "#fff", borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
<Typography variant="h4" component="h2" gutterBottom sx={{ mb: 3 }}>
{this.props.t ? this.props.t('product.similarProducts') : 'Ähnliche Produkte'}
</Typography>
<Box sx={{
display: "grid",
gridTemplateColumns: {
xs: "1fr",
sm: "repeat(2, 1fr)",
md: "repeat(3, 1fr)",
lg: "repeat(4, 1fr)"
},
gap: 2
}}>
{this.state.similarProducts.map((similarProductData, index) => {
const product = similarProductData.product;
return (
<Box key={product.id} sx={{ display: 'flex', justifyContent: 'center' }}>
<Product
id={product.id}
name={product.name}
seoName={product.seoName}
price={product.price}
currency={product.currency}
available={product.available}
manufacturer={product.manufacturer}
vat={product.vat}
cGrundEinheit={product.cGrundEinheit}
fGrundPreis={product.fGrundPreis}
incoming={product.incomingDate}
neu={product.neu}
thc={product.thc}
floweringWeeks={product.floweringWeeks}
versandklasse={product.versandklasse}
weight={product.weight}
pictureList={product.pictureList}
availableSupplier={product.availableSupplier}
komponenten={product.komponenten}
rebate={product.rebate}
priority={index < 6 ? 'high' : 'auto'}
t={this.props.t}
/>
</Box>
);
})}
</Box>
</Box>
)}
{/* Snackbar for user feedback */}
<Snackbar
open={snackbarOpen}

View File

@@ -28,6 +28,7 @@ export default {
"individualPriceTotal": "Einzelpreis gesamt:",
"setPrice": "Set-Preis:",
"yourSavings": "Ihre Ersparnis:",
"similarProducts": "Ähnliche Produkte",
"countDisplay": {
"noProducts": "0 Produkte",
"oneProduct": "1 Produkt",

View File

@@ -28,6 +28,7 @@ export default {
"individualPriceTotal": "Total individual price:", // Einzelpreis gesamt:
"setPrice": "Set price:", // Set-Preis:
"yourSavings": "Your savings:", // Ihre Ersparnis:
"similarProducts": "Similar Products", // Ähnliche Produkte
"countDisplay": {
"noProducts": "0 products", // 0 Produkte
"oneProduct": "1 product", // 1 Produkt

View File

@@ -28,6 +28,7 @@ export default {
"individualPriceTotal": "Precio individual total:",
"setPrice": "Precio del set:",
"yourSavings": "Tus ahorros:",
"similarProducts": "Productos Similares",
"countDisplay": {
"noProducts": "0 productos",
"oneProduct": "1 producto",