225 lines
No EOL
6.4 KiB
JavaScript
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; |