Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@
.document-capture-file-image--loading {
@extend %pad-common-id-card;
}
// Styles for the text that appears over the selfie capture screen to help users position their face for a good photo
.document-capture-selfie-feedback {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following prior comment, an advantage of BEM is it generally avoids issues with needing to qualify (nest) CSS selectors, so I'd expect this to be defined at a top-level.

left: 50%;
top: 10%;
position: fixed;
transform: translateX(-50%);
z-index: 11;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ function AcuantCapture(
const [attempt, incrementAttempt] = useCounter(1);
const [acuantFailureCookie, setAcuantFailureCookie, refreshAcuantFailureCookie] =
useCookie('AcuantCameraHasFailed');
const [imageCaptureText, setImageCaptureText] = useState('');
// There's some pretty significant changes to this component when it's used for
// selfie capture vs document image capture. This controls those changes.
const selfieCapture = name === 'selfie';
Expand Down Expand Up @@ -653,6 +654,10 @@ function AcuantCapture(
});
}

function onImageCaptureFeedback(text: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I keep waffling whether it's better to have this function, or remove it and just use setImageCaptureText 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's six one way and a half a dozen the other. 🤷🏻‍♀️

setImageCaptureText(text);
}

return (
<div className={[className, 'document-capture-acuant-capture'].filter(Boolean).join(' ')}>
{isCapturingEnvironment && !selfieCapture && (
Expand All @@ -678,11 +683,13 @@ function AcuantCapture(
onImageCaptureFailure={onSelfieCaptureFailure}
onImageCaptureOpen={onSelfieCaptureOpen}
onImageCaptureClose={onSelfieCaptureClosed}
onImageCaptureFeedback={onImageCaptureFeedback}
>
<AcuantSelfieCaptureCanvas
fullScreenRef={fullScreenRef}
fullScreenLabel={t('doc_auth.accessible_labels.document_capture_dialog')}
onRequestClose={() => setIsCapturingEnvironment(false)}
imageCaptureText={imageCaptureText}
/>
</AcuantSelfieCamera>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useContext, useEffect } from 'react';
import type { ReactNode } from 'react';
import { t } from '@18f/identity-i18n';
import AcuantContext from '../context/acuant';

declare global {
Expand Down Expand Up @@ -43,6 +44,11 @@ interface AcuantSelfieCameraContextProps {
* when the fullscreen selfie capture page has been closed
*/
onImageCaptureClose: () => void;
/**
* Capture hint text from onDetection callback, tells the user
* why the acuant sdk cannot capture a selfie.
*/
onImageCaptureFeedback: (text: string) => void;
/**
* React children node
*/
Expand All @@ -63,8 +69,6 @@ interface FaceCaptureCallback {
interface FaceDetectionStates {
FACE_NOT_FOUND: string;
TOO_MANY_FACES: string;
FACE_ANGLE_TOO_LARGE: string;
PROBABILITY_TOO_SMALL: string;
FACE_TOO_SMALL: string;
FACE_CLOSE_TO_BORDER: string;
}
Expand All @@ -74,6 +78,7 @@ function AcuantSelfieCamera({
onImageCaptureFailure = () => {},
onImageCaptureOpen = () => {},
onImageCaptureClose = () => {},
onImageCaptureFeedback = () => {},
children,
}: AcuantSelfieCameraContextProps) {
const { isReady, setIsActive } = useContext(AcuantContext);
Expand All @@ -85,7 +90,8 @@ function AcuantSelfieCamera({
// Until then, no actions are executed and the user sees only the camera stream.
// You can opt to display an alert before the callback is triggered.
},
onDetection: () => {
onDetection: (text) => {
onImageCaptureFeedback(text);
// Triggered when the face does not pass the scan. The UI element
// should be updated here to provide guidence to the user
},
Expand All @@ -104,6 +110,7 @@ function AcuantSelfieCamera({
},
onPhotoTaken: () => {
// The photo has been taken and it's showing a preview with a button to accept or retake the image.
onImageCaptureFeedback('');
},
onPhotoRetake: () => {
// Triggered when retake button is tapped
Expand All @@ -115,12 +122,10 @@ function AcuantSelfieCamera({
};

const faceDetectionStates = {
FACE_NOT_FOUND: 'FACE NOT FOUND',
TOO_MANY_FACES: 'TOO MANY FACES',
FACE_ANGLE_TOO_LARGE: 'FACE ANGLE TOO LARGE',
PROBABILITY_TOO_SMALL: 'PROBABILITY TOO SMALL',
FACE_TOO_SMALL: 'FACE TOO SMALL',
FACE_CLOSE_TO_BORDER: 'TOO CLOSE TO THE FRAME',
FACE_NOT_FOUND: t('doc_auth.info.selfie_capture_status.face_not_found'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, these strings should not be embedded directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TOO_MANY_FACES: t('doc_auth.info.selfie_capture_status.too_many_faces'),
FACE_TOO_SMALL: t('doc_auth.info.selfie_capture_status.face_too_small'),
FACE_CLOSE_TO_BORDER: t('doc_auth.info.selfie_capture_status.face_close_to_border'),
};
const cleanupSelfieCamera = () => {
window.AcuantPassiveLiveness.end();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ function FullScreenLoadingSpinner({ fullScreenRef, onRequestClose, fullScreenLab
);
}

function AcuantSelfieCaptureCanvas({ fullScreenRef, onRequestClose, fullScreenLabel }) {
function AcuantSelfieCaptureCanvas({
fullScreenRef,
onRequestClose,
fullScreenLabel,
imageCaptureText,
}) {
const { isReady } = useContext(AcuantContext);
// The Acuant SDK script AcuantPassiveLiveness attaches to whatever element has
// this id. It then uses that element as the root for the full screen selfie capture
const acuantCaptureContainerId = 'acuant-face-capture-container';
return isReady ? (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return isReady ? (
// The <p> to display the text over the selfie capture can be moved to another component
// without too much work, putting it here to keep it conceptually grouped with the selfie capture canvas
return isReady ? (

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, personally think it's too much for an additional component just for 3 lines of static code, actually just one line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, interesting. Seems like that comment might be more confusing than it's worth. Agreed that the <p> should't be it's own component.

What I meant was that it actually doesn't matter where the <p> is (as long as it's in the selfie components) because the CSS removes it from the document flow.

<div id={acuantCaptureContainerId} />
<div id={acuantCaptureContainerId}>
<p className="document-capture-selfie-feedback">{imageCaptureText}</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our use of BEM naming conventions aligns neatly 1-to-1 with React components, where if we had a CSS class name of document-capture-selfie-feedback, I'd expect it to be assigned in a component DocumentCaptureSelfieFeedback. I think that could make sense to split out as a component. As currently implemented, I'd assume it would be called something like acuant-selfie-capture-canvas__selfie-feedback, being an element (BEM) within AcuantSelfieCaptureCanvas.

</div>
) : (
<FullScreenLoadingSpinner
fullScreenRef={fullScreenRef}
Expand Down
5 changes: 5 additions & 0 deletions config/locales/doc_auth/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ en:
secure_account: We’ll encrypt your account with your password. Encryption means
your data is protected and only you will be able to access or change
your information.
selfie_capture_status:
face_close_to_border: TOO CLOSE TO THE FRAME
face_not_found: FACE NOT FOUND
face_too_small: FACE TOO SMALL
too_many_faces: TOO MANY FACES
ssn: We need your Social Security number to verify your name, date of birth and
address.
tag: Recommended
Expand Down
5 changes: 5 additions & 0 deletions config/locales/doc_auth/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ es:
secure_account: Vamos a encriptar su cuenta con su contraseña. La encriptación
significa que sus datos están protegidos y solo usted podrá acceder o
modificar su información.
selfie_capture_status:
face_close_to_border: DEMASIADO CERCA DEL MARCO
face_not_found: NO SE ENCONTRÓ LA CARA
face_too_small: LA CARA ES DEMASIADO CHICA
too_many_faces: HAY DEMASIADAS CARAS
ssn: Necesitamos su número de Seguro Social para validar su nombre, fecha de
nacimiento y dirección.
tag: Recomendado
Expand Down
5 changes: 5 additions & 0 deletions config/locales/doc_auth/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ fr:
secure_account: Nous chiffrerons votre compte avec votre mot de passe. Le
chiffrage signifie que vos données sont protégées et que vous êtes le/la
seul(e) à pouvoir accéder à vos informations ou les modifier.
selfie_capture_status:
face_close_to_border: TROP PRÈS DU CADRE
face_not_found: VISAGE NON TROUVÉ
face_too_small: VISAGE TROP PETIT
too_many_faces: TROP DE VISAGES
ssn: Nous avons besoin de votre numéro de sécurité sociale pour vérifier votre
nom, date de naissance et adresse.
tag: Recommandation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AcuantContextProvider, DeviceContext } from '@18f/identity-document-capture';
import AcuantSelfieCamera from '@18f/identity-document-capture/components/acuant-selfie-camera';
import AcuantSelfieCaptureCanvas from '@18f/identity-document-capture/components/acuant-selfie-capture-canvas';
import { t } from '@18f/identity-i18n';
import { render, useAcuant } from '../../../support/document-capture';

describe('document-capture/components/acuant-selfie-camera', () => {
Expand Down Expand Up @@ -40,12 +41,10 @@ describe('document-capture/components/acuant-selfie-camera', () => {
expect(callbackNames).to.equal(expectedCallbackNames);

expect(window.AcuantPassiveLiveness.start.getCall(0).args[1]).to.deep.equal({
FACE_NOT_FOUND: 'FACE NOT FOUND',
TOO_MANY_FACES: 'TOO MANY FACES',
FACE_ANGLE_TOO_LARGE: 'FACE ANGLE TOO LARGE',
PROBABILITY_TOO_SMALL: 'PROBABILITY TOO SMALL',
FACE_TOO_SMALL: 'FACE TOO SMALL',
FACE_CLOSE_TO_BORDER: 'TOO CLOSE TO THE FRAME',
FACE_NOT_FOUND: t('doc_auth.info.selfie_capture_status.face_not_found'),
TOO_MANY_FACES: t('doc_auth.info.selfie_capture_status.too_many_faces'),
FACE_TOO_SMALL: t('doc_auth.info.selfie_capture_status.face_too_small'),
FACE_CLOSE_TO_BORDER: t('doc_auth.info.selfie_capture_status.face_close_to_border'),
});
});

Expand Down