Merge pull request #292 from edx/bseverino/idv-improvements

[MST-353] IDV a11y and UX improvements
This commit is contained in:
Bianca Severino
2020-08-13 10:01:44 -04:00
committed by GitHub
9 changed files with 62 additions and 32 deletions

View File

@@ -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);

View File

@@ -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.',

View File

@@ -1,6 +1,6 @@
.page__id-verification {
.verification-panel {
.card.requirements {
.card.accent {
border-top: solid 4px theme-color('warning');
}
.image-preview {

View File

@@ -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 ?

View File

@@ -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'])}

View File

@@ -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'])}

View File

@@ -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'])}

View File

@@ -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>

View File

@@ -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'])}