diff --git a/app/javascript/packages/document-capture/components/acuant-capture.scss b/app/javascript/packages/document-capture/components/acuant-capture.scss index 2ed7922a445..451142b77d3 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.scss +++ b/app/javascript/packages/document-capture/components/acuant-capture.scss @@ -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 { + left: 50%; + top: 10%; + position: fixed; + transform: translateX(-50%); + z-index: 11; + } } diff --git a/app/javascript/packages/document-capture/components/acuant-capture.tsx b/app/javascript/packages/document-capture/components/acuant-capture.tsx index 8e61ba6e2b8..a9f22662c2b 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.tsx +++ b/app/javascript/packages/document-capture/components/acuant-capture.tsx @@ -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'; @@ -653,6 +654,10 @@ function AcuantCapture( }); } + function onImageCaptureFeedback(text: string) { + setImageCaptureText(text); + } + return (
{isCapturingEnvironment && !selfieCapture && ( @@ -678,11 +683,13 @@ function AcuantCapture( onImageCaptureFailure={onSelfieCaptureFailure} onImageCaptureOpen={onSelfieCaptureOpen} onImageCaptureClose={onSelfieCaptureClosed} + onImageCaptureFeedback={onImageCaptureFeedback} > setIsCapturingEnvironment(false)} + imageCaptureText={imageCaptureText} /> )} diff --git a/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx b/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx index e031aef7008..29eb473aa2e 100644 --- a/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx +++ b/app/javascript/packages/document-capture/components/acuant-selfie-camera.tsx @@ -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 { @@ -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 */ @@ -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; } @@ -74,6 +78,7 @@ function AcuantSelfieCamera({ onImageCaptureFailure = () => {}, onImageCaptureOpen = () => {}, onImageCaptureClose = () => {}, + onImageCaptureFeedback = () => {}, children, }: AcuantSelfieCameraContextProps) { const { isReady, setIsActive } = useContext(AcuantContext); @@ -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 }, @@ -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 @@ -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'), + 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(); diff --git a/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx b/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx index 6091567aabf..d89bed1d406 100644 --- a/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx +++ b/app/javascript/packages/document-capture/components/acuant-selfie-capture-canvas.jsx @@ -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 ? ( -
+
+

{imageCaptureText}

+
) : ( { @@ -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'), }); });