diff --git a/src/id-verification/Camera.jsx b/src/id-verification/Camera.jsx index 360148a..0bd741a 100644 --- a/src/id-verification/Camera.jsx +++ b/src/id-verification/Camera.jsx @@ -1,101 +1,90 @@ import React from 'react'; import PropTypes from 'prop-types'; import CameraPhoto, { FACING_MODES } from 'jslib-html5-camera-photo'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import shutter from './data/camera-shutter.base64.json' - -const PHOTO_PROMTS = { - TAKE_PHOTO: 'Take Photo', - RETAKE_PHOTO: 'Retake Photo' -} +import shutter from './data/camera-shutter.base64.json'; +import messages from './IdVerification.messages'; class Camera extends React.Component { - constructor (props, context) { + constructor(props, context) { super(props, context); this.cameraPhoto = null; this.videoRef = React.createRef(); this.state = { trackedObject: null, - dataUri: '' - } + dataUri: '', + }; } - componentDidMount () { + componentDidMount() { this.cameraPhoto = new CameraPhoto(this.videoRef.current); - this.cameraPhoto.startCameraMaxResolution(FACING_MODES.USER) + this.cameraPhoto.startCameraMaxResolution(FACING_MODES.USER); } - takePhoto () { + takePhoto() { if (this.state.dataUri) { - return this.reset() + return this.reset(); } const config = { - sizeFactor: 1 + sizeFactor: 1, }; - - this.playShutterClick() - let dataUri = this.cameraPhoto.getDataUri(config); + + this.playShutterClick(); + const dataUri = this.cameraPhoto.getDataUri(config); this.setState({ dataUri }); - this.props.onImageCapture(dataUri) + this.props.onImageCapture(dataUri); } - playShutterClick () { - let audio = new Audio('data:audio/mp3;base64,' + shutter.base64); + playShutterClick() { + const audio = new Audio('data:audio/mp3;base64,' + shutter.base64); audio.play(); } - reset () { - this.setState({dataUri: ''}) + reset() { + this.setState({ dataUri: '' }); } - printTrackingInfo () { - let trackedObject = this.state.trackedObject; - if (!trackedObject) { - trackedObject = { - x: 'N/A', - y: 'N/A', - width: 'N/A', - height: 'N/A', - } - } - var res = ` - x: ${trackedObject.x} - y: ${trackedObject.y} - width: ${trackedObject.width} - height: ${trackedObject.height} - ` - return res - } - - render () { - let cameraFlashClass = this.state.dataUri ? 'do-transition camera-flash' : 'camera-flash'; + render() { + const cameraFlashClass = this.state.dataUri + ? 'do-transition camera-flash' + : 'camera-flash'; return ( -
-
-
+
+
+
- +
); } } Camera.propTypes = { + intl: intlShape.isRequired, onImageCapture: PropTypes.func.isRequired, -} +}; -export default Camera; \ No newline at end of file +export default injectIntl(Camera); diff --git a/src/id-verification/CameraHelp.jsx b/src/id-verification/CameraHelp.jsx index d02f409..f6fa094 100644 --- a/src/id-verification/CameraHelp.jsx +++ b/src/id-verification/CameraHelp.jsx @@ -1,19 +1,30 @@ import React from 'react'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -export default function CameraHelp() { +import messages from './IdVerification.messages'; + +function CameraHelp(props) { return ( -
-
What if I can't see the camera image or if I can't see my photo to determine which side is visible
-

- You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts - to get the camera positioning right. Optimal camera positioning varies with each computer, but generally the best position for - a headshot is approximately 12-18 inches (30-45 centimeters) from the camera, with your head centered relative to the computer screen. - If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle. - The most common reason for rejection is in ability to read the text on the ID card. -

-
What if I have difficulty holding my head in position relative to the camera?
-

If you require assistance with taking a photo for submission, contact edX support for additional suggestions.

-
+
+
+ {props.intl.formatMessage(messages['id.verification.camera.help.sight.question'])} +
+

+ {props.intl.formatMessage(messages['id.verification.camera.help.sight.answer'])} +

+
+ {props.intl.formatMessage(messages['id.verification.camera.help.head.question'])} +
+

+ {props.intl.formatMessage(messages['id.verification.camera.help.head.answer'])} +

+
); } + +CameraHelp.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(CameraHelp); diff --git a/src/id-verification/ExistingRequest.jsx b/src/id-verification/ExistingRequest.jsx new file mode 100644 index 0000000..9ec7b7e --- /dev/null +++ b/src/id-verification/ExistingRequest.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { getConfig } from '@edx/frontend-platform'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import messages from './IdVerification.messages'; + +function ExistingRequest(props) { + return ( +
+

+ {props.intl.formatMessage(messages['id.verification.existing.request.title'])} +

+ {props.status === 'pending' || props.status == 'approved' + ?

{props.intl.formatMessage(messages['id.verification.existing.request.pending.text'])}

+ :

{props.intl.formatMessage(messages['id.verification.existing.request.denied.text'])}

+ } + + {props.intl.formatMessage(messages['id.verification.return'])} + +
+ ); +} + +ExistingRequest.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(ExistingRequest); diff --git a/src/id-verification/IdVerification.messages.js b/src/id-verification/IdVerification.messages.js new file mode 100644 index 0000000..f48bd9b --- /dev/null +++ b/src/id-verification/IdVerification.messages.js @@ -0,0 +1,346 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + 'id.verification.next': { + id: 'id.verification.next', + defaultMessage: 'Next', + description: 'Next button.', + }, + 'id.verification.requirements.title': { + id: 'id.verification.requirements.title', + defaultMessage: 'Photo Verification Requirements', + description: 'Title for the Photo Verification Requirements page.', + }, + 'id.verification.requirements.description': { + id: 'id.verification.requirements.description', + defaultMessage: 'In order to complete Photo Verification online, you will need the following:', + description: 'Description for the Photo Verification Requirements page.', + }, + 'id.verification.requirements.card.device.title': { + id: 'id.verification.requirements.card.device.title', + defaultMessage: 'Device with Camera', + description: 'Title for the Device with Camera card.', + }, + 'id.verification.requirements.card.device.allow': { + id: 'id.verification.requirements.card.device.allow', + defaultMessage: 'Allow', + description: 'Bold text emphasizing that the user needs to click "allow" in order to enable the camera.', + }, + 'id.verification.requirements.card.id.title': { + id: 'id.verification.requirements.card.id.title', + defaultMessage: 'Photo Identification', + description: 'Title for the Photo Identification requirement card.', + }, + 'id.verification.requirements.card.id.text': { + id: 'id.verification.requirements.card.id.text', + defaultMessage: 'You need a valid ID that contains your full name and photo.', + description: 'Text that explains that the user needs a photo ID.', + }, + 'id.verification.privacy.title': { + id: 'id.verification.privacy.title', + defaultMessage: 'Privacy Information', + description: 'Title for Privacy Information.', + }, + 'id.verification.privacy.need.photo.question': { + id: 'id.verification.privacy.need.photo.question', + defaultMessage: 'Why does edX need my photo?', + description: 'Question about why edX needs a verification photo.', + }, + 'id.verification.privacy.need.photo.answer': { + id: 'id.verification.privacy.need.photo.answer', + defaultMessage: 'We use your verification photos to confirm your identity and ensure the validity of your certificate.', + description: 'Answering why edX needs a verification photo.', + }, + 'id.verification.privacy.do.with.photo.question': { + id: 'id.verification.privacy.do.with.photo.question', + defaultMessage: 'What does edX do with this photo?', + description: 'Question about what edX does with the verification photo.', + }, + 'id.verification.privacy.do.with.photo.answer': { + id: 'id.verification.privacy.do.with.photo.answer', + defaultMessage: 'We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.', + description: 'Answering what edX does with the verification photo.', + }, + 'id.verification.existing.request.title': { + id: 'id.verification.existing.request.title', + defaultMessage: 'Identity Verification', + description: 'Title for text that displays when user has already made a request.', + }, + 'id.verification.existing.request.pending.text': { + id: 'id.verification.existing.request.pending.text', + defaultMessage: 'You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days).', + description: 'Text that displays when user has a pending or approved request.', + }, + 'id.verification.existing.request.denied.text': { + id: 'id.verification.existing.request.denied.text', + defaultMessage: 'You cannot verify your identity at this time.', + description: 'Text that displays when user is denied from making a request.', + }, + 'id.verification.photo.take': { + id: 'id.verification.photo.take', + defaultMessage: 'Take Photo', + description: 'Button to take photo.', + }, + 'id.verification.photo.retake': { + id: 'id.verification.photo.retake', + defaultMessage: 'Retake Photo', + description: 'Button to retake photo.', + }, + 'id.verification.camera.access.title': { + id: 'id.verification.camera.access.title', + defaultMessage: 'Camera Permissions', + description: 'Title for the Camera Access page.', + }, + 'id.verification.camera.access.click.allow': { + id: 'id.verification.camera.access.click.allow', + defaultMessage: 'Please make sure to click "Allow"', + description: 'Instruction to allow camera access.', + }, + 'id.verification.camera.access.enable': { + id: 'id.verification.camera.access.enable', + defaultMessage: 'Enable Camera', + description: 'Text to enable camera.', + }, + 'id.verification.camera.access.problems': { + id: 'id.verification.camera.access.problems', + defaultMessage: 'Having problems?', + description: 'Text for when the user is having problems enabling camera access.', + }, + 'id.verification.camera.access.skip': { + id: 'id.verification.camera.access.skip', + defaultMessage: 'Skip and upload image files instead', + description: 'Text to skip camera access and enable image uploading.', + }, + 'id.verification.camera.access.success': { + id: 'id.verification.camera.access.success', + defaultMessage: 'Looks like your camera is working and ready.', + description: 'Text to confirm that camera is working.', + }, + 'id.verification.camera.access.failure': { + id: 'id.verification.camera.access.failure', + defaultMessage: 'It looks like we\'re unable to access your camera. You will need to upload image files of you and your photo id.', + description: 'Text indicating that the camera could not be accessed and image upload will be enabled.', + }, + 'id.verification.photo.tips.title': { + id: 'id.verification.photo.tips.title', + defaultMessage: 'Helpful Photo Tips', + description: 'Title for the Photo Tips page.', + }, + 'id.verification.photo.tips.description': { + id: 'id.verification.photo.tips.description', + defaultMessage: 'Next, we\'ll need you to take a photo of your face. Please review the helpful tips below.', + description: 'Description for the photo tips page.', + }, + 'id.verification.photo.tips.list.title': { + id: 'id.verification.photo.tips.list.title', + defaultMessage: 'Photo Tips', + description: 'Title for the list of photo tips.', + }, + 'id.verification.photo.tips.list.description': { + id: 'id.verification.photo.tips.list.description', + defaultMessage: 'To take a successful photo, make sure that:', + description: 'Description for the list of photo tips.', + }, + 'id.verification.photo.tips.list.well.lit': { + id: 'id.verification.photo.tips.list.well.lit', + defaultMessage: 'Your face is well-lit.', + description: 'Tip to make sure the user\'s face is well lit.', + }, + 'id.verification.photo.tips.list.inside.frame': { + id: 'id.verification.photo.tips.list.inside.frame', + defaultMessage: 'Your entire face fits inside the frame.', + description: 'Tip to make sure the user\'s face fits inside the frame.', + }, + 'id.verification.portrait.photo.title.camera': { + id: 'id.verification.portrait.photo.title.camera', + defaultMessage: 'Take Your Photo', + description: 'Title for the Portrait Photo page if camera access is enabled.', + }, + 'id.verification.portrait.photo.title.upload': { + id: 'id.verification.portrait.photo.title.upload', + defaultMessage: 'Upload Your Portrait Photo', + description: 'Title for the Portrait Photo page if camera access is disabled.', + }, + 'id.verification.portrait.photo.preview.alt': { + id: 'id.verification.portrait.photo.preview.alt', + defaultMessage: 'Preview of photo of user\'s face.', + description: 'Alt text for the portrait photo preview.', + }, + 'id.verification.portrait.photo.instructions.camera': { + id: 'id.verification.portrait.photo.instructions.camera', + defaultMessage: 'When your face is in position, use the Take Photo button below to take your photo.', + description: 'Instructions to use the camera to take a portrait photo..', + }, + '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)', + description: 'Instructions for portrait photo upload.', + }, + 'id.verification.camera.help.sight.question': { + id: 'id.verification.camera.help.sight.question', + defaultMessage: 'What if I can\'t see the camera image or if I can\'t see my photo to determine which side is visible?', + description: 'Question on what to do if the user cannot see the camera image or photo during verification.', + }, + 'id.verification.camera.help.sight.answer': { + id: 'id.verification.camera.help.sight.answer', + defaultMessage: 'You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts to get the camera positioning right. Optimal camera positioning varies with each computer, but generally the best position for a headshot is approximately 12-18 inches (30-45 centimeters) from the camera, with your head centered relative to the computer screen. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle. The most common reason for rejection is inability to read the text on the ID card.', + description: 'Confirming what to do if the camera image or photo cannot be seen during verification.', + }, + 'id.verification.camera.help.head.question': { + id: 'id.verification.camera.help.head.question', + defaultMessage: 'What if I have difficulty holding my head in position relative to the camera?', + description: 'Question on what to do if the user has difficulty holding their head relative to the camera.', + }, + 'id.verification.camera.help.head.answer': { + id: 'id.verification.camera.help.head.answer', + defaultMessage: 'If you require assistance with taking a photo for submission, contact edX support for additional suggestions.', + description: 'Confirming what to do if the user has difficult holding their head relative to the camera.', + }, + 'id.verification.id.tips.title': { + id: 'id.verification.id.tips.title', + defaultMessage: 'Helpful ID Tips', + description: 'Title for the ID Tips page.', + }, + 'id.verification.id.tips.description': { + id: 'id.verification.id.tips.description', + defaultMessage: 'Next you\'ll need an eligible ID photo, make sure that:', + description: 'Description for the ID Tips page.', + }, + 'id.verification.id.tips.list.well.lit': { + id: 'id.verification.id.tips.list.well.lit', + defaultMessage: 'Your ID is well-lit.', + description: 'Tip to ensure ID is well lit.', + }, + 'id.verification.id.tips.list.clear': { + id: 'id.verification.id.tips.list.clear', + defaultMessage: 'Ensure that you can see your photo and clearly read your name.', + description: 'Tip to ensure ID and name can be seen clearly.', + }, + 'id.verification.id.photo.title.camera': { + id: 'id.verification.id.photo.title.camera', + defaultMessage: 'Take ID Photo', + description: 'Title for the ID Photo page if camera access is enabled.', + }, + 'id.verification.id.photo.title.upload': { + id: 'id.verification.id.photo.title.upload', + defaultMessage: 'Upload Your ID Photo', + description: 'Title for the ID Photo page if camera access is disabled.', + }, + 'id.verification.id.photo.preview.alt': { + id: 'id.verification.id.photo.preview.alt', + defaultMessage: 'Preview of photo ID.', + description: 'Alt text for the ID photo preview.', + }, + 'id.verification.id.photo.instructions.camera': { + id: 'id.verification.id.photo.instructions.camera', + defaultMessage: 'When your ID is in position, use the Take Photo button below to take your photo.', + description: 'Instructions to use the camera to take an ID photo.', + }, + '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)', + description: 'Instructions for ID photo upload.', + }, + 'id.verification.account.name.title': { + id: 'id.verification.account.name.title', + defaultMessage: 'Account Name Check', + description: 'Title for the Account Name Check page.', + }, + 'id.verification.account.name.instructions': { + id: 'id.verification.account.name.instructions', + defaultMessage: 'Please check the Account Name below to ensure it matches the name on your ID. If not, click "Edit".', + description: 'Text to verify that the account name matches the name on the ID photo.', + }, + 'id.verification.account.name.warning.prefix': { + id: 'id.verification.account.name.warning.prefix', + defaultMessage: 'Please Note:', + description: 'Prefix to the warning that any change to the account name will be saved to the account.', + }, + 'id.verification.account.name.settings': { + id: 'id.verification.account.name.settings', + defaultMessage: 'Account Settings', + description: 'Link to Account Settings.', + }, + 'id.verification.account.name.label': { + id: 'id.verification.account.name.label', + defaultMessage: 'Name on ID', + description: 'Label for account name input.', + }, + 'id.verification.account.name.edit': { + id: 'id.verification.account.name.edit', + defaultMessage: 'Edit', + description: 'Button to edit account name.', + }, + 'id.verification.account.name.photo.alt': { + id: 'id.verification.account.name.photo.alt', + defaultMessage: 'Photo of your ID to be submitted.', + description: 'Alt text for the photo of the user\'s ID.', + }, + 'id.verification.account.name.save': { + id: 'id.verification.account.name.save', + defaultMessage: 'Save', + description: 'Button to save the account name.', + }, + 'id.verification.review.title': { + id: 'id.verification.review.title', + defaultMessage: 'Review Your Photos', + description: 'Title for the review your photos page.', + }, + 'id.verification.review.description': { + id: 'id.verification.review.description', + defaultMessage: 'Make sure we can verify your identity with the photos and information you have provided.', + description: 'Description for the review your photos page.', + }, + 'id.verification.review.portrait.label': { + id: 'id.verification.review.portrait.label', + defaultMessage: 'Your Portrait', + description: 'Label for the portrait card.', + }, + 'id.verification.review.portrait.alt': { + id: 'id.verification.review.portrait.alt', + defaultMessage: 'Photo of your face to be submitted.', + description: 'Alt text for the portrait photo.', + }, + 'id.verification.review.portrait.retake': { + id: 'id.verification.review.portrait.retake', + defaultMessage: 'Retake Portrait Photo', + description: 'Button to retake the portrait photo.', + }, + 'id.verification.review.id.label': { + id: 'id.verification.review.id.label', + defaultMessage: 'Your Photo ID', + description: 'Label for the Photo ID card.', + }, + 'id.verification.review.id.alt': { + id: 'id.verification.review.id.alt', + defaultMessage: 'Photo of your ID to be submitted.', + description: 'Alt text for the ID photo.', + }, + 'id.verification.review.id.retake': { + id: 'id.verification.review.id.retake', + defaultMessage: 'Retake ID Photo', + description: 'Button to retake the ID photo.', + }, + 'id.verification.review.confirm': { + id: 'id.verification.review.confirm', + defaultMessage: 'Confirm', + description: 'Button to confirm all information is correct.', + }, + 'id.verification.submitted.title': { + id: 'id.verification.submitted.title', + defaultMessage: 'Identity Verification in Progress', + description: 'Title for the submitted page.', + }, + 'id.verification.submitted.text': { + id: 'id.verification.submitted.text', + defaultMessage: 'We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days). In the meantime, you can still access all available course content.', + description: 'Text confirming that ID verification request has been received.', + }, + 'id.verification.return': { + id: 'id.verification.submitted.return', + defaultMessage: 'Return to Your Dashboard', + description: 'Button to return to the dashboard.', + }, +}); + +export default messages; diff --git a/src/id-verification/IdVerificationContext.jsx b/src/id-verification/IdVerificationContext.jsx index 187ae1a..e75dc56 100644 --- a/src/id-verification/IdVerificationContext.jsx +++ b/src/id-verification/IdVerificationContext.jsx @@ -1,11 +1,11 @@ import React, { useState, useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; import { AppContext } from '@edx/frontend-platform/react'; -import { getConfig } from '@edx/frontend-platform'; import { hasGetUserMediaSupport } from './getUserMediaShim'; import { getExistingIdVerification } from './data/service'; -import PageLoading from '../account-settings/PageLoading' +import PageLoading from '../account-settings/PageLoading'; +import ExistingRequest from './ExistingRequest'; const IdVerificationContext = React.createContext({}); @@ -54,35 +54,22 @@ function IdVerificationContextProvider({ children }) { }; // Call verification status endpoint to check whether we can verify. - useEffect(() => {(async () => { - const existingIdV = await getExistingIdVerification(); - setExistingIdVerification(existingIdV); - })()}, []); + useEffect(() => { + (async () => { + const existingIdV = await getExistingIdVerification(); + setExistingIdVerification(existingIdV); + })(); + }, []); // If we are waiting for verification status endpoint, show spinner. if (!existingIdVerification) { - return ; + return ; } if (!existingIdVerification.canVerify) { - const status = existingIdVerification.status; + const { status } = existingIdVerification; return ( -
-

Identity Verification

- {status === 'pending' || status == 'approved' - ?

- You have already submitted your verification information. - You will see a message on your dashboard when the verification process - is complete (usually within 1-2 days). -

- :

- You cannot verify your identity at this time. -

- } - - Return to Your Dashboard - -
+ ); } @@ -93,10 +80,7 @@ function IdVerificationContextProvider({ children }) { ); } IdVerificationContextProvider.propTypes = { - children: PropTypes.node, -}; -IdVerificationContextProvider.defaultProps = { - children: undefined, + children: PropTypes.node.isRequired, }; export { diff --git a/src/id-verification/IdVerificationPage.jsx b/src/id-verification/IdVerificationPage.jsx index 77e4850..f24a464 100644 --- a/src/id-verification/IdVerificationPage.jsx +++ b/src/id-verification/IdVerificationPage.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { connect } from 'react-redux'; import { Route, Switch, Redirect, useRouteMatch } from 'react-router-dom'; -import { injectIntl } from '@edx/frontend-platform/i18n'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Modal, Button } from '@edx/paragon'; import { idVerificationSelector } from './data/selectors'; import './getUserMediaShim'; @@ -17,57 +17,68 @@ import TakeIdPhotoPanel from './panels/TakeIdPhotoPanel'; import SummaryPanel from './panels/SummaryPanel'; import SubmittedPanel from './panels/SubmittedPanel'; +import messages from './IdVerification.messages'; + // eslint-disable-next-line react/prefer-stateless-function -function IdVerificationPage() { +function IdVerificationPage(props) { const { path } = useRouteMatch(); const [isModalOpen, setIsModalOpen] = useState(false); return ( -
-
-
- - - - - - - - - - - - - - - - -
-
- -
-
- - - -
Why does edX need my photo?
-

We use your verification photos to confirm your identity and ensure the validity of your certificate.

-
What does edX do with this photo?
-

We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.

+ <> + {/* If user reloads, redirect to the beginning of the process */} + +
+
+
+ + + + + + + + + + + + + + + +
- )} - onClose={() => setIsModalOpen(false)} - /> +
+ +
+
-
+ + +
{props.intl.formatMessage(messages['id.verification.privacy.need.photo.question'])}
+

{props.intl.formatMessage(messages['id.verification.privacy.need.photo.answer'])}

+
{props.intl.formatMessage(messages['id.verification.privacy.do.with.photo.question'])}
+

{props.intl.formatMessage(messages['id.verification.privacy.do.with.photo.answer'])}

+
+ )} + onClose={() => setIsModalOpen(false)} + /> + +
+ ); } + +IdVerificationPage.propTypes = { + intl: intlShape.isRequired, +}; + export default connect(idVerificationSelector, { })(injectIntl(IdVerificationPage)); diff --git a/src/id-verification/ImageFileUpload.jsx b/src/id-verification/ImageFileUpload.jsx index 00d7941..18593d8 100644 --- a/src/id-verification/ImageFileUpload.jsx +++ b/src/id-verification/ImageFileUpload.jsx @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; export default function ImageFileUpload({ onFileChange }) { const handleChange = useCallback((e) => { if (e.target.files.length === 0) { - // do something else return; } diff --git a/src/id-verification/data/service.js b/src/id-verification/data/service.js index 41dec84..b270239 100644 --- a/src/id-verification/data/service.js +++ b/src/id-verification/data/service.js @@ -40,12 +40,13 @@ export async function getExistingIdVerification() { * * Returns { success: Boolean, message: String|null } */ -export async function submitIdVerfication(verificationData) { +export async function submitIdVerification(verificationData) { const keyMap = { facePhotoFile: 'face_image', idPhotoFile: 'photo_id_image', idPhotoName: 'full_name', courseRunKey: 'course_id', + // Currently does not support a redirect back to the original course. See MST-282: https://openedx.atlassian.net/browse/MST-282 }; const postData = {}; // Don't include blank/null/undefined values. diff --git a/src/id-verification/index.js b/src/id-verification/index.js index fea8a5d..7b4ceea 100644 --- a/src/id-verification/index.js +++ b/src/id-verification/index.js @@ -1,4 +1 @@ export { default } from './IdVerificationPage'; -// export { default as reducer } from './data/reducers'; -// export { default as saga } from './data/sagas'; -// export { storeName } from './data/selectors'; diff --git a/src/id-verification/panels/GetNameIdPanel.jsx b/src/id-verification/panels/GetNameIdPanel.jsx index 9516f4a..f5fc936 100644 --- a/src/id-verification/panels/GetNameIdPanel.jsx +++ b/src/id-verification/panels/GetNameIdPanel.jsx @@ -1,13 +1,16 @@ import React, { useContext, useState, useEffect, useRef } from 'react'; import { Input, Button } from '@edx/paragon'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; import { IdVerificationContext } from '../IdVerificationContext'; import ImagePreview from '../ImagePreview'; -export default function GetNameIdPanel() { +import messages from '../IdVerification.messages'; + +function GetNameIdPanel(props) { const panelSlug = 'get-name-id'; const [isEditing, setIsEditing] = useState(false); const nameInputRef = useRef(); @@ -26,14 +29,26 @@ export default function GetNameIdPanel() { name={panelSlug} title="Account Name Check" > -

Please check the Account Name below to ensure it matches the name on your ID. If not, click "Edit".

+

+ {props.intl.formatMessage(messages['id.verification.account.name.instructions'])} +

- Please Note: any edit to your name will be saved to your account and can be reviewed on Account Settings. + {props.intl.formatMessage(messages['id.verification.account.name.warning.prefix'])}, + accountSettings: {props.intl.formatMessage(messages['id.verification.account.name.settings'])}, + }} + />
- +
setIsEditing(true)} > - Edit + {props.intl.formatMessage(messages['id.verification.account.name.edit'])} )}
@@ -57,14 +72,20 @@ export default function GetNameIdPanel() {
- {isEditing ? 'Save' : 'Next'} + {isEditing ? props.intl.formatMessage(messages['id.verification.account.name.save']) : props.intl.formatMessage(messages['id.verification.next'])}
); } + +GetNameIdPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(GetNameIdPanel); diff --git a/src/id-verification/panels/IdContextPanel.jsx b/src/id-verification/panels/IdContextPanel.jsx index efce411..6195f1f 100644 --- a/src/id-verification/panels/IdContextPanel.jsx +++ b/src/id-verification/panels/IdContextPanel.jsx @@ -1,33 +1,50 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; -export default function IdContextPanel() { +import messages from '../IdVerification.messages'; + +function IdContextPanel(props) { const panelSlug = 'id-context'; const nextPanelSlug = useNextPanelSlug(panelSlug); return ( -

Next you'll need an eligible photo, make sure that:

+

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

-
Photo Tips
-

To take a successful photo, make sure that:

+
+ {props.intl.formatMessage(messages['id.verification.photo.tips.list.title'])} +
+

+ {props.intl.formatMessage(messages['id.verification.photo.tips.list.description'])} +

    -
  • Your ID is well-lit.
  • -
  • Ensure that you can see your photo and clearly read your name.
  • +
  • + {props.intl.formatMessage(messages['id.verification.id.tips.list.well.lit'])} +
  • +
  • + {props.intl.formatMessage(messages['id.verification.id.tips.list.clear'])} +
- Next + {props.intl.formatMessage(messages['id.verification.next'])}
); } + +IdContextPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(IdContextPanel); diff --git a/src/id-verification/panels/PortraitPhotoContextPanel.jsx b/src/id-verification/panels/PortraitPhotoContextPanel.jsx index 9d243e7..ca1db33 100644 --- a/src/id-verification/panels/PortraitPhotoContextPanel.jsx +++ b/src/id-verification/panels/PortraitPhotoContextPanel.jsx @@ -1,33 +1,52 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; -export default function PortraitPhotoContextPanel() { +import messages from '../IdVerification.messages'; + +function PortraitPhotoContextPanel(props) { const panelSlug = 'portrait-photo-context'; const nextPanelSlug = useNextPanelSlug(panelSlug); return ( -

Next, we'll need you to take a photo of your face. Please review the helpful tips below.

+

+ {props.intl.formatMessage(messages['id.verification.photo.tips.description'])} +

-
Photo Tips
-

To take a successful photo, make sure that:

+
+ {props.intl.formatMessage(messages['id.verification.photo.tips.list.title'])} +
+

+ {props.intl.formatMessage(messages['id.verification.photo.tips.list.description'])} +

    -
  • Your face is well-lit.
  • -
  • Your entire face fits inside the frame.
  • +
  • + {props.intl.formatMessage(messages['id.verification.photo.tips.list.well.lit'])} +
  • +
  • + {props.intl.formatMessage(messages['id.verification.photo.tips.list.inside.frame'])} +
- Next + {props.intl.formatMessage(messages['id.verification.next'])}
); } + +PortraitPhotoContextPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(PortraitPhotoContextPanel); diff --git a/src/id-verification/panels/RequestCameraAccessPanel.jsx b/src/id-verification/panels/RequestCameraAccessPanel.jsx index ae811fc..76140a9 100644 --- a/src/id-verification/panels/RequestCameraAccessPanel.jsx +++ b/src/id-verification/panels/RequestCameraAccessPanel.jsx @@ -1,12 +1,15 @@ import React, { useContext } from 'react'; import { Link } from 'react-router-dom'; import { Collapsible } from '@edx/paragon'; +import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; import { IdVerificationContext, MEDIA_ACCESS } from '../IdVerificationContext'; -export default function RequestCameraAccessPanel() { +import messages from '../IdVerification.messages'; + +function RequestCameraAccessPanel(props) { const panelSlug = 'request-camera-access'; const nextPanelSlug = useNextPanelSlug(panelSlug); const { tryGetUserMedia, mediaAccess } = useContext(IdVerificationContext); @@ -14,25 +17,33 @@ export default function RequestCameraAccessPanel() { return ( {mediaAccess === MEDIA_ACCESS.PENDING && (
-

In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. Please make sure to click "Allow"

- +

+ {props.intl.formatMessage(messages['id.verification.camera.access.click.allow'])}, + }} + /> +

- Having problems? + {props.intl.formatMessage(messages['id.verification.camera.access.problems'])} - Skip and upload image files instead + {props.intl.formatMessage(messages['id.verification.camera.access.skip'])} @@ -43,11 +54,11 @@ export default function RequestCameraAccessPanel() { {mediaAccess === MEDIA_ACCESS.GRANTED && (

- Looks like your camera is working and ready. + {props.intl.formatMessage(messages['id.verification.camera.access.success'])}

- Next + {props.intl.formatMessage(messages['id.verification.next'])}
@@ -56,12 +67,11 @@ export default function RequestCameraAccessPanel() { {[MEDIA_ACCESS.UNSUPPORTED, MEDIA_ACCESS.DENIED].includes(mediaAccess) && (

- It looks like we're unable to access your camera. You will need to upload - image files of you and your photo id. + {props.intl.formatMessage(messages['id.verification.camera.access.failure'])}

- Next + {props.intl.formatMessage(messages['id.verification.next'])}
@@ -70,3 +80,9 @@ export default function RequestCameraAccessPanel() { ); } + +RequestCameraAccessPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(RequestCameraAccessPanel); diff --git a/src/id-verification/panels/ReviewRequirementsPanel.jsx b/src/id-verification/panels/ReviewRequirementsPanel.jsx index 1a8512a..c44d7ba 100644 --- a/src/id-verification/panels/ReviewRequirementsPanel.jsx +++ b/src/id-verification/panels/ReviewRequirementsPanel.jsx @@ -1,10 +1,13 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; -export default function ReviewRequirementsPanel() { +import messages from '../IdVerification.messages'; + +function ReviewRequirementsPanel(props) { const panelSlug = 'review-requirements'; const nextPanelSlug = useNextPanelSlug(panelSlug); return ( @@ -14,31 +17,62 @@ export default function ReviewRequirementsPanel() { focusOnMount={false} >

- In order to complete Photo Verification online, you will need the following + {props.intl.formatMessage(messages['id.verification.requirements.description'])}

-
Device with Camera
-

You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click Allow.

+
+ {props.intl.formatMessage(messages['id.verification.requirements.card.device.title'])} +
+

+ {props.intl.formatMessage(messages['id.verification.requirements.card.device.allow'])}, + }} + /> +

-
Photo Identification
-

You need a valid ID that contains your full name and photo.

+
+ {props.intl.formatMessage(messages['id.verification.requirements.card.id.title'])} +
+

+ {props.intl.formatMessage(messages['id.verification.requirements.card.id.text'])} +

-

Privacy Information

-
Why does edX need my photo?
-

We use your verification photos to confirm your identity and ensure the validity of your certificate.

-
What does edX do with this photo?
-

We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.

+

+ {props.intl.formatMessage(messages['id.verification.privacy.title'])} +

+
+ {props.intl.formatMessage(messages['id.verification.privacy.need.photo.question'])} +
+

+ {props.intl.formatMessage(messages['id.verification.privacy.need.photo.answer'])} +

+
+ {props.intl.formatMessage(messages['id.verification.privacy.do.with.photo.question'])} +
+

+ {props.intl.formatMessage(messages['id.verification.privacy.do.with.photo.answer'])} +

- Next + {props.intl.formatMessage(messages['id.verification.next'])}
); } + +ReviewRequirementsPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(ReviewRequirementsPanel); diff --git a/src/id-verification/panels/SubmittedPanel.jsx b/src/id-verification/panels/SubmittedPanel.jsx index 634d9ba..1ca4969 100644 --- a/src/id-verification/panels/SubmittedPanel.jsx +++ b/src/id-verification/panels/SubmittedPanel.jsx @@ -1,18 +1,30 @@ import React from 'react'; import { getConfig } from '@edx/frontend-platform'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; -export default function SubmittedPanel() { +import messages from '../IdVerification.messages'; + +function SubmittedPanel(props) { const panelSlug = 'submitted'; return ( -

We have received you information and are verifiying your identity. You will see a message on your dashboard when the verification process is complete (usually within 1-2 days). In the meantime, you can still access all available course content.

- Return to Your Dashboard +

+ {props.intl.formatMessage(messages['id.verification.submitted.text'])} +

+ + {props.intl.formatMessage(messages['id.verification.return'])} +
); } + +SubmittedPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(SubmittedPanel); diff --git a/src/id-verification/panels/SummaryPanel.jsx b/src/id-verification/panels/SummaryPanel.jsx index 3ff8476..4a016ee 100644 --- a/src/id-verification/panels/SummaryPanel.jsx +++ b/src/id-verification/panels/SummaryPanel.jsx @@ -2,14 +2,16 @@ import React, { useContext } from 'react'; import { history } from '@edx/frontend-platform'; import { Input, Button } from '@edx/paragon'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; -import { submitIdVerfication } from '../data/service'; import BasePanel from './BasePanel'; import { IdVerificationContext } from '../IdVerificationContext'; import ImagePreview from '../ImagePreview'; -export default function SummaryPanel() { +import messages from '../IdVerification.messages'; + +function SummaryPanel(props) { const panelSlug = 'summary'; const nextPanelSlug = useNextPanelSlug(panelSlug); const { @@ -19,22 +21,15 @@ export default function SummaryPanel() { idPhotoName, } = useContext(IdVerificationContext); const nameToBeUsed = idPhotoName || nameOnAccount || ''; - const courseRunKey = null; // TODO: Implement course run key + // TODO: Implement course run key function SubmitButton() { - function handleClick(e) { - const verificationData = { - facePhotoFile, - idPhotoFile, - idPhotoName, - courseRunKey, - }; - const { success, message } = submitIdVerfication(verificationData); + function handleClick() { history.push(nextPanelSlug); } return ( - ); } @@ -42,16 +37,20 @@ export default function SummaryPanel() { return ( -

Make sure we can verify your identity with the photos and information you have provided.

+

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

- + - Retake Portrait Photo + {props.intl.formatMessage(messages['id.verification.review.portrait.retake'])}
- + - Retake ID Photo + {props.intl.formatMessage(messages['id.verification.review.id.retake'])}
- +
- Edit + {props.intl.formatMessage(messages['id.verification.account.name.edit'])}
@@ -107,3 +110,9 @@ export default function SummaryPanel() {
); } + +SummaryPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(SummaryPanel); diff --git a/src/id-verification/panels/TakeIdPhotoPanel.jsx b/src/id-verification/panels/TakeIdPhotoPanel.jsx index 3932e0c..67e6173 100644 --- a/src/id-verification/panels/TakeIdPhotoPanel.jsx +++ b/src/id-verification/panels/TakeIdPhotoPanel.jsx @@ -1,5 +1,6 @@ import React, { useContext } from 'react'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; @@ -9,7 +10,9 @@ import Camera from '../Camera'; import CameraHelp from '../CameraHelp'; import { IdVerificationContext, MEDIA_ACCESS } from '../IdVerificationContext'; -export default function TakeIdPhotoPanel() { +import messages from '../IdVerification.messages'; + +function TakeIdPhotoPanel(props) { const panelSlug = 'take-id-photo'; const nextPanelSlug = useNextPanelSlug(panelSlug); const { setIdPhotoFile, idPhotoFile, mediaAccess } = useContext(IdVerificationContext); @@ -17,29 +20,39 @@ export default function TakeIdPhotoPanel() { return (
- {idPhotoFile && !shouldUseCamera && } + {idPhotoFile && !shouldUseCamera && } {shouldUseCamera ? (
-

When your ID is in position, use the Take Photo button below to take your photo.

+

+ {props.intl.formatMessage(messages['id.verification.id.photo.instructions.camera'])} +

) : (
-

Please upload a ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)

+

+ {props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])} +

)}
- Next + {props.intl.formatMessage(messages['id.verification.next'])}
- {shouldUseCamera && } + {shouldUseCamera && }
); } + +TakeIdPhotoPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(TakeIdPhotoPanel); diff --git a/src/id-verification/panels/TakePortraitPhotoPanel.jsx b/src/id-verification/panels/TakePortraitPhotoPanel.jsx index f5c6846..806391f 100644 --- a/src/id-verification/panels/TakePortraitPhotoPanel.jsx +++ b/src/id-verification/panels/TakePortraitPhotoPanel.jsx @@ -1,5 +1,6 @@ import React, { useContext } from 'react'; import { Link } from 'react-router-dom'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useNextPanelSlug } from '../routing-utilities'; import BasePanel from './BasePanel'; @@ -9,7 +10,9 @@ import Camera from '../Camera'; import CameraHelp from '../CameraHelp'; import { IdVerificationContext, MEDIA_ACCESS } from '../IdVerificationContext'; -export default function TakePortraitPhotoPanel() { +import messages from '../IdVerification.messages'; + +function TakePortraitPhotoPanel(props) { const panelSlug = 'take-portrait-photo'; const nextPanelSlug = useNextPanelSlug(panelSlug); const { setFacePhotoFile, facePhotoFile, mediaAccess } = useContext(IdVerificationContext); @@ -18,29 +21,39 @@ export default function TakePortraitPhotoPanel() { return (
- {facePhotoFile && !shouldUseCamera && } + {facePhotoFile && !shouldUseCamera && } {shouldUseCamera ? (
-

When your face is in position, use the Take Photo button below to take your photo.

+

+ {props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.camera'])} +

) : (
-

Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)

+

+ {props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.upload'])} +

)}
- Next + {props.intl.formatMessage(messages['id.verification.next'])}
- {shouldUseCamera && } + {shouldUseCamera && }
); } + +TakePortraitPhotoPanel.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(TakePortraitPhotoPanel); diff --git a/src/id-verification/routing-utilities.js b/src/id-verification/routing-utilities.js index 96da242..a115b79 100644 --- a/src/id-verification/routing-utilities.js +++ b/src/id-verification/routing-utilities.js @@ -30,10 +30,6 @@ export const useNextPanelSlug = (originSlug) => { // check if the user is too far into the flow and if so, return the slug of the // furthest panel they are allow to be. export const useVerificationRedirectSlug = (slug) => { - // TODO: remove this short-circuit after development is done - return null; - - // eslint-disable-next-line no-unreachable const { facePhotoFile, idPhotoFile } = useContext(IdVerificationContext); const indexOfCurrentPanel = panelSteps.indexOf(slug);