Move image from URL option from validate step to add images step
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useState, useRef, useEffect } from "react";
|
import { useCallback, useState, useRef, useEffect } from "react";
|
||||||
import { useRsi } from "../../hooks/useRsi";
|
import { useRsi } from "../../hooks/useRsi";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Loader2, Upload, Trash2, AlertCircle, GripVertical, Maximize2, X } from "lucide-react";
|
import { Loader2, Upload, Trash2, AlertCircle, GripVertical, Maximize2, X, Link2 } from "lucide-react";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -37,6 +37,11 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
type Props<T extends string = string> = {
|
type Props<T extends string = string> = {
|
||||||
data: any[];
|
data: any[];
|
||||||
@@ -70,7 +75,6 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
const { translations } = useRsi<T>();
|
const { translations } = useRsi<T>();
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [productImages, setProductImages] = useState<ProductImageSortable[]>([]);
|
|
||||||
const fileInputRefs = useRef<{ [key: number]: HTMLInputElement | null }>({});
|
const fileInputRefs = useRef<{ [key: number]: HTMLInputElement | null }>({});
|
||||||
const [unassignedImages, setUnassignedImages] = useState<UnassignedImage[]>([]);
|
const [unassignedImages, setUnassignedImages] = useState<UnassignedImage[]>([]);
|
||||||
const [processingBulk, setProcessingBulk] = useState(false);
|
const [processingBulk, setProcessingBulk] = useState(false);
|
||||||
@@ -78,6 +82,49 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
const [activeId, setActiveId] = useState<string | null>(null);
|
const [activeId, setActiveId] = useState<string | null>(null);
|
||||||
const [activeImage, setActiveImage] = useState<ProductImageSortable | null>(null);
|
const [activeImage, setActiveImage] = useState<ProductImageSortable | null>(null);
|
||||||
|
|
||||||
|
// Add state for URL input
|
||||||
|
const [urlInputs, setUrlInputs] = useState<{ [key: number]: string }>({});
|
||||||
|
const [processingUrls, setProcessingUrls] = useState<{ [key: number]: boolean }>({});
|
||||||
|
|
||||||
|
// Initialize product images from data
|
||||||
|
const [productImages, setProductImages] = useState<ProductImageSortable[]>(() => {
|
||||||
|
// Convert existing product_images to ProductImageSortable objects
|
||||||
|
const initialImages: ProductImageSortable[] = [];
|
||||||
|
|
||||||
|
data.forEach((product, productIndex) => {
|
||||||
|
if (product.product_images) {
|
||||||
|
let imageUrls: string[] = [];
|
||||||
|
|
||||||
|
// Handle different formats of product_images
|
||||||
|
if (typeof product.product_images === 'string') {
|
||||||
|
// Split by comma if it's a string
|
||||||
|
imageUrls = product.product_images.split(',').filter(Boolean);
|
||||||
|
} else if (Array.isArray(product.product_images)) {
|
||||||
|
// Use the array directly
|
||||||
|
imageUrls = product.product_images.filter(Boolean);
|
||||||
|
} else if (product.product_images) {
|
||||||
|
// Handle case where it might be a single value
|
||||||
|
imageUrls = [String(product.product_images)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ProductImageSortable objects for each URL
|
||||||
|
imageUrls.forEach((url, i) => {
|
||||||
|
if (url && url.trim()) {
|
||||||
|
initialImages.push({
|
||||||
|
id: `image-${productIndex}-initial-${i}`,
|
||||||
|
productIndex,
|
||||||
|
imageUrl: url.trim(),
|
||||||
|
loading: false,
|
||||||
|
fileName: `Image ${i + 1}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return initialImages;
|
||||||
|
});
|
||||||
|
|
||||||
// Set up sensors for drag and drop with enhanced configuration
|
// Set up sensors for drag and drop with enhanced configuration
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(PointerSensor, {
|
useSensor(PointerSensor, {
|
||||||
@@ -245,23 +292,52 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
// Get the current product
|
// Get the current product
|
||||||
const product = newData[productIndex];
|
const product = newData[productIndex];
|
||||||
|
|
||||||
// Get current image URLs
|
// We need to update product_images array directly instead of the image_url field
|
||||||
let currentUrls = product.image_url ?
|
if (!product.product_images) {
|
||||||
(typeof product.image_url === 'string' ? product.image_url.split(',') : product.image_url)
|
product.product_images = [];
|
||||||
: [];
|
} else if (typeof product.product_images === 'string') {
|
||||||
|
// Handle case where it might be a comma-separated string
|
||||||
|
product.product_images = product.product_images.split(',').filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
// Filter out all instances of the URL we're removing
|
// Filter out the image URL we're removing
|
||||||
currentUrls = currentUrls.filter((url: string) => url && url !== imageUrl);
|
if (Array.isArray(product.product_images)) {
|
||||||
|
product.product_images = product.product_images.filter((url: string) => url && url !== imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
// Update the product
|
return newData;
|
||||||
product.image_url = currentUrls.join(',');
|
};
|
||||||
|
|
||||||
|
// Function to add an image URL to a product
|
||||||
|
const addImageToProduct = (productIndex: number, imageUrl: string) => {
|
||||||
|
// Create a copy of the data
|
||||||
|
const newData = [...data];
|
||||||
|
|
||||||
|
// Get the current product
|
||||||
|
const product = newData[productIndex];
|
||||||
|
|
||||||
|
// Initialize product_images array if it doesn't exist
|
||||||
|
if (!product.product_images) {
|
||||||
|
product.product_images = [];
|
||||||
|
} else if (typeof product.product_images === 'string') {
|
||||||
|
// Handle case where it might be a comma-separated string
|
||||||
|
product.product_images = product.product_images.split(',').filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure it's an array
|
||||||
|
if (!Array.isArray(product.product_images)) {
|
||||||
|
product.product_images = [product.product_images].filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add if the URL doesn't already exist
|
||||||
|
if (!product.product_images.includes(imageUrl)) {
|
||||||
|
product.product_images.push(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
// This is important - actually update the data reference in the parent component
|
|
||||||
// by passing the newData back to onSubmit, which will update the parent state
|
|
||||||
return newData;
|
return newData;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle drag end event to reorder or reassign images
|
// Update handleDragEnd to work with the updated product data structure
|
||||||
const handleDragEnd = (event: DragEndEvent) => {
|
const handleDragEnd = (event: DragEndEvent) => {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
|
|
||||||
@@ -358,7 +434,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
targetImagesAfter: filteredItems.filter(item => item.productIndex === targetProductIndex).length
|
targetImagesAfter: filteredItems.filter(item => item.productIndex === targetProductIndex).length
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update both products' image_url fields - creating new objects to ensure state updates
|
// Update both products' image data fields
|
||||||
let updatedData = [...data]; // Start with a fresh copy
|
let updatedData = [...data]; // Start with a fresh copy
|
||||||
|
|
||||||
// First remove from source
|
// First remove from source
|
||||||
@@ -406,7 +482,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
newItems.push(...newFilteredItems);
|
newItems.push(...newFilteredItems);
|
||||||
|
|
||||||
// Update the product data with the new image order
|
// Update the product data with the new image order
|
||||||
|
// Since we're just reordering, the URLs don't change, but their order might matter
|
||||||
return newItems;
|
return newItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +503,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
newItems.push(...newFilteredItems);
|
newItems.push(...newFilteredItems);
|
||||||
|
|
||||||
// Update the product data with the new image order
|
// Update the product data with the new image order
|
||||||
|
// The order might matter for display purposes
|
||||||
return newItems;
|
return newItems;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -437,45 +513,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
setActiveImage(null);
|
setActiveImage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to add an image URL to a product
|
// Function to handle image upload - update product data
|
||||||
const addImageToProduct = (productIndex: number, imageUrl: string) => {
|
|
||||||
// Create a copy of the data
|
|
||||||
const newData = [...data];
|
|
||||||
|
|
||||||
// Get the current product
|
|
||||||
const product = newData[productIndex];
|
|
||||||
|
|
||||||
// Get the current image URLs or initialize as empty array
|
|
||||||
let currentUrls = product.image_url ?
|
|
||||||
(typeof product.image_url === 'string' ? product.image_url.split(',') : product.image_url)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// If it's not an array, convert to array
|
|
||||||
if (!Array.isArray(currentUrls)) {
|
|
||||||
currentUrls = [currentUrls];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out empty values and make sure the URL doesn't already exist
|
|
||||||
currentUrls = currentUrls.filter((url: string) => url);
|
|
||||||
|
|
||||||
// Only add if the URL doesn't already exist
|
|
||||||
if (!currentUrls.includes(imageUrl)) {
|
|
||||||
// Add the new URL
|
|
||||||
currentUrls.push(imageUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the product
|
|
||||||
product.image_url = currentUrls.join(',');
|
|
||||||
|
|
||||||
// Update the data
|
|
||||||
newData[productIndex] = product;
|
|
||||||
|
|
||||||
return newData;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to update product data with the new image order
|
|
||||||
|
|
||||||
// Function to handle image upload
|
|
||||||
const handleImageUpload = async (files: FileList | File[], productIndex: number) => {
|
const handleImageUpload = async (files: FileList | File[], productIndex: number) => {
|
||||||
if (!files || files.length === 0) return;
|
if (!files || files.length === 0) return;
|
||||||
|
|
||||||
@@ -523,6 +561,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update the product data with the new image URL
|
// Update the product data with the new image URL
|
||||||
|
addImageToProduct(productIndex, result.imageUrl);
|
||||||
|
|
||||||
toast.success(`Image uploaded for ${data[productIndex].name || `Product #${productIndex + 1}`}`);
|
toast.success(`Image uploaded for ${data[productIndex].name || `Product #${productIndex + 1}`}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -719,7 +758,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Function to remove an image
|
// Function to remove an image - update to work with product_images
|
||||||
const removeImage = async (imageIndex: number) => {
|
const removeImage = async (imageIndex: number) => {
|
||||||
const image = productImages[imageIndex];
|
const image = productImages[imageIndex];
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
@@ -749,6 +788,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
setProductImages(prev => prev.filter((_, idx) => idx !== imageIndex));
|
setProductImages(prev => prev.filter((_, idx) => idx !== imageIndex));
|
||||||
|
|
||||||
// Remove the image URL from the product data
|
// Remove the image URL from the product data
|
||||||
|
removeImageFromProduct(image.productIndex, image.imageUrl);
|
||||||
|
|
||||||
toast.success('Image removed successfully');
|
toast.success('Image removed successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -761,14 +801,30 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
const handleSubmit = useCallback(async () => {
|
const handleSubmit = useCallback(async () => {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await onSubmit(data, file);
|
// First, we need to ensure product_images is properly formatted for each product
|
||||||
|
const updatedData = [...data].map((product, index) => {
|
||||||
|
// Get all images for this product
|
||||||
|
const images = productImages
|
||||||
|
.filter(img => img.productIndex === index)
|
||||||
|
.map(img => img.imageUrl)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Update the product with the formatted image URLs
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
// Store as comma-separated string to ensure compatibility
|
||||||
|
product_images: images.join(',')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await onSubmit(updatedData, file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Submit error:', error);
|
console.error('Submit error:', error);
|
||||||
toast.error(`Failed to submit: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
toast.error(`Failed to submit: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
}, [data, file, onSubmit]);
|
}, [data, file, onSubmit, productImages]);
|
||||||
|
|
||||||
// Function to ensure URLs are properly formatted with absolute paths
|
// Function to ensure URLs are properly formatted with absolute paths
|
||||||
const getFullImageUrl = (url: string): string => {
|
const getFullImageUrl = (url: string): string => {
|
||||||
@@ -1173,13 +1229,140 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle adding an image from a URL
|
||||||
|
const handleAddImageFromUrl = async (productIndex: number, url: string) => {
|
||||||
|
if (!url || !url.trim()) {
|
||||||
|
toast.error("Please enter a valid URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Set processing state
|
||||||
|
setProcessingUrls(prev => ({ ...prev, [productIndex]: true }));
|
||||||
|
|
||||||
|
// Create a unique ID for this image
|
||||||
|
const imageId = `image-${productIndex}-url-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
||||||
|
|
||||||
|
// Add a placeholder for this image while it's being processed
|
||||||
|
const newImage: ProductImageSortable = {
|
||||||
|
id: imageId,
|
||||||
|
productIndex,
|
||||||
|
imageUrl: url, // Use the URL directly initially
|
||||||
|
loading: true,
|
||||||
|
fileName: "From URL"
|
||||||
|
};
|
||||||
|
|
||||||
|
setProductImages(prev => [...prev, newImage]);
|
||||||
|
|
||||||
|
// Call the API to validate and potentially process the URL
|
||||||
|
const response = await fetch(`${config.apiUrl}/import/add-image-from-url`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: url,
|
||||||
|
productIndex: productIndex,
|
||||||
|
upc: data[productIndex].upc || '',
|
||||||
|
supplier_no: data[productIndex].supplier_no || ''
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to add image from URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Update the image URL in our state
|
||||||
|
setProductImages(prev =>
|
||||||
|
prev.map(img =>
|
||||||
|
img.id === imageId
|
||||||
|
? { ...img, imageUrl: result.imageUrl || url, loading: false }
|
||||||
|
: img
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the product data with the new image URL
|
||||||
|
addImageToProduct(productIndex, result.imageUrl || url);
|
||||||
|
|
||||||
|
// Clear the URL input field on success
|
||||||
|
setUrlInputs(prev => ({ ...prev, [productIndex]: '' }));
|
||||||
|
|
||||||
|
toast.success(`Image added from URL for ${data[productIndex].name || `Product #${productIndex + 1}`}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Add image from URL error:', error);
|
||||||
|
|
||||||
|
// Remove the failed image from our state
|
||||||
|
setProductImages(prev =>
|
||||||
|
prev.filter(img =>
|
||||||
|
!(img.loading && img.productIndex === productIndex && img.fileName === "From URL")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
toast.error(`Failed to add image from URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
} finally {
|
||||||
|
setProcessingUrls(prev => ({ ...prev, [productIndex]: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the URL input value
|
||||||
|
const updateUrlInput = (productIndex: number, value: string) => {
|
||||||
|
setUrlInputs(prev => ({ ...prev, [productIndex]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a URL input component
|
||||||
|
const ImageUrlInput = ({ productIndex }: { productIndex: number }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 flex gap-1 items-center text-xs whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<Link2 className="h-4 w-4" />
|
||||||
|
Add from URL
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-80">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-medium text-sm">Add image from URL</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Enter image URL"
|
||||||
|
value={urlInputs[productIndex] || ''}
|
||||||
|
onChange={(e) => updateUrlInput(productIndex, e.target.value)}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={processingUrls[productIndex] || !urlInputs[productIndex]}
|
||||||
|
onClick={() => {
|
||||||
|
handleAddImageFromUrl(productIndex, urlInputs[productIndex] || '');
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{processingUrls[productIndex] ?
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" /> :
|
||||||
|
"Add"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-[calc(100vh-9.5rem)] overflow-hidden">
|
<div className="flex flex-col h-[calc(100vh-9.5rem)] overflow-hidden">
|
||||||
{/* Header - fixed at top */}
|
{/* Header - fixed at top */}
|
||||||
<div className="px-8 py-6 bg-background shrink-0">
|
<div className="px-8 py-6 bg-background shrink-0">
|
||||||
<h2 className="text-2xl font-semibold">Add Product Images</h2>
|
<h2 className="text-2xl font-semibold">Add Product Images</h2>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
Upload images for each product. Drag images to reorder them or move them between products.
|
Upload images for each product or add them from URLs. Drag images to reorder them or move them between products.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1235,16 +1418,23 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
>
|
>
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-baseline gap-0.5 lg:gap-4">
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-2">
|
||||||
<h3 className="text-base font-medium">{product.name || `Product #${index + 1}`}</h3>
|
<div className="flex flex-col lg:flex-row lg:items-baseline gap-0.5 lg:gap-4">
|
||||||
<div className="text-xs lg:text-sm text-muted-foreground">
|
<h3 className="text-base font-medium">{product.name || `Product #${index + 1}`}</h3>
|
||||||
<span className="font-medium">UPC:</span> {product.upc || 'N/A'} |
|
<div className="text-xs lg:text-sm text-muted-foreground">
|
||||||
<span className="font-medium"> Supplier #:</span> {product.supplier_no || 'N/A'}
|
<span className="font-medium">UPC:</span> {product.upc || 'N/A'} |
|
||||||
|
<span className="font-medium"> Supplier #:</span> {product.supplier_no || 'N/A'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<ImageUrlInput productIndex={index} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex flex-col sm:flex-row items-start gap-2">
|
||||||
<ImageDropzone productIndex={index} />
|
<div className="flex flex-row gap-2 items-start">
|
||||||
|
<ImageDropzone productIndex={index} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={getProductContainerClasses(index)}
|
className={getProductContainerClasses(index)}
|
||||||
|
|||||||
@@ -84,13 +84,6 @@ const BASE_IMPORT_FIELDS = [
|
|||||||
{ rule: "unique", errorMessage: "Must be unique", level: "error" },
|
{ rule: "unique", errorMessage: "Must be unique", level: "error" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Image URL",
|
|
||||||
key: "image_url",
|
|
||||||
description: "Product image URL(s)",
|
|
||||||
fieldType: { type: "multi-input" },
|
|
||||||
width: 300,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "MSRP",
|
label: "MSRP",
|
||||||
key: "msrp",
|
key: "msrp",
|
||||||
|
|||||||
Reference in New Issue
Block a user