diff --git a/frontend/src/components/files/UploadImage.tsx b/frontend/src/components/files/UploadImage.tsx index 3a6f857c..df0a576b 100644 --- a/frontend/src/components/files/UploadImage.tsx +++ b/frontend/src/components/files/UploadImage.tsx @@ -2,8 +2,8 @@ import imageCompression from "browser-image-compression"; import TCButton from "components/files/TCButton"; import { api } from "hooks/api"; import { useAuthentication } from "hooks/auth"; -import React, { useState } from "react"; -import { Alert, Col, Form } from "react-bootstrap"; +import React, { useEffect, useRef, useState } from "react"; +import { Alert, Col } from "react-bootstrap"; interface ImageUploadProps { onUploadSuccess: (url: string) => void; @@ -16,38 +16,106 @@ const ImageUploadComponent: React.FC = ({ const [compressedFile, setCompressedFile] = useState(null); const [uploadStatus, setUploadStatus] = useState(null); const [fileError, setFileError] = useState(null); + const [dragOver, setDragOver] = useState(false); const auth = useAuthentication(); const auth_api = new api(auth.api); - const MAX_FILE_SIZE = 2 * 1024 * 1024; - const handleFileChange = async ( + const MAX_FILE_SIZE = 25 * 1024 * 1024; + const validFileTypes = ["image/png", "image/jpeg", "image/jpg"]; + + const fileInputRef = useRef(null); + + useEffect(() => { + const handleWindowDrop = async (event: DragEvent) => { + event.preventDefault(); + setDragOver(false); + const file = event.dataTransfer?.files[0]; + if (file) { + handleFileChange(file); + } + }; + + const handleWindowDragOver = (event: DragEvent) => { + event.preventDefault(); + setDragOver(true); + }; + + const handleWindowDragLeave = () => { + setDragOver(false); + }; + + window.addEventListener("drop", handleWindowDrop); + window.addEventListener("dragover", handleWindowDragOver); + window.addEventListener("dragleave", handleWindowDragLeave); + + return () => { + window.removeEventListener("drop", handleWindowDrop); + window.removeEventListener("dragover", handleWindowDragOver); + window.removeEventListener("dragleave", handleWindowDragLeave); + }; + }, []); + + const handleFileChange = async (file: File) => { + setUploadStatus(null); + + // Validate file type + if (!validFileTypes.includes(file.type)) { + setFileError("Only PNG, JPG, and JPEG files are allowed"); + setSelectedFile(null); + return; + } + + // Validate file size + if (file.size > MAX_FILE_SIZE) { + setFileError( + `File size should not exceed ${MAX_FILE_SIZE / 1024 / 1024} MB`, + ); + setSelectedFile(null); + return; + } + + const options = { + maxSizeMB: 0.2, // Maximum size in MB + maxWidthOrHeight: 800, // Maximum width or height in pixels + useWebWorker: true, // Use multi-threading for compression + }; + + try { + const thecompressedFile = await imageCompression(file, options); + setCompressedFile(thecompressedFile); + setSelectedFile(file); + setFileError(null); + } catch (error) { + console.error("Error compressing the image:", error); + setFileError("Error compressing the image"); + setSelectedFile(null); + } + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + setDragOver(true); + }; + + const handleDragLeave = () => { + setDragOver(false); + }; + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + setDragOver(false); + const file = event.dataTransfer.files[0]; + if (file) { + handleFileChange(file); + } + }; + + const handleFileInputChange = async ( event: React.ChangeEvent, ) => { if (event.target.files) { const file = event.target.files[0]; if (file) { - setUploadStatus(null); - if (file.size > MAX_FILE_SIZE) { - setFileError( - `File size should not exceed ${MAX_FILE_SIZE / 1024 / 1024} MB`, - ); - } else { - const options = { - maxSizeMB: 0.2, // Maximum size in MB - maxWidthOrHeight: 800, // Maximum width or height in pixels - useWebWorker: true, // Use multi-threading for compression - }; - try { - const thecompressedFile = await imageCompression(file, options); - setCompressedFile(thecompressedFile); - } catch (error) { - console.error("Error compressing the image:", error); - setFileError("Error compressing the image"); - } - setSelectedFile(file); - setFileError(null); - } - } else { - setFileError("No file selected"); + handleFileChange(file); } } }; @@ -75,13 +143,46 @@ const ImageUploadComponent: React.FC = ({ } }; + const triggerFileInput = () => { + fileInputRef.current?.click(); + }; + return ( -
- - Select Image - - +
+
+ {selectedFile ? selectedFile.name : "No file chosen"} +
+ {fileError && {fileError}} Upload diff --git a/store/app/routers/image.py b/store/app/routers/image.py index 33bf4317..870b3857 100644 --- a/store/app/routers/image.py +++ b/store/app/routers/image.py @@ -25,7 +25,7 @@ class UserInfoResponse(BaseModel): @image_router.post("/upload/") async def upload_image(crud: Annotated[Crud, Depends(Crud.get)], file: UploadFile) -> UserInfoResponse: try: - if file.content_type != "image/png": + if file.content_type in ["image/png", "image/jpeg", "image/jpg"]: raise HTTPException(status_code=400, detail="Only PNG images are supported") if len(await file.read()) > 1024 * 1024 * 2: raise HTTPException(status_code=400, detail="Image is too large")