21099 update filename validation with #
This commit is contained in:
@@ -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
@@ -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 "
|
||||
|
||||
@@ -177,6 +177,8 @@ export default function BuildRow(props) {
|
||||
props.props.setFilesForAppeal
|
||||
}
|
||||
maxSubField={maxSubField}
|
||||
setFileCount={setFileCount}
|
||||
setUploadCount={setUploadCount}
|
||||
/>
|
||||
);
|
||||
// } else {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user