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