diff --git a/src/id-verification/CameraHelpWithUpload.jsx b/src/id-verification/CameraHelpWithUpload.jsx
index bc27dc6..d1cb8fa 100644
--- a/src/id-verification/CameraHelpWithUpload.jsx
+++ b/src/id-verification/CameraHelpWithUpload.jsx
@@ -8,6 +8,7 @@ import messages from './IdVerification.messages';
import ImageFileUpload from './ImageFileUpload';
import IdVerificationContext from './IdVerificationContext';
import ImagePreview from './ImagePreview';
+import SupportedMediaTypes from './SupportedMediaTypes';
function CameraHelpWithUpload(props) {
const { setIdPhotoFile, idPhotoFile, userId } = useContext(IdVerificationContext);
@@ -34,6 +35,7 @@ function CameraHelpWithUpload(props) {
{idPhotoFile && hasUploadedImage && }
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])}
+
diff --git a/src/id-verification/IdVerification.messages.js b/src/id-verification/IdVerification.messages.js
index 0a42a02..59caddc 100644
--- a/src/id-verification/IdVerification.messages.js
+++ b/src/id-verification/IdVerification.messages.js
@@ -413,7 +413,7 @@ const messages = defineMessages({
},
'id.verification.portrait.photo.instructions.upload': {
id: 'id.verification.portrait.photo.instructions.upload',
- defaultMessage: 'Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)',
+ defaultMessage: 'Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. Supported formats: ',
description: 'Instructions for portrait photo upload.',
},
'id.verification.camera.help.sight.question': {
@@ -493,11 +493,16 @@ const messages = defineMessages({
},
'id.verification.id.photo.instructions.upload': {
id: 'id.verification.id.photo.instructions.upload',
- defaultMessage: 'Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)',
+ defaultMessage: 'Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. Supported formats: ',
description: 'Instructions for ID photo upload.',
},
- 'id.verification.id.photo.instructions.upload.error': {
- id: 'id.verification.id.photo.instructions.upload.error',
+ 'id.verification.id.photo.instructions.upload.error.invalidFileType': {
+ id: 'id.verification.id.photo.instructions.upload.error.invalidFileType',
+ defaultMessage: 'The file you have selected is not a supported image type. Please choose from the following formats: ',
+ description: 'Error message for file upload that is not a supported image type.',
+ },
+ 'id.verification.id.photo.instructions.upload.error.fileTooLarge': {
+ id: 'id.verification.id.photo.instructions.upload.error.fileTooLarge',
defaultMessage: 'The file you have selected is too large. Please try again with a file less than 10MB.',
description: 'Error message for file upload that is larger than 10MB.',
},
@@ -616,6 +621,11 @@ const messages = defineMessages({
defaultMessage: 'A valid account name is required. Please update your account name to match the name on your ID.',
description: 'Error message displayed when the user\'s account name is missing.',
},
+ 'id.verification.submission.alert.error.unsupported': {
+ id: 'id.verification.submission.alert.error.unsupported',
+ defaultMessage: 'One or more of the files you have uploaded is in an unsupported format. Please choose from the following: ',
+ description: 'Error message displayed when the user uploads an unsupported file type.',
+ },
'id.verification.review.error': {
id: 'id.verification.review.error',
defaultMessage: 'edX Support Page',
diff --git a/src/id-verification/ImageFileUpload.jsx b/src/id-verification/ImageFileUpload.jsx
index 2f966a4..4419719 100644
--- a/src/id-verification/ImageFileUpload.jsx
+++ b/src/id-verification/ImageFileUpload.jsx
@@ -3,9 +3,14 @@ import { intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Alert } from '@edx/paragon';
import messages from './IdVerification.messages';
+import SupportedMediaTypes from './SupportedMediaTypes';
export default function ImageFileUpload({ onFileChange, intl }) {
- const [fileTooLargeError, setFileTooLargeError] = useState(false);
+ const [error, setError] = useState(null);
+ const errorTypes = {
+ invalidFileType: 'invalidFileType',
+ fileTooLarge: 'fileTooLarge',
+ };
const maxFileSize = 10000000;
const handleChange = useCallback((e) => {
@@ -14,12 +19,15 @@ export default function ImageFileUpload({ onFileChange, intl }) {
}
const fileObject = e.target.files[0];
- if (fileObject.size < maxFileSize) {
+ if (!fileObject.type.startsWith('image')) {
+ setError(errorTypes.invalidFileType);
+ } else if (fileObject.size >= maxFileSize) {
+ setError(errorTypes.fileTooLarge);
+ } else {
+ setError(null);
const fileReader = new FileReader();
fileReader.addEventListener('load', () => onFileChange(fileReader.result));
fileReader.readAsDataURL(fileObject);
- } else {
- setFileTooLargeError(true);
}
}, []);
@@ -27,18 +35,19 @@ export default function ImageFileUpload({ onFileChange, intl }) {
<>
- {fileTooLargeError && (
+ {error && (
- {intl.formatMessage(messages['id.verification.id.photo.instructions.upload.error'])}
+ {intl.formatMessage(messages[`id.verification.id.photo.instructions.upload.error.${error}`])}
+
)}
>
diff --git a/src/id-verification/SupportedMediaTypes.jsx b/src/id-verification/SupportedMediaTypes.jsx
new file mode 100644
index 0000000..b2c6f18
--- /dev/null
+++ b/src/id-verification/SupportedMediaTypes.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+export default function SupportedMediaTypes() {
+ const SUPPORTED_TYPES = ['.png', '.jpeg', '.jpg', '.bmp', '.webp', '.tiff'];
+
+ const getSupportedTypes = () => SUPPORTED_TYPES.map((type, index) => {
+ if (index === SUPPORTED_TYPES.length - 1) {
+ return type;
+ }
+ return `${type}, `;
+ });
+
+ return {getSupportedTypes()};
+}
diff --git a/src/id-verification/panels/SummaryPanel.jsx b/src/id-verification/panels/SummaryPanel.jsx
index ee8d245..a11b466 100644
--- a/src/id-verification/panels/SummaryPanel.jsx
+++ b/src/id-verification/panels/SummaryPanel.jsx
@@ -14,6 +14,7 @@ import ImagePreview from '../ImagePreview';
import messages from '../IdVerification.messages';
import CameraHelpWithUpload from '../CameraHelpWithUpload';
+import SupportedMediaTypes from '../SupportedMediaTypes';
function SummaryPanel(props) {
const panelSlug = 'summary';
@@ -103,6 +104,14 @@ function SummaryPanel(props) {
if (submissionError.message.includes('Name')) {
return props.intl.formatMessage(messages['id.verification.submission.alert.error.name']);
}
+ if (submissionError.message.includes('unsupported format')) {
+ return (
+ <>
+ {props.intl.formatMessage(messages['id.verification.submission.alert.error.unsupported'])}
+
+ >
+ );
+ }
}
return (
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])}
+
diff --git a/src/id-verification/panels/TakePortraitPhotoPanel.jsx b/src/id-verification/panels/TakePortraitPhotoPanel.jsx
index f1d073a..b9656a5 100644
--- a/src/id-verification/panels/TakePortraitPhotoPanel.jsx
+++ b/src/id-verification/panels/TakePortraitPhotoPanel.jsx
@@ -12,6 +12,7 @@ import IdVerificationContext from '../IdVerificationContext';
import messages from '../IdVerification.messages';
import CollapsibleImageHelp from '../CollapsibleImageHelp';
+import SupportedMediaTypes from '../SupportedMediaTypes';
function TakePortraitPhotoPanel(props) {
const panelSlug = 'take-portrait-photo';
@@ -39,6 +40,7 @@ function TakePortraitPhotoPanel(props) {
{props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.upload'])}
+
diff --git a/src/id-verification/tests/panels/SummaryPanel.test.jsx b/src/id-verification/tests/panels/SummaryPanel.test.jsx
index b63ac2e..e7a9627 100644
--- a/src/id-verification/tests/panels/SummaryPanel.test.jsx
+++ b/src/id-verification/tests/panels/SummaryPanel.test.jsx
@@ -179,6 +179,21 @@ describe('SummaryPanel', () => {
);
});
+ it('displays correct error for unsupported file type', async () => {
+ dataService.submitIdVerification = jest.fn().mockReturnValue({
+ success: false,
+ status: 400,
+ message: 'Image data is in an unsupported format.',
+ });
+ await getPanel();
+ const button = await screen.findByTestId('submit-button');
+ await act(async () => fireEvent.click(button));
+ const error = await screen.getByTestId('submission-error');
+ expect(error).toHaveTextContent(
+ 'One or more of the files you have uploaded is in an unsupported format. Please choose from the following:',
+ );
+ });
+
it('does not show ID upload option if user is in experiment', async () => {
await getPanel();
const collapsible = await screen.queryByTestId('collapsible');