in src/application/ui/src/components/ProductDetails.js [74:555]
function ProductDetails() {
const { productId } = useParams();
const navigate = useNavigate();
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [tabValue, setTabValue] = useState(0);
// State for AI enhancement features
const [enhancementStates, setEnhancementStates] = useState({
image: 'idle',
description: 'idle',
specifications: 'idle'
});
// State for enhanced content
const [enhancedContent, setEnhancedContent] = useState({
image: null,
description: null,
specifications: null
});
// State for confirmation dialog
const [confirmDialog, setConfirmDialog] = useState({
open: false,
contentType: null
});
// State for success notification
const [notification, setNotification] = useState({
open: false,
message: '',
severity: 'success'
});
// State for marketing campaign dialog
const [marketingDialogOpen, setMarketingDialogOpen] = useState(false);
// State for image enhancer dialog
const [imageEnhancerOpen, setImageEnhancerOpen] = useState(false);
useEffect(() => {
setLoading(true);
try {
// Get product data from localStorage
const searchResults = localStorage.getItem('searchResults');
if (searchResults) {
const products = JSON.parse(searchResults);
const foundProduct = products.find(p => p.id === productId);
if (foundProduct) {
setProduct(foundProduct);
} else {
setError('Product not found');
}
} else {
// If no search results in localStorage, try to fetch from API
console.warn('No search results found in localStorage, product details may be incomplete');
// Create a mock product if needed for demonstration
const mockProduct = {
id: productId,
name: "San Pellegrino Aranciata",
title: "Premium Italian Sparkling Orange Beverage",
brands: ["San Pellegrino"],
categories: ["Beverages", "Sparkling Water"],
priceInfo: {
price: "4.00",
originalPrice: "7.00",
currencyCode: "USD"
},
availability: "IN_STOCK",
images: [
{
uri: "https://m.media-amazon.com/images/I/71FSUOFvAQL._SL1500_.jpg"
}
],
attributes: [
{
key: "material",
value: { text: ["Aluminum Can"] }
},
{
key: "features",
value: { text: ["Made with Italian Oranges", "Refreshing Citrus Flavor", "Elegant Packaging"] }
},
{
key: "gender",
value: { text: ["Adults"] }
},
{
key: "activity",
value: { text: ["Dining", "Refreshment"] }
}
]
};
setProduct(mockProduct);
}
} catch (err) {
console.error('Error loading product:', err);
setError('Failed to load product details');
} finally {
setLoading(false);
}
}, [productId]);
const handleTabChange = (event, newValue) => {
setTabValue(newValue);
};
// Function to get formatted image URL - now supports primaryImageUrl
const getImageUrl = (product) => {
// First check if we have the primaryImageUrl (optimized storage format)
if (product.primaryImageUrl) {
const imageUrl = product.primaryImageUrl;
// Convert gs:// URLs to https://storage.googleapis.com/
if (imageUrl.startsWith('gs://')) {
const gcsPath = imageUrl.replace('gs://', '');
const slashIndex = gcsPath.indexOf('/');
if (slashIndex !== -1) {
const bucket = gcsPath.substring(0, slashIndex);
const objectPath = gcsPath.substring(slashIndex + 1);
return `https://storage.googleapis.com/${bucket}/${objectPath}`;
}
}
return imageUrl;
}
// Fall back to images array if available
if (product.images && product.images.length && product.images[0].uri) {
const imageUrl = product.images[0].uri;
// Convert gs:// URLs to https://storage.googleapis.com/
if (imageUrl.startsWith('gs://')) {
const gcsPath = imageUrl.replace('gs://', '');
const slashIndex = gcsPath.indexOf('/');
if (slashIndex !== -1) {
const bucket = gcsPath.substring(0, slashIndex);
const objectPath = gcsPath.substring(slashIndex + 1);
return `https://storage.googleapis.com/${bucket}/${objectPath}`;
}
}
return imageUrl;
}
// If no image is available, return placeholder
return 'https://via.placeholder.com/400x400?text=No+Image';
};
// Handler for initiating content enhancement
const handleEnhance = async (contentType) => {
// Check if this is a cancel action
if (contentType === 'cancel') {
// Reset enhancement state back to idle
setEnhancementStates(prev => ({
...prev,
[contentType]: 'idle'
}));
return;
}
// Set the state to "enhancing"
setEnhancementStates(prev => ({
...prev,
[contentType]: 'enhancing'
}));
// Setup a timeout to prevent UI from being stuck in loading state
const timeoutId = setTimeout(() => {
// Check if we're still in enhancing state after timeout
setEnhancementStates(prev => {
if (prev[contentType] === 'enhancing') {
// Show error notification for timeout
setNotification({
open: true,
message: `The AI enhancement is taking longer than expected. Please try again.`,
severity: 'warning'
});
// Reset to idle state
return {
...prev,
[contentType]: 'idle'
};
}
return prev;
});
}, 30000); // 30 second timeout
try {
let enhancedContent;
switch (contentType) {
// Image enhancement now handled by dialog
case 'description':
const descriptionResponse = await getProductEnrichment(
product.id,
product,
['description']
);
enhancedContent = descriptionResponse.enriched_fields.description;
break;
case 'specifications':
// Log the product data being sent for enrichment
console.log('Product data sent for specifications enrichment:', {
id: product.id,
data: product
});
const specsResponse = await getProductEnrichment(
product.id,
product,
['technical_specs']
);
console.log('Raw specifications response received:', specsResponse);
// Handle different potential response formats gracefully
if (specsResponse.enriched_fields && specsResponse.enriched_fields.technical_specs) {
const techSpecs = specsResponse.enriched_fields.technical_specs;
console.log('Technical specs data:', techSpecs);
// Handle both object format and array format
if (typeof techSpecs === 'object' && !Array.isArray(techSpecs)) {
// Compare with current product data to determine which specs are new/changed
enhancedContent = Object.entries(techSpecs).map(([name, value]) => {
// Check if this spec already exists with same value
let isNew = true;
const nameLower = name.toLowerCase();
// Check for brand
if (nameLower === 'brand' && product.brands && product.brands.length > 0) {
const currentBrand = product.brands[0];
isNew = currentBrand !== value;
}
// Check for category
else if (nameLower === 'category' && product.categories && product.categories.length > 0) {
const currentCategory = product.categories[0];
isNew = currentCategory !== value;
}
// Check in attributes
else {
const attrValues = getAttributeValues(product, nameLower);
if (attrValues.length > 0) {
// Consider unchanged if any attribute value matches
isNew = !attrValues.some(attr => attr === value);
}
}
return { name, value, isNew };
});
console.log('Transformed specs data (from object) with isNew flags:', enhancedContent);
} else if (Array.isArray(techSpecs)) {
enhancedContent = techSpecs.map(spec => {
const name = spec.name || spec.key || 'Specification';
const value = spec.value || spec.text || '';
const nameLower = name.toLowerCase();
// Check if this spec already exists with same value
let isNew = true;
// Check for brand
if (nameLower === 'brand' && product.brands && product.brands.length > 0) {
const currentBrand = product.brands[0];
isNew = currentBrand !== value;
}
// Check for category
else if (nameLower === 'category' && product.categories && product.categories.length > 0) {
const currentCategory = product.categories[0];
isNew = currentCategory !== value;
}
// Check in attributes
else {
const attrValues = getAttributeValues(product, nameLower);
if (attrValues.length > 0) {
// Consider unchanged if any attribute value matches
isNew = !attrValues.some(attr => attr === value);
}
}
return { name, value, isNew };
});
console.log('Transformed specs data (from array) with isNew flags:', enhancedContent);
} else {
// Fallback for unexpected format
console.error('Unexpected format for technical_specs:', techSpecs);
throw new Error('Unexpected data format received for specifications');
}
} else {
console.error('Missing technical_specs in response:', specsResponse);
throw new Error('Technical specifications data missing from AI response');
}
break;
default:
enhancedContent = null;
}
// Clear the timeout since we got a response
clearTimeout(timeoutId);
// Set the enhanced content
setEnhancedContent(prev => ({
...prev,
[contentType]: enhancedContent
}));
// Update state to show diff
setEnhancementStates(prev => {
// Only update if we're still in enhancing state (in case timeout fired)
if (prev[contentType] === 'enhancing') {
return {
...prev,
[contentType]: 'showingDiff'
};
}
return prev;
});
} catch (error) {
// Clear the timeout since we got a response (error)
clearTimeout(timeoutId);
console.error(`Error enhancing ${contentType}:`, error);
// Show error notification
setNotification({
open: true,
message: `Error enhancing ${contentType}: ${error.message || 'Unknown error occurred'}`,
severity: 'error'
});
// Reset enhancement state
setEnhancementStates(prev => ({
...prev,
[contentType]: 'idle'
}));
}
};
// Handler for confirming updates
const handleUpdate = (contentType) => {
setConfirmDialog({
open: true,
contentType
});
};
// Handler for closing the confirmation dialog
const handleCloseConfirmation = () => {
setConfirmDialog({
...confirmDialog,
open: false
});
};
// Handler for confirming the update
const handleConfirmUpdate = () => {
const contentType = confirmDialog.contentType;
// Close the dialog
setConfirmDialog({
...confirmDialog,
open: false
});
// Update product with enhanced content
if (contentType === 'image') {
setProduct({
...product,
images: [{ uri: enhancedContent.image }]
});
} else if (contentType === 'description') {
setProduct({
...product,
description: enhancedContent.description
});
} else if (contentType === 'specifications') {
// For technical specifications, we need to update the product's attributes
console.log('Updating product with new specifications:', enhancedContent.specifications);
// Create a copy of product data to update
const updatedProduct = { ...product };
// Extract specs data
const specsData = enhancedContent.specifications;
// Create a helper to update attributes
const updateAttributeValue = (key, value) => {
// First check if this attribute already exists
const existingAttrIndex = updatedProduct.attributes?.findIndex(attr =>
attr.key?.toLowerCase() === key.toLowerCase());
if (existingAttrIndex >= 0 && updatedProduct.attributes) {
// Update existing attribute
updatedProduct.attributes[existingAttrIndex] = {
...updatedProduct.attributes[existingAttrIndex],
value: { text: [value] }
};
} else if (updatedProduct.attributes) {
// Add new attribute
updatedProduct.attributes.push({
key: key.toLowerCase(),
value: { text: [value] }
});
} else {
// Initialize attributes array if it doesn't exist
updatedProduct.attributes = [{
key: key.toLowerCase(),
value: { text: [value] }
}];
}
};
// Update attributes based on specs
specsData.forEach(spec => {
// Skip brand and category as they are stored elsewhere
if (spec.name.toLowerCase() === 'brand') {
if (spec.value && spec.value.trim() !== '') {
updatedProduct.brands = [spec.value];
}
} else if (spec.name.toLowerCase() === 'category') {
if (spec.value && spec.value.trim() !== '') {
updatedProduct.categories = [spec.value];
}
} else {
// Update other attributes
updateAttributeValue(spec.name, spec.value);
}
});
// Update the product state
setProduct(updatedProduct);
}
// Reset enhancement state
setEnhancementStates(prev => ({
...prev,
[contentType]: 'idle'
}));
// Show success notification
setNotification({
open: true,
message: `The product ${contentType} has been successfully updated!`,
severity: 'success'
});
};
// Handler for closing the notification
const handleCloseNotification = () => {
setNotification({
...notification,
open: false
});
};
// Render loading state
if (loading) {
return (
<Container>
<Typography variant="h5" sx={{ my: 4 }}>Loading product details...</Typography>
</Container>
);
}
// Render error state
if (error || !product) {
return (
<Container>
<Typography variant="h5" color="error" sx={{ my: 4 }}>
{error || 'Product not found'}
</Typography>
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/')}>
Return to Search
</Button>
</Container>
);
}