Compare commits

...

33 Commits

Author SHA1 Message Date
Jawayria
ba5456b03f fix: add es-6 check 2022-06-07 19:57:02 +05:00
Adam Stankiewicz
4d8151a1d0 build: use shared browserslist config 2022-06-02 20:56:18 +05:00
edX requirements bot
c39fd332b6 chore!: Dropped support for Node 12 (#594) 2022-05-24 13:45:40 +05:00
Kshitij Sobti
04d515f554 feat: remove usage of scss variables (#601)
Remove usage of scss variables so that the MFE doesn't need scss variables at build-time.
2022-05-16 15:41:48 -04:00
David Joy
f425e9b94f build: adding nvmrc, bumping paragon to latest (#602)
Adding .nvmrc is a nicety.  Bumping paragon to latest is necessary to ensure compatiblity with the edX override header (frontend-component-header-edx).
2022-05-09 16:15:50 -04:00
edX requirements bot
7ec147fe6f feat: Add package-lock file version check (#599)
* feat: Add package-lock file version check

* fix: update name

Co-authored-by: Jawayria <39649635+Jawayria@users.noreply.github.com>
2022-05-09 19:19:39 +05:00
edx-semantic-release
7a8ae85b72 chore(i18n): update translations 2022-04-10 17:08:04 -04:00
Mohammad Ahtasham ul Hassan
0f8f5a1e9a fix: update packagelock (#596) 2022-04-08 15:05:24 +05:00
Usama Sadiq
757a9ac033 Merge pull request #593 from openedx/update-transifex-pull-translations-command
build: update pull_translations command
2022-04-04 14:29:45 +05:00
UsamaSadiq
9efc8d1290 build: update pull_translations command 2022-04-04 14:24:04 +05:00
Usama Sadiq
5f53270148 Merge pull request #591 from openedx/jenkins/transifex-client-migration-bb6390f
fix: transifex migration to new client
2022-04-04 14:06:38 +05:00
Ali Adnan
9716495951 Merge pull request #590 from openedx/aadnan/migrate-reactifex
feat: migrate translations to reactifex
2022-03-18 14:01:24 +05:00
edX requirements bot
f9b29948e7 fix: transifex migration to new client 2022-03-17 08:48:14 -04:00
aliadnan
bededb3912 feat: migrate translations to reactifex 2022-03-17 15:36:53 +05:00
David Joy
bb6390f9ae build: Delete CODEOWNERS (#589)
The community-engineering team no longer exists, so we don't want this CODEOWNERS file anymore.
2022-03-15 14:13:18 -04:00
Jawayria
d2300d2dfd feat: Added support for Node 16 (#588)
* feat: Added support for Node 16

* fix: run both npm 6 and 8
2022-03-15 19:15:11 +05:00
Renovate Bot
a501407907 fix(deps): update dependency qs to v6.10.3 2022-03-14 11:35:58 +00:00
Renovate Bot
88e63cd390 chore(deps): update dependency @testing-library/react to v12.1.4 2022-03-14 11:19:33 +00:00
Sarina Canelake
eaebe6980b Merge DEPR automation workflow
Add DEPR workflow automation
2022-02-24 15:21:56 -05:00
Sarina Canelake
d6efba63ca build: add DEPR workflow automation 2022-02-23 14:37:30 -05:00
edX Transifex Bot
257e425fd9 chore(i18n): update translations 2022-02-20 16:06:24 -05:00
Bianca Severino
02e3364874 Merge pull request #580 from openedx/bseverino/dashboard-language
[MST-1372] Remove dashboard reference from IDV submission confirmation
2022-02-16 14:21:07 -05:00
Bianca Severino
9ae74708fb fix: remove dashboard reference from IDV submitted 2022-02-16 11:40:58 -05:00
alangsto
e946e377c6 Merge pull request #579 from openedx/alangsto/remove_pending_language
fix: remove alert and update language if verified name comes from proctored exam attempt
2022-02-16 09:18:59 -05:00
Alie Langston
5dbe649b2c fix: remove alert if verified name comes from proctored exam attempt 2022-02-16 09:06:19 -05:00
Bianca Severino
2792902975 Merge pull request #577 from openedx/bseverino/idv-camera-language
[MST-1269] Change language referring to government ID
2022-02-14 15:11:57 -05:00
Bianca Severino
6f643070ea fix: change language referring to government ID 2022-02-14 11:45:54 -05:00
edX Transifex Bot
ce946f56b2 chore(i18n): update translations 2022-02-06 16:06:00 -05:00
Bianca Severino
de1e67f68d Merge pull request #573 from openedx/bseverino/remove-idv-experiment
[MST-869] Remove unused IDV experiment paths
2022-02-02 11:13:32 -05:00
Bianca Severino
79001bccd8 feat: remove unused IDV experiment paths 2022-01-27 14:21:43 -05:00
Bianca Severino
963884cc4c Merge pull request #570 from openedx/bseverino/idv-camera-focus
[MST-1075] Prevent focus jump when taking photo
2022-01-24 13:48:53 -05:00
Bianca Severino
e43c1bcc9e fix: prevent focus jump when taking photo 2022-01-20 17:09:15 -05:00
edX Transifex Bot
32cc2c7835 chore(i18n): update translations 2021-12-26 16:14:50 -05:00
57 changed files with 26425 additions and 6294 deletions

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @edx/community-engineering

View File

@@ -0,0 +1,19 @@
# Run the workflow that adds new tickets that are either:
# - labelled "DEPR"
# - title starts with "[DEPR]"
# - body starts with "Proposal Date" (this is the first template field)
# to the org-wide DEPR project board
name: Add newly created DEPR issues to the DEPR project board
on:
issues:
types: [opened]
jobs:
routeissue:
uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}

View File

@@ -1,4 +1,3 @@
---
name: ci
on:
push:
@@ -6,23 +5,23 @@ on:
- master
pull_request:
jobs:
build:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- 12
node: [16]
npm: [8.5.0]
npm-test:
- i18n_extract
- is-es5
- lint
- is-es6
- test
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install -g npm@6
node-version: ${{ matrix.node }}
- run: npm install -g npm@${{ matrix.npm }}
- run: make requirements
- run: make test NPM_TESTS=build
- run: make test NPM_TESTS=${{ matrix.npm-test }}

View File

@@ -0,0 +1,14 @@
#check package-lock file version
name: Lockfile Version check
on:
push:
branches:
- master
pull_request:
jobs:
version-check:
uses: edx/.github/.github/workflows/lockfileversion-check.yml@master

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16

View File

@@ -1,8 +1,9 @@
[main]
host = https://www.transifex.com
[edx-platform.frontend-app-account]
[o:open-edx:p:edx-platform:r:frontend-app-account]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON
type = KEYVALUEJSON

View File

@@ -1,3 +1,4 @@
export TRANSIFEX_RESOURCE = frontend-app-account
transifex_resource = frontend-app-account
transifex_langs = "ar,fr,es_419,zh_CN"
@@ -10,7 +11,7 @@ tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transi
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
NPM_TESTS=build i18n_extract lint test is-es5
NPM_TESTS=build i18n_extract lint test is-es6
.PHONY: test
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
@@ -45,15 +46,15 @@ push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --language=$(transifex_langs)
tx pull -f --mode reviewed --languages=$(transifex_langs)
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:

31108
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"is-es5": "es-check es5 ./dist/*.js",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"is-es6": "es-check es6 ./dist/*.js",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "fedx-scripts jest --coverage --passWithNoTests"
@@ -25,15 +25,14 @@
"access": "public"
},
"browserslist": [
"last 2 versions",
"ie 11"
"extends @edx/browserslist-config"
],
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.1.6",
"@edx/frontend-component-header": "2.3.0",
"@edx/frontend-platform": "1.14.1",
"@edx/paragon": "16.1.0",
"@edx/frontend-component-footer": "10.2.0",
"@edx/frontend-component-header": "2.4.5",
"@edx/frontend-platform": "1.15.1",
"@edx/paragon": "19.20.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
@@ -61,7 +60,7 @@
"lodash.pickby": "4.6.0",
"memoize-one": "5.2.1",
"prop-types": "15.7.2",
"qs": "6.10.1",
"qs": "6.10.3",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.6",
@@ -80,9 +79,11 @@
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/frontend-build": "8.1.6",
"@edx/browserslist-config": "1.0.0",
"@edx/frontend-build": "9.1.1",
"@edx/reactifex": "^1.0.3",
"@testing-library/jest-dom": "5.15.1",
"@testing-library/react": "12.1.2",
"@testing-library/react": "12.1.4",
"codecov": "3.8.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",

View File

@@ -259,25 +259,27 @@ class AccountSettingsPage extends React.Component {
);
}
renderFullNameHelpText = (status) => {
if (
!this.props.verifiedNameHistory
) {
renderFullNameHelpText = (status, proctoredExamId) => {
if (!this.props.verifiedNameHistory) {
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text']);
}
switch (status) {
case 'submitted':
if (this.props.committedValues.useVerifiedNameForCerts) {
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.submitted']);
}
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.submitted.certificate']);
default:
if (this.props.committedValues.useVerifiedNameForCerts) {
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.non.certificate']);
}
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.certificate']);
let messageString = 'account.settings.field.full.name.help.text';
if (status === 'submitted') {
messageString += '.submitted';
if (proctoredExamId) {
messageString += '.proctored';
}
} else {
messageString += '.default';
}
if (!this.props.committedValues.useVerifiedNameForCerts) {
messageString += '.certificate';
}
return this.props.intl.formatMessage(messages[messageString]);
}
renderVerifiedNameSuccessMessage = (verifiedName, created) => {
@@ -350,6 +352,7 @@ class AccountSettingsPage extends React.Component {
status,
profile_name: profileName,
verified_name: verifiedName,
proctored_exam_attempt_id: proctoredExamId,
} = verifiedNameRecord;
let willCertNameChange = false;
@@ -368,6 +371,10 @@ class AccountSettingsPage extends React.Component {
willCertNameChange = true;
}
if (proctoredExamId) {
return null;
}
switch (status) {
case 'approved':
return this.renderVerifiedNameSuccessMessage(verifiedName, created);
@@ -391,21 +398,29 @@ class AccountSettingsPage extends React.Component {
}
}
renderVerifiedNameHelpText = (status) => {
switch (status) {
case 'approved':
if (this.props.committedValues.useVerifiedNameForCerts) {
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.certificate']);
}
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.verified']);
case 'submitted':
if (this.props.committedValues.useVerifiedNameForCerts) {
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.submitted.certificate']);
}
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.submitted']);
default:
return null;
renderVerifiedNameHelpText = (status, proctoredExamId) => {
let messageStr = 'account.settings.field.name.verified.help.text';
// add additional string based on status
if (status === 'approved') {
messageStr += '.verified';
} else if (status === 'submitted') {
messageStr += '.submitted';
} else {
return null;
}
// add additional string if verified name came from a proctored exam attempt
if (proctoredExamId) {
messageStr += '.proctored';
}
// add additional string based on certificate name use
if (this.props.committedValues.useVerifiedNameForCerts) {
messageStr += '.certificate';
}
return this.props.intl.formatMessage(messages[messageStr]);
}
renderEmptyStaticFieldMessage() {
@@ -481,13 +496,13 @@ class AccountSettingsPage extends React.Component {
const hasLinkedTPA = findIndex(this.props.tpaProviders, provider => provider.connected) >= 0;
return (
<>
<div className="account-section" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
<div className="account-section pt-3 mb-5" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
{
this.props.mostRecentVerifiedName
&& this.renderVerifiedNameMessage(this.props.mostRecentVerifiedName)
}
<h2 className="section-heading">
<h2 className="section-heading h4 mb-3">
{this.props.intl.formatMessage(messages['account.settings.section.account.information'])}
</h2>
<p>{this.props.intl.formatMessage(messages['account.settings.section.account.information.description'])}</p>
@@ -524,7 +539,7 @@ class AccountSettingsPage extends React.Component {
}
helpText={
verifiedName
? this.renderFullNameHelpText(verifiedName.status)
? this.renderFullNameHelpText(verifiedName.status, verifiedName.proctored_exam_attempt_id)
: this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text'])
}
isEditable={
@@ -554,7 +569,7 @@ class AccountSettingsPage extends React.Component {
</div>
)
}
helpText={this.renderVerifiedNameHelpText(verifiedName.status)}
helpText={this.renderVerifiedNameHelpText(verifiedName.status, verifiedName.proctored_exam_attempt_id)}
isEditable={this.isEditable('verifiedName')}
isGrayedOut={!this.isEditable('verifiedName')}
onChange={this.handleEditableFieldChange}
@@ -626,8 +641,8 @@ class AccountSettingsPage extends React.Component {
)}
</div>
<div className="account-section" id="profile-information" ref={this.navLinkRefs['#profile-information']}>
<h2 className="section-heading">
<div className="account-section pt-3 mb-5" id="profile-information" ref={this.navLinkRefs['#profile-information']}>
<h2 className="section-heading h4 mb-3">
{this.props.intl.formatMessage(messages['account.settings.section.profile.information'])}
</h2>
@@ -669,8 +684,8 @@ class AccountSettingsPage extends React.Component {
)}
</div>
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && this.renderDemographicsSection()}
<div className="account-section" id="social-media">
<h2 className="section-heading">
<div className="account-section pt-3 mb-5" id="social-media">
<h2 className="section-heading h4 mb-3">
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
</h2>
<p>
@@ -706,8 +721,8 @@ class AccountSettingsPage extends React.Component {
/>
</div>
<div className="account-section" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
<h2 className="section-heading">
<div className="account-section pt-3 mb-5" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
<h2 className="section-heading h4 mb-3">
{this.props.intl.formatMessage(messages['account.settings.section.site.preferences'])}
</h2>
@@ -737,8 +752,8 @@ class AccountSettingsPage extends React.Component {
/>
</div>
<div className="account-section" id="linked-accounts" ref={this.navLinkRefs['#linked-accounts']}>
<h2 className="section-heading">{this.props.intl.formatMessage(messages['account.settings.section.linked.accounts'])}</h2>
<div className="account-section pt-3 mb-5" id="linked-accounts" ref={this.navLinkRefs['#linked-accounts']}>
<h2 className="section-heading h4 mb-3">{this.props.intl.formatMessage(messages['account.settings.section.linked.accounts'])}</h2>
<p>
{this.props.intl.formatMessage(
messages['account.settings.section.linked.accounts.description'],
@@ -748,7 +763,7 @@ class AccountSettingsPage extends React.Component {
<ThirdPartyAuth />
</div>
<div className="account-section" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
<DeleteAccount
isVerifiedAccount={this.props.isActive}
hasLinkedTPA={hasLinkedTPA}
@@ -888,15 +903,18 @@ AccountSettingsPage.propTypes = {
verifiedName: PropTypes.shape({
verified_name: PropTypes.string,
status: PropTypes.string,
proctored_exam_attempt_id: PropTypes.number,
}),
mostRecentVerifiedName: PropTypes.shape({
verified_name: PropTypes.string,
status: PropTypes.string,
proctored_exam_attempt_id: PropTypes.number,
}),
verifiedNameHistory: PropTypes.arrayOf(
PropTypes.shape({
verified_name: PropTypes.string,
status: PropTypes.string,
proctored_exam_attempt_id: PropTypes.number,
}),
),
};

View File

@@ -91,13 +91,13 @@ const messages = defineMessages({
defaultMessage: 'The name that is used for ID verification and that appears on your certificates.',
description: 'Help text for the account settings name field.',
},
'account.settings.field.full.name.help.text.non.certificate': {
id: 'account.settings.field.full.name.help.text.non.certificate',
'account.settings.field.full.name.help.text.default': {
id: 'account.settings.field.full.name.help.text.default',
defaultMessage: 'The name that appears on your public profile.',
description: 'Help text for the account settings name field.',
},
'account.settings.field.full.name.help.text.certificate': {
id: 'account.settings.field.full.name.help.text.certificate',
'account.settings.field.full.name.help.text.default.certificate': {
id: 'account.settings.field.full.name.help.text.default.certificate',
defaultMessage: 'This name is selected to appear on your certificates and public-facing records.',
description: 'Help text for the account settings name field.',
},
@@ -108,27 +108,47 @@ const messages = defineMessages({
},
'account.settings.field.name.verified.help.text.verified': {
id: 'account.settings.field.name.verified.help.text.verified',
defaultMessage: 'This name has been verified by government ID.',
defaultMessage: 'This name has been verified by photo ID.',
description: 'Help text for the account settings verified name field when the name is verified.',
},
'account.settings.field.name.verified.help.text.certificate': {
id: 'account.settings.field.name.verified.help.text.certificate',
defaultMessage: 'This name has been verified by government ID and selected to appear on your certificates and public-facing records.',
'account.settings.field.name.verified.help.text.verified.proctored': {
id: 'account.settings.field.name.verified.help.text.verified.proctored',
defaultMessage: 'This name has been verified by proctoring.',
description: 'Help text for the account settings verified name field when the name is verified through proctoring.',
},
'account.settings.field.name.verified.help.text.verified.certificate': {
id: 'account.settings.field.name.verified.help.text.verified.certificate',
defaultMessage: 'This name has been verified by photo ID, and is selected to appear on your certificates and public-facing records.',
description: 'Help text for the account settings verified name field when the name is selected for certificates.',
},
'account.settings.field.name.verified.help.text.verified.proctored.certificate': {
id: 'account.settings.field.name.verified.help.text.verified.proctored.certificate',
defaultMessage: 'This name has been verified by proctoring, and is selected to appear on your certificates and public-facing records.',
description: 'Help text for the account settings verified name field when the name is selected for certificates, and the name is verified through proctoring.',
},
'account.settings.field.name.verified.help.text.submitted': {
id: 'account.settings.field.name.verified.help.text.submitted',
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Verified name cannot be changed at this time.',
description: 'Help text for the account settings verified name field when a verified name has been submitted.',
},
'account.settings.field.name.verified.help.text.submitted.proctored': {
id: 'account.settings.field.name.verified.help.text.submitted.proctored',
defaultMessage: 'Your proctored exam has been submitted. Verified name cannot be changed at this time. Please check back in 2-5 days.',
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring.',
},
'account.settings.field.name.verified.help.text.submitted.certificate': {
id: 'account.settings.field.name.verified.help.text.submitted.certificate',
defaultMessage: 'When identity verification is successful, this name will appear on your certificates and public-facing records. Verified name cannot be changed at this time.',
description: 'Help text for the account settings verified name field when a verified name has been submitted and will appear on certificates.',
},
'account.settings.field.name.verified.help.text.submitted.proctored.certificate': {
id: 'account.settings.field.name.verified.help.text.submitted.proctored.certificate',
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.',
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring and will appear on certificates.',
},
'account.settings.field.name.verified.verification.alert': {
id: 'account.settings.field.name.verified.verification.help',
defaultMessage: 'Enter your name as it appears on your government-issued ID.',
defaultMessage: 'Enter your name as it appears on your unexpired student, work, or government-issued identification card.',
description: 'Form label instructing the user to enter the name on their ID.',
},
'account.settings.field.full.name.help.text.submitted': {
@@ -136,11 +156,21 @@ const messages = defineMessages({
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
description: 'Help text for the account settings full name field when a verified name has been submitted.',
},
'account.settings.field.full.name.help.text.submitted.proctored': {
id: 'account.settings.field.full.name.help.text.submitted.proctored',
defaultMessage: 'Your proctored exam has been submitted. Full name cannot be changed at this time. Please check back in 2-5 days.',
description: 'Help text for the account settings full name field when a verified name has been submitted through proctoring.',
},
'account.settings.field.full.name.help.text.submitted.certificate': {
id: 'account.settings.field.full.name.help.text.submitted.certificate',
defaultMessage: 'When identity verification is successful, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.',
description: 'Help text for the account settings full name field when a full name has been submitted and will appear on certificates.',
},
'account.settings.field.full.name.help.text.submitted.proctored.certificate': {
id: 'account.settings.field.full.name.help.text.submitted.proctored.certificate',
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.',
description: 'Help text for the account settings full name field when a full name has been submitted and will appear on certificates.',
},
'account.settings.field.name.verified.success.message': {
id: 'account.settings.field.name.verified.success.message',
defaultMessage: 'Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.',

View File

@@ -1,15 +1,20 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { breakpoints, useWindowSize } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { NavHashLink } from 'react-router-hash-link';
import Scrollspy from 'react-scrollspy';
import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
import messages from './AccountSettingsPage.messages';
function JumpNav({ intl, displayDemographicsLink }) {
function JumpNav({
intl,
displayDemographicsLink,
}) {
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
return (
<div className="jump-nav">
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
<Scrollspy
items={[
'basic-information',

View File

@@ -8,7 +8,7 @@ export default function NotFoundPage() {
<FormattedMessage
id="error.notfound.message"
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
description="error message when a page does not exist"
description="Error message when a page does not exist"
/>
</p>
</div>

View File

@@ -14,12 +14,11 @@
display: inline-block;
}
.jump-nav-sm {
top: 1rem;
}
.jump-nav {
@media (min-width: map-get($grid-breakpoints, "sm")) {
padding-top: 1rem;
position: sticky;
top: 1rem;
}
li {
margin-bottom: .5rem;
@@ -30,16 +29,6 @@
}
}
.section-heading {
@extend .h4;
margin-bottom: map-get($spacers, 3);
}
.account-section {
// These properties together will shift the hashlink position
margin-bottom: map-get($spacers, 5);
padding-top: 1rem;
}
.custom-switch {
padding: 0;

View File

@@ -17,7 +17,7 @@ const ManagedProfileAlert = ({ profileDataManager }) => (
<FormattedMessage
id="account.settings.coaching.managed.alert"
defaultMessage="Your name is managed by {managerTitle}. Contact your administrator for help."
description="alert message informing the user their account data is managed by a third party"
description="Alert message informing the user their account data is managed by a third party"
values={{
managerTitle: <b>{profileDataManager}</b>,
}}

View File

@@ -114,7 +114,7 @@ exports[`CoachingConsent disables name field on enterprise user 1`] = `
className="mt-3"
>
<a
className="mt-3 text-dark btn-link small"
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
href="http://localhost:18000/dashboard/"
onClick={[Function]}
target="_self"
@@ -263,7 +263,7 @@ exports[`CoachingConsent should render 1`] = `
className="mt-3"
>
<a
className="mt-3 text-dark btn-link small"
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
href="http://localhost:18000/dashboard/"
onClick={[Function]}
target="_self"

View File

@@ -72,7 +72,7 @@ export class DeleteAccount extends React.Component {
return (
<div>
<h2 className="section-heading">
<h2 className="section-heading h4 mb-3">
{intl.formatMessage(messages['account.settings.delete.account.header'])}
</h2>
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>

View File

@@ -16,21 +16,6 @@ Array [
className=""
role="dialog"
>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>
<div
data-focus-guard={true}
style={
@@ -207,21 +192,6 @@ Array [
}
tabIndex={0}
/>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={1}
/>
<div
data-focus-lock-disabled={false}
onBlur={[Function]}
@@ -418,21 +388,6 @@ Array [
}
tabIndex={0}
/>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={1}
/>
<div
data-focus-lock-disabled={false}
onBlur={[Function]}

View File

@@ -3,7 +3,7 @@
exports[`DeleteAccount should match default section snapshot 1`] = `
<div>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Delete My Account
</h2>
@@ -28,7 +28,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
</p>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
onClick={[Function]}
target="_self"
@@ -52,7 +52,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
exports[`DeleteAccount should match unverified account section snapshot 1`] = `
<div>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Delete My Account
</h2>
@@ -77,7 +77,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
</p>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
onClick={[Function]}
target="_self"
@@ -121,7 +121,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
<span>
Before proceeding, please
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email-"
onClick={[Function]}
target="_self"
@@ -138,7 +138,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
exports[`DeleteAccount should match unverified account section snapshot 2`] = `
<div>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Delete My Account
</h2>
@@ -163,7 +163,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
</p>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
onClick={[Function]}
target="_self"
@@ -207,7 +207,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
<span>
Before proceeding, please
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/articles/207206067"
onClick={[Function]}
target="_self"

View File

@@ -16,21 +16,6 @@ Array [
className=""
role="dialog"
>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>
<div
data-focus-guard={true}
style={
@@ -128,21 +113,6 @@ Array [
className=""
role="dialog"
>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>
<div
data-focus-guard={true}
style={
@@ -240,21 +210,6 @@ Array [
className=""
role="dialog"
>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>
<div
data-focus-guard={true}
style={
@@ -352,21 +307,6 @@ Array [
className=""
role="dialog"
>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={-1}
/>
<div
data-focus-guard={true}
style={
@@ -479,21 +419,6 @@ Array [
}
tabIndex={0}
/>
<div
data-focus-guard={true}
style={
Object {
"height": "0px",
"left": "1px",
"overflow": "hidden",
"padding": 0,
"position": "fixed",
"top": "1px",
"width": "1px",
}
}
tabIndex={1}
/>
<div
data-focus-lock-disabled={false}
onBlur={[Function]}

View File

@@ -162,8 +162,8 @@ class DemographicsSection extends React.Component {
const showWorkStatusDescribe = this.props.formValues.demographics_work_status === OTHER;
return (
<div className="account-section" id="demographics-information" ref={this.props.forwardRef}>
<h2 className="section-heading">
<div className="account-section pt-3 mb-5" id="demographics-information" ref={this.props.forwardRef}>
<h2 className="section-heading h4 mb-3">
{this.props.intl.formatMessage(messages['account.settings.section.demographics.information'])}
</h2>
<p>

View File

@@ -2,27 +2,27 @@
exports[`DemographicsSection should render 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -34,7 +34,6 @@ exports[`DemographicsSection should render 1`] = `
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -48,6 +47,11 @@ exports[`DemographicsSection should render 1`] = `
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
@@ -644,27 +648,27 @@ exports[`DemographicsSection should render 1`] = `
exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -676,7 +680,6 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -690,6 +693,11 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
@@ -1300,27 +1308,27 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
exports[`DemographicsSection should render an Alert when demographicsOptions props are empty 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -1332,7 +1340,6 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -1346,6 +1353,11 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
@@ -1369,27 +1381,27 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
exports[`DemographicsSection should render ethnicity correctly when multiple options are selected 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -1401,7 +1413,6 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -1415,6 +1426,11 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
@@ -2004,27 +2020,27 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
exports[`DemographicsSection should render ethnicity text correctly 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -2036,7 +2052,6 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -2050,6 +2065,11 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
@@ -2639,27 +2659,27 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
exports[`DemographicsSection should set user input correctly when user provides answers to work_status question 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -2671,7 +2691,6 @@ exports[`DemographicsSection should set user input correctly when user provides
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -2685,6 +2704,11 @@ exports[`DemographicsSection should set user input correctly when user provides
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
@@ -3281,27 +3305,27 @@ exports[`DemographicsSection should set user input correctly when user provides
exports[`DemographicsSection should set user input correctly when user provides gender self-description 1`] = `
<div
className="account-section"
className="account-section pt-3 mb-5"
id="demographics-information"
>
<h2
className="section-heading"
className="section-heading h4 mb-3"
>
Optional Information
</h2>
<p>
<a
className="default-link standalone-link"
className="pgn__hyperlink default-link standalone-link"
href="http://localhost:5335/demographics"
onClick={[Function]}
rel="noopener noopener noreferrer"
rel="noopener noreferrer"
target="_blank"
>
Why does localhost collect this information?
<span
className="d-inline-block align-text-top"
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
@@ -3313,7 +3337,6 @@ exports[`DemographicsSection should set user input correctly when user provides
>
<svg
aria-hidden={true}
aria-label=""
fill="none"
focusable={false}
height={24}
@@ -3327,6 +3350,11 @@ exports[`DemographicsSection should set user input correctly when user provides
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>

View File

@@ -23,12 +23,12 @@ const messages = defineMessages({
},
'account.settings.name.change.id.name.label': {
id: 'account.settings.name.change.id.name.label',
defaultMessage: 'Enter your name as it appears on your government-issued ID.',
defaultMessage: 'Enter your name as it appears on your unexpired student, work, or government-issued identification card.',
description: 'Form label instructing the user to enter the name on their ID.',
},
'account.settings.name.change.id.name.placeholder': {
id: 'account.settings.name.change.id.name.placeholder',
defaultMessage: 'Enter the name on your government ID',
defaultMessage: 'Enter the name on your photo ID',
description: 'Form label instructing the user to enter the name on their ID.',
},
'account.settings.name.change.error.valid.name': {

View File

@@ -70,7 +70,7 @@ describe('NameChange', () => {
afterEach(() => jest.clearAllMocks());
it('renders populated input after clicking continue if verified_name in form data', async () => {
const getInput = () => screen.queryByPlaceholderText('Enter the name on your government ID');
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
render(reduxWrapper(<IntlNameChange {...props} />));
expect(getInput()).toBeNull();
@@ -82,7 +82,7 @@ describe('NameChange', () => {
});
it('renders empty input after clicking continue if verified_name not in form data', async () => {
const getInput = () => screen.queryByPlaceholderText('Enter the name on your government ID');
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
const formProps = {
...props,
formValues: {
@@ -112,7 +112,7 @@ describe('NameChange', () => {
const continueButton = screen.getByText('Continue');
fireEvent.click(continueButton);
const input = screen.getByPlaceholderText('Enter the name on your government ID');
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
fireEvent.change(input, { target: { value: 'Verified Name' } });
const submitButton = screen.getByText('Continue');
@@ -139,7 +139,7 @@ describe('NameChange', () => {
const continueButton = screen.getByText('Continue');
fireEvent.click(continueButton);
const input = screen.getByPlaceholderText('Enter the name on your government ID');
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
fireEvent.change(input, { target: { value: 'Verified Name' } });
const submitButton = screen.getByText('Continue');
@@ -155,7 +155,7 @@ describe('NameChange', () => {
const continueButton = screen.getByText('Continue');
fireEvent.click(continueButton);
const input = screen.getByPlaceholderText('Enter the name on your government ID');
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
fireEvent.change(input, { target: { value: 'Verified Name' } });
const submitButton = screen.getByText('Continue');

View File

@@ -2,7 +2,7 @@
exports[`JumpNav should not render Optional Information link 1`] = `
<div
className="jump-nav"
className="jump-nav jump-nav-sm position-sticky pt-3"
>
<ul
className="list-unstyled"
@@ -92,7 +92,7 @@ exports[`JumpNav should not render Optional Information link 1`] = `
exports[`JumpNav should render Optional Information link 1`] = `
<div
className="jump-nav"
className="jump-nav jump-nav-sm position-sticky pt-3"
>
<ul
className="list-unstyled"

View File

@@ -20,16 +20,22 @@
"account.settings.field.full.name": "الاسم الكامل",
"account.settings.field.full.name.empty": "إضافة اسم",
"account.settings.field.full.name.help.text": "الاسم المستخدم للتحقق من هويتك والذي سوف يظهر على الشهادات الخاصة بك.",
"account.settings.field.full.name.help.text.non.certificate": "The name that appears on your public profile.",
"account.settings.field.full.name.help.text.certificate": "This name is selected to appear on your certificates and public-facing records.",
"account.settings.field.full.name.help.text.default": "The name that appears on your public profile.",
"account.settings.field.full.name.help.text.default.certificate": "This name is selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified": "Verified name",
"account.settings.field.name.verified.help.text.verified": "This name has been verified by government ID.",
"account.settings.field.name.verified.help.text.certificate": "This name has been verified by government ID and selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.verified": "This name has been verified by photo ID.",
"account.settings.field.name.verified.help.text.verified.proctored": "This name has been verified by proctoring.",
"account.settings.field.name.verified.help.text.verified.certificate": "This name has been verified by photo ID, and is selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "This name has been verified by proctoring, and is selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Verified name cannot be changed at this time.",
"account.settings.field.name.verified.help.text.submitted.proctored": "Your proctored exam has been submitted. Verified name cannot be changed at this time. Please check back in 2-5 days.",
"account.settings.field.name.verified.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Verified name cannot be changed at this time.",
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your government-issued ID.",
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.",
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your unexpired student, work, or government-issued identification card.",
"account.settings.field.full.name.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.",
"account.settings.field.full.name.help.text.submitted.proctored": "Your proctored exam has been submitted. Full name cannot be changed at this time. Please check back in 2-5 days.",
"account.settings.field.full.name.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "Once your proctored exam passes review, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
"account.settings.field.name.verified.success.message": "Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.",
"account.settings.field.name.verified.success.message.header": "Your name change request is complete!",
"account.settings.field.name.verified.failure.message": "Your most recent identity verification attempt did not pass. Related account settings have been restored.",
@@ -186,8 +192,8 @@
"account.settings.name.change.title.begin": "Before we begin",
"account.settings.name.change.warning.one": "Warning: This action updates the name that appears on all certificates that have been earned on this account in the past and any certificates you are currently earning or will earn in the future.",
"account.settings.name.change.warning.two": "This action cannot be undone without verifying your identity.",
"account.settings.name.change.id.name.label": "Enter your name as it appears on your government-issued ID.",
"account.settings.name.change.id.name.placeholder": "Enter the name on your government ID",
"account.settings.name.change.id.name.label": "Enter your name as it appears on your unexpired student, work, or government-issued identification card.",
"account.settings.name.change.id.name.placeholder": "Enter the name on your photo ID",
"account.settings.name.change.error.valid.name": "Please enter a valid name.",
"account.settings.name.change.error.general": "A technical error occurred. Please try again.",
"account.settings.name.change.continue": "Continue",
@@ -206,7 +212,6 @@
"id.verification.access.blocked.denied": "We 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": "التالي",
"id.verification.support": "support",
"id.verification.continue.upload": "Continue with Upload",
"id.verification.example.card.alt": "مثال على بطاقة هوية صحيحة بالاسم الكامل وصورة.",
"id.verification.requirements.title": "متطلبات التحقق من الصورة",
"id.verification.requirements.description": "In order to complete Photo Verification, you will need the following:",
@@ -284,18 +289,13 @@
"id.verification.photo.tips.list.well.lit": "أنّ الإضاءة جيّدة على وجهك.",
"id.verification.photo.tips.list.inside.frame": "أنّ وجهك داخل إطار الصورة بالكامل.",
"id.verification.portrait.photo.title.camera": "التقط صورة لنفسك",
"id.verification.portrait.photo.title.upload": "ارفع صورتك",
"id.verification.portrait.photo.preview.alt": "معاينة صورة وجه المستخدم.",
"id.verification.portrait.photo.instructions.camera": "عندما يكون وجهك في موضعه، استخدم زر التقاط صورة أدناه لالتقاط الصورة.",
"id.verification.portrait.photo.instructions.upload": "Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. Supported formats: ",
"id.verification.camera.help.sight.question": "ماذا لو لم أتمكن من رؤية صورة الكاميرا ؟ أو إذا لم أتمكن من رؤية صورتي لتحديد أي جانب مرئي؟",
"id.verification.camera.help.sight.answer.portrait": "قد تتمكن من إكمال إجراء التقاط الصور من دون مساعدة، ولكن قد يتطلب الأمر بضع محاولات ضبط وضع الكاميرا بشكل صحيح. يختلف وضع الكاميرا المثالي باختلاف الكمبيوتر، ولكن بشكل عام، يكون أفضل موضع للتصوير في الرأس هو 12 إلى 18 بوصة (30-45 سم) تقريبًا من الكاميرا، مع وضع الرأس في المنتصف بالنسبة إلى شاشة الكمبيوتر. إذا تم رفض الصور التي ترسلها، فحاول تحريك اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة.",
"id.verification.camera.help.sight.answer.id": "قد تتمكن من إكمال إجراء التقاط الصور من دون مساعدة، ولكن قد يتطلب الأمر بضع محاولات لضبط وضع الكاميرا بشكل صحيح. يختلف الوضع الأمثل للكاميرا باختلاف جهاز الكمبيوتر، ولكن بشكل عام، يكون أفضل وضع لصورة بطاقة تعريف من 8 إلى 12 بوصة (من 20 إلى 30 سم) عن الكاميرا، مع وضع بطاقة الهوية في الوسط بالنسبة للكاميرا. إذا تم رفض الصور التي ترسلها، فحاول تحريك اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة. إن السبب الأكثر شيوعاً للرفض هو عدم القدرة على قراءة النص الموجود على بطاقة الهوية.",
"id.verification.camera.help.difficulty.question.portrait": "ماذا لو واجهت صعوبة في تثبيت رأسي في الموضع المناسب للكاميرا؟",
"id.verification.camera.help.difficulty.question.id": "ماذا لو واجهت صعوبة في تثبيت بطاقة هويتي في الموضع المناسب للكاميرا؟",
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact {siteName} support for additional suggestions.",
"id.verification.camera.help.upload.question": "What if I want to upload a photo instead?",
"id.verification.camera.help.upload.answer": "On the next page you will have the option to switch to upload mode. By selecting that option, you will be able to upload a photo instead.",
"id.verification.id.photo.unclear.question": "Is your ID card image not clear or too blurry?",
"id.verification.id.tips.title": "Helpful Identification Card Tips",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo, such as a drivers license or passport. Please have your ID ready.",
@@ -309,9 +309,9 @@
"id.verification.id.photo.instructions.upload.error.invalidFileType": "The file you have selected is not a supported image type. Please choose from the following formats: ",
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.name.check.title": "Double-Check Your Name",
"id.verification.name.check.instructions": "Does the name below match the name on your government-issued ID? If not, update the name below to match your goverment-issued ID.",
"id.verification.name.check.mismatch.information": "If the name below does not match your government-issued ID, your identity verification will be denied.",
"id.verification.name.error": "Please enter your name as it appears on your government-issued ID.",
"id.verification.name.check.instructions": "Does the name below match the name on your photo ID? If not, update the name below to match your photo ID.",
"id.verification.name.check.mismatch.information": "If the name below does not match your photo ID, your identity verification will be denied.",
"id.verification.name.error": "Please enter your name as it appears on your photo ID.",
"id.verification.account.name.warning.prefix": "يُرجى الملاحظة:",
"id.verification.account.name.settings": "إعدادات الحساب",
"id.verification.name.label": "Name",
@@ -331,7 +331,7 @@
"id.verification.submission.alert.error.unsupported": "One or more of the files you have uploaded is in an unsupported format. Please choose from the following: ",
"id.verification.review.error": "{siteName} Support Page",
"id.verification.submitted.title": "جارِ التحقق من الهوية",
"id.verification.submitted.text": "لقد تلقينا معلوماتك وجاري الآن العمل على التحقق من هويتك. ستصلك رسالة على لوحة المعلومات عند اكتمال عملية التحقق (عادةً خلال 5 أيام). في غضون ذلك، لا يزال بإمكانك الوصول إلى كل محتوى المساق المتوفر.",
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will be notified 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": "العودة إلى لوحة المعلومات",
"id.verification.return.course": "العودة للمساق",
"id.verification.return.generic": "Return",
@@ -341,10 +341,6 @@
"id.verification.photo.camera.help.text": "If you are having trouble uploading a photo above, you may want to use your camera instead. To use your camera, click the button below.",
"id.verification.upload.help.button": "Switch to Upload Mode",
"id.verification.camera.help.button": "Switch to Camera Mode",
"id.verification.choose.mode.title": "Photo Requirements Options",
"id.verification.choose.mode.hep.text": "To complete verification, please select one of the following options to submit photos. You will be able to switch between these options throughout the process if needed.",
"id.verification.choose.mode.radio.upload": "Upload photos from my device",
"id.verification.choose.mode.radio.camera": "Take pictures using my camera",
"id.verification.request.camera.access.instructions": "لالتقاط صورة باستخدام كاميرا الويب، قد تتلقى طلب المتصفح للوصول إلى الكاميرا. {clickAllow}",
"id.verification.requirements.account.managed.alert": "Your account settings are managed by {managerTitle}. If the name on your photo ID does not match the name on your account, please contact your {profileDataManager} administrator or {support} for help before completing the Photo Verification process.",
"id.verification.requirements.card.device.text": "أنت بحاجة إلى جهاز مزود بكاميرا. إذا تلقيت طلب المتصفح للوصول إلى الكاميرا، فيرجى التأكد من النقر فوق {السماح}.",

View File

@@ -20,16 +20,22 @@
"account.settings.field.full.name": "Nombre completo",
"account.settings.field.full.name.empty": "Añade nombre",
"account.settings.field.full.name.help.text": "El nombre que es usado para la verificación de identidad y aparece en sus certificados.",
"account.settings.field.full.name.help.text.non.certificate": "El nombre que aparece en tu perfil público.",
"account.settings.field.full.name.help.text.certificate": "Este nombre está seleccionado para aparecer en tus certificados y registros públicos.",
"account.settings.field.full.name.help.text.default": "El nombre que aparece en tu perfil público.",
"account.settings.field.full.name.help.text.default.certificate": "Este nombre está seleccionado para aparecer en tus certificados y registros públicos.",
"account.settings.field.name.verified": "Nombre verificado",
"account.settings.field.name.verified.help.text.verified": "Este nombre ha sido verificado por un ID gubernamental.",
"account.settings.field.name.verified.help.text.certificate": "Este nombre ha sido verificado por un ID gubernamental y seleccionado para aparecer en tus certificados y registros públicos.",
"account.settings.field.name.verified.help.text.verified": "Este nombre ha sido verificado por una identificación con foto.",
"account.settings.field.name.verified.help.text.verified.proctored": "Este nombre ha sido verificado por supervisión.",
"account.settings.field.name.verified.help.text.verified.certificate": "Este nombre ha sido verificado por una identificación con foto y está seleccionado para aparecer en sus certificados y registros públicos.",
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "Este nombre se ha verificado mediante supervisión y se ha seleccionado para que aparezca en sus certificados y registros públicos.",
"account.settings.field.name.verified.help.text.submitted": "La verificación ha sido enviada. Este proceso usualmente toma 48 horas o menos. El nombre verificado no puede ser modificado en este momento. ",
"account.settings.field.name.verified.help.text.submitted.proctored": "Su examen supervisado ha sido enviado. El nombre verificado no se puede cambiar en este momento. Vuelva a consultar en 2-5 días.",
"account.settings.field.name.verified.help.text.submitted.certificate": "Cuando el proceso de verificación de identidad sea exitoso, este nombre aparecerá en tus certificados y registros públicos. El nombre verificado no puede ser cambiado en este momento. ",
"account.settings.field.name.verified.verification.help": "Ingresa tu nombre tal como aparece en tu ID. ",
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "Una vez que su examen supervisado pase la revisión, este nombre aparecerá en su certificado y en los registros públicos. El nombre verificado no se puede cambiar en este momento.",
"account.settings.field.name.verified.verification.help": "Ingrese su nombre tal como aparece en su tarjeta de identificación vigente de estudiante, trabajo o emitida por el gobierno.",
"account.settings.field.full.name.help.text.submitted": "La verificación ha sido enviada. Este proceso usualmente toma 48 horas o menos. El nombre verificado no puede ser modificado en este momento. ",
"account.settings.field.full.name.help.text.submitted.proctored": "Su examen supervisado ha sido enviado. El nombre completo no se puede cambiar en este momento. Vuelva a consultar en 2-5 días.",
"account.settings.field.full.name.help.text.submitted.certificate": "En cuanto el proceso de verificación de identidad sea exitoso, este nombre aparecerá en tus certificados y registros públicos. El nombre completo no puede ser cambiado en este momento. ",
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "Una vez que su examen supervisado pase la revisión, este nombre aparecerá en sus certificados y registros públicos. El nombre completo no se puede cambiar en este momento.",
"account.settings.field.name.verified.success.message": "Tu solicitud de verificación de identidad se ha completado exitosamente. Ahora puedes seleccionar el nombre prefieres que aparezca en tus certificados y registros públicos.",
"account.settings.field.name.verified.success.message.header": "¡Tu solicitud de cambio de nombre está completada!",
"account.settings.field.name.verified.failure.message": "Tu más reciente intento de verificación de ID no fue aprobado. Las configuraciones relacionadas con el proceso se han restablecido.",
@@ -186,8 +192,8 @@
"account.settings.name.change.title.begin": "Antes de empezar",
"account.settings.name.change.warning.one": "Atención: esta acción actualizará el nombre que aparece en todos los certificados obtenidos a través de esta cuenta en el pasado y en aquellos en que actualmente estás obteniendo o que adquirirás en el futuro.",
"account.settings.name.change.warning.two": "Esta acción no podrá revertirse sin antes verificar tu identidad.",
"account.settings.name.change.id.name.label": "Ingresa tu nombre tal como aparece en tu documento de identificación. ",
"account.settings.name.change.id.name.placeholder": "Ingresa el nombre en tu ID ",
"account.settings.name.change.id.name.label": "Ingrese su nombre tal como aparece en su tarjeta de identificación vigente de estudiante, trabajo o emitida por el gobierno.",
"account.settings.name.change.id.name.placeholder": "Enter the name on your photo ID",
"account.settings.name.change.error.valid.name": "Por favor ingresa un nombre valido.",
"account.settings.name.change.error.general": "Ha ocurrido un error técnico. Por favor intentalo de nuevo.",
"account.settings.name.change.continue": "Continuar",
@@ -206,7 +212,6 @@
"id.verification.access.blocked.denied": "No podemos verificar tu identidad en este momento. Si aún no has activado tu cuenta, comprueba en tu carpeta de correo no deseado el correo de activación de {email}.",
"id.verification.next": "Siguiente",
"id.verification.support": "soporte",
"id.verification.continue.upload": "Continúa con la carga de archivos",
"id.verification.example.card.alt": "Ejemplo de un documento de identidad válido con foto y nombre completo.",
"id.verification.requirements.title": "Requerimientos de verificación por foto",
"id.verification.requirements.description": "Para completa la verificación de foto, necesitarás lo siguiente:",
@@ -284,18 +289,13 @@
"id.verification.photo.tips.list.well.lit": "El rostro esté bien iluminado",
"id.verification.photo.tips.list.inside.frame": "Tu cara está completamente dentro del marco de la foto.",
"id.verification.portrait.photo.title.camera": "Toma una foto de ti mismo",
"id.verification.portrait.photo.title.upload": "Sube una foto tuya",
"id.verification.portrait.photo.preview.alt": "Previsualización de la foto con el rostro del usuario",
"id.verification.portrait.photo.instructions.camera": "Cuando tu rostro esté en posición, usa el botón Tomar foto a continuación para tomar tu foto.",
"id.verification.portrait.photo.instructions.upload": "Por favor sube una foto de retrato. Asegurate que toda tu cara encaje dentro del marco y que este bien iluminada. Los formatos admitidos son:",
"id.verification.camera.help.sight.question": "¿Qué pasa si no puedo ver la imagen de la cámara o si no puedo ver mi foto para determinar qué lado es visible?",
"id.verification.camera.help.sight.answer.portrait": "Es posible que puedas completar el procedimiento de captura de imágenes sin ayuda, pero es posible que necesites un par de intentos de envío para que la cámara se coloque correctamente. La posición óptima de la cámara varía con cada computadora, pero generalmente la mejor distancia para una foto de rostro es aproximadamente a 12 a 18 pulgadas (30 a 45 centímetros) de la cámara, con la cabeza centrada en relación con la pantalla de la computadora. Si las fotos que envías son rechazadas, intenta mover la computadora o la orientación de la cámara para cambiar el ángulo de iluminación.",
"id.verification.camera.help.sight.answer.id": "Es posible que puedas completar el procedimiento de captura de imágenes sin ayuda, pero es posible que necesites un par de intentos de envío para que la cámara se coloque correctamente. El posicionamiento óptimo de la cámara varía con cada computadora pero, generalmente, la mejor distancia para una foto de un documento de identificación es a 8 a 12 pulgadas (20 a 30 centímetros) de la cámara, con el documento de identificación centrado en relación con la cámara. Si las fotos que envías son rechazadas, intenta mover la computadora o la orientación de la cámara para cambiar el ángulo de iluminación. La razón más común de rechazo es la imposibilidad de leer el texto del documento de identidad.",
"id.verification.camera.help.difficulty.question.portrait": "¿Qué sucede si tengo dificultades para mantener la cabeza en posición con respecto a la cámara?",
"id.verification.camera.help.difficulty.question.id": "¿Qué sucede si tengo dificultades para mantener mi identificación en posición con respecto a la cámara?",
"id.verification.camera.help.difficulty.answer": "Si requieres de asistencia para cargar una foto, contacta el equipo de soporte de {siteName} para sugerencias adicionales.",
"id.verification.camera.help.upload.question": "¿Qué pasa si en vez quiero subir una foto?",
"id.verification.camera.help.upload.answer": "En la siguiente página tendrás la opción de cambiar a modo carga. Al seleccionar esa opción, podrás subir una foto en vez.",
"id.verification.id.photo.unclear.question": "¿No es clara la foto de tu tarjeta de ID ó es muy borrosa?",
"id.verification.id.tips.title": "Tips de ayuda para tarjetas de identificación",
"id.verification.id.tips.description": "Después, necesitaremos que tomes una foto de una tarjeta de ID válida que tengas en la que incluya tu nombre completo y foto, como una licencia de conducción o un pasaporte. Por favor ten tú ID lista.",
@@ -309,9 +309,9 @@
"id.verification.id.photo.instructions.upload.error.invalidFileType": "El archivo que has seleccionado no cumple con el tipo de imágenes soportadas. Por favor escoge entre los siguientes formatos:",
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "El archivo que has seleccionado es demasiado grande. Vuelve a intentarlo con un archivo de menos de 10 MB.",
"id.verification.name.check.title": "Revisa nuevamente tu nombre",
"id.verification.name.check.instructions": "¿El nombre a continuación coincide con el nombre de tú ID? De no ser así, actualiza el nombre que se muestra a continuación para que coincida con tú ID.",
"id.verification.name.check.mismatch.information": "En caso de que el nombre a continuación no coincida con el que aparece en tú ID, tu verificación de identidad será negada.",
"id.verification.name.error": "Introduce tu nombre tal y como aparece en tu ID.",
"id.verification.name.check.instructions": "Does the name below match the name on your photo ID? If not, update the name below to match your photo ID.",
"id.verification.name.check.mismatch.information": "Si el nombre a continuación no coincide con su identificación con foto, se denegará su verificación de identidad.",
"id.verification.name.error": "Ingrese su nombre tal como aparece en su identificación con foto.",
"id.verification.account.name.warning.prefix": "Ten en cuenta:",
"id.verification.account.name.settings": "Configuración de cuenta",
"id.verification.name.label": "Nombre",
@@ -331,7 +331,7 @@
"id.verification.submission.alert.error.unsupported": "Uno o más de los siguientes archivos que has subido se encuentran en un formato no soportado. Por favor escoge de los siguientes formatos:",
"id.verification.review.error": "Página de soporte de {siteName}",
"id.verification.submitted.title": "Verificación de identidad en progreso.",
"id.verification.submitted.text": "Hemos recibido tu información y estamos verificando tu identidad. Verás un mensaje en tu tablero cuando se complete el proceso de verificación (generalmente en un periodo de 5 días). Mientras tanto, aún puedes acceder a todo el contenido del curso disponible.",
"id.verification.submitted.text": "Hemos recibido su información y estamos verificando su identidad. Se le notificará cuando se complete el proceso de verificación (generalmente dentro de los 5 días). Mientras tanto, aún puede acceder a todo el contenido del curso disponible.",
"id.verification.return.dashboard": "Volver al panel principal",
"id.verification.return.course": "Regresar al curso",
"id.verification.return.generic": "Regresar",
@@ -341,10 +341,6 @@
"id.verification.photo.camera.help.text": "Si estás presentando problemas subiendo una foto, es posible que prefieras usar la cámara en vez. Para usar una cámara, has clic en el botón a continuación.",
"id.verification.upload.help.button": "Cambia a modo de carga",
"id.verification.camera.help.button": "Cambia a modo de cámara",
"id.verification.choose.mode.title": "Opciones de requerimientos de foto",
"id.verification.choose.mode.hep.text": "Para completar la verificación, por favor selecciona una de las siguientes opciones para cargar fotos. Podrás cambiar entre estas opciones durante el proceso en caso de requerirlo.",
"id.verification.choose.mode.radio.upload": "Subir fotos de mi dispositivo",
"id.verification.choose.mode.radio.camera": "Toma fotos usando mi cámara",
"id.verification.request.camera.access.instructions": "Para tomar una foto con tu cámara web, es posible que recibas un aviso del navegador para acceder a tu cámara. {clickAllow}",
"id.verification.requirements.account.managed.alert": "La configuración de tu perfil es administrada por {managerTitle}. Por lo tanto si el nombre en tu ID de foto coincide con tu nombre de cuenta por favor ponte en contacto con tu administrador de {profileDataManager} o con {soporte} para solicitar ayuda antes de completar el proceso de verificación de foto.",
"id.verification.requirements.card.device.text": "Necesitas un dispositivo que tenga una cámara. Si has recibido un aviso del navegador para habilitar acceso a tu cámara, por favor asegúrate de seleccionar [allow].",

View File

@@ -20,16 +20,22 @@
"account.settings.field.full.name": "Nom complet",
"account.settings.field.full.name.empty": "Ajouter un nom",
"account.settings.field.full.name.help.text": "Le nom qui apparaîtra sur vos certificats et dans le cadre de toute vérification d'identité.",
"account.settings.field.full.name.help.text.non.certificate": "Le nom qui apparaît sur votre profile public.",
"account.settings.field.full.name.help.text.certificate": "Ce nom va apparaître sur vos attestations et dossiers publics.",
"account.settings.field.full.name.help.text.default": "Le nom qui apparaît sur votre profile public.",
"account.settings.field.full.name.help.text.default.certificate": "Ce nom va apparaître sur vos attestations et dossiers publics.",
"account.settings.field.name.verified": "Nom vérifié",
"account.settings.field.name.verified.help.text.verified": "Ce nom a été vérifié par une pièce d'identité gouvernementale.",
"account.settings.field.name.verified.help.text.certificate": "This name has been verified by government ID and selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.verified": "Ce nom a été vérifié par une pièce d&#39;identité avec photo.",
"account.settings.field.name.verified.help.text.verified.proctored": "Ce nom a été vérifié par la surveillance.",
"account.settings.field.name.verified.help.text.verified.certificate": "Ce nom a été vérifié par une pièce d&#39;identité avec photo et est sélectionné pour apparaître sur vos certificats et vos dossiers publics.",
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "Ce nom a été vérifié par la surveillance et est sélectionné pour apparaître sur vos certificats et vos enregistrements publics.",
"account.settings.field.name.verified.help.text.submitted": "La vérification a été soumise. Cela prend généralement 48 heures ou moins. Le nom vérifié ne peut pas être modifié pour le moment.",
"account.settings.field.name.verified.help.text.submitted.proctored": "Votre examen surveillé a été soumis. Le nom vérifié ne peut pas être modifié pour le moment. Veuillez réessayer dans 2 à 5 jours.",
"account.settings.field.name.verified.help.text.submitted.certificate": "Une fois la vérification d'identité réussie, ce nom apparaîtra sur vos attestations et vos dossiers publics. Le nom complet ne peut pas être modifié pour le moment.",
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your government-issued ID.",
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "Une fois que votre examen surveillé a réussi l&#39;examen, ce nom apparaîtra sur votre certificat et vos dossiers publics. Le nom vérifié ne peut pas être modifié pour le moment.",
"account.settings.field.name.verified.verification.help": "Entrez votre nom tel qu&#39;il apparaît sur votre carte d&#39;étudiant, de travail ou d&#39;identité émise par le gouvernement.",
"account.settings.field.full.name.help.text.submitted": "La vérification a été soumise. Cela prend généralement 48 heures ou moins. Le nom vérifié ne peut pas être modifié pour le moment.",
"account.settings.field.full.name.help.text.submitted.proctored": "Votre examen surveillé a été soumis. Le nom complet ne peut pas être modifié pour le moment. Veuillez réessayer dans 2 à 5 jours.",
"account.settings.field.full.name.help.text.submitted.certificate": "Une fois la vérification d'identité réussie, ce nom apparaîtra sur vos attestations et vos dossiers publics. Le nom complet ne peut pas être modifié pour le moment.",
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "Une fois que votre examen surveillé a réussi l&#39;examen, ce nom apparaîtra sur vos certificats et vos dossiers publics. Le nom complet ne peut pas être modifié pour le moment.",
"account.settings.field.name.verified.success.message": "Votre demande de vérification d'identité a été complétée avec succès. Vous avez maintenant la possibilité de sélectionner le nom que vous préférez voir apparaître sur vos attestations et documents publics.",
"account.settings.field.name.verified.success.message.header": "Votre demande de changement de nom est terminée!",
"account.settings.field.name.verified.failure.message": "Votre dernière tentative de vérification d'identité n'a pas abouti. Les paramètres du compte correspondant ont été restaurés.",
@@ -133,7 +139,7 @@
"account.settings.delete.account.text.change.instead": "Vous souhaitez modifier votre adresse électronique, votre nom ou votre mot de passe?",
"account.settings.delete.account.button": "Supprimer mon compte",
"account.settings.delete.account.please.activate": "activez votre compte",
"account.settings.delete.account.please.confirm": "confirm your account",
"account.settings.delete.account.please.confirm": "confirmer votre compte",
"account.settings.delete.account.please.unlink": "dissocier tous les comptes de médias sociaux",
"account.settings.delete.account.modal.header": "Êtes-vous certain ?",
"account.settings.delete.account.modal.text.1": "Vous avez sélectionné \"Supprimer mon compte\". La suppression de votre compte et de vos données personnelles est permanente et ne peut être annulée. {siteName} ne pourra ni récupérer votre compte ni les données supprimées.",
@@ -186,8 +192,8 @@
"account.settings.name.change.title.begin": "Avant que l'on commençons",
"account.settings.name.change.warning.one": "Warning: This action updates the name that appears on all certificates that have been earned on this account in the past and any certificates you are currently earning or will earn in the future.",
"account.settings.name.change.warning.two": "Cette action ne peut pas être renversée sans vérification d'identité.",
"account.settings.name.change.id.name.label": "Enter your name as it appears on your government-issued ID.",
"account.settings.name.change.id.name.placeholder": "Enter the name on your government ID",
"account.settings.name.change.id.name.label": "Entrez votre nom tel qu&#39;il apparaît sur votre carte d&#39;étudiant, de travail ou d&#39;identité émise par le gouvernement.",
"account.settings.name.change.id.name.placeholder": "Entrez le nom sur votre pièce d&#39;identité avec photo",
"account.settings.name.change.error.valid.name": "Entrez un nom valide",
"account.settings.name.change.error.general": "Une erreur est survenue. Veuillez réessayer.",
"account.settings.name.change.continue": "Continuer",
@@ -206,7 +212,6 @@
"id.verification.access.blocked.denied": "Nous ne pouvez pas vérifier votre identité pour le moment. Si vous n'avez pas encore activé votre compte, veuillez vérifier votre dossier de pourriels pour le courriel d'activation de {email}.",
"id.verification.next": "Suivant",
"id.verification.support": "support",
"id.verification.continue.upload": "Continuer avec le téléchargement",
"id.verification.example.card.alt": "Exemple de carte d'identité valide avec un nom complet et une photo.",
"id.verification.requirements.title": "Exigences de vérification des photos",
"id.verification.requirements.description": "Afin de procéder à la vérification des photos, vous aurez besoin des éléments suivants :",
@@ -284,18 +289,13 @@
"id.verification.photo.tips.list.well.lit": "Votre visage est bien éclairé",
"id.verification.photo.tips.list.inside.frame": " Votre visage est entièrement dans le cadre.",
"id.verification.portrait.photo.title.camera": "Prenez une photo de vous",
"id.verification.portrait.photo.title.upload": "Téléchargez une photo de vous",
"id.verification.portrait.photo.preview.alt": "Aperçu de la photo du visage de l'utilisateur.",
"id.verification.portrait.photo.instructions.camera": "Lorsque votre visage est en position, utilisez le bouton Prendre une photo ci-dessous pour prendre votre photo.",
"id.verification.portrait.photo.instructions.upload": "Veuillez télécharger une photo de portrait. Assurez-vous que tout votre visage s'insère dans le cadre et est bien éclairé. Formats pris en charge :",
"id.verification.camera.help.sight.question": "Que faire si je ne peux pas voir l'image de la caméra ou si je ne peux pas voir ma photo pour déterminer quel côté est visible?",
"id.verification.camera.help.sight.answer.portrait": "Vous pourrez peut-être terminer la procédure de capture d'image sans aide, mais cela peut prendre quelques tentatives de soumission pour obtenir le bon positionnement de la caméra. Le positionnement optimal de la caméra varie avec chaque ordinateur, mais généralement la meilleure position pour une prise de vue de la tête est d'environ 12-18 pouces (30-45 centimètres) de la caméra, la tête étant centrée par rapport à l'écran de l'ordinateur. Si les photos que vous soumettez sont rejetées, essayez de déplacer lorientation de lordinateur ou de lappareil photo pour modifier langle déclairage.",
"id.verification.camera.help.sight.answer.id": "Vous pourrez peut-être terminer la procédure de capture d'image sans aide, mais cela peut prendre quelques tentatives de soumission pour obtenir le bon positionnement de la caméra. Le positionnement optimal de la caméra varie avec chaque ordinateur, mais généralement, la meilleure position pour une photo d'une carte d'identité est de 8-12 pouces (20-30 centimètres) de la caméra, la carte d'identité étant centrée par rapport à la caméra. Si les photos que vous soumettez sont rejetées, essayez de déplacer lorientation de lordinateur ou de lappareil photo pour modifier langle déclairage. La raison la plus courante de rejet est l'incapacité de lire le texte sur la carte d'identité.",
"id.verification.camera.help.difficulty.question.portrait": "Que faire si j'ai des difficultés à maintenir ma tête en position par rapport à la caméra ?",
"id.verification.camera.help.difficulty.question.id": "Que faire si j'ai des difficultés à tenir ma carte d'identité en position par rapport à la caméra ?",
"id.verification.camera.help.difficulty.answer": "Si vous avez besoin d'aide pour prendre une photo à soumettre, contactez le support {siteName} pour des suggestions supplémentaires.",
"id.verification.camera.help.upload.question": "Et si je souhaite télécharger une photo à la place ?",
"id.verification.camera.help.upload.answer": "Sur la page suivante, vous aurez la possibilité de passer en mode téléchargement. En sélectionnant cette option, vous pourrez télécharger une photo à la place.",
"id.verification.id.photo.unclear.question": "L'image de votre carte d'identité n'est pas claire ou trop floue ?",
"id.verification.id.tips.title": "Conseils utiles pour les cartes d'identité",
"id.verification.id.tips.description": "Ensuite, nous vous demanderons de prendre une photo d'une carte d'identité valide comportant votre nom complet et votre photo, comme un permis de conduire ou un passeport. Veuillez préparer votre carte d'identité.",
@@ -309,9 +309,9 @@
"id.verification.id.photo.instructions.upload.error.invalidFileType": "Le fichier que vous avez sélectionné n'est pas un type d'image pris en charge. Veuillez choisir parmi les formats suivants :",
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "Le fichier que vous avez sélectionné est trop volumineux. Veuillez réessayer avec un fichier de moins de 10 Mo.",
"id.verification.name.check.title": "Vérifiez votre nom",
"id.verification.name.check.instructions": "Le nom ci-dessous correspond-il au nom figurant sur votre pièce d'identité officielle ? Si ce n'est pas le cas, mettez à jour le nom ci-dessous afin qu'il corresponde à votre pièce d'identité.",
"id.verification.name.check.mismatch.information": "Si le nom ci-dessous ne correspond pas à votre pièce d'identité, la vérification de votre identité sera refusée.",
"id.verification.name.error": "Veuillez entrer votre nom tel qu'il apparaît sur votre pièce d'identité. ",
"id.verification.name.check.instructions": "Le nom ci-dessous correspond-il au nom sur votre pièce d&#39;identité avec photo ? Si ce n&#39;est pas le cas, mettez à jour le nom ci-dessous pour qu&#39;il corresponde à votre pièce d&#39;identité avec photo.",
"id.verification.name.check.mismatch.information": "Si le nom ci-dessous ne correspond pas à votre pièce d&#39;identité avec photo, votre vérification d&#39;identité sera refusée.",
"id.verification.name.error": "Veuillez entrer votre nom tel qu&#39;il apparaît sur votre pièce d&#39;identité avec photo.",
"id.verification.account.name.warning.prefix": "Veuillez noter:",
"id.verification.account.name.settings": "Paramètres du compte",
"id.verification.name.label": "Nom",
@@ -331,7 +331,7 @@
"id.verification.submission.alert.error.unsupported": "Un ou plusieurs des fichiers que vous avez téléchargés sont dans un format non pris en charge. Veuillez choisir parmi les formats suivants :",
"id.verification.review.error": "Page de Support {siteName}",
"id.verification.submitted.title": "Vérification d'identité en cours",
"id.verification.submitted.text": "Nous avons reçu vos informations et vérifions votre identité. Vous verrez un message sur votre tableau de bord lorsque le processus de vérification sera terminé (généralement dans les 5 jours). En attendant, vous pouvez toujours accéder à tous les contenus de cours disponibles.",
"id.verification.submitted.text": "Nous avons reçu vos informations et vérifions votre identité. Vous serez averti lorsque le processus de vérification sera terminé (généralement dans les 5 jours). En attendant, vous pouvez toujours accéder à tous les contenus de cours disponibles.",
"id.verification.return.dashboard": "Retour au Tableau de bord",
"id.verification.return.course": "Revenir au cours",
"id.verification.return.generic": "Retour",
@@ -341,10 +341,6 @@
"id.verification.photo.camera.help.text": "Si vous rencontrez des difficultés pour télécharger une photo ci-dessus, vous pouvez utiliser votre appareil photo à la place. Pour utiliser votre appareil photo, cliquez sur le bouton ci-dessous.",
"id.verification.upload.help.button": "Passer en mode de téléchargement",
"id.verification.camera.help.button": "Passer en mode caméra",
"id.verification.choose.mode.title": "Options d'exigences relatives aux photos",
"id.verification.choose.mode.hep.text": "Pour terminer la vérification, veuillez sélectionner l'une des options suivantes pour soumettre des photos. Vous pourrez basculer entre ces options tout au long du processus si nécessaire.",
"id.verification.choose.mode.radio.upload": "Télécharger des photos depuis mon appareil",
"id.verification.choose.mode.radio.camera": "Prendre des photos avec mon appareil photo",
"id.verification.request.camera.access.instructions": "Afin de prendre une photo à l'aide de votre webcam, vous pouvez recevoir une invite du navigateur pour accéder à votre caméra. {clickAllow}",
"id.verification.requirements.account.managed.alert": "Les paramètres de votre compte sont gérés par {managerTitle}. Si le nom sur votre pièce d'identité avec photo ne correspond pas au nom de votre compte, veuillez contacter votre administrateur {profileDataManager} ou {support} pour obtenir de l'aide avant de terminer le processus de vérification des photos.",
"id.verification.requirements.card.device.text": "Vous avez besoin d'un appareil équipé d'une caméra. Si vous recevez une invite du navigateur pour accéder à votre caméra, assurez-vous de cliquer sur {allow}.",

View File

@@ -20,16 +20,22 @@
"account.settings.field.full.name": "Full name",
"account.settings.field.full.name.empty": "Add name",
"account.settings.field.full.name.help.text": "The name that is used for ID verification and that appears on your certificates.",
"account.settings.field.full.name.help.text.non.certificate": "The name that appears on your public profile.",
"account.settings.field.full.name.help.text.certificate": "This name is selected to appear on your certificates and public-facing records.",
"account.settings.field.full.name.help.text.default": "The name that appears on your public profile.",
"account.settings.field.full.name.help.text.default.certificate": "This name is selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified": "Verified name",
"account.settings.field.name.verified.help.text.verified": "This name has been verified by government ID.",
"account.settings.field.name.verified.help.text.certificate": "This name has been verified by government ID and selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.verified": "This name has been verified by photo ID.",
"account.settings.field.name.verified.help.text.verified.proctored": "This name has been verified by proctoring.",
"account.settings.field.name.verified.help.text.verified.certificate": "This name has been verified by photo ID, and is selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "This name has been verified by proctoring, and is selected to appear on your certificates and public-facing records.",
"account.settings.field.name.verified.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Verified name cannot be changed at this time.",
"account.settings.field.name.verified.help.text.submitted.proctored": "Your proctored exam has been submitted. Verified name cannot be changed at this time. Please check back in 2-5 days.",
"account.settings.field.name.verified.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Verified name cannot be changed at this time.",
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your government-issued ID.",
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.",
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your unexpired student, work, or government-issued identification card.",
"account.settings.field.full.name.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.",
"account.settings.field.full.name.help.text.submitted.proctored": "Your proctored exam has been submitted. Full name cannot be changed at this time. Please check back in 2-5 days.",
"account.settings.field.full.name.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "Once your proctored exam passes review, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
"account.settings.field.name.verified.success.message": "Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.",
"account.settings.field.name.verified.success.message.header": "Your name change request is complete!",
"account.settings.field.name.verified.failure.message": "Your most recent identity verification attempt did not pass. Related account settings have been restored.",
@@ -186,8 +192,8 @@
"account.settings.name.change.title.begin": "Before we begin",
"account.settings.name.change.warning.one": "Warning: This action updates the name that appears on all certificates that have been earned on this account in the past and any certificates you are currently earning or will earn in the future.",
"account.settings.name.change.warning.two": "This action cannot be undone without verifying your identity.",
"account.settings.name.change.id.name.label": "Enter your name as it appears on your government-issued ID.",
"account.settings.name.change.id.name.placeholder": "Enter the name on your government ID",
"account.settings.name.change.id.name.label": "Enter your name as it appears on your unexpired student, work, or government-issued identification card.",
"account.settings.name.change.id.name.placeholder": "Enter the name on your photo ID",
"account.settings.name.change.error.valid.name": "Please enter a valid name.",
"account.settings.name.change.error.general": "A technical error occurred. Please try again.",
"account.settings.name.change.continue": "Continue",
@@ -206,7 +212,6 @@
"id.verification.access.blocked.denied": "We 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": "Next",
"id.verification.support": "support",
"id.verification.continue.upload": "Continue with Upload",
"id.verification.example.card.alt": "Example of a valid identification card with a full name and photo.",
"id.verification.requirements.title": "Photo Verification Requirements",
"id.verification.requirements.description": "In order to complete Photo Verification, you will need the following:",
@@ -284,18 +289,13 @@
"id.verification.photo.tips.list.well.lit": "Your face is well-lit.",
"id.verification.photo.tips.list.inside.frame": "Your entire face fits inside the frame.",
"id.verification.portrait.photo.title.camera": "Take a Photo of Yourself",
"id.verification.portrait.photo.title.upload": "Upload a Photo of Yourself",
"id.verification.portrait.photo.preview.alt": "Preview of photo of user's face.",
"id.verification.portrait.photo.instructions.camera": "When your face is in position, use the Take Photo button below to take your photo.",
"id.verification.portrait.photo.instructions.upload": "Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. Supported formats: ",
"id.verification.camera.help.sight.question": "What if I can't see the camera image or if I can't see my photo to determine which side is visible?",
"id.verification.camera.help.sight.answer.portrait": "You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts to get the camera positioning right. Optimal camera positioning varies with each computer, but generally the best position for a headshot is approximately 12-18 inches (30-45 centimeters) from the camera, with your head centered relative to the computer screen. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle.",
"id.verification.camera.help.sight.answer.id": "You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts to get the camera positioning right. Optimal camera positioning varies with each computer, but generally, the best position for a photo of an ID card is 8-12 inches (20-30 centimeters) from the camera, with the ID card centered relative to the camera. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle. The most common reason for rejection is inability to read the text on the ID card.",
"id.verification.camera.help.difficulty.question.portrait": "What if I have difficulty holding my head in position relative to the camera?",
"id.verification.camera.help.difficulty.question.id": "What if I have difficulty holding my ID in position relative to the camera?",
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact {siteName} support for additional suggestions.",
"id.verification.camera.help.upload.question": "What if I want to upload a photo instead?",
"id.verification.camera.help.upload.answer": "On the next page you will have the option to switch to upload mode. By selecting that option, you will be able to upload a photo instead.",
"id.verification.id.photo.unclear.question": "Is your ID card image not clear or too blurry?",
"id.verification.id.tips.title": "Helpful Identification Card Tips",
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo, such as a drivers license or passport. Please have your ID ready.",
@@ -309,9 +309,9 @@
"id.verification.id.photo.instructions.upload.error.invalidFileType": "The file you have selected is not a supported image type. Please choose from the following formats: ",
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "The file you have selected is too large. Please try again with a file less than 10MB.",
"id.verification.name.check.title": "Double-Check Your Name",
"id.verification.name.check.instructions": "Does the name below match the name on your government-issued ID? If not, update the name below to match your goverment-issued ID.",
"id.verification.name.check.mismatch.information": "If the name below does not match your government-issued ID, your identity verification will be denied.",
"id.verification.name.error": "Please enter your name as it appears on your government-issued ID.",
"id.verification.name.check.instructions": "Does the name below match the name on your photo ID? If not, update the name below to match your photo ID.",
"id.verification.name.check.mismatch.information": "If the name below does not match your photo ID, your identity verification will be denied.",
"id.verification.name.error": "Please enter your name as it appears on your photo ID.",
"id.verification.account.name.warning.prefix": "Please Note:",
"id.verification.account.name.settings": "Account Settings",
"id.verification.name.label": "Name",
@@ -331,7 +331,7 @@
"id.verification.submission.alert.error.unsupported": "One or more of the files you have uploaded is in an unsupported format. Please choose from the following: ",
"id.verification.review.error": "{siteName} 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.submitted.text": "We have received your information and are verifying your identity. You will be notified 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.return.generic": "Return",
@@ -341,10 +341,6 @@
"id.verification.photo.camera.help.text": "If you are having trouble uploading a photo above, you may want to use your camera instead. To use your camera, click the button below.",
"id.verification.upload.help.button": "Switch to Upload Mode",
"id.verification.camera.help.button": "Switch to Camera Mode",
"id.verification.choose.mode.title": "Photo Requirements Options",
"id.verification.choose.mode.hep.text": "To complete verification, please select one of the following options to submit photos. You will be able to switch between these options throughout the process if needed.",
"id.verification.choose.mode.radio.upload": "Upload photos from my device",
"id.verification.choose.mode.radio.camera": "Take pictures using my camera",
"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.account.managed.alert": "Your account settings are managed by {managerTitle}. If the name on your photo ID does not match the name on your account, please contact your {profileDataManager} administrator or {support} for help before completing the Photo Verification process.",
"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}.",

View File

@@ -262,7 +262,6 @@ class Camera extends React.Component {
const dataUri = this.cameraPhoto.getDataUri(config);
this.setState({ dataUri });
this.props.onImageCapture(dataUri);
this.props.setPhotoMode('camera');
}
playShutterClick() {
@@ -360,7 +359,6 @@ class Camera extends React.Component {
Camera.propTypes = {
intl: intlShape.isRequired,
onImageCapture: PropTypes.func.isRequired,
setPhotoMode: PropTypes.func.isRequired,
isPortrait: PropTypes.bool.isRequired,
};

View File

@@ -1,30 +1,14 @@
import React, { useContext } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Collapsible } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import messages from './IdVerification.messages';
import IdVerificationContext from './IdVerificationContext';
function CameraHelp(props) {
const { optimizelyExperimentName } = useContext(IdVerificationContext);
return (
<div>
{ optimizelyExperimentName
&& (
<Collapsible
styling="card"
title={props.intl.formatMessage(messages['id.verification.camera.help.upload.question'])}
className="mb-4 shadow"
defaultOpen={props.isOpen}
>
<p>
{props.intl.formatMessage(messages['id.verification.camera.help.upload.answer'])}
</p>
</Collapsible>
)}
<Collapsible
styling="card"
title={props.intl.formatMessage(messages['id.verification.camera.help.sight.question'])}

View File

@@ -2,72 +2,55 @@ import React, { useContext } from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, Collapsible } from '@edx/paragon';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import IdVerificationContext, { MEDIA_ACCESS } from './IdVerificationContext';
import IdVerificationContext from './IdVerificationContext';
import messages from './IdVerification.messages';
function CollapsibleImageHelp(props) {
const {
userId, shouldUseCamera, setShouldUseCamera, optimizelyExperimentName, mediaAccess,
userId, useCameraForId, setUseCameraForId,
} = useContext(IdVerificationContext);
function handleClick() {
const toggleTo = shouldUseCamera ? 'upload' : 'camera';
const toggleTo = useCameraForId ? 'upload' : 'camera';
const eventName = `edx.id_verification.toggle_to.${toggleTo}`;
sendTrackEvent(eventName, {
category: 'id_verification',
user_id: userId,
});
setShouldUseCamera(!shouldUseCamera);
setUseCameraForId(!useCameraForId);
}
if (optimizelyExperimentName && mediaAccess !== MEDIA_ACCESS.DENIED && mediaAccess !== MEDIA_ACCESS.UNSUPPORTED) {
return (
<Collapsible
styling="card"
title={shouldUseCamera ? props.intl.formatMessage(messages['id.verification.photo.upload.help.title']) : props.intl.formatMessage(messages['id.verification.photo.camera.help.title'])}
className="mb-4 shadow"
defaultOpen
return (
<Collapsible
styling="card"
title={useCameraForId
? props.intl.formatMessage(messages['id.verification.photo.upload.help.title'])
: props.intl.formatMessage(messages['id.verification.photo.camera.help.title'])}
className="mb-4 shadow"
defaultOpen
>
<p data-testid="help-text">
{useCameraForId
? props.intl.formatMessage(messages['id.verification.photo.upload.help.text'])
: props.intl.formatMessage(messages['id.verification.photo.camera.help.text'])}
</p>
<Button
title={useCameraForId ? 'Upload Photo' : 'Take Photo'} // TO-DO: translation
data-testid="toggle-button"
onClick={handleClick}
style={{ marginTop: '0.5rem' }}
>
<p data-testid="help-text">
{shouldUseCamera
? props.intl.formatMessage(messages['id.verification.photo.upload.help.text'])
: props.intl.formatMessage(messages['id.verification.photo.camera.help.text'])}
</p>
{ (mediaAccess === MEDIA_ACCESS.PENDING && !shouldUseCamera)
? (
// if a user has not enabled camera access yet, and they are trying to switch
// to camera mode, direct them to panel that requests camera access
<Link
to={{ pathname: 'request-camera-access', state: { fromPortraitCapture: props.isPortrait, fromIdCapture: !props.isPortrait } }}
className="btn btn-primary"
data-testid="access-link"
>
{props.intl.formatMessage(messages['id.verification.photo.camera.help.button'])}
</Link>
)
: (
<Button
title={shouldUseCamera ? 'Upload Portrait Photo' : 'Take Portrait Photo'}
data-testid="toggle-button"
onClick={handleClick}
style={{ marginTop: '0.5rem' }}
>
{shouldUseCamera ? props.intl.formatMessage(messages['id.verification.photo.upload.help.button']) : props.intl.formatMessage(messages['id.verification.photo.camera.help.button'])}
</Button>
)}
</Collapsible>
);
}
return null;
{useCameraForId
? props.intl.formatMessage(messages['id.verification.photo.upload.help.button'])
: props.intl.formatMessage(messages['id.verification.photo.camera.help.button'])}
</Button>
</Collapsible>
);
}
CollapsibleImageHelp.propTypes = {
intl: intlShape.isRequired,
isPortrait: PropTypes.bool.isRequired,
};
export default injectIntl(CollapsibleImageHelp);

View File

@@ -11,11 +11,6 @@ const messages = defineMessages({
defaultMessage: 'support',
description: 'Website support.',
},
'id.verification.continue.upload': {
id: 'id.verification.continue.upload',
defaultMessage: 'Continue with Upload',
description: 'Button to continue with upload.',
},
'id.verification.example.card.alt': {
id: 'id.verification.example.card.alt',
defaultMessage: 'Example of a valid identification card with a full name and photo.',
@@ -401,26 +396,11 @@ const messages = defineMessages({
defaultMessage: 'Take a Photo of Yourself',
description: 'Title for the Portrait Photo page if camera access is enabled.',
},
'id.verification.portrait.photo.title.upload': {
id: 'id.verification.portrait.photo.title.upload',
defaultMessage: 'Upload a Photo of Yourself',
description: 'Title for the Portrait Photo page if camera access is disabled.',
},
'id.verification.portrait.photo.preview.alt': {
id: 'id.verification.portrait.photo.preview.alt',
defaultMessage: 'Preview of photo of user\'s face.',
description: 'Alt text for the portrait photo preview.',
},
'id.verification.portrait.photo.instructions.camera': {
id: 'id.verification.portrait.photo.instructions.camera',
defaultMessage: 'When your face is in position, use the Take Photo button below to take your photo.',
description: 'Instructions to use the camera to take a portrait photo..',
},
'id.verification.portrait.photo.instructions.upload': {
id: 'id.verification.portrait.photo.instructions.upload',
defaultMessage: 'Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. Supported formats: ',
description: 'Instructions for portrait photo upload.',
},
'id.verification.camera.help.sight.question': {
id: 'id.verification.camera.help.sight.question',
defaultMessage: 'What if I can\'t see the camera image or if I can\'t see my photo to determine which side is visible?',
@@ -451,16 +431,6 @@ const messages = defineMessages({
defaultMessage: 'If you require assistance with taking a photo for submission, contact {siteName} support for additional suggestions.',
description: 'Confirming what to do if the user has difficult holding their head relative to the camera.',
},
'id.verification.camera.help.upload.question': {
id: 'id.verification.camera.help.upload.question',
defaultMessage: 'What if I want to upload a photo instead?',
description: 'Question on what to do if the user would like to upload a photo instead.',
},
'id.verification.camera.help.upload.answer': {
id: 'id.verification.camera.help.upload.answer',
defaultMessage: 'On the next page you will have the option to switch to upload mode. By selecting that option, you will be able to upload a photo instead.',
description: 'Confirming what to do if the user would like to upload a photo.',
},
'id.verification.id.photo.unclear.question': {
id: 'id.verification.id.photo.unclear.question',
defaultMessage: 'Is your ID card image not clear or too blurry?',
@@ -528,17 +498,17 @@ const messages = defineMessages({
},
'id.verification.name.check.instructions': {
id: 'id.verification.name.check.instructions',
defaultMessage: 'Does the name below match the name on your government-issued ID? If not, update the name below to match your goverment-issued ID.',
description: 'Text to instruct the user to check that the name displayed on the page matches what is on their government-issued ID.',
defaultMessage: 'Does the name below match the name on your photo ID? If not, update the name below to match your photo ID.',
description: 'Text to instruct the user to check that the name displayed on the page matches what is on their photo ID.',
},
'id.verification.name.check.mismatch.information': {
id: 'id.verification.name.check.mismatch.information',
defaultMessage: 'If the name below does not match your government-issued ID, your identity verification will be denied.',
description: 'Text to inform the user that if the name displayed on the page does not match what is on their government-issued ID, identity verification will be denied.',
defaultMessage: 'If the name below does not match your photo ID, your identity verification will be denied.',
description: 'Text to inform the user that if the name displayed on the page does not match what is on their photo ID, identity verification will be denied.',
},
'id.verification.name.error': {
id: 'id.verification.name.error',
defaultMessage: 'Please enter your name as it appears on your government-issued ID.',
defaultMessage: 'Please enter your name as it appears on your photo ID.',
description: 'Error that shows when the user needs to update their name to match the name on their ID.',
},
'id.verification.account.name.warning.prefix': {
@@ -638,7 +608,7 @@ const messages = defineMessages({
},
'id.verification.submitted.text': {
id: 'id.verification.submitted.text',
defaultMessage: '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.',
defaultMessage: 'We have received your information and are verifying your identity. You will be notified when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.',
description: 'Text confirming that ID verification request has been received.',
},
'id.verification.return.dashboard': {
@@ -686,26 +656,6 @@ const messages = defineMessages({
defaultMessage: 'Switch to Camera Mode',
description: 'Button used to switch to camera mode.',
},
'id.verification.choose.mode.title': {
id: 'id.verification.choose.mode.title',
defaultMessage: 'Photo Requirements Options',
description: 'Title for section that allows user to choose photo mode.',
},
'id.verification.choose.mode.help.text': {
id: 'id.verification.choose.mode.hep.text',
defaultMessage: 'To complete verification, please select one of the following options to submit photos. You will be able to switch between these options throughout the process if needed.',
description: 'Help text for section that allows user to choose photo mode.',
},
'id.verification.choose.mode.radio.upload': {
id: 'id.verification.choose.mode.radio.upload',
defaultMessage: 'Upload photos from my device',
description: 'Radio button to choose to upload photos.',
},
'id.verification.choose.mode.radio.camera': {
id: 'id.verification.choose.mode.radio.camera',
defaultMessage: 'Take pictures using my camera',
description: 'Radio button to choose to use camera for photos.',
},
});
export default messages;

View File

@@ -45,12 +45,8 @@ export default function IdVerificationContextProvider({ children }) {
}
}, [authenticatedUser]);
const [optimizelyExperimentName, setOptimizelyExperimentName] = useState('');
const [shouldUseCamera, setShouldUseCamera] = useState(false);
// The following are used to keep track of how a user has submitted photos
const [portraitPhotoMode, setPortraitPhotoMode] = useState('');
const [idPhotoMode, setIdPhotoMode] = useState('');
// Default to upload for the ID image
const [useCameraForId, setUseCameraForId] = useState(false);
// If the user reaches the end of the flow and goes back to retake their photos,
// this flag ensures that they are directed straight back to the summary panel
@@ -86,31 +82,23 @@ export default function IdVerificationContextProvider({ children }) {
// when determining the context value nameOnAccount.
nameOnAccount: verifiedName || authenticatedUser.name,
profileDataManager,
optimizelyExperimentName,
shouldUseCamera,
portraitPhotoMode,
idPhotoMode,
useCameraForId,
reachedSummary,
setFacePhotoFile,
setIdPhotoFile,
setIdPhotoName,
setOptimizelyExperimentName,
setShouldUseCamera,
setPortraitPhotoMode,
setIdPhotoMode,
setUseCameraForId,
setReachedSummary,
tryGetUserMedia: async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
setMediaAccess(MEDIA_ACCESS.GRANTED);
setMediaStream(stream);
setShouldUseCamera(true);
// stop the stream, as we are not using it yet
const tracks = stream.getTracks();
tracks.forEach(track => track.stop());
} catch (err) {
setMediaAccess(MEDIA_ACCESS.DENIED);
setShouldUseCamera(false);
}
},
stopUserMedia: () => {

View File

@@ -14,7 +14,6 @@ import './getUserMediaShim';
import IdVerificationContextProvider from './IdVerificationContextProvider';
import { VerifiedNameContextProvider } from './VerifiedNameContext';
import ReviewRequirementsPanel from './panels/ReviewRequirementsPanel';
import ChooseModePanel from './panels/ChooseModePanel';
import RequestCameraAccessPanel from './panels/RequestCameraAccessPanel';
import PortraitPhotoContextPanel from './panels/PortraitPhotoContextPanel';
import TakePortraitPhotoPanel from './panels/TakePortraitPhotoPanel';
@@ -57,7 +56,6 @@ function IdVerificationPage(props) {
<IdVerificationContextProvider>
<Switch>
<Route path={`${path}/review-requirements`} component={ReviewRequirementsPanel} />
<Route path={`${path}/choose-mode`} component={ChooseModePanel} />
<Route path={`${path}/request-camera-access`} component={RequestCameraAccessPanel} />
<Route path={`${path}/portrait-photo-context`} component={PortraitPhotoContextPanel} />
<Route path={`${path}/take-portrait-photo`} component={TakePortraitPhotoPanel} />

View File

@@ -5,7 +5,7 @@ import { Alert } from '@edx/paragon';
import messages from './IdVerification.messages';
import SupportedMediaTypes from './SupportedMediaTypes';
export default function ImageFileUpload({ onFileChange, setPhotoMode, intl }) {
export default function ImageFileUpload({ onFileChange, intl }) {
const [error, setError] = useState(null);
const errorTypes = {
invalidFileType: 'invalidFileType',
@@ -28,7 +28,6 @@ export default function ImageFileUpload({ onFileChange, setPhotoMode, intl }) {
const fileReader = new FileReader();
fileReader.addEventListener('load', () => {
onFileChange(fileReader.result);
setPhotoMode('upload');
});
fileReader.readAsDataURL(fileObject);
}
@@ -59,6 +58,5 @@ export default function ImageFileUpload({ onFileChange, setPhotoMode, intl }) {
ImageFileUpload.propTypes = {
onFileChange: PropTypes.func.isRequired,
setPhotoMode: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};

View File

@@ -4,7 +4,8 @@
max-width: 100%;
}
.card.accent {
border-top: solid 4px theme-color('warning');
border-top-width: 4px;
border-top-style: solid;
}
.image-preview {
margin-bottom: 1rem;

View File

@@ -64,9 +64,6 @@ export async function submitIdVerification(verificationData) {
facePhotoFile: 'face_image',
idPhotoFile: 'photo_id_image',
idPhotoName: 'full_name',
optimizelyExperimentName: 'experiment_name',
portraitPhotoMode: 'portrait_photo_mode',
idPhotoMode: 'id_photo_mode',
};
const postData = {};
// Don't include blank/null/undefined values.

View File

@@ -1,67 +0,0 @@
import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import { useNextPanelSlug } from '../routing-utilities';
import BasePanel from './BasePanel';
import IdVerificationContext from '../IdVerificationContext';
import messages from '../IdVerification.messages';
function ChooseModePanel(props) {
const panelSlug = 'choose-mode';
const { userId, shouldUseCamera, setShouldUseCamera } = useContext(IdVerificationContext);
function onPhotoModeChange(value) {
setShouldUseCamera(value);
const mode = value ? 'camera' : 'upload';
const eventName = `edx.id_verification.choose.${mode}`;
sendTrackEvent(eventName, {
category: 'id_verification',
user_id: userId,
});
}
return (
<BasePanel
name={panelSlug}
title={props.intl.formatMessage(messages['id.verification.choose.mode.title'])}
>
<p>
{props.intl.formatMessage(messages['id.verification.choose.mode.help.text'])}
</p>
<fieldset>
<Form.Group controlId="formChoosePhotoOption" style={{ marginLeft: '1.25rem' }}>
<Form.Check
type="radio"
id="useUploadMode"
label={props.intl.formatMessage(messages['id.verification.choose.mode.radio.upload'])}
name="photoMode"
checked={!shouldUseCamera}
onChange={() => onPhotoModeChange(false)}
/>
<Form.Check
type="radio"
id="useCameraMode"
label={props.intl.formatMessage(messages['id.verification.choose.mode.radio.camera'])}
name="photoMode"
checked={shouldUseCamera}
onChange={() => onPhotoModeChange(true)}
/>
</Form.Group>
</fieldset>
<div className="action-row">
<Link to={useNextPanelSlug(panelSlug)} className="btn btn-primary" data-testid="next-button">
{props.intl.formatMessage(messages['id.verification.next'])}
</Link>
</div>
</BasePanel>
);
}
ChooseModePanel.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(ChooseModePanel);

View File

@@ -17,7 +17,7 @@ function IdContextPanel(props) {
title={props.intl.formatMessage(messages['id.verification.id.tips.title'])}
>
<p>{props.intl.formatMessage(messages['id.verification.id.tips.description'])}</p>
<div className="card mb-4 shadow accent">
<div className="card mb-4 shadow accent border-warning">
<div className="card-body">
<h6>
{props.intl.formatMessage(messages['id.verification.photo.tips.list.title'])}

View File

@@ -18,7 +18,7 @@ function PortraitPhotoContextPanel(props) {
<p>
{props.intl.formatMessage(messages['id.verification.photo.tips.description'])}
</p>
<div className="card mb-4 shadow accent">
<div className="card mb-4 shadow accent border-warning">
<div className="card-body">
<h6>
{props.intl.formatMessage(messages['id.verification.photo.tips.list.title'])}

View File

@@ -19,7 +19,7 @@ function RequestCameraAccessPanel(props) {
const panelSlug = 'request-camera-access';
const nextPanelSlug = useNextPanelSlug(panelSlug);
const {
tryGetUserMedia, mediaAccess, userId, optimizelyExperimentName,
tryGetUserMedia, mediaAccess, userId,
} = useContext(IdVerificationContext);
const browserName = Bowser.parse(window.navigator.userAgent).browser.name;
@@ -54,12 +54,6 @@ function RequestCameraAccessPanel(props) {
</a>
);
const nextButtonLink = (
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
{props.intl.formatMessage(messages['id.verification.continue.upload'])}
</Link>
);
return (
<BasePanel
name={panelSlug}
@@ -105,7 +99,7 @@ function RequestCameraAccessPanel(props) {
</p>
<EnableCameraDirectionsPanel browserName={browserName} intl={props.intl} />
<div className="action-row">
{optimizelyExperimentName ? nextButtonLink : returnLink}
{returnLink}
</div>
</div>
)}
@@ -117,7 +111,7 @@ function RequestCameraAccessPanel(props) {
</p>
<UnsupportedCameraDirectionsPanel browserName={browserName} intl={props.intl} />
<div className="action-row">
{optimizelyExperimentName ? nextButtonLink : returnLink}
{returnLink}
</div>
</div>
)}

View File

@@ -13,31 +13,15 @@ import messages from '../IdVerification.messages';
import exampleCard from '../assets/example-card.png';
function ReviewRequirementsPanel(props) {
const {
userId, profileDataManager, setOptimizelyExperimentName,
} = useContext(IdVerificationContext);
const { userId, profileDataManager } = useContext(IdVerificationContext);
const panelSlug = 'review-requirements';
const nextPanelSlug = useNextPanelSlug(panelSlug);
const getExperiments = () => {
const {
experimentVariables: {
experimentName = '',
} = {},
} = window;
if (experimentName) {
setOptimizelyExperimentName(experimentName);
}
};
useEffect(() => {
sendTrackEvent('edx.id_verification.started', {
category: 'id_verification',
user_id: userId,
});
getExperiments();
}, [userId]);
function renderManagedProfileMessage() {
@@ -77,7 +61,7 @@ function ReviewRequirementsPanel(props) {
<p>
{props.intl.formatMessage(messages['id.verification.requirements.description'])}
</p>
<div className="card mb-4 shadow accent">
<div className="card mb-4 shadow accent border-warning">
<div className="card-body">
<h6 aria-level="3">
{props.intl.formatMessage(messages['id.verification.requirements.card.device.title'])}
@@ -94,7 +78,7 @@ function ReviewRequirementsPanel(props) {
</p>
</div>
</div>
<div className="card mb-4 shadow accent">
<div className="card mb-4 shadow accent border-warning">
<div className="card-body">
<h6 aria-level="3">
{props.intl.formatMessage(messages['id.verification.requirements.card.id.title'])}

View File

@@ -26,10 +26,7 @@ function SummaryPanel(props) {
nameOnAccount,
idPhotoName,
stopUserMedia,
optimizelyExperimentName,
setReachedSummary,
portraitPhotoMode,
idPhotoMode,
} = useContext(IdVerificationContext);
const nameToBeUsed = idPhotoName || nameOnAccount || '';
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -79,11 +76,6 @@ function SummaryPanel(props) {
*/
verificationData.idPhotoName = nameOnAccount;
}
if (optimizelyExperimentName) {
verificationData.optimizelyExperimentName = optimizelyExperimentName;
verificationData.portraitPhotoMode = portraitPhotoMode;
verificationData.idPhotoMode = idPhotoMode;
}
const result = await submitIdVerification(verificationData);
if (result.success) {
stopUserMedia();
@@ -208,7 +200,7 @@ function SummaryPanel(props) {
</Link>
</div>
</div>
{!optimizelyExperimentName && <CameraHelpWithUpload />}
<CameraHelpWithUpload />
<div className="form-group">
<label htmlFor="name-to-be-used" className="font-weight-bold">
{props.intl.formatMessage(messages['id.verification.name.label'])}

View File

@@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -17,24 +17,36 @@ import SupportedMediaTypes from '../SupportedMediaTypes';
function TakeIdPhotoPanel(props) {
const panelSlug = 'take-id-photo';
const nextPanelSlug = useNextPanelSlug(panelSlug);
const {
setIdPhotoFile, idPhotoFile, optimizelyExperimentName, shouldUseCamera, setIdPhotoMode,
} = useContext(IdVerificationContext);
const { setIdPhotoFile, idPhotoFile, useCameraForId } = useContext(IdVerificationContext);
const [mounted, setMounted] = useState(false);
useEffect(() => {
// This prevents focus switching to the heading when taking a photo
setMounted(true);
}, []);
return (
<BasePanel
name={panelSlug}
title={shouldUseCamera ? props.intl.formatMessage(messages['id.verification.id.photo.title.camera']) : props.intl.formatMessage(messages['id.verification.id.photo.title.upload'])}
focusOnMount={!mounted}
title={useCameraForId
? props.intl.formatMessage(messages['id.verification.id.photo.title.camera'])
: props.intl.formatMessage(messages['id.verification.id.photo.title.upload'])}
>
<div>
{idPhotoFile && !shouldUseCamera && <ImagePreview src={idPhotoFile} alt={props.intl.formatMessage(messages['id.verification.id.photo.preview.alt'])} />}
{idPhotoFile && !useCameraForId && (
<ImagePreview
src={idPhotoFile}
alt={props.intl.formatMessage(messages['id.verification.id.photo.preview.alt'])}
/>
)}
{shouldUseCamera ? (
{useCameraForId ? (
<div>
<p>
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.camera'])}
</p>
<Camera onImageCapture={setIdPhotoFile} setPhotoMode={setIdPhotoMode} isPortrait={false} />
<Camera onImageCapture={setIdPhotoFile} isPortrait={false} />
</div>
) : (
<div style={{ marginBottom: '1.25rem' }}>
@@ -42,12 +54,12 @@ function TakeIdPhotoPanel(props) {
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])}
<SupportedMediaTypes />
</p>
<ImageFileUpload onFileChange={setIdPhotoFile} setPhotoMode={setIdPhotoMode} intl={props.intl} />
<ImageFileUpload onFileChange={setIdPhotoFile} intl={props.intl} />
</div>
)}
</div>
{shouldUseCamera && !optimizelyExperimentName && <CameraHelp />}
<CollapsibleImageHelp isPortrait={false} />
{useCameraForId && <CameraHelp />}
<CollapsibleImageHelp />
<div className="action-row" style={{ visibility: idPhotoFile ? 'unset' : 'hidden' }}>
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
{props.intl.formatMessage(messages['id.verification.next'])}

View File

@@ -1,53 +1,39 @@
import React, { useContext } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useNextPanelSlug } from '../routing-utilities';
import BasePanel from './BasePanel';
import ImageFileUpload from '../ImageFileUpload';
import ImagePreview from '../ImagePreview';
import Camera from '../Camera';
import CameraHelp from '../CameraHelp';
import IdVerificationContext from '../IdVerificationContext';
import messages from '../IdVerification.messages';
import CollapsibleImageHelp from '../CollapsibleImageHelp';
import SupportedMediaTypes from '../SupportedMediaTypes';
function TakePortraitPhotoPanel(props) {
const panelSlug = 'take-portrait-photo';
const nextPanelSlug = useNextPanelSlug(panelSlug);
const {
setFacePhotoFile, facePhotoFile, shouldUseCamera, optimizelyExperimentName, setPortraitPhotoMode,
} = useContext(IdVerificationContext);
const { setFacePhotoFile, facePhotoFile } = useContext(IdVerificationContext);
const [mounted, setMounted] = useState(false);
useEffect(() => {
// This prevents focus switching to the heading when taking a photo
setMounted(true);
}, []);
return (
<BasePanel
name={panelSlug}
title={shouldUseCamera ? props.intl.formatMessage(messages['id.verification.portrait.photo.title.camera']) : props.intl.formatMessage(messages['id.verification.portrait.photo.title.upload'])}
focusOnMount={!mounted}
title={props.intl.formatMessage(messages['id.verification.portrait.photo.title.camera'])}
>
<div>
{facePhotoFile && !shouldUseCamera && <ImagePreview src={facePhotoFile} alt={props.intl.formatMessage(messages['id.verification.portrait.photo.preview.alt'])} />}
{shouldUseCamera ? (
<div>
<p>
{props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.camera'])}
</p>
<Camera onImageCapture={setFacePhotoFile} setPhotoMode={setPortraitPhotoMode} isPortrait />
</div>
) : (
<div style={{ marginBottom: '1.25rem' }}>
<p data-testid="upload-text">
{props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.upload'])}
<SupportedMediaTypes />
</p>
<ImageFileUpload onFileChange={setFacePhotoFile} setPhotoMode={setPortraitPhotoMode} intl={props.intl} />
</div>
)}
<p>
{props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.camera'])}
</p>
<Camera onImageCapture={setFacePhotoFile} isPortrait />
</div>
{shouldUseCamera && !optimizelyExperimentName && <CameraHelp isPortrait />}
<CollapsibleImageHelp isPortrait />
<CameraHelp isPortrait />
<div className="action-row" style={{ visibility: facePhotoFile ? 'unset' : 'hidden' }}>
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
{props.intl.formatMessage(messages['id.verification.next'])}

View File

@@ -1,6 +1,5 @@
import { useContext } from 'react';
import { useLocation } from 'react-router-dom';
import IdVerificationContext, { MEDIA_ACCESS } from './IdVerificationContext';
import IdVerificationContext from './IdVerificationContext';
const SLUGS = {
REVIEW_REQUIREMENTS: 'review-requirements',
@@ -17,7 +16,6 @@ const SLUGS = {
const panelSteps = [
SLUGS.REVIEW_REQUIREMENTS,
SLUGS.CHOOSE_MODE,
SLUGS.REQUEST_CAMERA_ACCESS,
SLUGS.PORTRAIT_PHOTO_CONTEXT,
SLUGS.TAKE_PORTRAIT_PHOTO,
@@ -31,15 +29,7 @@ const panelSteps = [
// eslint-disable-next-line import/prefer-default-export
export const useNextPanelSlug = (originSlug) => {
// Go back to the summary view if that's where they came from
const location = useLocation();
const isFromPortrait = location.state && location.state.fromPortraitCapture;
const isFromId = location.state && location.state.fromIdCapture;
const {
mediaAccess,
optimizelyExperimentName,
reachedSummary,
shouldUseCamera,
} = useContext(IdVerificationContext);
const { reachedSummary } = useContext(IdVerificationContext);
const canRerouteToSummary = [
SLUGS.TAKE_PORTRAIT_PHOTO,
@@ -51,32 +41,6 @@ export const useNextPanelSlug = (originSlug) => {
return SLUGS.SUMMARY;
}
// the following are used as part of an A/B experiment
if (isFromPortrait) {
if (mediaAccess === MEDIA_ACCESS.GRANTED) {
return SLUGS.PORTRAIT_PHOTO_CONTEXT;
}
return SLUGS.TAKE_PORTRAIT_PHOTO;
}
if (isFromId) {
if (mediaAccess === MEDIA_ACCESS.GRANTED) {
return SLUGS.ID_CONTEXT;
}
return SLUGS.TAKE_ID_PHOTO;
}
if (originSlug === SLUGS.REVIEW_REQUIREMENTS && !optimizelyExperimentName) {
return SLUGS.REQUEST_CAMERA_ACCESS;
}
if (originSlug === SLUGS.CHOOSE_MODE && !shouldUseCamera) {
return SLUGS.TAKE_PORTRAIT_PHOTO;
}
if (originSlug === SLUGS.TAKE_PORTRAIT_PHOTO && !shouldUseCamera) {
return SLUGS.TAKE_ID_PHOTO;
}
if (originSlug === SLUGS.REQUEST_CAMERA_ACCESS && mediaAccess !== MEDIA_ACCESS.GRANTED) {
return SLUGS.TAKE_PORTRAIT_PHOTO;
}
const nextIndex = panelSteps.indexOf(originSlug) + 1;
return nextIndex < panelSteps.length ? panelSteps[nextIndex] : null;
};
@@ -84,11 +48,8 @@ export const useNextPanelSlug = (originSlug) => {
// check if the user is too far into the flow and if so, return the slug of the
// furthest panel they are allow to be.
export const useVerificationRedirectSlug = (slug) => {
const { facePhotoFile, idPhotoFile, optimizelyExperimentName } = useContext(IdVerificationContext);
const { facePhotoFile, idPhotoFile } = useContext(IdVerificationContext);
const indexOfCurrentPanel = panelSteps.indexOf(slug);
if (!optimizelyExperimentName && slug === SLUGS.CHOOSE_MODE) {
return SLUGS.REVIEW_REQUIREMENTS;
}
if (!facePhotoFile) {
if (indexOfCurrentPanel > panelSteps.indexOf(SLUGS.TAKE_PORTRAIT_PHOTO)) {
return SLUGS.PORTRAIT_PHOTO_CONTEXT;

View File

@@ -27,7 +27,6 @@ describe('SubmittedPanel', () => {
const defaultProps = {
intl: {},
onImageCapture: jest.fn(),
setPhotoMode: jest.fn(),
isPortrait: true,
};
@@ -57,7 +56,6 @@ describe('SubmittedPanel', () => {
expect(button).toHaveTextContent('Take Photo');
fireEvent.click(button);
expect(defaultProps.onImageCapture).toHaveBeenCalled();
expect(defaultProps.setPhotoMode).toHaveBeenCalledWith('camera');
});
it('shows correct help text for portrait photo capture', async () => {

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import {
render, cleanup, screen, act, fireEvent,
render, cleanup, screen, act,
} from '@testing-library/react';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import * as analytics from '@edx/frontend-platform/analytics';
@@ -22,71 +22,18 @@ const IntlCollapsible = injectIntl(CollapsibleImageHelp);
const history = createMemoryHistory();
describe('CollapsibleImageHelpPanel', () => {
const defaultProps = {
intl: {},
isPortrait: true,
};
const defaultProps = { intl: {} };
const contextValue = {
shouldUseCamera: true,
setShouldUseCamera: jest.fn(),
optimizelyExperimentName: '',
mediaAccess: 'granted',
useCameraForId: true,
setUseCameraForId: jest.fn(),
};
afterEach(() => {
cleanup();
});
it('does not return if not part of experiment', async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCollapsible {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const titleText = screen.queryByText('Upload a Photo Instead');
expect(titleText).not.toBeInTheDocument();
});
it('does not return if media access denied or unsupported', async () => {
let titleText = '';
contextValue.mediaAccess = 'denied';
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCollapsible {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
titleText = screen.queryByText('Upload a Photo Instead');
expect(titleText).not.toBeInTheDocument();
contextValue.mediaAccess = 'unsupported';
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCollapsible {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
titleText = screen.queryByText('Upload a Photo Instead');
expect(titleText).not.toBeInTheDocument();
});
it('shows the correct text if user should switch to upload', async () => {
contextValue.optimizelyExperimentName = 'test';
contextValue.mediaAccess = 'granted';
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -106,9 +53,7 @@ describe('CollapsibleImageHelpPanel', () => {
});
it('shows the correct text if user should switch to camera', async () => {
contextValue.optimizelyExperimentName = 'test';
contextValue.mediaAccess = 'granted';
contextValue.shouldUseCamera = false;
contextValue.useCameraForId = false;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -126,27 +71,4 @@ describe('CollapsibleImageHelpPanel', () => {
const button = screen.getByTestId('toggle-button');
expect(button).toHaveTextContent('Switch to Camera Mode');
});
it('shows the correct text if user should switch to camera with pending media access', async () => {
contextValue.optimizelyExperimentName = 'test';
contextValue.mediaAccess = 'pending';
contextValue.shouldUseCamera = false;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlCollapsible {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const titleText = screen.getByText('Use Your Camera Instead');
expect(titleText).toBeInTheDocument();
const helpText = screen.getByTestId('help-text');
expect(helpText.textContent).toContain('If you are having trouble uploading a photo above');
const accessLink = screen.getByTestId('access-link');
fireEvent.click(accessLink);
expect(history.location.pathname).toEqual('/request-camera-access');
});
});

View File

@@ -1,110 +0,0 @@
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import {
render, cleanup, act, screen, fireEvent,
} from '@testing-library/react';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import IdVerificationContext from '../../IdVerificationContext';
import ChooseModePanel from '../../panels/ChooseModePanel';
jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
const IntlChooseModePanel = injectIntl(ChooseModePanel);
const history = createMemoryHistory();
describe('ChooseModePanel', () => {
const defaultProps = {
intl: {},
};
const contextValue = {
optimizelyExperimentName: 'test',
shouldUseCamera: false,
reachedSummary: false,
};
afterEach(() => {
cleanup();
});
it('renders correctly', async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlChooseModePanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
// check that radio button for upload is selected
const uploadRadioButton = await screen.findByLabelText('Upload photos from my device');
expect(uploadRadioButton).toBeChecked();
// check that if upload is selected, next button goes to correct screen
const nextButton = await screen.findByTestId('next-button');
expect(nextButton.getAttribute('href')).toEqual('/take-portrait-photo');
});
it('renders correctly if user wants to use camera', async () => {
contextValue.shouldUseCamera = true;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlChooseModePanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
// check that radio button for camera is selected
const cameraRadioButton = await screen.findByLabelText('Take pictures using my camera');
expect(cameraRadioButton).toBeChecked();
// check that if upload is selected, next button goes to correct screen
const nextButton = await screen.findByTestId('next-button');
expect(nextButton.getAttribute('href')).toEqual('/request-camera-access');
});
it('reroutes correctly if reachedSummary is true', async () => {
contextValue.shouldUseCamera = true;
contextValue.reachedSummary = true;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlChooseModePanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const nextButton = await screen.findByTestId('next-button');
fireEvent.click(nextButton);
expect(history.location.pathname).toEqual('/request-camera-access');
});
it('redirects if user is not part of experiment', async () => {
contextValue.optimizelyExperimentName = '';
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlChooseModePanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
// check that radio button is not in document
const cameraRadioButton = await screen.queryByLabelText('Take pictures using my camera');
expect(cameraRadioButton).not.toBeInTheDocument();
});
});

View File

@@ -23,7 +23,6 @@ describe('IdContextPanel', () => {
};
const contextValue = {
optimizelyExperimentName: '',
facePhotoFile: 'test.jpg',
reachedSummary: false,
};
@@ -62,33 +61,4 @@ describe('IdContextPanel', () => {
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-id-photo');
});
it('does not show help text for photo upload if not part of experiment', async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlIdContextPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const title = await screen.queryByText('What if I want to upload a photo instead?');
expect(title).not.toBeInTheDocument();
});
it('shows help text for photo upload if part of experiment', async () => {
contextValue.optimizelyExperimentName = 'test';
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlIdContextPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const title = await screen.queryByText('What if I want to upload a photo instead?');
expect(title).toBeInTheDocument();
});
});

View File

@@ -22,10 +22,7 @@ describe('PortraitPhotoContextPanel', () => {
intl: {},
};
const contextValue = {
optimizelyExperimentName: '',
reachedSummary: false,
};
const contextValue = { reachedSummary: false };
afterEach(() => {
cleanup();
@@ -61,33 +58,4 @@ describe('PortraitPhotoContextPanel', () => {
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-portrait-photo');
});
it('does not show help text for photo upload if not part of experiment', async () => {
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlPortraitPhotoContextPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const title = await screen.queryByText('What if I want to upload a photo instead?');
expect(title).not.toBeInTheDocument();
});
it('shows help text for photo upload if part of experiment', async () => {
contextValue.optimizelyExperimentName = 'test';
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlPortraitPhotoContextPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const title = await screen.queryByText('What if I want to upload a photo instead?');
expect(title).toBeInTheDocument();
});
});

View File

@@ -5,6 +5,7 @@ import { createMemoryHistory } from 'history';
import {
render, screen, cleanup, act, fireEvent,
} from '@testing-library/react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import IdVerificationContext from '../../IdVerificationContext';
import RequestCameraAccessPanel from '../../panels/RequestCameraAccessPanel';
@@ -84,27 +85,6 @@ describe('RequestCameraAccessPanel', () => {
expect(text).toHaveTextContent(/It looks like we're unable to access your camera./);
});
it('renders correctly with media access denied in optimizely experiment', async () => {
contextValue.mediaAccess = 'denied';
contextValue.optimizelyExperimentName = 'test';
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const text = await screen.findByTestId('camera-access-failure');
expect(text).toHaveTextContent(/It looks like we're unable to access your camera./);
const nextButton = await screen.findByText('Continue with Upload');
fireEvent.click(nextButton);
expect(history.location.pathname).toEqual('/take-portrait-photo');
contextValue.optimizelyExperimentName = '';
});
it('renders correctly with media access unsupported with Chrome browser', async () => {
contextValue.mediaAccess = 'unsupported';
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: 'Chrome' } });
@@ -203,9 +183,25 @@ describe('RequestCameraAccessPanel', () => {
expect(text).toHaveTextContent(/Open the Flash Player/);
});
it('reroutes correctly to portrait context', async () => {
it('routes to dashboard when camera access is denied', async () => {
contextValue.mediaAccess = 'denied';
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByRole('link');
expect(button).toHaveAttribute('href', `${getConfig().LMS_BASE_URL}/dashboard`);
});
it('routes correctly to portrait context', async () => {
contextValue.mediaAccess = 'granted';
history.location.state = { fromPortraitCapture: true };
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
@@ -221,105 +217,4 @@ describe('RequestCameraAccessPanel', () => {
fireEvent.click(button);
expect(history.location.pathname).toEqual('/portrait-photo-context');
});
it('reroutes correctly to ID context', async () => {
contextValue.mediaAccess = 'granted';
history.location.state = { fromIdCapture: true };
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('next-button');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/id-context');
});
it('reroutes to portrait context when reachedSummary is true', async () => {
contextValue.mediaAccess = 'granted';
contextValue.reachedSummary = true;
history.location.state = { fromPortraitCapture: true };
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('next-button');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/portrait-photo-context');
});
it('reroutes to ID context when reachedSummary is true', async () => {
contextValue.mediaAccess = 'granted';
contextValue.reachedSummary = true;
history.location.state = { fromIdCapture: true };
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('next-button');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/id-context');
});
it('reroutes correctly to portrait context with no media access', async () => {
contextValue.mediaAccess = 'denied';
contextValue.optimizelyExperimentName = 'test';
history.location.state = { fromPortraitCapture: true };
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('next-button');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-portrait-photo');
contextValue.optimizelyExperimentName = '';
});
it('reroutes correctly to ID context with no media access', async () => {
contextValue.mediaAccess = 'denied';
contextValue.optimizelyExperimentName = 'test';
history.location.state = { fromIdCapture: true };
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlRequestCameraAccessPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
const button = await screen.findByTestId('next-button');
fireEvent.click(button);
expect(history.location.pathname).toEqual('/take-id-photo');
contextValue.optimizelyExperimentName = '';
});
});

View File

@@ -22,7 +22,7 @@ describe('ReviewRequirementsPanel', () => {
intl: {},
};
const context = { setOptimizelyExperimentName: jest.fn() };
const context = {};
const getPanel = async () => {
await act(async () => render((
@@ -47,13 +47,6 @@ describe('ReviewRequirementsPanel', () => {
expect(history.location.pathname).toEqual('/request-camera-access');
});
it('updates optimizely experiment name in context', async () => {
window.experimentVariables = {};
window.experimentVariables.experimentName = 'test-experiment';
await getPanel();
expect(context.setOptimizelyExperimentName).toHaveBeenCalledWith('test-experiment');
});
it('displays an alert if the user\'s account information is managed by a third party', async () => {
context.profileDataManager = 'test-org';
await getPanel();

View File

@@ -32,9 +32,6 @@ describe('SummaryPanel', () => {
idPhotoFile: 'test.jpg',
nameOnAccount: 'test name',
idPhotoName: 'test name',
optimizelyExperimentName: 'test-experiment',
portraitPhotoMode: 'camera',
idPhotoMode: 'upload',
stopUserMedia: jest.fn(),
setReachedSummary: jest.fn(),
};
@@ -76,13 +73,11 @@ describe('SummaryPanel', () => {
});
it('allows user to upload ID photo', async () => {
appContextValue.optimizelyExperimentName = '';
await getPanel();
const collapsible = await screen.getAllByRole('button', { 'aria-expanded': false })[0];
fireEvent.click(collapsible);
const uploadButton = await screen.getByTestId('fileUpload');
expect(uploadButton).toBeVisible();
appContextValue.optimizelyExperimentName = 'test-experiment';
});
it('displays warning if account is managed by a third party', async () => {
@@ -97,9 +92,6 @@ describe('SummaryPanel', () => {
facePhotoFile: appContextValue.facePhotoFile,
idPhotoFile: appContextValue.idPhotoFile,
idPhotoName: appContextValue.idPhotoName,
optimizelyExperimentName: appContextValue.optimizelyExperimentName,
portraitPhotoMode: appContextValue.portraitPhotoMode,
idPhotoMode: appContextValue.idPhotoMode,
courseRunKey: null,
};
await getPanel();
@@ -114,9 +106,6 @@ describe('SummaryPanel', () => {
const verificationData = {
facePhotoFile: appContextValue.facePhotoFile,
idPhotoFile: appContextValue.idPhotoFile,
portraitPhotoMode: appContextValue.portraitPhotoMode,
idPhotoMode: appContextValue.idPhotoMode,
optimizelyExperimentName: appContextValue.optimizelyExperimentName,
courseRunKey: null,
idPhotoName: appContextValue.nameOnAccount,
};
@@ -131,9 +120,6 @@ describe('SummaryPanel', () => {
const verificationData = {
facePhotoFile: appContextValue.facePhotoFile,
idPhotoFile: appContextValue.idPhotoFile,
portraitPhotoMode: appContextValue.portraitPhotoMode,
idPhotoMode: appContextValue.idPhotoMode,
optimizelyExperimentName: appContextValue.optimizelyExperimentName,
courseRunKey: null,
idPhotoName: appContextValue.nameOnAccount,
};
@@ -208,11 +194,4 @@ describe('SummaryPanel', () => {
'One or more of the files you have uploaded is in an unsupported format. Please choose from the following:',
);
});
it('does not show ID upload option if user is in experiment', async () => {
await getPanel();
const collapsible = await screen.queryByTestId('collapsible');
expect(collapsible).not.toBeInTheDocument();
appContextValue.optimizelyExperimentName = 'test-experiment';
});
});

View File

@@ -28,6 +28,7 @@ describe('TakeIdPhotoPanel', () => {
idPhotoFile: null,
reachedSummary: false,
setIdPhotoFile: jest.fn(),
useCameraForId: false,
};
afterEach(() => {
@@ -83,9 +84,6 @@ describe('TakeIdPhotoPanel', () => {
});
it('shows correct text if user should use upload', async () => {
contextValue.optimizelyExperimentName = 'test';
contextValue.shouldUseCamera = false;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">

View File

@@ -28,7 +28,6 @@ describe('TakePortraitPhotoPanel', () => {
idPhotoFile: null,
reachedSummary: false,
setFacePhotoFile: jest.fn(),
setShouldUseCamera: jest.fn(),
};
afterEach(() => {
@@ -51,7 +50,6 @@ describe('TakePortraitPhotoPanel', () => {
it('shows next button after photo is taken and routes to IdContextPanel', async () => {
contextValue.facePhotoFile = 'test.jpg';
contextValue.shouldUseCamera = true;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
@@ -84,26 +82,4 @@ describe('TakePortraitPhotoPanel', () => {
fireEvent.click(button);
expect(history.location.pathname).toEqual('/summary');
});
it('shows correct text if user should use upload', async () => {
contextValue.optimizelyExperimentName = 'test';
contextValue.shouldUseCamera = false;
await act(async () => render((
<Router history={history}>
<IntlProvider locale="en">
<IdVerificationContext.Provider value={contextValue}>
<IntlTakePortraitPhotoPanel {...defaultProps} />
</IdVerificationContext.Provider>
</IntlProvider>
</Router>
)));
// check that upload title and text are correct
const title = await screen.findByText('Upload a Photo of Yourself');
expect(title).toBeVisible();
const text = await screen.findByTestId('upload-text');
expect(text.textContent).toContain('Please upload a portrait photo');
});
});