import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormMessage, } from "@/components/ui/form"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import { useCallback, useState } from "react"; import { useDropzone } from "react-dropzone"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { LuUpload, LuX } from "react-icons/lu"; import { z } from "zod"; type ImageEntryProps = { onSave: (file: File) => void; children?: React.ReactNode; maxSize?: number; accept?: Record; }; export default function ImageEntry({ onSave, children, maxSize = 20 * 1024 * 1024, // 20MB default accept = { "image/*": [".jpeg", ".jpg", ".png", ".gif", ".webp"] }, }: ImageEntryProps) { const { t } = useTranslation(["views/faceLibrary"]); const [preview, setPreview] = useState(null); const formSchema = z.object({ file: z .instanceof(File, { message: t("imageEntry.validation.selectImage") }) .refine((file) => accept["image/*"].includes(`.${file.type.split("/")[1]}`), ), }); const form = useForm>({ resolver: zodResolver(formSchema), }); const onDrop = useCallback( (acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { const file = acceptedFiles[0]; form.setValue("file", file, { shouldValidate: true }); // Create preview const objectUrl = URL.createObjectURL(file); setPreview(objectUrl); // Clean up preview URL when component unmounts return () => URL.revokeObjectURL(objectUrl); } }, [form], ); const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ onDrop, maxSize, accept, multiple: false, }); const onSubmit = useCallback( (data: z.infer) => { if (!data.file) return; onSave(data.file); }, [onSave], ); const clearSelection = () => { form.reset(); setPreview(null); }; return (
(
{!preview ? (

{isDragActive ? t("imageEntry.dropActive") : t("imageEntry.dropInstructions")}

{t("imageEntry.maxSize", { size: Math.round(maxSize / (1024 * 1024)), })}

) : (
Preview
)}
)} />
{children}
); }