diff --git a/packages/admin/dashboard/src/components/common/file-upload/file-upload.tsx b/packages/admin/dashboard/src/components/common/file-upload/file-upload.tsx index f05c45acf7..41600abf26 100644 --- a/packages/admin/dashboard/src/components/common/file-upload/file-upload.tsx +++ b/packages/admin/dashboard/src/components/common/file-upload/file-upload.tsx @@ -8,21 +8,30 @@ export interface FileType { file: File } +export interface RejectedFile { + file: File + reason: "size" | "format" +} + export interface FileUploadProps { label: string multiple?: boolean hint?: string hasError?: boolean formats: string[] - onUploaded: (files: FileType[]) => void + maxFileSize?: number // in bytes, defaults to 1MB. Set to Infinity to disable. + onUploaded: (files: FileType[], rejectedFiles?: RejectedFile[]) => void } +const DEFAULT_MAX_FILE_SIZE = 1024 * 1024 // 1MB + export const FileUpload = ({ label, hint, multiple = true, hasError, formats, + maxFileSize = DEFAULT_MAX_FILE_SIZE, onUploaded, }: FileUploadProps) => { const [isDragOver, setIsDragOver] = useState(false) @@ -65,18 +74,26 @@ export const FileUpload = ({ } const fileList = Array.from(files) - const fileObj = fileList.map((file) => { - const id = Math.random().toString(36).substring(7) + const validFiles: FileType[] = [] + const rejectedFiles: RejectedFile[] = [] + const normalizedMaxFileSize = Math.min(maxFileSize, Infinity) + fileList.forEach((file) => { + if (file.size > normalizedMaxFileSize) { + rejectedFiles.push({ file, reason: "size" }) + return + } + + const id = Math.random().toString(36).substring(7) const previewUrl = URL.createObjectURL(file) - return { + validFiles.push({ id: id, url: previewUrl, file, - } + }) }) - onUploaded(fileObj) + onUploaded(validFiles, rejectedFiles) } const handleDrop = (event: DragEvent) => { diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json index 388de5849c..f413a59c52 100644 --- a/packages/admin/dashboard/src/i18n/translations/$schema.json +++ b/packages/admin/dashboard/src/i18n/translations/$schema.json @@ -1935,6 +1935,9 @@ "invalidFileType": { "type": "string" }, + "fileTooLarge": { + "type": "string" + }, "failedToUpload": { "type": "string" }, diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json index 90a06cbe3a..905fc257a4 100644 --- a/packages/admin/dashboard/src/i18n/translations/en.json +++ b/packages/admin/dashboard/src/i18n/translations/en.json @@ -513,6 +513,7 @@ "uploadImagesLabel": "Upload images", "uploadImagesHint": "Drag and drop images here or click to upload.", "invalidFileType": "'{{name}}' is not a supported file type. Supported file types are: {{types}}.", + "fileTooLarge": "'{{name}}' exceeds the maximum file size of {{size}}. Please upload a smaller file.", "failedToUpload": "Failed to upload the added media. Please try again.", "deleteWarning_one": "You are about to delete {{count}} image. This action cannot be undone.", "deleteWarning_other": "You are about to delete {{count}} images. This action cannot be undone.", diff --git a/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx b/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx index 2d23cb8f74..81c6ec407d 100644 --- a/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx +++ b/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx @@ -5,6 +5,7 @@ import { z } from "zod" import { FileType, FileUpload, + RejectedFile, } from "../../../../../components/common/file-upload" import { Form } from "../../../../../components/common/form" import { MediaSchema } from "../../../product-create/constants" @@ -47,11 +48,13 @@ export const UploadMediaFormItem = ({ const { t } = useTranslation() const hasInvalidFiles = useCallback( - (fileList: FileType[]) => { + (fileList: FileType[] = [], rejectedFiles: RejectedFile[] = []) => { const invalidFile = fileList.find( - (f) => !SUPPORTED_FORMATS.includes(f.file.type) + (f) => !SUPPORTED_FORMATS.includes(f?.file?.type) ) + let hasInvalidFile = false; + if (invalidFile) { form.setError("media", { type: "invalid_file", @@ -61,22 +64,37 @@ export const UploadMediaFormItem = ({ }), }) - return true + hasInvalidFile = true; } - return false + const fileSizeRejections = rejectedFiles.filter((f) => f?.reason === "size") + + if (fileSizeRejections.length) { + const fileNames = fileSizeRejections.map((f) => f.file.name).join(", ") + form.setError("media", { + type: "file_too_large", + message: t("products.media.fileTooLarge", { + name: fileNames, + size: "1MB", + }), + }) + + hasInvalidFile = true; + } + + return hasInvalidFile; }, [form, t] ) const onUploaded = useCallback( - (files: FileType[]) => { + (files: FileType[] = [], rejectedFiles: RejectedFile[] = []) => { form.clearErrors("media") - if (hasInvalidFiles(files)) { + if (hasInvalidFiles(files, rejectedFiles)) { return } - files.forEach((f) => append({ ...f, isThumbnail: false })) + files?.forEach((f) => append({ ...f, isThumbnail: false })) }, [form, append, hasInvalidFiles] ) diff --git a/packages/admin/dashboard/src/routes/products/product-import/components/upload-import.tsx b/packages/admin/dashboard/src/routes/products/product-import/components/upload-import.tsx index 447157405c..b7618b5e8f 100644 --- a/packages/admin/dashboard/src/routes/products/product-import/components/upload-import.tsx +++ b/packages/admin/dashboard/src/routes/products/product-import/components/upload-import.tsx @@ -48,6 +48,7 @@ export const UploadImport = ({ } onUploaded(files[0].file) }} + maxFileSize={Infinity} /> {error && (