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 "