From e6b0b0763ad6302274c0b08a4e4b437770479ec0 Mon Sep 17 00:00:00 2001 From: robbond Date: Tue, 20 Jan 2026 15:20:54 +0000 Subject: [PATCH] 21099 update filename validation with # --- .../representation/representationElements.js | 84 +++++++- components/elements/index.js | 194 ++++++++++++------ components/newappeal/buildrow.js | 2 + pages/myportal/representation.js | 3 +- 4 files changed, 218 insertions(+), 65 deletions(-) diff --git a/components/case/representation/representationElements.js b/components/case/representation/representationElements.js index 65c38700..db0675b3 100644 --- a/components/case/representation/representationElements.js +++ b/components/case/representation/representationElements.js @@ -1042,7 +1042,9 @@ export const RenderFileUpload = (field) => { }); }; + const [errorMessage, setErrorMessage] = useState(null); const [rejectedFiles, setRejectedFiles] = useState([]); // Store rejected files + const [completedUploadFiles, setCompletedUploadFiles] = useState(false); const [uploadCountMessage, setUploadCountMessage] = useState(""); @@ -1060,6 +1062,15 @@ export const RenderFileUpload = (field) => { "myrepresentations:fileupload-file-error-invalid-type-label" )}`; } + + if (error.code === "filename-invalid-chars") { + return ( + error.message || + `${file.name} ${t( + "newappeal:new-appeal-fileupload-file-error-invalid-filename-label" + )}` + ); + } return `${file.name} ${t( "myrepresentations:fileupload-file-error-invalid-label" )}`; @@ -1076,6 +1087,44 @@ export const RenderFileUpload = (field) => { setRejectedFiles([]); // Clear any previously rejected file errorsss }; + const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-()]+$/; + + const validateFilename = (file, t) => { + const name = file.name; + + // block # + if (name.includes("#")) { + return { + code: "filename-invalid-chars", + message: `${name} ${t( + "myrepresentations:fileupload-file-error-invalid-filename-label" + )}`, + }; + } + + // broader blocklist (optional but recommended) + if (!FILENAME_ALLOWED.test(name)) { + return { + code: "filename-invalid-chars", + message: `${name} ${t( + "myrepresentations:fileupload-file-error-invalid-filename-label" + )}`, + }; + } + + // block characters < > : " / \ | ? * + if (/[<>:"/\\|?*]/.test(name)) { + return { + code: "filename-invalid-chars", + message: `${name} ${t( + "myrepresentations:fileupload-file-error-invalid-filename-label" + )}`, + }; + } + + return null; // valid + }; + return ( <> { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], }} - onDrop={(filesToUpload, e) => { + validator={(file) => validateFilename(file, t)} + onDrop={(acceptedFiles, fileRejections, e) => { + // build + display rejected messages (ALWAYS, even if some accepted) + if (fileRejections?.length) { + handleDropRejected(fileRejections); + } else { + // only clear rejections when nothing was rejected on this drop + setRejectedFiles([]); + setErrorMessage(null); + } + + // if nothing accepted, stop (prevents showing only "uploading" when all invalid) + if (!acceptedFiles || acceptedFiles.length === 0) { + setCompletedUploadFiles(true); // optional: depends on your UX + setUploadCountMessage(0); + return; + } + setCompletedUploadFiles(false); - const renamedAcceptedFiles = filesToUpload.map( + + const renamedAcceptedFiles = acceptedFiles.map( (file) => new File( [file], @@ -1115,7 +1182,16 @@ export const RenderFileUpload = (field) => { } ) ); + field.input.onChange(renamedAcceptedFiles); + + if (typeof field.setFileCount === "function") { + field.setFileCount( + (field.uploadCount || 0) + + renamedAcceptedFiles.length + ); + } + setUploadCountMessage(renamedAcceptedFiles.length); uploadSingleFile( @@ -1196,8 +1272,6 @@ export const RenderFileUpload = (field) => { }); }); }} - onDropRejected={handleDropRejected} - onDropAccepted={handleDropAccepted} > {({ getRootProps, getInputProps }) => ( <> @@ -1224,7 +1298,7 @@ export const RenderFileUpload = (field) => { )} - {rejectedFiles.length > 0 && completedUploadFiles == true && ( + {rejectedFiles.length > 0 && (
{ }; export function FileUploadField(props) { + const uploadCount = + typeof props.uploadCount === "number" ? props.uploadCount : 0; + const setFileCount = + typeof props.setFileCount === "function" + ? props.setFileCount + : () => {}; + return ( <>

@@ -2144,6 +2151,14 @@ const RenderFileUpload = (field) => { "newappeal:new-appeal-fileupload-file-error-invalid-type-label" )}`; } + if (error.code === "filename-invalid-chars") { + return ( + error.message || + `${file.name} ${t( + "newappeal:new-appeal-fileupload-file-error-invalid-filename-label" + )}` + ); + } return `${file.name} ${t( "newappeal:new-appeal-fileupload-file-error-invalid-label" )}`; @@ -2160,6 +2175,44 @@ const RenderFileUpload = (field) => { setRejectedFiles([]); // Clear any previously rejected file errorsss }; + const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-()]+$/; + + const validateFilename = (file, t) => { + const name = file.name; + + // block # + if (name.includes("#")) { + return { + code: "filename-invalid-chars", + message: `${name} ${t( + "newappeal:new-appeal-fileupload-file-error-invalid-filename-label" + )}`, + }; + } + + // broader blocklist (optional but recommended) + if (!FILENAME_ALLOWED.test(name)) { + return { + code: "filename-invalid-chars", + message: `${name} ${t( + "newappeal:new-appeal-fileupload-file-error-invalid-filename-label" + )}`, + }; + } + + // block characters < > : " / \ | ? * + if (/[<>:"/\\|?*]/.test(name)) { + return { + code: "filename-invalid-chars", + message: `${name} ${t( + "newappeal:new-appeal-fileupload-file-error-invalid-filename-label" + )}`, + }; + } + + return null; // valid + }; + return ( <> { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], }} - onDrop={(filesToUpload, e) => { + validator={(file) => validateFilename(file, t)} + onDrop={(acceptedFiles, fileRejections, e) => { + // build + display rejected messages (ALWAYS, even if some accepted) + if (fileRejections?.length) { + handleDropRejected(fileRejections); + } else { + // only clear rejections when nothing was rejected on this drop + setRejectedFiles([]); + setErrorMessage(null); + } + + // if nothing accepted, stop (prevents showing only "uploading" when all invalid) + if (!acceptedFiles || acceptedFiles.length === 0) { + setCompletedUploadFiles(true); // optional: depends on your UX + setUploadCountMessage(0); + return; + } + + // continue with your existing upload logic for accepted files setCompletedUploadFiles(false); - const renamedAcceptedFiles = filesToUpload.map( + + const renamedAcceptedFiles = acceptedFiles.map( (file) => new File( [file], `${getDocumentType(field.documentTypeCode)}_-_${ file.name }`, - { - type: file.type, - } + { type: file.type } ) ); + field.input.onChange(renamedAcceptedFiles); - field.setFileCount( - field.uploadCount + renamedAcceptedFiles.length - ); + if (typeof field.setFileCount === "function") { + field.setFileCount( + (field.uploadCount || 0) + + renamedAcceptedFiles.length + ); + } setUploadCountMessage(renamedAcceptedFiles.length); @@ -2202,66 +2276,68 @@ const RenderFileUpload = (field) => { renamedAcceptedFiles, field.containerID, field.ticketnumber - ).then((data) => { - console.log(data); - let buildAppealFilesArray = []; - let values = field.form.appealForm.values; - let filesUploadObj = renamedAcceptedFiles; + ) + .then((data) => { + let buildAppealFilesArray = []; + let values = field.form.appealForm.values; + let filesUploadObj = renamedAcceptedFiles; - for (let key in filesUploadObj) { - typeof filesUploadObj[key].name != "undefined" && - buildAppealFilesArray.push({ - "name": filesUploadObj[key].name, - "size": filesUploadObj[key].size, + for (let key in filesUploadObj) { + typeof filesUploadObj[key].name !== + "undefined" && + buildAppealFilesArray.push({ + name: filesUploadObj[key].name, + size: filesUploadObj[key].size, + }); + } + + function removeDuplicates(arr, key) { + const seen = new Set(); + return arr.filter((item) => { + const value = item[key]; + if (seen.has(value)) return false; + seen.add(value); + return true; }); - } + } - //console.log(buildAppealFilesArray); - function removeDuplicates(arr, key) { - const seen = new Set(); - return arr.filter((item) => { - const value = item[key]; - if (seen.has(value)) { - return false; // Duplicate found - } else { - seen.add(value); // Add value to the set - return true; // Keep the item - } + buildAppealFilesArray = values.hasOwnProperty( + "filesList" + ) + ? buildAppealFilesArray.concat(values.filesList) + : buildAppealFilesArray; + + buildAppealFilesArray = removeDuplicates( + buildAppealFilesArray, + "name" + ); + + Object.assign(values, { + filesList: buildAppealFilesArray, }); - } - buildAppealFilesArray = values.hasOwnProperty( - "filesList" - ) - ? buildAppealFilesArray.concat(values.filesList) - : buildAppealFilesArray; + // keep your existing server-side invalid handling + if (data.invalidFiles?.length > 0) { + setRejectedFiles((prev) => [ + ...prev, + ...data.invalidFiles, + ]); + setUploadCountMessage( + renamedAcceptedFiles.length - + data.invalidFiles.length + ); + } - buildAppealFilesArray = removeDuplicates( - buildAppealFilesArray, - "name" - ); - - Object.assign(values, { - "filesList": buildAppealFilesArray, - }); - data.invalidFiles.length > 0 && - (setRejectedFiles(data.invalidFiles), - setUploadCountMessage( - renamedAcceptedFiles.length - - data.invalidFiles.length - )); - getFilesFromBlobproxy( - field.containerID, - field.ticketnumber - ).then((data) => { - field.input.value == null; + return getFilesFromBlobproxy( + field.containerID, + field.ticketnumber + ); + }) + .then((data) => { field.setFilesForAppeal(data); setCompletedUploadFiles(true); }); - }); }} - onDropRejected={handleDropRejected} - onDropAccepted={handleDropAccepted} > {({ getRootProps, getInputProps }) => ( <> @@ -2288,7 +2364,7 @@ const RenderFileUpload = (field) => { )} - {rejectedFiles.length > 0 && completedUploadFiles == true && ( + {rejectedFiles.length > 0 && (
); // } else { diff --git a/pages/myportal/representation.js b/pages/myportal/representation.js index f48fbcea..d344e0a8 100644 --- a/pages/myportal/representation.js +++ b/pages/myportal/representation.js @@ -304,6 +304,7 @@ export const getServerSideProps = wrapper.getServerSideProps( "incidentid": result.incidentID, "appealType": result.appealType, "repDetails": result, + "filesList": result.filesList, }) ); @@ -318,7 +319,7 @@ export const getServerSideProps = wrapper.getServerSideProps( const repsFileListObj = await getRepsFilesBlobs( result.containerID, - result.ticketnumber, + result.ticketnumber || result.caseRef, result.repfile_name );