E-Commerce-Module/frontend/src/components/ImageUploader.jsx
2025-04-25 00:41:30 -05:00

225 lines
No EOL
6.4 KiB
JavaScript

import React, { useState } from 'react';
import {
Box,
Button,
Typography,
CircularProgress,
Alert,
Grid,
IconButton,
Card,
CardMedia,
CardActions,
FormControlLabel,
Checkbox,
Tooltip
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import StarIcon from '@mui/icons-material/Star';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import { useAuth } from '@hooks/reduxHooks';
import imageUtils from '@utils/imageUtils';
/**
* Image uploader component for product images
* @param {Object} props - Component props
* @param {Array} props.images - Current images
* @param {Function} props.onChange - Callback when images change
* @param {boolean} props.multiple - Whether to allow multiple images
* @param {boolean} props.admin - Whether this is for admin use
* @returns {JSX.Element} - Image uploader component
*/
const ImageUploader = ({
images = [],
onChange,
multiple = true,
admin = true
}) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const { apiKey } = useAuth();
const handleUpload = async (event) => {
const files = event.target.files;
if (!files || files.length === 0) return;
setLoading(true);
setError(null);
try {
if (multiple && files.length > 1) {
// Upload multiple images
const response = await imageUtils.uploadMultipleImages(
Array.from(files),
{ apiKey, isProductImage: true }
);
// Format new images
const newImages = [...images];
response.images.forEach((img, index) => {
newImages.push({
path: img.imagePath,
isPrimary: images.length === 0 && index === 0,
displayOrder: images.length + index
});
});
onChange(newImages);
} else {
// Upload single image
const response = await imageUtils.uploadImage(
files[0],
{ apiKey, isProductImage: true }
);
// Add the new image
const newImage = {
path: response.imagePath,
isPrimary: images.length === 0,
displayOrder: images.length
};
onChange([...images, newImage]);
}
} catch (err) {
console.error('Image upload failed:', err);
setError(err.message || 'Failed to upload image. Please try again.');
} finally {
setLoading(false);
}
};
const handleRemoveImage = (index) => {
// Get the image to remove
const imageToRemove = images[index];
// Extract filename if we need to delete from server
const filename = imageUtils.getFilenameFromPath(imageToRemove.path);
// Remove from array first
const newImages = [...images];
newImages.splice(index, 1);
// If the removed image was primary, make the first one primary
if (imageToRemove.isPrimary && newImages.length > 0) {
newImages[0].isPrimary = true;
}
// Update state
onChange(newImages);
// If we have the filename and this is admin mode, delete from server
if (admin && filename) {
imageUtils.deleteImage(filename, { apiKey })
.catch(err => console.error('Failed to delete image from server:', err));
}
};
const handleSetPrimary = (index) => {
// Update all images, setting only one as primary
const newImages = images.map((img, i) => ({
...img,
isPrimary: i === index
}));
onChange(newImages);
};
return (
<Box>
{/* Hidden file input */}
<input
type="file"
multiple={multiple}
accept="image/*"
style={{ display: 'none' }}
id="image-upload-input"
onChange={handleUpload}
/>
{/* Upload button */}
<label htmlFor="image-upload-input">
<Button
variant="outlined"
component="span"
startIcon={<CloudUploadIcon />}
disabled={loading}
sx={{ mb: 2 }}
>
{loading ? (
<>
Uploading...
<CircularProgress size={24} sx={{ ml: 1 }} />
</>
) : (
`Upload Image${multiple ? 's' : ''}`
)}
</Button>
</label>
{/* Error message */}
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{/* Image grid */}
{images.length > 0 ? (
<Grid container spacing={2} sx={{ mt: 1 }}>
{images.map((image, index) => (
<Grid item xs={6} sm={4} md={3} key={index}>
<Card
elevation={2}
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
border: image.isPrimary ? '2px solid' : 'none',
borderColor: 'primary.main'
}}
>
<CardMedia
component="img"
sx={{ height: 140, objectFit: 'cover' }}
image={imageUtils.getImageUrl(image.path)}
alt={`Product image ${index + 1}`}
/>
<CardActions sx={{ justifyContent: 'space-between', mt: 'auto' }}>
<Tooltip title={image.isPrimary ? "Primary Image" : "Set as Primary"}>
<IconButton
size="small"
color={image.isPrimary ? "primary" : "default"}
onClick={() => handleSetPrimary(index)}
disabled={image.isPrimary}
>
{image.isPrimary ? <StarIcon /> : <StarBorderIcon />}
</IconButton>
</Tooltip>
<Tooltip title="Remove Image">
<IconButton
size="small"
color="error"
onClick={() => handleRemoveImage(index)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</CardActions>
</Card>
</Grid>
))}
</Grid>
) : (
<Typography color="text.secondary" sx={{ mt: 2 }}>
No images uploaded yet. Click the button above to upload.
</Typography>
)}
</Box>
);
};
export default ImageUploader;