diff --git a/app/javascript/packages/document-capture/components/acuant-capture.tsx b/app/javascript/packages/document-capture/components/acuant-capture.tsx index 438a56b75a9..6bc249fb945 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.tsx +++ b/app/javascript/packages/document-capture/components/acuant-capture.tsx @@ -702,12 +702,13 @@ function AcuantCapture( allowUpload && formatHTML(t('doc_auth.buttons.take_or_upload_picture_html'), { 'lg-take-photo': () => null, + 'lg-or': ({ children }) => ( + {children} + ), 'lg-upload': ({ children }) => ( - - - + ), })} diff --git a/app/javascript/packages/document-capture/components/documents-step.jsx b/app/javascript/packages/document-capture/components/documents-step.jsx index d8b9492a171..239cbfb2c23 100644 --- a/app/javascript/packages/document-capture/components/documents-step.jsx +++ b/app/javascript/packages/document-capture/components/documents-step.jsx @@ -21,6 +21,7 @@ import DocumentCaptureAbandon from './document-capture-abandon'; * * @prop {Blob|string|null|undefined} front Front image value. * @prop {Blob|string|null|undefined} back Back image value. + * @prop {Blob|string|null|undefined} selfie Selfie image value. * @prop {string=} front_image_metadata Front image metadata. * @prop {string=} back_image_metadata Back image metadata. */ @@ -35,6 +36,13 @@ function DocumentsStep({ onError = () => {}, registerField = () => undefined, }) { + /** + * Sides of the ID document to present as file input. + * + * @type {DocumentSide[]} + */ + const documentsSides = ['front', 'back']; + const { t } = useI18n(); const { isMobile } = useContext(DeviceContext); const { isLastStep } = useContext(FormStepsContext); @@ -42,38 +50,62 @@ function DocumentsStep({ const { notReadySectionEnabled, exitQuestionSectionEnabled, selfieCaptureEnabled } = useContext(FeatureFlagContext); - /** - * Sides of document to present as file input. - * - * @type {DocumentSide[]} - */ - const documentSides = selfieCaptureEnabled ? ['front', 'back', 'selfie'] : ['front', 'back']; + const pageHeaderText = selfieCaptureEnabled + ? t('doc_auth.headings.document_capture_with_selfie') + : t('doc_auth.headings.document_capture'); + const defaultSideProps = { + registerField, + onChange, + errors, + onError, + }; return ( <> {flowPath === 'hybrid' && } - {t('doc_auth.headings.document_capture')} -

{t('doc_auth.info.document_capture_intro_acknowledgment')}

+ {pageHeaderText} +

+ {selfieCaptureEnabled && '1. '} + {t('doc_auth.headings.document_capture_subheader_id')} +

- {documentSides.map((side) => ( + {documentsSides.map((side) => ( ))} + {selfieCaptureEnabled && ( + <> +
+

2. {t('doc_auth.headings.document_capture_subheader_selfie')}

+ + + + )} {isLastStep ? : } {notReadySectionEnabled && } {exitQuestionSectionEnabled && } diff --git a/config/locales/doc_auth/en.yml b/config/locales/doc_auth/en.yml index 2bb33c86a74..6334869c8bc 100644 --- a/config/locales/doc_auth/en.yml +++ b/config/locales/doc_auth/en.yml @@ -8,8 +8,8 @@ en: buttons: add_new_photos: Add new photos continue: Continue - take_or_upload_picture_html: 'Take photo or - Upload photo' + take_or_upload_picture_html: 'Take photo + or Upload photo' take_picture: Take photo take_picture_retry: Retake photo upload_picture: Upload photo @@ -151,7 +151,10 @@ en: document_capture: Add photos of your ID document_capture_back: Back of your ID document_capture_front: Front of your ID - document_capture_selfie: Selfie + document_capture_selfie: A photo of yourself + document_capture_subheader_id: Driver’s license or state ID card + document_capture_subheader_selfie: Photo of yourself + document_capture_with_selfie: Add photos of your ID and a photo of yourself front: Front of your driver’s license or state ID getting_started: Let’s verify your identity for %{sp_name} how_to_verify: Choose how you want to verify your identity @@ -189,9 +192,6 @@ en: capture_status_none: Align capture_status_small_document: Move Closer capture_status_tap_to_capture: Tap to Capture - document_capture_intro_acknowledgment: We’ll collect information about you by - reading your driver’s license or state ID card. We use this information - to verify your identity. exit: with_sp: Exit %{app_name} and return to %{sp_name} without_sp: Exit identity verification and go to your account page @@ -273,12 +273,16 @@ en: process. header: Not ready to add photos? tips: - document_capture_header_text: 'For best results:' document_capture_hint: Must be a JPG or PNG document_capture_id_text1: Use a dark background document_capture_id_text2: Take the photo on a flat surface document_capture_id_text3: Do not use the flash on your camera document_capture_id_text4: File size should be at least 2 MB + document_capture_selfie_id_header_text: Tips for taking clear photos + document_capture_selfie_selfie_text: Tips for taking a clear photo + document_capture_selfie_text1: Hold your device at eye level + document_capture_selfie_text2: Make sure your whole face is visible + document_capture_selfie_text3: Take your photo in a well-lit place most_common: Most Common review_issues_id_header_text: 'Review the images of your state‑issued ID:' review_issues_id_text1: Did you use a dark background? diff --git a/config/locales/doc_auth/es.yml b/config/locales/doc_auth/es.yml index de94d035b0f..ac12ab0e65f 100644 --- a/config/locales/doc_auth/es.yml +++ b/config/locales/doc_auth/es.yml @@ -8,8 +8,9 @@ es: buttons: add_new_photos: Añadir nuevas fotos continue: Continuar - take_or_upload_picture_html: 'Toma una foto o - Sube una foto' + take_or_upload_picture_html: 'Toma una + foto o Sube una + foto' take_picture: Toma una foto take_picture_retry: Retirar la foto upload_picture: Subir foto @@ -181,7 +182,10 @@ es: document_capture: Incluir fotos de su identificación document_capture_back: Parte trasera de su documento de identidad document_capture_front: Parte delantera de su documento de identidad - document_capture_selfie: Selfi + document_capture_selfie: Una foto tuya + document_capture_subheader_id: Licencia de conducir o con un documento de identidad estatal + document_capture_subheader_selfie: Foto suya + document_capture_with_selfie: Incluir fotos de su identificación y una foto suya front: Anverso de su licencia de conducir o identificación estatal getting_started: Vamos a verificar su identidad para %{sp_name} how_to_verify: Elija cómo quiere verificar su identidad @@ -222,9 +226,6 @@ es: capture_status_none: Alinea capture_status_small_document: Muévete mas cerca capture_status_tap_to_capture: Toque para capturar - document_capture_intro_acknowledgment: Recopilaremos información sobre usted - leyendo su licencia de conducir o identificación estatal. Usamos esta - información para verificar su identidad. exit: with_sp: Salir de %{app_name} y volver a %{sp_name} without_sp: Salir de la verificación de identidad e ir a la página de su cuenta @@ -315,12 +316,16 @@ es: proceso. header: ¿No está listo para enviar las fotos? tips: - document_capture_header_text: 'Para obtener los mejores resultados:' document_capture_hint: Debe ser un JPG o PNG document_capture_id_text1: Use un fondo oscuro document_capture_id_text2: Tome la foto en una superficie plana document_capture_id_text3: No use el flash de su cámara document_capture_id_text4: El tamaño del archivo debe ser de al menos 2 MB + document_capture_selfie_id_header_text: Consejos para obtener fotografías nítidas + document_capture_selfie_selfie_text: Consejos para obtener una foto nítidas + document_capture_selfie_text1: Mantenga el dispositivo al mismo nivel que los ojos + document_capture_selfie_text2: Asegúrate de que tu rostro completo sea visible + document_capture_selfie_text3: Tómese la foto en un sitio con buena iluminación most_common: Más común review_issues_id_header_text: 'Revise las imágenes de su documento de identidad expedido por el estado:' diff --git a/config/locales/doc_auth/fr.yml b/config/locales/doc_auth/fr.yml index 59e84585ce1..846a3de8e9e 100644 --- a/config/locales/doc_auth/fr.yml +++ b/config/locales/doc_auth/fr.yml @@ -8,8 +8,9 @@ fr: buttons: add_new_photos: Ajoutez de nouvelles photos continue: Continuer - take_or_upload_picture_html: 'Prendre une photo - ou Télécharger une photo' + take_or_upload_picture_html: 'Prendre une + photo ou Télécharger une + photo' take_picture: Prendre une photo take_picture_retry: Reprendre la photo upload_picture: Télécharger une photo @@ -189,7 +190,10 @@ fr: document_capture: Ajoutez des photos de votre pièce d’identité document_capture_back: Verso de votre carte d’identité document_capture_front: Recto de votre carte d’identité - document_capture_selfie: Égoportrait + document_capture_selfie: Une photo de vous + document_capture_subheader_id: Permis de conduire ou de carte d’identité d’État + document_capture_subheader_selfie: Photo de vous-même + document_capture_with_selfie: Ajoutez des photos de votre pièce d’identité et une photo de vous-même front: Recto de votre permis de conduire ou de votre carte d’identité de l’État getting_started: Vérifions votre identité pour %{sp_name} how_to_verify: Choisissez la manière dont vous souhaitez confirmer votre identité @@ -229,9 +233,6 @@ fr: capture_status_none: Alignez capture_status_small_document: Approchez-vous capture_status_tap_to_capture: Appuyez pour capturer - document_capture_intro_acknowledgment: Nous recueillons des informations sur - vous en lisant votre permis de conduire ou votre carte d’identité de - l’État. Nous utilisons ces informations pour vérifier votre identité. exit: with_sp: Quittez %{app_name} et retournez à %{sp_name} without_sp: Quittez la vérification d’identité et accédez à la page de votre compte @@ -327,12 +328,16 @@ fr: pour terminer ce processus. header: Vous n’êtes pas prêt à ajouter des photos? tips: - document_capture_header_text: 'Pour obtenir les meilleurs résultats:' document_capture_hint: Doit être un JPG ou PNG document_capture_id_text1: Utilisez un fond sombre document_capture_id_text2: Prenez la photo sur une surface plane document_capture_id_text3: N’utilisez pas le flash de votre appareil photo document_capture_id_text4: La taille du fichier doit être d’au moins 2 Mo + document_capture_selfie_id_header_text: Conseils pour prendre des photos claires + document_capture_selfie_selfie_text: Conseils pour prendre un photo claires + document_capture_selfie_text1: Tenez votre appareil à hauteur des yeux + document_capture_selfie_text2: Veillez à ce que l’ensemble de votre visage soit visible + document_capture_selfie_text3: Prenez votre photo dans un endroit bien éclairé most_common: Le plus commun review_issues_id_header_text: 'Examinez les images de votre carte d’identité délivrée par l’État:' review_issues_id_text1: Avez-vous utilisé un fond sombre? diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index dd62489e059..1253d95b2b5 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -3,6 +3,7 @@ RSpec.feature 'document capture step', :js do include IdvStepHelper include DocAuthHelper + include DocCaptureHelper include ActionView::Helpers::DateHelper let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } @@ -204,7 +205,9 @@ expect(page).to have_current_path(idv_document_capture_url) expect_step_indicator_current_step(t('step_indicator.flows.idv.verify_id')) - + expect_doc_capture_page_header(t('doc_auth.headings.document_capture_with_selfie')) + expect_doc_capture_id_subheader + expect_doc_capture_selfie_subheader attach_images attach_selfie submit_images diff --git a/spec/features/idv/doc_auth/redo_document_capture_spec.rb b/spec/features/idv/doc_auth/redo_document_capture_spec.rb index 852e10407ec..7790f14a3d6 100644 --- a/spec/features/idv/doc_auth/redo_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/redo_document_capture_spec.rb @@ -3,6 +3,7 @@ RSpec.feature 'doc auth redo document capture', js: true do include IdvStepHelper include DocAuthHelper + include DocCaptureHelper let(:fake_analytics) { FakeAnalytics.new } @@ -168,7 +169,6 @@ ) end end - context 'error due to data issue with 2xx status code', allow_browser_log: true do before do sign_in_and_2fa_user @@ -239,4 +239,25 @@ it_behaves_like 'image re-upload not allowed' end + + context 'when selfie is enabled' do + context 'error due to data issue with 2xx status code', allow_browser_log: true do + before do + allow(IdentityConfig.store).to receive(:doc_auth_selfie_capture). + and_return({ enabled: true }) + sign_in_and_2fa_user + complete_doc_auth_steps_before_document_capture_step + mock_doc_auth_acuant_error_unknown + attach_images + attach_selfie + submit_images + click_try_again + sleep(10) + end + it_behaves_like 'image re-upload not allowed' + it 'shows current existing header' do + expect_doc_capture_page_header(t('doc_auth.headings.review_issues')) + end + end + end end diff --git a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx index c54dcc0b1be..5675099308c 100644 --- a/spec/javascript/packages/document-capture/components/documents-step-spec.jsx +++ b/spec/javascript/packages/document-capture/components/documents-step-spec.jsx @@ -1,4 +1,5 @@ import userEvent from '@testing-library/user-event'; +import { within } from '@testing-library/react'; import sinon from 'sinon'; import { expect } from 'chai'; import { t } from '@18f/identity-i18n'; @@ -164,15 +165,72 @@ describe('document-capture/components/documents-step', () => { ], [DocumentsStep], ); - const { getByLabelText, queryByLabelText } = render(); + const { getAllByRole, getByText, getByRole, getByLabelText, queryByLabelText } = render( + , + ); const front = getByLabelText('doc_auth.headings.document_capture_front'); const back = getByLabelText('doc_auth.headings.document_capture_back'); const selfie = queryByLabelText('doc_auth.headings.document_capture_selfie'); - + const pageHeader = getByRole('heading', { + name: 'doc_auth.headings.document_capture_with_selfie', + level: 1, + }); + const idHeader = getByRole('heading', { + name: '1. doc_auth.headings.document_capture_subheader_id', + level: 2, + }); + const selfieHeader = getByRole('heading', { + name: '2. doc_auth.headings.document_capture_subheader_selfie', + level: 2, + }); expect(front).to.be.ok(); expect(back).to.be.ok(); expect(selfie).to.be.ok(); + expect(pageHeader).to.be.ok(); + expect(idHeader).to.be.ok(); + expect(selfieHeader).to.be.ok(); + + const tipListHeader = getByText('doc_auth.tips.document_capture_selfie_selfie_text'); + expect(tipListHeader).to.be.ok(); + const lists = getAllByRole('list'); + const tipList = lists[1]; + expect(tipList).to.be.ok(); + const tipListItem = within(tipList).getAllByRole('listitem'); + tipListItem.forEach((li, idx) => { + expect(li.textContent).to.equals(`doc_auth.tips.document_capture_selfie_text${idx + 1}`); + }); + }); + }); + + it('renders with front, back when featureflag is off', () => { + const App = composeComponents( + [ + FeatureFlagContext.Provider, + { + value: { + selfieCaptureEnabled: false, + }, + }, + ], + [DocumentsStep], + ); + const { getByRole, getByLabelText } = render(); + + const front = getByLabelText('doc_auth.headings.document_capture_front'); + const back = getByLabelText('doc_auth.headings.document_capture_back'); + const pageHeader = getByRole('heading', { + name: 'doc_auth.headings.document_capture', + level: 1, + }); + const idHeader = getByRole('heading', { + name: 'doc_auth.headings.document_capture_subheader_id', + level: 2, }); + + expect(front).to.be.ok(); + expect(back).to.be.ok(); + expect(pageHeader).to.be.ok(); + expect(idHeader).to.be.ok(); }); }); diff --git a/spec/support/features/doc_capture_helper.rb b/spec/support/features/doc_capture_helper.rb index a802ca49a09..89f2c633360 100644 --- a/spec/support/features/doc_capture_helper.rb +++ b/spec/support/features/doc_capture_helper.rb @@ -46,4 +46,16 @@ def mock_doc_captured(user_id, response = DocAuth::Response.new(success: true)) user = User.find(user_id) user.document_capture_sessions.last.store_result_from_response(response) end + + def expect_doc_capture_page_header(text) + expect(page).to have_css('.page-heading', text: text, wait: 5) + end + + def expect_doc_capture_id_subheader + expect(page).to have_text(t('doc_auth.headings.document_capture_subheader_id')) + end + + def expect_doc_capture_selfie_subheader + expect(page).to have_text(t('doc_auth.headings.document_capture_subheader_selfie')) + end end