src/applications/mhv-secure-messaging/components/ComposeForm/FileInput.jsx
import React, { useRef, useState, useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';
import {
acceptedFileTypes,
Attachments,
ErrorMessages,
} from '../../util/constants';
const FileInput = props => {
const {
attachments,
setAttachments,
setAttachFileSuccess,
draftSequence,
attachmentScanError,
} = props;
const [error, setError] = useState();
const fileInputRef = useRef();
const errorRef = useRef(null);
const [selectedFileId, setSelectedFileId] = useState(null);
// Validation for files
const handleFiles = event => {
const currentTotalSize = attachments.reduce((currentSize, item) => {
return currentSize + item.size;
}, 0);
const selectedFile = event.target.files[0];
setSelectedFileId(selectedFile.lastModified);
// eslint disabled here to clear the input's stored value to allow a user to remove and re-add the same attachment
// https://stackoverflow.com/questions/42192346/how-to-reset-reactjs-file-input
// eslint-disable-next-line no-param-reassign
event.target.value = null;
if (!selectedFile) return;
const fileExtension =
selectedFile.name && selectedFile.name.split('.').pop();
setError(null);
if (selectedFile.size === 0) {
setError({
message: ErrorMessages.ComposeForm.ATTACHMENTS.FILE_EMPTY,
});
fileInputRef.current.value = null;
return;
}
if (!fileExtension || !acceptedFileTypes[fileExtension.toLowerCase()]) {
setError({
message: ErrorMessages.ComposeForm.ATTACHMENTS.INVALID_FILE_TYPE,
});
fileInputRef.current.value = null;
return;
}
if (attachments.filter(a => a.name === selectedFile.name).length > 0) {
setError({
message: ErrorMessages.ComposeForm.ATTACHMENTS.FILE_DUPLICATE,
});
fileInputRef.current.value = null;
return;
}
if (selectedFile.size > Attachments.MAX_FILE_SIZE) {
setError({
message: ErrorMessages.ComposeForm.ATTACHMENTS.FILE_TOO_LARGE,
});
fileInputRef.current.value = null;
return;
}
if (
currentTotalSize + selectedFile.size >
Attachments.TOTAL_MAX_FILE_SIZE
) {
setError({
message:
ErrorMessages.ComposeForm.ATTACHMENTS.TOTAL_MAX_FILE_SIZE_EXCEEDED,
});
fileInputRef.current.value = null;
return;
}
if (attachments.length) {
setAttachments(prevFiles => {
setAttachFileSuccess(true);
return [...prevFiles, selectedFile];
});
} else {
setAttachFileSuccess(true);
setAttachments([selectedFile]);
}
};
useEffect(
() => {
const errorElement = document.getElementById(`error-${selectedFileId}`);
if (errorElement) {
errorElement.focus();
}
},
[selectedFileId],
);
const useFileInput = () => {
fileInputRef.current.click();
setAttachFileSuccess(false);
};
const draftText = useMemo(
() => {
if (draftSequence) {
return ` to draft ${draftSequence}`;
}
return '';
},
[draftSequence],
);
const attachText = useMemo(
() => {
if (attachments.length > 0) {
return 'Attach additional file';
}
return 'Attach file';
},
[attachments.length],
);
return (
<div
className={`
file-input
vads-u-font-weight--bold
vads-u-color--secondary-dark
${
error
? 'vads-u-margin-left--neg2 vads-u-border-left--4px vads-u-border-color--secondary-dark vads-u-padding-left--2'
: ''
}`}
>
{error && (
<label
htmlFor={`attachments${draftSequence ? `-${draftSequence}` : ''}`}
id={`error-${selectedFileId}`}
role="alert"
data-testid="file-input-error-message"
ref={errorRef}
tabIndex="-1"
aria-live="polite"
>
{error.message}
</label>
)}
{attachments?.length < Attachments.MAX_FILE_COUNT &&
!attachmentScanError && (
<>
{/* Wave plugin addressed this as an issue, label required */}
<label
htmlFor={`attachments${draftSequence ? `-${draftSequence}` : ''}`}
hidden
>
Attachments input
</label>
<input
ref={fileInputRef}
type="file"
id={`attachments${draftSequence ? `-${draftSequence}` : ''}`}
name={`attachments${draftSequence ? `-${draftSequence}` : ''}`}
data-testid={`attach-file-input${
draftSequence ? `-${draftSequence}` : ''
}`}
onChange={handleFiles}
hidden
/>
<va-button
onClick={useFileInput}
secondary
text={`${attachText}${draftText}`}
class="attach-file-button"
data-testid={`attach-file-button${
draftSequence ? `-${draftSequence}` : ''
}`}
id={`attach-file-button${
draftSequence ? `-${draftSequence}` : ''
}`}
data-dd-action-name={`${attachText}${draftText} Button`}
/>
</>
)}
</div>
);
};
FileInput.propTypes = {
attachmentScanError: PropTypes.bool,
attachments: PropTypes.array,
draftSequence: PropTypes.number,
setAttachFileSuccess: PropTypes.func,
setAttachments: PropTypes.func,
};
export default FileInput;