Merge pull request #292 from edx/bseverino/idv-improvements
[MST-353] IDV a11y and UX improvements
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Collapsible } from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
@@ -12,18 +13,20 @@ function CameraHelp(props) {
|
||||
styling="card"
|
||||
title={props.intl.formatMessage(messages['id.verification.camera.help.sight.question'])}
|
||||
className="mb-4 shadow"
|
||||
defaultOpen={props.isOpen}
|
||||
>
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.camera.help.sight.answer'])}
|
||||
{props.intl.formatMessage(messages[`id.verification.camera.help.sight.answer.${props.isPortrait ? 'portrait' : 'id'}`])}
|
||||
</p>
|
||||
</Collapsible>
|
||||
<Collapsible
|
||||
styling="card"
|
||||
title={props.intl.formatMessage(messages['id.verification.camera.help.head.question'])}
|
||||
title={props.intl.formatMessage(messages[`id.verification.camera.help.difficulty.question.${props.isPortrait ? 'portrait' : 'id'}`])}
|
||||
className="mb-4 shadow"
|
||||
defaultOpen={props.isOpen}
|
||||
>
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.camera.help.head.answer'])}
|
||||
{props.intl.formatMessage(messages['id.verification.camera.help.difficulty.answer'])}
|
||||
</p>
|
||||
</Collapsible>
|
||||
</div>
|
||||
@@ -32,6 +35,13 @@ function CameraHelp(props) {
|
||||
|
||||
CameraHelp.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
isOpen: PropTypes.bool,
|
||||
isPortrait: PropTypes.bool,
|
||||
};
|
||||
|
||||
CameraHelp.defaultProps = {
|
||||
isOpen: false,
|
||||
isPortrait: false,
|
||||
};
|
||||
|
||||
export default injectIntl(CameraHelp);
|
||||
|
||||
@@ -191,18 +191,28 @@ const messages = defineMessages({
|
||||
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.sight.answer.portrait': {
|
||||
id: 'id.verification.camera.help.sight.answer.portrait',
|
||||
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.',
|
||||
description: 'Confirming what to do if the camera image of the portrait cannot be seen during verification.',
|
||||
},
|
||||
'id.verification.camera.help.head.question': {
|
||||
id: 'id.verification.camera.help.head.question',
|
||||
'id.verification.camera.help.sight.answer.id': {
|
||||
id: 'id.verification.camera.help.sight.answer.id',
|
||||
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 photo of an ID card is 8-12 inches (20-30 centimeters) from the camera, with the ID card centered relative to the camera. 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 of the ID cannot be seen during verification.',
|
||||
},
|
||||
'id.verification.camera.help.difficulty.question.portrait': {
|
||||
id: 'id.verification.camera.help.difficulty.question.portrait',
|
||||
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',
|
||||
'id.verification.camera.help.difficulty.question.id': {
|
||||
id: 'id.verification.camera.help.difficulty.question.id',
|
||||
defaultMessage: 'What if I have difficulty holding my ID in position relative to the camera?',
|
||||
description: 'Question on what to do if the user has difficulty holding their ID relative to the camera.',
|
||||
},
|
||||
'id.verification.camera.help.difficulty.answer': {
|
||||
id: 'id.verification.camera.help.difficulty.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.',
|
||||
},
|
||||
@@ -213,7 +223,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'id.verification.id.tips.description': {
|
||||
id: 'id.verification.id.tips.description',
|
||||
defaultMessage: 'Next, we\'ll need you to take a photo of a valid ID that includes your name. Please have your ID ready. Make sure that:',
|
||||
defaultMessage: 'Next, we\'ll need you to take a photo of a valid ID that includes your name. Please have your ID ready.',
|
||||
description: 'Description for the ID Tips page.',
|
||||
},
|
||||
'id.verification.id.tips.list.well.lit': {
|
||||
@@ -296,11 +306,6 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Account Name',
|
||||
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.',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.page__id-verification {
|
||||
.verification-panel {
|
||||
.card.requirements {
|
||||
.card.accent {
|
||||
border-top: solid 4px theme-color('warning');
|
||||
}
|
||||
.image-preview {
|
||||
|
||||
@@ -21,6 +21,7 @@ function GetNameIdPanel(props) {
|
||||
} = useContext(IdVerificationContext);
|
||||
const nameOnAccountValue = nameOnAccount || '';
|
||||
const invalidName = !nameMatches && (!idPhotoName || idPhotoName === nameOnAccount);
|
||||
const blankName = !nameOnAccount && !idPhotoName;
|
||||
|
||||
useEffect(() => {
|
||||
setIdPhotoName('');
|
||||
@@ -36,7 +37,10 @@ function GetNameIdPanel(props) {
|
||||
user_id: userId,
|
||||
});
|
||||
}
|
||||
}, [nameMatches]);
|
||||
if (blankName) {
|
||||
setNameMatches(false);
|
||||
}
|
||||
}, [nameMatches, blankName]);
|
||||
|
||||
return (
|
||||
<BasePanel
|
||||
@@ -60,6 +64,7 @@ function GetNameIdPanel(props) {
|
||||
data-testid="name-matches-yes"
|
||||
label={props.intl.formatMessage(messages['id.verification.account.name.radio.yes'])}
|
||||
checked={nameMatches}
|
||||
disabled={!nameOnAccount}
|
||||
inline
|
||||
onChange={() => {
|
||||
setNameMatches(true);
|
||||
@@ -72,8 +77,9 @@ function GetNameIdPanel(props) {
|
||||
name="nameMatches"
|
||||
data-testid="name-matches-no"
|
||||
label={props.intl.formatMessage(messages['id.verification.account.name.radio.no'])}
|
||||
inline
|
||||
checked={!nameMatches}
|
||||
disabled={!nameOnAccount}
|
||||
inline
|
||||
onChange={() => setNameMatches(false)}
|
||||
/>
|
||||
</Form.Row>
|
||||
@@ -88,7 +94,7 @@ function GetNameIdPanel(props) {
|
||||
type="text"
|
||||
ref={nameInputRef}
|
||||
readOnly={nameMatches}
|
||||
isInvalid={invalidName}
|
||||
isInvalid={invalidName || blankName}
|
||||
aria-describedby="photo-id-name-feedback"
|
||||
value={
|
||||
!nameMatches ?
|
||||
@@ -107,9 +113,9 @@ function GetNameIdPanel(props) {
|
||||
<div className="action-row">
|
||||
<Link
|
||||
to={nextPanelSlug}
|
||||
className={`btn btn-primary ${invalidName && 'disabled'}`}
|
||||
className={`btn btn-primary ${(invalidName || blankName) && 'disabled'}`}
|
||||
data-testid="next-button"
|
||||
aria-disabled={invalidName}
|
||||
aria-disabled={invalidName || blankName}
|
||||
>
|
||||
{
|
||||
!nameMatches ?
|
||||
|
||||
@@ -4,7 +4,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useNextPanelSlug } from '../routing-utilities';
|
||||
import BasePanel from './BasePanel';
|
||||
|
||||
import CameraHelp from '../CameraHelp';
|
||||
import messages from '../IdVerification.messages';
|
||||
|
||||
function IdContextPanel(props) {
|
||||
@@ -16,7 +16,7 @@ function IdContextPanel(props) {
|
||||
title={props.intl.formatMessage(messages['id.verification.id.tips.title'])}
|
||||
>
|
||||
<p>{props.intl.formatMessage(messages['id.verification.id.tips.description'])}</p>
|
||||
<div className="card mb-4 shadow">
|
||||
<div className="card mb-4 shadow accent">
|
||||
<div className="card-body">
|
||||
<h6>
|
||||
{props.intl.formatMessage(messages['id.verification.photo.tips.list.title'])}
|
||||
@@ -34,6 +34,7 @@ function IdContextPanel(props) {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<CameraHelp isOpen />
|
||||
<div className="action-row">
|
||||
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
|
||||
{props.intl.formatMessage(messages['id.verification.next'])}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useNextPanelSlug } from '../routing-utilities';
|
||||
import BasePanel from './BasePanel';
|
||||
|
||||
import CameraHelp from '../CameraHelp';
|
||||
import messages from '../IdVerification.messages';
|
||||
|
||||
function PortraitPhotoContextPanel(props) {
|
||||
@@ -18,7 +18,7 @@ function PortraitPhotoContextPanel(props) {
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.photo.tips.description'])}
|
||||
</p>
|
||||
<div className="card mb-4 shadow">
|
||||
<div className="card mb-4 shadow accent">
|
||||
<div className="card-body">
|
||||
<h6>
|
||||
{props.intl.formatMessage(messages['id.verification.photo.tips.list.title'])}
|
||||
@@ -36,6 +36,7 @@ function PortraitPhotoContextPanel(props) {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<CameraHelp isOpen isPortrait />
|
||||
<div className="action-row">
|
||||
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
|
||||
{props.intl.formatMessage(messages['id.verification.next'])}
|
||||
|
||||
@@ -30,7 +30,7 @@ function ReviewRequirementsPanel(props) {
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.requirements.description'])}
|
||||
</p>
|
||||
<div className="card mb-4 shadow requirements">
|
||||
<div className="card mb-4 shadow accent">
|
||||
<div className="card-body">
|
||||
<h6 aria-level="3">
|
||||
{props.intl.formatMessage(messages['id.verification.requirements.card.device.title'])}
|
||||
@@ -47,7 +47,7 @@ function ReviewRequirementsPanel(props) {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card mb-4 shadow requirements">
|
||||
<div className="card mb-4 shadow accent">
|
||||
<div className="card-body">
|
||||
<h6 aria-level="3">
|
||||
{props.intl.formatMessage(messages['id.verification.requirements.card.id.title'])}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useContext } from 'react';
|
||||
import { history } from '@edx/frontend-platform';
|
||||
import { Input, Button, Spinner } from '@edx/paragon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { submitIdVerification } from '../data/service';
|
||||
import { useNextPanelSlug } from '../routing-utilities';
|
||||
@@ -119,7 +119,14 @@ function SummaryPanel(props) {
|
||||
state: { fromSummary: true },
|
||||
}}
|
||||
>
|
||||
{props.intl.formatMessage(messages['id.verification.account.name.edit'])}
|
||||
<FormattedMessage
|
||||
id="id.verification.account.name.edit"
|
||||
defaultMessage="Edit{sr}"
|
||||
description="Button to edit account name, with clarifying information for screen readers."
|
||||
values={{
|
||||
sr: <span className="sr-only">Account Name</span>,
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ function TakePortraitPhotoPanel(props) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{shouldUseCamera && <CameraHelp />}
|
||||
{shouldUseCamera && <CameraHelp isPortrait />}
|
||||
<div className="action-row" style={{ visibility: facePhotoFile ? 'unset' : 'hidden' }}>
|
||||
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
|
||||
{props.intl.formatMessage(messages['id.verification.next'])}
|
||||
|
||||
Reference in New Issue
Block a user