Make upload by URL input always visible, fix deleting URL images
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
type Props<T extends string = string> = {
|
type Props<T extends string = string> = {
|
||||||
data: any[];
|
data: any[];
|
||||||
@@ -767,6 +768,12 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
if (!image) return;
|
if (!image) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Check if this is an external URL-based image or an uploaded image
|
||||||
|
const isExternalUrl = image.imageUrl.startsWith('http') &&
|
||||||
|
!image.imageUrl.includes(config.apiUrl.replace(/^https?:\/\//, ''));
|
||||||
|
|
||||||
|
// Only call the API to delete the file if it's an uploaded image
|
||||||
|
if (!isExternalUrl) {
|
||||||
// Extract the filename from the URL
|
// Extract the filename from the URL
|
||||||
const urlParts = image.imageUrl.split('/');
|
const urlParts = image.imageUrl.split('/');
|
||||||
const filename = urlParts[urlParts.length - 1];
|
const filename = urlParts[urlParts.length - 1];
|
||||||
@@ -786,6 +793,7 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to delete image');
|
throw new Error('Failed to delete image');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the image from our state
|
// Remove the image from our state
|
||||||
setProductImages(prev => prev.filter((_, idx) => idx !== imageIndex));
|
setProductImages(prev => prev.filter((_, idx) => idx !== imageIndex));
|
||||||
@@ -1232,84 +1240,37 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a URL input component
|
// Add a URL input component that doesn't expand/collapse
|
||||||
const ImageUrlInput = ({ productIndex }: { productIndex: number }) => {
|
const ImageUrlInput = ({ productIndex }: { productIndex: number }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
// Use a stable format that won't get affected by DndContext events
|
||||||
|
|
||||||
// Add input reference to maintain focus
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
<form
|
||||||
<PopoverTrigger asChild>
|
className="flex items-center gap-1"
|
||||||
<Button
|
onSubmit={(e) => {
|
||||||
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"
|
|
||||||
onInteractOutside={(e) => {
|
|
||||||
// Prevent closing when interacting with the input
|
|
||||||
if (inputRef.current?.contains(e.target as Node)) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (urlInputs[productIndex]) {
|
||||||
|
handleAddImageFromUrl(productIndex, urlInputs[productIndex]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onOpenAutoFocus={() => {
|
|
||||||
// Focus the input field when the popover opens
|
|
||||||
setTimeout(() => inputRef.current?.focus(), 0);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="space-y-2" onClick={(e) => e.stopPropagation()}>
|
|
||||||
<h4 className="font-medium text-sm">Add image from URL</h4>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
placeholder="Image URL"
|
||||||
placeholder="Enter image URL"
|
|
||||||
value={urlInputs[productIndex] || ''}
|
value={urlInputs[productIndex] || ''}
|
||||||
onChange={(e) => updateUrlInput(productIndex, e.target.value)}
|
onChange={(e) => updateUrlInput(productIndex, e.target.value)}
|
||||||
className="text-sm"
|
className="text-xs h-8 max-w-[180px]"
|
||||||
// Full prevention of event bubbling
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (e.key === 'Enter' && urlInputs[productIndex]) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleAddImageFromUrl(productIndex, urlInputs[productIndex] || '');
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
// Important for paste operation
|
|
||||||
onPaste={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
type="submit"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
className="h-8 whitespace-nowrap"
|
||||||
disabled={processingUrls[productIndex] || !urlInputs[productIndex]}
|
disabled={processingUrls[productIndex] || !urlInputs[productIndex]}
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
handleAddImageFromUrl(productIndex, urlInputs[productIndex] || '');
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
{processingUrls[productIndex] ?
|
{processingUrls[productIndex] ?
|
||||||
<Loader2 className="h-4 w-4 animate-spin" /> :
|
<Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> :
|
||||||
"Add"}
|
<Link2 className="h-3.5 w-3.5 mr-1" />}
|
||||||
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1504,7 +1465,34 @@ export const ImageUploadStep = <T extends string>({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<ImageUrlInput productIndex={index} />
|
<form
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (urlInputs[index]) {
|
||||||
|
handleAddImageFromUrl(index, urlInputs[index]);
|
||||||
|
updateUrlInput(index, '');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Image URL"
|
||||||
|
value={urlInputs[index] || ''}
|
||||||
|
onChange={(e) => updateUrlInput(index, e.target.value)}
|
||||||
|
className="text-xs h-8 w-[180px]"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 whitespace-nowrap flex gap-1 items-center text-xs"
|
||||||
|
disabled={processingUrls[index] || !urlInputs[index]}
|
||||||
|
>
|
||||||
|
{processingUrls[index] ?
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" /> :
|
||||||
|
<Link2 className="h-3.5 w-3.5" />}
|
||||||
|
Add Image
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user