21099 update filename validation with #

This commit is contained in:
2026-01-20 15:20:54 +00:00
parent 737b74539d
commit e6b0b0763a
4 changed files with 218 additions and 65 deletions
@@ -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 (
<>
<Dropzone
@@ -1091,9 +1140,27 @@ export const RenderFileUpload = (field) => {
"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) => {
</>
)}
</Dropzone>
{rejectedFiles.length > 0 && completedUploadFiles == true && (
{rejectedFiles.length > 0 && (
<div
style={{ color: "red", marginTop: "10px" }}
className="govuk-body govuk-!-font-size-14 "
+135 -59
View File
@@ -1903,6 +1903,13 @@ const PickListTranslations = (optionValue) => {
};
export function FileUploadField(props) {
const uploadCount =
typeof props.uploadCount === "number" ? props.uploadCount : 0;
const setFileCount =
typeof props.setFileCount === "function"
? props.setFileCount
: () => {};
return (
<>
<h2 className="govuk-heading-s">
@@ -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 (
<>
<Dropzone
@@ -2176,25 +2229,46 @@ const RenderFileUpload = (field) => {
"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) => {
</>
)}
</Dropzone>
{rejectedFiles.length > 0 && completedUploadFiles == true && (
{rejectedFiles.length > 0 && (
<div
style={{ color: "red", marginTop: "10px" }}
className="govuk-body govuk-!-font-size-14 "
+2
View File
@@ -177,6 +177,8 @@ export default function BuildRow(props) {
props.props.setFilesForAppeal
}
maxSubField={maxSubField}
setFileCount={setFileCount}
setUploadCount={setUploadCount}
/>
);
// } else {
+2 -1
View File
@@ -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
);