Skip to content

Commit

Permalink
LG-3427: Update doc auth ID support texts (#4228)
Browse files Browse the repository at this point in the history
* Support string tagName for formatHTML handler

* Support variables substitution in i18n strings

* Update doc auth ID support texts

* Remove unused string idv.messages.jurisdiction.no_id

* Remove unused "No ID" error screen

* Remove unnecessary HTML suffix for plaintext use_cac string

* Extract getServiceProvider to conditionally create SP object

See: https://github.com/18F/identity-idp/pull/4228/files#r492903440

* Support including attributes in match in formatHTML

**Why**: Improved reuse of strings between server-side and client-side

* Substitute no_other_id_help_bold with no_other_id_help_bold_html

* Remove link "Full list of accepted IDs"
  • Loading branch information
aduth committed Sep 24, 2020
1 parent dc7b577 commit 7d5d77a
Show file tree
Hide file tree
Showing 24 changed files with 217 additions and 117 deletions.
10 changes: 0 additions & 10 deletions app/controllers/idv/jurisdiction_errors_controller.rb

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import React from 'react';
import React, { useContext } from 'react';
import ServiceProviderContext from '../context/service-provider';
import useI18n from '../hooks/use-i18n';

function MobileIntroStep() {
const { t } = useI18n();
const { t, formatHTML } = useI18n();
const serviceProvider = useContext(ServiceProviderContext);

return (
<>
<p>{t('doc_auth.info.document_capture_intro_acknowledgment')}</p>
<p>
<a href="/verify/jurisdiction/errors/no_id">{t('idv.messages.jurisdiction.no_id')}</a>
{formatHTML(t('doc_auth.info.id_worn_html'), {
strong: 'strong',
})}
</p>
{serviceProvider && (
<p>
{formatHTML(
t('doc_auth.info.no_other_id_help_bold_html', { sp_name: serviceProvider.name }),
{
strong: 'strong',
a: ({ children }) => <a href={serviceProvider.failureToProofURL}>{children}</a>,
},
)}
</p>
)}
<p className="margin-top-4 margin-bottom-0">
{t('doc_auth.tips.document_capture_header_text')}
</p>
<p className="margin-bottom-0">{t('doc_auth.tips.document_capture_header_text')}</p>
<ul>
<li>{t('doc_auth.tips.document_capture_id_text1')}</li>
<li>{t('doc_auth.tips.document_capture_id_text2')}</li>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createContext } from 'react';

/**
* @typedef ServiceProviderContext
*
* @prop {string} name Service provider name.
* @prop {string} failureToProofURL URL to redirect user on failure to proof.
*/

const ServiceProviderContext = createContext(/** @type {ServiceProviderContext=} */ (undefined));

export default ServiceProviderContext;
31 changes: 26 additions & 5 deletions app/javascript/packages/document-capture/hooks/use-i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import I18nContext from '../context/i18n';
* });
* ```
*
* @param {string} html HTML to format.
* @param {Record<string,Component>} handlers Mapping of tag names to components.
* @param {string} html HTML to format.
* @param {Record<string,Component|string>} handlers Mapping of tag names to tag name or component.
*
* @return {import('react').ReactNode}
*/
export function formatHTML(html, handlers) {
const pattern = new RegExp(`</?(?:${Object.keys(handlers).join('|')})>`, 'g');
const pattern = new RegExp(`</?(?:${Object.keys(handlers).join('|')})(?: .*?)?>`, 'g');
const matches = html.match(pattern);
if (!matches) {
return html;
Expand All @@ -37,7 +37,9 @@ export function formatHTML(html, handlers) {
const parts = html.split(pattern);

for (let i = 0; i < matches.length; i += 2) {
const tag = matches[i].slice(1, -1);
const match = matches[i];
const end = match.search(/[ >]/);
const tag = matches[i].slice(1, end);
const part = /** @type {string} */ (parts[i + 1]);
const replacement = createElement(handlers[tag], null, part);
parts[i + 1] = cloneElement(replacement, { key: part });
Expand All @@ -46,17 +48,36 @@ export function formatHTML(html, handlers) {
return parts.filter(Boolean);
}

/**
* Returns string with variable substitution.
*
* @param {string} string Original string.
* @param {Record<string,string>} variables Variables to replace.
*
* @return {string} String with variables substituted.
*/
export function replaceVariables(string, variables) {
return Object.keys(variables).reduce(
(result, key) => result.replace(new RegExp(`%{${key}}`, 'g'), variables[key]),
string,
);
}

function useI18n() {
const strings = useContext(I18nContext);

/**
* Returns the translated string by the given key.
*
* @param {string} key Key to retrieve.
* @param {Record<string,string>=} variables Variables to substitute in string.
*
* @return {string} Translated string.
*/
const t = (key) => (Object.prototype.hasOwnProperty.call(strings, key) ? strings[key] : key);
function t(key, variables) {
const string = Object.prototype.hasOwnProperty.call(strings, key) ? strings[key] : key;
return variables ? replaceVariables(string, variables) : string;
}

return {
t,
Expand Down
1 change: 1 addition & 0 deletions app/javascript/packages/document-capture/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as I18nContext } from './context/i18n';
export { default as DeviceContext } from './context/device';
export { Provider as AcuantProvider } from './context/acuant';
export { Provider as UploadContextProvider } from './context/upload';
export { default as ServiceProviderContext } from './context/service-provider';
29 changes: 20 additions & 9 deletions app/javascript/packs/document-capture.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,25 @@ import {
DeviceContext,
AcuantProvider,
UploadContextProvider,
ServiceProviderContext,
} from '@18f/identity-document-capture';
import { loadPolyfills } from '@18f/identity-polyfill';
import { isCameraCapableMobile } from '@18f/identity-device';

const { I18n: i18n, assets } = window.LoginGov;

const appRoot = document.getElementById('document-capture-form');
const isLivenessEnabled = appRoot.hasAttribute('data-liveness');
const isMockClient = appRoot.hasAttribute('data-mock-client');

function getServiceProvider() {
const name = appRoot.getAttribute('data-sp-name');
const failureToProofURL = appRoot.getAttribute('data-failure-to-proof-url');
if (name && failureToProofURL) {
return { name, failureToProofURL };
}
}

function getMetaContent(name) {
return document.querySelector(`meta[name="${name}"]`)?.content ?? null;
}
Expand All @@ -23,10 +36,6 @@ const device = {
};

loadPolyfills(['fetch']).then(() => {
const appRoot = document.getElementById('document-capture-form');
const isLivenessEnabled = appRoot.hasAttribute('data-liveness');
const isMockClient = appRoot.hasAttribute('data-mock-client');

render(
<AcuantProvider
credentials={getMetaContent('acuant-sdk-initialization-creds')}
Expand All @@ -42,11 +51,13 @@ loadPolyfills(['fetch']).then(() => {
}}
>
<I18nContext.Provider value={i18n.strings}>
<AssetContext.Provider value={assets}>
<DeviceContext.Provider value={device}>
<DocumentCapture isLivenessEnabled={isLivenessEnabled} />
</DeviceContext.Provider>
</AssetContext.Provider>
<ServiceProviderContext.Provider value={getServiceProvider()}>
<AssetContext.Provider value={assets}>
<DeviceContext.Provider value={device}>
<DocumentCapture isLivenessEnabled={isLivenessEnabled} />
</DeviceContext.Provider>
</AssetContext.Provider>
</ServiceProviderContext.Provider>
</I18nContext.Provider>
</UploadContextProvider>
</AcuantProvider>,
Expand Down
7 changes: 6 additions & 1 deletion app/views/idv/capture_doc/document_capture.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
<%= render 'idv/shared/document_capture', flow_session: flow_session %>
<%= render(
'idv/shared/document_capture',
flow_session: flow_session,
sp_name: decorated_session.sp_name,
failure_to_proof_url: decorated_session.failure_to_proof_url
) %>
7 changes: 6 additions & 1 deletion app/views/idv/doc_auth/document_capture.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
<%= render 'idv/shared/document_capture', flow_session: flow_session %>
<%= render(
'idv/shared/document_capture',
flow_session: flow_session,
sp_name: decorated_session.sp_name,
failure_to_proof_url: decorated_session.failure_to_proof_url
) %>
37 changes: 21 additions & 16 deletions app/views/idv/doc_auth/upload.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,33 @@
<% end %>

<p>
<strong><%= t('doc_auth.info.upload_no_image_storage') %></strong>
<%= t('doc_auth.info.upload_no_image_storage') %>
</p>

<% if Figaro.env.cac_proofing_enabled == 'true' && desktop_device? %>
<div class='mt2 mb2'>
<p>
<%= t('doc_auth.info.id_worn_html') %>
</p>

<% if decorated_session.sp_name %>
<p>
<%= t(
'doc_auth.info.use_cac_html',
link: link_to(
t('doc_auth.info.use_cac_link'),
idv_cac_step_path(step: :choose_method),
),
'doc_auth.info.no_other_id_help_bold_html',
failure_to_proof_url: decorated_session.failure_to_proof_url,
sp_name: decorated_session.sp_name,
) %>
</div>
</p>
<% end %>
<div class='mt2 mb3'>
<%= link_to t('idv.messages.jurisdiction.no_id'), idv_jurisdiction_errors_no_id_path %>
</div>
<% if Figaro.env.cac_proofing_enabled == 'true' && desktop_device? %>
<p>
<strong><%= t('doc_auth.info.use_cac') %></strong>
<%= link_to(t('doc_auth.info.use_cac_link'), idv_cac_step_path(step: :choose_method)) %>
</p>
<% end %>

<hr/>
<hr class="margin-y-4" />

<div class='clearfix mt3 mb3'>
<div class='clearfix'>
<div class='sm-col sm-col-3'>
<%= image_tag(
asset_url('idv/phone.png'),
Expand Down Expand Up @@ -75,9 +80,9 @@
</div>
</div>

<hr/>
<hr class="margin-y-4" />

<div class='clearfix mt3 mb3'>
<div class='clearfix'>
<div id="upload-comp-liveness-off"
class='sm-col sm-col-12 <%= 'hidden' if liveness_checking_enabled? %>'>
<%= t('doc_auth.info.upload_from_computer') %>&nbsp;
Expand Down
13 changes: 0 additions & 13 deletions app/views/idv/jurisdiction_errors/no_id.html.erb

This file was deleted.

2 changes: 2 additions & 0 deletions app/views/idv/shared/_document_capture.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
mock_client: (DocAuth::Client.doc_auth_vendor == 'mock').presence,
document_capture_session_uuid: flow_session[:document_capture_session_uuid],
endpoint: api_verify_images_url,
sp_name: sp_name,
failure_to_proof_url: failure_to_proof_url,
} %>
<div class="js-fallback">
<%= render 'idv/doc_auth/error_messages', flow_session: flow_session %>
Expand Down
3 changes: 2 additions & 1 deletion config/js_locale_strings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
- doc_auth.info.capture_status_tap_to_capture
- doc_auth.info.document_capture_intro_acknowledgment
- doc_auth.info.document_capture_upload_image
- doc_auth.info.id_worn_html
- doc_auth.info.interstitial_eta
- doc_auth.info.interstitial_thanks
- doc_auth.info.no_other_id_help_bold_html
- doc_auth.instructions.document_capture_selfie_consent_banner
- doc_auth.instructions.document_capture_selfie_consent_blocked
- doc_auth.instructions.document_capture_selfie_consent_reason
Expand Down Expand Up @@ -52,7 +54,6 @@
- idv.errors.pattern_mismatch.state_id_number
- idv.errors.pattern_mismatch.zipcode
- idv.failure.button.warning
- idv.messages.jurisdiction.no_id
- instructions.password.strength.i
- instructions.password.strength.ii
- instructions.password.strength.iii
Expand Down
6 changes: 5 additions & 1 deletion config/locales/doc_auth/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,17 @@ en:
your identity.
document_capture_upload_image: We only use your ID to verify your identity,
and we will not save any images.
id_worn_html: "<strong>Is your ID worn or damaged?</strong> Use another state-issued
ID if you have one."
interstitial_eta: This might take up to a minute. We’ll load the next step automatically
when it’s done.
interstitial_thanks: Thanks for your patience!
link_sent:
- Please check your phone and follow instructions to take a photo of your state
issued ID.
- When you are done click continue here to finish verifying your identity.
no_other_id_help_bold_html: "<strong>If you do not have another state-issued
ID</strong>, <a href=%{failure_to_proof_url}>get help at %{sp_name}.</a>"
tag: Recommended
take_picture: Use the camera on your mobile phone and upload images of your
ID. We only use the images to verify your identity.
Expand All @@ -90,7 +94,7 @@ en:
are the owner of the ID.
upload_no_image_storage: We do not store images you upload. We only verify your
identity.
use_cac_html: Do you have a government employee ID? %{link}
use_cac: Do you have a government employee ID?
use_cac_link: Use a PIV/CAC instead
welcome: We verify your identity to make sure you are you—not someone pretending
to be you. Verifying your identity lets you access services that handle sensitive
Expand Down
6 changes: 5 additions & 1 deletion config/locales/doc_auth/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ es:
que subes. Solo verificamos su identidad.
document_capture_upload_image: Solo utilizamos su ID para verificar su identidad
y no guardaremos ninguna imagen.
id_worn_html: "<strong>¿Su identificación está desgastada o dañada?</strong>
Use otra identificación emitida por el estado si tiene una."
interstitial_eta: Esto puede tardar hasta un minuto. Cargaremos el siguiente
paso automáticamente cuando esté listo.
interstitial_thanks: "¡Gracias por su paciencia!"
Expand All @@ -77,6 +79,8 @@ es:
la identificación emitida por su estado.
- Cuando haya terminado, haga clic en continuar aquí para terminar de verificar
su identidad.
no_other_id_help_bold_html: "<strong>Si no tiene otra identificación emitida
por el estado</strong>, <a href=%{failure_to_proof_url}>obtenga ayuda en %{sp_name}.</a>"
tag: Recomendar
take_picture: Use la cámara en su teléfono móvil y cargue imágenes de su identificación.
Solo usamos las imágenes para verificar su identidad.
Expand All @@ -95,7 +99,7 @@ es:
propietario de la identificación.
upload_no_image_storage: No almacenamos imágenes que cargue. Solo verificamos
su identidad.
use_cac_html: "¿Tiene una identificación de empleado del gobierno? %{link}"
use_cac: "¿Tiene una identificación de empleado del gobierno?"
use_cac_link: Usa un PIV/CAC en su lugar
welcome: Verificamos su identidad para asegurarnos de que usted es usted, y
no alguien que pretende ser usted. Verificar su identidad le permite acceder
Expand Down
7 changes: 6 additions & 1 deletion config/locales/doc_auth/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,18 @@ fr:
images que vous téléchargez. Nous vérifions uniquement votre identité.
document_capture_upload_image: Nous n'utilisons votre identifiant que pour vérifier
votre identité, et nous n'enregistrerons aucune image.
id_worn_html: "<strong>Votre pièce d'identité est-elle usée ou endommagée?</strong>
Utilisez un autre identifiant émis par l'État si vous en avez un."
interstitial_eta: Cela peut prendre jusqu'à une minute. Nous chargerons automatiquement
l’étape suivante une fois celle-ci terminée.
interstitial_thanks: Merci pour votre patience!
link_sent:
- Veuillez vérifier votre téléphone et suivre les instructions pour prendre
une photo de votre identité émise par l'État.
- Lorsque vous avez terminé, cliquez ici pour continuer à vérifier votre identité.
no_other_id_help_bold_html: "<strong>Si vous n'avez pas d'autre identifiant
émis par l'État</strong>, <a href=%{failure_to_proof_url}>obtenez de l'aide
auprès de %{sp_name}.</a>"
tag: Recommander
take_picture: Utilisez l'appareil photo sur votre téléphone portable et téléchargez
des images de votre identifiant. Nous utilisons uniquement les images pour
Expand All @@ -103,7 +108,7 @@ fr:
une photo de vous pour confirmer que vous êtes le propriétaire de la pièce
d'identité.
upload_no_image_storage: Nous ne stockons pas les images que vous téléchargez.
use_cac_html: Avez-vous un identifiant d'employé du gouvernement? %{link}
use_cac: Avez-vous un identifiant d'employé du gouvernement?
use_cac_link: Utilisez plutôt un PIV/CAC
welcome: Nous vérifions votre identité pour nous assurer que vous êtes bien,
et non quelqu'un prétendant être vous. La vérification de votre identité vous
Expand Down
Loading

0 comments on commit 7d5d77a

Please sign in to comment.