Compare commits

...

52 Commits

Author SHA1 Message Date
Sagirov Eugeniy
b23d72dd37 chore: update frontend-platform version to v4.2.0 2023-05-09 18:11:54 -03:00
Eugene Dyudyunov
c792b0ec65 feat: make password reset support URL configurable (#746)
* feat: make password reset support URL configurable

Replace the hardcoded `support.edx.org` value with the one from the env vars.

* fix: linting error
2023-03-02 12:26:32 +05:00
renovate[bot]
d280062fe3 chore(deps): update dependency @edx/frontend-build to v12.0.6 2022-10-10 07:24:33 +00:00
renovate[bot]
f0366a98f4 chore(deps): update dependency @edx/browserslist-config to v1.1.1 2022-10-10 07:16:08 +00:00
Muhammad Abdullah Waheed
9d0577fd93 fix: removed env constants and used them directly (#657) 2022-10-04 18:23:56 +05:00
Muhammad Abdullah Waheed
dba70b557c Automate Browserlist DB Update (#642)
* feat: added cron github action to auto update brwoserlist DB periodically

* feat: added workflow_dispatch for manual testing

* fix: fixed error

* fix: fixed indentation issue

* fix: linting issue
2022-10-04 17:55:30 +05:00
Jenkins
f6840bc202 chore(i18n): update translations 2022-10-02 17:05:49 -04:00
renovate[bot]
1af5f0d179 fix(deps): update dependency classnames to v2.3.2 2022-09-26 12:40:22 +00:00
renovate[bot]
b3a91470f8 fix(deps): update dependency @edx/frontend-component-header to v3.2.1 2022-09-26 12:32:19 +00:00
Jenkins
9b18588e26 chore(i18n): update translations 2022-09-25 17:04:29 -04:00
renovate[bot]
3d2e7dc8a4 fix(deps): update dependency @edx/frontend-component-footer to v11.2.1 2022-09-19 12:29:38 +00:00
renovate[bot]
41f1552f9b chore(deps): update dependency @edx/frontend-build to v12.0.5 2022-09-19 12:22:14 +00:00
Jenkins
ffa8a36ce6 chore(i18n): update translations 2022-09-18 17:01:34 -04:00
alangsto
3505e645e3 Merge pull request #647 from openedx/alangsto/update_dob_language
fix: update dob form language
2022-09-14 16:15:01 -04:00
Alie Langston
88db6084df fix: update dob form language 2022-09-14 15:59:58 -04:00
Sarina Canelake
27c8ab28f3 Merge pull request #643 from openedx/tcril/fix-gh-org-url
Fix github url strings (org edx -> openedx)
2022-09-14 10:19:22 -04:00
renovate[bot]
e01b595a0c fix(deps): update dependency @edx/frontend-platform to v2.6.2 2022-09-12 11:37:36 +00:00
renovate[bot]
0d351f5173 chore(deps): update dependency @edx/frontend-build to v12.0.4 2022-09-12 11:29:56 +00:00
Jenkins
03dbfb5670 chore(i18n): update translations 2022-09-11 17:01:42 -04:00
Sarina Canelake
1e17fc9e32 fix: update path to .github workflows to read from openedx org 2022-09-10 18:05:35 -04:00
Sarina Canelake
da912f1e35 fix: fix github url strings (org edx -> openedx) 2022-09-07 08:57:12 -04:00
renovate[bot]
fb0f832832 fix(deps): update dependency @edx/frontend-component-header to v3.2.0 2022-09-05 18:05:19 +00:00
renovate[bot]
870dd631bb fix(deps): update dependency @edx/frontend-component-footer to v11.2.0 2022-09-05 17:57:37 +00:00
Jenkins
5b9f251e93 chore(i18n): update translations 2022-09-04 17:01:50 -04:00
renovate[bot]
98c14bbd3b chore(deps): update dependency @testing-library/jest-dom to v5.16.5 2022-08-29 12:14:47 +00:00
renovate[bot]
00ce8b927f fix(deps): update dependency react-transition-group to v4.4.5 2022-08-29 12:06:54 +00:00
renovate[bot]
ab215cd909 fix(deps): update dependency react-redux to v7.2.8 2022-08-22 11:04:54 +00:00
renovate[bot]
51e7773207 chore(deps): pin dependencies 2022-08-22 10:57:13 +00:00
Jenkins
6f5a1a8aa9 chore(i18n): update translations 2022-08-21 17:02:10 -04:00
Bilal Qamar
deb48fb9d2 Merge pull request #625 from openedx/bilalqamar95/revert-eslint-fragment-change
Reverted changes made to satisfy jsx-no-useless-fragment rule
2022-08-19 18:46:58 +05:00
Bilal Qamar
20b451afb6 refactor: reverted changes made to satisfy jsx-no-useless-fragment rule 2022-08-19 17:25:48 +05:00
alangsto
ad6f812974 Merge pull request #623 from openedx/alangsto/update_dob
feat: add DOB month and year field
2022-08-18 13:59:26 -04:00
Alie Langston
9573516b37 feat: add DOB month and year field 2022-08-18 13:40:30 -04:00
Bilal Qamar
f0d6a92ab2 Upgraded frontend-build version to v12 (#613)
* refactor: upgraded frontend-build version to v12

* refactor: updated frontend-build

* refactor: resolved eslint issues

* refactor: updated package-lock

* refactor: resolved eslint issues after master branch merge
2022-08-16 10:57:43 -04:00
renovate[bot]
dc4d4031e9 fix(deps): update dependency @edx/frontend-component-header to v3.1.3 2022-08-15 09:43:03 +00:00
renovate[bot]
84a5c7aaf1 fix(deps): update dependency @edx/frontend-component-footer to v11.1.2 2022-08-15 09:29:22 +00:00
edx-semantic-release
6ad666342d chore(i18n): update translations 2022-08-14 17:02:20 -04:00
Diana Olarte
1252498872 feat: allow runtime configuration (#603) 2022-08-09 13:44:05 -04:00
renovate[bot]
f9d04e4dd4 fix(deps): update dependency @edx/frontend-platform to v1.15.6 2022-08-08 12:23:08 +00:00
renovate[bot]
c96b9bb77d chore(deps): update dependency @testing-library/react to v12.1.5 2022-08-08 12:13:06 +00:00
edx-semantic-release
f3c672c5ae chore(i18n): update translations 2022-08-07 17:02:31 -04:00
renovate[bot]
63c396c03a chore(deps): update dependency node-forge to 1.3.0 [security] (#567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 11:33:38 +05:00
Maman Khan
0981857062 fix: removed depreciated codecov package (#608) 2022-07-26 13:27:58 +05:00
Maman Khan
0527c73529 Update packages to remove security vulnerabilities (#605)
* fix: updated vulnerable packages

* fix: fixed failed tests after package update

* fix: linting issues failing ci tests

* fix: package lock update

* fix: snapshot updated to UTC

* fix: missing dependency 'long'
2022-07-26 13:05:31 +05:00
renovate[bot]
5c5dbc369b chore(deps): update dependency lodash to 4.17.21 [security] (#550)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-07-26 11:51:21 +05:00
Maman Khan
196719963f Merge pull request #607 from openedx/maman/add-browserslist-config
build: use shared browserslist configuration
2022-06-29 15:55:16 +05:00
maman
92ee4dfbb9 fix: removed es5 ci check 2022-06-13 18:11:05 +05:00
maman
9d0b3524cb feat: made browserslist setting configurable 2022-06-13 17:48:29 +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
83 changed files with 13090 additions and 12520 deletions

4
.env
View File

@@ -26,4 +26,8 @@ STUDIO_BASE_URL=''
SUPPORT_URL=''
USER_INFO_COOKIE_NAME=''
ENABLE_COPPA_COMPLIANCE=''
ENABLE_DOB_UPDATE=''
MARKETING_EMAILS_OPT_IN=''
APP_ID=
MFE_CONFIG_API_URL=
PASSWORD_RESET_SUPPORT_LINK=''

View File

@@ -27,4 +27,9 @@ STUDIO_BASE_URL=''
SUPPORT_URL='http://localhost:18000/support'
USER_INFO_COOKIE_NAME='edx-user-info'
ENABLE_COPPA_COMPLIANCE=''
ENABLE_DOB_UPDATE=''
MARKETING_EMAILS_OPT_IN=''
APP_ID=
MFE_CONFIG_API_URL=
PASSWORD_RESET_SUPPORT_LINK='mailto:support@example.com'

View File

@@ -26,4 +26,7 @@ STUDIO_BASE_URL=''
SUPPORT_URL='http://localhost:18000/support'
USER_INFO_COOKIE_NAME='edx-user-info'
ENABLE_COPPA_COMPLIANCE=''
ENABLE_DOB_UPDATE=''
MARKETING_EMAILS_OPT_IN=''
APP_ID=
MFE_CONFIG_API_URL=

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');

View File

@@ -9,20 +9,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [12, 14, 16]
npm: [6, 8.5.0]
node: [16]
npm: [8.5.0]
npm-test:
- i18n_extract
- is-es5
- lint
- test
exclude:
- node: 12
npm: 8.5.0
- node: 14
npm: 8.5.0
- node: 16
npm: 6
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
@@ -33,6 +25,6 @@ jobs:
- run: make test NPM_TESTS=build
- run: make test NPM_TESTS=${{ matrix.npm-test }}
- name: upload coverage
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: false

View File

@@ -7,4 +7,4 @@ on:
jobs:
commitlint:
uses: edx/.github/.github/workflows/commitlint.yml@master
uses: openedx/.github/.github/workflows/commitlint.yml@master

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: openedx/.github/.github/workflows/lockfileversion-check.yml@master

View File

@@ -0,0 +1,41 @@
name: Update Browserlist DB
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
jobs:
update-dep:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16]
npm: [8.5.x]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- run: npm install -g npm@${{ matrix.npm }}
- run: make requirements
- name: Update dependencies
run: npx browserslist@latest --update-db
- name: setup testeng-ci
run: |
git clone https://github.com/edx/testeng-ci.git
cd $GITHUB_WORKSPACE/testeng-ci
pip install -r requirements/base.txt
- name: create pull request
id: createpullrequest
env:
GITHUB_TOKEN: ${{ secrets.requirements_bot_github_token }}
GITHUB_USER_EMAIL: ${{ secrets.requirements_bot_github_email }}
run: |
cd $GITHUB_WORKSPACE/testeng-ci
python -m jenkins.pull_request_creator --repo-root=$GITHUB_WORKSPACE \
--target-branch="master" --base-branch-name="update-browserslist" \
--commit-message="chore: update browserslist" --pr-title="Update browserslist DB" \
--pr-body="Updated browserlist DB" --delete-old-pull-requests --output-pr-url-for-github-action

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16

View File

@@ -11,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
.PHONY: test
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite

View File

@@ -12,7 +12,7 @@ This is a micro-frontend application responsible for the display and updating of
What is the domain of this MFE?
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/edx/frontend-app-profile>`_
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/openedx/frontend-app-profile>`_
- Account settings page
@@ -23,9 +23,9 @@ In this MFE: Private user settings UIs. Public facing profile is in a `separate
Installation
------------
This MFE is bundled with `Devstack <https://github.com/edx/devstack>`_, see the `Getting Started <https://github.com/edx/devstack#getting-started>`_ section for setup instructions.
This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
1. Install Devstack using the `Getting Started <https://github.com/edx/devstack#getting-started>`_ instructions.
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
2. Start up Devstack, if it's not already started.
@@ -55,6 +55,16 @@ Example: ``https://support.example.com``
The fully-qualified URL to the support page in the target environment.
``PASSWORD_RESET_SUPPORT_LINK``
Examples:
- ``https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-``
- ``mailto:support@example.com``
The fully-qualified URL to the support page or email to request the support from in the target environment.
edX-specific Environment Variables
**********************************
@@ -102,8 +112,8 @@ In the future, it's possible that demographics could be modeled as a plugin rath
==============================
.. |ci-badge| image:: https://github.com/edx/edx-developer-docs/actions/workflows/ci.yml/badge.svg
:target: https://github.com/edx/edx-developer-docs/actions/workflows/ci.yml
.. |ci-badge| image:: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml/badge.svg
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
:alt: Continuous Integration
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-account
:target: https://codecov.io/gh/edx/frontend-app-account

23778
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,44 +6,42 @@
"license": "AGPL-3.0",
"repository": {
"type": "git",
"url": "git+https://github.com/edx/frontend-app-account.git"
"url": "git+https://github.com/openedx/frontend-app-account.git"
},
"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 .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "fedx-scripts jest --coverage --passWithNoTests"
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests"
},
"bugs": {
"url": "https://github.com/edx/frontend-app-account/issues"
"url": "https://github.com/openedx/frontend-app-account/issues"
},
"homepage": "https://github.com/edx/frontend-app-account#readme",
"homepage": "https://github.com/openedx/frontend-app-account#readme",
"publishConfig": {
"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.2.0",
"@edx/frontend-component-header": "2.4.5",
"@edx/frontend-platform": "1.15.1",
"@edx/paragon": "19.6.0",
"@edx/frontend-component-footer": "12.0.0",
"@edx/frontend-component-header": "4.0.0",
"@edx/frontend-platform": "4.2.0",
"@edx/paragon": "20.28.5",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.1.16",
"@tensorflow-models/blazeface": "0.0.7",
"@tensorflow/tfjs-converter": "1.7.4",
"@tensorflow/tfjs-core": "1.7.4",
"@tensorflow/tfjs-converter": "3.18.0",
"@tensorflow/tfjs-core": "3.18.0",
"bowser": "2.11.0",
"classnames": "2.3.1",
"classnames": "2.3.2",
"core-js": "3.19.3",
"font-awesome": "4.7.0",
"form-urlencoded": "4.0.1",
@@ -59,17 +57,19 @@
"lodash.omit": "4.5.0",
"lodash.pick": "4.4.0",
"lodash.pickby": "4.6.0",
"long": "5.2.0",
"memoize-one": "5.2.1",
"prop-types": "15.7.2",
"qs": "6.10.3",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.6",
"react-helmet": "6.1.0",
"react-redux": "7.2.8",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"react-router-hash-link": "1.2.2",
"react-scrollspy": "3.4.3",
"react-transition-group": "4.4.2",
"react-transition-group": "4.4.5",
"redux": "4.1.2",
"redux-devtools-extension": "2.13.9",
"redux-logger": "3.0.6",
@@ -80,16 +80,15 @@
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/frontend-build": "9.1.1",
"@edx/reactifex": "^1.0.3",
"@testing-library/jest-dom": "5.15.1",
"@testing-library/react": "12.1.4",
"codecov": "3.8.3",
"@edx/browserslist-config": "1.1.1",
"@edx/frontend-build": "12.0.6",
"@edx/reactifex": "1.1.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"es-check": "6.1.1",
"react-test-renderer": "16.14.0",
"reactifex": "1.1.1",
"redux-mock-store": "1.5.4"
}
}
}

View File

@@ -37,11 +37,13 @@ import ThirdPartyAuth from './third-party-auth';
import BetaLanguageBanner from './BetaLanguageBanner';
import EmailField from './EmailField';
import OneTimeDismissibleAlert from './OneTimeDismissibleAlert';
import DOBModal from './DOBForm';
import {
YEAR_OF_BIRTH_OPTIONS,
EDUCATION_LEVELS,
GENDER_OPTIONS,
COUNTRY_WITH_STATES,
COPPA_COMPLIANCE_YEAR,
getStatesList,
} from './data/constants';
import { fetchSiteLanguages } from './site-language';
@@ -146,28 +148,13 @@ class AccountSettingsPage extends React.Component {
})),
}));
sortDates = (a, b) => {
const aTimeSinceEpoch = new Date(a).getTime();
const bTimeSinceEpoch = new Date(b).getTime();
return bTimeSinceEpoch - aTimeSinceEpoch;
}
sortVerifiedNameRecords = verifiedNameHistory => {
if (Array.isArray(verifiedNameHistory)) {
return [...verifiedNameHistory].sort(this.sortDates);
}
return [];
}
handleEditableFieldChange = (name, value) => {
this.props.updateDraft(name, value);
}
};
handleSubmit = (formId, values) => {
this.props.saveSettings(formId, values);
}
};
handleSubmitProfileName = (formId, values) => {
if (Object.keys(this.props.drafts).includes('useVerifiedNameForCerts')) {
@@ -195,7 +182,7 @@ class AccountSettingsPage extends React.Component {
} else {
this.props.saveSettings(formId, values);
}
}
};
isEditable(fieldName) {
return !this.props.staticFields.includes(fieldName);
@@ -280,7 +267,7 @@ class AccountSettingsPage extends React.Component {
}
return this.props.intl.formatMessage(messages[messageString]);
}
};
renderVerifiedNameSuccessMessage = (verifiedName, created) => {
const dateValue = new Date(created).valueOf();
@@ -295,7 +282,7 @@ class AccountSettingsPage extends React.Component {
body={this.props.intl.formatMessage(messages['account.settings.field.name.verified.success.message'])}
/>
);
}
};
renderVerifiedNameFailureMessage = (verifiedName, created) => {
const dateValue = new Date(created).valueOf();
@@ -326,7 +313,7 @@ class AccountSettingsPage extends React.Component {
}
/>
);
}
};
renderVerifiedNameSubmittedMessage = (willCertNameChange) => (
<Alert
@@ -344,7 +331,7 @@ class AccountSettingsPage extends React.Component {
}
</p>
</Alert>
)
);
renderVerifiedNameMessage = verifiedNameRecord => {
const {
@@ -385,7 +372,7 @@ class AccountSettingsPage extends React.Component {
default:
return null;
}
}
};
renderVerifiedNameIcon = (status) => {
switch (status) {
@@ -396,7 +383,7 @@ class AccountSettingsPage extends React.Component {
default:
return null;
}
}
};
renderVerifiedNameHelpText = (status, proctoredExamId) => {
let messageStr = 'account.settings.field.name.verified.help.text';
@@ -421,7 +408,7 @@ class AccountSettingsPage extends React.Component {
}
return this.props.intl.formatMessage(messages[messageStr]);
}
};
renderEmptyStaticFieldMessage() {
if (this.isManagedProfile()) {
@@ -494,15 +481,39 @@ class AccountSettingsPage extends React.Component {
);
const hasLinkedTPA = findIndex(this.props.tpaProviders, provider => provider.connected) >= 0;
// if user is under 13 and does not have cookie set
const shouldUpdateDOB = (
getConfig().ENABLE_COPPA_COMPLIANCE
&& getConfig().ENABLE_DOB_UPDATE
&& this.props.formValues.year_of_birth.toString() >= COPPA_COMPLIANCE_YEAR.toString()
&& !localStorage.getItem('submittedDOB')
);
return (
<>
<div className="account-section" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
{ shouldUpdateDOB
&& (
<DOBModal
{...editableFieldProps}
/>
)}
<div className="account-section pt-3 mb-5" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
{
this.props.mostRecentVerifiedName
&& this.renderVerifiedNameMessage(this.props.mostRecentVerifiedName)
}
{localStorage.getItem('submittedDOB')
&& (
<OneTimeDismissibleAlert
id="updated-dob"
variant="success"
icon={CheckCircle}
header={this.props.intl.formatMessage(messages['account.settings.field.dob.form.success'])}
body=""
/>
)}
<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>
@@ -641,8 +652,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>
@@ -684,8 +695,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>
@@ -721,8 +732,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>
@@ -752,8 +763,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'],
@@ -763,7 +774,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}

View File

@@ -266,6 +266,46 @@ const messages = defineMessages({
defaultMessage: 'Select a year of birth',
description: 'Option for empty value on account settings year of birth field.',
},
'account.settings.field.dob.month': {
id: 'account.settings.field.dob.month',
defaultMessage: 'Month',
description: 'Label for account settings month of birth field.',
},
'account.settings.field.dob.year': {
id: 'account.settings.field.dob.year',
defaultMessage: 'Year',
description: 'Label for account settings year of birth field.',
},
'account.settings.field.dob.form.button': {
id: 'account.settings.field.dob.form.button',
defaultMessage: 'Please confirm your date of birth',
description: 'Message to prompt user to enter dob',
},
'account.settings.field.dob.form.title': {
id: 'account.settings.field.dob.form.title',
defaultMessage: 'Confirm your date of birth',
description: 'Title of DOB form',
},
'account.settings.field.dob.form.help.text': {
id: 'account.settings.field.dob.form.help.text',
defaultMessage: 'We ask for birth date information to help us comply with our legal obligations.',
description: 'Help text for DOB form',
},
'account.settings.field.dob.form.success': {
id: 'account.settings.field.dob.form.success',
defaultMessage: 'Thank you for entering your birth date information.',
description: 'Title of banner when date of birth is successfully entered',
},
'account.settings.field.month_of_birth.options.empty': {
id: 'account.settings.field.month_of_birth.options.empty',
defaultMessage: 'Select a month of birth',
description: 'Option for empty value on account settings month of birth field.',
},
'account.settingsfield.dob.error.general': {
id: 'account.settingsfield.dob.error.general',
defaultMessage: 'A technical error occurred. Please try again.',
description: 'Generic error message.',
},
'account.settings.field.country': {
id: 'account.settings.field.country',
defaultMessage: 'Country',

View File

@@ -0,0 +1,148 @@
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
} from '@edx/paragon';
import React, { useCallback, useEffect } from 'react';
import { connect, useDispatch } from 'react-redux';
import messages from './AccountSettingsPage.messages';
import { YEAR_OF_BIRTH_OPTIONS } from './data/constants';
import { editableFieldSelector } from './data/selectors';
import { saveSettingsReset } from './data/actions';
function DOBModal(props) {
const {
saveState,
error,
onSubmit,
intl,
} = props;
const dispatch = useDispatch();
// eslint-disable-next-line no-unused-vars
const [isOpen, open, close, toggle] = useToggle(true, {});
const handleSubmit = (e) => {
e.preventDefault();
const month = e.target.month.value;
const year = e.target.year.value;
const data = month !== '' && year !== '' ? [{ field_name: 'DOB', field_value: `${year}-${month}` }] : [];
onSubmit('extended_profile', data);
};
const handleComplete = useCallback(() => {
localStorage.setItem('submittedDOB', 'true');
close();
dispatch(saveSettingsReset());
}, [dispatch, close]);
const handleClose = useCallback(() => {
close();
dispatch(saveSettingsReset());
}, [dispatch, close]);
function renderErrors() {
if (saveState === 'error' || error) {
return (
<Form.Control.Feedback type="invalid" key="general-error">
{intl.formatMessage(messages['account.settingsfield.dob.error.general'])}
</Form.Control.Feedback>
);
}
return null;
}
useEffect(() => {
if (saveState === 'complete' && isOpen) {
handleComplete();
}
}, [handleComplete, saveState, isOpen]);
return (
<>
<Button variant="primary" onClick={open}>
{intl.formatMessage(messages['account.settings.field.dob.form.button'])}
</Button>
<ModalDialog
title={intl.formatMessage(messages['account.settings.field.dob.form.title'])}
isOpen={isOpen}
onClose={handleClose}
hasCloseButton={false}
variant="default"
>
<form onSubmit={handleSubmit}>
<ModalDialog.Header>
<ModalDialog.Title>
{intl.formatMessage(messages['account.settings.field.dob.form.title'])}
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body className="overflow-hidden" style={{ padding: '1.5rem' }}>
<p>{intl.formatMessage(messages['account.settings.field.dob.form.help.text'])}</p>
<Form.Group>
<Form.Label>
{intl.formatMessage(messages['account.settings.field.dob.month'])}
</Form.Label>
<Form.Control
as="select"
name="month"
>
{[...Array(12).keys()].map(month => (
<option key={month + 1} value={month + 1}>{month + 1}</option>
))}
</Form.Control>
</Form.Group>
<Form.Group>
<Form.Label>
{intl.formatMessage(messages['account.settings.field.dob.year'])}
</Form.Label>
<Form.Control
as="select"
name="year"
>
{YEAR_OF_BIRTH_OPTIONS.map(year => (
<option key={year.value} value={year.value}>{year.label}</option>
))}
</Form.Control>
</Form.Group>
{renderErrors()}
</ModalDialog.Body>
<ModalDialog.Footer>
<ActionRow>
<ModalDialog.CloseButton variant="tertiary">
Cancel
</ModalDialog.CloseButton>
<StatefulButton
type="submit"
state={saveState}
labels={{
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
}}
disabledStates={[]}
/>
</ActionRow>
</ModalDialog.Footer>
</form>
</ModalDialog>
</>
);
}
DOBModal.propTypes = {
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
error: PropTypes.string,
onSubmit: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};
DOBModal.defaultProps = {
saveState: undefined,
error: undefined,
};
export default connect(editableFieldSelector)(injectIntl(DOBModal));

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

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

@@ -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

@@ -29,17 +29,12 @@ function CertificatePreference({
saveState,
useVerifiedNameForCerts,
}) {
if (!originalVerifiedName) {
// If the user doesn't have an approved verified name, do not display this component
return null;
}
const dispatch = useDispatch();
const [checked, setChecked] = useState(false);
const [modalIsOpen, setModalIsOpen] = useState(false);
const formId = 'useVerifiedNameForCerts';
function handleCheckboxChange() {
const handleCheckboxChange = () => {
if (!checked) {
if (fieldName === 'verified_name') {
dispatch(updateDraft(formId, true));
@@ -49,22 +44,22 @@ function CertificatePreference({
} else {
setModalIsOpen(true);
}
}
};
function handleCancel() {
const handleCancel = () => {
setModalIsOpen(false);
dispatch(resetDrafts());
}
};
function handleModalChange(e) {
const handleModalChange = (e) => {
if (e.target.value === 'fullName') {
dispatch(updateDraft(formId, false));
} else {
dispatch(updateDraft(formId, true));
}
}
};
function handleSubmit(e) {
const handleSubmit = (e) => {
e.preventDefault();
if (saveState === 'pending') {
@@ -72,24 +67,30 @@ function CertificatePreference({
}
dispatch(saveSettings(formId, useVerifiedNameForCerts));
}
};
useEffect(() => {
if (fieldName === 'verified_name') {
setChecked(useVerifiedNameForCerts);
} else {
setChecked(!useVerifiedNameForCerts);
if (originalVerifiedName) {
if (fieldName === 'verified_name') {
setChecked(useVerifiedNameForCerts);
} else {
setChecked(!useVerifiedNameForCerts);
}
}
}, [useVerifiedNameForCerts]);
}, [originalVerifiedName, fieldName, useVerifiedNameForCerts]);
useEffect(() => {
if (modalIsOpen && saveState === 'complete') {
setModalIsOpen(false);
dispatch(closeForm(fieldName));
if (originalVerifiedName) {
if (modalIsOpen && saveState === 'complete') {
setModalIsOpen(false);
dispatch(closeForm(fieldName));
}
}
}, [modalIsOpen, saveState]);
}, [dispatch, originalVerifiedName, fieldName, modalIsOpen, saveState]);
return (
// If the user doesn't have an approved verified name, do not display this component
return originalVerifiedName ? (
<>
<Form.Checkbox className="mt-1 mb-4" checked={checked} onChange={handleCheckboxChange}>
{intl.formatMessage(messages['account.settings.field.name.checkbox.certificate.select'])}
@@ -150,7 +151,7 @@ function CertificatePreference({
</Form>
</ModalDialog>
</>
);
) : null;
}
CertificatePreference.propTypes = {

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-import-assign */
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

View File

@@ -17,27 +17,28 @@ import LogoSVG from '../../logo.svg';
import { fetchSettings } from '../data/actions';
import { coachingConsentPageSelector } from '../data/selectors';
const Logo = ({ src, alt, ...attributes }) => (
<>
<img src={src} alt={alt} {...attributes} />
</>
);
function Logo({ src, alt, ...attributes }) {
return <img src={src} alt={alt} {...attributes} />;
}
const SuccessMessage = props => (
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
<div className="h3">{props.header}</div>
<div>{props.message}</div>
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
{props.continue}
</Hyperlink>
</div>
);
function SuccessMessage(props) {
return (
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
<div className="h3">{props.header}</div>
<div>{props.message}</div>
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
{props.continue}
</Hyperlink>
</div>
);
}
const AutoRedirect = (props) => {
function AutoRedirect(props) {
window.location.href = props.redirectUrl;
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
};
}
const VIEWS = {
NOT_LOADED: 'NOT_LOADED',
@@ -71,6 +72,23 @@ class CoachingConsent extends React.Component {
this.props.fetchSettings();
}
handleSubmit(e) {
e.preventDefault();
const fullName = e.target.fullName.value;
const phoneNumber = e.target.phoneNumber.value;
const body = {
coaching_consent: true,
consent_form_seen: true,
phone_number: phoneNumber,
full_name: fullName,
};
this.setState({
formErrors: {},
formSubmitted: true,
declineSubmitted: false,
}, () => this.patchUsingCoachingConsentForm(body));
}
sanitizeForwardingUrl(url) {
// Redirect to root of MFE if invalid next param is sent
return url && url.startsWith(getConfig().LMS_BASE_URL) ? url : `${getConfig().LMS_BASE_URL}/dashboard/`;
@@ -99,23 +117,6 @@ class CoachingConsent extends React.Component {
}
}
handleSubmit(e) {
e.preventDefault();
const fullName = e.target.fullName.value;
const phoneNumber = e.target.phoneNumber.value;
const body = {
coaching_consent: true,
consent_form_seen: true,
phone_number: phoneNumber,
full_name: fullName,
};
this.setState({
formErrors: {},
formSubmitted: true,
declineSubmitted: false,
}, () => this.patchUsingCoachingConsentForm(body));
}
declineCoaching(e) {
e.preventDefault();
const body = {
@@ -160,6 +161,7 @@ class CoachingConsent extends React.Component {
case VIEWS.DECLINED:
return <AutoRedirect redirectUrl={this.state.redirectUrl} />;
default:
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
}
@@ -253,12 +255,12 @@ CoachingConsent.propTypes = {
}),
}).isRequired,
formErrors: PropTypes.shape({
coaching: PropTypes.object,
coaching: PropTypes.shape({}),
}).isRequired,
confirmationValues: PropTypes.shape({
coaching: PropTypes.object,
name: PropTypes.object,
phone_number: PropTypes.object,
coaching: PropTypes.shape({}),
name: PropTypes.shape({}),
phone_number: PropTypes.shape({}),
}).isRequired,
fetchSettings: PropTypes.func.isRequired,
profileDataManager: PropTypes.string,

View File

@@ -8,82 +8,86 @@ import PropTypes from 'prop-types';
import Alert from '../Alert';
import messages from './CoachingConsent.messages';
const ErrorMessage = props => (
<div className="alert-warning mb-2">{props.message}</div>
);
function ErrorMessage(props) {
return <div className="alert-warning mb-2">{props.message}</div>;
}
const ManagedProfileAlert = ({ profileDataManager }) => (
<Alert className="alert alert-primary" role="alert">
<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"
values={{
managerTitle: <b>{profileDataManager}</b>,
}}
/>
</Alert>
);
const CoachingForm = props => (
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
<h2 className="h2">
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
</h2>
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
<div>
<form onSubmit={props.onSubmit}>
<div className="py-3">
{!!props.profileDataManager && (
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
)}
<ErrorMessage message={props.formErrors.full_name} />
<label className="h6" htmlFor="fullName">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
</label>
<Input
type="text"
name="full-name"
id="fullName"
disabled={!!props.profileDataManager}
defaultValue={props.formValues.name}
/>
</div>
<div className="py-3">
<ErrorMessage message={props.formErrors.phone_number} />
<label className="h6" htmlFor="phoneNumber">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
</label>
<Input
type="text"
name="phone_number"
id="phoneNumber"
defaultValue={props.formValues.phone_number}
/>
</div>
<div className=" py-3">
<p className="small font-italic">
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
</p>
</div>
<ErrorMessage message={props.formErrors.coaching} />
<div className="d-flex flex-column align-items-center">
<Button variant="outline-primary" className="w-100" type="submit">
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
</Button>
</div>
<div className="mt-3">
<Hyperlink
className="mt-3 text-dark btn-link small"
destination={props.redirectUrl}
onClick={props.declineCoaching}
>
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
</Hyperlink>
</div>
</form>
function ManagedProfileAlert({ profileDataManager }) {
return (
<Alert className="alert alert-primary" role="alert">
<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"
values={{
managerTitle: <b>{profileDataManager}</b>,
}}
/>
</Alert>
);
}
function CoachingForm(props) {
return (
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
<h2 className="h2">
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
</h2>
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
<div>
<form onSubmit={props.onSubmit}>
<div className="py-3">
{!!props.profileDataManager && (
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
)}
<ErrorMessage message={props.formErrors.full_name} />
<label className="h6" htmlFor="fullName">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
</label>
<Input
type="text"
name="full-name"
id="fullName"
disabled={!!props.profileDataManager}
defaultValue={props.formValues.name}
/>
</div>
<div className="py-3">
<ErrorMessage message={props.formErrors.phone_number} />
<label className="h6" htmlFor="phoneNumber">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
</label>
<Input
type="text"
name="phone_number"
id="phoneNumber"
defaultValue={props.formValues.phone_number}
/>
</div>
<div className=" py-3">
<p className="small font-italic">
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
</p>
</div>
<ErrorMessage message={props.formErrors.coaching} />
<div className="d-flex flex-column align-items-center">
<Button variant="outline-primary" className="w-100" type="submit">
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
</Button>
</div>
<div className="mt-3">
<Hyperlink
className="mt-3 text-dark btn-link small"
destination={props.redirectUrl}
onClick={props.declineCoaching}
>
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
</Hyperlink>
</div>
</form>
</div>
</div>
</div>
);
);
}
CoachingForm.defaultProps = {
formErrors: {

View File

@@ -8,66 +8,69 @@ import { editableFieldSelector } from '../data/selectors';
import { saveSettings, updateDraft, saveMultipleSettings } from '../data/actions';
import EditableField from '../EditableField';
const CoachingToggle = props => (
<>
<EditableField
name="phone_number"
type="text"
value={props.phone_number}
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
onChange={props.updateDraft}
onSubmit={() => {
const { coaching } = props;
if (coaching.coaching_consent === true) {
return props.saveMultipleSettings([
{
formId: 'coaching',
commitValues: {
...coaching,
phone_number: props.phone_number,
function CoachingToggle(props) {
return (
<>
<EditableField
name="phone_number"
type="text"
value={props.phone_number}
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
onChange={props.updateDraft}
onSubmit={() => {
const { coaching } = props;
if (coaching.coaching_consent === true) {
return props.saveMultipleSettings([
{
formId: 'coaching',
commitValues: {
...coaching,
phone_number: props.phone_number,
},
},
},
{
formId: 'phone_number',
commitValues: props.phone_number,
},
], 'phone_number');
}
return props.saveSettings('phone_number', props.phone_number);
}}
/>
<ValidationFormGroup
for="coachingConsent"
helpText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
invalid={!!props.error}
invalidMessage={props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
className="custom-control custom-switch"
>
<Input
name={props.name}
className="custom-control-input"
disabled={props.saveState === 'pending'}
type="checkbox"
id="coachingConsent"
checked={props.coaching.coaching_consent}
value={props.coaching.coaching_consent}
onChange={async (e) => {
const { name } = e.target;
// eslint-disable-next-line camelcase
const { user, eligible_for_coaching } = props.coaching;
const value = {
user,
eligible_for_coaching,
coaching_consent: e.target.checked,
};
props.saveSettings(name, value);
{
formId: 'phone_number',
commitValues: props.phone_number,
},
], 'phone_number');
}
return props.saveSettings('phone_number', props.phone_number);
}}
/>
<label className="custom-control-label" htmlFor="coachingConsent">{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}</label>
</ValidationFormGroup>
</>
);
<ValidationFormGroup
for="coachingConsent"
helpText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
invalid={!!props.error}
invalidMessage={props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
className="custom-control custom-switch"
>
<Input
name={props.name}
className="custom-control-input"
disabled={props.saveState === 'pending'}
type="checkbox"
id="coachingConsent"
checked={props.coaching.coaching_consent}
value={props.coaching.coaching_consent}
onChange={async (e) => {
const { name } = e.target;
// eslint-disable-next-line camelcase
const { user, eligible_for_coaching } = props.coaching;
const value = {
user,
// eslint-disable-next-line camelcase
eligible_for_coaching,
coaching_consent: e.target.checked,
};
props.saveSettings(name, value);
}}
/>
<label className="custom-control-label" htmlFor="coachingConsent">{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}</label>
</ValidationFormGroup>
</>
);
}
CoachingToggle.defaultProps = {
phone_number: '',

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';

View File

@@ -34,13 +34,11 @@ exports[`CoachingConsent disables name field on enterprise user 1`] = `
>
<div />
<div>
<span>
Your name is managed by
<b>
test person
</b>
. Contact your administrator for help.
</span>
Your name is managed by
<b>
test person
</b>
. Contact your administrator for help.
</div>
</div>
<div

View File

@@ -10,6 +10,11 @@ export const YEAR_OF_BIRTH_OPTIONS = (() => {
return years.reverse();
})();
export const COPPA_COMPLIANCE_YEAR = (() => {
const currentYear = new Date().getFullYear();
return currentYear - 13;
})();
export const EDUCATION_LEVELS = [
'',
'p',

View File

@@ -41,7 +41,7 @@ export const defaultState = {
verifiedNameHistory: {},
};
const reducer = (state = defaultState, action) => {
const reducer = (state = defaultState, action = {}) => {
let dispatcherIsOpenForm;
switch (action.type) {

View File

@@ -179,7 +179,7 @@ const formValuesSelector = createSelector(
const transformTimeZonesToOptions = timeZoneArr => timeZoneArr
.map(({ time_zone, description }) => ({ // eslint-disable-line camelcase
value: time_zone, label: description,
value: time_zone, label: description, // eslint-disable-line camelcase
}));
const timeZonesSelector = createSelector(

View File

@@ -12,7 +12,7 @@ import messages from './messages';
// Components
import Alert from '../Alert';
const BeforeProceedingBanner = (props) => {
function BeforeProceedingBanner(props) {
const { instructionMessageId, intl, supportArticleUrl } = props;
return (
@@ -35,7 +35,7 @@ const BeforeProceedingBanner = (props) => {
/>
</Alert>
);
};
}
BeforeProceedingBanner.propTypes = {
instructionMessageId: PropTypes.string.isRequired,

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>
@@ -92,8 +92,10 @@ export class DeleteAccount extends React.Component {
<PrintingInstructions />
</p>
<p className="text-danger h6">
{intl.formatMessage(messages['account.settings.delete.account.text.warning'],
{ siteName: getConfig().SITE_NAME })}
{intl.formatMessage(
messages['account.settings.delete.account.text.warning'],
{ siteName: getConfig().SITE_NAME },
)}
</p>
<p>
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">

View File

@@ -1,10 +1,15 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import renderer from 'react-test-renderer';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
// Testing the modals separately, they just clutter up the snapshots if included here.
jest.mock('./ConfirmationModal');
jest.mock('./SuccessModal');
jest.mock('./ConfirmationModal', () => function () {
return <></>;
});
jest.mock('./SuccessModal', () => function () {
return <></>;
});
import { DeleteAccount } from './DeleteAccount'; // eslint-disable-line import/first
@@ -37,6 +42,7 @@ describe('DeleteAccount', () => {
</IntlProvider>
))
.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@@ -5,7 +5,7 @@ import { Hyperlink } from '@edx/paragon';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
const PrintingInstructions = (props) => {
function PrintingInstructions(props) {
const actionLink = (
<Hyperlink
// TODO: What would a generic version of this link look like? Should
@@ -38,7 +38,7 @@ const PrintingInstructions = (props) => {
values={{ actionLink }}
/>
);
};
}
PrintingInstructions.propTypes = {
intl: intlShape.isRequired,

View File

@@ -5,7 +5,7 @@ import { Modal } from '@edx/paragon';
import messages from './messages';
export const SuccessModal = (props) => {
export function SuccessModal(props) {
const { status, intl, onClose } = props;
return (
<Modal
@@ -23,7 +23,7 @@ export const SuccessModal = (props) => {
onClose={onClose}
/>
);
};
}
SuccessModal.propTypes = {
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),

View File

@@ -86,9 +86,7 @@ Array [
If you proceed, you will be unable to use this account to take courses on localhost.
</p>
<p>
<span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</p>
</div>
</div>
@@ -282,9 +280,7 @@ Array [
If you proceed, you will be unable to use this account to take courses on localhost.
</p>
<p>
<span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</p>
</div>
</div>
@@ -386,16 +382,14 @@ Array [
"width": "1px",
}
}
tabIndex={0}
tabIndex={-1}
/>
<div
data-focus-lock-disabled={false}
data-focus-lock-disabled="disabled"
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onScrollCapture={[Function]}
onTouchMoveCapture={[Function]}
onTouchStart={[Function]}
onWheelCapture={[Function]}
>
<div
@@ -445,9 +439,7 @@ Array [
If you proceed, you will be unable to use this account to take courses on localhost.
</p>
<p>
<span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</p>
</div>
</div>
@@ -513,7 +505,7 @@ Array [
"width": "1px",
}
}
tabIndex={0}
tabIndex={-1}
/>
</div>
</div>,

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>
@@ -17,9 +17,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
Once your account is deleted, you cannot use it to take courses on localhost.
</p>
<p>
<span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</p>
<p
className="text-danger h6"
@@ -52,7 +50,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>
@@ -66,9 +64,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
Once your account is deleted, you cannot use it to take courses on localhost.
</p>
<p>
<span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</p>
<p
className="text-danger h6"
@@ -118,18 +114,16 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
</svg>
</div>
<div>
<span>
Before proceeding, please
<a
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"
>
activate your account
</a>
.
</span>
Before proceeding, please
<a
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"
>
activate your account
</a>
.
</div>
</div>
</div>
@@ -138,7 +132,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>
@@ -152,9 +146,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
Once your account is deleted, you cannot use it to take courses on localhost.
</p>
<p>
<span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</span>
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
</p>
<p
className="text-danger h6"
@@ -204,18 +196,16 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
</svg>
</div>
<div>
<span>
Before proceeding, please
<a
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/articles/207206067"
onClick={[Function]}
target="_self"
>
unlink all social media accounts
</a>
.
</span>
Before proceeding, please
<a
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/articles/207206067"
onClick={[Function]}
target="_self"
>
unlink all social media accounts
</a>
.
</div>
</div>
</div>

View File

@@ -417,16 +417,14 @@ Array [
"width": "1px",
}
}
tabIndex={0}
tabIndex={-1}
/>
<div
data-focus-lock-disabled={false}
data-focus-lock-disabled="disabled"
onBlur={[Function]}
onFocus={[Function]}
onMouseDown={[Function]}
onScrollCapture={[Function]}
onTouchMoveCapture={[Function]}
onTouchStart={[Function]}
onWheelCapture={[Function]}
>
<div
@@ -480,7 +478,7 @@ Array [
"width": "1px",
}
}
tabIndex={0}
tabIndex={-1}
/>
</div>
</div>,

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './DeleteAccount';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { CheckBox } from '@edx/paragon';
import { DECLINED } from '../data/constants';
const Checkboxes = (props) => {
function Checkboxes(props) {
const {
id,
options,
@@ -14,7 +14,7 @@ const Checkboxes = (props) => {
const [selected, setSelected] = useState(values);
useEffect(() => {
onChange(id, selected);
}, [selected]);
}, [id, onChange, selected]);
const handleToggle = (value, option) => {
// If the user checked 'declined', uncheck all other options
@@ -59,7 +59,7 @@ const Checkboxes = (props) => {
{renderCheckboxes()}
</div>
);
};
}
Checkboxes.propTypes = {
id: PropTypes.string.isRequired,

View File

@@ -75,7 +75,7 @@ class DemographicsSection extends React.Component {
const matchingOption = demographicsEthnicityOptions.filter(option => option.value === e)[0];
return matchingOption && matchingOption.label;
}).join(', ');
}
};
handleEditableFieldChange = (name, value) => {
this.props.updateDraft(name, value);
@@ -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>
@@ -317,7 +317,7 @@ DemographicsSection.propTypes = {
intl: intlShape.isRequired,
formValues: PropTypes.shape({
demographics_gender: PropTypes.string,
demographics_user_ethnicity: PropTypes.array,
demographics_user_ethnicity: PropTypes.shape([]),
demographics_income: PropTypes.string,
demographics_military_history: PropTypes.string,
demographics_learner_education_level: PropTypes.string,
@@ -327,11 +327,11 @@ DemographicsSection.propTypes = {
demographics_future_work_sector: PropTypes.string,
demographics_work_status_description: PropTypes.string,
demographics_gender_description: PropTypes.string,
demographicsOptions: PropTypes.object,
demographicsOptions: PropTypes.shape({}),
}).isRequired,
drafts: PropTypes.shape({
demographics_gender: PropTypes.string,
demographics_user_ethnicity: PropTypes.array,
demographics_user_ethnicity: PropTypes.shape([]),
demographics_income: PropTypes.string,
demographics_military_history: PropTypes.string,
demographics_learner_education_level: PropTypes.string,
@@ -341,7 +341,7 @@ DemographicsSection.propTypes = {
demographics_future_work_sector: PropTypes.string,
demographics_work_status_description: PropTypes.string,
demographics_gender_description: PropTypes.string,
demographicsOptions: PropTypes.object,
demographicsOptions: PropTypes.shape({}),
}).isRequired,
formErrors: PropTypes.shape({
demographicsError: PropTypes.string,

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-import-assign */
import * as auth from '@edx/frontend-platform/auth';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';

View File

@@ -2,11 +2,11 @@
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>
@@ -648,11 +648,11 @@ 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>
@@ -710,9 +710,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
>
<div />
<div>
<span>
An error occurred attempting to retrieve or save your account information. Please try again later.
</span>
An error occurred attempting to retrieve or save your account information. Please try again later.
</div>
</div>
</div>
@@ -1308,11 +1306,11 @@ 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>
@@ -1370,9 +1368,7 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
>
<div />
<div>
<span>
An error occurred attempting to retrieve or save your account information. Please try again later.
</span>
An error occurred attempting to retrieve or save your account information. Please try again later.
</div>
</div>
</div>
@@ -1381,11 +1377,11 @@ 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>
@@ -2020,11 +2016,11 @@ 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>
@@ -2659,11 +2655,11 @@ 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>
@@ -3305,11 +3301,11 @@ 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>

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './AccountSettingsPage';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { connect, useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
@@ -34,22 +34,22 @@ function NameChangeModal({
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
const [confirmedWarning, setConfirmedWarning] = useState(false);
function resetLocalState() {
const resetLocalState = useCallback(() => {
setConfirmedWarning(false);
dispatch(requestNameChangeReset());
}
}, [dispatch]);
function handleChange(e) {
const handleChange = (e) => {
setVerifiedNameInput(e.target.value);
}
};
function handleClose() {
const handleClose = useCallback(() => {
resetLocalState();
dispatch(closeForm(targetFormId));
dispatch(saveSettingsReset());
}
}, [dispatch, resetLocalState, targetFormId]);
function handleSubmit(e) {
const handleSubmit = (e) => {
e.preventDefault();
if (saveState === 'pending') {
@@ -64,14 +64,14 @@ function NameChangeModal({
const draftProfileName = targetFormId === 'name' ? formValues.name : null;
dispatch(requestNameChange(username, draftProfileName, verifiedNameInput));
}
}
};
useEffect(() => {
if (saveState === 'complete') {
handleClose();
push(`/id-verification?next=${encodeURIComponent('account/settings')}`);
}
}, [saveState]);
}, [handleClose, push, saveState]);
function renderErrors() {
if (Object.keys(errors).length > 0) {

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './NameChange';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-import-assign */
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

View File

@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -7,12 +8,12 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import Alert from '../Alert';
const ConfirmationAlert = (props) => {
function ConfirmationAlert(props) {
const { email } = props;
const technicalSupportLink = (
<Hyperlink
destination="https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-"
destination={getConfig().PASSWORD_RESET_SUPPORT_LINK}
>
<FormattedMessage
id="account.settings.editable.field.password.reset.button.confirmation.support.link"
@@ -38,7 +39,7 @@ const ConfirmationAlert = (props) => {
/>
</Alert>
);
};
}
ConfirmationAlert.propTypes = {
email: PropTypes.string.isRequired,

View File

@@ -5,17 +5,19 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import Alert from '../Alert';
const RequestInProgressAlert = () => (
<Alert
className="alert-warning mt-n2"
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
>
<FormattedMessage
id="account.settings.editable.field.password.reset.button.forbidden"
defaultMessage="Your previous request is in progress, please try again in few moments."
description="A message displayed when a previous password reset request is still in progress."
/>
</Alert>
);
function RequestInProgressAlert() {
return (
<Alert
className="alert-warning mt-n2"
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
>
<FormattedMessage
id="account.settings.editable.field.password.reset.button.forbidden"
defaultMessage="Your previous request is in progress, please try again in few moments."
description="A message displayed when a previous password reset request is still in progress."
/>
</Alert>
);
}
export default RequestInProgressAlert;

View File

@@ -9,7 +9,7 @@ import messages from './messages';
import ConfirmationAlert from './ConfirmationAlert';
import RequestInProgressAlert from './RequestInProgressAlert';
const ResetPassword = (props) => {
function ResetPassword(props) {
const { email, intl, status } = props;
return (
<div className="form-group">
@@ -47,7 +47,7 @@ const ResetPassword = (props) => {
{status === 'forbidden' ? <RequestInProgressAlert /> : null}
</div>
);
};
}
ResetPassword.propTypes = {
email: PropTypes.string,

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './ResetPassword';
export { default as reducer } from './data/reducers';
export { RESET_PASSWORD } from './data/actions';

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

@@ -16,7 +16,7 @@ class ThirdPartyAuth extends Component {
}
const disconnectUrl = e.currentTarget.getAttribute('data-disconnect-url');
this.props.disconnectAuth(disconnectUrl, providerId);
}
};
renderUnconnectedProvider(url, name) {
return (

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './ThirdPartyAuth';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';

23
src/head/Head.jsx Normal file
View File

@@ -0,0 +1,23 @@
import React from 'react';
import { Helmet } from 'react-helmet';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
function Head({ intl }) {
return (
<Helmet>
<title>
{intl.formatMessage(messages['account.page.title'], { siteName: getConfig().SITE_NAME })}
</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
);
}
Head.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(Head);

17
src/head/Head.test.jsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { Helmet } from 'react-helmet';
import { mount } from 'enzyme';
import { getConfig } from '@edx/frontend-platform';
import Head from './Head';
describe('Head', () => {
const props = {};
it('should match render title tag and fivicon with the site configuration values', () => {
mount(<IntlProvider locale="en"><Head {...props} /></IntlProvider>);
const helmet = Helmet.peek();
expect(helmet.title).toEqual(`Account | ${getConfig().SITE_NAME}`);
expect(helmet.linkTags[0].rel).toEqual('shortcut icon');
expect(helmet.linkTags[0].href).toEqual(getConfig().FAVICON_URL);
});
});

11
src/head/messages.js Normal file
View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'account.page.title': {
id: 'account.page.title',
defaultMessage: 'Account | {siteName}',
description: 'Title tag',
},
});
export default messages;

View File

@@ -1,79 +1,87 @@
{
"account.settings.message.duplicate.tpa.provider": "The {provider} account you selected is already linked to another {siteName} account.",
"account.settings.message.managed.settings": "تتم إدارة إعدادات الملف الشخصي بواسطة {ManagerTitle}. اتصل بالمسؤول أو {support} للحصول على المساعدة.",
"account.settings.message.duplicate.tpa.provider": "حساب {provider} الذي حددته موصول من قبل بحساب آخر على {siteName}.",
"account.settings.message.managed.settings": "إعدادات ملفك الشخصي يديرها {ManagerTitle}. اتصل بمديرك أو ب{support} للحصول على المساعدة.",
"account.settings.message.managed.settings.support": "الدعم",
"account.settings.page.heading": "إعدادات الحساب",
"account.settings.loading.message": "جاري التحميل...",
"account.settings.loading.message": "التحميل جارٍ...",
"account.settings.loading.error": "خطأ: {error}",
"account.settings.banner.beta.language": "لقد قمت بتعيين لغتك الى {beta_language}، والتي لم تتم ترجمتها بالكامل حاليًا. يمكنك مساعدتنا في ترجمة هذه اللغة بالكامل من خلال الانضمام إلى مجتمع Transifex وإضافة ترجمات من الإنجليزية للمتعلمين الذين يتحدثون {beta_language}.",
"account.settings.banner.beta.language.action.switch.back": "العودة لـِ {previous_language}",
"account.settings.banner.beta.language.action.help.translate": "المساعدة في الترجمة إلى {beta_language}",
"account.settings.banner.beta.language": "لقد قمت بضبط لغتك على {beta_language}، وهي حاليا غير مترجمة بالكامل. يمكنك مساعدتنا في إتمام ترجمة هذه اللغة من خلال الانضمام إلى مجتمع Transifex وإضافة ترجمات من الإنجليزية للمتعلمين الذين يتحدثون {beta_language}.",
"account.settings.banner.beta.language.action.switch.back": "العودة إلى {previous_language}",
"account.settings.banner.beta.language.action.help.translate": "ساعد في الترجمة إلى {beta_language}",
"account.settings.section.account.information": "معلومات الحساب",
"account.settings.section.account.information.description": "تتضمن هذه الإعدادات معلومات أساسية عن حسابك.",
"account.settings.section.profile.information": "معلومات الملف الشخصي",
"account.settings.section.demographics.information": "معلومات اختيارية",
"account.settings.section.site.preferences": "تفضيلات الموقع",
"account.settings.section.linked.accounts": "الحسابات المرتبطة",
"account.settings.section.linked.accounts.description": "You can link your identity accounts to simplify signing in to {siteName}.",
"account.settings.section.linked.accounts": "الحسابات الموصولة",
"account.settings.section.linked.accounts.description": "يمكنك وصل حسابات هويتك لتبسيط تسجيل دخولك إلى {siteName}.",
"account.settings.field.username": "اسم المستخدم",
"account.settings.field.username.help.text": "The name that identifies you on {siteName}. You cannot change your username.",
"account.settings.field.username.help.text": "الاسم الذي يعرّفك على {siteName}. لا يمكنك تغيير اسم المستخدم الخاص بك.",
"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.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 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.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.",
"account.settings.field.name.verified.failure.message.header": "We were not able to verify your identity.",
"account.settings.field.name.verified.failure.message.help.link": "Learn more about ID verification",
"account.settings.field.name.verified.submitted.message": "Your identity verification request has been submitted and usually takes between 24 and 48 hours to complete.",
"account.settings.field.name.verified.submitted.message.certificate": "When your request is approved, your updated name will appear on all associated certificates and public-facing records.",
"account.settings.field.name.verified.submitted.message.header": "Your name change request is almost complete!",
"account.settings.field.email": "البريد الالكتروني (الدخول)",
"account.settings.field.full.name.empty": "إضافة الاسم",
"account.settings.field.full.name.help.text": "الاسم المستعمل للتحقق من هويتك والذي يظهر في شهاداتك.",
"account.settings.field.full.name.help.text.default": "الاسم الذي يظهر في ملفك الشخصي العامّ.",
"account.settings.field.full.name.help.text.default.certificate": "سيظهر هذا الاسم في شهاداتك و سجلّاتك العامّة.",
"account.settings.field.name.verified": "اسم متحقَّق منه",
"account.settings.field.name.verified.help.text.verified": "تم التحقق من هذا الاسم عن طريق بطاقة تعريف ذات صورة.",
"account.settings.field.name.verified.help.text.verified.proctored": "تم التحقق من هذا الاسم عن طريق المراقبة.",
"account.settings.field.name.verified.help.text.verified.certificate": "تم التحقق من هذا الاسم عن طريق بطاقة هوية ذات صورة، وتم اختياره للظهور في الشهادات والسجلات العامة.",
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "تم التحقق من هذا الاسم من خلال المراقبة، وتم تحديده للظهور في الشهادات والسجلات العامة.",
"account.settings.field.name.verified.help.text.submitted": "تم تسليم طلب التحقق. في العادة يستغرق هذا 48 ساعة أو أقل. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت.",
"account.settings.field.name.verified.help.text.submitted.proctored": "تم تسليم امتحانك المراقَب. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت. رجاءً أعد التفقد بعد 2-5 أيام.",
"account.settings.field.name.verified.help.text.submitted.certificate": "عند نجاح التحقق من الهوية، سيظهر هذا الاسم على شهاداتك و سجلاتك العامة. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت.",
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "بمجرد اجتياز امتحانك المراقب للمراجعة، سيظهر هذا الاسم في شهادتك و سجلاتك العامة. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت.",
"account.settings.field.name.verified.verification.help": "أدخل اسمك كما يظهر في بطاقة تعريف الطالب أو الموظف الخاصة بك، أو بطاقة تعريفك الصادرة عن الحكومة.",
"account.settings.field.full.name.help.text.submitted": "تم تسليم طلب التحقق. يستغرق هذا عادةً 48 ساعة أو أقل. لا يمكن تغيير الاسم الكامل في هذا الوقت.",
"account.settings.field.full.name.help.text.submitted.proctored": "تم تسليم امتحانك المراقب. لا يمكن تغيير الاسم الكامل في هذا الوقت. يرجى التحقق مرة أخرى خلال 2-5 أيام.",
"account.settings.field.full.name.help.text.submitted.certificate": "عند نجاح التحقق من الهوية، سيظهر هذا الاسم على الشهادات والسجلات العامة. لا يمكن تغيير الاسم الكامل في هذا الوقت.",
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "بمجرد اجتياز امتحانك المراقب للمراجعة، سيظهر هذا الاسم في شهاداتك وسجلاتك العامة. لا يمكن تغيير الاسم الكامل في هذا الوقت.",
"account.settings.field.name.verified.success.message": "اكتمل طلب التحقق من هويتك بنجاح. لديك الآن خيار تحديد الاسم الذي تفضل ظهوره على شهاداتك وسجلاتك العامة.",
"account.settings.field.name.verified.success.message.header": "اكتمل طلب تغيير اسمك!",
"account.settings.field.name.verified.failure.message": "لم تنجح آخر محاولاتك للتحقق من هويتك. تم إرجاع إعدادات الحساب ذات الصلة إلى وضعها السابق.",
"account.settings.field.name.verified.failure.message.header": "لم نستطع التحقق من هويتك.",
"account.settings.field.name.verified.failure.message.help.link": "اعرف المزيد عن التحقق من الهوية.",
"account.settings.field.name.verified.submitted.message": "تم تسليم طلب التحقق من هويتك و عادة ما يستغرق إكماله ما بين 24 و 48 ساعة.",
"account.settings.field.name.verified.submitted.message.certificate": "عند الموافقة على طلبك، سيظهر اسمك المحدَّث على جميع الشهادات السجلات العامة ذات الصلة.",
"account.settings.field.name.verified.submitted.message.header": "طلب تغيير اسمك يكاد يكتمل!",
"account.settings.field.email": "البريد الالكتروني (تسجيل الدخول)",
"account.settings.field.email.empty": "إضافة عنوان البريد الإلكتروني",
"account.settings.field.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر فوق الرابط في الرسالة لتحديث عنوان بريدك الإلكتروني.",
"account.settings.field.email.help.text": "You receive messages from {siteName} and course teams at this address.",
"account.settings.field.secondary.email": "عنوان البريد الإلكتروني للاسترداد",
"account.settings.field.secondary.email.empty": "إضافة عنوان البريد الإلكتروني للاسترداد",
"account.settings.field.secondary.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر فوق الرابط في الرسالة لتحديث عنوان بريد الاسترداد الإلكتروني.",
"account.settings.field.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر على الرابط في الرسالة لتحديث عنوان بريدك الإلكتروني.",
"account.settings.field.email.help.text": "على هذا العنوان تصلك الرسائل من {siteName} و من فرق المساقات.",
"account.settings.field.secondary.email": "عنوان بريد الاستعادة الإلكتروني.",
"account.settings.field.secondary.email.empty": "إضافة عنوان بريد إلكتروني لاستعادة حسابك",
"account.settings.field.secondary.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر على الرابط في الرسالة لتحديث عنوان بريد الاستعادة الإلكتروني الخاص بك.",
"account.settings.email.field.confirmation.header": "تعليق عملية التأكيد",
"account.settings.field.dob": "سنة الميلاد",
"account.settings.field.dob.empty": "إضافة سنة الميلاد",
"account.settings.field.year_of_birth.options.empty": "اختر سنة ميلاد",
"account.settings.field.country": "الدولة",
"account.settings.field.year_of_birth.options.empty": "تحديد سنة الميلاد",
"account.settings.field.dob.month": "الشهر",
"account.settings.field.dob.year": "السنة",
"account.settings.field.dob.form.button": "يرجى تأكيد تاريخ ميلادك",
"account.settings.field.dob.form.title": "تأكيد تاريخ ميلادك",
"account.settings.field.dob.form.help.text": "نطلب معلومة تاريخ الميلاد لمساعدتنا على الالتزام بواجباتنا القانونية.",
"account.settings.field.dob.form.success": "شكرا لك على إدخال تاريخ ميلادك.",
"account.settings.field.month_of_birth.options.empty": "تحديد شهر الميلاد",
"account.settingsfield.dob.error.general": "حدث خطأ تقني. رجاءً حاول مجددا.",
"account.settings.field.country": "البلد",
"account.settings.field.country.empty": "إضافة البلد ",
"account.settings.field.country.options.empty": "اختر دولة",
"account.settings.field.state": "الحالة",
"account.settings.field.state.empty": "إضافة منطقة",
"account.settings.field.state.options.empty": "اختر منطقة",
"account.settings.field.country.options.empty": "اختر البلد",
"account.settings.field.state": "المنطقة / الولاية / المحافظة",
"account.settings.field.state.empty": "إضافة المنطقة / الولاية / المحافظة",
"account.settings.field.state.options.empty": "حدد المنطقة",
"account.settings.field.site.language": "لغة الموقع",
"account.settings.field.site.language.help.text": "اللغة المستخدمة في كافة أقسام هذا الموقع. يتوفّر هذا الموقع حاليًا بعدد محدود من اللغات.",
"account.settings.field.education": "المستوى التعليمي",
"account.settings.field.education.empty": "إضافة المستوى التعليمي",
"account.settings.field.education.levels.empty": "اختر المستوى التعليمي",
"account.settings.field.education.levels.empty": "اختر مستوى تعليميًّا",
"account.settings.field.education.levels.p": "دكتوراه",
"account.settings.field.education.levels.m": "ماجستير أو شهادة مهنيّة",
"account.settings.field.education.levels.b": "بكالوريوس",
"account.settings.field.education.levels.a": "زمالة",
"account.settings.field.education.levels.hs": "شهادة الثانوية العامة",
"account.settings.field.education.levels.jhs": "شهادة الثانوية الصغرى/الإعدادية/المرحلة المتوسّطة",
"account.settings.field.education.levels.el": "شهادة المدرسة الابتدائية",
"account.settings.field.education.levels.none": "لا يوجد تعليم رسمي",
"account.settings.field.education.levels.m": "ماجستير / ماستر أو شهادة مهنيّة",
"account.settings.field.education.levels.b": "بكالوريوس / ليسانس",
"account.settings.field.education.levels.a": "درجة الزمالة / دبلوم الدراسات الجامعية",
"account.settings.field.education.levels.hs": "الثانوية العامة / البكالوريا",
"account.settings.field.education.levels.jhs": "المدرسة الإعدادية / المتوسطة",
"account.settings.field.education.levels.el": "المدرسة الابتدائية / الأساسية",
"account.settings.field.education.levels.none": ون تعليم رسمي",
"account.settings.field.education.levels.o": "نوع آخر من التعليم",
"account.settings.field.gender": "الجنس",
"account.settings.field.gender.empty": "إضافة الجنس",
@@ -86,183 +94,184 @@
"account.settings.field.language_proficiencies.options.empty": "اختر لغة",
"account.settings.field.time.zone": "المنطقة الزمنية",
"account.settings.field.time.zone.empty": "ضبط المنطقة الزمنية",
"account.settings.field.time.zone.description": "حدد المنطقة الزمنية لعرض تواريخ المساق. إذا لم تحدد منطقة زمنية، فسيتم عرض تواريخ المساق، بما في ذلك المواعيد النهائية للواجب في المنطقة الزمنية المحلية في المتصفح.",
"account.settings.field.time.zone.default": "الافتراضي (منطقة التوقيت المحلي)",
"account.settings.field.time.zone.description": "حدد المنطقة الزمنية لعرض تواريخ المساقات. إذا لم تحدد منطقة زمنية، فسيتم عرض تواريخ المساق، بما في ذلك المواعيد النهائية للواجبات بالتوقيت المحلّي للمتصفح.",
"account.settings.field.time.zone.default": "الافتراضي (المنطقة الزمنية المحلية)",
"account.settings.field.time.zone.all": "جميع المناطق الزمنية",
"account.settings.field.time.zone.country": "المنطقة الزمنية للدولة",
"account.settings.section.social.media": "روابط منصات التواصل الإجتماعي",
"account.settings.section.social.media.description": "Optionally, link your personal accounts to the social media icons on your {siteName} profile.",
"account.settings.field.time.zone.country": "المناطق الزمنية للبلدان",
"account.settings.section.social.media": "روابط التواصل الإجتماعي",
"account.settings.section.social.media.description": "اختياريًا، اربط حساباتك الشخصية بأيقونات التواصل الاجتماعي في ملفك على {siteName}.",
"account.settings.field.social.platform.name.linkedin": "لينكد إن",
"account.settings.field.social.platform.name.linkedin.empty": "إضافة عنوان ملف لينكد إن ",
"account.settings.jump.nav.delete.account": "احذف حسابي",
"account.settings.field.social.platform.name.linkedin.empty": "إضافة سيرة لينكد إن",
"account.settings.jump.nav.delete.account": "حذف حسابي",
"account.settings.field.social.platform.name.twitter": "تويتر",
"account.settings.field.social.platform.name.twitter.empty": "إضافة عنوان صفحة تويتر",
"account.settings.field.social.platform.name.twitter.empty": "إضافة صفحة تويتر",
"account.settings.field.social.platform.name.facebook": "فيسبوك",
"account.settings.field.social.platform.name.facebook.empty": "إضافة عنوان صفحة فيسبوك",
"account.settings.field.social.platform.name.facebook.empty": "إضافة حساب فيسبوك",
"account.settings.editable.field.action.save": "حفظ",
"account.settings.editable.field.action.cancel": "إلغاء",
"account.settings.editable.field.action.edit": حرير",
"account.settings.static.field.empty": م يتم تحديد قيمة، فضلًا اتصل بمدير {enterprise} لتعيين بعض التغييرات.",
"account.settings.static.field.empty.no.admin": م يتم تحديد قيمة",
"account.settings.field.name.certificate.select": "If checked, this name will appear on your certificates and public-facing records.",
"account.settings.field.name.modal.certificate.title": "Choose a preferred name for certificates and public-facing records",
"account.settings.field.name.modal.certificate.select": "Select a name",
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "لنبدأ",
"account.settings.editable.field.action.edit": عديل",
"account.settings.static.field.empty": ا قيمة محددة. رجاءً اتصل بمديرك في {enterprise} ليقوم بالتعديلات.",
"account.settings.static.field.empty.no.admin": ا قيمة محددة.",
"account.settings.field.name.certificate.select": "في حال التأشير، سيظهر هذا الاسم في شهاداتك و سجلاتك العامة.",
"account.settings.field.name.modal.certificate.title": "اختر اسمًا مفضلًا للشهادات والسجلات العامة",
"account.settings.field.name.modal.certificate.select": "اختر اسمًا",
"account.settings.field.name.modal.certificate.option.full": "الاسم الكامل",
"account.settings.field.name.modal.certificate.option.verified": "اسم متحقَّق منه",
"account.settings.field.name.modal.certificate.button.choose": "اختيار الاسم",
"account.settings.coaching.consent.welcome.header": "لنبدأ.",
"account.settings.coaching.consent.welcome.subheader": "نحن هنا لأجلك من البداية حتى النهاية",
"account.settings.coaching.consent.description": "تتضمن برامج البكالوريوس التدريب الذي يركز على مهنتك وتعليمك وكيفية تحقيق نتائج مبهرة من خلال التواصل الشخصي مع خبراء متمرسين. إذا كنت مهتمًا، فقدّم المعلومات أدناه وانقر فوق \"إرسال\"، وسيتصل بك شريكنا في التدريب عبر البريد الإلكتروني و/أو الرسائل النصية لمساعدتك على المضي قدمًا. تنطبق الشروط والأحكام.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* يتم تضمين خدمات التدريب بدون أي تكلفة إضافية للمتعلمين الذين لديهم أرقام هواتف أمريكية. يتضمن التدريب رسائل نصية متكررة. قد تنطبق أسعار الرسائل والبيانات. إيقاف النص لإلغاء الاشتراك.",
"account.settings.coaching.consent.accept-coaching": "سجّل للاستفادة من خدمات التدريب",
"account.settings.coaching.consent.decline-coaching": "أفضّل عدم الاتصال بخدمات التدريب المجانية",
"account.settings.coaching.consent.label.name": "يرجى تأكيد الاسم",
"account.settings.coaching.consent.label.phone-number": "فضلًا أدخل رقم الهاتف ",
"account.settings.coaching.consent.success.header": "تمت العملية بنجاح",
"account.settings.coaching.consent.success.message": "لقد اشتركت في التدريب. يمكنك توقع استلام رسالة عبر البريد الإلكتروني أو الرسائل القصيرة في الأيام المقبلة.",
"account.settings.coaching.consent.description": "تتضمن برامج MicroBachelors مرافقة تركز على مهنتك و تعليمك و كيفية تحقيقك للنتائج، و ذلك من خلال التواصل الفردي مع خبير متمرس. إن كنت مهتمًا، فيرجى تزويدنا بالمعلومات أدناه و النقر على \"إرسال\"، و سيتصل بك شريكنا في المرافقة عبر البريد الإلكتروني و/أو الرسائل النصية لمساعدتك على المضي قدمًا. تنطبق الشروط والأحكام.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* خدمات المرافقة مشمولة دون تكاليف إضافية للمتعلمين الذين لديهم أرقام هواتف أمريكية. تتضمن المرافقة رسائل نصية دورية. قد تنطبق أسعار على الرسائل والبيانات. أرسل STOP للانسحاب.",
"account.settings.coaching.consent.accept-coaching": "سجّل للاستفادة من خدمات المرافقة",
"account.settings.coaching.consent.decline-coaching": "أفضّل ألا يٌتصَل بي بخصوص خدمات المرافقة المجانية",
"account.settings.coaching.consent.label.name": "رجاءً أكّد اسمك",
"account.settings.coaching.consent.label.phone-number": "أدخل رقم هاتفك الجوّال",
"account.settings.coaching.consent.success.header": "نجحت العملية!",
"account.settings.coaching.consent.success.message": "أنت الآن مشترك في المرافقة. ترقّب رسالة عبر البريد الإلكتروني أو خدمة الرسائل القصيرة في في الأيام المقبلة.",
"account.settings.coaching.consent.success.continue": "البدء في مساقي",
"account.settings.coaching.managed.support": "الدعم",
"account.settings.coaching.managed.alert": "تتم إدارة اسمك بواسطة {ManagerTitle}. اتصل بالمسؤول للحصول على المساعدة.",
"account.settings.coaching.managed.alert": "اسمك يديره {ManagerTitle}. اتصل بمديرك للحصول على المساعدة.",
"account.settings.field.phone_number": "رقم الهاتف",
"account.settings.field.phone_number.empty": "إضافة رقم الهاتف",
"account.settings.field.coaching_consent": "اتفاقية التدريب",
"account.settings.field.coaching_consent.tooltip": "تتضمن برامج البكالوريوس التدريب القائم على الرسائل النصية الذي يساعدك على إقران التجارب التعليمية مع أهدافك المهنية من خلال النصائح الشخصية. يتم تضمين خدمات التدريب بدون أي تكلفة إضافية، وهي متوفرة للمتعلمين الذين لديهم أرقام هواتف نقالة أمريكية. تنطبق أسعار المراسلة القياسية. أرسل \"قف\" في أي وقت لإلغاء الرسائل.",
"account.settings.field.coaching_consent.error": "مطلوب رقم هاتف أمريكي صالح للدخول في التدريب",
"account.settings.field.phone_number.empty": "إضافة رقم هاتف",
"account.settings.field.coaching_consent": "الموافقة على المرافقة",
"account.settings.field.coaching_consent.tooltip": "تتضمن برامج MicroBachelors مرافقة قائمة على الرسائل النصية، تساعدك على إقران تجاربك التعلّمية مع أهدافك المهنية من خلال النصائح الفردية. خدمات المرافقة مشمولة دون تكاليف إضافية، وهي متوفرة للمتعلمين الذين لديهم أرقام هواتف جوالة أمريكية. تنطبق أسعار المراسلة القياسية. أرسل 'STOP' في أي وقت للانسحاب من الرسائل.",
"account.settings.field.coaching_consent.error": "مطلوب رقم هاتف أمريكي صحيح للتسجيل في المرافقة",
"account.settings.delete.account.before.proceeding": "قبل المتابعة، يرجى {actionLink}.",
"account.settings.delete.account.header": "احذف حسابي",
"account.settings.delete.account.header": "حذف حسابي",
"account.settings.delete.account.subheader": "نأسف لذهابك!",
"account.settings.delete.account.text.1": "Please note: Deletion of your account and personal data is permanent and cannot be undone. {siteName} will not be able to recover your account or the data that is deleted.",
"account.settings.delete.account.text.2": "Once your account is deleted, you cannot use it to take courses on {siteName}.",
"account.settings.delete.account.text.2.edX": "Once your account is deleted, you cannot use it to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employers or universitys system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
"account.settings.delete.account.text.3.link": "Follow these instructions for printing or downloading a certificate",
"account.settings.delete.account.text.warning": "Warning: Account deletion is permanent. Please read the above carefully before proceeding. This is an irreversible action, and you will no longer be able to use the same email on {siteName}.",
"account.settings.delete.account.text.change.instead": "هل تريد تغيير البريد الإلكتروني أو الاسم أو كلمة المرور بدلاً من ذلك؟",
"account.settings.delete.account.button": "احذف حسابي",
"account.settings.delete.account.please.activate": نشيط حسابك",
"account.settings.delete.account.please.confirm": "confirm your account",
"account.settings.delete.account.please.unlink": "إلغاء ربط جميع حسابات التواصل الاجتماعي",
"account.settings.delete.account.text.1": "ترجى الملاحظة: إن حذف حسابك وبياناتك الشخصية له أثر دائم و لا يمكن التراجع عنه. لن يكون بمقدور {siteName} استعادة حسابك ولا البيانات التي يتم حذفها.",
"account.settings.delete.account.text.2": "بمجرد حذف حسابك، فإنك لن تستطيع استخدامه لمتابعة المساقات على {siteName}.",
"account.settings.delete.account.text.2.edX": "بمجرد حذف حسابك، فإنك لن تستطيع استخدامه لمتابعة المساقات على تطبيق edX ولا edx.org ولا أي موقع آخر تستضيفه edX. وهذا يشمل الوصول إلى edx.org من نظام صاحب العمل أو الجامعة و الوصول إلى المواقع الخاصة التي تقدمها MIT Open Learning و Wharton Executive Education و Harvard Medical School.",
"account.settings.delete.account.text.3.link": "اتّبع هذه التعليمات لطباعة أو تحميل شهادة",
"account.settings.delete.account.text.warning": "تحذير: حذف الحساب أثره دائم. يرجى قراءة ما ورد أعلاه بعناية قبل المتابعة. هذا إجراء غير رجعي، و لن تتمكن بعده من استخدام نفس البريد الإلكتروني على {siteName}.",
"account.settings.delete.account.text.change.instead": "هل تريد بدلاً من ذلك تغيير بريدك الإلكتروني أو اسمك أو كلمة المرور الخاصة بك؟",
"account.settings.delete.account.button": "حذف حسابي",
"account.settings.delete.account.please.activate": فعيل حسابك",
"account.settings.delete.account.please.confirm": "تأكيد حسابك",
"account.settings.delete.account.please.unlink": "فصل جميع حسابات التواصل الاجتماعي",
"account.settings.delete.account.modal.header": "هل أنت متأكد؟",
"account.settings.delete.account.modal.text.1": "You have selected \"Delete My Account\". Deletion of your account and personal data is permanent and cannot be undone. {siteName} will not be able to recover your account or the data that is deleted.",
"account.settings.delete.account.modal.text.2": "If you proceed, you will be unable to use this account to take courses on {siteName}.",
"account.settings.delete.account.modal.text.2.edX": "If you proceed, you will be unable to use this account to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employer's or university's system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
"account.settings.delete.account.modal.enter.password": ذا كنت لا تزال ترغب في المتابعة وحذف حسابك ، فيرجى إدخال كلمة مرور حسابك:",
"account.settings.delete.account.modal.confirm.delete": "تعم، أحذف",
"account.settings.delete.account.modal.confirm.cancel": "إلغاء",
"account.settings.delete.account.error.unable.to.delete": "تعذر حذف الحساب",
"account.settings.delete.account.modal.text.1": "لقد اخترت \"حذف حسابي\". إن حذف حسابك وبياناتك الشخصية ذو أثر دائم لا يمكن التراجع عنه. لن يكون بمقدور {siteName} استعادة حسابك و لا البيانات التي حذفت.",
"account.settings.delete.account.modal.text.2": "إن واصلت، فلن تستطيع استخدام هذا الحساب لمتابعة المساقات على {siteName}.",
"account.settings.delete.account.modal.text.2.edX": "إن واصلت، فلن تستطيع استخدام هذا الحساب لمتابعة المساقات على على تطبيق edX و لا edx.org و لا أي موقع آخر تستضيفه edX. وهذا يشمل الوصول إلى edx.org من نظام صاحب العمل أو الجامعة و الوصول إلى المواقع الخاصة التي يقدمها MIT Open Learning و Wharton Executive Education و Harvard Medical School.",
"account.settings.delete.account.modal.enter.password": ن كنت لا تزال ترغب في المتابعة و حذف حسابك، فيرجى إدخال كلمة المرور:",
"account.settings.delete.account.modal.confirm.delete": "تعم، احذف",
"account.settings.delete.account.modal.confirm.cancel": "لا",
"account.settings.delete.account.error.unable.to.delete": "لم نستطع حذف الحساب",
"account.settings.delete.account.error.no.password": "كلمة المرور مطلوبة",
"account.settings.delete.account.error.invalid.password": "كلمة المرور المدخلة غير صحيحة",
"account.settings.delete.account.error.unable.to.delete.details": "عذراً ، حدث خطأ أثناء محاولة معالجة طلبك. الرجاء معاودة المحاولة في وقت لاحق.",
"account.settings.delete.account.modal.after.header": "نأسف لذهابك! سيتم حذف حسابك قريبا.",
"account.settings.delete.account.modal.after.text": "قد يستغرق حذف الحساب ، بما في ذلك الإزالة من قوائم البريد الإلكتروني ، بضعة أسابيع حتى تتم معالجته بالكامل من خلال نظامنا. إذا كنت ترغب في إلغاء الاشتراك في رسائل البريد الإلكتروني قبل ذلك الحين ، يرجى إلغاء الاشتراك من تذييل أي بريد إلكتروني.",
"account.settings.delete.account.error.invalid.password": "كلمة المرور غير صحيحة",
"account.settings.delete.account.error.unable.to.delete.details": "عذراً، حدث خطأ أثناء محاولة معالجة طلبك. رجاءً أعد المحاولة لاحقًا.",
"account.settings.delete.account.modal.after.header": "نأسف لذهابك! سيُحذف حسابك في ظرف وجيز.",
"account.settings.delete.account.modal.after.text": "حذف الحساب، بما في ذلك من إزالة من القوائم البريدية، إجراء قد يستغرق بضعة أسابيع حتى يكتمل عبر نظامنا. إن كنت تريد قبل ذلك الحين إيقاف تلقي البريد الإلكتروني، فيرجى إلغاء الاشتراك من تذييل أي بريد إلكتروني.",
"account.settings.delete.account.modal.after.button": "إغلاق ",
"account.settings.delete.account.text.3.edX": "You may also lose access to verified certificates and other program credentials like MicroMasters certificates. You can make a copy of these for your records before proceeding with deletion. {actionLink}.",
"account.settings.delete.account.text.3": "You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.",
"account.settings.message.demographics.service.issue": "حدث خطأ أثناء محاولة استرداد معلومات حسابك أو حفظها. يرجى المحاولة مرة أخرى لاحقًا.",
"account.settings.delete.account.text.3.edX": "قد تفقد كذلك إمكانية الوصول إلى الشهادات الموثقة و كذا مؤهلات البرامج الأخرى مثل شهادات MicroMasters. يمكنك الاحتفاظ بنسخة عنها لديك قبل المواصلة إلى الحذف. {actionLink}.",
"account.settings.delete.account.text.3": "قد تفقد كذلك إمكانية الوصول إلى الشهادات الموثقة و كذا مؤهلات البرامج الأخرى. يمكنك الاحتفاظ بنسخة عنها لديك قبل المواصلة إلى الحذف.",
"account.settings.message.demographics.service.issue": "حدث خطأ أثناء محاولة استخراج أو حفظ معلومات حسابك. رجاءً أعد المحاولة لاحقًا.",
"account.settings.field.demographics.gender": "هوية الجنس",
"account.settings.field.demographics.gender.empty": "إضافة هوية الجنس",
"account.settings.field.demographics.gender.options.empty": "حدد هوية الجنس",
"account.settings.field.demographics.gender_description": "وصف هوية الجنس",
"account.settings.field.demographics.gender_description.empty": "أدخل وصفًا",
"account.settings.field.demographics.gender_description.empty": "أدخل الوصف",
"account.settings.field.demographics.ethnicity": "هوية العرق/ الأصل",
"account.settings.field.demographics.ethnicity.empty": "إضافة هوية العرق/الأصل",
"account.settings.field.demographics.ethnicity.options.empty": "فضلًا اختر جميع ما ينطبق",
"account.settings.field.demographics.income": "الدخل المادي الأسري",
"account.settings.field.demographics.income.empty": "إضافة الدخل المادي الأسري",
"account.settings.field.demographics.income.options.empty": "حدد نطاق الدخل المادي للأسرة",
"account.settings.field.demographics.military_history": "حالة الخدمة العسكرية في الولايات المتحدة الأمريكية",
"account.settings.field.demographics.military_history.empty": "إضافة حالة الخدمة العسكرية",
"account.settings.field.demographics.military_history.options.empty": "اختر حالة الخدمة العسكرية",
"account.settings.field.demographics.learner_education_level": ؤهلك التعليمي",
"account.settings.field.demographics.learner_education_level.empty": "إضافة مؤهلك التعليمي",
"account.settings.field.demographics.parent_education_level": ؤهل الوالدين/الآوصياء التعليمي",
"account.settings.field.demographics.parent_education_level.empty": "إضافة المؤهل التعليمي",
"account.settings.field.demographics.education_level.options.empty": "حدد موهلاً تعليميًا",
"account.settings.field.demographics.ethnicity.empty": "إضافة هوية العرق / الأصل",
"account.settings.field.demographics.ethnicity.options.empty": "اختر كل ما ينطبق",
"account.settings.field.demographics.income": "دخل الأسرة",
"account.settings.field.demographics.income.empty": "إضافة دخل الأسرة",
"account.settings.field.demographics.income.options.empty": "حدد نطاقًا لدخل الأسرة",
"account.settings.field.demographics.military_history": "الوضعية إزاء الخدمة العسكرية في الولايات المتحدة",
"account.settings.field.demographics.military_history.empty": "إضافة الوضعية إزاء الخدمة العسكرية",
"account.settings.field.demographics.military_history.options.empty": "اختر الوضعية إزاء الخدمة العسكرية",
"account.settings.field.demographics.learner_education_level": ستواك التعليمي",
"account.settings.field.demographics.learner_education_level.empty": "إضافة المستوى التعليمي",
"account.settings.field.demographics.parent_education_level": ستوى الوالدين/الأولياء التعليمي",
"account.settings.field.demographics.parent_education_level.empty": "إضافة المستوى التعليمي",
"account.settings.field.demographics.education_level.options.empty": "حدد المستوى التعليمي",
"account.settings.field.demographics.work_status": "الحالة الوظيفية",
"account.settings.field.demographics.work_status.empty": "إضافة الحالة الوظيفية",
"account.settings.field.demographics.work_status.options.empty": "فضلًا حدد حالتك الوظيفية",
"account.settings.field.demographics.work_status.options.empty": "حدد الحالة الوظيفية",
"account.settings.field.demographics.work_status_description": "وصف الحالة الوظيفية",
"account.settings.field.demographics.work_status_description.empty": "أدخل وصفًا",
"account.settings.field.demographics.work_status_description.empty": "أدخل الوصف",
"account.settings.field.demographics.current_work_sector": "مجال العمل الحالي",
"account.settings.field.demographics.current_work_sector.empty": "إضافة مجال العمل",
"account.settings.field.demographics.future_work_sector": "مجال العمل المستقبلي",
"account.settings.field.demographics.future_work_sector.empty": "إضافة مجال العمل",
"account.settings.field.demographics.work_sector.options.empty": "حدد مجال العمل",
"account.settings.section.demographics.why": "Why does {siteName} collect this information?",
"account.settings.name.change.title.id": "This name change requires identity verification",
"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 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",
"account.settings.name.change.cancel": "Cancel",
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في نص الرابط. الرجاء التحقق من الرابط والمحاولة مجددا.",
"account.settings.section.demographics.why": "ما هي غاية {siteName} من جمع هذه المعلومات؟",
"account.settings.name.change.title.id": "تغيير الاسم هذا يتطلب التحقق من الهوية",
"account.settings.name.change.title.begin": "قبل أن نبدأ",
"account.settings.name.change.warning.one": "تحذير: يقوم هذا الإجراء بتحديث الاسم الذي يظهر على جميع الشهادات التي تم الحصول عليها على هذا الحساب في الماضي و أي شهادات تحصل عليها حاليا أو مستقبلاً.",
"account.settings.name.change.warning.two": "لا يمكن التراجع عن هذا الإجراء دون التحقق من هويتك.",
"account.settings.name.change.id.name.label": "أدخل اسمك كما يظهر في بطاقة تعريف الطالب أو العمل أو بطاقة الهوية الصادرة عن الحكومة.",
"account.settings.name.change.id.name.placeholder": "أدخل الاسم الموجود في بطاقة تعريفك ذات الصورة.",
"account.settings.name.change.error.valid.name": "رجاءً أدخل اسما صحيحا.",
"account.settings.name.change.error.general": "حدث خطأ تقني. رجاءً حاول مجددًا.",
"account.settings.name.change.continue": "مواصلة",
"account.settings.name.change.cancel": "إلغاء",
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
"account.settings.editable.field.password.reset.button.confirmation.support.link": "الدعم الفني",
"account.settings.editable.field.password.reset.button.confirmation": "لقد أرسلنا رسالة إلى {email}. انقر فوق الرابط في الرسالة لإعادة تعيين كلمة المرور. إذا لم يتم استلام الرسالة؟ اتصل بـ {technicalSupportLink}.",
"account.settings.editable.field.password.reset.button": "تغيير كلمة المرور",
"account.settings.editable.field.password.reset.button.forbidden": "طلبك السابق قيد التقدم، يرجى إعادة المحاولة بعد لحظات قليلة.",
"account.settings.editable.field.password.reset.button.confirmation": "لقد أرسلنا رسالة إلى {email}. انقر على الرابط في الرسالة لإعادة ضبط كلمة المرور الخاصة بك. لم تصلك الرسالة؟ اتصل بـ{technicalSupportLink}.",
"account.settings.editable.field.password.reset.button": "إعادة ضبط كلمة المرور",
"account.settings.editable.field.password.reset.button.forbidden": "طلبك السابق في تقدّم، رجاءً حاول مجددًا بعد لحظات قليلة.",
"account.settings.editable.field.password.reset.label": "كلمة المرور",
"account.settings.sso.link.account": "تسجيل الدخول كـ {name}",
"account.settings.sso.account.connected": ربوط",
"account.settings.sso.account.disconnect.error": "حدثت مشكلة أثناء قطع اتصال هذا الحساب، اتصل بالدعم عند استمرار المشكلة.",
"account.settings.sso.unlink.account": "إلغاء ربط حساب {name} ",
"account.settings.sso.no.providers": "لا يمكن ربط أية حسابات حاليًا",
"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}.",
"account.settings.sso.link.account": "تسجيل الدخول باستخدام {name}",
"account.settings.sso.account.connected": وصول",
"account.settings.sso.account.disconnect.error": "حدثت مشكلة أثناء فصل هذا الحساب، اتصل بالدعم إن استمرت المشكلة.",
"account.settings.sso.unlink.account": "فصل حساب {name}",
"account.settings.sso.no.providers": "لا يمكن وصل أي حسابات في الوقت الراهن.",
"account.page.title": "الحساب | {siteName}",
"id.verification.access.blocked.denied": "لا يمكننا التحقق من هويتك في الوقت الراهن. إن لم تكن قد فعّلت حسابك بعد، فيرجى تفقد مجلد الرسائل غير المرغوب فيها بحثًا عن بريد التفعيل الإلكتروني من {email}.",
"id.verification.next": "التالي",
"id.verification.support": "support",
"id.verification.example.card.alt": "مثال على بطاقة هوية صحيحة بالاسم الكامل وصورة.",
"id.verification.requirements.title": "متطلبات التحقق من الصورة",
"id.verification.requirements.description": "In order to complete Photo Verification, you will need the following:",
"id.verification.support": "الدعم",
"id.verification.example.card.alt": "مثال بطاقة تعريف صحيحة بالاسم الكامل والصورة.",
"id.verification.requirements.title": "متطلبات التحقق باستخدام الصورة",
"id.verification.requirements.description": "لإكمال التحقق بالصورة، ستحتاج ما يلي:",
"id.verification.requirements.card.device.title": "جهاز مزود بكاميرا",
"id.verification.requirements.card.device.allow": "موافق",
"id.verification.requirements.card.id.title": "Photo Identification Card",
"id.verification.requirements.card.id.text": "You need a valid identification card that contains your full name and photo, such as a drivers license or passport.",
"id.verification.privacy.title": "بيانات الخصوصية.",
"id.verification.privacy.need.photo.question": "Why does {siteName} need my photo?",
"id.verification.privacy.need.photo.answer": "نستخدم صور التحقق الخاصة بك لتأكيد هويتك والتأكد من صحة شهادتك.",
"id.verification.privacy.do.with.photo.question": "What does {siteName} do with this photo?",
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on {siteName} after the verification process is complete.",
"id.verification.requirements.card.device.allow": "السماح",
"id.verification.requirements.card.id.title": "بطاقة تعريف بها صورة",
"id.verification.requirements.card.id.text": "تحتاج إلى بطاقة تعريف صالحة تحتوي اسمك الكامل وصورتك، كرخصة القيادة أو جواز السفر مثلاً.",
"id.verification.privacy.title": "معلومات الخصوصية",
"id.verification.privacy.need.photo.question": "ما حاجة {siteName} لصورتي؟",
"id.verification.privacy.need.photo.answer": "نستخدم صور التحقق الخاصة بك لتأكيد هويتك ولضمان صحة شهادتك.",
"id.verification.privacy.do.with.photo.question": "ماذا يُفعَل بهذه الصورة في {siteName}؟",
"id.verification.privacy.do.with.photo.answer": "نقوم بتشفير صورتك بشكل آمن وإرسالها إلى خدمة الترخيص الخاصة بنا للمراجعة. صورتك و معلوماتك لا تُحفَظ و لا تظهر في أي مكان على {siteName} بعد اكتمال عملية التحقق.",
"id.verification.access.blocked.title": "التحقق من الهوية",
"id.verification.access.blocked.enrollment": "أنت الآن ملتحق بمساق يتطلب التحقق من الهوية.",
"id.verification.access.blocked.pending": "لقد قمت بالفعل بإرسال معلومات التحقق الخاصة بك. ستصلك رسالة على لوحة المعلومات عند اكتمال عملية التحقق (عادةً خلال 5 أيام).",
"id.verification.access.blocked.enrollment": "أنت حاليا غير ملتحق بأي مساق يتطلب التحقق من الهوية.",
"id.verification.access.blocked.pending": "لقد سلّمت من قبل معلومات التحقق الخاصة بك. ستصلك رسالة على لوحة المعلومات الخاصة بك عند اكتمال عملية التحقق (عادةً في ظرف 5 أيام).",
"id.verification.photo.take": "التقاط صورة ",
"id.verification.photo.retake": "Retake Photo?",
"id.verification.photo.enable.detection": مكين خاصية التعرف على الوجه",
"id.verification.photo.enable.detection.portrait.help.text": "عند تحديد هذا الخيار، فسيظهر مربع حول وجهك. يمكن رؤية وجهك بوضوح إذا كان المربع المحيط به أزرق. إذا لم يكن وجهك في وضع جيد أو إذا لم يكن قابلاً للاكتشاف، فسيكون المربع باللون الأحمر.",
"id.verification.photo.enable.detection.id.help.text": "عند تحديد هذا الخيار، فسيظهر مربع حول صورة وجهك في بطاقة الهوية. يمكن رؤية وجهك بوضوح إذا كان المربع المحيط به أزرق. إذا لم يكن وجهك في وضع جيد أو إذا لم يكن قابلاً للاكتشاف، فسيكون المربع باللون الأحمر.",
"id.verification.photo.feedback.correct": "وضع الوجه جيد.",
"id.verification.photo.feedback.two.faces": "تم تحديد أكثر من وجه.",
"id.verification.photo.feedback.no.faces": "لم يتم تحديد أي وجه.",
"id.verification.photo.feedback.top.left": "وضع خاطئ. أعلى اليسار.",
"id.verification.photo.feedback.top.center": "وضع خاطئ. أعلى الوسط.",
"id.verification.photo.feedback.top.right": "وضع خاطئ. أعلى اليمين.",
"id.verification.photo.feedback.center.left": "وضع خاطئ. وسط اليسار.",
"id.verification.photo.feedback.center.center": "وضع خاطئ. قريب جدًا من الكاميرا.",
"id.verification.photo.feedback.center.right": "وضع خاطئ. وسط اليمين.",
"id.verification.photo.feedback.bottom.left": "وضع خاطئ. أسفل اليسار.",
"id.verification.photo.feedback.bottom.center": "وضع خاطئ. أسفل الوسط.",
"id.verification.photo.feedback.bottom.right": "وضع خاطئ. أسفل اليمين.",
"id.verification.photo.retake": "إعادة التقاط الصورة؟",
"id.verification.photo.enable.detection": فعيل اكتشاف الوجوه",
"id.verification.photo.enable.detection.portrait.help.text": "في حال التأشير، سيبرز مربع حول وجهك. يمكن رؤية وجهك بوضوح إن كان المربع المحيط به أزرق اللون. أما إن كان وجهك في وضع غير جيد أو غير قابل للاكتشاف، فسيكون المربع أحمر اللون.",
"id.verification.photo.enable.detection.id.help.text": "في حال التأشير، سيظهر مربع حول صورة وجهك في بطاقة الهوية. يمكن رؤية وجهك بوضوح إن كان المربع المحيط به أزرق اللون. أما إن كان وجهك في وضع غير جيد أو غير قابل للاكتشاف، فسيكون المربع أحمر اللون.",
"id.verification.photo.feedback.correct": "موضع الوجه جيد.",
"id.verification.photo.feedback.two.faces": "تم اكتشاف أكثر من وجه واحد.",
"id.verification.photo.feedback.no.faces": "لم يتم اكتشاف أي وجه.",
"id.verification.photo.feedback.top.left": "الموضع خاطئ. أعلى اليسار.",
"id.verification.photo.feedback.top.center": "الموضع خاطئ. أعلى الوسط.",
"id.verification.photo.feedback.top.right": "الموضع خاطئ. أعلى اليمين.",
"id.verification.photo.feedback.center.left": "الموضع خاطئ. وسط اليسار.",
"id.verification.photo.feedback.center.center": "الموضع خاطئ. قريب جدًا من الكاميرا.",
"id.verification.photo.feedback.center.right": "الموضع خاطئ. وسط اليمين.",
"id.verification.photo.feedback.bottom.left": "الموضع خاطئ. أسفل اليسار.",
"id.verification.photo.feedback.bottom.center": "الموضع خاطئ. أسفل الوسط.",
"id.verification.photo.feedback.bottom.right": "الموضع خاطئ. أسفل اليمين.",
"id.verification.camera.access.title": "صلاحيات الكاميرا",
"id.verification.camera.access.title.success": "تمكين الوصول للكاميرا",
"id.verification.camera.access.title.success": "الوصول للكاميرا ممكن",
"id.verification.camera.access.title.failed": "تعذّر الوصول للكاميرا.",
"id.verification.camera.access.click.allow": "فضلًا تأكد من اختيار الأمر \"السماح\"",
"id.verification.camera.access.enable": مكين الكاميرا",
"id.verification.camera.access.problems": "هل تواجه أية مشكلة؟",
"id.verification.camera.access.skip": "قم بتخطي ملفات الصور وتحميلها بدلاً من ذلك",
"id.verification.camera.access.success": "يبدو أن الكاميرا تعمل وجاهزة.",
"id.verification.camera.access.failure": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. ستحتاج إلى تحميل ملفات الصور الخاصة بك و معرّف الصور الخاص بك.",
"id.verification.camera.access.failure.temporary": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. يرجى التحقق من أن كاميرا الويب متصلة ومن أنك سمحت للمتصفح بالوصول إليها.",
"id.verification.camera.access.failure.temporary.chrome": "تمكين الوصول للكاميرا في متصفّح كروم: ",
"id.verification.camera.access.failure.temporary.chrome.step1": "فتح متصفّح كروم.",
"id.verification.camera.access.click.allow": "رجاءً تأكد من النقر على \"السماح\"",
"id.verification.camera.access.enable": فعيل الكاميرا",
"id.verification.camera.access.problems": "لديك مشاكل؟",
"id.verification.camera.access.skip": "تَخَطَّ و قم برفع ملفات صور بدلاً من ذلك.",
"id.verification.camera.access.success": "يبدو أن كاميرا جهازك جاهزة و تعمل.",
"id.verification.camera.access.failure": "يبدو أننا غير قادرين على الوصول إلى كاميرا جهازك. ستحتاج لرفع ملف صورتك و صورة بطاقة هويتك.",
"id.verification.camera.access.failure.temporary": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. رجاءً تحقق من أن كاميرا جهازك متصلة ومن أنك قد سمحت للمتصفح بالوصول إليها.",
"id.verification.camera.access.failure.temporary.chrome": "لتفعيل الوصول للكاميرا في متصفح كروم:",
"id.verification.camera.access.failure.temporary.chrome.step1": "افتح كروم.",
"id.verification.camera.access.failure.temporary.chrome.step2": "توجه إلى المزيد > الإعدادات.",
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "لنظام ويندوز: Alt+F أو Alt+E أو F10 متبوعاً بـ SPACEBAR",
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "لنظام ماك: Command+,",
"id.verification.camera.access.failure.temporary.chrome.step3": "ضمن علامة التبويب \"الخصوصية والأمان\"، حدد \"إعدادات الموقع\" ثم \"الكاميرا\".",
"id.verification.camera.access.failure.temporary.chrome.step3": "تحت علامة التبويب \"الخصوصية والأمان\"، اختر \"إعدادات الموقع\" ثم \"الكاميرا\".",
"id.verification.camera.access.failure.temporary.chrome.step4": "ضمن \"قائمة الحظر\"، ابحث عن \"edx.org\" وحدده.",
"id.verification.camera.access.failure.temporary.chrome.step5": "في القسم \"الصلاحيات\"، قم بتحديث صلاحيات الكاميرا إلى \"السماح\".",
"id.verification.camera.access.failure.temporary.ie11": "تمكين الوصول للكاميرا في متصفح انترنت إكسبلورر:",
"id.verification.camera.access.failure.temporary.chrome.step5": "في قسم \"الصلاحيات\"، قم بتحديث صلاحيات الكاميرا إلى \"السماح\".",
"id.verification.camera.access.failure.temporary.ie11": "لتفعيل الوصول للكاميرا في متصفح انترنت إكسبلورر:",
"id.verification.camera.access.failure.temporary.ie11.step1": "افتح إدارة إعدادات Flash Player عن طريق الانتقال إلى إعدادات Windows > لوحة التحكم > Flash Player.",
"id.verification.camera.access.failure.temporary.ie11.step2": "حدد علامة التبويب \"Camera and Mic\" (الكاميرا والميكروفون)، ثم حدد الزر \"Camera and Microphone Settings by Site\" (إعدادات الكاميرا والميكروفون حسب الموقع).",
"id.verification.camera.access.failure.temporary.ie11.step3": "اختر \"edx.org\" من قائمة مواقع ويب وقم بتغيير الصلاحيات من خلال تحديد \"السماح\" في القائمة المنسدلة.",
@@ -276,75 +285,75 @@
"id.verification.camera.access.failure.temporary.firefox.step7": "اختر \"حفظ التغييرات.\"",
"id.verification.camera.access.failure.temporary.safari": "تمكين الوصول للكاميرا في متصفّح سفاري: ",
"id.verification.camera.access.failure.temporary.safari.step1": "فتح متصفّح سفاري.",
"id.verification.camera.access.failure.temporary.safari.step2": "انقر فوق قائمة تطبيق سفاري، ثم حدد \"Preferences\" (التفضيلات). يمكنك أيضاً استخدام الأمرCommand+ كاختصار للوحة المفاتيح",
"id.verification.camera.access.failure.temporary.safari.step3": "حدد علامة التبويب \"مواقع ويب\" ثم حدد \"كاميرا\".",
"id.verification.camera.access.failure.temporary.safari.step2": "انقر فوق قائمة تطبيق سفاري، ثم حدد \"Preferences\" (التفضيلات). يمكنك أيضاً استخدام الأمرCommand+, كاختصار للوحة المفاتيح",
"id.verification.camera.access.failure.temporary.safari.step3": "حدد علامة التبويب \"مواقع الويب\" ثم حدد \"كاميرا\".",
"id.verification.camera.access.failure.temporary.safari.step4": "حدد \"edx.org\" وقم بتغيير صلاحيات الكاميرا إلى \"السماح\".",
"id.verification.camera.access.failure.unsupported": "It looks like your browser does not support camera access.",
"id.verification.camera.access.failure.unsupported.chrome.explanation": "The Chrome browser currently does not support camera access on iOS devices, such as iPhones and iPads.",
"id.verification.camera.access.failure.unsupported.instructions": "Please use another browser to complete Identity Verification.",
"id.verification.photo.tips.title": "تلميحات مفيدة للصورة",
"id.verification.photo.tips.description": "بعد ذلك، سنحتاج منك التقاط صورة لوجهك. يرجى مراجعة التلميحات المفيدة أدناه.",
"id.verification.photo.tips.list.title": "تلميحات الصورة",
"id.verification.camera.access.failure.unsupported": "يبدو أن متصفحك لا يدعم الوصول إلى الكاميرا.",
"id.verification.camera.access.failure.unsupported.chrome.explanation": "لا يدعم متصفح Chrome حاليًا الوصول إلى الكاميرا على أجهزة iOS مثل أجهزة iPhone و iPad.",
"id.verification.camera.access.failure.unsupported.instructions": "رجاءً استخدم متصفحًا آخر لإكمال التحقق من الهوية.",
"id.verification.photo.tips.title": "نصائح مفيدة بخصوص الصورة",
"id.verification.photo.tips.description": "بعد ذلك، سنحتاج منك التقاط صورة لوجهك. يرجى مراجعة النصائح المفيدة أدناه.",
"id.verification.photo.tips.list.title": "نصائح بخصوص الصورة",
"id.verification.photo.tips.list.description": "لالتقاط صورة ناجحة، يُرجى التأكّد ممّا يلي:",
"id.verification.photo.tips.list.well.lit": "أنّ الإضاءة جيّدة على وجهك.",
"id.verification.photo.tips.list.inside.frame": "أنّ وجهك داخل إطار الصورة بالكامل.",
"id.verification.photo.tips.list.well.lit": "أنّ وجهك مُضاء جيدًا.",
"id.verification.photo.tips.list.inside.frame": "أنّ وجهك كلَّه داخل إطار الصورة.",
"id.verification.portrait.photo.title.camera": "التقط صورة لنفسك",
"id.verification.portrait.photo.instructions.camera": "عندما يكون وجهك في موضعه، استخدم زر التقاط صورة أدناه لالتقاط الصورة.",
"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.portrait.photo.instructions.camera": "عندما يكون وجهك في موضعه، استخدم زر 'التقاط صورة' أدناه لالتقاط صورتك.",
"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.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.",
"id.verification.id.tips.list.well.lit": "Your identification card is well-lit.",
"id.verification.camera.help.difficulty.answer": "إن احتجت لمساعدة في التقاط صورة لإرسالها، فاتصل بدعم {siteName} للحصول على اقتراحات إضافية.",
"id.verification.id.photo.unclear.question": "هل صورة بطاقة تعريفك غير واضحة أو ضبابية جدًا؟",
"id.verification.id.tips.title": "نصائح مفيدة بخصوص بطاقة التعريف",
"id.verification.id.tips.description": "بعد ذلك، عليك التقاط صورة لبطاقة تعريف صالحة تتضمن اسمك الكامل مع صورة، مثل رخصة القيادة أو جواز السفر.يرجى منك تجهيز بطاقة تعريفك.",
"id.verification.id.tips.list.well.lit": "أن تكون بطاقة تعريفك مضاءة جيدًا.",
"id.verification.id.tips.list.clear": "تأكد من قدرتك على رؤية صورتك وقراءة اسمك بوضوح.",
"id.verification.id.photo.title.camera": "Take a Photo of Your Identification Card",
"id.verification.id.photo.title.upload": "Upload a Photo of Your Identification Card",
"id.verification.id.photo.title.camera": "التقط صورة لبطاقة تعريفك",
"id.verification.id.photo.title.upload": "ارفع صورة لبطاقة تعريفك",
"id.verification.id.photo.preview.alt": "معاينة صورة الهوية.",
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo. Please use a passport, drivers license, or another identification card that includes your full name and a picture of your face.",
"id.verification.id.photo.instructions.upload": "Please upload a photo of your identification card. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. Supported formats: ",
"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 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.id.photo.instructions.camera": "عندما تكون بطاقتك في موضعها، استخدم زر 'التقاط صورة' أدناه لالتقاط صورتك. يرجى استخدام جواز سفر أو رخصة قيادة أو بطاقة تعريف أخرى تتضمن اسمك الكامل وصورة لوجهك.",
"id.verification.id.photo.instructions.upload": "يرجى رفع صورة لبطاقة تعريفك. تأكد من أن البطاقة كاملة داخل الإطار و أنها مضاءة جيدًا. يجب أن يكون حجم الملف أقل من 10 ميجابايت. الأنساق المدعومة:",
"id.verification.id.photo.instructions.upload.error.invalidFileType": "الملف الذي حددته ليس ضمن أنواع الصورة المدعومة. اختر رجاءً من بين الأنساق التالية:",
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "الملف الذي حددته كبير جداً. رجاءً أعد المحاولة باستخدام ملف ذي حجم أقل من 10 ميجابايت.",
"id.verification.name.check.title": "تحقق مرة أخرى من اسمك",
"id.verification.name.check.instructions": "هل الاسم أدناه يطابق الاسم الموجود في بطاقة تعريفك ذات الصورة. إن لم يكن كذلك، فقم بتحديث الاسم أدناه ليطابق بطاقة تعريفك.",
"id.verification.name.check.mismatch.information": "إن كان الاسم أدناه لا يطابق بطاقة تعريفك، فسيتم رفض تأكيد هويتك.",
"id.verification.name.error": "رجاءً أدخل اسمك كما يظهر في بطاقة تعريفك ذات الصورة.",
"id.verification.account.name.warning.prefix": "تُرجى الملاحظة:",
"id.verification.account.name.settings": "إعدادات الحساب",
"id.verification.name.label": "Name",
"id.verification.account.name.photo.alt": "صورة من هويتك للتقديم.",
"id.verification.name.label": "الاسم",
"id.verification.account.name.photo.alt": "صورة بطاقة هويتك التي ستُسلَّم.",
"id.verification.review.title": "مراجعة صورك",
"id.verification.review.description": "يُرجى التأكّد من أنّ الصور والمعلومات التي قدّمتها تمكّننا من التحقّق من هويّتك. ",
"id.verification.review.portrait.label": "صورتك الشخصية",
"id.verification.review.portrait.alt": "صورة شخصية للتقديم.",
"id.verification.review.portrait.retake": "إعادة التقاط الصورة شخصية",
"id.verification.review.id.label": "Your Identification Card",
"id.verification.review.id.alt": "Photo of your identification card to be submitted.",
"id.verification.review.id.retake": "إعادة التقاط صورة الهوية",
"id.verification.review.confirm": "إرسال",
"id.verification.submission.alert.error.face": "مطلوب صورة لوجهك. يرجى إعادة التقاط الصورة الشخصية.",
"id.verification.submission.alert.error.id": "مطلوب صورة لبطاقة هويتك. يرجى إعادة التقاط صورة بطاقة الهوية.",
"id.verification.submission.alert.error.name": "مطلوب اسم حساب صالح. يرجى تحديث اسم حسابك لمطابقة الاسم على هويتك.",
"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": "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.review.portrait.alt": "صورة وجهك التي ستُسلَّم.",
"id.verification.review.portrait.retake": "إعادة التقاط الصورة الشخصية",
"id.verification.review.id.label": "بطاقة تعريفك",
"id.verification.review.id.alt": "صورة بطاقة تعريفك التي ستُسلَّم.",
"id.verification.review.id.retake": "إعادة التقاط صورة بطاقة الهوية",
"id.verification.review.confirm": "تسليم",
"id.verification.submission.alert.error.face": "مطلوبة صورة لوجهك. رجاءً أعد التقاط صورتك الشخصية.",
"id.verification.submission.alert.error.id": "مطلوبة صورة لبطاقة تعريفك. رجاءً أعد التقاط صورة لبطاقة تعريفك.",
"id.verification.submission.alert.error.name": "مطلوب اسم حساب صحيح. يرجى تحديث اسم حسابك لمطابقة الاسم على بطاقة تعريفك.",
"id.verification.submission.alert.error.unsupported": "واحد أو أكثر من الملفات التي قمت برفعها في نَسقٍ غير مدعوم. رجاءً اختر مما يلي:",
"id.verification.review.error": "{siteName} صفحة دعم",
"id.verification.submitted.title": "التحقق من الهوية جارٍ",
"id.verification.submitted.text": "لقد تلقينا معلوماتك و نحن الآن نتحقق من هويتك. سيتم إخطارك عند اكتمال عملية التحقق (عادةً في ظرف 5 أيام). إلى ذلكم الحين، لا يزال يمكنك الوصول لجميع محتويات المساق المتاحة.",
"id.verification.return.dashboard": "العودة للوحة المعلومات",
"id.verification.return.course": "العودة للمساق",
"id.verification.return.generic": "Return",
"id.verification.photo.upload.help.title": "Upload a Photo Instead",
"id.verification.photo.camera.help.title": "Use Your Camera Instead",
"id.verification.photo.upload.help.text": "If you are having trouble using the photo capture above, you may want to upload a photo instead. To upload a photo, click the button below.",
"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.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": "أنت بحاجة إلى جهاز مزود بكاميرا. إذا تلقيت طلب المتصفح للوصول إلى الكاميرا، فيرجى التأكد من النقر فوق {السماح}.",
"id.verification.account.name.summary.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.",
"idv.submission.alert.error": "\nواجهنا خطأ فني أثناء محاولة رفع طلب التحقق من الهوية\nقد تكون هذه مشكلة مؤقتة، لذا يرجى المحاولة مرة أخرى بعد بضع دقائق\nعند استمرار المشكلة يرجى الانتقال إلى {support_link} للحصول على المساعدة",
"id.verification.account.name.edit": "Edit {sr}"
"id.verification.return.generic": "العودة",
"id.verification.photo.upload.help.title": "قم يدلا من هذا برفع صورة",
"id.verification.photo.camera.help.title": "استخدم الكاميرا بدلاً من هذا",
"id.verification.photo.upload.help.text": "إن واجهتك مشكلة في استخدام مُلتقط الصور أعلاه، فقد ترغب بدﻷ من ذلك في رفع صورة. لرفع صورة، انقر على الزر أدناه.",
"id.verification.photo.camera.help.text": "إن واجهتك مشكلة في رفع صورة أعلاه، فقد ترغب بدلا من ذلك في استخدام كاميرا جهازك. لاستخدام الكاميرا، انقر على الزر أدناه.",
"id.verification.upload.help.button": "انتقل إلى وضع الرفع",
"id.verification.camera.help.button": "انتقل إلى وضع الكاميرا",
"id.verification.request.camera.access.instructions": "حتى تلتقط صورة باستخدام الكاميرا، قد تتلقى طلبًا من المتصفح للوصول إلى الكاميرا. {clickAllow}",
"id.verification.requirements.account.managed.alert": "إعدادات حسابك يديرها {managerTitle}. إن لم يكن الاسم في بطاقة هويتك ذات الصورة. مطابقًا للاسم الذي في حسابك، فيرجى الاتصال بالمسؤول {profileDataManager} أو ب{support} للحصول على المساعدة قبل إتمام عملية التحقق من الصورة..",
"id.verification.requirements.card.device.text": "أنت بحاجة إلى جهاز مزود بكاميرا. إذا تلقيت طلبًا من المتصفح للوصول إلى كاميرا جهازك، فتأكد رجاءً من النقر على {السماح}.",
"id.verification.account.name.summary.alert": "إعدادات حسابك يديرها {managerTitle}. إن لم يكن الاسم في بطاقة هويتك ذات الصورة. مطابقًا للاسم الذي في حسابك، فيرجى الاتصال بالمسؤول {profileDataManager} أو ب{support} للحصول على المساعدة قبل إتمام عملية التحقق من الصورة..",
"idv.submission.alert.error": "\nواجهنا خطأ فني أثناء محاولة رفع طلب التحقق من الهوية\nقد تكون هذه مشكلة مؤقتة، لذا يرجى المحاولة مجددًا بعد بضع دقائق\nإن استمرت المشكلة، فيرجى الذهاب إلى {support_link} للحصول على المساعدة.",
"id.verification.account.name.edit": "تعديل {sr}"
}

View File

@@ -55,6 +55,14 @@
"account.settings.field.dob": "Año de nacimiento",
"account.settings.field.dob.empty": "Agregar año de nacimiento",
"account.settings.field.year_of_birth.options.empty": "Selecciona año de nacimiento",
"account.settings.field.dob.month": "Mes",
"account.settings.field.dob.year": "Año",
"account.settings.field.dob.form.button": "Por favor confirme su fecha de nacimiento",
"account.settings.field.dob.form.title": "Confirma tu fecha de nacimiento",
"account.settings.field.dob.form.help.text": "Solicitamos información sobre la fecha de nacimiento para ayudarnos a cumplir con nuestras obligaciones legales.",
"account.settings.field.dob.form.success": "Gracias por ingresar la información de su fecha de nacimiento.",
"account.settings.field.month_of_birth.options.empty": "Seleccione un mes de nacimiento",
"account.settingsfield.dob.error.general": "Ha ocurrido un error técnico. Por favor intentalo de nuevo.",
"account.settings.field.country": "País",
"account.settings.field.country.empty": "Agregar país",
"account.settings.field.country.options.empty": "Seleccionar un país",
@@ -193,7 +201,7 @@
"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": "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.id.name.placeholder": "Ingrese el nombre en su identificación con foto",
"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",
@@ -209,6 +217,7 @@
"account.settings.sso.account.disconnect.error": "Hubo un problema al desconectar esta Cuenta. Si el problema persiste, contacte soporte.",
"account.settings.sso.unlink.account": "Desvincular la cuenta de {accountName} ",
"account.settings.sso.no.providers": "No se pueden vincular cuentas en este momento.",
"account.page.title": "Cuenta | {siteName}",
"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",
@@ -309,7 +318,7 @@
"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": "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.instructions": "¿El nombre a continuación coincide con el nombre en su identificación con foto? De lo contrario, actualice el nombre a continuación para que coincida con su identificación con foto.",
"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:",

View File

@@ -55,6 +55,14 @@
"account.settings.field.dob": "Année de naissance",
"account.settings.field.dob.empty": "Ajouter année de naissance",
"account.settings.field.year_of_birth.options.empty": "Sélectionnez une année de naissance",
"account.settings.field.dob.month": "Month",
"account.settings.field.dob.year": "Year",
"account.settings.field.dob.form.button": "Please confirm your date of birth",
"account.settings.field.dob.form.title": "Confirm your date of birth",
"account.settings.field.dob.form.help.text": "We ask for birth date information to help us comply with our legal obligations.",
"account.settings.field.dob.form.success": "Thank you for entering your birth date information.",
"account.settings.field.month_of_birth.options.empty": "Select a month of birth",
"account.settingsfield.dob.error.general": "A technical error occurred. Please try again.",
"account.settings.field.country": "Pays",
"account.settings.field.country.empty": "Ajouter un pays",
"account.settings.field.country.options.empty": "Sélectionnez un pays",
@@ -209,6 +217,7 @@
"account.settings.sso.account.disconnect.error": "Un problème est survenu lors de la déconnexion de ce compte. Contactez le support si le problème persiste.",
"account.settings.sso.unlink.account": "Dissocier le compte {name}",
"account.settings.sso.no.providers": "Aucun compte ne peut être lié pour le moment.",
"account.page.title": "Account | {siteName}",
"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",

View File

@@ -55,6 +55,14 @@
"account.settings.field.dob": "Year of birth",
"account.settings.field.dob.empty": "Add year of birth",
"account.settings.field.year_of_birth.options.empty": "Select a year of birth",
"account.settings.field.dob.month": "Month",
"account.settings.field.dob.year": "Year",
"account.settings.field.dob.form.button": "Please confirm your date of birth",
"account.settings.field.dob.form.title": "Confirm your date of birth",
"account.settings.field.dob.form.help.text": "We ask for birth date information to help us comply with our legal obligations.",
"account.settings.field.dob.form.success": "Thank you for entering your birth date information.",
"account.settings.field.month_of_birth.options.empty": "Select a month of birth",
"account.settingsfield.dob.error.general": "A technical error occurred. Please try again.",
"account.settings.field.country": "Country",
"account.settings.field.country.empty": "Add country",
"account.settings.field.country.options.empty": "Select a Country",
@@ -209,6 +217,7 @@
"account.settings.sso.account.disconnect.error": "There was a problem disconnecting this account. Contact support if the problem persists.",
"account.settings.sso.unlink.account": "Unlink {name} account",
"account.settings.sso.no.providers": "No accounts can be linked at this time.",
"account.page.title": "Account | {siteName}",
"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",

View File

@@ -175,7 +175,7 @@ class Camera extends React.Component {
if (predictions.length === 0) {
this.giveFeedback(predictions.length, [], false);
}
}
};
startDetection() {
setTimeout(() => {

View File

@@ -14,14 +14,14 @@ function CameraHelpWithUpload(props) {
const { setIdPhotoFile, idPhotoFile, userId } = useContext(IdVerificationContext);
const [hasUploadedImage, setHasUploadedImage] = useState(false);
function setAndTrackIdPhotoFile(image) {
const setAndTrackIdPhotoFile = (image) => {
sendTrackEvent('edx.id_verification.upload_id', {
category: 'id_verification',
user_id: userId,
});
setHasUploadedImage(true);
setIdPhotoFile(image);
}
};
return (
<div>

View File

@@ -11,7 +11,7 @@ function CollapsibleImageHelp(props) {
userId, useCameraForId, setUseCameraForId,
} = useContext(IdVerificationContext);
function handleClick() {
const handleClick = () => {
const toggleTo = useCameraForId ? 'upload' : 'camera';
const eventName = `edx.id_verification.toggle_to.${toggleTo}`;
sendTrackEvent(eventName, {
@@ -19,7 +19,7 @@ function CollapsibleImageHelp(props) {
user_id: userId,
});
setUseCameraForId(!useCameraForId);
}
};
return (
<Collapsible

View File

@@ -1,4 +1,6 @@
import React, { useState, useContext, useEffect } from 'react';
import React, {
useState, useContext, useEffect, useMemo,
} from 'react';
import PropTypes from 'prop-types';
import { AppContext } from '@edx/frontend-platform/react';
@@ -70,7 +72,7 @@ export default function IdVerificationContextProvider({ children }) {
}
}
const contextValue = {
const contextValue = useMemo(() => ({
existingIdVerification,
facePhotoFile,
idPhotoFile,
@@ -108,7 +110,9 @@ export default function IdVerificationContextProvider({ children }) {
setMediaStream(null);
}
},
};
}), [authenticatedUser.name, authenticatedUser.userId, existingIdVerification, facePhotoFile,
idPhotoFile, idPhotoName, mediaAccess, mediaStream, profileDataManager, reachedSummary,
useCameraForId, verifiedName]);
const loadingStatuses = [IDLE_STATUS, LOADING_STATUS];
// If we are waiting for verification status or verified name history endpoint, show spinner.

View File

@@ -31,7 +31,7 @@ export default function ImageFileUpload({ onFileChange, intl }) {
});
fileReader.readAsDataURL(fileObject);
}
}, []);
}, [errorTypes.fileTooLarge, errorTypes.invalidFileType, onFileChange]);
return (
<>

View File

@@ -1,4 +1,4 @@
import React, { createContext } from 'react';
import React, { createContext, useMemo } from 'react';
import PropTypes from 'prop-types';
import { getVerifiedNameHistory } from '../account-settings/data/service';
@@ -18,10 +18,10 @@ export function VerifiedNameContextProvider({ children }) {
verifiedName = getMostRecentApprovedOrPendingVerifiedName(results);
}
const value = {
const value = useMemo(() => ({
verifiedNameHistoryCallStatus: status,
verifiedName,
};
}), [status, verifiedName]);
return (<VerifiedNameContext.Provider value={value}>{children}</VerifiedNameContext.Provider>);
}

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

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './IdVerificationPage';

View File

@@ -16,7 +16,7 @@ export default function BasePanel({
if (focusOnMount && headingRef.current) {
headingRef.current.focus();
}
}, [headingRef.current]);
}, [focusOnMount]);
const redirectSlug = useVerificationRedirectSlug(name);
if (redirectSlug) {

View File

@@ -63,6 +63,7 @@ export function EnableCameraDirectionsPanel(props) {
</>
);
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}

View File

@@ -28,14 +28,14 @@ function GetNameIdPanel(props) {
if (location.state?.fromSummary && nameInputRef.current) {
nameInputRef.current.focus();
}
}, []);
}, [idPhotoName, location.state, nameOnAccountValue, setIdPhotoName]);
function handleSubmit(e) {
const handleSubmit = (e) => {
e.preventDefault();
if (idPhotoName) {
push(nextPanelSlug);
}
}
};
return (
<BasePanel

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

@@ -61,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'])}
@@ -78,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

@@ -32,7 +32,7 @@ function SummaryPanel(props) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [submissionError, setSubmissionError] = useState(null);
useEffect(() => setReachedSummary(true), []);
useEffect(() => setReachedSummary(true), [setReachedSummary]);
function renderManagedProfileMessage() {
if (!profileDataManager) {
@@ -59,8 +59,9 @@ function SummaryPanel(props) {
);
}
// eslint-disable-next-line react/no-unstable-nested-components
function SubmitButton() {
async function handleClick() {
const handleClick = async () => {
setIsSubmitting(true);
const verificationData = {
facePhotoFile,
@@ -85,7 +86,7 @@ function SummaryPanel(props) {
setIsSubmitting(false);
setSubmissionError(result);
}
}
};
return (
<Button
title="Confirmation"

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';

View File

@@ -13,6 +13,7 @@ jest.mock('jslib-html5-camera-photo');
jest.mock('@tensorflow-models/blazeface');
jest.mock('@edx/frontend-platform/analytics');
// eslint-disable-next-line no-import-assign
analytics.sendTrackEvent = jest.fn();
window.HTMLMediaElement.prototype.play = () => {};

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
@@ -18,15 +19,33 @@ jest.mock('../VerifiedNameContext', () => {
VerifiedNameContextProvider: jest.fn(({ children }) => children),
};
});
jest.mock('../panels/ReviewRequirementsPanel');
jest.mock('../panels/RequestCameraAccessPanel');
jest.mock('../panels/PortraitPhotoContextPanel');
jest.mock('../panels/TakePortraitPhotoPanel');
jest.mock('../panels/IdContextPanel');
jest.mock('../panels/GetNameIdPanel');
jest.mock('../panels/TakeIdPhotoPanel');
jest.mock('../panels/SummaryPanel');
jest.mock('../panels/SubmittedPanel');
jest.mock('../panels/ReviewRequirementsPanel', () => function () {
return <></>;
});
jest.mock('../panels/RequestCameraAccessPanel', () => function () {
return <></>;
});
jest.mock('../panels/PortraitPhotoContextPanel', () => function () {
return <></>;
});
jest.mock('../panels/TakePortraitPhotoPanel', () => function () {
return <></>;
});
jest.mock('../panels/IdContextPanel', () => function () {
return <></>;
});
jest.mock('../panels/GetNameIdPanel', () => function () {
return <></>;
});
jest.mock('../panels/TakeIdPhotoPanel', () => function () {
return <></>;
});
jest.mock('../panels/SummaryPanel', () => function () {
return <></>;
});
jest.mock('../panels/SubmittedPanel', () => function () {
return <></>;
});
const IntlIdVerificationPage = injectIntl(IdVerificationPage);

View File

@@ -1,17 +1,18 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useContext } from 'react';
import { render, cleanup, waitFor } from '@testing-library/react';
import { getVerifiedNameHistory } from '../../account-settings/data/service';
import { VerifiedNameContext, VerifiedNameContextProvider } from '../VerifiedNameContext';
const VerifiedNameContextTestComponent = () => {
function VerifiedNameContextTestComponent() {
const { verifiedName } = useContext(VerifiedNameContext);
return (
<>
{verifiedName && (<div data-testid="verified-name">{verifiedName}</div>)}
</>
);
};
}
jest.mock('../../account-settings/data/service', () => ({
getVerifiedNameHistory: jest.fn(() => ({})),

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
@@ -12,7 +13,9 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
jest.mock('../../Camera');
jest.mock('../../Camera', () => function () {
return <></>;
});
const history = createMemoryHistory();

View File

@@ -20,10 +20,12 @@ import CoachingConsent from './account-settings/coaching/CoachingConsent';
import appMessages from './i18n';
import './index.scss';
import Head from './head/Head';
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider store={configureStore()}>
<Head />
<Switch>
<Route path="/coaching_consent" component={CoachingConsent} />
<div className="d-flex flex-column" style={{ minHeight: '100vh' }}>
@@ -64,7 +66,9 @@ initialize({
ENABLE_DEMOGRAPHICS_COLLECTION: (process.env.ENABLE_DEMOGRAPHICS_COLLECTION || false),
DEMOGRAPHICS_BASE_URL: process.env.DEMOGRAPHICS_BASE_URL,
ENABLE_COPPA_COMPLIANCE: (process.env.ENABLE_COPPA_COMPLIANCE || false),
ENABLE_DOB_UPDATE: (process.env.ENABLE_DOB_UPDATE || false),
MARKETING_EMAILS_OPT_IN: (process.env.MARKETING_EMAILS_OPT_IN || false),
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK,
}, 'App loadConfig override handler');
},
},

View File

@@ -4,7 +4,7 @@ import { render, waitFor } from '@testing-library/react';
import { useAsyncCall } from '../hooks';
import { LOADING_STATUS, SUCCESS_STATUS, FAILURE_STATUS } from '../constants';
const TestUseAsyncCallHookComponent = ({ asyncFunc }) => {
function TestUseAsyncCallHookComponent({ asyncFunc }) {
const { status, data } = useAsyncCall(asyncFunc);
return (
<>
@@ -12,7 +12,7 @@ const TestUseAsyncCallHookComponent = ({ asyncFunc }) => {
{data && Object.keys(data).length !== 0 && <div data-testid="data">{ data.data }</div>}
</>
);
};
}
TestUseAsyncCallHookComponent.propTypes = {
asyncFunc: PropTypes.func.isRequired,