// image-upload.tsx
"use client";
import { useState, useEffect } from "react";
import { Button } from "./button";
import { ImagePlusIcon, Trash } from "lucide-react";
import Image from "next/image";
import { CldUploadWidget } from "next-cloudinary";
interface ImageUploadProps {
disabled?: boolean;
onChange: (value: string) => void;
onRemove: (value: string) => void;
value: string[];
}
const ImageUpload: React.FC<ImageUploadProps> = ({
disabled,
onChange,
onRemove,
value,
}) => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
const onUpload = (result: any) => {
onChange(result.info.secure_url);
};
if (!isMounted) {
return null;
}
return (
<div>
<div className="mb-4 flex items-center gap-4">
{value.map((url) => (
<div
key={url}
className="relative w-[200px] h-[200px] rounded-md overflow-hidden"
>
<div className="z-10 absolute top-2 right-2">
<Button
type="button"
onClick={() => onRemove(url)}
variant="destructive"
size="icon"
>
<Trash className="h-4 w-4" />
</Button>
</div>
<Image fill className="object-cover" alt="Image" src={url} />
</div>
))}
</div>
<CldUploadWidget
onSuccess={onUpload}
options={{
multiple: true,
maxFiles: 5,
sources: ["local", "url", "unsplash"],
}}
uploadPreset="djrccgni"
>
{({ open }) => {
const onClick = () => {
open();
};
return (
<Button
type="button"
disabled={disabled}
variant="secondary"
onClick={onClick}
>
<ImagePlusIcon className="h-4 w-4 mr-2" />
Upload an Image
</Button>
);
}}
</CldUploadWidget>
</div>
);
};
export default ImageUpload;
// product-form.tsx
"use client";
import ImageUpload from "@/components/ui/image-upload";
const formSchema = z.object({
images: z.object({ url: z.string() }).array(),
})
interface ProductFormProps {
initialData:
| (Product & {
images: Image[];
})
| null;
type ProductFormValues = z.infer<typeof formSchema>;
export const ProductForm: React.FC<ProductFormProps> = ({
initialData
}) => {
const params = useParams();
const router = useRouter();
const origin = UseOrigin();
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const form = useForm<ProductFormValues>({
resolver: zodResolver(formSchema),
defaultValues: initialData
? {
...initialData,
}
: {
images: [],
},
});
const onSubmit = async (data: ProductFormValues) => {
try {
setLoading(true);
if (initialData) {
await axios.patch(
`/api/${params.storeId}/products/${params.productId}`,
data
);
} else {
await axios.post(`/api/${params.storeId}/products`, data);
}
router.refresh();
router.push(`/${params.storeId}/products`);
toast.success(toastMessage);
} catch (error) {
toast.error("Oops! Something went wrong.");
} finally {
setLoading(false);
}
};
const onDelete = async () => {
try {
setLoading(true);
await axios.delete(`/api/${params.storeId}/products/${params.productId}`);
router.refresh();
router.push(`/${params.storeId}/products`);
toast.success("Product deleted.");
} catch (error) {
toast.error("Oops! Something went wrong. ");
} finally {
setLoading(false);
setOpen(false);
}
};
return (
<>
<div className="flex items-center justify-between">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 w-full"
>
<FormField
control={form.control}
name="images"
render={({ field }) => (
<FormItem>
<FormLabel>Images</FormLabel>
<FormControl>
<ImageUpload
value={field.value.map((image) => image.url)}
disabled={loading}
onChange={(url) => field.onChange([...field.value, {url}])}
onRemove={(url) => field.onChange([...field.value.filter((current) =>
current.url !== url)])}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
I am building an app using React and NextJS, and I have a form in which I upload an array of images via Cloudinary, but I am having a problem with Cloudinary because it doesn’t allow me to upload more than one image. I checked their docs and it looks like since they deprecated onUpload and replaced it with onSuccess, when uploading multiple images, you have to use the options and add multiple: true to be able to upload more than one file. I did as they suggested and still, I am unable to upload more than one image.