diff --git a/src/id-verification/CameraHelpWithUpload.jsx b/src/id-verification/CameraHelpWithUpload.jsx index ec46a36..3926ec3 100644 --- a/src/id-verification/CameraHelpWithUpload.jsx +++ b/src/id-verification/CameraHelpWithUpload.jsx @@ -35,7 +35,7 @@ function CameraHelpWithUpload(props) {

{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 33119fa..de765a0 100644 --- a/src/id-verification/IdVerification.messages.js +++ b/src/id-verification/IdVerification.messages.js @@ -403,9 +403,14 @@ 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. (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: .jpg, .jpeg, .png)', description: 'Instructions for ID photo upload.', }, + 'id.verification.id.photo.instructions.upload.error': { + id: 'id.verification.id.photo.instructions.upload.error', + 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.', + }, 'id.verification.account.name.title': { id: 'id.verification.account.name.title', defaultMessage: 'Account Name Check', @@ -506,6 +511,11 @@ const messages = defineMessages({ defaultMessage: 'Submit', description: 'Button to confirm all information is correct and submit.', }, + 'id.verification.review.error': { + id: 'id.verification.review.error', + defaultMessage: 'edX Support Page', + description: 'Text linking to the support page.', + }, 'id.verification.submitted.title': { id: 'id.verification.submitted.title', defaultMessage: 'Identity Verification in Progress', diff --git a/src/id-verification/ImageFileUpload.jsx b/src/id-verification/ImageFileUpload.jsx index 9d52ea1..ff607c6 100644 --- a/src/id-verification/ImageFileUpload.jsx +++ b/src/id-verification/ImageFileUpload.jsx @@ -1,28 +1,52 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; +import { intlShape } from '@edx/frontend-platform/i18n'; import PropTypes from 'prop-types'; +import { Alert } from '@edx/paragon'; +import messages from './IdVerification.messages'; + + +export default function ImageFileUpload({ onFileChange, intl }) { + const [fileTooLargeError, setFileTooLargeError] = useState(false); + const maxFileSize = 10000000; -export default function ImageFileUpload({ onFileChange }) { const handleChange = useCallback((e) => { if (e.target.files.length === 0) { return; } const fileObject = e.target.files[0]; - const fileReader = new FileReader(); - fileReader.addEventListener('load', () => onFileChange(fileReader.result)); - fileReader.readAsDataURL(fileObject); + if (fileObject.size < maxFileSize) { + const fileReader = new FileReader(); + fileReader.addEventListener('load', () => onFileChange(fileReader.result)); + fileReader.readAsDataURL(fileObject); + } else { + setFileTooLargeError(true); + } }, []); return ( - + <> + + {fileTooLargeError && ( + + {intl.formatMessage(messages['id.verification.id.photo.instructions.upload.error'])} + + )} + ); } ImageFileUpload.propTypes = { onFileChange: PropTypes.func.isRequired, + intl: intlShape.isRequired, }; diff --git a/src/id-verification/panels/SummaryPanel.jsx b/src/id-verification/panels/SummaryPanel.jsx index 141e5ef..e41f18c 100644 --- a/src/id-verification/panels/SummaryPanel.jsx +++ b/src/id-verification/panels/SummaryPanel.jsx @@ -1,6 +1,6 @@ import React, { useState, useContext } from 'react'; import { history } from '@edx/frontend-platform'; -import { Input, Button, Spinner } from '@edx/paragon'; +import { Input, Button, Spinner, Alert } from '@edx/paragon'; import { Link } from 'react-router-dom'; import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; @@ -25,6 +25,7 @@ function SummaryPanel(props) { } = useContext(IdVerificationContext); const nameToBeUsed = idPhotoName || nameOnAccount || ''; const [isSubmitting, setIsSubmitting] = useState(false); + const [submissionError, setSubmissionError] = useState(false); function SubmitButton() { async function handleClick() { @@ -39,6 +40,10 @@ function SummaryPanel(props) { if (result.success) { stopUserMedia(); history.push(nextPanelSlug); + } else { + stopUserMedia(); + setIsSubmitting(false); + setSubmissionError(true); } } return ( @@ -59,6 +64,24 @@ function SummaryPanel(props) { name={panelSlug} title={props.intl.formatMessage(messages['id.verification.review.title'])} > + {submissionError && + setSubmissionError(false)} + > + {props.intl.formatMessage(messages['id.verification.review.error'])} }} + /> + }

{props.intl.formatMessage(messages['id.verification.review.description'])}

diff --git a/src/id-verification/tests/panels/SummaryPanel.test.jsx b/src/id-verification/tests/panels/SummaryPanel.test.jsx index 54aecdf..4f4c1bc 100644 --- a/src/id-verification/tests/panels/SummaryPanel.test.jsx +++ b/src/id-verification/tests/panels/SummaryPanel.test.jsx @@ -5,7 +5,7 @@ import { render, cleanup, act, screen, fireEvent, waitFor } from '@testing-libra import '@edx/frontend-platform/analytics'; import '@testing-library/jest-dom/extend-expect'; import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; -import { submitIdVerification } from '../../data/service'; +import * as dataService from '../../data/service'; import { IdVerificationContext } from '../../IdVerificationContext'; import SummaryPanel from '../../panels/SummaryPanel'; @@ -13,9 +13,8 @@ jest.mock('@edx/frontend-platform/analytics', () => ({ sendTrackEvent: jest.fn(), })); -jest.mock('../../data/service', () => ({ - submitIdVerification: jest.fn(() => ({ success: true, message: null })), -})); +jest.mock('../../data/service'); +dataService.submitIdVerification = jest.fn().mockReturnValue({ success: true }); const IntlSummaryPanel = injectIntl(SummaryPanel); @@ -74,7 +73,26 @@ describe('SummaryPanel', () => { it('submits', async () => { const button = await screen.findByTestId('submit-button'); fireEvent.click(button); - expect(submitIdVerification).toHaveBeenCalled(); - await waitFor(() => expect(contextValue.stopUserMedia).toHaveBeenCalled()) + expect(dataService.submitIdVerification).toHaveBeenCalled(); + await waitFor(() => expect(contextValue.stopUserMedia).toHaveBeenCalled()); + }); + + it('shows error when cannot submit', async () => { + await cleanup(); + dataService.submitIdVerification = jest.fn().mockReturnValue({ success: false }); + await act(async () => render(( + + + + + + + + ))); + const button = await screen.findByTestId('submit-button'); + await act(async () => fireEvent.click(button)); + expect(dataService.submitIdVerification).toHaveBeenCalled(); + const error = await screen.getByTestId('submission-error'); + expect(error).toBeDefined(); }); });