Compare commits

..

1 Commits

Author SHA1 Message Date
Alie Langston
139689ccf7 added object tracking
moved load of library

updated test

removed async

trying to retest

Retesting

added test back

fixed errors due to next button

removed try catch so errors occur

added try catch back

added in ignore

readded libraries

stops detection when photo is taken, stops erroring issue

added help text

added spinner and better mocked blazeface

moved img element back to correct place

updated for requested changes

added coco-ssd object tracking

updated ranges

updated messages and ranges

updated ranges
2020-09-01 11:10:29 -04:00
21 changed files with 5494 additions and 9511 deletions

14113
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,16 +31,17 @@
"dependencies": {
"@edx/frontend-component-footer": "10.0.11",
"@edx/frontend-component-header": "2.0.5",
"@edx/frontend-platform": "1.6.1",
"@edx/frontend-platform": "1.1.14",
"@edx/paragon": "9.1.1",
"@fortawesome/fontawesome-svg-core": "1.2.32",
"@fortawesome/fontawesome-svg-core": "1.2.30",
"@fortawesome/free-brands-svg-icons": "5.8.2",
"@fortawesome/free-regular-svg-icons": "5.7.2",
"@fortawesome/free-solid-svg-icons": "5.8.2",
"@fortawesome/react-fontawesome": "0.1.12",
"@tensorflow-models/blazeface": "git+https://github.com/alangsto/blazeface.git",
"@tensorflow/tfjs-converter": "1.6.1",
"@tensorflow/tfjs-core": "1.6.1",
"@fortawesome/react-fontawesome": "0.1.11",
"@tensorflow-models/coco-ssd": "1.0.0",
"@tensorflow/tfjs": "1.6.0",
"@tensorflow/tfjs-converter": "1.6.0",
"@tensorflow/tfjs-core": "1.6.0",
"babel-polyfill": "6.26.0",
"bowser": "^2.10.0",
"classnames": "2.2.6",
@@ -77,15 +78,15 @@
"redux-saga": "1.1.3",
"redux-thunk": "2.3.0",
"reselect": "4.0.0",
"universal-cookie": "4.0.4"
"universal-cookie": "4.0.3"
},
"devDependencies": {
"@edx/frontend-build": "5.3.2",
"@edx/frontend-build": "2.0.6",
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.7",
"codecov": "3.7.2",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.15.5",
"enzyme-adapter-react-16": "1.15.4",
"es-check": "5.0.0",
"husky": "3.0.9",
"jest": "^26.1.0",

View File

@@ -4,7 +4,7 @@
<title>Account | edX</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=webpackConfig.output.publicPath%>favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="root"></div>

View File

@@ -106,7 +106,6 @@ function EditableField(props) {
>
<label className="h6 d-block" htmlFor={id}>{label}</label>
<Input
data-hj-suppress
name={name}
id={id}
type={type}
@@ -156,7 +155,7 @@ function EditableField(props) {
</Button>
) : null}
</div>
<p data-hj-suppress>{renderValue(value)}</p>
<p>{renderValue(value)}</p>
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
</div>
),

View File

@@ -109,7 +109,6 @@ function EmailField(props) {
>
<label className="h6 d-block" htmlFor={id}>{label}</label>
<Input
data-hj-suppress
name={name}
id={id}
type="email"
@@ -157,7 +156,7 @@ function EmailField(props) {
</Button>
) : null}
</div>
<p data-hj-suppress>{renderValue()}</p>
<p>{renderValue()}</p>
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
</div>
),

View File

@@ -74,9 +74,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -138,9 +136,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -210,9 +206,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -274,9 +268,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -338,9 +330,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -402,9 +392,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -466,9 +454,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -530,9 +516,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -594,9 +578,7 @@ exports[`DemographicsSection should render 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -697,9 +679,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -761,9 +741,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -833,9 +811,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -897,9 +873,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -961,9 +935,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1025,9 +997,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1089,9 +1059,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1153,9 +1121,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1217,9 +1183,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1341,9 +1305,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1405,9 +1367,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Hispanic, Latin, or Spanish origin, White
</p>
<p
@@ -1469,9 +1429,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1533,9 +1491,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1597,9 +1553,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1661,9 +1615,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1725,9 +1677,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1789,9 +1739,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1853,9 +1801,7 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -1942,9 +1888,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2006,9 +1950,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Asian
</p>
<p
@@ -2070,9 +2012,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2134,9 +2074,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2198,9 +2136,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2262,9 +2198,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2326,9 +2260,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2390,9 +2322,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2454,9 +2384,7 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2543,9 +2471,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2607,9 +2533,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -2679,9 +2603,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2743,9 +2665,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2807,9 +2727,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2871,9 +2789,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -2935,9 +2851,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Other: test
</p>
<p
@@ -2999,9 +2913,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3063,9 +2975,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3152,9 +3062,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer to self describe: test
</p>
<p
@@ -3216,9 +3124,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
<button
className="btn btn-link p-0"
onBlur={[Function]}
@@ -3288,9 +3194,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3352,9 +3256,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3416,9 +3318,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3480,9 +3380,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3544,9 +3442,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3608,9 +3504,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p
@@ -3672,9 +3566,7 @@ exports[`DemographicsSection should set user input correctly when user provides
Edit
</button>
</div>
<p
data-hj-suppress={true}
>
<p>
Prefer not to respond
</p>
<p

View File

@@ -182,21 +182,6 @@
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.photo.enable.detection": "Enable Face Detection",
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.feedback.correct": "Face is in a good position.",
"id.verification.photo.feedback.two.faces": "More than one face detected.",
"id.verification.photo.feedback.no.faces": "No face detected.",
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
@@ -258,8 +243,7 @@
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
@@ -280,13 +264,11 @@
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.review.error": "edX Support Page",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists,\n please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Edit{sr}"
}

View File

@@ -11,7 +11,7 @@
"account.settings.section.account.information": "Información de la cuenta",
"account.settings.section.account.information.description": "Estas configuraciones incluyen información básica sobre tu cuenta.",
"account.settings.section.profile.information": "Información del perfil",
"account.settings.section.demographics.information": "Información opcional",
"account.settings.section.demographics.information": "Optional Information",
"account.settings.section.site.preferences": "Preferencias del sitio",
"account.settings.section.linked.accounts": "Cuentas vinculadas",
"account.settings.section.linked.accounts.description": "Puedes vincular tus cuentas de redes sociales para simplificar el proceso de iniciar sesión en edX.",
@@ -117,43 +117,43 @@
"account.settings.delete.account.modal.confirm.cancel": "Cancelar",
"account.settings.delete.account.error.unable.to.delete": "No es posible eliminar esta cuenta",
"account.settings.delete.account.error.no.password": "Se requiere una contraseña",
"account.settings.delete.account.error.invalid.password": "Contraseña incorrecta",
"account.settings.delete.account.error.invalid.password": "Password is incorrect",
"account.settings.delete.account.error.unable.to.delete.details": "Ocurrió un error al procesar tu solicitud. Por favor, intente nuevamente más tarde.",
"account.settings.delete.account.modal.after.header": "¡Sentimos que te vayas! Tu cuenta será eliminada en breve.",
"account.settings.delete.account.modal.after.text": "La eliminación de cuenta, incluyendo la eliminación de las listas de correo electrónico, puede tardar unas semanas en procesarse totalmente en nuestro sistema. Si quieres renunciar a recibir correos antes de que la eliminación se haya completado, por favor date de baja mediante el enlace que aparece al final de los correos.",
"account.settings.delete.account.modal.after.button": "Cerrar",
"account.settings.delete.account.text.3": "Puede que también pierdas el acceso a los certificados verificados y otros certificados de programas como los de los MicroMasters. Si quieres hacer una copia de dichos certificados para tus archivos antes de proceder a la eliminación, {actionLink}.",
"account.settings.message.demographics.service.issue": "Ocurrió un error al intentar recuperar o guardar la información de tu cuenta. Por favor inténtalo más tarde.",
"account.settings.field.demographics.gender": "Identidad de género",
"account.settings.field.demographics.gender.empty": "Añade identidad de género",
"account.settings.field.demographics.gender.options.empty": "Selecciona una identidad de género",
"account.settings.field.demographics.gender_description": "Descripción de identidad de género",
"account.settings.field.demographics.gender_description.empty": "Ingresa descripción",
"account.settings.field.demographics.ethnicity": "Identidad étnica/raza",
"account.settings.field.demographics.ethnicity.empty": "Añade identidad étnica/raza",
"account.settings.field.demographics.ethnicity.options.empty": "Selecciona todas las que apliquen",
"account.settings.field.demographics.income": "Ingreso familiar",
"account.settings.field.demographics.income.empty": "Añade ingreso familiar",
"account.settings.field.demographics.income.options.empty": "Selecciona un rango de ingreso familiar",
"account.settings.field.demographics.military_history": "Estatus militar en EE.UU.",
"account.settings.message.demographics.service.issue": "An error occurred attempting to retrieve or save your account information. Please try again later.",
"account.settings.field.demographics.gender": "Gender identity",
"account.settings.field.demographics.gender.empty": "Add gender identity",
"account.settings.field.demographics.gender.options.empty": "Select a gender identity",
"account.settings.field.demographics.gender_description": "Gender identity description",
"account.settings.field.demographics.gender_description.empty": "Enter description",
"account.settings.field.demographics.ethnicity": "Race/Ethnicity identity",
"account.settings.field.demographics.ethnicity.empty": "Add race/ethnicity identity",
"account.settings.field.demographics.ethnicity.options.empty": "Select all that apply",
"account.settings.field.demographics.income": "Family income",
"account.settings.field.demographics.income.empty": "Add family income",
"account.settings.field.demographics.income.options.empty": "Select a family income range",
"account.settings.field.demographics.military_history": "U.S. Military status",
"account.settings.field.demographics.military_history.empty": "Add military status",
"account.settings.field.demographics.military_history.options.empty": "Selecciona estatus militar",
"account.settings.field.demographics.learner_education_level": "Tu nivel educacional",
"account.settings.field.demographics.learner_education_level.empty": "Añade nivel educacional",
"account.settings.field.demographics.parent_education_level": "Nivel educacional de padres/tutores",
"account.settings.field.demographics.parent_education_level.empty": "Añade nivel educacional",
"account.settings.field.demographics.education_level.options.empty": "Selecciona nivel educacional",
"account.settings.field.demographics.work_status": "Estatus laboral",
"account.settings.field.demographics.work_status.empty": "Añade estatus laboral",
"account.settings.field.demographics.work_status.options.empty": "Selecciona estatus laboral",
"account.settings.field.demographics.work_status_description": "Descripción estatus laboral",
"account.settings.field.demographics.work_status_description.empty": "Ingresa descripción",
"account.settings.field.demographics.current_work_sector": "Área profesional actual",
"account.settings.field.demographics.current_work_sector.empty": "Añade área profesional",
"account.settings.field.demographics.future_work_sector": "Área profesional futura",
"account.settings.field.demographics.future_work_sector.empty": "Añade área profesional",
"account.settings.field.demographics.work_sector.options.empty": "Selecciona área profesional",
"account.settings.section.demographics.why": "¿Por qué edX obtiene esta información?",
"account.settings.field.demographics.military_history.options.empty": "Select military status",
"account.settings.field.demographics.learner_education_level": "Your education level",
"account.settings.field.demographics.learner_education_level.empty": "Add education level",
"account.settings.field.demographics.parent_education_level": "Parents/Guardians education level",
"account.settings.field.demographics.parent_education_level.empty": "Add education level",
"account.settings.field.demographics.education_level.options.empty": "Select education level",
"account.settings.field.demographics.work_status": "Employment status",
"account.settings.field.demographics.work_status.empty": "Add employment status",
"account.settings.field.demographics.work_status.options.empty": "Select employment status",
"account.settings.field.demographics.work_status_description": "Employment status description",
"account.settings.field.demographics.work_status_description.empty": "Enter description",
"account.settings.field.demographics.current_work_sector": "Current work industry",
"account.settings.field.demographics.current_work_sector.empty": "Add work industry",
"account.settings.field.demographics.future_work_sector": "Future work industry",
"account.settings.field.demographics.future_work_sector.empty": "Add work industry",
"account.settings.field.demographics.work_sector.options.empty": "Select work industry",
"account.settings.section.demographics.why": "Why does edX collect this information?",
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, comprueba la URL y vuelve a intentarlo.",
"account.settings.editable.field.password.reset.button.confirmation.support.link": "soporte técnico",
"account.settings.editable.field.password.reset.button.confirmation": "Hemos mandado un mensaje a {email}. Haz clic en el enlace en el mensaje para restablecer tu contraseña. ¿No recibiste el mensaje? Contáctate con {technicalSupportLink}.",
@@ -166,45 +166,30 @@
"account.settings.sso.unlink.account": "Desvincular la cuenta de {accountName} ",
"account.settings.sso.no.providers": "No se pueden vincular cuentas en este momento.",
"id.verification.existing.request.denied.text": "You cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
"id.verification.next": "Siguiente",
"id.verification.requirements.title": "Requerimientos de verificación por foto",
"id.verification.requirements.description": "Para completar la verificación por foto en línea, necesitarás lo siguiente:",
"id.verification.requirements.card.device.title": "Dispositivo con cámara",
"id.verification.requirements.card.device.allow": "Permitir",
"id.verification.requirements.card.id.title": "Identificación por foto",
"id.verification.requirements.card.id.text": "Necesitas un ID válido que contenga tu nombre completo y tu foto.",
"id.verification.next": "Next",
"id.verification.requirements.title": "Photo Verification Requirements",
"id.verification.requirements.description": "In order to complete Photo Verification online, you will need the following:",
"id.verification.requirements.card.device.title": "Device with Camera",
"id.verification.requirements.card.device.allow": "Allow",
"id.verification.requirements.card.id.title": "Photo Identification",
"id.verification.requirements.card.id.text": "You need a valid ID that contains your full name and photo.",
"id.verification.privacy.title": "Privacy Information",
"id.verification.privacy.need.photo.question": "¿Por qué edX necesita mi foto?",
"id.verification.privacy.need.photo.answer": "Utilizamos tus fotos de verificación para confirmar tu identidad y garantizar la validez de tu certificado.",
"id.verification.privacy.do.with.photo.question": "¿Qué hace edX con esta foto?",
"id.verification.privacy.need.photo.question": "Why does edX need my photo?",
"id.verification.privacy.need.photo.answer": "We use your verification photos to confirm your identity and ensure the validity of your certificate.",
"id.verification.privacy.do.with.photo.question": "What does edX do with this photo?",
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on edX after the verification process is complete.",
"id.verification.existing.request.title": "Verificación de identidad",
"id.verification.existing.request.title": "Identity Verification",
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Tomar la foto",
"id.verification.photo.retake": "Tomar nuevamente la foto",
"id.verification.photo.enable.detection": "Enable Face Detection",
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.feedback.correct": "Face is in a good position.",
"id.verification.photo.feedback.two.faces": "More than one face detected.",
"id.verification.photo.feedback.no.faces": "No face detected.",
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
"id.verification.camera.access.title": "Permisos de la cámara",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
"id.verification.camera.access.click.allow": "Por favor asegúrate de hacer clic en \"Permitir\"",
"id.verification.camera.access.enable": "Habilitar cámara",
"id.verification.camera.access.problems": "¿Tienes problemas?",
"id.verification.camera.access.skip": "Omitir y cargar un archivo de imagen",
"id.verification.camera.access.success": "Parece que tu cámara está funcionando y está lista.",
"id.verification.camera.access.click.allow": "Please make sure to click \"Allow\"",
"id.verification.camera.access.enable": "Enable Camera",
"id.verification.camera.access.problems": "Having problems?",
"id.verification.camera.access.skip": "Skip and upload image files instead",
"id.verification.camera.access.success": "Looks like your camera is working and ready.",
"id.verification.camera.access.failure": "It looks like we're unable to access your camera. You will need to upload image files of you and your photo id.",
"id.verification.camera.access.failure.temporary": "It looks like we're unable to access your camera. Please verify that your webcam is connected and that you have allowed your browser to access it.",
"id.verification.camera.access.failure.temporary.chrome": "To enable camera access in Chrome:",
@@ -232,7 +217,7 @@
"id.verification.camera.access.failure.temporary.safari.step2": "Click on the Safari app menu, then select \"Preferences.\" You can also use Command+, as a keyboard shortcut.",
"id.verification.camera.access.failure.temporary.safari.step3": "Select the \"Websites\" tab and then select \"Camera.\"",
"id.verification.camera.access.failure.temporary.safari.step4": "Select \"edx.org\" and change the camera permissions to \"Allow.\"",
"id.verification.photo.tips.title": "Consejos útiles de fotos",
"id.verification.photo.tips.title": "Helpful Photo Tips",
"id.verification.photo.tips.description": "Next, we'll need you to take a photo of your face. Please review the helpful tips below.",
"id.verification.photo.tips.list.title": "Photo Tips",
"id.verification.photo.tips.list.description": "To take a successful photo, make sure that:",
@@ -258,8 +243,7 @@
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
@@ -280,13 +264,11 @@
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.review.error": "edX Support Page",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists,\n please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Edit{sr}"
}

View File

@@ -182,21 +182,6 @@
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.photo.enable.detection": "Enable Face Detection",
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.feedback.correct": "Face is in a good position.",
"id.verification.photo.feedback.two.faces": "More than one face detected.",
"id.verification.photo.feedback.no.faces": "No face detected.",
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
@@ -258,8 +243,7 @@
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
@@ -280,13 +264,11 @@
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.review.error": "edX Support Page",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists,\n please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Edit{sr}"
}

View File

@@ -182,21 +182,6 @@
"id.verification.existing.request.pending.text": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
"id.verification.photo.take": "Take Photo",
"id.verification.photo.retake": "Retake Photo",
"id.verification.photo.enable.detection": "Enable Face Detection",
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
"id.verification.photo.feedback.correct": "Face is in a good position.",
"id.verification.photo.feedback.two.faces": "More than one face detected.",
"id.verification.photo.feedback.no.faces": "No face detected.",
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
"id.verification.camera.access.title": "Camera Permissions",
"id.verification.camera.access.title.success": "Camera Access Enabled",
"id.verification.camera.access.title.failed": "Camera Access Failed",
@@ -258,8 +243,7 @@
"id.verification.id.photo.title.upload": "Upload a Photo of Your ID",
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.id.photo.instructions.upload.error": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.id.photo.instructions.upload": "Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)",
"id.verification.account.name.title": "Account Name Check",
"id.verification.account.name.instructions": "The name on your account and the name on your ID must be an exact match. If not, please click \"No\" to update your account name.",
"id.verification.account.name.radio.label": "Does the name on your ID match the Account Name below?",
@@ -280,13 +264,11 @@
"id.verification.review.id.alt": "Photo of your ID to be submitted.",
"id.verification.review.id.retake": "Retake ID Photo",
"id.verification.review.confirm": "Submit",
"id.verification.review.error": "edX Support Page",
"id.verification.submitted.title": "Identity Verification in Progress",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will see a message on your dashboard when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
"id.verification.return.dashboard": "Return to Your Dashboard",
"id.verification.return.course": "Return to Course",
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
"idv.submission.alert.error": "\n We encountered a technical error while trying to submit ID verification.\n This might be a temporary issue, so please try again in a few minutes.\n If the problem persists,\n please go to {support_link} for help.\n ",
"id.verification.account.name.edit": "Edit{sr}"
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import * as blazeface from '@tensorflow-models/blazeface';
import * as cocoSsd from '@tensorflow-models/coco-ssd';
import CameraPhoto, { FACING_MODES } from 'jslib-html5-camera-photo';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form, Spinner } from '@edx/paragon';
@@ -18,42 +17,22 @@ class Camera extends React.Component {
this.setDetection = this.setDetection.bind(this);
this.state = {
dataUri: '',
outlineColor: '#ff3300',
videoHasLoaded: false,
shouldDetect: false,
isFinishedLoadingDetection: true,
shouldGiveFeedback: true,
feedback: '',
};
}
componentDidMount() {
this.cameraPhoto = new CameraPhoto(this.videoRef.current);
this.cameraPhoto.startCamera(
this.props.isPortrait ? FACING_MODES.USER : FACING_MODES.ENVIRONMENT,
{ width: 640, height: 480 },
);
this.cameraPhoto.startCamera(FACING_MODES.USER, { width: 1280 });
}
async componentWillUnmount() {
this.cameraPhoto.stopCamera();
}
sendEvent() {
let eventName = 'edx.id_verification';
if (this.props.isPortrait) {
eventName += '.user_photo';
} else {
eventName += '.id_photo';
}
if (this.state.shouldDetect) {
eventName += '.face_detection_enabled';
} else {
eventName += '.face_detection_disabled';
}
sendTrackEvent(eventName);
}
setDetection() {
this.setState(
{ shouldDetect: !this.state.shouldDetect },
@@ -62,7 +41,6 @@ class Camera extends React.Component {
this.setState({ isFinishedLoadingDetection: false });
this.startDetection();
}
this.sendEvent();
},
);
}
@@ -70,7 +48,7 @@ class Camera extends React.Component {
startDetection() {
setTimeout(() => {
if (this.state.videoHasLoaded) {
const loadModelPromise = blazeface.load();
const loadModelPromise = cocoSsd.load();
Promise.all([loadModelPromise])
.then((values) => {
this.setState({ isFinishedLoadingDetection: true });
@@ -85,7 +63,7 @@ class Camera extends React.Component {
}
detectFromVideoFrame = (model, video) => {
model.estimateFaces(video).then((predictions) => {
model.detect(video).then((predictions) => {
if (this.state.shouldDetect && !this.state.dataUri) {
this.showDetections(predictions);
@@ -104,117 +82,42 @@ class Camera extends React.Component {
}
// predictions is an array of objects describing each detected face
predictions.forEach((prediction) => {
const start = [prediction.topLeft[0], prediction.topLeft[1]];
const end = [prediction.bottomRight[0], prediction.bottomRight[1]];
const size = [end[0] - start[0], end[1] - start[1]];
if (prediction.class === 'person') {
const xAdjustment = 70;
const yAdjustment = 55;
const x = prediction.bbox[0] - xAdjustment;
const y = prediction.bbox[1] - yAdjustment;
const width = prediction.bbox[2];
// landmarks is an array of points representing each facial landmark (i.e. right eye, left eye, nose, etc.)
const features = prediction.landmarks;
let isInPosition = true;
let isInPosition;
// for each of the landmarks, determine if it is in position
for (let j = 0; j < features.length; j++) {
const x = features[j][0];
const y = features[j][1];
let isInRange;
if (this.props.isPortrait) {
isInRange = this.isInRangeForPortrait(x, y);
isInPosition = this.isInRangeForPortrait(x, y, width);
} else {
isInRange = this.isInRangeForID(x, y);
isInPosition = this.isInRangeForID(x, y, width);
}
// if it is not in range, give feedback depending on which feature is out of range
isInPosition = isInPosition && isInRange;
}
// draw a box depending on if all landmarks are in position
if (isInPosition) {
canvasContext.strokeStyle = '#00ffff';
canvasContext.lineWidth = 6;
canvasContext.strokeRect(start[0], start[1], size[0], size[1]);
// give positive feedback here if user is in correct position
this.giveFeedback(predictions.length, [], true);
} else {
canvasContext.fillStyle = 'rgba(255, 51, 0, 0.75)';
canvasContext.fillRect(start[0], start[1], size[0], size[1]);
this.giveFeedback(predictions.length, features[0], false);
// set the color depending on if all landmarks are in position
if (isInPosition) {
this.setState({ outlineColor: '#00ffff' });
} else {
this.setState({ outlineColor: '#ff3300' });
}
// Draw the bounding box.
canvasContext.strokeStyle = this.state.outlineColor;
canvasContext.lineWidth = 15;
canvasContext.strokeRect(0, 0, canvasContext.canvas.width, canvasContext.canvas.height);
}
});
if (predictions.length === 0) {
this.giveFeedback(predictions.length, [], false);
}
}
giveFeedback(numFaces, rightEye, isCorrect) {
if (this.state.shouldGiveFeedback) {
const currentFeedback = this.state.feedback;
let newFeedback = '';
if (numFaces === 1) {
// only give feedback if one face is detected otherwise
// it would be difficult to tell a user which face to move
if (isCorrect) {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.correct']);
} else {
// give feedback based on where user is
newFeedback = this.props.intl.formatMessage(messages[this.getGridPosition(rightEye)]);
}
} else if (numFaces > 1) {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.two.faces']);
} else {
newFeedback = this.props.intl.formatMessage(messages['id.verification.photo.feedback.no.faces']);
}
if (currentFeedback !== newFeedback) {
// only update status if it is different, so we don't overload the user with status updates
this.setState({ feedback: newFeedback });
}
// turn off feedback for one to ensure that instructions aren't disruptive/interrupting
this.setState({ shouldGiveFeedback: false });
setTimeout(() => {
this.setState({ shouldGiveFeedback: true });
}, 1000);
}
isInRangeForPortrait(x, y, width) {
return x > -80 && x < 70 && y > -20 && y < 80 && width > 300 && width < 650;
}
getGridPosition(coordinates) {
// Used to determine where a face is (i.e. top-left, center-right, bottom-center, etc.)
const x = coordinates[0];
const y = coordinates[1];
let messageBase = 'id.verification.photo.feedback';
const heightUpperLimit = 320;
const heightMiddleLimit = 160;
if (y < heightMiddleLimit) {
messageBase += '.top';
} else if (y < heightUpperLimit && y >= heightMiddleLimit) {
messageBase += '.center';
} else {
messageBase += '.bottom';
}
const widthRightLimit = 213;
const widthMiddleLimit = 427;
if (x < widthRightLimit) {
messageBase += '.right';
} else if (x >= widthRightLimit && x < widthMiddleLimit) {
messageBase += '.center';
} else {
messageBase += '.left';
}
return messageBase;
}
isInRangeForPortrait(x, y) {
return x > 47 && x < 570 && y > 100 && y < 410;
}
isInRangeForID(x, y) {
return x > 120 && x < 470 && y > 120 && y < 350;
isInRangeForID(x, y, width) {
return x > -60 && x < 10 && y > 0 && y < 150 && width > 230 && width < 540;
}
setVideoHasLoaded() {
@@ -225,9 +128,8 @@ class Camera extends React.Component {
if (this.state.dataUri) {
return this.reset();
}
const config = {
sizeFactor: this.getSizeFactor(),
sizeFactor: 1,
};
this.playShutterClick();
@@ -236,30 +138,6 @@ class Camera extends React.Component {
this.props.onImageCapture(dataUri);
}
getSizeFactor() {
let sizeFactor = 1;
const settings = this.cameraPhoto.getCameraSettings();
if (settings) {
const videoWidth = settings.width;
const videoHeight = settings.height;
// need to multiply by 3 because each pixel contains 3 bytes
const currentSize = videoWidth * videoHeight * 3;
// chose a limit of 9,999,999 (bytes) so that result will
// always be less than 10MB
const ratio = 9999999 / currentSize;
if (ratio < 1) {
// if the current resolution creates an image larger than 10 MB, adjust sizeFactor (resolution)
// to ensure that image will have a file size of less than 10 MB.
sizeFactor = ratio;
} else if (videoWidth === 640 && videoHeight === 480) {
// otherwise increase the resolution to try and prevent blurry images.
sizeFactor = 2;
}
}
return sizeFactor;
}
playShutterClick() {
const audio = new Audio(`data:audio/mp3;base64,${shutter.base64}`);
audio.play();
@@ -303,33 +181,15 @@ class Camera extends React.Component {
autoPlay
className="camera-video"
onLoadedData={() => { this.setVideoHasLoaded(); }}
style={{
display: this.state.dataUri ? 'none' : 'block',
WebkitTransform: 'scaleX(-1)',
transform: 'scaleX(-1)',
}}
playsInline
/>
<canvas
ref={this.canvasRef}
data-testid="detection-canvas"
className="canvas-video"
style={{
display: !this.state.shouldDetect || this.state.dataUri ? 'none' : 'block',
WebkitTransform: 'scaleX(-1)',
transform: 'scaleX(-1)',
}}
width="640"
height="480"
style={{ display: this.state.dataUri ? 'none' : 'block' }}
/>
<canvas ref={this.canvasRef} data-testid="detection-canvas" className="canvas-video" style={{ display: !this.state.shouldDetect || this.state.dataUri ? 'none' : 'block' }} height="375" width="500" />
<img
data-hj-suppress
alt="imgCamera"
src={this.state.dataUri}
className="camera-video"
style={{ display: this.state.dataUri ? 'block' : 'none' }}
/>
<div role="status" className="sr-only">{this.state.feedback}</div>
</div>
<button
className={`btn camera-btn ${

View File

@@ -35,7 +35,7 @@ function CameraHelpWithUpload(props) {
<p>
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])}
</p>
<ImageFileUpload onFileChange={setAndTrackIdPhotoFile} intl={props.intl} />
<ImageFileUpload onFileChange={setAndTrackIdPhotoFile} />
</Collapsible>
</div>
);

View File

@@ -88,74 +88,14 @@ const messages = defineMessages({
},
'id.verification.photo.enable.detection.portrait.help.text': {
id: 'id.verification.photo.enable.detection.portrait.help.text',
defaultMessage: 'If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.',
defaultMessage: 'If checked, a border will appear around the camera view. Your face can be seen clearly if the border is blue. If your face is not in a good position or undetectable, the border will be red.',
description: 'Help text that appears for enabling face detection on the portrait photo panel.',
},
'id.verification.photo.enable.detection.id.help.text': {
id: 'id.verification.photo.enable.detection.id.help.text',
defaultMessage: 'If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.',
defaultMessage: 'If checked, a border will appear around the camera view. The face can be seen clearly if the border is blue. If the face is not in a good position or undetectable, the border will be red.',
description: 'Help text that appears for enabling face detection on the portrait photo panel.',
},
'id.verification.photo.feedback.correct': {
id: 'id.verification.photo.feedback.correct',
defaultMessage: 'Face is in a good position.',
description: 'Text for screen reader when user\'s face is in a good position.',
},
'id.verification.photo.feedback.two.faces': {
id: 'id.verification.photo.feedback.two.faces',
defaultMessage: 'More than one face detected.',
description: 'Text for screen reader when more than one face detected.',
},
'id.verification.photo.feedback.no.faces': {
id: 'id.verification.photo.feedback.no.faces',
defaultMessage: 'No face detected.',
description: 'Text for screen reader when no face detected.',
},
'id.verification.photo.feedback.top.left': {
id: 'id.verification.photo.feedback.top.left',
defaultMessage: 'Incorrect position. Top left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.top.center': {
id: 'id.verification.photo.feedback.top.center',
defaultMessage: 'Incorrect position. Top center.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.top.right': {
id: 'id.verification.photo.feedback.top.right',
defaultMessage: 'Incorrect position. Top right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.left': {
id: 'id.verification.photo.feedback.center.left',
defaultMessage: 'Incorrect position. Center left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.center': {
id: 'id.verification.photo.feedback.center.center',
defaultMessage: 'Incorrect position. Too close to camera.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.center.right': {
id: 'id.verification.photo.feedback.center.right',
defaultMessage: 'Incorrect position. Center right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.left': {
id: 'id.verification.photo.feedback.bottom.left',
defaultMessage: 'Incorrect position. Bottom left.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.center': {
id: 'id.verification.photo.feedback.bottom.center',
defaultMessage: 'Incorrect position. Bottom center.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.photo.feedback.bottom.right': {
id: 'id.verification.photo.feedback.bottom.right',
defaultMessage: 'Incorrect position. Bottom right.',
description: 'Text for screen reader when face is in a bad position.',
},
'id.verification.camera.access.title': {
id: 'id.verification.camera.access.title',
defaultMessage: 'Camera Permissions',
@@ -463,14 +403,9 @@ const messages = defineMessages({
},
'id.verification.id.photo.instructions.upload': {
id: 'id.verification.id.photo.instructions.upload',
defaultMessage: 'Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. (Supported formats: .jpg, .jpeg, .png)',
defaultMessage: 'Please upload an ID photo. Ensure the entire ID fits inside the frame and is well-lit. (Supported formats: .jpg, .jpeg, .png)',
description: 'Instructions for ID photo upload.',
},
'id.verification.id.photo.instructions.upload.error': {
id: 'id.verification.id.photo.instructions.upload.error',
defaultMessage: 'The file you have selected is too large. Please try again with a file less than 10MB.',
description: 'Error message for file upload that is larger than 10MB.',
},
'id.verification.account.name.title': {
id: 'id.verification.account.name.title',
defaultMessage: 'Account Name Check',
@@ -571,11 +506,6 @@ const messages = defineMessages({
defaultMessage: 'Submit',
description: 'Button to confirm all information is correct and submit.',
},
'id.verification.review.error': {
id: 'id.verification.review.error',
defaultMessage: 'edX Support Page',
description: 'Text linking to the support page.',
},
'id.verification.submitted.title': {
id: 'id.verification.submitted.title',
defaultMessage: 'Identity Verification in Progress',

View File

@@ -45,9 +45,9 @@ function IdVerificationContextProvider({ children }) {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
setMediaAccess(MEDIA_ACCESS.GRANTED);
setMediaStream(stream);
// stop the stream, as we are not using it yet
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
// If we would like to stop the stream immediately. I guess we can leave it open
// const tracks = stream.getTracks();
// tracks.forEach(track => track.stop());
} catch (err) {
setMediaAccess(MEDIA_ACCESS.DENIED);
}

View File

@@ -1,52 +1,28 @@
import React, { useCallback, useState } from 'react';
import { intlShape } from '@edx/frontend-platform/i18n';
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@edx/paragon';
import messages from './IdVerification.messages';
export default function ImageFileUpload({ onFileChange, intl }) {
const [fileTooLargeError, setFileTooLargeError] = useState(false);
const maxFileSize = 10000000;
export default function ImageFileUpload({ onFileChange }) {
const handleChange = useCallback((e) => {
if (e.target.files.length === 0) {
return;
}
const fileObject = e.target.files[0];
if (fileObject.size < maxFileSize) {
const fileReader = new FileReader();
fileReader.addEventListener('load', () => onFileChange(fileReader.result));
fileReader.readAsDataURL(fileObject);
} else {
setFileTooLargeError(true);
}
const fileReader = new FileReader();
fileReader.addEventListener('load', () => onFileChange(fileReader.result));
fileReader.readAsDataURL(fileObject);
}, []);
return (
<>
<input
type="file"
accept="image/*"
data-testid="fileUpload"
onChange={handleChange}
/>
{fileTooLargeError && (
<Alert
id="fileTooLargeError"
variant="danger"
tabIndex="-1"
style={{ marginTop: '1rem' }}
>
{intl.formatMessage(messages['id.verification.id.photo.instructions.upload.error'])}
</Alert>
)}
</>
<input
type="file"
accept="image/*"
data-testid="fileUpload"
onChange={handleChange}
/>
);
}
ImageFileUpload.propTypes = {
onFileChange: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};

View File

@@ -5,7 +5,7 @@ export default function ImagePreview({ src, alt, id }) {
return (
<div id={id} className="image-preview">
<img data-hj-suppress style={{ objectFit: 'contain' }} src={src} alt={alt} />
<img style={{ objectFit: 'contain' }} src={src} alt={alt} />
</div>
);

View File

@@ -54,7 +54,6 @@
.canvas-video {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;

View File

@@ -1,6 +1,6 @@
import React, { useContext, useState, useEffect, useRef } from 'react';
import { Form } from '@edx/paragon';
import { Link, useHistory } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -11,7 +11,6 @@ import { IdVerificationContext } from '../IdVerificationContext';
import messages from '../IdVerification.messages';
function GetNameIdPanel(props) {
const { push } = useHistory();
const panelSlug = 'get-name-id';
const [nameMatches, setNameMatches] = useState(true);
const nameInputRef = useRef();
@@ -43,15 +42,6 @@ function GetNameIdPanel(props) {
}
}, [nameMatches, blankName]);
const handleSubmit = (e) => {
e.preventDefault();
// If the input is empty, or if no changes have been made to the
// mismatching name, the user should not be able to proceed.
if (!invalidName && !blankName) {
push(nextPanelSlug);
}
};
return (
<BasePanel
name={panelSlug}
@@ -61,7 +51,7 @@ function GetNameIdPanel(props) {
{props.intl.formatMessage(messages['id.verification.account.name.instructions'])}
</p>
<Form onSubmit={handleSubmit}>
<Form>
<Form.Group>
<Form.Label htmlFor="nameMatchesYes">
{props.intl.formatMessage(messages['id.verification.account.name.radio.label'])}

View File

@@ -1,6 +1,6 @@
import React, { useState, useContext } from 'react';
import { history } from '@edx/frontend-platform';
import { Input, Button, Spinner, Alert } from '@edx/paragon';
import { Input, Button, Spinner } from '@edx/paragon';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
@@ -25,7 +25,6 @@ function SummaryPanel(props) {
} = useContext(IdVerificationContext);
const nameToBeUsed = idPhotoName || nameOnAccount || '';
const [isSubmitting, setIsSubmitting] = useState(false);
const [submissionError, setSubmissionError] = useState(false);
function SubmitButton() {
async function handleClick() {
@@ -40,10 +39,6 @@ function SummaryPanel(props) {
if (result.success) {
stopUserMedia();
history.push(nextPanelSlug);
} else {
stopUserMedia();
setIsSubmitting(false);
setSubmissionError(true);
}
}
return (
@@ -64,24 +59,6 @@ function SummaryPanel(props) {
name={panelSlug}
title={props.intl.formatMessage(messages['id.verification.review.title'])}
>
{submissionError &&
<Alert
variant="danger"
data-testid="submission-error"
dismissible
onClose={() => setSubmissionError(false)}
>
<FormattedMessage
id="idv.submission.alert.error"
defaultMessage={`
We encountered a technical error while trying to submit ID verification.
This might be a temporary issue, so please try again in a few minutes.
If the problem persists,
please go to {support_link} for help.
`}
values={{ support_link: <Alert.Link href="https://support.edx.org/hc/en-us">{props.intl.formatMessage(messages['id.verification.review.error'])}</Alert.Link> }}
/>
</Alert>}
<p>
{props.intl.formatMessage(messages['id.verification.review.description'])}
</p>

View File

@@ -4,16 +4,12 @@ import { createMemoryHistory } from 'history';
import '@testing-library/jest-dom/extend-expect';
import { render, cleanup, screen, act, fireEvent } from '@testing-library/react';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import * as blazeface from '@tensorflow-models/blazeface';
import * as analytics from '@edx/frontend-platform/analytics';
import * as cocoSsd from '@tensorflow-models/coco-ssd';
import { IdVerificationContext } from '../IdVerificationContext';
import Camera from '../Camera';
jest.mock('jslib-html5-camera-photo');
jest.mock('@tensorflow-models/blazeface');
jest.mock('@edx/frontend-platform/analytics');
analytics.sendTrackEvent = jest.fn();
jest.mock('@tensorflow-models/coco-ssd');
window.HTMLMediaElement.prototype.play = () => {};
@@ -85,7 +81,7 @@ describe('SubmittedPanel', () => {
});
it('shows spinner when loading face detection', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
cocoSsd.load = jest.fn().mockResolvedValue({ detect: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -103,7 +99,7 @@ describe('SubmittedPanel', () => {
});
it('canvas is visible when detection is enabled', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
cocoSsd.load = jest.fn().mockResolvedValue({ detect: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -122,8 +118,7 @@ describe('SubmittedPanel', () => {
});
it('blazeface is called when detection is enabled', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
cocoSsd.load = jest.fn().mockResolvedValue({ detect: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -137,48 +132,6 @@ describe('SubmittedPanel', () => {
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
setTimeout(() => { expect(blazeface.load).toHaveBeenCalled(); }, 2000);
});
it('sends tracking events on portrait photo page', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.user_photo.face_detection_enabled');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.user_photo.face_detection_disabled');
});
it('sends tracking events on id photo page', async () => {
blazeface.load = jest.fn().mockResolvedValue({ estimateFaces: jest.fn().mockResolvedValue([]) });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCamera {...idProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
await fireEvent.loadedData(screen.queryByTestId('video'));
const checkbox = await screen.findByLabelText('Enable Face Detection');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.id_photo.face_detection_enabled');
await fireEvent.click(checkbox);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.id_verification.id_photo.face_detection_disabled');
setTimeout(() => { expect(cocoSsd.load).toHaveBeenCalled(); }, 2000);
});
});

View File

@@ -5,7 +5,7 @@ import { render, cleanup, act, screen, fireEvent, waitFor } from '@testing-libra
import '@edx/frontend-platform/analytics';
import '@testing-library/jest-dom/extend-expect';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import * as dataService from '../../data/service';
import { submitIdVerification } from '../../data/service';
import { IdVerificationContext } from '../../IdVerificationContext';
import SummaryPanel from '../../panels/SummaryPanel';
@@ -13,8 +13,9 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
jest.mock('../../data/service');
dataService.submitIdVerification = jest.fn().mockReturnValue({ success: true });
jest.mock('../../data/service', () => ({
submitIdVerification: jest.fn(() => ({ success: true, message: null })),
}));
const IntlSummaryPanel = injectIntl(SummaryPanel);
@@ -73,26 +74,7 @@ describe('SummaryPanel', () => {
it('submits', async () => {
const button = await screen.findByTestId('submit-button');
fireEvent.click(button);
expect(dataService.submitIdVerification).toHaveBeenCalled();
await waitFor(() => expect(contextValue.stopUserMedia).toHaveBeenCalled());
});
it('shows error when cannot submit', async () => {
await cleanup();
dataService.submitIdVerification = jest.fn().mockReturnValue({ success: false });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlSummaryPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('submit-button');
await act(async () => fireEvent.click(button));
expect(dataService.submitIdVerification).toHaveBeenCalled();
const error = await screen.getByTestId('submission-error');
expect(error).toBeDefined();
expect(submitIdVerification).toHaveBeenCalled();
await waitFor(() => expect(contextValue.stopUserMedia).toHaveBeenCalled())
});
});