From b525d12d6f40135a22f714363fed73520a9a24b7 Mon Sep 17 00:00:00 2001 From: tenkalden Date: Mon, 18 May 2026 16:49:38 +0530 Subject: [PATCH] feat: add client-side file size validation with error handling for file uploads --- .../files-page/CourseFilesTable.tsx | 17 ++++++++++++- src/files-and-videos/generic/FileInput.jsx | 25 ++++++++++++++++--- src/files-and-videos/generic/FileTable.jsx | 5 ++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/files-and-videos/files-page/CourseFilesTable.tsx b/src/files-and-videos/files-page/CourseFilesTable.tsx index c1d65c3964..31721dd60c 100644 --- a/src/files-and-videos/files-page/CourseFilesTable.tsx +++ b/src/files-and-videos/files-page/CourseFilesTable.tsx @@ -10,6 +10,7 @@ import { updateAssetOrder, validateAssetFiles, } from '@src/files-and-videos/files-page/data/thunks'; +import { updateEditStatus, updateErrors } from '@src/files-and-videos/files-page/data/slice'; import FileInfoModalSidebar from '@src/files-and-videos/files-page/FileInfoModalSidebar'; import FileThumbnail from '@src/files-and-videos/files-page/FileThumbnail'; import FileValidationModal from '@src/files-and-videos/files-page/FileValidationModal'; @@ -22,6 +23,8 @@ import { } from '@src/files-and-videos/generic'; import { useModels } from '@src/generic/model-store'; import { DeprecatedReduxState } from '@src/store'; +import { RequestStatus } from '@src/data/constants'; +import { UPLOAD_FILE_MAX_SIZE } from '@src/constants'; import { getFileSizeToClosestByte } from '@src/utils'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -63,6 +66,18 @@ export const CourseFilesTable = () => { const handleFileOrder = ({ newFileIdOrder }) => { dispatch(updateAssetOrder(courseId, newFileIdOrder)); }; + const maxFileSize = UPLOAD_FILE_MAX_SIZE; + const maxFileSizeInMB = Math.round(maxFileSize / (1024 * 1024)); + const handleFileSizeError = (invalidFiles) => { + handleErrorReset({ errorType: 'add' }); + dispatch(updateEditStatus({ editType: 'add', status: RequestStatus.FAILED })); + invalidFiles.forEach((file) => { + dispatch(updateErrors({ + error: 'add', + message: `File ${file.name} exceeds maximum size of ${maxFileSizeInMB} MB.`, + })); + }); + }; const thumbnailPreview = (props) => FileThumbnail(props); const infoModalSidebar = (asset) => FileInfoModalSidebar({ @@ -81,7 +96,6 @@ export const CourseFilesTable = () => { usageErrorMessages: errorMessages.usageMetrics, fileType: 'file', }; - const maxFileSize = 1 * 1024 * 1024 * 1024; const activeColumn = { id: 'activeStatus', @@ -176,6 +190,7 @@ export const CourseFilesTable = () => { handleUsagePaths, handleErrorReset, handleFileOrder, + handleFileSizeError, tableColumns, maxFileSize, thumbnailPreview, diff --git a/src/files-and-videos/generic/FileInput.jsx b/src/files-and-videos/generic/FileInput.jsx index 9e7de8a9d7..4815dd9d35 100644 --- a/src/files-and-videos/generic/FileInput.jsx +++ b/src/files-and-videos/generic/FileInput.jsx @@ -8,14 +8,33 @@ export const useFileInput = ({ onAddFile, setSelectedRows, setAddOpen, + maxFileSize, + onFileSizeError, }) => { const ref = React.useRef(); const click = () => ref.current.click(); const addFile = (e) => { const { files } = e.target; - setSelectedRows([...files]); - onAddFile(Object.values(files)); - setAddOpen(); + const fileArray = Object.values(files); + + if (maxFileSize) { + const validFiles = fileArray.filter(file => file.size <= maxFileSize); + const invalidFiles = fileArray.filter(file => file.size > maxFileSize); + + if (invalidFiles.length > 0 && onFileSizeError) { + onFileSizeError(invalidFiles); + } + + if (validFiles.length > 0) { + setSelectedRows(validFiles); + onAddFile(validFiles); + setAddOpen(); + } + } else { + setSelectedRows(fileArray); + onAddFile(fileArray); + setAddOpen(); + } e.target.value = ''; }; return { diff --git a/src/files-and-videos/generic/FileTable.jsx b/src/files-and-videos/generic/FileTable.jsx index c9d7318bbe..8341f302c7 100644 --- a/src/files-and-videos/generic/FileTable.jsx +++ b/src/files-and-videos/generic/FileTable.jsx @@ -44,6 +44,7 @@ const FileTable = ({ handleUsagePaths, handleErrorReset, handleFileOrder, + handleFileSizeError, tableColumns, maxFileSize, thumbnailPreview, @@ -133,6 +134,8 @@ const FileTable = ({ onAddFile: (uploads) => handleAddFile(uploads), setSelectedRows, setAddOpen, + maxFileSize, + onFileSizeError: handleFileSizeError, }); const handleDropzoneAsset = ({ fileData, handleError }) => { try { @@ -384,6 +387,7 @@ FileTable.propTypes = { handleLockFile: PropTypes.func, handleErrorReset: PropTypes.func.isRequired, handleFileOrder: PropTypes.func.isRequired, + handleFileSizeError: PropTypes.func, tableColumns: PropTypes.arrayOf(PropTypes.shape({ Header: PropTypes.string, accessor: PropTypes.string, @@ -396,6 +400,7 @@ FileTable.propTypes = { FileTable.defaultProps = { files: null, handleLockFile: () => {}, + handleFileSizeError: () => {}, }; export default FileTable;