Compare commits
147 Commits
sajjad/aut
...
split-full
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25909563a4 | ||
|
|
94e823663e | ||
|
|
dc266a613e | ||
|
|
1b5755664c | ||
|
|
02bd8abcd1 | ||
|
|
a6e96f5ed1 | ||
|
|
872aa48675 | ||
|
|
60efe3cbb7 | ||
|
|
167f86c283 | ||
|
|
02d14a6359 | ||
|
|
a6a473ee5c | ||
|
|
8be469680d | ||
|
|
45e84d3f9c | ||
|
|
d6d71587c7 | ||
|
|
6b70692dd4 | ||
|
|
a056f241b5 | ||
|
|
115ce8d7c6 | ||
|
|
6e58c13ef5 | ||
|
|
65e29a021b | ||
|
|
6c91f01226 | ||
|
|
36a9ebef8c | ||
|
|
5c921fb983 | ||
|
|
98699b08ad | ||
|
|
1f3d1d1aee | ||
|
|
fc60d9f7d1 | ||
|
|
ad7099ad38 | ||
|
|
2ea9301c5e | ||
|
|
b9b4492de9 | ||
|
|
d74b5c49d9 | ||
|
|
27545ea4b6 | ||
|
|
db3655c843 | ||
|
|
10a10c8ed9 | ||
|
|
800a5fc6be | ||
|
|
924488c29b | ||
|
|
dae050ecb3 | ||
|
|
3a31cf33e2 | ||
|
|
66a0d5d840 | ||
|
|
26caf857bf | ||
|
|
fa34eae800 | ||
|
|
644b16580d | ||
|
|
479dac8397 | ||
|
|
c097b5b831 | ||
|
|
78722f3e73 | ||
|
|
7f8a270770 | ||
|
|
4ff14c8731 | ||
|
|
a957973105 | ||
|
|
f0b855d87e | ||
|
|
446649735d | ||
|
|
397c237e30 | ||
|
|
e10d6b6384 | ||
|
|
38c186d5a7 | ||
|
|
55c320a88a | ||
|
|
6ec0a22194 | ||
|
|
7bae030713 | ||
|
|
7b4714a22a | ||
|
|
332d6abee7 | ||
|
|
4df13cf0b7 | ||
|
|
512deae883 | ||
|
|
2d11477037 | ||
|
|
5e15969f4a | ||
|
|
37e811d7e5 | ||
|
|
a35a1d1ba6 | ||
|
|
41a9c89d71 | ||
|
|
d469102cee | ||
|
|
c685bdd373 | ||
|
|
daa7ae4d73 | ||
|
|
c5caaeba60 | ||
|
|
a473d79554 | ||
|
|
1b5aa106ab | ||
|
|
d8b5653224 | ||
|
|
4cc4ff6c4b | ||
|
|
48a3c57e5f | ||
|
|
efdefc300e | ||
|
|
9730a4f55d | ||
|
|
fc62241332 | ||
|
|
0846001b6d | ||
|
|
90658722e1 | ||
|
|
240752c6cd | ||
|
|
429d4547e4 | ||
|
|
e278b5f74a | ||
|
|
a723058bc1 | ||
|
|
59fa7d5de3 | ||
|
|
60578189bd | ||
|
|
82cd11e01e | ||
|
|
4a10540d4a | ||
|
|
aeda262fb0 | ||
|
|
dff3903617 | ||
|
|
1399caf003 | ||
|
|
2dfb6bc528 | ||
|
|
a392395876 | ||
|
|
5542311c95 | ||
|
|
21e6bb6eec | ||
|
|
bfa7874108 | ||
|
|
423958c899 | ||
|
|
cb380a2031 | ||
|
|
f4e89efdb4 | ||
|
|
f5cb7a1dbd | ||
|
|
72e601948c | ||
|
|
29e30981ae | ||
|
|
06a61e6a22 | ||
|
|
1c83020b43 | ||
|
|
fa4a0ac2d5 | ||
|
|
2addf57cbd | ||
|
|
d521fd20ec | ||
|
|
38d44ac586 | ||
|
|
4768306f53 | ||
|
|
6c6b527dfc | ||
|
|
e14c9bd1b7 | ||
|
|
a2bdc4031b | ||
|
|
8f38eb9e3a | ||
|
|
c22aa58904 | ||
|
|
067bddf892 | ||
|
|
7e4bccbc29 | ||
|
|
f39bb35dc8 | ||
|
|
0d760c04b7 | ||
|
|
6f113542f5 | ||
|
|
1e4c342703 | ||
|
|
3ce0585d7e | ||
|
|
5bf6dd6361 | ||
|
|
929abdff69 | ||
|
|
f295d69e76 | ||
|
|
32a4c55e4a | ||
|
|
615ba91bdb | ||
|
|
dfb2f89a36 | ||
|
|
c9783234cc | ||
|
|
0513e6c2de | ||
|
|
3cdc0234ef | ||
|
|
e06d12be07 | ||
|
|
56394881fc | ||
|
|
61056240c4 | ||
|
|
a59e7c548c | ||
|
|
f63d7674e2 | ||
|
|
fa3a70e9a9 | ||
|
|
b817a8d122 | ||
|
|
2a6668cef3 | ||
|
|
a802821ae9 | ||
|
|
9e13141f6b | ||
|
|
4b64ce2534 | ||
|
|
c550069e11 | ||
|
|
1e10e9c89c | ||
|
|
cd6c1c0e42 | ||
|
|
5edcee9eb9 | ||
|
|
d41c06b1fd | ||
|
|
2a2c5abc81 | ||
|
|
ccdd648603 | ||
|
|
5c1ea04970 | ||
|
|
5ebd22f088 |
13
.env
13
.env
@@ -15,21 +15,28 @@ SEGMENT_KEY=''
|
|||||||
SITE_NAME=null
|
SITE_NAME=null
|
||||||
INFO_EMAIL=''
|
INFO_EMAIL=''
|
||||||
# ***** Cookies *****
|
# ***** Cookies *****
|
||||||
REGISTER_CONVERSION_COOKIE_NAME=null
|
USER_RETENTION_COOKIE_NAME=null
|
||||||
USER_SURVEY_COOKIE_NAME=null
|
|
||||||
# ***** Links *****
|
# ***** Links *****
|
||||||
LOGIN_ISSUE_SUPPORT_LINK=''
|
LOGIN_ISSUE_SUPPORT_LINK=''
|
||||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
|
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
|
||||||
|
POST_REGISTRATION_REDIRECT_URL=''
|
||||||
|
SEARCH_CATALOG_URL=''
|
||||||
# ***** Features flags *****
|
# ***** Features flags *****
|
||||||
DISABLE_ENTERPRISE_LOGIN=''
|
DISABLE_ENTERPRISE_LOGIN=''
|
||||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
|
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
|
||||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
||||||
ENABLE_PERSONALIZED_RECOMMENDATIONS=''
|
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
SHOW_CONFIGURABLE_EDX_FIELDS=''
|
SHOW_CONFIGURABLE_EDX_FIELDS=''
|
||||||
|
ENABLE_IMAGE_LAYOUT=''
|
||||||
# ***** Zendesk related keys *****
|
# ***** Zendesk related keys *****
|
||||||
ZENDESK_KEY=''
|
ZENDESK_KEY=''
|
||||||
ZENDESK_LOGO_URL=''
|
ZENDESK_LOGO_URL=''
|
||||||
|
# ***** Base Container Images *****
|
||||||
|
BANNER_IMAGE_LARGE=''
|
||||||
|
BANNER_IMAGE_MEDIUM=''
|
||||||
|
BANNER_IMAGE_SMALL=''
|
||||||
|
BANNER_IMAGE_EXTRA_SMALL=''
|
||||||
# ***** Miscellaneous *****
|
# ***** Miscellaneous *****
|
||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
|
|||||||
@@ -19,17 +19,23 @@ REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
|||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
SITE_NAME='Your Platform Name Here'
|
SITE_NAME='Your Platform Name Here'
|
||||||
INFO_EMAIL='info@example.com'
|
INFO_EMAIL='info@example.com'
|
||||||
|
# ***** Features *****
|
||||||
|
ENABLE_DYNAMIC_REGISTRATION_FIELDS='true'
|
||||||
|
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
|
||||||
# ***** Cookies *****
|
# ***** Cookies *****
|
||||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
|
||||||
SESSION_COOKIE_DOMAIN='localhost'
|
SESSION_COOKIE_DOMAIN='localhost'
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
|
||||||
# ***** Links *****
|
# ***** Links *****
|
||||||
LOGIN_ISSUE_SUPPORT_LINK='http://localhost:18000/login-issue-support-url'
|
LOGIN_ISSUE_SUPPORT_LINK='http://localhost:18000/login-issue-support-url'
|
||||||
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
|
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
|
||||||
TOS_LINK='http://localhost:18000/tos'
|
TOS_LINK='http://localhost:18000/tos'
|
||||||
PRIVACY_POLICY='http://localhost:18000/privacy'
|
PRIVACY_POLICY='http://localhost:18000/privacy'
|
||||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK='http://localhost:1999/welcome'
|
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK='http://localhost:1999/welcome'
|
||||||
|
# ***** Base Container Images *****
|
||||||
|
BANNER_IMAGE_LARGE=''
|
||||||
|
BANNER_IMAGE_MEDIUM=''
|
||||||
|
BANNER_IMAGE_SMALL=''
|
||||||
|
BANNER_IMAGE_EXTRA_SMALL=''
|
||||||
# ***** Miscellaneous *****
|
# ***** Miscellaneous *****
|
||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
|
|||||||
@@ -16,7 +16,5 @@ ORDER_HISTORY_URL='http://localhost:1996/orders'
|
|||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||||
SEGMENT_KEY=''
|
SEGMENT_KEY=''
|
||||||
SITE_NAME='Your Platform Name Here'
|
SITE_NAME='Your Platform Name Here'
|
||||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
|
||||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
|
||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
const { createConfig } = require('@edx/frontend-build');
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('eslint', {
|
module.exports = createConfig('eslint', {
|
||||||
rules: {
|
rules: {
|
||||||
// Temporarily update the 'indent', 'template-curly-spacing' and
|
// Temporarily update the 'indent', 'template-curly-spacing' and
|
||||||
// 'no-multiple-empty-lines' rules since they are causing eslint
|
// 'no-multiple-empty-lines' rules since they are causing eslint
|
||||||
// to fail for no apparent reason since upgrading
|
// to fail for no apparent reason since upgrading
|
||||||
// @edx/frontend-build from v3 to v5:
|
// @openedx/frontend-build from v3 to v5:
|
||||||
// - TypeError: Cannot read property 'range' of null
|
// - TypeError: Cannot read property 'range' of null
|
||||||
indent: [
|
indent: [
|
||||||
'error',
|
'error',
|
||||||
|
|||||||
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version-check:
|
version-check:
|
||||||
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -1,5 +1,5 @@
|
|||||||
export TRANSIFEX_RESOURCE = frontend-app-authn
|
export TRANSIFEX_RESOURCE = frontend-app-authn
|
||||||
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA,it_IT,pt_PT,de_DE"
|
transifex_langs = "ar,de,de_DE,es_419,fa_IR,fr,fr_CA,hi,it,it_IT,pt,pt_PT,ru,uk,zh_CN"
|
||||||
|
|
||||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||||
@@ -7,14 +7,14 @@ i18n = ./src/i18n
|
|||||||
transifex_input = $(i18n)/transifex_input.json
|
transifex_input = $(i18n)/transifex_input.json
|
||||||
|
|
||||||
# This directory must match .babelrc .
|
# This directory must match .babelrc .
|
||||||
transifex_temp = ./temp/babel-plugin-react-intl
|
transifex_temp = ./temp/babel-plugin-formatjs
|
||||||
|
|
||||||
precommit:
|
precommit:
|
||||||
npm run lint
|
npm run lint
|
||||||
npm audit
|
npm audit
|
||||||
|
|
||||||
requirements:
|
requirements:
|
||||||
npm install
|
npm ci
|
||||||
|
|
||||||
i18n.extract:
|
i18n.extract:
|
||||||
# Pulling display strings from .jsx files into .json files...
|
# Pulling display strings from .jsx files into .json files...
|
||||||
@@ -53,11 +53,12 @@ pull_translations:
|
|||||||
rm -rf src/i18n/messages
|
rm -rf src/i18n/messages
|
||||||
mkdir src/i18n/messages
|
mkdir src/i18n/messages
|
||||||
cd src/i18n/messages \
|
cd src/i18n/messages \
|
||||||
&& atlas pull --filter=$(transifex_langs) \
|
&& atlas pull $(ATLAS_OPTIONS) \
|
||||||
translations/paragon/src/i18n/messages:paragon \
|
translations/paragon/src/i18n/messages:paragon \
|
||||||
|
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
||||||
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
|
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
|
||||||
|
|
||||||
$(intl_imports) paragon frontend-app-authn
|
$(intl_imports) paragon frontend-platform frontend-app-authn
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# This target is used by Travis.
|
# This target is used by Travis.
|
||||||
|
|||||||
68
README.rst
68
README.rst
@@ -1,12 +1,12 @@
|
|||||||
|
##################
|
||||||
|
frontend-app-authn
|
||||||
|
##################
|
||||||
|
|
||||||
|Build Status| |ci-badge| |Codecov| |semantic-release|
|
|Build Status| |ci-badge| |Codecov| |semantic-release|
|
||||||
|
|
||||||
frontend-app-authn
|
********
|
||||||
====================
|
Purpose
|
||||||
|
********
|
||||||
Please tag **@openedx/vanguards** on any PRs or issues. Thanks!
|
|
||||||
|
|
||||||
Introduction
|
|
||||||
------------
|
|
||||||
|
|
||||||
This is a micro-frontend application responsible for the login, registration and password reset functionality.
|
This is a micro-frontend application responsible for the login, registration and password reset functionality.
|
||||||
|
|
||||||
@@ -22,9 +22,12 @@ This is a micro-frontend application responsible for the login, registration and
|
|||||||
|
|
||||||
- Progressive profiling page
|
- Progressive profiling page
|
||||||
|
|
||||||
|
***************
|
||||||
|
Getting Started
|
||||||
|
***************
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
============
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see
|
|||||||
**Note:** Follow `Enable social auth locally <docs/how_tos/enable_social_auth.rst>`_ for enabling Social Sign-on Buttons (SSO) locally
|
**Note:** Follow `Enable social auth locally <docs/how_tos/enable_social_auth.rst>`_ for enabling Social Sign-on Buttons (SSO) locally
|
||||||
|
|
||||||
Environment Variables/Setup Notes
|
Environment Variables/Setup Notes
|
||||||
---------------------------------
|
=================================
|
||||||
|
|
||||||
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||||
|
|
||||||
@@ -112,9 +115,13 @@ The authentication micro-frontend also requires the following additional variabl
|
|||||||
- Name of MFE, this will be used by the API to get runtime configurations for the specific micro frontend. For a frontend repo `frontend-app-appName`, use `appName` as APP_ID.
|
- Name of MFE, this will be used by the API to get runtime configurations for the specific micro frontend. For a frontend repo `frontend-app-appName`, use `appName` as APP_ID.
|
||||||
- ``authn`` | ``''``
|
- ``authn`` | ``''``
|
||||||
|
|
||||||
|
* - ``ENABLE_IMAGE_LAYOUT``
|
||||||
|
- Enables the image layout feature within the authn. When set to True, this feature allows the inclusion of images in the base container layout. For more details on configuring this feature, please refer to the `Modifying base container <docs/how_tos/modifying_base_container.rst>`_.
|
||||||
|
- ``true`` | ``''`` (empty strings are falsy)
|
||||||
|
|
||||||
|
|
||||||
edX-specific Environment Variables
|
edX-specific Environment Variables
|
||||||
**********************************
|
==================================
|
||||||
|
|
||||||
Furthermore, there are several edX-specific environment variables that enable integrations with closed-source services private to the edX organization, and might be unsupported in Open edX.
|
Furthermore, there are several edX-specific environment variables that enable integrations with closed-source services private to the edX organization, and might be unsupported in Open edX.
|
||||||
|
|
||||||
@@ -138,7 +145,8 @@ For more information see the document: `Micro-frontend applications in Open
|
|||||||
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||||
|
|
||||||
How To Contribute
|
How To Contribute
|
||||||
------------
|
=================
|
||||||
|
|
||||||
Contributions are very welcome, and strongly encouraged! We've
|
Contributions are very welcome, and strongly encouraged! We've
|
||||||
put together `some documentation that describes our contribution process <https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html>`_.
|
put together `some documentation that describes our contribution process <https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html>`_.
|
||||||
|
|
||||||
@@ -149,34 +157,58 @@ can find it it at `PULL_REQUEST_TEMPLATE.md <https://github.com/openedx/frontend
|
|||||||
|
|
||||||
This project is currently accepting all types of contributions, bug fixes and security fixes.
|
This project is currently accepting all types of contributions, bug fixes and security fixes.
|
||||||
|
|
||||||
Open edX Code of Conduct
|
Getting Help
|
||||||
------------------------
|
============
|
||||||
|
|
||||||
|
If you're having trouble, we have discussion forums at
|
||||||
|
https://discuss.openedx.org where you can connect with others in the community.
|
||||||
|
|
||||||
|
Our real-time conversations are on Slack. You can request a `Slack
|
||||||
|
invitation`_, then join our `community Slack workspace`_. Because this is a
|
||||||
|
frontend repository, the best place to discuss it would be in the `#wg-frontend
|
||||||
|
channel`_.
|
||||||
|
|
||||||
|
For anything non-trivial, the best path is to open an issue in this repository
|
||||||
|
with as many details about the issue you are facing as you can provide.
|
||||||
|
|
||||||
|
https://github.com/openedx/frontend-app-authn/issues
|
||||||
|
|
||||||
|
For more information about these options, see the `Getting Help`_ page.
|
||||||
|
|
||||||
|
.. _Slack invitation: https://openedx.org/slack
|
||||||
|
.. _community Slack workspace: https://openedx.slack.com/
|
||||||
|
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
|
||||||
|
.. _Getting Help: https://openedx.org/community/connect
|
||||||
|
|
||||||
|
The Open edX Code of Conduct
|
||||||
|
============================
|
||||||
All community members are expected to follow the `Open edX Code of Conduct <https://openedx.org/code-of-conduct/>`_.
|
All community members are expected to follow the `Open edX Code of Conduct <https://openedx.org/code-of-conduct/>`_.
|
||||||
|
|
||||||
People
|
People
|
||||||
------
|
======
|
||||||
The assigned maintainers for this component and other project details may be
|
The assigned maintainers for this component and other project details may be
|
||||||
found in `Backstage <https://backstage.openedx.org/catalog/default/group/vanguards>`_. Backstage pulls this data from the ``catalog-info.yaml``
|
found in `Backstage <https://backstage.openedx.org/catalog/default/group/vanguards>`_. Backstage pulls this data from the ``catalog-info.yaml``
|
||||||
file in this repo.
|
file in this repo.
|
||||||
|
|
||||||
Reporting Security Issues
|
Reporting Security Issues
|
||||||
-------------------------
|
=========================
|
||||||
|
|
||||||
Please do not report security issues in public. Please email security@edx.org.
|
Please do not report security issues in public. Please email security@openedx.org.
|
||||||
|
|
||||||
Known Issues
|
Known Issues
|
||||||
------------
|
============
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
=======
|
||||||
|
|
||||||
The code in this repository is licensed under the GNU Affero General Public License v3.0, unless
|
The code in this repository is licensed under the GNU Affero General Public License v3.0, unless
|
||||||
otherwise noted.
|
otherwise noted.
|
||||||
|
|
||||||
Please see `LICENSE <https://github.com/openedx/frontend-app-authn/blob/master/LICENSE>`_ for details.
|
Please see `LICENSE <https://github.com/openedx/frontend-app-authn/blob/master/LICENSE>`_ for details.
|
||||||
|
|
||||||
|
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-app-authn.svg?branch=master
|
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-app-authn.svg?branch=master
|
||||||
|
|||||||
39
docs/how_tos/modifying_base_container.rst
Normal file
39
docs/how_tos/modifying_base_container.rst
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
========================================
|
||||||
|
Modifying the Base Container in Authn
|
||||||
|
========================================
|
||||||
|
|
||||||
|
The base container in Authn serves as the fundamental layout structure for rendering different components based on configurations. This document outlines the process for modifying the base container to accommodate changes or customize layouts as needed.
|
||||||
|
|
||||||
|
Understanding Base Container Versions
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
The base container supports two main versions:
|
||||||
|
|
||||||
|
- **Default Layout:** The default layout is the standard layout used when specific configurations do not dictate otherwise.
|
||||||
|
.. image:: ../images/default_layout.png
|
||||||
|
- **Image Layout:** The image layout is an alternative layout option that can be enabled based on configurations.
|
||||||
|
.. image:: ../images/image_layout.png
|
||||||
|
|
||||||
|
Enabling the Image Layout
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
To activate the image layout feature, navigate to your .env file and update the configurations:
|
||||||
|
|
||||||
|
**Update Configuration**
|
||||||
|
|
||||||
|
Locate the ``ENABLE_IMAGE_LAYOUT`` parameter and set its value to ``true``. Additionally, ensure that the Image configuration settings are provided. Your overall configurations should resemble the following:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# ***** Image Layout Configuration *****
|
||||||
|
ENABLE_IMAGE_LAYOUT = True # Set to True to enable image layout feature
|
||||||
|
|
||||||
|
# ***** Base Container Images *****
|
||||||
|
BANNER_IMAGE_LARGE='' # Path to the large banner image
|
||||||
|
BANNER_IMAGE_MEDIUM='' # Path to the medium-sized banner image
|
||||||
|
BANNER_IMAGE_SMALL='' # Path to the small banner image
|
||||||
|
BANNER_IMAGE_EXTRA_SMALL='' # Path to the extra-small banner image
|
||||||
|
|
||||||
|
|
||||||
|
This allows for the customization and adaptation of the base container layout according to specific requirements.
|
||||||
BIN
docs/images/default_layout.png
Normal file
BIN
docs/images/default_layout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
BIN
docs/images/image_layout.png
Normal file
BIN
docs/images/image_layout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -1,4 +1,4 @@
|
|||||||
const { createConfig } = require('@edx/frontend-build');
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('jest', {
|
module.exports = createConfig('jest', {
|
||||||
setupFiles: [
|
setupFiles: [
|
||||||
|
|||||||
16054
package-lock.json
generated
16054
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -11,7 +11,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "fedx-scripts webpack",
|
"build": "fedx-scripts webpack",
|
||||||
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
"i18n_extract": "fedx-scripts formatjs extract",
|
||||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||||
"start": "fedx-scripts webpack-dev-server --progress",
|
"start": "fedx-scripts webpack-dev-server --progress",
|
||||||
@@ -32,52 +32,54 @@
|
|||||||
"url": "https://github.com/openedx/frontend-app-authn/issues"
|
"url": "https://github.com/openedx/frontend-app-authn/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||||
"@edx/frontend-platform": "4.5.1",
|
"@edx/frontend-platform": "7.0.1",
|
||||||
"@edx/paragon": "20.40.2",
|
"@edx/openedx-atlas": "^0.6.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
"@fortawesome/fontawesome-svg-core": "6.5.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
"@fortawesome/free-brands-svg-icons": "6.5.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
|
"@openedx/paragon": "^22.1.1",
|
||||||
"@optimizely/react-sdk": "^2.9.1",
|
"@optimizely/react-sdk": "^2.9.1",
|
||||||
"@redux-devtools/extension": "3.2.5",
|
"@redux-devtools/extension": "3.3.0",
|
||||||
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"algoliasearch": "^4.14.3",
|
"algoliasearch": "^4.14.3",
|
||||||
"classnames": "2.3.2",
|
"algoliasearch-helper": "^3.14.0",
|
||||||
"core-js": "3.30.2",
|
"classnames": "2.5.1",
|
||||||
|
"core-js": "3.35.1",
|
||||||
"fastest-levenshtein": "1.0.16",
|
"fastest-levenshtein": "1.0.16",
|
||||||
"form-urlencoded": "6.1.0",
|
"form-urlencoded": "6.1.4",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"query-string": "7.1.3",
|
"query-string": "7.1.3",
|
||||||
"react": "16.14.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "16.14.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-helmet": "6.1.0",
|
"react-helmet": "6.1.0",
|
||||||
"react-loading-skeleton": "3.2.1",
|
"react-loading-skeleton": "3.3.1",
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.2.9",
|
||||||
"react-responsive": "8.2.0",
|
"react-responsive": "8.2.0",
|
||||||
"react-router": "5.3.4",
|
"react-router": "6.21.3",
|
||||||
"react-router-dom": "5.3.4",
|
"react-router-dom": "6.21.3",
|
||||||
"react-zendesk": "^0.1.13",
|
"react-zendesk": "^0.1.13",
|
||||||
"redux": "4.2.0",
|
"redux": "4.2.0",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-mock-store": "1.5.4",
|
"redux-mock-store": "1.5.4",
|
||||||
"redux-saga": "1.2.3",
|
"redux-saga": "1.3.0",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"regenerator-runtime": "0.13.11",
|
"regenerator-runtime": "0.14.1",
|
||||||
"reselect": "4.1.8",
|
"reselect": "4.1.8",
|
||||||
"universal-cookie": "4.0.4"
|
"universal-cookie": "4.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "^1.1.1",
|
"@edx/browserslist-config": "^1.1.1",
|
||||||
"@edx/frontend-build": "12.8.38",
|
|
||||||
"@edx/reactifex": "1.1.0",
|
"@edx/reactifex": "1.1.0",
|
||||||
"babel-plugin-formatjs": "10.5.1",
|
"@openedx/frontend-build": "13.0.28",
|
||||||
"enzyme": "3.11.0",
|
"babel-plugin-formatjs": "10.5.13",
|
||||||
"enzyme-adapter-react-16": "1.15.7",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-import": "2.27.5",
|
|
||||||
"glob": "7.2.3",
|
"glob": "7.2.3",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"husky": "7.0.4",
|
"husky": "7.0.4",
|
||||||
"jest": "29.5.0",
|
"jest": "29.7.0",
|
||||||
"react-test-renderer": "16.14.0"
|
"react-test-renderer": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,21 @@
|
|||||||
<title>Authn | <%= process.env.SITE_NAME %></title>
|
<title>Authn | <%= process.env.SITE_NAME %></title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon"/>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.9/iframeResizer.contentWindow.min.js"
|
||||||
|
integrity="sha512-mdT/HQRzoRP4laVz49Mndx6rcCGA3IhuyhP3gaY0E9sZPkwbtDk9ttQIq9o8qGCf5VvJv1Xsy3k2yTjfUoczqw=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer">
|
||||||
|
</script>
|
||||||
|
<% if (process.env.OPTIMIZELY_URL) { %>
|
||||||
|
<script
|
||||||
|
src="<%= process.env.OPTIMIZELY_URL %>"
|
||||||
|
></script>
|
||||||
|
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||||
|
<script
|
||||||
|
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
||||||
|
></script>
|
||||||
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import React from 'react';
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
|
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
|
||||||
} from './common-components';
|
} from './common-components';
|
||||||
import configureStore from './data/configureStore';
|
import configureStore from './data/configureStore';
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
PAGE_NOT_FOUND,
|
PAGE_NOT_FOUND,
|
||||||
PASSWORD_RESET_CONFIRM,
|
PASSWORD_RESET_CONFIRM,
|
||||||
RECOMMENDATIONS,
|
RECOMMENDATIONS,
|
||||||
|
REGISTER_EMBEDDED_PAGE,
|
||||||
REGISTER_PAGE,
|
REGISTER_PAGE,
|
||||||
RESET_PAGE,
|
RESET_PAGE,
|
||||||
} from './data/constants';
|
} from './data/constants';
|
||||||
@@ -23,7 +24,9 @@ import { ForgotPasswordPage } from './forgot-password';
|
|||||||
import Logistration from './logistration/Logistration';
|
import Logistration from './logistration/Logistration';
|
||||||
import { ProgressiveProfiling } from './progressive-profiling';
|
import { ProgressiveProfiling } from './progressive-profiling';
|
||||||
import { RecommendationsPage } from './recommendations';
|
import { RecommendationsPage } from './recommendations';
|
||||||
|
import { RegistrationPage } from './register';
|
||||||
import { ResetPasswordPage } from './reset-password';
|
import { ResetPasswordPage } from './reset-password';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
registerIcons();
|
registerIcons();
|
||||||
@@ -34,21 +37,26 @@ const MainApp = () => (
|
|||||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{getConfig().ZENDESK_KEY && <Zendesk />}
|
{getConfig().ZENDESK_KEY && <Zendesk />}
|
||||||
<Switch>
|
<Routes>
|
||||||
<Route exact path="/">
|
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
|
||||||
<Redirect to={updatePathWithQueryParams(REGISTER_PAGE)} />
|
<Route
|
||||||
</Route>
|
path={REGISTER_EMBEDDED_PAGE}
|
||||||
<UnAuthOnlyRoute exact path={LOGIN_PAGE} render={() => <Logistration selectedPage={LOGIN_PAGE} />} />
|
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
|
||||||
<UnAuthOnlyRoute exact path={REGISTER_PAGE} component={Logistration} />
|
/>
|
||||||
<UnAuthOnlyRoute exact path={RESET_PAGE} component={ForgotPasswordPage} />
|
<Route
|
||||||
<Route exact path={PASSWORD_RESET_CONFIRM} component={ResetPasswordPage} />
|
path={LOGIN_PAGE}
|
||||||
<Route exact path={AUTHN_PROGRESSIVE_PROFILING} component={ProgressiveProfiling} />
|
element={
|
||||||
<Route exact path={RECOMMENDATIONS} component={RecommendationsPage} />
|
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
|
||||||
<Route path={PAGE_NOT_FOUND} component={NotFoundPage} />
|
}
|
||||||
<Route path="*">
|
/>
|
||||||
<Redirect to={PAGE_NOT_FOUND} />
|
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
|
||||||
</Route>
|
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
|
||||||
</Switch>
|
<Route path={PASSWORD_RESET_CONFIRM} element={<ResetPasswordPage />} />
|
||||||
|
<Route path={AUTHN_PROGRESSIVE_PROFILING} element={<ProgressiveProfiling />} />
|
||||||
|
<Route path={RECOMMENDATIONS} element={<RecommendationsPage />} />
|
||||||
|
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
|
||||||
|
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
|
||||||
|
</Routes>
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
|
||||||
import { breakpoints } from '@edx/paragon';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import MediaQuery from 'react-responsive';
|
|
||||||
|
|
||||||
import AuthLargeLayout from './AuthLargeLayout';
|
|
||||||
import AuthMediumLayout from './AuthMediumLayout';
|
|
||||||
import AuthSmallLayout from './AuthSmallLayout';
|
|
||||||
import LargeLayout from './LargeLayout';
|
|
||||||
import MediumLayout from './MediumLayout';
|
|
||||||
import SmallLayout from './SmallLayout';
|
|
||||||
|
|
||||||
const BaseComponent = ({ children, showWelcomeBanner }) => {
|
|
||||||
const authenticatedUser = showWelcomeBanner ? getAuthenticatedUser() : null;
|
|
||||||
const username = authenticatedUser ? authenticatedUser.username : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="col-md-12 extra-large-screen-top-stripe" />
|
|
||||||
<div className="layout">
|
|
||||||
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
|
|
||||||
{authenticatedUser ? <AuthSmallLayout username={username} /> : <SmallLayout />}
|
|
||||||
</MediaQuery>
|
|
||||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
|
||||||
{authenticatedUser ? <AuthMediumLayout username={username} /> : <MediumLayout />}
|
|
||||||
</MediaQuery>
|
|
||||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth} maxWidth={breakpoints.extraExtraLarge.maxWidth}>
|
|
||||||
{authenticatedUser ? <AuthLargeLayout username={username} /> : <LargeLayout />}
|
|
||||||
</MediaQuery>
|
|
||||||
|
|
||||||
<div className={classNames('content', { 'align-items-center mt-0': authenticatedUser })}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseComponent.defaultProps = {
|
|
||||||
showWelcomeBanner: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseComponent.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
showWelcomeBanner: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BaseComponent;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
|
||||||
export { default as BaseComponent } from './BaseComponent';
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
|
||||||
import { mount } from 'enzyme';
|
|
||||||
|
|
||||||
import LargeLayout from '../LargeLayout';
|
|
||||||
import MediumLayout from '../MediumLayout';
|
|
||||||
import SmallLayout from '../SmallLayout';
|
|
||||||
|
|
||||||
describe('ScreenLayout', () => {
|
|
||||||
it('should display the form, pass as a child in SmallScreenLayout', () => {
|
|
||||||
const smallScreen = mount(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<div>
|
|
||||||
<SmallLayout />
|
|
||||||
<form>
|
|
||||||
<input type="text" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
expect(smallScreen.find('form').exists()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the form, pass as a child in MediumScreenLayout', () => {
|
|
||||||
const mediumScreen = mount(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<div>
|
|
||||||
<MediumLayout />
|
|
||||||
<form>
|
|
||||||
<input type="text" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
expect(mediumScreen.find('form').exists()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the form, pass as a child in LargeScreenLayout', () => {
|
|
||||||
const largeScreen = mount(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<div>
|
|
||||||
<LargeLayout />
|
|
||||||
<form>
|
|
||||||
<input type="text" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
expect(largeScreen.find('form').exists()).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index';
|
||||||
|
|
||||||
|
describe('Default Layout tests', () => {
|
||||||
|
it('should display the form passed as a child in SmallScreenLayout', () => {
|
||||||
|
render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<div>
|
||||||
|
<DefaultSmallLayout />
|
||||||
|
<form aria-label="form">
|
||||||
|
<input type="text" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
expect(screen.getByRole('form')).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the form passed as a child in MediumScreenLayout', () => {
|
||||||
|
render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<div>
|
||||||
|
<DefaultMediumLayout />
|
||||||
|
<form aria-label="form">
|
||||||
|
<input type="text" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
expect(screen.getByRole('form')).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the form passed as a child in LargeScreenLayout', () => {
|
||||||
|
render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<div>
|
||||||
|
<DefaultLargeLayout />
|
||||||
|
<form aria-label="form">
|
||||||
|
<input type="text" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
expect(screen.getByRole('form')).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, Image } from '@edx/paragon';
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, Image } from '@edx/paragon';
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -23,13 +23,15 @@ const MediumLayout = () => {
|
|||||||
<div>
|
<div>
|
||||||
<h1
|
<h1
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'display-1 text-white mt-5 mb-5 mr-2',
|
'display-1 text-white mt-5 mb-5 mr-2 main-heading',
|
||||||
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
|
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="mr-2">{formatMessage(messages['start.learning'])}</span>
|
<span>
|
||||||
<span className="text-accent-a d-inline-block">
|
{formatMessage(messages['start.learning'])}{' '}
|
||||||
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
<span className="text-accent-a d-inline-block">
|
||||||
|
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, Image } from '@edx/paragon';
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -17,17 +17,18 @@ const SmallLayout = () => {
|
|||||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
|
<div className="d-flex align-items-center m-3.5">
|
||||||
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} />
|
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} />
|
||||||
<h1
|
<h1
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'text-white mt-3.5 mb-3.5',
|
'text-white mt-3.5 mb-3.5',
|
||||||
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="mr-1">{formatMessage(messages['start.learning'])}</span>
|
<span>
|
||||||
<span className="text-accent-a d-inline-block">
|
{formatMessage(messages['start.learning'])}{' '}
|
||||||
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
<span className="text-accent-a d-inline-block">
|
||||||
|
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
3
src/base-container/components/default-layout/index.jsx
Normal file
3
src/base-container/components/default-layout/index.jsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as DefaultLargeLayout } from './LargeLayout';
|
||||||
|
export { default as DefaultMediumLayout } from './MediumLayout';
|
||||||
|
export { default as DefaultSmallLayout } from './SmallLayout';
|
||||||
16
src/base-container/components/default-layout/messages.js
Normal file
16
src/base-container/components/default-layout/messages.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'start.learning': {
|
||||||
|
id: 'start.learning',
|
||||||
|
defaultMessage: 'Start learning',
|
||||||
|
description: 'Header text for logistration MFE pages',
|
||||||
|
},
|
||||||
|
'with.site.name': {
|
||||||
|
id: 'with.site.name',
|
||||||
|
defaultMessage: 'with {siteName}',
|
||||||
|
description: 'Header text with site name for logistration MFE pages',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
const ExtraSmallLayout = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="w-100 bg-primary-500 banner__image extra-small-layout"
|
||||||
|
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_EXTRA_SMALL})` }}
|
||||||
|
>
|
||||||
|
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||||
|
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||||
|
</Hyperlink>
|
||||||
|
<div className="ml-4.5 mr-1 pb-3.5 pt-3.5">
|
||||||
|
<h1 className="banner__heading">
|
||||||
|
<span className="text-light-500">
|
||||||
|
{formatMessage(messages['your.career.turning.point'])}{' '}
|
||||||
|
</span>
|
||||||
|
<span className="text-warning-300">
|
||||||
|
{formatMessage(messages['is.here'])}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExtraSmallLayout;
|
||||||
35
src/base-container/components/image-layout/LargeLayout.jsx
Normal file
35
src/base-container/components/image-layout/LargeLayout.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
const LargeLayout = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="w-50 bg-primary-500 banner__image large-layout"
|
||||||
|
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_LARGE})` }}
|
||||||
|
>
|
||||||
|
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||||
|
<Image className="company-logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||||
|
</Hyperlink>
|
||||||
|
<div className="min-vh-100 p-5 d-flex align-items-end">
|
||||||
|
<h1 className="display-2 mw-sm mb-3 d-flex flex-column flex-shrink-0 justify-content-center">
|
||||||
|
<span className="text-light-500">
|
||||||
|
{formatMessage(messages['your.career.turning.point'])}
|
||||||
|
</span>
|
||||||
|
<span className="text-warning-300">
|
||||||
|
{formatMessage(messages['is.here'])}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LargeLayout;
|
||||||
35
src/base-container/components/image-layout/MediumLayout.jsx
Normal file
35
src/base-container/components/image-layout/MediumLayout.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
const MediumLayout = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="w-100 mb-3 bg-primary-500 banner__image medium-layout"
|
||||||
|
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_MEDIUM})` }}
|
||||||
|
>
|
||||||
|
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||||
|
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||||
|
</Hyperlink>
|
||||||
|
<div className="ml-5 pb-4 pt-4">
|
||||||
|
<h1 className="display-2 banner__heading">
|
||||||
|
<span className="text-light-500">
|
||||||
|
{formatMessage(messages['your.career.turning.point'])}{' '}
|
||||||
|
</span>
|
||||||
|
<span className="text-warning-300 d-inline-block">
|
||||||
|
{formatMessage(messages['is.here'])}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MediumLayout;
|
||||||
34
src/base-container/components/image-layout/SmallLayout.jsx
Normal file
34
src/base-container/components/image-layout/SmallLayout.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
const SmallLayout = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="w-100 bg-primary-500 banner__image small-layout"
|
||||||
|
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_SMALL})` }}
|
||||||
|
>
|
||||||
|
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||||
|
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||||
|
</Hyperlink>
|
||||||
|
<div className="ml-5 mr-1 pb-3.5 pt-3.5">
|
||||||
|
<h1 className="display-2">
|
||||||
|
<span className="text-light-500">
|
||||||
|
{formatMessage(messages['your.career.turning.point'])}{' '}
|
||||||
|
</span>
|
||||||
|
<span className="text-warning-300">
|
||||||
|
{formatMessage(messages['is.here'])}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SmallLayout;
|
||||||
4
src/base-container/components/image-layout/index.jsx
Normal file
4
src/base-container/components/image-layout/index.jsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { default as ImageLargeLayout } from './LargeLayout';
|
||||||
|
export { default as ImageMediumLayout } from './MediumLayout';
|
||||||
|
export { default as ImageSmallLayout } from './SmallLayout';
|
||||||
|
export { default as ImageExtraSmallLayout } from './ExtraSmallLayout';
|
||||||
37
src/base-container/components/image-layout/index.scss
Normal file
37
src/base-container/components/image-layout/index.scss
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
.company-logo {
|
||||||
|
width: 71px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.company-logo {
|
||||||
|
width: 44.67px;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner__image {
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 464px) and (max-width: 575.98px) {
|
||||||
|
.banner__heading {
|
||||||
|
font-size: 60px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 60px;
|
||||||
|
letter-spacing: -1.2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) and (max-width: 800px) {
|
||||||
|
.banner__heading {
|
||||||
|
font-size: 60px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
line-height: 60px !important;
|
||||||
|
letter-spacing: -2px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/base-container/components/image-layout/messages.js
Normal file
16
src/base-container/components/image-layout/messages.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'your.career.turning.point': {
|
||||||
|
id: 'your.career.turning.point',
|
||||||
|
defaultMessage: 'Your career turning point',
|
||||||
|
description: 'Part of the heading "Your career turning point is here." shown on Authn MFE',
|
||||||
|
},
|
||||||
|
'is.here': {
|
||||||
|
id: 'is.here',
|
||||||
|
defaultMessage: 'is here.',
|
||||||
|
description: 'Part of the heading "Your career turning point is here." shown on Authn MFE',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
@@ -2,12 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, Image } from '@edx/paragon';
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const AuthLargeLayout = ({ username }) => {
|
const LargeLayout = ({ fullName }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +20,7 @@ const AuthLargeLayout = ({ username }) => {
|
|||||||
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
|
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="welcome-to-platform data-hj-suppress">
|
<h1 className="welcome-to-platform data-hj-suppress">
|
||||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 className="complete-your-profile">
|
<h2 className="complete-your-profile">
|
||||||
{formatMessage(messages['complete.your.profile.1'])}
|
{formatMessage(messages['complete.your.profile.1'])}
|
||||||
@@ -42,8 +42,8 @@ const AuthLargeLayout = ({ username }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthLargeLayout.propTypes = {
|
LargeLayout.propTypes = {
|
||||||
username: PropTypes.string.isRequired,
|
fullName: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthLargeLayout;
|
export default LargeLayout;
|
||||||
@@ -2,12 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, Image } from '@edx/paragon';
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const AuthMediumLayout = ({ username }) => {
|
const MediumLayout = ({ fullName }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -22,7 +22,7 @@ const AuthMediumLayout = ({ username }) => {
|
|||||||
<div className="medium-yellow-line mt-5 mr-n2" />
|
<div className="medium-yellow-line mt-5 mr-n2" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="h3 data-hj-suppress mw-320">
|
<h1 className="h3 data-hj-suppress mw-320">
|
||||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 className="display-1">
|
<h2 className="display-1">
|
||||||
{formatMessage(messages['complete.your.profile.1'])}
|
{formatMessage(messages['complete.your.profile.1'])}
|
||||||
@@ -45,8 +45,8 @@ const AuthMediumLayout = ({ username }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthMediumLayout.propTypes = {
|
MediumLayout.propTypes = {
|
||||||
username: PropTypes.string.isRequired,
|
fullName: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthMediumLayout;
|
export default MediumLayout;
|
||||||
@@ -2,12 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink, Image } from '@edx/paragon';
|
import { Hyperlink, Image } from '@openedx/paragon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const AuthSmallLayout = ({ username }) => {
|
const SmallLayout = ({ fullName }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -16,11 +16,11 @@ const AuthSmallLayout = ({ username }) => {
|
|||||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
|
<div className="d-flex align-items-center m-3.5">
|
||||||
<div className="small-yellow-line mt-4.5" />
|
<div className="small-yellow-line mt-4.5" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="h5 data-hj-suppress">
|
<h1 className="h5 data-hj-suppress">
|
||||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 className="h1">
|
<h2 className="h1">
|
||||||
{formatMessage(messages['complete.your.profile.1'])}
|
{formatMessage(messages['complete.your.profile.1'])}
|
||||||
@@ -34,8 +34,8 @@ const AuthSmallLayout = ({ username }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthSmallLayout.propTypes = {
|
SmallLayout.propTypes = {
|
||||||
username: PropTypes.string.isRequired,
|
fullName: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthSmallLayout;
|
export default SmallLayout;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as AuthLargeLayout } from './LargeLayout';
|
||||||
|
export { default as AuthMediumLayout } from './MediumLayout';
|
||||||
|
export { default as AuthSmallLayout } from './SmallLayout';
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
'start.learning': {
|
'welcome.to.platform': {
|
||||||
id: 'start.learning',
|
id: 'welcome.to.platform',
|
||||||
defaultMessage: 'Start learning',
|
defaultMessage: 'Welcome to {siteName}, {fullName}!',
|
||||||
description: 'Header text for logistration MFE pages',
|
description: 'Welcome message that appears on progressive profile page',
|
||||||
},
|
},
|
||||||
'with.site.name': {
|
|
||||||
id: 'with.site.name',
|
|
||||||
defaultMessage: 'with {siteName}',
|
|
||||||
description: 'Header text with site name for logistration MFE pages',
|
|
||||||
},
|
|
||||||
// authenticated user base component text
|
|
||||||
'complete.your.profile.1': {
|
'complete.your.profile.1': {
|
||||||
id: 'complete.your.profile.1',
|
id: 'complete.your.profile.1',
|
||||||
defaultMessage: 'Complete',
|
defaultMessage: 'Complete',
|
||||||
@@ -22,11 +16,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'your profile',
|
defaultMessage: 'your profile',
|
||||||
description: 'part of text "complete your profile"',
|
description: 'part of text "complete your profile"',
|
||||||
},
|
},
|
||||||
'welcome.to.platform': {
|
|
||||||
id: 'welcome.to.platform',
|
|
||||||
defaultMessage: 'Welcome to {siteName}, {username}!',
|
|
||||||
description: 'Welcome message that appears on progressive profile page',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
4
src/base-container/data/constants.js
Normal file
4
src/base-container/data/constants.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
const IMAGE_LAYOUT = 'image-layout';
|
||||||
|
const DEFAULT_LAYOUT = 'default-layout';
|
||||||
|
|
||||||
|
export { DEFAULT_LAYOUT, IMAGE_LAYOUT };
|
||||||
89
src/base-container/index.jsx
Normal file
89
src/base-container/index.jsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { breakpoints } from '@openedx/paragon';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MediaQuery from 'react-responsive';
|
||||||
|
|
||||||
|
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './components/default-layout';
|
||||||
|
import {
|
||||||
|
ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout,
|
||||||
|
} from './components/image-layout';
|
||||||
|
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
|
||||||
|
import { DEFAULT_LAYOUT, IMAGE_LAYOUT } from './data/constants';
|
||||||
|
|
||||||
|
const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
|
||||||
|
const [baseContainerVersion, setBaseContainerVersion] = useState(DEFAULT_LAYOUT);
|
||||||
|
const enableImageLayout = getConfig().ENABLE_IMAGE_LAYOUT;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initRebrandExperiment = () => {
|
||||||
|
if (window.experiments?.rebrandExperiment) {
|
||||||
|
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
|
||||||
|
} else {
|
||||||
|
window.experiments = window.experiments || {};
|
||||||
|
window.experiments.rebrandExperiment = {};
|
||||||
|
window.experiments.rebrandExperiment.handleLoaded = () => {
|
||||||
|
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
initRebrandExperiment();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (baseContainerVersion === IMAGE_LAYOUT || enableImageLayout) {
|
||||||
|
return (
|
||||||
|
<div className="layout">
|
||||||
|
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>
|
||||||
|
{showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <ImageExtraSmallLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth - 1}>
|
||||||
|
{showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <ImageSmallLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||||
|
{showWelcomeBanner ? <AuthMediumLayout fullName={fullName} /> : <ImageMediumLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||||
|
{showWelcomeBanner ? <AuthLargeLayout fullName={fullName} /> : <ImageLargeLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="col-md-12 extra-large-screen-top-stripe" />
|
||||||
|
<div className="layout">
|
||||||
|
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
|
||||||
|
{showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <DefaultSmallLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||||
|
{showWelcomeBanner ? <AuthMediumLayout fullName={fullName} /> : <DefaultMediumLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||||
|
{showWelcomeBanner ? <AuthLargeLayout fullName={fullName} /> : <DefaultLargeLayout />}
|
||||||
|
</MediaQuery>
|
||||||
|
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseContainer.defaultProps = {
|
||||||
|
showWelcomeBanner: false,
|
||||||
|
fullName: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseContainer.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
showWelcomeBanner: PropTypes.bool,
|
||||||
|
fullName: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BaseContainer;
|
||||||
59
src/base-container/tests/BaseContainer.test.jsx
Normal file
59
src/base-container/tests/BaseContainer.test.jsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { mergeConfig } from '@edx/frontend-platform';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { Context as ResponsiveContext } from 'react-responsive';
|
||||||
|
|
||||||
|
import BaseContainer from '../index';
|
||||||
|
|
||||||
|
const LargeScreen = {
|
||||||
|
wrappingComponent: ResponsiveContext.Provider,
|
||||||
|
wrappingComponentProps: { value: { width: 1200 } },
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Base component tests', () => {
|
||||||
|
it('should show default layout', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<BaseContainer />
|
||||||
|
</IntlProvider>,
|
||||||
|
LargeScreen,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('.banner__image')).toBeNull();
|
||||||
|
expect(container.querySelector('.large-screen-svg-primary')).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[experiment] should show image layout for treatment group', () => {
|
||||||
|
window.experiments = {
|
||||||
|
rebrandExperiment: {
|
||||||
|
variation: 'image-layout',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<BaseContainer />
|
||||||
|
</IntlProvider>,
|
||||||
|
LargeScreen,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('.banner__image')).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => {
|
||||||
|
mergeConfig({
|
||||||
|
ENABLE_IMAGE_LAYOUT: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<BaseContainer showWelcomeBanner={false} />
|
||||||
|
</IntlProvider>,
|
||||||
|
LargeScreen,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('.banner__image')).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
28
src/common-components/EmbeddedRegistrationRoute.jsx
Normal file
28
src/common-components/EmbeddedRegistrationRoute.jsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PAGE_NOT_FOUND } from '../data/constants';
|
||||||
|
import { isHostAvailableInQueryParams } from '../data/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This wrapper redirects the requester to embedded register page only if host
|
||||||
|
* query param is present.
|
||||||
|
*/
|
||||||
|
const EmbeddedRegistrationRoute = ({ children }) => {
|
||||||
|
const registrationEmbedded = isHostAvailableInQueryParams();
|
||||||
|
|
||||||
|
// Show registration page for embedded experience even if the user is authenticated
|
||||||
|
if (registrationEmbedded) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Navigate to={PAGE_NOT_FOUND} replace />;
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbeddedRegistrationRoute.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmbeddedRegistrationRoute;
|
||||||
@@ -2,12 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import {
|
import {
|
||||||
Button, Form,
|
Button, Form,
|
||||||
Icon,
|
Icon,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { Login } from '@edx/paragon/icons';
|
import { Login } from '@openedx/paragon/icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -19,7 +19,8 @@ import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
|||||||
const EnterpriseSSO = (props) => {
|
const EnterpriseSSO = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const tpaProvider = props.provider;
|
const tpaProvider = props.provider;
|
||||||
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false;
|
const hideRegistrationLink = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false
|
||||||
|
|| getConfig().SHOW_REGISTRATION_LINKS === false;
|
||||||
|
|
||||||
const handleSubmit = (e, url) => {
|
const handleSubmit = (e, url) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -74,7 +75,7 @@ const EnterpriseSSO = (props) => {
|
|||||||
className="w-100"
|
className="w-100"
|
||||||
onClick={(e) => handleClick(e)}
|
onClick={(e) => handleClick(e)}
|
||||||
>
|
>
|
||||||
{disablePublicAccountCreation
|
{hideRegistrationLink
|
||||||
? formatMessage(messages['enterprisetpa.login.button.text.public.account.creation.disabled'])
|
? formatMessage(messages['enterprisetpa.login.button.text.public.account.creation.disabled'])
|
||||||
: formatMessage(messages['enterprisetpa.login.button.text'])}
|
: formatMessage(messages['enterprisetpa.login.button.text'])}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Form, TransitionReplace,
|
Form, TransitionReplace,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormGroup = (props) => {
|
const FormGroup = (props) => {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Button, Hyperlink, Icon } from '@edx/paragon';
|
import { Button, Hyperlink, Icon } from '@openedx/paragon';
|
||||||
import { Institution } from '@edx/paragon/icons';
|
import { Institution } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,41 +1,103 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
|
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import {
|
import {
|
||||||
Check, Remove, Visibility, VisibilityOff,
|
Check, Remove, Visibility, VisibilityOff,
|
||||||
} from '@edx/paragon/icons';
|
} from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
|
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
|
||||||
|
import { clearRegistrationBackendError, fetchRealtimeValidations } from '../register/data/actions';
|
||||||
|
import { validatePasswordField } from '../register/data/utils';
|
||||||
|
|
||||||
const PasswordField = (props) => {
|
const PasswordField = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const validationApiRateLimited = useSelector(state => state.register.validationApiRateLimited);
|
||||||
const [isPasswordHidden, setHiddenTrue, setHiddenFalse] = useToggle(true);
|
const [isPasswordHidden, setHiddenTrue, setHiddenFalse] = useToggle(true);
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
|
|
||||||
const handleBlur = (e) => {
|
const handleBlur = (e) => {
|
||||||
if (props.handleBlur) { props.handleBlur(e); }
|
const { name, value } = e.target;
|
||||||
|
if (name === props.name && e.relatedTarget?.name === 'passwordIcon') {
|
||||||
|
return; // Do not run validations on password icon click
|
||||||
|
}
|
||||||
|
|
||||||
|
let passwordValue = value;
|
||||||
|
if (name === 'passwordIcon') {
|
||||||
|
// To validate actual password value when onBlur is triggered by focusing out the password icon
|
||||||
|
passwordValue = props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.handleBlur) {
|
||||||
|
props.handleBlur({
|
||||||
|
target: {
|
||||||
|
name: props.name,
|
||||||
|
value: passwordValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setShowTooltip(props.showRequirements && false);
|
setShowTooltip(props.showRequirements && false);
|
||||||
|
if (props.handleErrorChange) { // If rendering from register page
|
||||||
|
const fieldError = validatePasswordField(passwordValue, formatMessage);
|
||||||
|
if (fieldError) {
|
||||||
|
props.handleErrorChange('password', fieldError);
|
||||||
|
} else if (!validationApiRateLimited) {
|
||||||
|
dispatch(fetchRealtimeValidations({ password: passwordValue }));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFocus = (e) => {
|
const handleFocus = (e) => {
|
||||||
|
if (e.target?.name === 'passwordIcon') {
|
||||||
|
return; // Do not clear error on password icon focus
|
||||||
|
}
|
||||||
|
|
||||||
if (props.handleFocus) {
|
if (props.handleFocus) {
|
||||||
props.handleFocus(e);
|
props.handleFocus(e);
|
||||||
}
|
}
|
||||||
|
if (props.handleErrorChange) {
|
||||||
|
props.handleErrorChange('password', '');
|
||||||
|
dispatch(clearRegistrationBackendError('password'));
|
||||||
|
}
|
||||||
setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
|
setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
|
||||||
};
|
};
|
||||||
|
|
||||||
const HideButton = (
|
const HideButton = (
|
||||||
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={VisibilityOff} iconAs={Icon} onClick={setHiddenTrue} size="sm" variant="secondary" alt={formatMessage(messages['hide.password'])} />
|
<IconButton
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
name="passwordIcon"
|
||||||
|
src={VisibilityOff}
|
||||||
|
iconAs={Icon}
|
||||||
|
onClick={setHiddenTrue}
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
alt={formatMessage(messages['hide.password'])}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ShowButton = (
|
const ShowButton = (
|
||||||
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={Visibility} iconAs={Icon} onClick={setHiddenFalse} size="sm" variant="secondary" alt={formatMessage(messages['show.password'])} />
|
<IconButton
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
name="passwordIcon"
|
||||||
|
src={Visibility}
|
||||||
|
iconAs={Icon}
|
||||||
|
onClick={setHiddenFalse}
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
alt={formatMessage(messages['show.password'])}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const placement = window.innerWidth < 768 ? 'top' : 'left';
|
const placement = window.innerWidth < 768 ? 'top' : 'left';
|
||||||
const tooltip = (
|
const tooltip = (
|
||||||
<Tooltip id={`password-requirement-${placement}`}>
|
<Tooltip id={`password-requirement-${placement}`}>
|
||||||
@@ -76,7 +138,7 @@ const PasswordField = (props) => {
|
|||||||
{props.errorMessage !== '' && (
|
{props.errorMessage !== '' && (
|
||||||
<Form.Control.Feedback key="error" className="form-text-size" hasIcon={false} feedback-for={props.name} type="invalid">
|
<Form.Control.Feedback key="error" className="form-text-size" hasIcon={false} feedback-for={props.name} type="invalid">
|
||||||
{props.errorMessage}
|
{props.errorMessage}
|
||||||
<span className="sr-only">{formatMessage(messages['password.sr.only.helping.text'])}</span>
|
{props.showScreenReaderText && <span className="sr-only">{formatMessage(messages['password.sr.only.helping.text'])}</span>}
|
||||||
</Form.Control.Feedback>
|
</Form.Control.Feedback>
|
||||||
)}
|
)}
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
@@ -89,7 +151,9 @@ PasswordField.defaultProps = {
|
|||||||
handleBlur: null,
|
handleBlur: null,
|
||||||
handleFocus: null,
|
handleFocus: null,
|
||||||
handleChange: () => {},
|
handleChange: () => {},
|
||||||
|
handleErrorChange: null,
|
||||||
showRequirements: true,
|
showRequirements: true,
|
||||||
|
showScreenReaderText: true,
|
||||||
autoComplete: null,
|
autoComplete: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,10 +164,12 @@ PasswordField.propTypes = {
|
|||||||
handleBlur: PropTypes.func,
|
handleBlur: PropTypes.func,
|
||||||
handleFocus: PropTypes.func,
|
handleFocus: PropTypes.func,
|
||||||
handleChange: PropTypes.func,
|
handleChange: PropTypes.func,
|
||||||
|
handleErrorChange: PropTypes.func,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
showRequirements: PropTypes.bool,
|
showRequirements: PropTypes.bool,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
autoComplete: PropTypes.string,
|
autoComplete: PropTypes.string,
|
||||||
|
showScreenReaderText: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasswordField;
|
export default PasswordField;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS } from '../data/constants';
|
import {
|
||||||
|
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
|
||||||
|
} from '../data/constants';
|
||||||
import { setCookie } from '../data/utils';
|
import { setCookie } from '../data/utils';
|
||||||
|
|
||||||
const RedirectLogistration = (props) => {
|
const RedirectLogistration = (props) => {
|
||||||
const {
|
const {
|
||||||
|
authenticatedUser,
|
||||||
finishAuthUrl,
|
finishAuthUrl,
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
redirectToProgressiveProfilingPage,
|
redirectToProgressiveProfilingPage,
|
||||||
@@ -17,6 +18,8 @@ const RedirectLogistration = (props) => {
|
|||||||
redirectToRecommendationsPage,
|
redirectToRecommendationsPage,
|
||||||
educationLevel,
|
educationLevel,
|
||||||
userId,
|
userId,
|
||||||
|
registrationEmbedded,
|
||||||
|
host,
|
||||||
} = props;
|
} = props;
|
||||||
let finalRedirectUrl = '';
|
let finalRedirectUrl = '';
|
||||||
|
|
||||||
@@ -35,15 +38,24 @@ const RedirectLogistration = (props) => {
|
|||||||
if (redirectToProgressiveProfilingPage) {
|
if (redirectToProgressiveProfilingPage) {
|
||||||
// TODO: Do we still need this cookie?
|
// TODO: Do we still need this cookie?
|
||||||
setCookie('van-504-returning-user', true);
|
setCookie('van-504-returning-user', true);
|
||||||
|
|
||||||
|
if (registrationEmbedded) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
action: REDIRECT,
|
||||||
|
redirectUrl: getConfig().POST_REGISTRATION_REDIRECT_URL,
|
||||||
|
}, host);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
||||||
return (
|
return (
|
||||||
<Redirect to={{
|
<Navigate
|
||||||
pathname: AUTHN_PROGRESSIVE_PROFILING,
|
to={AUTHN_PROGRESSIVE_PROFILING}
|
||||||
state: {
|
state={{
|
||||||
registrationResult,
|
registrationResult,
|
||||||
optionalFields,
|
optionalFields,
|
||||||
},
|
authenticatedUser,
|
||||||
}}
|
}}
|
||||||
|
replace
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,14 +64,14 @@ const RedirectLogistration = (props) => {
|
|||||||
if (redirectToRecommendationsPage) {
|
if (redirectToRecommendationsPage) {
|
||||||
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
||||||
return (
|
return (
|
||||||
<Redirect to={{
|
<Navigate
|
||||||
pathname: RECOMMENDATIONS,
|
to={RECOMMENDATIONS}
|
||||||
state: {
|
state={{
|
||||||
registrationResult,
|
registrationResult,
|
||||||
educationLevel,
|
educationLevel,
|
||||||
userId,
|
userId,
|
||||||
},
|
}}
|
||||||
}}
|
replace
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -71,6 +83,7 @@ const RedirectLogistration = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RedirectLogistration.defaultProps = {
|
RedirectLogistration.defaultProps = {
|
||||||
|
authenticatedUser: {},
|
||||||
educationLevel: null,
|
educationLevel: null,
|
||||||
finishAuthUrl: null,
|
finishAuthUrl: null,
|
||||||
success: false,
|
success: false,
|
||||||
@@ -79,9 +92,12 @@ RedirectLogistration.defaultProps = {
|
|||||||
optionalFields: {},
|
optionalFields: {},
|
||||||
redirectToRecommendationsPage: false,
|
redirectToRecommendationsPage: false,
|
||||||
userId: null,
|
userId: null,
|
||||||
|
registrationEmbedded: false,
|
||||||
|
host: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
RedirectLogistration.propTypes = {
|
RedirectLogistration.propTypes = {
|
||||||
|
authenticatedUser: PropTypes.shape({}),
|
||||||
educationLevel: PropTypes.string,
|
educationLevel: PropTypes.string,
|
||||||
finishAuthUrl: PropTypes.string,
|
finishAuthUrl: PropTypes.string,
|
||||||
success: PropTypes.bool,
|
success: PropTypes.bool,
|
||||||
@@ -90,6 +106,8 @@ RedirectLogistration.propTypes = {
|
|||||||
optionalFields: PropTypes.shape({}),
|
optionalFields: PropTypes.shape({}),
|
||||||
redirectToRecommendationsPage: PropTypes.bool,
|
redirectToRecommendationsPage: PropTypes.bool,
|
||||||
userId: PropTypes.number,
|
userId: PropTypes.number,
|
||||||
|
registrationEmbedded: PropTypes.bool,
|
||||||
|
host: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RedirectLogistration;
|
export default RedirectLogistration;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Icon } from '@edx/paragon';
|
|
||||||
import { Login } from '@edx/paragon/icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { Icon } from '@openedx/paragon';
|
||||||
|
import { Login } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import {
|
||||||
|
Hyperlink, Icon,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import { Institution } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Skeleton from 'react-loading-skeleton';
|
import Skeleton from 'react-loading-skeleton';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
import {
|
||||||
|
ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
|
||||||
|
} from '../data/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RenderInstitutionButton,
|
RenderInstitutionButton,
|
||||||
SocialAuthProviders,
|
SocialAuthProviders,
|
||||||
} from '../common-components';
|
} from './index';
|
||||||
import {
|
|
||||||
PENDING_STATE, REGISTER_PAGE,
|
|
||||||
} from '../data/constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the Single sign-on (SSO) buttons for the providers passed.
|
* This component renders the Single sign-on (SSO) buttons for the providers passed.
|
||||||
@@ -20,19 +25,33 @@ import {
|
|||||||
const ThirdPartyAuth = (props) => {
|
const ThirdPartyAuth = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const {
|
const {
|
||||||
providers, secondaryProviders, currentProvider, handleInstitutionLogin, thirdPartyAuthApiStatus,
|
providers,
|
||||||
|
secondaryProviders,
|
||||||
|
currentProvider,
|
||||||
|
handleInstitutionLogin,
|
||||||
|
thirdPartyAuthApiStatus,
|
||||||
|
isLoginPage,
|
||||||
} = props;
|
} = props;
|
||||||
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
|
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
|
||||||
const isSocialAuthActive = !!providers.length && !currentProvider;
|
const isSocialAuthActive = !!providers.length && !currentProvider;
|
||||||
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
||||||
|
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{((isEnterpriseLoginDisabled && isInstitutionAuthActive) || isSocialAuthActive) && (
|
{((isEnterpriseLoginDisabled && isInstitutionAuthActive) || isSocialAuthActive) && (
|
||||||
<div className="mt-4 mb-3 h4">
|
<div className="mt-4 mb-3 h4">
|
||||||
{formatMessage(messages['registration.other.options.heading'])}
|
{isLoginPage
|
||||||
|
? formatMessage(messages['login.other.options.heading'])
|
||||||
|
: formatMessage(messages['registration.other.options.heading'])}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(isLoginPage && !isEnterpriseLoginDisabled && isSocialAuthActive) && (
|
||||||
|
<Hyperlink className="btn btn-link btn-sm text-body p-0 mb-4" destination={enterpriseLoginURL}>
|
||||||
|
<Icon src={Institution} className="institute-icon" />
|
||||||
|
{formatMessage(messages['enterprise.login.btn.text'])}
|
||||||
|
</Hyperlink>
|
||||||
|
)}
|
||||||
|
|
||||||
{thirdPartyAuthApiStatus === PENDING_STATE ? (
|
{thirdPartyAuthApiStatus === PENDING_STATE ? (
|
||||||
<Skeleton className="tpa-skeleton" height={36} count={2} />
|
<Skeleton className="tpa-skeleton" height={36} count={2} />
|
||||||
@@ -41,12 +60,15 @@ const ThirdPartyAuth = (props) => {
|
|||||||
{(isEnterpriseLoginDisabled && isInstitutionAuthActive) && (
|
{(isEnterpriseLoginDisabled && isInstitutionAuthActive) && (
|
||||||
<RenderInstitutionButton
|
<RenderInstitutionButton
|
||||||
onSubmitHandler={handleInstitutionLogin}
|
onSubmitHandler={handleInstitutionLogin}
|
||||||
buttonTitle={formatMessage(messages['register.institution.login.button'])}
|
buttonTitle={formatMessage(messages['institution.login.button'])}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isSocialAuthActive && (
|
{isSocialAuthActive && (
|
||||||
<div className="row m-0">
|
<div className="row m-0">
|
||||||
<SocialAuthProviders socialAuthProviders={providers} referrer={REGISTER_PAGE} />
|
<SocialAuthProviders
|
||||||
|
socialAuthProviders={providers}
|
||||||
|
referrer={isLoginPage ? LOGIN_PAGE : REGISTER_PAGE}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -59,7 +81,8 @@ ThirdPartyAuth.defaultProps = {
|
|||||||
currentProvider: null,
|
currentProvider: null,
|
||||||
providers: [],
|
providers: [],
|
||||||
secondaryProviders: [],
|
secondaryProviders: [],
|
||||||
thirdPartyAuthApiStatus: 'pending',
|
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||||
|
isLoginPage: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
ThirdPartyAuth.propTypes = {
|
ThirdPartyAuth.propTypes = {
|
||||||
@@ -86,6 +109,7 @@ ThirdPartyAuth.propTypes = {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
thirdPartyAuthApiStatus: PropTypes.string,
|
thirdPartyAuthApiStatus: PropTypes.string,
|
||||||
|
isLoginPage: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThirdPartyAuth;
|
export default ThirdPartyAuth;
|
||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Alert } from '@edx/paragon';
|
import { Alert } from '@openedx/paragon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
import { Route } from 'react-router-dom';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { DEFAULT_REDIRECT_URL } from '../data/constants';
|
import {
|
||||||
|
DEFAULT_REDIRECT_URL,
|
||||||
|
} from '../data/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This wrapper redirects the requester to our default redirect url if they are
|
* This wrapper redirects the requester to our default redirect url if they are
|
||||||
* already authenticated.
|
* already authenticated.
|
||||||
*/
|
*/
|
||||||
const UnAuthOnlyRoute = (props) => {
|
const UnAuthOnlyRoute = ({ children }) => {
|
||||||
const [authUser, setAuthUser] = useState({});
|
const [authUser, setAuthUser] = useState({});
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
|
||||||
@@ -27,10 +29,14 @@ const UnAuthOnlyRoute = (props) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Route {...props} />;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnAuthOnlyRoute.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default UnAuthOnlyRoute;
|
export default UnAuthOnlyRoute;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
|||||||
import Zendesk from 'react-zendesk';
|
import Zendesk from 'react-zendesk';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
import { REGISTER_EMBEDDED_PAGE } from '../data/constants';
|
||||||
|
|
||||||
const ZendeskHelp = () => {
|
const ZendeskHelp = () => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@@ -48,6 +49,10 @@ const ZendeskHelp = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (window.location.pathname === REGISTER_EMBEDDED_PAGE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
|
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
|
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
|
||||||
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
|
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
fieldDescriptions: {},
|
fieldDescriptions: {},
|
||||||
optionalFields: {},
|
optionalFields: {
|
||||||
|
fields: {},
|
||||||
|
extended_profile: [],
|
||||||
|
},
|
||||||
thirdPartyAuthApiStatus: null,
|
thirdPartyAuthApiStatus: null,
|
||||||
thirdPartyAuthContext: {
|
thirdPartyAuthContext: {
|
||||||
|
autoSubmitRegForm: false,
|
||||||
currentProvider: null,
|
currentProvider: null,
|
||||||
finishAuthUrl: null,
|
finishAuthUrl: null,
|
||||||
countryCode: null,
|
countryCode: null,
|
||||||
@@ -13,6 +17,7 @@ export const defaultState = {
|
|||||||
secondaryProviders: [],
|
secondaryProviders: [],
|
||||||
pipelineUserDetails: null,
|
pipelineUserDetails: null,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
|
welcomePageRedirectUrl: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,7 +31,7 @@ const reducer = (state = defaultState, action = {}) => {
|
|||||||
case THIRD_PARTY_AUTH_CONTEXT.SUCCESS: {
|
case THIRD_PARTY_AUTH_CONTEXT.SUCCESS: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
fieldDescriptions: action.payload.fieldDescriptions.fields,
|
fieldDescriptions: action.payload.fieldDescriptions?.fields,
|
||||||
optionalFields: action.payload.optionalFields,
|
optionalFields: action.payload.optionalFields,
|
||||||
thirdPartyAuthContext: action.payload.thirdPartyAuthContext,
|
thirdPartyAuthContext: action.payload.thirdPartyAuthContext,
|
||||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||||
@@ -35,7 +40,11 @@ const reducer = (state = defaultState, action = {}) => {
|
|||||||
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
|
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
thirdPartyAuthApiStatus: FAILURE_STATE,
|
||||||
|
thirdPartyAuthContext: {
|
||||||
|
...state.thirdPartyAuthContext,
|
||||||
|
errorMessage: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
case THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG:
|
case THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe('common components reducer', () => {
|
|||||||
providers: [],
|
providers: [],
|
||||||
secondaryProviders: [],
|
secondaryProviders: [],
|
||||||
pipelineUserDetails: null,
|
pipelineUserDetails: null,
|
||||||
errorMessage: 'An error occured',
|
errorMessage: 'An error occurred',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export { default as RedirectLogistration } from './RedirectLogistration';
|
export { default as RedirectLogistration } from './RedirectLogistration';
|
||||||
export { default as registerIcons } from './RegisterFaIcons';
|
export { default as registerIcons } from './RegisterFaIcons';
|
||||||
|
export { default as EmbeddedRegistrationRoute } from './EmbeddedRegistrationRoute';
|
||||||
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
|
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
|
||||||
export { default as NotFoundPage } from './NotFoundPage';
|
export { default as NotFoundPage } from './NotFoundPage';
|
||||||
export { default as SocialAuthProviders } from './SocialAuthProviders';
|
export { default as SocialAuthProviders } from './SocialAuthProviders';
|
||||||
|
|||||||
@@ -112,6 +112,26 @@ const messages = defineMessages({
|
|||||||
description: 'Select ticket form',
|
description: 'Select ticket form',
|
||||||
defaultMessage: 'Please choose your request type:',
|
defaultMessage: 'Please choose your request type:',
|
||||||
},
|
},
|
||||||
|
'registration.other.options.heading': {
|
||||||
|
id: 'registration.other.options.heading',
|
||||||
|
defaultMessage: 'Or register with:',
|
||||||
|
description: 'A message that appears above third party auth providers i.e saml, google, facebook etc',
|
||||||
|
},
|
||||||
|
'institution.login.button': {
|
||||||
|
id: 'institution.login.button',
|
||||||
|
defaultMessage: 'Institution/campus credentials',
|
||||||
|
description: 'shows institutions list',
|
||||||
|
},
|
||||||
|
'login.other.options.heading': {
|
||||||
|
id: 'login.other.options.heading',
|
||||||
|
defaultMessage: 'Or sign in with:',
|
||||||
|
description: 'Text that appears above other sign in options like social auth buttons',
|
||||||
|
},
|
||||||
|
'enterprise.login.btn.text': {
|
||||||
|
id: 'enterprise.login.btn.text',
|
||||||
|
defaultMessage: 'Company or school credentials',
|
||||||
|
description: 'Company or school login link text.',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/* eslint-disable import/no-import-module-exports */
|
||||||
|
/* eslint-disable react/function-component-definition */
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
import { REGISTER_EMBEDDED_PAGE } from '../../data/constants';
|
||||||
|
import EmbeddedRegistrationRoute from '../EmbeddedRegistrationRoute';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MemoryRouter, Route, BrowserRouter as Router, Routes,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
const RRD = require('react-router-dom');
|
||||||
|
// Just render plain div with its children
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
RRD.BrowserRouter = ({ children }) => <div>{ children }</div>;
|
||||||
|
module.exports = RRD;
|
||||||
|
|
||||||
|
const TestApp = () => (
|
||||||
|
<Router>
|
||||||
|
<div>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path={REGISTER_EMBEDDED_PAGE}
|
||||||
|
element={<EmbeddedRegistrationRoute><span>Embedded Register Page</span></EmbeddedRegistrationRoute>}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('EmbeddedRegistrationRoute', () => {
|
||||||
|
const routerWrapper = () => (
|
||||||
|
<MemoryRouter initialEntries={[REGISTER_EMBEDDED_PAGE]}>
|
||||||
|
<TestApp />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render embedded register page if host query param is not available in the url', async () => {
|
||||||
|
let embeddedRegistrationPage = null;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const { container } = await render(routerWrapper());
|
||||||
|
embeddedRegistrationPage = container;
|
||||||
|
});
|
||||||
|
|
||||||
|
const spanElement = embeddedRegistrationPage.querySelector('span');
|
||||||
|
|
||||||
|
expect(spanElement).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render embedded register page if host query param is available in the url (embedded)', async () => {
|
||||||
|
delete window.location;
|
||||||
|
window.location = {
|
||||||
|
href: getConfig().BASE_URL.concat(REGISTER_EMBEDDED_PAGE),
|
||||||
|
search: '?host=http://localhost/host-websit',
|
||||||
|
};
|
||||||
|
|
||||||
|
let embeddedRegistrationPage = null;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const { container } = await render(routerWrapper());
|
||||||
|
embeddedRegistrationPage = container;
|
||||||
|
});
|
||||||
|
|
||||||
|
const spanElement = embeddedRegistrationPage.querySelector('span');
|
||||||
|
|
||||||
|
expect(spanElement).toBeTruthy();
|
||||||
|
expect(spanElement.textContent).toBe('Embedded Register Page');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { mount } from 'enzyme';
|
import { fireEvent, render } from '@testing-library/react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
import { fetchRealtimeValidations } from '../../register/data/actions';
|
||||||
import FormGroup from '../FormGroup';
|
import FormGroup from '../FormGroup';
|
||||||
import PasswordField from '../PasswordField';
|
import PasswordField from '../PasswordField';
|
||||||
|
|
||||||
@@ -17,19 +21,42 @@ describe('FormGroup', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('should show help text on field focus', () => {
|
it('should show help text on field focus', () => {
|
||||||
const formGroup = mount(<FormGroup {...props} />);
|
const { queryByText, getByLabelText } = render(<FormGroup {...props} />);
|
||||||
expect(formGroup.find('.pgn-transition-replace-group').find('div#email-1').exists()).toBeFalsy();
|
const emailInput = getByLabelText('Email');
|
||||||
|
|
||||||
formGroup.find('input#email').simulate('focus');
|
expect(queryByText('Email field help text')).toBeNull();
|
||||||
expect(formGroup.find('.pgn-transition-replace-group').find('div#email-1').text()).toEqual('Email field help text');
|
|
||||||
|
fireEvent.focus(emailInput);
|
||||||
|
|
||||||
|
const helpText = queryByText('Email field help text');
|
||||||
|
|
||||||
|
expect(helpText).toBeTruthy();
|
||||||
|
expect(helpText.textContent).toEqual('Email field help text');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('PasswordField', () => {
|
describe('PasswordField', () => {
|
||||||
|
const mockStore = configureStore();
|
||||||
const IntlPasswordField = injectIntl(PasswordField);
|
const IntlPasswordField = injectIntl(PasswordField);
|
||||||
let props = {};
|
let props = {};
|
||||||
|
let store = {};
|
||||||
|
|
||||||
|
const reduxWrapper = children => (
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<MemoryRouter>
|
||||||
|
<Provider store={store}>{children}</Provider>
|
||||||
|
</MemoryRouter>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
register: {
|
||||||
|
validationApiRateLimited: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
store = mockStore(initialState);
|
||||||
props = {
|
props = {
|
||||||
floatingLabel: 'Password',
|
floatingLabel: 'Password',
|
||||||
name: 'password',
|
name: 'password',
|
||||||
@@ -39,25 +66,29 @@ describe('PasswordField', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show/hide password on icon click', () => {
|
it('should show/hide password on icon click', () => {
|
||||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = getByLabelText('Password');
|
||||||
|
|
||||||
passwordField.find('button[aria-label="Show password"]').simulate('click');
|
const showPasswordButton = getByLabelText('Show password');
|
||||||
expect(passwordField.find('input').prop('type')).toEqual('text');
|
fireEvent.click(showPasswordButton);
|
||||||
|
expect(passwordInput.type).toBe('text');
|
||||||
|
|
||||||
passwordField.find('button[aria-label="Hide password"]').simulate('click');
|
const hidePasswordButton = getByLabelText('Hide password');
|
||||||
expect(passwordField.find('input').prop('type')).toEqual('password');
|
fireEvent.click(hidePasswordButton);
|
||||||
|
expect(passwordInput.type).toBe('password');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show password requirement tooltip on focus', async () => {
|
it('should show password requirement tooltip on focus', async () => {
|
||||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = getByLabelText('Password');
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
passwordField.find('input').simulate('focus');
|
fireEvent.focus(passwordInput);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
passwordField.update();
|
const passwordRequirementTooltip = document.querySelector('#password-requirement-left');
|
||||||
|
|
||||||
expect(passwordField.find('#password-requirement-left').exists()).toBeTruthy();
|
expect(passwordRequirementTooltip).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show all password requirement checks as failed', async () => {
|
it('should show all password requirement checks as failed', async () => {
|
||||||
@@ -65,31 +96,195 @@ describe('PasswordField', () => {
|
|||||||
...props,
|
...props,
|
||||||
value: '',
|
value: '',
|
||||||
};
|
};
|
||||||
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = getByLabelText('Password');
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
passwordField.find('input').simulate('focus');
|
fireEvent.focus(passwordInput);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
passwordField.update();
|
|
||||||
|
|
||||||
expect(passwordField.find('#letter-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
|
const letterCheckIcon = document.querySelector('#letter-check span');
|
||||||
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
|
const numberCheckIcon = document.querySelector('#number-check span');
|
||||||
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
|
const charactersCheckIcon = document.querySelector('#characters-check span');
|
||||||
|
|
||||||
|
expect(letterCheckIcon).toBeTruthy();
|
||||||
|
expect(letterCheckIcon.className).toContain('pgn__icon mr-1 text-light-700');
|
||||||
|
|
||||||
|
expect(numberCheckIcon).toBeTruthy();
|
||||||
|
expect(numberCheckIcon.className).toContain('pgn__icon mr-1 text-light-700');
|
||||||
|
|
||||||
|
expect(charactersCheckIcon).toBeTruthy();
|
||||||
|
expect(charactersCheckIcon.className).toContain('pgn__icon mr-1 text-light-700');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update password requirement checks', async () => {
|
it('should update password requirement checks', async () => {
|
||||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = getByLabelText('Password');
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
passwordField.find('input').simulate('focus');
|
fireEvent.focus(passwordInput);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
passwordField.update();
|
|
||||||
|
|
||||||
expect(passwordField.find('#letter-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
|
const letterCheckIcon = document.querySelector('#letter-check span');
|
||||||
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
|
const numberCheckIcon = document.querySelector('#number-check span');
|
||||||
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
|
const charactersCheckIcon = document.querySelector('#characters-check span');
|
||||||
|
|
||||||
|
expect(letterCheckIcon).toBeTruthy();
|
||||||
|
expect(letterCheckIcon.className).toContain('pgn__icon text-success mr-1');
|
||||||
|
|
||||||
|
expect(numberCheckIcon).toBeTruthy();
|
||||||
|
expect(numberCheckIcon.className).toContain('pgn__icon text-success mr-1');
|
||||||
|
|
||||||
|
expect(charactersCheckIcon).toBeTruthy();
|
||||||
|
expect(charactersCheckIcon.className).toContain('pgn__icon text-success mr-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not run validations when blur is fired on password icon click', () => {
|
||||||
|
const { container, getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = container.querySelector('input[name="password"]');
|
||||||
|
|
||||||
|
const passwordIcon = getByLabelText('Show password');
|
||||||
|
|
||||||
|
fireEvent.blur(passwordInput, {
|
||||||
|
target: {
|
||||||
|
name: 'password',
|
||||||
|
value: 'invalid',
|
||||||
|
},
|
||||||
|
relatedTarget: passwordIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector('div[feedback-for="password"]')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call props handle blur if available', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
handleBlur: jest.fn(),
|
||||||
|
};
|
||||||
|
const { container } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = container.querySelector('input[name="password"]');
|
||||||
|
|
||||||
|
fireEvent.blur(passwordInput, {
|
||||||
|
target: {
|
||||||
|
name: 'password',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(props.handleBlur).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run validations on blur event when rendered from register page', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
handleErrorChange: jest.fn(),
|
||||||
|
};
|
||||||
|
const { container } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordInput = container.querySelector('input[name="password"]');
|
||||||
|
|
||||||
|
fireEvent.blur(passwordInput, {
|
||||||
|
target: {
|
||||||
|
name: 'password',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||||
|
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||||
|
'password',
|
||||||
|
'Password criteria has not been met',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not clear error when focus is fired on password icon click when rendered from register page', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
handleErrorChange: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
|
||||||
|
const passwordIcon = getByLabelText('Show password');
|
||||||
|
|
||||||
|
fireEvent.focus(passwordIcon, {
|
||||||
|
target: {
|
||||||
|
name: 'passwordIcon',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(props.handleErrorChange).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear error when focus is fired on password icon click when rendered from register page', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
handleErrorChange: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
|
||||||
|
const passwordIcon = getByLabelText('Show password');
|
||||||
|
|
||||||
|
fireEvent.focus(passwordIcon, {
|
||||||
|
target: {
|
||||||
|
name: 'password',
|
||||||
|
value: 'invalid',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||||
|
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||||
|
'password',
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run backend validations when frontend validations pass on blur when rendered from register page', () => {
|
||||||
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
handleErrorChange: jest.fn(),
|
||||||
|
};
|
||||||
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
const passwordField = getByLabelText('Password');
|
||||||
|
fireEvent.blur(passwordField, {
|
||||||
|
target: {
|
||||||
|
name: 'password',
|
||||||
|
value: 'password123',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ password: 'password123' }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use password value from prop when password icon is focused out (blur due to icon)', () => {
|
||||||
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
value: 'testPassword',
|
||||||
|
handleErrorChange: jest.fn(),
|
||||||
|
handleBlur: jest.fn(),
|
||||||
|
};
|
||||||
|
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
|
||||||
|
|
||||||
|
const passwordIcon = getByLabelText('Show password');
|
||||||
|
|
||||||
|
fireEvent.blur(passwordIcon, {
|
||||||
|
target: {
|
||||||
|
name: 'passwordIcon',
|
||||||
|
value: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(props.handleBlur).toHaveBeenCalledTimes(1);
|
||||||
|
expect(props.handleBlur).toHaveBeenCalledWith({
|
||||||
|
target: {
|
||||||
|
name: 'password',
|
||||||
|
value: 'testPassword',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
import { mount } from 'enzyme';
|
import { render } from '@testing-library/react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import { UnAuthOnlyRoute } from '..';
|
import { UnAuthOnlyRoute } from '..';
|
||||||
import { LOGIN_PAGE } from '../../data/constants';
|
import { REGISTER_PAGE } from '../../data/constants';
|
||||||
|
|
||||||
import { MemoryRouter, BrowserRouter as Router, Switch } from 'react-router-dom';
|
import {
|
||||||
|
MemoryRouter, Route, BrowserRouter as Router, Routes,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||||
getAuthenticatedUser: jest.fn(),
|
getAuthenticatedUser: jest.fn(),
|
||||||
@@ -25,16 +27,16 @@ module.exports = RRD;
|
|||||||
const TestApp = () => (
|
const TestApp = () => (
|
||||||
<Router>
|
<Router>
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Routes>
|
||||||
<UnAuthOnlyRoute path={LOGIN_PAGE} render={() => (<span>Login Page</span>)} />
|
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><span>Register Page</span></UnAuthOnlyRoute>} />
|
||||||
</Switch>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('UnAuthOnlyRoute', () => {
|
describe('UnAuthOnlyRoute', () => {
|
||||||
const routerWrapper = () => (
|
const routerWrapper = () => (
|
||||||
<MemoryRouter initialEntries={[LOGIN_PAGE]}>
|
<MemoryRouter initialEntries={[REGISTER_PAGE]}>
|
||||||
<TestApp />
|
<TestApp />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
@@ -53,7 +55,7 @@ describe('UnAuthOnlyRoute', () => {
|
|||||||
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(user));
|
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(user));
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await mount(routerWrapper());
|
await render(routerWrapper());
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
|
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
|
||||||
@@ -64,7 +66,7 @@ describe('UnAuthOnlyRoute', () => {
|
|||||||
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(null));
|
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(null));
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await mount(routerWrapper());
|
await render(routerWrapper());
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
|
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ exports[`SocialAuthProviders should match social auth provider with default icon
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M11 7L9.6 8.4l2.6 2.6H2v2h10.2l-2.6 2.6L11 17l5-5-5-5zm9 12h-8v2h10V3H12v2h8v14z"
|
d="M11 7 9.6 8.4l2.6 2.6H2v2h10.2l-2.6 2.6L11 17l5-5-5-5zm9 12h-8v2h10V3H12v2h8v14z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,28 +1,39 @@
|
|||||||
const configuration = {
|
const configuration = {
|
||||||
// Cookies related configs
|
// Cookies related configs
|
||||||
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
|
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
|
||||||
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
|
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
|
||||||
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
|
|
||||||
// Features
|
// Features
|
||||||
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
|
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
|
||||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
|
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
|
||||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
|
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
|
||||||
ENABLE_PERSONALIZED_RECOMMENDATIONS: process.env.ENABLE_PERSONALIZED_RECOMMENDATIONS || false,
|
ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false,
|
||||||
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
||||||
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
|
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
|
||||||
|
SHOW_REGISTRATION_LINKS: process.env.SHOW_REGISTRATION_LINKS !== 'false',
|
||||||
|
ENABLE_IMAGE_LAYOUT: process.env.ENABLE_IMAGE_LAYOUT || false,
|
||||||
// Links
|
// Links
|
||||||
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
|
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
|
||||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK || null,
|
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK || null,
|
||||||
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
|
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
|
||||||
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
|
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
|
||||||
|
POST_REGISTRATION_REDIRECT_URL: process.env.POST_REGISTRATION_REDIRECT_URL || '',
|
||||||
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
|
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
|
||||||
|
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
|
||||||
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
|
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
|
||||||
TOS_LINK: process.env.TOS_LINK || null,
|
TOS_LINK: process.env.TOS_LINK || null,
|
||||||
// Miscellaneous
|
// Base container images
|
||||||
|
BANNER_IMAGE_LARGE: process.env.BANNER_IMAGE_LARGE || '',
|
||||||
|
BANNER_IMAGE_MEDIUM: process.env.BANNER_IMAGE_MEDIUM || '',
|
||||||
|
BANNER_IMAGE_SMALL: process.env.BANNER_IMAGE_SMALL || '',
|
||||||
|
BANNER_IMAGE_EXTRA_SMALL: process.env.BANNER_IMAGE_EXTRA_SMALL || '',
|
||||||
|
// Recommendation constants
|
||||||
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
|
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
|
||||||
|
// Miscellaneous
|
||||||
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
||||||
ZENDESK_KEY: process.env.ZENDESK_KEY,
|
ZENDESK_KEY: process.env.ZENDESK_KEY,
|
||||||
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
|
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
|
||||||
|
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
|
||||||
|
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default configuration;
|
export default configuration;
|
||||||
|
|||||||
20
src/data/algolia.js
Normal file
20
src/data/algolia.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import algoliasearch from 'algoliasearch';
|
||||||
|
|
||||||
|
// initialize Algolia workers
|
||||||
|
const initializeSearchClient = () => algoliasearch(
|
||||||
|
getConfig().ALGOLIA_APP_ID,
|
||||||
|
getConfig().ALGOLIA_SEARCH_API_KEY,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getLocationRestrictionFilter = (userCountry) => {
|
||||||
|
if (userCountry) {
|
||||||
|
return `NOT blocked_in:"${userCountry}" AND (allowed_in:"null" OR allowed_in:"${userCountry}")`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
initializeSearchClient,
|
||||||
|
getLocationRestrictionFilter,
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// URL Paths
|
// URL Paths
|
||||||
export const LOGIN_PAGE = '/login';
|
export const LOGIN_PAGE = '/login';
|
||||||
export const REGISTER_PAGE = '/register';
|
export const REGISTER_PAGE = '/register';
|
||||||
|
export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
|
||||||
export const RESET_PAGE = '/reset';
|
export const RESET_PAGE = '/reset';
|
||||||
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
|
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
|
||||||
export const DEFAULT_REDIRECT_URL = '/dashboard';
|
export const DEFAULT_REDIRECT_URL = '/dashboard';
|
||||||
@@ -23,16 +24,16 @@ export const PENDING_STATE = 'pending';
|
|||||||
export const COMPLETE_STATE = 'complete';
|
export const COMPLETE_STATE = 'complete';
|
||||||
export const FAILURE_STATE = 'failure';
|
export const FAILURE_STATE = 'failure';
|
||||||
export const FORBIDDEN_STATE = 'forbidden';
|
export const FORBIDDEN_STATE = 'forbidden';
|
||||||
|
export const EMBEDDED = 'embedded';
|
||||||
|
|
||||||
// Regex
|
export const LETTER_REGEX = /[a-zA-Z]/;
|
||||||
|
export const NUMBER_REGEX = /\d/;
|
||||||
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
|
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
|
||||||
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
|
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
|
||||||
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
|
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
|
||||||
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
|
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
|
||||||
export const LETTER_REGEX = /[a-zA-Z]/;
|
|
||||||
export const NUMBER_REGEX = /\d/;
|
|
||||||
export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi; // eslint-disable-line no-useless-escape
|
|
||||||
|
|
||||||
// Query string parameters that can be passed to LMS to manage
|
// Query string parameters that can be passed to LMS to manage
|
||||||
// things like auto-enrollment upon login and registration.
|
// things like auto-enrollment upon login and registration.
|
||||||
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'save_for_later', 'register_for_free', 'track', 'is_account_recovery'];
|
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta'];
|
||||||
|
export const REDIRECT = 'redirect';
|
||||||
|
|||||||
3
src/data/oneTrust.js
Normal file
3
src/data/oneTrust.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const isOneTrustFunctionalCookieEnabled = () => !!window?.OnetrustActiveGroups?.includes('C0003');
|
||||||
|
|
||||||
|
export default isOneTrustFunctionalCookieEnabled;
|
||||||
16
src/data/tests/algolia.test.js
Normal file
16
src/data/tests/algolia.test.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { getLocationRestrictionFilter } from '../algolia';
|
||||||
|
|
||||||
|
describe('algoliaUtilsTests', () => {
|
||||||
|
it('test getLocationRestrictionFilter returns filter if country is passed', () => {
|
||||||
|
const countryCode = 'PK';
|
||||||
|
const filter = getLocationRestrictionFilter(countryCode);
|
||||||
|
const expectedFilter = `NOT blocked_in:"${countryCode}" AND (allowed_in:"null" OR allowed_in:"${countryCode}")`;
|
||||||
|
expect(filter).toEqual(expectedFilter);
|
||||||
|
});
|
||||||
|
it('test getLocationRestrictionFilter returns empty string if country is not passed', () => {
|
||||||
|
const countryCode = '';
|
||||||
|
const filter = getLocationRestrictionFilter(countryCode);
|
||||||
|
const expectedFilter = '';
|
||||||
|
expect(filter).toEqual(expectedFilter);
|
||||||
|
});
|
||||||
|
});
|
||||||
52
src/data/tests/cookies.test.js
Normal file
52
src/data/tests/cookies.test.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
|
||||||
|
import { setCookie } from '../utils';
|
||||||
|
|
||||||
|
// Mock getConfig function
|
||||||
|
jest.mock('@edx/frontend-platform', () => ({
|
||||||
|
getConfig: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock Cookies class
|
||||||
|
jest.mock('universal-cookie');
|
||||||
|
|
||||||
|
describe('setCookie function', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set a cookie with default options', () => {
|
||||||
|
getConfig.mockReturnValue({ SESSION_COOKIE_DOMAIN: 'example.com' });
|
||||||
|
|
||||||
|
setCookie('testCookie', 'testValue');
|
||||||
|
|
||||||
|
expect(Cookies).toHaveBeenCalled();
|
||||||
|
expect(Cookies).toHaveBeenCalledWith();
|
||||||
|
expect(Cookies.prototype.set).toHaveBeenCalledWith('testCookie', 'testValue', {
|
||||||
|
domain: 'example.com',
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set a cookie with specified expiry', () => {
|
||||||
|
getConfig.mockReturnValue({ SESSION_COOKIE_DOMAIN: 'example.com' });
|
||||||
|
|
||||||
|
const expiry = new Date('2023-12-31');
|
||||||
|
setCookie('testCookie', 'testValue', expiry);
|
||||||
|
|
||||||
|
expect(Cookies).toHaveBeenCalled();
|
||||||
|
expect(Cookies).toHaveBeenCalledWith();
|
||||||
|
expect(Cookies.prototype.set).toHaveBeenCalledWith('testCookie', 'testValue', {
|
||||||
|
domain: 'example.com',
|
||||||
|
path: '/',
|
||||||
|
expires: expiry,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set a cookie if cookieName is undefined', () => {
|
||||||
|
setCookie(undefined, 'testValue');
|
||||||
|
|
||||||
|
expect(Cookies).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { updatePathWithQueryParams } from './dataUtils';
|
|
||||||
import { LOGIN_PAGE } from '../constants';
|
import { LOGIN_PAGE } from '../constants';
|
||||||
|
import { updatePathWithQueryParams } from '../utils/dataUtils';
|
||||||
|
|
||||||
describe('updatePathWithQueryParams', () => {
|
describe('updatePathWithQueryParams', () => {
|
||||||
it('should append query params into the path', () => {
|
it('should append query params into the path', () => {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import AsyncActionType from './reduxUtils';
|
import AsyncActionType from '../utils/reduxUtils';
|
||||||
|
|
||||||
describe('AsyncActionType', () => {
|
describe('AsyncActionType', () => {
|
||||||
it('should return well formatted action strings', () => {
|
it('should return well formatted action strings', () => {
|
||||||
@@ -1,21 +1,13 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import Cookies from 'universal-cookie';
|
import Cookies from 'universal-cookie';
|
||||||
|
|
||||||
export function setCookie(cookieName, cookieValue, cookieExpiry) {
|
export default function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||||
const cookies = new Cookies();
|
if (cookieName) { // To avoid setting getting exception when setting cookie with undefined names.
|
||||||
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
|
const cookies = new Cookies();
|
||||||
if (cookieExpiry) {
|
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
|
||||||
options.expires = cookieExpiry;
|
if (cookieExpiry) {
|
||||||
}
|
options.expires = cookieExpiry;
|
||||||
cookies.set(cookieName, cookieValue, options);
|
}
|
||||||
}
|
cookies.set(cookieName, cookieValue, options);
|
||||||
|
|
||||||
export default function setSurveyCookie(surveyType) {
|
|
||||||
const cookieName = getConfig().USER_SURVEY_COOKIE_NAME;
|
|
||||||
if (cookieName) {
|
|
||||||
const signupTimestamp = (new Date()).getTime();
|
|
||||||
// set expiry to exactly 24 hours from now
|
|
||||||
const cookieExpiry = new Date(signupTimestamp + 1 * 864e5);
|
|
||||||
setCookie(cookieName, surveyType, cookieExpiry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,3 +76,8 @@ export const windowScrollTo = (options) => {
|
|||||||
|
|
||||||
return window.scrollTo(options.top, options.left);
|
return window.scrollTo(options.top, options.left);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isHostAvailableInQueryParams = () => {
|
||||||
|
const queryParams = getAllPossibleQueryParams();
|
||||||
|
return 'host' in queryParams;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
export {
|
export {
|
||||||
getTpaProvider,
|
getTpaProvider,
|
||||||
getTpaHint,
|
getTpaHint,
|
||||||
updatePathWithQueryParams,
|
|
||||||
getAllPossibleQueryParams,
|
getAllPossibleQueryParams,
|
||||||
getActivationStatus,
|
getActivationStatus,
|
||||||
|
isHostAvailableInQueryParams,
|
||||||
|
updatePathWithQueryParams,
|
||||||
windowScrollTo,
|
windowScrollTo,
|
||||||
} from './dataUtils';
|
} from './dataUtils';
|
||||||
export { default as AsyncActionType } from './reduxUtils';
|
export { default as AsyncActionType } from './reduxUtils';
|
||||||
export { default as setSurveyCookie, setCookie } from './cookies';
|
export { default as setCookie } from './cookies';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { breakpoints } from '@edx/paragon';
|
import { breakpoints } from '@openedx/paragon';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A react hook used to determine if the current window is mobile or not.
|
* A react hook used to determine if the current window is mobile or not.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Form, Icon } from '@edx/paragon';
|
import { Form, Icon } from '@openedx/paragon';
|
||||||
import { ExpandMore } from '@edx/paragon/icons';
|
import { ExpandMore } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const FormFieldRenderer = (props) => {
|
const FormFieldRenderer = (props) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { mount } from 'enzyme';
|
import { fireEvent, render } from '@testing-library/react';
|
||||||
|
|
||||||
import FieldRenderer from '../FieldRenderer';
|
import FieldRenderer from '../FieldRenderer';
|
||||||
|
|
||||||
@@ -28,13 +28,14 @@ describe('FieldRendererTests', () => {
|
|||||||
options: [['1997', '1997'], ['1998', '1998']],
|
options: [['1997', '1997'], ['1998', '1998']],
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||||
const field = fieldRenderer.find('select#yob-field');
|
const input = container.querySelector('select#yob-field');
|
||||||
field.simulate('change', { target: { value: 1997 } });
|
const label = container.querySelector('label');
|
||||||
|
fireEvent.change(input, { target: { value: 1997 } });
|
||||||
|
|
||||||
expect(field.type()).toEqual('select');
|
expect(input.type).toEqual('select-one');
|
||||||
expect(fieldRenderer.find('label').text()).toEqual('Year of Birth');
|
expect(label.textContent).toContain(fieldData.label);
|
||||||
expect(value).toEqual(1997);
|
expect(value).toEqual('1997');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if no options are provided for select field', () => {
|
it('should return null if no options are provided for select field', () => {
|
||||||
@@ -44,8 +45,8 @@ describe('FieldRendererTests', () => {
|
|||||||
name: 'yob-field',
|
name: 'yob-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||||
expect(fieldRenderer.html()).toBeNull();
|
expect(container.innerHTML).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render textarea field', () => {
|
it('should render textarea field', () => {
|
||||||
@@ -55,12 +56,13 @@ describe('FieldRendererTests', () => {
|
|||||||
name: 'goals-field',
|
name: 'goals-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||||
const field = fieldRenderer.find('#goals-field').last();
|
const input = container.querySelector('#goals-field');
|
||||||
field.simulate('change', { target: { value: 'These are my goals.' } });
|
const label = container.querySelector('label');
|
||||||
|
fireEvent.change(input, { target: { value: 'These are my goals.' } });
|
||||||
|
|
||||||
expect(field.type()).toEqual('textarea');
|
expect(input.type).toEqual(fieldData.type);
|
||||||
expect(fieldRenderer.find('label').text()).toEqual('Why do you want to join this platform?');
|
expect(label.textContent).toContain('Why do you want to join this platform?');
|
||||||
expect(value).toEqual('These are my goals.');
|
expect(value).toEqual('These are my goals.');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,12 +73,13 @@ describe('FieldRendererTests', () => {
|
|||||||
name: 'company-field',
|
name: 'company-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||||
const field = fieldRenderer.find('#company-field').last();
|
const input = container.querySelector('input#company-field');
|
||||||
field.simulate('change', { target: { value: 'ABC' } });
|
const label = container.querySelector('label');
|
||||||
|
fireEvent.change(input, { target: { value: 'ABC' } });
|
||||||
|
|
||||||
expect(field.type()).toEqual('input');
|
expect(input.type).toEqual(fieldData.type);
|
||||||
expect(fieldRenderer.find('label').text()).toEqual('Company');
|
expect(label.textContent).toContain(fieldData.label);
|
||||||
expect(value).toEqual('ABC');
|
expect(value).toEqual('ABC');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,12 +90,13 @@ describe('FieldRendererTests', () => {
|
|||||||
name: 'marketing-emails-opt-in-field',
|
name: 'marketing-emails-opt-in-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||||
const field = fieldRenderer.find('input#marketing-emails-opt-in-field');
|
const input = container.querySelector('input#marketing-emails-opt-in-field');
|
||||||
field.simulate('change', { target: { checked: true, type: 'checkbox' } });
|
const label = container.querySelector('label');
|
||||||
|
fireEvent.click(input);
|
||||||
|
|
||||||
expect(field.prop('type')).toEqual('checkbox');
|
expect(input.type).toEqual(fieldData.type);
|
||||||
expect(fieldRenderer.find('label').text()).toEqual(fieldData.label);
|
expect(label.textContent).toContain(fieldData.label);
|
||||||
expect(value).toEqual(true);
|
expect(value).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,8 +105,8 @@ describe('FieldRendererTests', () => {
|
|||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||||
expect(fieldRenderer.html()).toBeNull();
|
expect(container.innerHTML).toContain('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run onBlur and onFocus functions for a field if given', () => {
|
it('should run onBlur and onFocus functions for a field if given', () => {
|
||||||
@@ -117,7 +121,7 @@ describe('FieldRendererTests', () => {
|
|||||||
functionValue = `${e.target.name} focussed`;
|
functionValue = `${e.target.name} focussed`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(
|
const { container } = render(
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
handleFocus={onFocus}
|
handleFocus={onFocus}
|
||||||
handleBlur={onBlur}
|
handleBlur={onBlur}
|
||||||
@@ -126,19 +130,19 @@ describe('FieldRendererTests', () => {
|
|||||||
onChangeHandler={changeHandler}
|
onChangeHandler={changeHandler}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
const field = fieldRenderer.find('#test-field').last();
|
const input = container.querySelector('#test-field');
|
||||||
|
|
||||||
field.simulate('focus');
|
fireEvent.focus(input);
|
||||||
expect(functionValue).toEqual('test-field focussed');
|
expect(functionValue).toEqual('test-field focussed');
|
||||||
|
|
||||||
field.simulate('blur');
|
fireEvent.blur(input);
|
||||||
expect(functionValue).toEqual('test-field blurred');
|
expect(functionValue).toEqual('test-field blurred');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render error message for required text fields', () => {
|
it('should render error message for required text fields', () => {
|
||||||
const fieldData = { type: 'text', label: 'First Name', name: 'first-name-field' };
|
const fieldData = { type: 'text', label: 'First Name', name: 'first-name-field' };
|
||||||
|
|
||||||
const fieldRenderer = mount(
|
const { container } = render(
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
isRequired
|
isRequired
|
||||||
fieldData={fieldData}
|
fieldData={fieldData}
|
||||||
@@ -147,7 +151,7 @@ describe('FieldRendererTests', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Enter your first name');
|
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('Enter your first name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render error message for required select fields', () => {
|
it('should render error message for required select fields', () => {
|
||||||
@@ -155,7 +159,7 @@ describe('FieldRendererTests', () => {
|
|||||||
type: 'select', label: 'Preference', name: 'preference-field', options: [['a', 'Opt 1'], ['b', 'Opt 2']],
|
type: 'select', label: 'Preference', name: 'preference-field', options: [['a', 'Opt 1'], ['b', 'Opt 2']],
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldRenderer = mount(
|
const { container } = render(
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
isRequired
|
isRequired
|
||||||
fieldData={fieldData}
|
fieldData={fieldData}
|
||||||
@@ -164,13 +168,13 @@ describe('FieldRendererTests', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Select your preference');
|
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('Select your preference');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render error message for required textarea fields', () => {
|
it('should render error message for required textarea fields', () => {
|
||||||
const fieldData = { type: 'textarea', label: 'Goals', name: 'goals-field' };
|
const fieldData = { type: 'textarea', label: 'Goals', name: 'goals-field' };
|
||||||
|
|
||||||
const fieldRenderer = mount(
|
const { container } = render(
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
isRequired
|
isRequired
|
||||||
fieldData={fieldData}
|
fieldData={fieldData}
|
||||||
@@ -179,13 +183,13 @@ describe('FieldRendererTests', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Tell us your goals');
|
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('Tell us your goals');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render error message for required checkbox fields', () => {
|
it('should render error message for required checkbox fields', () => {
|
||||||
const fieldData = { type: 'checkbox', label: 'Honor Code', name: 'honor-code-field' };
|
const fieldData = { type: 'checkbox', label: 'Honor Code', name: 'honor-code-field' };
|
||||||
|
|
||||||
const fieldRenderer = mount(
|
const { container } = render(
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
isRequired
|
isRequired
|
||||||
fieldData={fieldData}
|
fieldData={fieldData}
|
||||||
@@ -194,6 +198,6 @@ describe('FieldRendererTests', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('You must agree to our Honor Code');
|
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('You must agree to our Honor Code');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Alert } from '@edx/paragon';
|
import { Alert } from '@openedx/paragon';
|
||||||
import { CheckCircle, Error } from '@edx/paragon/icons';
|
import { CheckCircle, Error } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import {
|
|||||||
StatefulButton,
|
StatefulButton,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { ChevronLeft } from '@edx/paragon/icons';
|
import { ChevronLeft } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
|
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
|
||||||
import { forgotPasswordResultSelector } from './data/selectors';
|
import { forgotPasswordResultSelector } from './data/selectors';
|
||||||
import ForgotPasswordAlert from './ForgotPasswordAlert';
|
import ForgotPasswordAlert from './ForgotPasswordAlert';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import { BaseComponent } from '../base-component';
|
import BaseContainer from '../base-container';
|
||||||
import { FormGroup } from '../common-components';
|
import { FormGroup } from '../common-components';
|
||||||
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||||
@@ -38,7 +38,7 @@ const ForgotPasswordPage = (props) => {
|
|||||||
const [bannerEmail, setBannerEmail] = useState('');
|
const [bannerEmail, setBannerEmail] = useState('');
|
||||||
const [formErrors, setFormErrors] = useState('');
|
const [formErrors, setFormErrors] = useState('');
|
||||||
const [validationError, setValidationError] = useState(emailValidationError);
|
const [validationError, setValidationError] = useState(emailValidationError);
|
||||||
const [key, setKey] = useState('');
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sendPageEvent('login_and_registration', 'reset');
|
sendPageEvent('login_and_registration', 'reset');
|
||||||
@@ -95,19 +95,16 @@ const ForgotPasswordPage = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseComponent>
|
<BaseContainer>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{formatMessage(messages['forgot.password.page.title'],
|
<title>{formatMessage(messages['forgot.password.page.title'],
|
||||||
{ siteName: getConfig().SITE_NAME })}
|
{ siteName: getConfig().SITE_NAME })}
|
||||||
</title>
|
</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div>
|
<div>
|
||||||
<Tabs activeKey="" id="controlled-tab" onSelect={(k) => setKey(k)}>
|
<Tabs activeKey="" id="controlled-tab" onSelect={(key) => navigate(updatePathWithQueryParams(key))}>
|
||||||
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
|
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{ key && (
|
|
||||||
<Redirect to={updatePathWithQueryParams(key)} />
|
|
||||||
)}
|
|
||||||
<div id="main-content" className="main-content">
|
<div id="main-content" className="main-content">
|
||||||
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
|
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
|
||||||
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
|
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
|
||||||
@@ -163,7 +160,7 @@ const ForgotPasswordPage = (props) => {
|
|||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseComponent>
|
</BaseContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { Provider } from 'react-redux';
|
|||||||
|
|
||||||
import { mergeConfig } from '@edx/frontend-platform';
|
import { mergeConfig } from '@edx/frontend-platform';
|
||||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { mount } from 'enzyme';
|
import {
|
||||||
import { createMemoryHistory } from 'history';
|
fireEvent, render, screen,
|
||||||
import { act } from 'react-dom/test-utils';
|
} from '@testing-library/react';
|
||||||
import { MemoryRouter, Router } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
|
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
|
||||||
@@ -14,15 +14,20 @@ import { PASSWORD_RESET } from '../../reset-password/data/constants';
|
|||||||
import { setForgotPasswordFormData } from '../data/actions';
|
import { setForgotPasswordFormData } from '../data/actions';
|
||||||
import ForgotPasswordPage from '../ForgotPasswordPage';
|
import ForgotPasswordPage from '../ForgotPasswordPage';
|
||||||
|
|
||||||
|
const mockedNavigator = jest.fn();
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||||
sendPageEvent: jest.fn(),
|
sendPageEvent: jest.fn(),
|
||||||
sendTrackEvent: jest.fn(),
|
sendTrackEvent: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...(jest.requireActual('react-router-dom')),
|
||||||
|
useNavigate: () => mockedNavigator,
|
||||||
|
}));
|
||||||
|
|
||||||
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
|
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
const history = createMemoryHistory();
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
forgotPassword: {
|
forgotPassword: {
|
||||||
@@ -68,31 +73,39 @@ describe('ForgotPasswordPage', () => {
|
|||||||
status: null,
|
status: null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const findByTextContent = (container, text) => Array.from(container.querySelectorAll('*')).find(
|
||||||
|
element => element.textContent === text,
|
||||||
|
);
|
||||||
|
|
||||||
it('not should display need other help signing in button', () => {
|
it('not should display need other help signing in button', () => {
|
||||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { queryByTestId } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
expect(wrapper.find('#forgot-password').exists()).toBeFalsy();
|
const forgotPasswordButton = queryByTestId('forgot-password');
|
||||||
|
expect(forgotPasswordButton).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display need other help signing in button', () => {
|
it('should display need other help signing in button', () => {
|
||||||
mergeConfig({
|
mergeConfig({
|
||||||
LOGIN_ISSUE_SUPPORT_LINK: '/support',
|
LOGIN_ISSUE_SUPPORT_LINK: '/support',
|
||||||
});
|
});
|
||||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
expect(wrapper.find('#forgot-password').first().text()).toEqual('Need help signing in?');
|
const forgotPasswordButton = screen.findByText('Need help signing in?');
|
||||||
|
expect(forgotPasswordButton).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display email validation error message', async () => {
|
it('should display email validation error message', async () => {
|
||||||
const validationMessage = 'We were unable to contact you.Enter a valid email address below.';
|
const validationMessage = 'We were unable to contact you.Enter a valid email address below.';
|
||||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
|
|
||||||
wrapper.find('input#email').simulate(
|
const emailInput = screen.getByLabelText('Email');
|
||||||
'change', { target: { value: 'invalid-email', name: 'email' } },
|
|
||||||
);
|
|
||||||
await act(async () => { await wrapper.find('button.btn-brand').simulate('click'); });
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
expect(wrapper.find('.alert-danger').text()).toEqual(validationMessage);
|
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
|
||||||
|
|
||||||
|
const submitButton = screen.getByText('Submit');
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
const alertElements = container.querySelectorAll('.alert-danger');
|
||||||
|
const validationErrors = alertElements[0].textContent;
|
||||||
|
expect(validationErrors).toBe(validationMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show alert on server error', () => {
|
it('should show alert on server error', () => {
|
||||||
@@ -101,19 +114,25 @@ describe('ForgotPasswordPage', () => {
|
|||||||
});
|
});
|
||||||
const expectedMessage = 'We were unable to contact you.'
|
const expectedMessage = 'We were unable to contact you.'
|
||||||
+ 'An error has occurred. Try refreshing the page, or check your internet connection.';
|
+ 'An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
|
||||||
|
|
||||||
expect(wrapper.find('#validation-errors').first().text()).toEqual(expectedMessage);
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
|
|
||||||
|
const alertElements = container.querySelectorAll('.alert-danger');
|
||||||
|
const validationErrors = alertElements[0].textContent;
|
||||||
|
expect(validationErrors).toBe(expectedMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display empty email validation message', async () => {
|
it('should display empty email validation message', async () => {
|
||||||
const validationMessage = 'We were unable to contact you.Enter your email below.';
|
const validationMessage = 'We were unable to contact you.Enter your email below.';
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
|
|
||||||
await act(async () => { await forgotPasswordPage.find('button.btn-brand').simulate('click'); });
|
const submitButton = screen.getByText('Submit');
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
forgotPasswordPage.update();
|
const alertElements = container.querySelectorAll('.alert-danger');
|
||||||
expect(forgotPasswordPage.find('.alert-danger').text()).toEqual(validationMessage);
|
const validationErrors = alertElements[0].textContent;
|
||||||
|
|
||||||
|
expect(validationErrors).toBe(validationMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display request in progress error message', () => {
|
it('should display request in progress error message', () => {
|
||||||
@@ -122,18 +141,22 @@ describe('ForgotPasswordPage', () => {
|
|||||||
forgotPassword: { status: 'forbidden' },
|
forgotPassword: { status: 'forbidden' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
expect(forgotPasswordPage.find('.alert-danger').text()).toEqual(rateLimitMessage);
|
|
||||||
|
const alertElements = container.querySelectorAll('.alert-danger');
|
||||||
|
const validationErrors = alertElements[0].textContent;
|
||||||
|
expect(validationErrors).toBe(rateLimitMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not display any error message on change event', () => {
|
it('should not display any error message on change event', () => {
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
|
|
||||||
const emailInput = forgotPasswordPage.find('input#email');
|
const emailInput = screen.getByLabelText('Email');
|
||||||
emailInput.simulate('change', { target: { value: 'invalid-email', name: 'email' } });
|
|
||||||
forgotPasswordPage.update();
|
|
||||||
|
|
||||||
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
|
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
|
||||||
|
|
||||||
|
const errorElement = screen.queryByTestId('email-invalid-feedback');
|
||||||
|
expect(errorElement).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set error in redux store on onBlur', () => {
|
it('should set error in redux store on onBlur', () => {
|
||||||
@@ -149,8 +172,11 @@ describe('ForgotPasswordPage', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
store.dispatch = jest.fn(store.dispatch);
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
forgotPasswordPage.find('input#email').simulate('blur');
|
const emailInput = screen.getByLabelText('Email');
|
||||||
|
|
||||||
|
fireEvent.blur(emailInput);
|
||||||
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
|
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,9 +187,9 @@ describe('ForgotPasswordPage', () => {
|
|||||||
emailValidationError: validationMessage,
|
emailValidationError: validationMessage,
|
||||||
email: '',
|
email: '',
|
||||||
};
|
};
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
forgotPasswordPage.update();
|
const validationElement = container.querySelector('.pgn__form-text-invalid');
|
||||||
expect(forgotPasswordPage.find('.pgn__form-text-invalid').text()).toEqual(validationMessage);
|
expect(validationElement.textContent).toEqual(validationMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear error in redux store on onFocus', () => {
|
it('should clear error in redux store on onFocus', () => {
|
||||||
@@ -178,8 +204,12 @@ describe('ForgotPasswordPage', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
store.dispatch = jest.fn(store.dispatch);
|
store.dispatch = jest.fn(store.dispatch);
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
|
||||||
forgotPasswordPage.find('input#email').simulate('focus');
|
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
|
const emailInput = screen.getByLabelText('Email');
|
||||||
|
|
||||||
|
fireEvent.focus(emailInput);
|
||||||
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
|
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -189,9 +219,9 @@ describe('ForgotPasswordPage', () => {
|
|||||||
emailValidationError: '',
|
emailValidationError: '',
|
||||||
email: '',
|
email: '',
|
||||||
};
|
};
|
||||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
forgotPasswordPage.update();
|
const errorElement = screen.queryByTestId('email-invalid-feedback');
|
||||||
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
|
expect(errorElement).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display success message after email is sent', () => {
|
it('should display success message after email is sent', () => {
|
||||||
@@ -201,12 +231,16 @@ describe('ForgotPasswordPage', () => {
|
|||||||
status: 'complete',
|
status: 'complete',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const successMessage = 'Check your emailWe sent an email to with instructions to reset your password. If you do not '
|
const successMessage = 'Check your emailWe sent an email to with instructions to reset your password. If you do not '
|
||||||
+ 'receive a password reset message after 1 minute, verify that you entered the correct email address,'
|
+ 'receive a password reset message after 1 minute, verify that you entered the correct email address,'
|
||||||
+ ' or check your spam folder. If you need further assistance, contact technical support.';
|
+ ' or check your spam folder. If you need further assistance, contact technical support.';
|
||||||
|
|
||||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
expect(wrapper.find('.alert-success').text()).toEqual(successMessage);
|
const successElement = findByTextContent(container, successMessage);
|
||||||
|
|
||||||
|
expect(successElement).toBeDefined();
|
||||||
|
expect(successElement.textContent).toEqual(successMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display invalid password reset link error', () => {
|
it('should display invalid password reset link error', () => {
|
||||||
@@ -220,20 +254,20 @@ describe('ForgotPasswordPage', () => {
|
|||||||
+ 'This password reset link is invalid. It may have been used already. '
|
+ 'This password reset link is invalid. It may have been used already. '
|
||||||
+ 'Enter your email below to receive a new link.';
|
+ 'Enter your email below to receive a new link.';
|
||||||
|
|
||||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
expect(wrapper.find('.alert-danger').text()).toEqual(successMessage);
|
const successElement = findByTextContent(container, successMessage);
|
||||||
|
|
||||||
|
expect(successElement).toBeDefined();
|
||||||
|
expect(successElement.textContent).toEqual(successMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should redirect onto login page', async () => {
|
it('should redirect onto login page', async () => {
|
||||||
const forgotPasswordPage = mount(reduxWrapper(
|
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||||
<Router history={history}>
|
|
||||||
<IntlForgotPasswordPage {...props} />
|
|
||||||
</Router>,
|
|
||||||
));
|
|
||||||
|
|
||||||
await act(async () => { await forgotPasswordPage.find('nav').find('a').first().simulate('click'); });
|
const navElement = container.querySelector('nav');
|
||||||
|
const anchorElement = navElement.querySelector('a');
|
||||||
|
fireEvent.click(anchorElement);
|
||||||
|
|
||||||
forgotPasswordPage.update();
|
expect(mockedNavigator).toHaveBeenCalledWith(LOGIN_PAGE);
|
||||||
expect(history.location.pathname).toEqual(LOGIN_PAGE);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,36 +1,38 @@
|
|||||||
import { messages as paragonMessages } from '@edx/paragon';
|
import { messages as paragonMessages } from '@openedx/paragon';
|
||||||
|
|
||||||
import arMessages from './messages/ar.json';
|
import arMessages from './messages/ar.json';
|
||||||
import deMessages from './messages/de.json';
|
import deMessages from './messages/de.json';
|
||||||
import dedeCAMessages from './messages/de_DE.json';
|
import deDEMessages from './messages/de_DE.json';
|
||||||
import es419Messages from './messages/es_419.json';
|
import es419Messages from './messages/es_419.json';
|
||||||
|
import faIRMessages from './messages/fa_IR.json';
|
||||||
import frMessages from './messages/fr.json';
|
import frMessages from './messages/fr.json';
|
||||||
import frCAMessages from './messages/fr_CA.json';
|
import frCAMessages from './messages/fr_CA.json';
|
||||||
import hiMessages from './messages/hi.json';
|
import hiMessages from './messages/hi.json';
|
||||||
import itMessages from './messages/it.json';
|
import itMessages from './messages/it.json';
|
||||||
import ititCAMessages from './messages/it_IT.json';
|
import itITMessages from './messages/it_IT.json';
|
||||||
import ptMessages from './messages/pt.json';
|
import ptMessages from './messages/pt.json';
|
||||||
import ptptCAMessages from './messages/pt_PT.json';
|
import ptPTMessages from './messages/pt_PT.json';
|
||||||
import ruMessages from './messages/ru.json';
|
import ruMessages from './messages/ru.json';
|
||||||
import ukMessages from './messages/uk.json';
|
import ukMessages from './messages/uk.json';
|
||||||
import zhcnMessages from './messages/zh_CN.json';
|
import zhCNMessages from './messages/zh_CN.json';
|
||||||
// no need to import en messages-- they are in the defaultMessage field
|
// no need to import en messages-- they are in the defaultMessage field
|
||||||
|
|
||||||
const appMessages = {
|
const appMessages = {
|
||||||
ar: arMessages,
|
ar: arMessages,
|
||||||
'es-419': es419Messages,
|
|
||||||
fr: frMessages,
|
|
||||||
'zh-cn': zhcnMessages,
|
|
||||||
pt: ptMessages,
|
|
||||||
it: itMessages,
|
|
||||||
de: deMessages,
|
de: deMessages,
|
||||||
hi: hiMessages,
|
'de-de': deDEMessages,
|
||||||
|
'es-419': es419Messages,
|
||||||
|
'fa-ir': faIRMessages,
|
||||||
|
fr: frMessages,
|
||||||
'fr-ca': frCAMessages,
|
'fr-ca': frCAMessages,
|
||||||
|
hi: hiMessages,
|
||||||
|
it: itMessages,
|
||||||
|
'it-it': itITMessages,
|
||||||
|
pt: ptMessages,
|
||||||
|
'pt-pt': ptPTMessages,
|
||||||
ru: ruMessages,
|
ru: ruMessages,
|
||||||
uk: ukMessages,
|
uk: ukMessages,
|
||||||
'de-de': dedeCAMessages,
|
'zh-cn': zhCNMessages,
|
||||||
'it-it': ititCAMessages,
|
|
||||||
'pt-pt': ptptCAMessages,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "ابدأ التعلم ",
|
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
|
||||||
"with.site.name": "مع {siteName}",
|
|
||||||
"complete.your.profile.1": "أكمل",
|
|
||||||
"complete.your.profile.2": "ملفك الشخصي",
|
|
||||||
"welcome.to.platform": "أهلا بك {username} في {siteName}",
|
|
||||||
"institution.login.page.sub.heading": "اختر مؤسستك من القائمة أدناه",
|
"institution.login.page.sub.heading": "اختر مؤسستك من القائمة أدناه",
|
||||||
"logistration.sign.in": "تسجيل الدخول",
|
"logistration.sign.in": "تسجيل الدخول",
|
||||||
"logistration.register": "التسجيل",
|
"logistration.register": "التسجيل",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "إتمام إنشاء حسابك",
|
"registration.using.tpa.form.heading": "إتمام إنشاء حسابك",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
|
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
|
||||||
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "لقد أرسلنا بريدًا إلكترونيًا إلى {email} به إرشادات لإعادة ضبط كلمة المرور الخاصة بك. إن لم تستلم رسالة إعادة ضبط كلمة المرور بعد دقيقة واحدة، فتحقق من إدخال عنوان البريد الإلكتروني الصحيح، أو تفقد مجلد الرسائل غير المرغوب فيها. إن احتجت مزيدًا من المساعدة، {supportLink}.",
|
|
||||||
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
|
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
|
||||||
"forgot.password.page.heading": "إعادة ضبط كلمة المرور",
|
"forgot.password.page.heading": "إعادة ضبط كلمة المرور",
|
||||||
"forgot.password.page.instructions": "رجاءً أدخل عنوان بريدك الإلكتروني أدناه وسنرسل إليك بريدًا به إرشادات بخصوص كيفية إعادة ضبط كلمة مرورك.",
|
"forgot.password.page.instructions": "رجاءً أدخل عنوان بريدك الإلكتروني أدناه وسنرسل إليك بريدًا به إرشادات بخصوص كيفية إعادة ضبط كلمة مرورك.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "تفقّد بريدك الإلكتروني",
|
"confirmation.message.title": "تفقّد بريدك الإلكتروني",
|
||||||
"confirmation.support.link": "اتصل بالدعم الفني",
|
"confirmation.support.link": "اتصل بالدعم الفني",
|
||||||
"need.help.sign.in.text": "هل تحتاج مساعدة في تسجيل الدخول؟",
|
"need.help.sign.in.text": "هل تحتاج مساعدة في تسجيل الدخول؟",
|
||||||
"additional.help.text": "للمزيد من المساعدة، اتصل بدعم {platformName} على ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "تسجيل الدخول",
|
"sign.in.text": "تسجيل الدخول",
|
||||||
"extend.field.errors": "{emailError} أدناه.",
|
"extend.field.errors": "{emailError} أدناه.",
|
||||||
"invalid.token.heading": "رابط إعادة ضبط كلمة المرور غير صالح",
|
"invalid.token.heading": "رابط إعادة ضبط كلمة المرور غير صالح",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
"token.validation.internal.sever.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
||||||
"internal.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
"internal.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
||||||
"account.activation.error.message": "شي ما لم يسر على ما يرام، يرجى {supportLink} لحل هذه المشكلة.",
|
"account.activation.error.message": "شي ما لم يسر على ما يرام، يرجى {supportLink} لحل هذه المشكلة.",
|
||||||
"login.inactive.user.error": "أنت بحاجة لتفعيل حسابك من أجل تسجيل الدخول{lineBreak}\n{lineBreak}لقد أرسلنا للتو رابطًا للتفعيل إلى {email}. إن لم تتلقّ بريدًا إلكترونيا، تفقّد مجلدات الرسائل غير المرغوب فيها أو {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "كونك مستخدمًا على {allowedDomain}، فإن عليك تسجيل الدخول باستخدام {tpaLink} الخاص بـ {allowedDomain} .",
|
"allowed.domain.login.error": "كونك مستخدمًا على {allowedDomain}، فإن عليك تسجيل الدخول باستخدام {tpaLink} الخاص بـ {allowedDomain} .",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "اسم المستخدم أو البريد الإلكتروني أو كلمة المرور التي أدخلتها غير صحيحة. لديك {remainingAttempts, plural,\n one {محاولة واحدة}\n two {محاولتان}\n few {# محاولات}\n many {# محاولة}\n other {# محاولة}\n} أخرى لتسجيل الدخول قبل أن يتم إقفال حسابك مؤقتًا.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "إن نسيت كلمة مرورك، {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "إن نسيت كلمة مرورك، {resetLink}",
|
||||||
"account.locked.out.message.2": "لتكون في مأمن، يمكنك {resetLink} قبل تكرار المحاولة.",
|
"account.locked.out.message.2": "لتكون في مأمن، يمكنك {resetLink} قبل تكرار المحاولة.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "اسم المستخدم أو البريد الإلكتروني أو كلمة المرور التي أدخلتها غير صحيحة. يرجى تكرار المحاولة أو {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "اسم المستخدم أو البريد الإلكتروني أو كلمة المرور التي أدخلتها غير صحيحة. يرجى تكرار المحاولة أو {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "التوصيات | {siteName}",
|
"recommendation.page.title": "التوصيات | {siteName}",
|
||||||
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
|
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
|
||||||
"recommendation.skip.button": "التخطي مؤقتا",
|
"recommendation.skip.button": "التخطي مؤقتا",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "التسجيل | {siteName}",
|
"register.page.title": "التسجيل | {siteName}",
|
||||||
"registration.fullname.label": "الاسم الكامل",
|
"registration.fullname.label": "الاسم الكامل",
|
||||||
"registration.email.label": "البريد الإلكتروني",
|
"registration.email.label": "البريد الإلكتروني",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "لتفعيل الحساب و التحديثات الهامة",
|
"help.text.email": "لتفعيل الحساب و التحديثات الهامة",
|
||||||
"create.account.for.free.button": "إنشاء حساب مجانا",
|
"create.account.for.free.button": "إنشاء حساب مجانا",
|
||||||
"registration.other.options.heading": "أو سجل باستخدام:",
|
"registration.other.options.heading": "أو سجل باستخدام:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "بيانات المؤسسة / الجامعة",
|
"register.institution.login.button": "بيانات المؤسسة / الجامعة",
|
||||||
"register.institution.login.page.title": "التسجيل باستخدام بيانات المؤسسة / الجامعة",
|
"register.institution.login.page.title": "التسجيل باستخدام بيانات المؤسسة / الجامعة",
|
||||||
"empty.name.field.error": "أدخل اسمك الكامل",
|
"empty.name.field.error": "أدخل اسمك الكامل",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "شروط الخدمة",
|
"terms.of.service": "شروط الخدمة",
|
||||||
"registration.username.suggestion.label": "مقترح:",
|
"registration.username.suggestion.label": "مقترح:",
|
||||||
"did.you.mean.alert.text": "هل تقصد",
|
"did.you.mean.alert.text": "هل تقصد",
|
||||||
"register.page.terms.of.service.and.honor.code": "بإنشاءك حسابًا، فإنك توافق على {tosAndHonorCode} و تقر بأن {platformName} و كل عضو يعالج بياناتك الشخصية وفقًا لـ{privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "اوافق على شروط {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "اوافق على {platformName} {termsOfService}",
|
|
||||||
"sign.in": "تسجيل الدخول",
|
"sign.in": "تسجيل الدخول",
|
||||||
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
|
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
|
||||||
"reset.password": "إعادة ضبط كلمة المرور",
|
"reset.password": "إعادة ضبط كلمة المرور",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "طلبات أكثر مما ينبغي.",
|
"reset.server.rate.limit.error": "طلبات أكثر مما ينبغي.",
|
||||||
"reset.password.success.heading": "تمت إعادة ضبط كلمة المرور.",
|
"reset.password.success.heading": "تمت إعادة ضبط كلمة المرور.",
|
||||||
"reset.password.success": "تمت إعادة ضبط كلمة مرورك. سجل الدخول إلى حسابك.",
|
"reset.password.success": "تمت إعادة ضبط كلمة مرورك. سجل الدخول إلى حسابك.",
|
||||||
"rate.limit.error": "حدث خطأ بسبب كثرة الطلبات. رجاءً حاول مرة أخرى بعد مضي بعض الوقت."
|
"rate.limit.error": "حدث خطأ بسبب كثرة الطلبات. رجاءً حاول مرة أخرى بعد مضي بعض الوقت.",
|
||||||
|
"start.learning": "ابدأ التعلم ",
|
||||||
|
"with.site.name": "مع {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "أهلا بك {username} في {siteName}",
|
||||||
|
"complete.your.profile.1": "أكمل",
|
||||||
|
"complete.your.profile.2": "ملفك الشخصي",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||||
"with.site.name": "with {siteName}",
|
|
||||||
"complete.your.profile.1": "Complete",
|
|
||||||
"complete.your.profile.2": "your profile",
|
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||||
"logistration.sign.in": "Sign in",
|
"logistration.sign.in": "Sign in",
|
||||||
"logistration.register": "Register",
|
"logistration.register": "Register",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.heading": "Reset password",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Check your email",
|
"confirmation.message.title": "Check your email",
|
||||||
"confirmation.support.link": "contact technical support",
|
"confirmation.support.link": "contact technical support",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"need.help.sign.in.text": "Need help signing in?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Sign in",
|
"sign.in.text": "Sign in",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"extend.field.errors": "{emailError} below.",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.heading": "Invalid password reset link",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Full name",
|
"registration.fullname.label": "Full name",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "For account activation and important updates",
|
"help.text.email": "For account activation and important updates",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"create.account.for.free.button": "Create an account for free",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.other.options.heading": "Or register with:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"register.institution.login.button": "Institution/campus credentials",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"empty.name.field.error": "Enter your full name",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "Terms of Service",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "Suggested:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "Did you mean",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Sign in",
|
"sign.in": "Sign in",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"reset.password.page.title": "Reset Password | {siteName}",
|
||||||
"reset.password": "Reset password",
|
"reset.password": "Reset password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"reset.server.rate.limit.error": "Too many requests.",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"reset.password.success.heading": "Password reset complete.",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||||
|
"start.learning": "Start learning",
|
||||||
|
"with.site.name": "with {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complete",
|
||||||
|
"complete.your.profile.2": "your profile",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Beginne zu lernen",
|
"error.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
|
||||||
"with.site.name": "mit {siteName}",
|
|
||||||
"complete.your.profile.1": "Vervollständige",
|
|
||||||
"complete.your.profile.2": "dein Profil",
|
|
||||||
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Wählen Sie Ihre Institution aus der folgenden Liste aus",
|
"institution.login.page.sub.heading": "Wählen Sie Ihre Institution aus der folgenden Liste aus",
|
||||||
"logistration.sign.in": "Anmelden",
|
"logistration.sign.in": "Anmelden",
|
||||||
"logistration.register": "Registrieren",
|
"logistration.register": "Registrieren",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Beenden Sie die Erstellung Ihres Kontos",
|
"registration.using.tpa.form.heading": "Beenden Sie die Erstellung Ihres Kontos",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "Wir haben eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts an {email} gesendet. Wenn Sie nach 1 Minute keine Nachricht zum Zurücksetzen des Passworts erhalten, überprüfen Sie, ob Sie die richtige E-Mail-Adresse eingegeben haben, oder überprüfen Sie Ihren Spam-Ordner. Wenn Sie weitere Hilfe benötigen, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Passwort vergessen | {siteName}",
|
"forgot.password.page.title": "Passwort vergessen | {siteName}",
|
||||||
"forgot.password.page.heading": "Passwort zurücksetzen",
|
"forgot.password.page.heading": "Passwort zurücksetzen",
|
||||||
"forgot.password.page.instructions": "Bitte geben Sie unten Ihre E-Mail-Adresse ein und wir senden Ihnen eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts.",
|
"forgot.password.page.instructions": "Bitte geben Sie unten Ihre E-Mail-Adresse ein und wir senden Ihnen eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Prüfen Sie Ihr E-Mail-Postfach",
|
"confirmation.message.title": "Prüfen Sie Ihr E-Mail-Postfach",
|
||||||
"confirmation.support.link": "wenden Sie sich an den technischen Support",
|
"confirmation.support.link": "wenden Sie sich an den technischen Support",
|
||||||
"need.help.sign.in.text": "Brauchen Sie Hilfe bei der Anmeldung?",
|
"need.help.sign.in.text": "Brauchen Sie Hilfe bei der Anmeldung?",
|
||||||
"additional.help.text": "Wenden Sie sich für weitere Hilfe an den {platformName}-Support unter",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Anmelden",
|
"sign.in.text": "Anmelden",
|
||||||
"extend.field.errors": "{emailError} unten.",
|
"extend.field.errors": "{emailError} unten.",
|
||||||
"invalid.token.heading": "Ungültiger Link zum Zurücksetzen des Passworts",
|
"invalid.token.heading": "Ungültiger Link zum Zurücksetzen des Passworts",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
"token.validation.internal.sever.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||||
"internal.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
"internal.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||||
"account.activation.error.message": "Etwas ist schief gelaufen, bitte {supportLink} um dieses Problem zu lösen.",
|
"account.activation.error.message": "Etwas ist schief gelaufen, bitte {supportLink} um dieses Problem zu lösen.",
|
||||||
"login.inactive.user.error": "Um sich anzumelden, müssen Sie Ihr Konto aktivieren.{lineBreak} {lineBreak}Wir haben gerade einen Aktivierungslink an {email} gesendet. Wenn Sie keine E-Mail erhalten, überprüfen Sie Ihre Spam-Ordner oder {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "Als {allowedDomain}-Benutzer müssen Sie sich mit Ihrem {allowedDomain} {tpaLink} anmelden.",
|
"allowed.domain.login.error": "Als {allowedDomain}-Benutzer müssen Sie sich mit Ihrem {allowedDomain} {tpaLink} anmelden.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "Der eingegebene Benutzername, die E-Mail oder das Passwort ist falsch. Sie haben {remainingAttempts} weitere Anmeldeversuche, bevor Ihr Konto vorübergehend gesperrt wird.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "Wenn Sie Ihr Passwort vergessen haben, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "Wenn Sie Ihr Passwort vergessen haben, {resetLink}",
|
||||||
"account.locked.out.message.2": "Um auf der sicheren Seite zu sein, können Sie {resetLink} tun, bevor Sie es erneut versuchen.",
|
"account.locked.out.message.2": "Um auf der sicheren Seite zu sein, können Sie {resetLink} tun, bevor Sie es erneut versuchen.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "Der eingegebene Benutzername, die E-Mail-Adresse oder das Passwort ist falsch. Bitte versuchen Sie es erneut oder {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "Der eingegebene Benutzername, die E-Mail-Adresse oder das Passwort ist falsch. Bitte versuchen Sie es erneut oder {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Empfehlungen | {siteName}",
|
"recommendation.page.title": "Empfehlungen | {siteName}",
|
||||||
"recommendation.page.heading": "Wir haben ein paar Empfehlungen für den Einstieg.",
|
"recommendation.page.heading": "Wir haben ein paar Empfehlungen für den Einstieg.",
|
||||||
"recommendation.skip.button": "Überspringen",
|
"recommendation.skip.button": "Überspringen",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Registrieren | {siteName}",
|
"register.page.title": "Registrieren | {siteName}",
|
||||||
"registration.fullname.label": "Vollständiger Name",
|
"registration.fullname.label": "Vollständiger Name",
|
||||||
"registration.email.label": "E-Mail-Adresse",
|
"registration.email.label": "E-Mail-Adresse",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "Für die Kontoaktivierung und wichtige Updates",
|
"help.text.email": "Für die Kontoaktivierung und wichtige Updates",
|
||||||
"create.account.for.free.button": "Erstellen Sie kostenlos ein Benutzerkonto",
|
"create.account.for.free.button": "Erstellen Sie kostenlos ein Benutzerkonto",
|
||||||
"registration.other.options.heading": "Oder registrieren Sie sich bei:",
|
"registration.other.options.heading": "Oder registrieren Sie sich bei:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Zeugnisse der Institution/des Campus",
|
"register.institution.login.button": "Zeugnisse der Institution/des Campus",
|
||||||
"register.institution.login.page.title": "Registrieren Sie sich mit Institutions-/Campus-Anmeldeinformationen",
|
"register.institution.login.page.title": "Registrieren Sie sich mit Institutions-/Campus-Anmeldeinformationen",
|
||||||
"empty.name.field.error": "Geben Sie Ihren vollständigen Namen ein",
|
"empty.name.field.error": "Geben Sie Ihren vollständigen Namen ein",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Nutzungsbedingungen",
|
"terms.of.service": "Nutzungsbedingungen",
|
||||||
"registration.username.suggestion.label": "Empfohlen:",
|
"registration.username.suggestion.label": "Empfohlen:",
|
||||||
"did.you.mean.alert.text": "Meinten Sie",
|
"did.you.mean.alert.text": "Meinten Sie",
|
||||||
"register.page.terms.of.service.and.honor.code": "Wenn Sie ein Konto erstellen, stimmen Sie den {tosAndHonorCode} zu und erkennen an, dass {platformName} und jedes \nMitglied Ihre personenbezogenen Daten in Übereinstimmung mit den {privacyPolicy} verarbeitet.",
|
|
||||||
"register.page.honor.code": "Ich stimme den {platformName} {tosAndHonorCode} zu",
|
|
||||||
"register.page.terms.of.service": "Ich stimme den {platformName} {termsOfService} zu",
|
|
||||||
"sign.in": "Anmelden",
|
"sign.in": "Anmelden",
|
||||||
"reset.password.page.title": "Passwort zurücksetzen | {siteName}",
|
"reset.password.page.title": "Passwort zurücksetzen | {siteName}",
|
||||||
"reset.password": "Passwort zurücksetzen",
|
"reset.password": "Passwort zurücksetzen",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Zu viele Anfragen.",
|
"reset.server.rate.limit.error": "Zu viele Anfragen.",
|
||||||
"reset.password.success.heading": "Zurücksetzen des Passworts abgeschlossen.",
|
"reset.password.success.heading": "Zurücksetzen des Passworts abgeschlossen.",
|
||||||
"reset.password.success": "Ihr Passwort wurde zurückgesetzt. Melden Sie sich bei Ihrem Konto an.",
|
"reset.password.success": "Ihr Passwort wurde zurückgesetzt. Melden Sie sich bei Ihrem Konto an.",
|
||||||
"rate.limit.error": "Aufgrund zu vieler Anfragen ist ein Fehler aufgetreten. Bitte versuchen Sie es nach einiger Zeit erneut."
|
"rate.limit.error": "Aufgrund zu vieler Anfragen ist ein Fehler aufgetreten. Bitte versuchen Sie es nach einiger Zeit erneut.",
|
||||||
|
"start.learning": "Beginne zu lernen",
|
||||||
|
"with.site.name": "mit {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Vervollständige",
|
||||||
|
"complete.your.profile.2": "dein Profil",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Empieza a aprender",
|
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, verifica la URL y vuelve a intentarlo.",
|
||||||
"with.site.name": "con {siteName}",
|
|
||||||
"complete.your.profile.1": "Completado",
|
|
||||||
"complete.your.profile.2": "tu perfil ",
|
|
||||||
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
|
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
|
||||||
"logistration.sign.in": "Iniciar sesión",
|
"logistration.sign.in": "Iniciar sesión",
|
||||||
"logistration.register": "Registrarse",
|
"logistration.register": "Registrarse",
|
||||||
"enterprisetpa.title.heading": "¿Deseas iniciar sesión con tus credenciales de {providerName}?",
|
"enterprisetpa.title.heading": "¿Deseas iniciar sesión con tus credenciales de {providerName}?",
|
||||||
"enterprisetpa.login.button.text": "Mostrar otras formas de iniciar sesión o de registrarme",
|
"enterprisetpa.login.button.text": "Mostrar otras formas de iniciar sesión o de registrarme",
|
||||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
"enterprisetpa.login.button.text.public.account.creation.disabled": "Mostrar otras formas de iniciar sesión",
|
||||||
"sso.sign.in.with": "Inicio de sesión con {providerName}",
|
"sso.sign.in.with": "Inicio de sesión con {providerName}",
|
||||||
"sso.create.account.using": "Crear una cuenta con {providerName}",
|
"sso.create.account.using": "Crear una cuenta con {providerName}",
|
||||||
"show.password": "Mostrar contraseña",
|
"show.password": "Mostrar contraseña",
|
||||||
@@ -22,10 +18,9 @@
|
|||||||
"login.third.party.auth.account.not.linked": "Te has registrado correctamente en {currentProvider}, pero tu cuenta de {currentProvider} no tiene una cuenta de {platformName} asociada. Para asociar tus cuentas, inicia sesión ahora usando tu contraseña de {platformName}.",
|
"login.third.party.auth.account.not.linked": "Te has registrado correctamente en {currentProvider}, pero tu cuenta de {currentProvider} no tiene una cuenta de {platformName} asociada. Para asociar tus cuentas, inicia sesión ahora usando tu contraseña de {platformName}.",
|
||||||
"register.third.party.auth.account.not.linked": "¡Has iniciado sesión con éxito en {currentProvider}! Sólo necesitamos un poco más de información antes de que empieces a aprender con {platformName}.",
|
"register.third.party.auth.account.not.linked": "¡Has iniciado sesión con éxito en {currentProvider}! Sólo necesitamos un poco más de información antes de que empieces a aprender con {platformName}.",
|
||||||
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
|
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "Soporte edX",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Elegir el tipo de solicitud:",
|
||||||
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, verifica la URL y vuelve a intentarlo.",
|
"forgot.password.confirmation.message": "Enviamos un correo electrónico a {email} con instrucciones para restablecer su contraseña. Si no recibe un mensaje de restablecimiento de contraseña después de 1 minuto, verifique que ingresó la dirección de correo electrónico correcta o verifique su carpeta de correo no deseado. Si necesita más ayuda, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "Hemos enviado un correo electrónico a {email} con instrucciones para restablecer tu contraseña.\n Si no recibes un mensaje de restablecimiento de contraseña después de 1 minuto, verifica que has introducido\n la dirección de correo electrónico correcta, o comprueba tu carpeta de correo no deseado. Si necesitas más ayuda, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Olvidé la contraseña | {siteName}",
|
"forgot.password.page.title": "Olvidé la contraseña | {siteName}",
|
||||||
"forgot.password.page.heading": "Restablecer mi contraseña",
|
"forgot.password.page.heading": "Restablecer mi contraseña",
|
||||||
"forgot.password.page.instructions": "Por favor, introduce tu dirección de correo electrónico y te enviaremos un correo electrónico con instrucciones sobre cómo restablecer tu contraseña.",
|
"forgot.password.page.instructions": "Por favor, introduce tu dirección de correo electrónico y te enviaremos un correo electrónico con instrucciones sobre cómo restablecer tu contraseña.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Verifica tu correo electrónico",
|
"confirmation.message.title": "Verifica tu correo electrónico",
|
||||||
"confirmation.support.link": "entra en contacto con el equipo de soporte técnico",
|
"confirmation.support.link": "entra en contacto con el equipo de soporte técnico",
|
||||||
"need.help.sign.in.text": "¿Necesitas ayuda para iniciar sesión?",
|
"need.help.sign.in.text": "¿Necesitas ayuda para iniciar sesión?",
|
||||||
"additional.help.text": "Para obtener ayuda adicional, comuníquese con el soporte de {platformName} en",
|
"additional.help.text": "Para obtener ayuda adicional, comuníquese con el soporte {platformName} en",
|
||||||
"sign.in.text": "Iniciar sesión",
|
"sign.in.text": "Iniciar sesión",
|
||||||
"extend.field.errors": "{emailError} a continuación.",
|
"extend.field.errors": "{emailError} a continuación.",
|
||||||
"invalid.token.heading": "Enlace de restablecimiento de contraseña inválido",
|
"invalid.token.heading": "Enlace de restablecimiento de contraseña inválido",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "Se ha producido un error. Intenta actualizar la página o verifica tu conexión a Internet.",
|
"token.validation.internal.sever.error": "Se ha producido un error. Intenta actualizar la página o verifica tu conexión a Internet.",
|
||||||
"internal.server.error": "Se ha producido un error. Intenta actualizar la página o verifica tu conexión a Internet.",
|
"internal.server.error": "Se ha producido un error. Intenta actualizar la página o verifica tu conexión a Internet.",
|
||||||
"account.activation.error.message": "Algo no funcionó correctamente, por favor {supportLink} para resolver este problema.",
|
"account.activation.error.message": "Algo no funcionó correctamente, por favor {supportLink} para resolver este problema.",
|
||||||
"login.inactive.user.error": "Para iniciar sesión, debes activar tu cuenta..{lineBreak}\n {lineBreak} Acabamos de enviar un enlace de activación a {email}. Si no recibes un correo electrónico,\n revisa tus carpetas de spam o {supportLink}.",
|
"login.inactive.user.error": "Para iniciar sesión, debe activar su cuenta.{lineBreak} {lineBreak}Acabamos de enviar un enlace de activación a {email}. Si no recibe un correo electrónico, revise sus carpetas de spam o {supportLink}.",
|
||||||
"allowed.domain.login.error": "Como usuario {allowedDomain}, debe iniciar sesión con su {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "Como usuario {allowedDomain}, debe iniciar sesión con su {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "El nombre de usuario, el email o la contraseña que has introducido son incorrectos. Tienes {remainingAttempts} intentos más de inicio de sesión\n antes de que tu cuenta se bloquee temporalmente.",
|
"login.incorrect.credentials.error.attempts.text.1": "El nombre de usuario, correo electrónico o contraseña que ingresó es incorrecto. Tiene {remainingAttempts} más intentos de inicio de sesión antes de que su cuenta se bloquee temporalmente.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "Si has olvidado tu contraseña, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "Si has olvidado tu contraseña, {resetLink}",
|
||||||
"account.locked.out.message.2": "Para estar seguro, puedes {resetLink} antes de volver a intentarlo.",
|
"account.locked.out.message.2": "Para estar seguro, puedes {resetLink} antes de volver a intentarlo.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "El nombre de usuario, el correo electrónico o la contraseña que has introducido son incorrectos. Por favor, inténtalo de nuevo o {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "El nombre de usuario, el correo electrónico o la contraseña que has introducido son incorrectos. Por favor, inténtalo de nuevo o {resetLink}.",
|
||||||
@@ -96,8 +91,8 @@
|
|||||||
"password.security.block.body": "Nuestro sistema detectó que su contraseña es vulnerable. Cambie su contraseña para que su cuenta permanezca segura.",
|
"password.security.block.body": "Nuestro sistema detectó que su contraseña es vulnerable. Cambie su contraseña para que su cuenta permanezca segura.",
|
||||||
"password.security.close.button": "Cerrar",
|
"password.security.close.button": "Cerrar",
|
||||||
"password.security.redirect.to.reset.password.button": "Restablece tu contraseña",
|
"password.security.redirect.to.reset.password.button": "Restablece tu contraseña",
|
||||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
"login.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
|
||||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
"progressive.profiling.page.title": "Bienvenido | {siteName}",
|
||||||
"progressive.profiling.page.heading": "Unas cuantas preguntas para ti nos ayudarán a mejorar.",
|
"progressive.profiling.page.heading": "Unas cuantas preguntas para ti nos ayudarán a mejorar.",
|
||||||
"optional.fields.information.link": "Aprende más sobre cómo usamos esta información.",
|
"optional.fields.information.link": "Aprende más sobre cómo usamos esta información.",
|
||||||
"optional.fields.submit.button": "Enviar",
|
"optional.fields.submit.button": "Enviar",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recomendaciones | {siteName}",
|
"recommendation.page.title": "Recomendaciones | {siteName}",
|
||||||
"recommendation.page.heading": "Tenemos algunas recomendaciones para empezar.",
|
"recommendation.page.heading": "Tenemos algunas recomendaciones para empezar.",
|
||||||
"recommendation.skip.button": "Saltar por ahora ",
|
"recommendation.skip.button": "Saltar por ahora ",
|
||||||
|
"recommendation.option.trending": "Tendencias",
|
||||||
|
"recommendation.option.popular": "Más popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recomendado para usted",
|
||||||
|
"recommendation.product-card.pill-text.course": "Curso",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Certificado profesional",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Ofrecido en Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Ofrecido a través de Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Suscripción",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Abrir un enlace en una pestaña nueva",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Nombre completo",
|
"registration.fullname.label": "Nombre completo",
|
||||||
"registration.email.label": "Correo electrónico",
|
"registration.email.label": "Correo electrónico",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "Para la activación de la cuenta y las actualizaciones importantes",
|
"help.text.email": "Para la activación de la cuenta y las actualizaciones importantes",
|
||||||
"create.account.for.free.button": "Crea una cuenta gratis",
|
"create.account.for.free.button": "Crea una cuenta gratis",
|
||||||
"registration.other.options.heading": "O regístrese con:",
|
"registration.other.options.heading": "O regístrese con:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Credenciales de la institución/campus",
|
"register.institution.login.button": "Credenciales de la institución/campus",
|
||||||
"register.institution.login.page.title": "Registro con credenciales de la institución/campus",
|
"register.institution.login.page.title": "Registro con credenciales de la institución/campus",
|
||||||
"empty.name.field.error": "Introduce tu nombre completo",
|
"empty.name.field.error": "Introduce tu nombre completo",
|
||||||
@@ -141,16 +147,13 @@
|
|||||||
"registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
|
"registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
|
||||||
"registration.rate.limit.error": "Demasiados intentos de registro fallidos. Vuelve a intentarlo más tarde.",
|
"registration.rate.limit.error": "Demasiados intentos de registro fallidos. Vuelve a intentarlo más tarde.",
|
||||||
"registration.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
|
"registration.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
|
||||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
"registration.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
|
||||||
"terms.of.service.and.honor.code": "Condiciones de servicio y código de honor",
|
"terms.of.service.and.honor.code": "Condiciones de servicio y código de honor",
|
||||||
"privacy.policy": "Política de privacidad ",
|
"privacy.policy": "Política de privacidad ",
|
||||||
"honor.code": "Código de Honor",
|
"honor.code": "Código de Honor",
|
||||||
"terms.of.service": "Términos de servicio",
|
"terms.of.service": "Términos de servicio",
|
||||||
"registration.username.suggestion.label": "Se recomienda:",
|
"registration.username.suggestion.label": "Se recomienda:",
|
||||||
"did.you.mean.alert.text": "¿Quieres decir",
|
"did.you.mean.alert.text": "¿Quieres decir",
|
||||||
"register.page.terms.of.service.and.honor.code": "Al crear una cuenta, aceptas el {tosAndHonorCode} y reconoces que {platformName} y cada\n Miembro procesa tus datos personales de acuerdo con la {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "Acepto las {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "Acepto las {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Iniciar sesión",
|
"sign.in": "Iniciar sesión",
|
||||||
"reset.password.page.title": "Restablecer contraseña | {siteName}",
|
"reset.password.page.title": "Restablecer contraseña | {siteName}",
|
||||||
"reset.password": "Restablecer mi contraseña",
|
"reset.password": "Restablecer mi contraseña",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Demasiadas solicitudes.",
|
"reset.server.rate.limit.error": "Demasiadas solicitudes.",
|
||||||
"reset.password.success.heading": "Restablecimiento de la contraseña completado.",
|
"reset.password.success.heading": "Restablecimiento de la contraseña completado.",
|
||||||
"reset.password.success": "Tu contraseña ha sido restablecida. Acceda a tu cuenta.",
|
"reset.password.success": "Tu contraseña ha sido restablecida. Acceda a tu cuenta.",
|
||||||
"rate.limit.error": "Se ha producido un error debido a demasiadas solicitudes. Por favor, inténtalo de nuevo después de algún tiempo."
|
"rate.limit.error": "Se ha producido un error debido a demasiadas solicitudes. Por favor, inténtalo de nuevo después de algún tiempo.",
|
||||||
|
"start.learning": "Empieza a aprender",
|
||||||
|
"with.site.name": "con {siteName}",
|
||||||
|
"your.career.turning.point": "El punto de inflexión de tu carrera",
|
||||||
|
"is.here": "es aquí.",
|
||||||
|
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Completado",
|
||||||
|
"complete.your.profile.2": "tu perfil ",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "Al crear una cuenta, acepta {tosAndHonorCode} y reconoce que {platformName} y cada miembro procesan sus datos personales de acuerdo con {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "Acepto las {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "Acepto las {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
181
src/i18n/messages/fa_IR.json
Normal file
181
src/i18n/messages/fa_IR.json
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
{
|
||||||
|
"error.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی اینترنتی وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره تلاش کنید.",
|
||||||
|
"institution.login.page.sub.heading": "موسسه خود را از فهرست زیر برگزینید",
|
||||||
|
"logistration.sign.in": "ورود",
|
||||||
|
"logistration.register": "ثبتنام",
|
||||||
|
"enterprisetpa.title.heading": "آیا میخواهید با استفاده از اطلاعات کاربری {providerName} خود وارد سامانه شوید؟",
|
||||||
|
"enterprisetpa.login.button.text": "راههای دیگری برای ورود به سامانه یا ثبتنام به من نشان بده",
|
||||||
|
"enterprisetpa.login.button.text.public.account.creation.disabled": "راه های دیگری را برای ورود به سیستم به من نشان دهید",
|
||||||
|
"sso.sign.in.with": "با {providerName} وارد شوید",
|
||||||
|
"sso.create.account.using": "با استفاده از {providerName} حساب کاربری بسازید",
|
||||||
|
"show.password": "نمایش گذرواژه",
|
||||||
|
"hide.password": "پنهانسازی گذرواژه",
|
||||||
|
"one.letter": "1 نامه",
|
||||||
|
"one.number": "1 رقم",
|
||||||
|
"eight.characters": "8 نویسه",
|
||||||
|
"password.sr.only.helping.text": "گذرواژه باید حداقل 8 نویسه، حداقل یک حرف و حداقل یک عدد داشته باشد",
|
||||||
|
"tpa.alert.heading": "تقریبا تمام شد!",
|
||||||
|
"login.third.party.auth.account.not.linked": "شما با موفقیت به {currentProvider} وارد شدید، اما حساب کاربری {currentProvider} شما به حساب کاربری {platformName} پیوند ندارد. برای پیوند حسابهای کاربری خود، اکنون با استفاده از گذرواژه {platformName} خود وارد شوید.",
|
||||||
|
"register.third.party.auth.account.not.linked": "شما با موفقیت به {currentProvider} وارد شدید! پیش از آغاز یادگیری با {platformName} فقط به کمی اطلاعات بیشتر نیاز داریم.",
|
||||||
|
"registration.using.tpa.form.heading": "ساخت حساب کاربری خود را به اتمام برسانید",
|
||||||
|
"zendesk.supportTitle": "پشتیبانی edX",
|
||||||
|
"zendesk.selectTicketForm": "لطفا نوع درخواست خود را انتخاب کنید:",
|
||||||
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
|
"forgot.password.page.title": "فراموش گذرواژه | {siteName}",
|
||||||
|
"forgot.password.page.heading": "بازتنظیم گذرواژه",
|
||||||
|
"forgot.password.page.instructions": "لطفا نشانی رایانامه خود را در قسمت زیر وارد کنید و ما رایانامهای حاوی دستورالعمل نحوه بازتنظیم مجدد گذرواژه برای شما ارسال خواهیم کرد.",
|
||||||
|
"forgot.password.page.invalid.email.message": "نشانی رایانامه معتبری را وارد کنید",
|
||||||
|
"forgot.password.page.email.field.label": "رایانامه",
|
||||||
|
"forgot.password.page.submit.button": "ارسال",
|
||||||
|
"forgot.password.error.alert.title.": "ما نتوانستیم با شما تماس بگیریم.",
|
||||||
|
"forgot.password.error.message.title": "خطایی رخ داد.",
|
||||||
|
"forgot.password.request.in.progress.message": "درخواست قبلی شما در حال انجام است، لطفا چند لحظه دیگر دوباره تلاش کنید.",
|
||||||
|
"forgot.password.empty.email.field.error": "نشانی رایانامه خود را وارد کنید",
|
||||||
|
"forgot.password.email.help.text": "نشانی رایانامهای که برای ثبتنام در {platformName} استفاده کردید",
|
||||||
|
"confirmation.message.title": "صندوق رایانامه خود را ببینید",
|
||||||
|
"confirmation.support.link": "با پشتیبانی فنی تماس بگیرید",
|
||||||
|
"need.help.sign.in.text": "برای ورود به سامانه نیاز به کمک دارید؟",
|
||||||
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
|
"sign.in.text": "ورود",
|
||||||
|
"extend.field.errors": "{emailError} زیر.",
|
||||||
|
"invalid.token.heading": "پیوند بازتنظیم گذرواژه معتبر نیست",
|
||||||
|
"invalid.token.error.message": "این پیوند برای بازتنظیم گذرواژه معتبر نیست. ممکن است قبلاً استفاده شده باشد. برای دریافت پیوند جدید نشانی رایانامه خود را در زیر وارد کنید.",
|
||||||
|
"token.validation.rate.limit.error.heading": "تعداد زیاد درخواست",
|
||||||
|
"token.validation.rate.limit.error": "به دلیل درخواستهای زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید.",
|
||||||
|
"token.validation.internal.sever.error.heading": "اعتبارسنجی رمز انجام نشد",
|
||||||
|
"token.validation.internal.sever.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||||
|
"internal.server.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||||
|
"account.activation.error.message": "اشتباهی رخ داد، لطفاً برای حل این مساله ، به این قسمت{supportLink} مراجعه کنید.",
|
||||||
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
|
"allowed.domain.login.error": "به عنوان کاربر {allowedDomain}، باید با {allowedDomain} {tpaLink} خود وارد شوید.",
|
||||||
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
|
"login.incorrect.credentials.error.attempts.text.2": "اگر گذرواژه خود را فراموش کردهاید، {resetLink}",
|
||||||
|
"account.locked.out.message.2": "برای حفظ امنیت، میتوانید پیش از تلاش مجدد، {resetLink} را انجام دهید.",
|
||||||
|
"login.incorrect.credentials.error.with.reset.link": "نام کاربری، نشانی رایانامه یا گذرواژهای که وارد کردید درست نیست. لطفاً دوباره امتحان کنید یا {resetLink}.",
|
||||||
|
"login.page.title": "ورود به سامانه | {siteName}",
|
||||||
|
"login.user.identity.label": "نام کاربری یا نشانی رایانامه",
|
||||||
|
"login.password.label": "گذرواژه",
|
||||||
|
"sign.in.button": "ورود به سامانه",
|
||||||
|
"forgot.password": "فراموشی گذرواژه",
|
||||||
|
"institution.login.button": "اعتبارنامههای موسسه/پردیس",
|
||||||
|
"institution.login.page.title": "با اعتبار موسسه/پردیس وارد شوید",
|
||||||
|
"login.other.options.heading": "یا وارد شوید با:",
|
||||||
|
"non.compliant.password.title": "ما اخیراً الزامات گذرواژه خود را تغییر دادیم",
|
||||||
|
"non.compliant.password.message": "گذرواژه فعلی شما الزامات امنیتی جدید را برآورده نمیکند. ما فقط یک پیام بازتنظیم گذرواژه به نشانی رایانامه مرتبط با این حساب کاربری ارسال کردیم. از اینکه به ما کمک میکنید تا دادههای شما را ایمن نگه دارید متشکریم.",
|
||||||
|
"account.locked.out.message.1": "حساب کاربری شما، به دلیل حفاظت، بهطور موقت قفل شده است. 30 دقیقه دیگر دوباره امتحان کنید.",
|
||||||
|
"enterprise.login.btn.text": "اعتبار دانشکده یا شرکت",
|
||||||
|
"username.or.email.format.validation.less.chars.message": "نام کاربری یا نشانی رایانامه حداقل باید 3 نویسه داشته باشد",
|
||||||
|
"email.validation.message": "نام کاربری یا رایانامه خود را وارد کنید",
|
||||||
|
"password.validation.message": "معیارهای گذرواژه رعایت نشده است",
|
||||||
|
"account.activation.success.message.title": "موفق شدید! شما حساب کاربری خود را فعال کردید.",
|
||||||
|
"account.activation.success.message": "اکنون رایانامههای مربوط به روزآمدسازیها و هشدارها درباره دورههای آموزشی را که در آن ثبتنام کردهاید از ما دریافت خواهید کرد. برای ادامه وارد شوید.",
|
||||||
|
"account.activation.info.message": "حساب کاربری مورد نظر شما قبلاً فعال شده است.",
|
||||||
|
"account.activation.error.message.title": "امکان فعالسازی حساب کاربری شما نبود",
|
||||||
|
"account.activation.support.link": "تماس با پشتیبانی ",
|
||||||
|
"account.confirmation.success.message.title": "موفق شدید! نشانی رایانامه خود را تایید کردید.",
|
||||||
|
"account.confirmation.success.message": "برای ادامه وارد سامانه شوید.",
|
||||||
|
"account.confirmation.info.message": "این نشانی رایانامه قبلا تایید شدهاست.",
|
||||||
|
"account.confirmation.error.message.title": "امکان تایید نشانی رایانامه شما وجود ندارد",
|
||||||
|
"tpa.account.link": "حساب {provider}",
|
||||||
|
"internal.server.error.message": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||||
|
"login.rate.limit.reached.message": "شما برای ورود به حساب کاربری چند بار تلاش ناموفق داشتید. لطفا بعدا تلاش نمایید.",
|
||||||
|
"login.failure.header.title": "قادر نیستیم شما را به سامانه وارد کنیم.",
|
||||||
|
"contact.support.link": "با پشتیبانی {platformName} تماس بگیرید",
|
||||||
|
"login.incorrect.credentials.error": "نام کاربری، نشانی رایانامه یا گذرواژهای که وارد کردید نادرست است. لطفا دوباره تلاش کنید.",
|
||||||
|
"login.form.invalid.error.message": "لطفا قسمتهای زیر را پر کنید.",
|
||||||
|
"login.incorrect.credentials.error.reset.link.text": "گذرواژه را بازتنظیم کنید",
|
||||||
|
"login.incorrect.credentials.error.before.account.blocked.text": "برای بازتنظیم اینجا بزنید",
|
||||||
|
"password.security.nudge.title": "امنیت گذرواژه",
|
||||||
|
"password.security.block.title": "تغییر گذرواژه ضروری است",
|
||||||
|
"password.security.nudge.body": "سامانه ما تشخیص داده است که گذرواژه شما آسیبپذیر است. توصیه ما این است که آن را تغییر دهید تا حساب کاربری شما ایمن بماند.",
|
||||||
|
"password.security.block.body": "سامانه ما تشخیص داده است که گذرواژه شما ضعیف و آسیبپذیر است. گذرواژه خود را تغییر دهید تا حساب کاربری شما ایمن بماند.",
|
||||||
|
"password.security.close.button": "بستن",
|
||||||
|
"password.security.redirect.to.reset.password.button": "بازتنظیم گذرواژه",
|
||||||
|
"login.tpa.authentication.failure": "متأسفیم، شما مجاز به دسترسی به {platform_name} از طریق این کانال نیستید. لطفاً برای دسترسی به {platform_name} با سرپرست یا مدیر آموزشی خود تماس بگیرید.{lineBreak}{lineBreak}جزئیات خطا:{lineBreak}{errorMessage}",
|
||||||
|
"progressive.profiling.page.title": "خوش آمدید | {siteName}",
|
||||||
|
"progressive.profiling.page.heading": "چند سوال برای شما به ما کمک خواهد کرد تا باهوشتر شویم.",
|
||||||
|
"optional.fields.information.link": "درباره نحوه استفاده ما از این اطلاعات بیشتر بدانید.",
|
||||||
|
"optional.fields.submit.button": "ارسال",
|
||||||
|
"optional.fields.skip.button": "فعلا بگذرید",
|
||||||
|
"optional.fields.next.button": "بعدی",
|
||||||
|
"continue.to.platform": "ادامه در {platformName}",
|
||||||
|
"modal.title": "از اینکه اطلاع دادید تشکر میکنیم.",
|
||||||
|
"modal.description": "در صورت تغییر تصمیمتان، شما هر زمانی این امکان را دارید که پرونده کاربری خود را در قسمت تنظیمات تکمیل کنید. ",
|
||||||
|
"welcome.page.error.heading": "امکان روزآمدسازی پرونده کاربری شما نیست",
|
||||||
|
"welcome.page.error.message": "خطایی رخ داد. میتوانید پرونده کاربری خود را هر زمان در قسمت تنظیمات تکمیل کنید.",
|
||||||
|
"recommendation.page.title": "توصیه ها | {siteName}",
|
||||||
|
"recommendation.page.heading": "ما چند توصیه برای شروع کار داریم.",
|
||||||
|
"recommendation.skip.button": "فعلا بگذرید",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "محبوبترین",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "دوره آموزشی",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "گواهی حرفهای",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "ارائه شده در Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "از طریق Shorelight ارائه می شود",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "اشتراک",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "پیوندی را در یک برگه جدید باز می کند",
|
||||||
|
"register.page.title": "ثبتنام | {siteName}",
|
||||||
|
"registration.fullname.label": "نام و نام خانوادگی",
|
||||||
|
"registration.email.label": "رایانامه",
|
||||||
|
"registration.username.label": "نامکاربری عمومی",
|
||||||
|
"registration.password.label": "گذرواژه",
|
||||||
|
"registration.country.label": "کشور/منطقه",
|
||||||
|
"registration.opt.in.label": "با ارسال پیامهای بازرگانی از سوی {siteName} موافقم.",
|
||||||
|
"help.text.name": "این نام در هر گواهی که بدست آورید استفاده خواهد شد.",
|
||||||
|
"help.text.username.1": "نامی که شما را در دورههای آموزشی با آن شناخته خواهید شد.",
|
||||||
|
"help.text.username.2": "این مورد بعدا قابل تغییر نیست.",
|
||||||
|
"help.text.email": "برای فعالسازی حساب کاربری و روزآمدسازیهای مهم",
|
||||||
|
"create.account.for.free.button": "یک حساب کاربری رایگان بسازید",
|
||||||
|
"registration.other.options.heading": "یا ثبتنام کنید با:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
|
"register.institution.login.button": "اعتبارنامههای موسسه/پردیس",
|
||||||
|
"register.institution.login.page.title": "با اعتبارنامه موسسه/پردیس ثبتنام کنید",
|
||||||
|
"empty.name.field.error": "نام و نام خانوادگی خود را وارد کنید",
|
||||||
|
"empty.email.field.error": "نشانی رایانامه خود را وارد کنید",
|
||||||
|
"empty.username.field.error": "نام کاربری باید بین 2 تا 30 نویسه داشته باشد",
|
||||||
|
"empty.password.field.error": "معیارهای گذرواژه رعایت نشده است",
|
||||||
|
"empty.country.field.error": "کشور یا منطقه محل سکونت خود را برگزینید",
|
||||||
|
"email.do.not.match": "نشانیهای رایانامه همخوانی ندارند.",
|
||||||
|
"email.invalid.format.error": "نشانی رایانامه معتبر وارد کنید",
|
||||||
|
"username.validation.message": "نام کاربری باید بین 2 تا 30 نویسه داشته باشد",
|
||||||
|
"name.validation.message": "نامی معتبر وارد کنید",
|
||||||
|
"username.format.validation.message": "نام کاربری فقط میتواند شامل حروف (A-Z، a-z)، اعداد (0-9)، خط زیر (_) و خط فاصله (-) باشد. نام کاربری نمیتواند حاوی فاصله باشد",
|
||||||
|
"registration.request.failure.header": "موفق به ایجاد حساب کاربری شما نشدیم.",
|
||||||
|
"registration.empty.form.submission.error": "لطفاً پاسخهای خود را بررسی کرده و دوباره امتحان کنید.",
|
||||||
|
"registration.request.server.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||||
|
"registration.rate.limit.error": "تعداد دفعات تلاش ناموفق برای ثبتنام بسیار بوده است. بعدا دوباره تلاش کنید.",
|
||||||
|
"registration.tpa.session.expired": "مهلت ثبتنام {provider} به پایان رسیدهاست.",
|
||||||
|
"registration.tpa.authentication.failure": "متأسفیم، شما مجاز به دسترسی به {platform_name} از طریق این کانال نیستید. لطفاً برای دسترسی به {platform_name} با سرپرست یا مدیر آموزشی خود تماس بگیرید.{lineBreak}{lineBreak}جزئیات خطا:{lineBreak}{errorMessage}",
|
||||||
|
"terms.of.service.and.honor.code": "شرایط استفاده از خدمات و اصول اخلاقی",
|
||||||
|
"privacy.policy": "قواعد حفظ حریم خصوصی",
|
||||||
|
"honor.code": "اصول اخلاقی",
|
||||||
|
"terms.of.service": "شرایط استفاده از خدمات",
|
||||||
|
"registration.username.suggestion.label": "پیشنهادشده:",
|
||||||
|
"did.you.mean.alert.text": "منظور شما این بود",
|
||||||
|
"sign.in": "ورود",
|
||||||
|
"reset.password.page.title": "بازتنظیم گذرواژه | {siteName}",
|
||||||
|
"reset.password": "بازتنظیم گذرواژه",
|
||||||
|
"reset.password.page.instructions": "گذرواژه جدید را مجددا وارد کنید ",
|
||||||
|
"new.password.label": "گذرواژه جدید",
|
||||||
|
"confirm.password.label": "تایید گذرواژه",
|
||||||
|
"passwords.do.not.match": "گذرواژهها مطابقت ندارند",
|
||||||
|
"confirm.your.password": "تایید گذرواژه ",
|
||||||
|
"reset.password.failure.heading": "امکان بازتنظیم گذرواژه شما نیست.",
|
||||||
|
"reset.password.form.submission.error": "لطفاً پاسخهای خود را بررسی کرده و دوباره امتحان کنید.",
|
||||||
|
"reset.server.rate.limit.error": "تعداد درخواستها خیلی زیاد است.",
|
||||||
|
"reset.password.success.heading": "بازتنظیم گذرواژه تکمیل شد.",
|
||||||
|
"reset.password.success": "گذرواژه شما بازتنظیم شد. وارد حساب کاربری خود شوید",
|
||||||
|
"rate.limit.error": "به دلیل درخواستهای زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید.",
|
||||||
|
"start.learning": "آغاز یادگیری",
|
||||||
|
"with.site.name": "با {siteName}",
|
||||||
|
"your.career.turning.point": "نقطه عطف حرفه ای شما",
|
||||||
|
"is.here": "اینجاست.",
|
||||||
|
"welcome.to.platform": "خوش آمدید به {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "کامل",
|
||||||
|
"complete.your.profile.2": "پرونده کاربری شما",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Démarrer l'apprentissage",
|
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
|
||||||
"with.site.name": "avec {siteName}",
|
|
||||||
"complete.your.profile.1": "Terminé",
|
|
||||||
"complete.your.profile.2": "votre profil",
|
|
||||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||||
"logistration.sign.in": "Connectez-vous",
|
"logistration.sign.in": "Connectez-vous",
|
||||||
"logistration.register": "S'inscrire",
|
"logistration.register": "S'inscrire",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe.\n Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi\nl'adresse courriel correctement, ou vérifiez votre dossier de courriel indésirable. Si vous avez besoin d'aide supplémentaire, {supportLink}.",
|
|
||||||
"forgot.password.page.title": " Mot de passe oublié | {siteName}",
|
"forgot.password.page.title": " Mot de passe oublié | {siteName}",
|
||||||
"forgot.password.page.heading": "Réinitialiser le mot de passe",
|
"forgot.password.page.heading": "Réinitialiser le mot de passe",
|
||||||
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
|
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Vérifiez votre email",
|
"confirmation.message.title": "Vérifiez votre email",
|
||||||
"confirmation.support.link": "contacter le support technique",
|
"confirmation.support.link": "contacter le support technique",
|
||||||
"need.help.sign.in.text": "Besoin d'aide pour vous enregistrer?",
|
"need.help.sign.in.text": "Besoin d'aide pour vous enregistrer?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Connectez-vous",
|
"sign.in.text": "Connectez-vous",
|
||||||
"extend.field.errors": "{emailError} ci-dessous.",
|
"extend.field.errors": "{emailError} ci-dessous.",
|
||||||
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||||
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||||
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
||||||
"login.inactive.user.error": "Pour vous connecter, vous devez activer votre compte.{lineBreak}\n {lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel,\n vérifiez vos dossiers de spam ou {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré est incorrect. Vous avez {remainingAttempts} tentatives\n de connexion avant que votre compte soit temporairement verrouillé.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
||||||
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "S'inscrire | {siteName}",
|
"register.page.title": "S'inscrire | {siteName}",
|
||||||
"registration.fullname.label": "Nom complet",
|
"registration.fullname.label": "Nom complet",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Identifiants de l'établissement/du campus",
|
"register.institution.login.button": "Identifiants de l'établissement/du campus",
|
||||||
"register.institution.login.page.title": "Inscription avec les crédentiels d'institution ou de campus",
|
"register.institution.login.page.title": "Inscription avec les crédentiels d'institution ou de campus",
|
||||||
"empty.name.field.error": "Saisissez votre nom complet",
|
"empty.name.field.error": "Saisissez votre nom complet",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": " Conditions d'utilisation",
|
"terms.of.service": " Conditions d'utilisation",
|
||||||
"registration.username.suggestion.label": "Suggéré :",
|
"registration.username.suggestion.label": "Suggéré :",
|
||||||
"did.you.mean.alert.text": "Vouliez-vous dire",
|
"did.you.mean.alert.text": "Vouliez-vous dire",
|
||||||
"register.page.terms.of.service.and.honor.code": "En créant un compte, vous acceptez le {tosAndHonorCode} et vous reconnaissez que {platformName} et chaque\n membre peut traiter vos données personnelles conformément à la {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Connectez-vous",
|
"sign.in": "Connectez-vous",
|
||||||
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
||||||
"reset.password": "Réinitialiser le mot de passe",
|
"reset.password": "Réinitialiser le mot de passe",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Trop de demandes.",
|
"reset.server.rate.limit.error": "Trop de demandes.",
|
||||||
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
|
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
|
||||||
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
|
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
|
||||||
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps."
|
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps.",
|
||||||
|
"start.learning": "Démarrer l'apprentissage",
|
||||||
|
"with.site.name": "avec {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Terminé",
|
||||||
|
"complete.your.profile.2": "votre profil",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Démarrer l'apprentissage",
|
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
|
||||||
"with.site.name": "avec {siteName}",
|
|
||||||
"complete.your.profile.1": "Complet",
|
|
||||||
"complete.your.profile.2": "votre profil",
|
|
||||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||||
"logistration.sign.in": "Connexion",
|
"logistration.sign.in": "Connexion",
|
||||||
"logistration.register": "Inscription",
|
"logistration.register": "Inscription",
|
||||||
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName}?",
|
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos informations d'identification {providerName}?",
|
||||||
"enterprisetpa.login.button.text": "Affichez moi d'autres façons de se connecter ou de s'inscrire",
|
"enterprisetpa.login.button.text": "Affichez moi d'autres façons de se connecter ou de s'inscrire",
|
||||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Montrez-moi d'autres façons de me connecter",
|
"enterprisetpa.login.button.text.public.account.creation.disabled": "Montrez-moi d'autres façons de me connecter",
|
||||||
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
||||||
@@ -22,10 +18,9 @@
|
|||||||
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
|
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
|
||||||
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
|
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
|
||||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||||
"zendesk.supportTitle": "Prise en charge d'edX",
|
"zendesk.supportTitle": "Prise en charge d'edX",
|
||||||
"zendesk.selectTicketForm": "Veuillez choisir votre type de demande :",
|
"zendesk.selectTicketForm": "Veuillez choisir votre type de demande :",
|
||||||
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
|
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe. Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi l'adresse courriel correctement, ou vérifiez votre dossier de pourriels. Si vous avez besoin d'aide supplémentaire, contactez {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe.\n Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi\nl'adresse courriel correctement, ou vérifiez votre dossier de pourriels. Si vous avez besoin d'aide supplémentaire, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Mot de passe oublié | {siteName}",
|
"forgot.password.page.title": "Mot de passe oublié | {siteName}",
|
||||||
"forgot.password.page.heading": "Réinitialiser le mot de passe",
|
"forgot.password.page.heading": "Réinitialiser le mot de passe",
|
||||||
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
|
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Vérifiez votre courriel",
|
"confirmation.message.title": "Vérifiez votre courriel",
|
||||||
"confirmation.support.link": "contacter le support technique",
|
"confirmation.support.link": "contacter le support technique",
|
||||||
"need.help.sign.in.text": "Besoin d'aide pour vous connecter?",
|
"need.help.sign.in.text": "Besoin d'aide pour vous connecter?",
|
||||||
"additional.help.text": "Pour obtenir une aide supplémentaire, contactez l'assistance {platformName} à l'adresse",
|
"additional.help.text": "Pour obtenir de l'aide supplémentaire, contactez l'assistance {platformName} à l'adresse",
|
||||||
"sign.in.text": "Connexion",
|
"sign.in.text": "Connexion",
|
||||||
"extend.field.errors": "{emailError} ci-dessous.",
|
"extend.field.errors": "{emailError} ci-dessous.",
|
||||||
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||||
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||||
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
||||||
"login.inactive.user.error": "Pour vous connecter, vous devez activer votre compte.{lineBreak}\n {lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel,\n vérifiez vos dossiers de pourriels ou {supportLink}.",
|
"login.inactive.user.error": "Pour vous connecter, vous devez activer votre compte.{lineBreak}{lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel, vérifiez vos dossiers de pourriels ou {supportLink}.",
|
||||||
"allowed.domain.login.error": "En tant qu'utilisateur {allowedDomain}, vous devez vous connecter avec votre {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "En tant qu'utilisateur {allowedDomain}, vous devez vous connecter avec votre {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré sont incorrects. Vous avez {remainingAttempts} tentatives\n de connexion avant que votre compte soit temporairement verrouillé.",
|
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré sont incorrects. Vous avez {remainingAttempts} tentatives de connexion avant que votre compte soit temporairement verrouillé.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
||||||
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
||||||
@@ -62,13 +57,13 @@
|
|||||||
"login.password.label": "Mot de passe",
|
"login.password.label": "Mot de passe",
|
||||||
"sign.in.button": "Connexion",
|
"sign.in.button": "Connexion",
|
||||||
"forgot.password": "Mot de passe oublié",
|
"forgot.password": "Mot de passe oublié",
|
||||||
"institution.login.button": "Identifiants de l'établissement/du campus",
|
"institution.login.button": "Informations d'identification de l'établissement/du campus",
|
||||||
"institution.login.page.title": "Connectez vous avec les crédentiels d'institution ou de campus",
|
"institution.login.page.title": "Connectez vous avec les informations d'identification d'institution ou de campus",
|
||||||
"login.other.options.heading": "Ou se connecter avec :",
|
"login.other.options.heading": "Ou se connecter avec :",
|
||||||
"non.compliant.password.title": "Nous avons récemment modifié nos exigences en matière de mot de passe",
|
"non.compliant.password.title": "Nous avons récemment modifié nos exigences en matière de mot de passe",
|
||||||
"non.compliant.password.message": "Votre mot de passe actuel ne répond pas aux nouvelles exigences de sécurité. Nous venons d'envoyer un message de réinitialisation de mot de passe à l'adresse courriel associée à ce compte. Merci de nous aider à protéger vos données.",
|
"non.compliant.password.message": "Votre mot de passe actuel ne répond pas aux nouvelles exigences de sécurité. Nous venons d'envoyer un message de réinitialisation de mot de passe à l'adresse courriel associée à ce compte. Merci de nous aider à protéger vos données.",
|
||||||
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
|
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
|
||||||
"enterprise.login.btn.text": "Identifiants de la compagnie ou de l'école",
|
"enterprise.login.btn.text": "Informations d'identification de la compagnie ou de l'école",
|
||||||
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 3 caractères.",
|
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 3 caractères.",
|
||||||
"email.validation.message": "Entrez votre nom d'utilisateur ou votre adresse courriel",
|
"email.validation.message": "Entrez votre nom d'utilisateur ou votre adresse courriel",
|
||||||
"password.validation.message": "Les critères de mot de passe n'ont pas été remplis",
|
"password.validation.message": "Les critères de mot de passe n'ont pas été remplis",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommandations | {siteName}",
|
"recommendation.page.title": "Recommandations | {siteName}",
|
||||||
"recommendation.page.heading": "Nous avons quelques recommandations pour vous aider à démarrer.",
|
"recommendation.page.heading": "Nous avons quelques recommandations pour vous aider à démarrer.",
|
||||||
"recommendation.skip.button": "Ignorer pour l'instant",
|
"recommendation.skip.button": "Ignorer pour l'instant",
|
||||||
|
"recommendation.option.trending": "Tendances actuelles",
|
||||||
|
"recommendation.option.popular": "Le plus populaire",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommandé pour vous",
|
||||||
|
"recommendation.product-card.pill-text.course": "Cours",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Attestation professionnelle",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offert à titre émérite",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offert via Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Abonnement",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Ouvre un lien dans un nouvel onglet",
|
||||||
"register.page.title": "S'inscrire | {siteName}",
|
"register.page.title": "S'inscrire | {siteName}",
|
||||||
"registration.fullname.label": "Nom complet",
|
"registration.fullname.label": "Nom complet",
|
||||||
"registration.email.label": "Courriel",
|
"registration.email.label": "Courriel",
|
||||||
@@ -124,8 +129,9 @@
|
|||||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||||
"register.institution.login.button": "Identifiants de l'établissement/du campus",
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.page.title": "Inscription avec les crédentiels d'institution ou de campus",
|
"register.institution.login.button": "Informations d'identification de l'établissement/du campus",
|
||||||
|
"register.institution.login.page.title": "Inscription avec les informations d'identification d'institution ou de campus",
|
||||||
"empty.name.field.error": "Saisissez votre nom complet",
|
"empty.name.field.error": "Saisissez votre nom complet",
|
||||||
"empty.email.field.error": "Saisissez votre courriel",
|
"empty.email.field.error": "Saisissez votre courriel",
|
||||||
"empty.username.field.error": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
"empty.username.field.error": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||||
@@ -135,7 +141,7 @@
|
|||||||
"email.invalid.format.error": "Entrez une adresse de courriel valide",
|
"email.invalid.format.error": "Entrez une adresse de courriel valide",
|
||||||
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||||
"name.validation.message": "Entrez un nom valide",
|
"name.validation.message": "Entrez un nom valide",
|
||||||
"username.format.validation.message": "Les noms d'utilisateur ne peuvent contenir que des lettres (AZ, az), des chiffres (0-9), des traits de soulignement (_) et des traits d'union (-). Les noms d'utilisateur ne peuvent pas contenir d'espaces",
|
"username.format.validation.message": "Les noms d'utilisateur ne peuvent contenir que des lettres (A-Z, a-z), des chiffres (0-9), des traits de soulignement (_) et des traits d'union (-). Les noms d'utilisateur ne peuvent pas contenir d'espaces",
|
||||||
"registration.request.failure.header": "Nous n'avons pas pu créer votre compte.",
|
"registration.request.failure.header": "Nous n'avons pas pu créer votre compte.",
|
||||||
"registration.empty.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
"registration.empty.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
||||||
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Conditions générales du service",
|
"terms.of.service": "Conditions générales du service",
|
||||||
"registration.username.suggestion.label": "Suggéré :",
|
"registration.username.suggestion.label": "Suggéré :",
|
||||||
"did.you.mean.alert.text": "Vouliez-vous dire",
|
"did.you.mean.alert.text": "Vouliez-vous dire",
|
||||||
"register.page.terms.of.service.and.honor.code": "En créant un compte, vous acceptez le {tosAndHonorCode} et vous reconnaissez que {platformName} et chaque\n membre peut traiter vos données personnelles conformément à la {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "J'accepte le {tosAndHonorCode} {platformName}",
|
|
||||||
"register.page.terms.of.service": "J'accepte les {termsOfService} {platformName}",
|
|
||||||
"sign.in": "Connexion",
|
"sign.in": "Connexion",
|
||||||
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
||||||
"reset.password": "Réinitialiser le mot de passe",
|
"reset.password": "Réinitialiser le mot de passe",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Trop de demandes.",
|
"reset.server.rate.limit.error": "Trop de demandes.",
|
||||||
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
|
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
|
||||||
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
|
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
|
||||||
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps."
|
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps.",
|
||||||
|
"start.learning": "Commencez à apprendre",
|
||||||
|
"with.site.name": "avec {siteName}",
|
||||||
|
"your.career.turning.point": "Un tournant dans votre carrière",
|
||||||
|
"is.here": "est là.",
|
||||||
|
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complet",
|
||||||
|
"complete.your.profile.2": "votre profil",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "En créant un compte, vous acceptez le {tosAndHonorCode} et vous reconnaissez que {platformName} et chaque membre traitent vos données personnelles conformément au {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "J'accepte le {tosAndHonorCode} {platformName}",
|
||||||
|
"register.page.terms.of.service": "J'accepte les {termsOfService} {platformName}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||||
"with.site.name": "with {siteName}",
|
|
||||||
"complete.your.profile.1": "Complete",
|
|
||||||
"complete.your.profile.2": "your profile",
|
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||||
"logistration.sign.in": "Sign in",
|
"logistration.sign.in": "Sign in",
|
||||||
"logistration.register": "Register",
|
"logistration.register": "Register",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.heading": "Reset password",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Check your email",
|
"confirmation.message.title": "Check your email",
|
||||||
"confirmation.support.link": "contact technical support",
|
"confirmation.support.link": "contact technical support",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"need.help.sign.in.text": "Need help signing in?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Sign in",
|
"sign.in.text": "Sign in",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"extend.field.errors": "{emailError} below.",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.heading": "Invalid password reset link",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Full name",
|
"registration.fullname.label": "Full name",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "For account activation and important updates",
|
"help.text.email": "For account activation and important updates",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"create.account.for.free.button": "Create an account for free",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.other.options.heading": "Or register with:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"register.institution.login.button": "Institution/campus credentials",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"empty.name.field.error": "Enter your full name",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "Terms of Service",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "Suggested:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "Did you mean",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Sign in",
|
"sign.in": "Sign in",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"reset.password.page.title": "Reset Password | {siteName}",
|
||||||
"reset.password": "Reset password",
|
"reset.password": "Reset password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"reset.server.rate.limit.error": "Too many requests.",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"reset.password.success.heading": "Password reset complete.",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||||
|
"start.learning": "Start learning",
|
||||||
|
"with.site.name": "with {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complete",
|
||||||
|
"complete.your.profile.2": "your profile",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||||
"with.site.name": "with {siteName}",
|
|
||||||
"complete.your.profile.1": "Complete",
|
|
||||||
"complete.your.profile.2": "your profile",
|
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||||
"logistration.sign.in": "Sign in",
|
"logistration.sign.in": "Sign in",
|
||||||
"logistration.register": "Register",
|
"logistration.register": "Register",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.heading": "Reset password",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Check your email",
|
"confirmation.message.title": "Check your email",
|
||||||
"confirmation.support.link": "contact technical support",
|
"confirmation.support.link": "contact technical support",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"need.help.sign.in.text": "Need help signing in?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Sign in",
|
"sign.in.text": "Sign in",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"extend.field.errors": "{emailError} below.",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.heading": "Invalid password reset link",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Full name",
|
"registration.fullname.label": "Full name",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "For account activation and important updates",
|
"help.text.email": "For account activation and important updates",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"create.account.for.free.button": "Create an account for free",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.other.options.heading": "Or register with:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"register.institution.login.button": "Institution/campus credentials",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"empty.name.field.error": "Enter your full name",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "Terms of Service",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "Suggested:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "Did you mean",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Sign in",
|
"sign.in": "Sign in",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"reset.password.page.title": "Reset Password | {siteName}",
|
||||||
"reset.password": "Reset password",
|
"reset.password": "Reset password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"reset.server.rate.limit.error": "Too many requests.",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"reset.password.success.heading": "Password reset complete.",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||||
|
"start.learning": "Start learning",
|
||||||
|
"with.site.name": "with {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complete",
|
||||||
|
"complete.your.profile.2": "your profile",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Inizia a imparare",
|
"error.notfound.message": "La pagina che stai cercando non è disponibile o si è verificato un errore nell'URL. Controlla l'URL e riprova. ",
|
||||||
"with.site.name": "con {siteName}",
|
|
||||||
"complete.your.profile.1": "Completata",
|
|
||||||
"complete.your.profile.2": "Il tuo profilo",
|
|
||||||
"welcome.to.platform": "Benvenuto in {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Scegli il tuo istituto dall'elenco sottostante",
|
"institution.login.page.sub.heading": "Scegli il tuo istituto dall'elenco sottostante",
|
||||||
"logistration.sign.in": "Accedi",
|
"logistration.sign.in": "Accedi",
|
||||||
"logistration.register": "Registrazione",
|
"logistration.register": "Registrazione",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Completa la creazione del tuo account",
|
"registration.using.tpa.form.heading": "Completa la creazione del tuo account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "La pagina che stai cercando non è disponibile o si è verificato un errore nell'URL. Controlla l'URL e riprova. ",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "Abbiamo inviato un'email a {email} con le istruzioni per reimpostare la password. Se non ricevi un messaggio di reimpostazione della password dopo 1 minuto, verifica di aver inserito l'indirizzo e-mail corretto o controlla la cartella spam. Se hai bisogno di ulteriore assistenza, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Dimenticato la password | {siteName}",
|
"forgot.password.page.title": "Dimenticato la password | {siteName}",
|
||||||
"forgot.password.page.heading": "Resetta la password",
|
"forgot.password.page.heading": "Resetta la password",
|
||||||
"forgot.password.page.instructions": "Inserisci il tuo indirizzo e-mail qui sotto e ti invieremo un'e-mail con le istruzioni su come reimpostare la tua password.",
|
"forgot.password.page.instructions": "Inserisci il tuo indirizzo e-mail qui sotto e ti invieremo un'e-mail con le istruzioni su come reimpostare la tua password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Controlla la tua casella di posta",
|
"confirmation.message.title": "Controlla la tua casella di posta",
|
||||||
"confirmation.support.link": "contatta il supporto tecnico",
|
"confirmation.support.link": "contatta il supporto tecnico",
|
||||||
"need.help.sign.in.text": "Hai bisogno di aiuto per l'accesso? ",
|
"need.help.sign.in.text": "Hai bisogno di aiuto per l'accesso? ",
|
||||||
"additional.help.text": "Per ulteriore assistenza, contattare l'assistenza {platformName} all'indirizzo",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Accedi",
|
"sign.in.text": "Accedi",
|
||||||
"extend.field.errors": "{emailError} di seguito.",
|
"extend.field.errors": "{emailError} di seguito.",
|
||||||
"invalid.token.heading": "Link di ripristino della password non valido",
|
"invalid.token.heading": "Link di ripristino della password non valido",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
"token.validation.internal.sever.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||||
"internal.server.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
"internal.server.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||||
"account.activation.error.message": "Si è verificato un errore, seleziona {supportLink} per risolvere il problema. ",
|
"account.activation.error.message": "Si è verificato un errore, seleziona {supportLink} per risolvere il problema. ",
|
||||||
"login.inactive.user.error": "Per accedere, devi attivare il tuo account.{lineBreak} {lineBreak}Abbiamo appena inviato un link di attivazione a {email}. Se non ricevi un'email, controlla la cartella della posta indesiderata oppure seleziona {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "Il nome utente, l'e-mail o la password che hai inserito non sono corretti. Hai {remainingAttempts} più tentativi di accesso prima che il tuo account venga temporaneamente bloccato.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "Se hai dimenticato la password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "Se hai dimenticato la password, {resetLink}",
|
||||||
"account.locked.out.message.2": "Per sicurezza, puoi {resetLink} prima di riprovare.",
|
"account.locked.out.message.2": "Per sicurezza, puoi {resetLink} prima di riprovare.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "Il nome utente, l'e-mail o la password inseriti non sono corretti. Riprova o {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "Il nome utente, l'e-mail o la password inseriti non sono corretti. Riprova o {resetLink}.",
|
||||||
@@ -97,7 +92,7 @@
|
|||||||
"password.security.close.button": "Chiudi",
|
"password.security.close.button": "Chiudi",
|
||||||
"password.security.redirect.to.reset.password.button": "Ripristina la tua password",
|
"password.security.redirect.to.reset.password.button": "Ripristina la tua password",
|
||||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
"progressive.profiling.page.title": "Benvenuto | {siteName}",
|
||||||
"progressive.profiling.page.heading": "Alcune domande per te ci aiuteranno a diventare più intelligenti.",
|
"progressive.profiling.page.heading": "Alcune domande per te ci aiuteranno a diventare più intelligenti.",
|
||||||
"optional.fields.information.link": "Ulteriori informazioni su come utilizziamo queste informazioni.",
|
"optional.fields.information.link": "Ulteriori informazioni su come utilizziamo queste informazioni.",
|
||||||
"optional.fields.submit.button": "Invia",
|
"optional.fields.submit.button": "Invia",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Registrazione | {siteName}",
|
"register.page.title": "Registrazione | {siteName}",
|
||||||
"registration.fullname.label": "Nome e Cognome",
|
"registration.fullname.label": "Nome e Cognome",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "Per l'attivazione dell'account e aggiornamenti importanti",
|
"help.text.email": "Per l'attivazione dell'account e aggiornamenti importanti",
|
||||||
"create.account.for.free.button": "Crea un account gratis",
|
"create.account.for.free.button": "Crea un account gratis",
|
||||||
"registration.other.options.heading": "Oppure registrati con:",
|
"registration.other.options.heading": "Oppure registrati con:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Credenziali dell'istituto/campus",
|
"register.institution.login.button": "Credenziali dell'istituto/campus",
|
||||||
"register.institution.login.page.title": "Registrati con le credenziali dell'istituzione/campus",
|
"register.institution.login.page.title": "Registrati con le credenziali dell'istituzione/campus",
|
||||||
"empty.name.field.error": "Inserisci il tuo nome e cognome",
|
"empty.name.field.error": "Inserisci il tuo nome e cognome",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Termini di Servizio",
|
"terms.of.service": "Termini di Servizio",
|
||||||
"registration.username.suggestion.label": "Suggerito:",
|
"registration.username.suggestion.label": "Suggerito:",
|
||||||
"did.you.mean.alert.text": "Intendevi",
|
"did.you.mean.alert.text": "Intendevi",
|
||||||
"register.page.terms.of.service.and.honor.code": "Creando un account, accetti il {tosAndHonorCode} e riconosci che {platformName} e ciascun Membro trattano i tuoi dati personali in conformità con l' {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "Accetto {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "Accetto {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Accedi",
|
"sign.in": "Accedi",
|
||||||
"reset.password.page.title": "Ripristina password | {siteName}",
|
"reset.password.page.title": "Ripristina password | {siteName}",
|
||||||
"reset.password": "Resetta la password",
|
"reset.password": "Resetta la password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Troppe richieste.",
|
"reset.server.rate.limit.error": "Troppe richieste.",
|
||||||
"reset.password.success.heading": "Ripristino della password completato.",
|
"reset.password.success.heading": "Ripristino della password completato.",
|
||||||
"reset.password.success": "La tua password è stata resettata. Accedi al tuo account.",
|
"reset.password.success": "La tua password è stata resettata. Accedi al tuo account.",
|
||||||
"rate.limit.error": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi."
|
"rate.limit.error": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi.",
|
||||||
|
"start.learning": "Inizia a imparare",
|
||||||
|
"with.site.name": "con {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Benvenuto in {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Completata",
|
||||||
|
"complete.your.profile.2": "Il tuo profilo",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||||
"with.site.name": "with {siteName}",
|
|
||||||
"complete.your.profile.1": "Complete",
|
|
||||||
"complete.your.profile.2": "your profile",
|
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||||
"logistration.sign.in": "Sign in",
|
"logistration.sign.in": "Sign in",
|
||||||
"logistration.register": "Register",
|
"logistration.register": "Register",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.heading": "Reset password",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Check your email",
|
"confirmation.message.title": "Check your email",
|
||||||
"confirmation.support.link": "contact technical support",
|
"confirmation.support.link": "contact technical support",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"need.help.sign.in.text": "Need help signing in?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Sign in",
|
"sign.in.text": "Sign in",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"extend.field.errors": "{emailError} below.",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.heading": "Invalid password reset link",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Full name",
|
"registration.fullname.label": "Full name",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "For account activation and important updates",
|
"help.text.email": "For account activation and important updates",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"create.account.for.free.button": "Create an account for free",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.other.options.heading": "Or register with:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"register.institution.login.button": "Institution/campus credentials",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"empty.name.field.error": "Enter your full name",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "Terms of Service",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "Suggested:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "Did you mean",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Sign in",
|
"sign.in": "Sign in",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"reset.password.page.title": "Reset Password | {siteName}",
|
||||||
"reset.password": "Reset password",
|
"reset.password": "Reset password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"reset.server.rate.limit.error": "Too many requests.",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"reset.password.success.heading": "Password reset complete.",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||||
|
"start.learning": "Start learning",
|
||||||
|
"with.site.name": "with {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complete",
|
||||||
|
"complete.your.profile.2": "your profile",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Começar a aprender",
|
"error.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
|
||||||
"with.site.name": "com {siteName}",
|
|
||||||
"complete.your.profile.1": "Concluído",
|
|
||||||
"complete.your.profile.2": "o seu perfil",
|
|
||||||
"welcome.to.platform": "Bem vindo a {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Escolha a sua instituição a partir da lista abaixo",
|
"institution.login.page.sub.heading": "Escolha a sua instituição a partir da lista abaixo",
|
||||||
"logistration.sign.in": "Iniciar sessão",
|
"logistration.sign.in": "Iniciar sessão",
|
||||||
"logistration.register": "Registe-se",
|
"logistration.register": "Registe-se",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Acabe de criar a sua conta",
|
"registration.using.tpa.form.heading": "Acabe de criar a sua conta",
|
||||||
"zendesk.supportTitle": "Apoio edX",
|
"zendesk.supportTitle": "Apoio edX",
|
||||||
"zendesk.selectTicketForm": "Por favor, escolha o seu tipo de pedido:",
|
"zendesk.selectTicketForm": "Por favor, escolha o seu tipo de pedido:",
|
||||||
"error.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "Enviámos um email para {email} com instruções para redefinir a sua palavra-passe.\n Se não receber uma mensagem de redefinição de palavra-passe após 1 minuto, verifique se introduziu\n o endereço de correio electrónico correto, ou verifique a sua pasta de spam. Se precisar de mais assistência, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Esqueceu a Senha | {siteName}",
|
"forgot.password.page.title": "Esqueceu a Senha | {siteName}",
|
||||||
"forgot.password.page.heading": "Redefinir palavra-passe",
|
"forgot.password.page.heading": "Redefinir palavra-passe",
|
||||||
"forgot.password.page.instructions": "Por favor introduza o seu endereço de email abaixo e enviar-lhe-emos um email com instruções sobre como redefinir a sua palavra-passe.",
|
"forgot.password.page.instructions": "Por favor introduza o seu endereço de email abaixo e enviar-lhe-emos um email com instruções sobre como redefinir a sua palavra-passe.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Verifique o seu email",
|
"confirmation.message.title": "Verifique o seu email",
|
||||||
"confirmation.support.link": "contacto o suporte técnico",
|
"confirmation.support.link": "contacto o suporte técnico",
|
||||||
"need.help.sign.in.text": "Precisa de ajuda para entrar?",
|
"need.help.sign.in.text": "Precisa de ajuda para entrar?",
|
||||||
"additional.help.text": "Para ajuda adicional, contacte o apoio {platformName} em",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Iniciar sessão",
|
"sign.in.text": "Iniciar sessão",
|
||||||
"extend.field.errors": "{emailError} abaixo.",
|
"extend.field.errors": "{emailError} abaixo.",
|
||||||
"invalid.token.heading": "Link para Redefinir Palavra-passe inválido",
|
"invalid.token.heading": "Link para Redefinir Palavra-passe inválido",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
"token.validation.internal.sever.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||||
"internal.server.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
"internal.server.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||||
"account.activation.error.message": "Alguma coisa correu mal, siga {supportLink} para resolver esta questão.",
|
"account.activation.error.message": "Alguma coisa correu mal, siga {supportLink} para resolver esta questão.",
|
||||||
"login.inactive.user.error": "Para iniciar sessão, precisa ativar a sua conta. {lineBreak}\n {lineBreak} Acabámos de enviar um link de ativação para {email}. Se não receber um e-mail,\n verifique as suas pastas de spam ou {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "Como utilizador do {allowedDomain}, deve iniciar sessão com o seu {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "Como utilizador do {allowedDomain}, deve iniciar sessão com o seu {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "O nome de utilizador, email ou palavra-passe que introduziu está incorreto. Tem {remainingAttempts} mais tentativas\n de login antes da sua conta ser temporariamente bloqueada.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "Se esqueceu a sua palavra-passe, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "Se esqueceu a sua palavra-passe, {resetLink}",
|
||||||
"account.locked.out.message.2": "Por precaução, pode {resetLink} antes de tentar novamente.",
|
"account.locked.out.message.2": "Por precaução, pode {resetLink} antes de tentar novamente.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "O nome de utilizador, email ou senha que introduziu está incorreto. Por favor, tente novamente ou {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "O nome de utilizador, email ou senha que introduziu está incorreto. Por favor, tente novamente ou {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recomendações | {siteName}",
|
"recommendation.page.title": "Recomendações | {siteName}",
|
||||||
"recommendation.page.heading": "Temos algumas recomendações para o ajudar a começar.",
|
"recommendation.page.heading": "Temos algumas recomendações para o ajudar a começar.",
|
||||||
"recommendation.skip.button": "Saltar por enquanto",
|
"recommendation.skip.button": "Saltar por enquanto",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Registar | {siteName}",
|
"register.page.title": "Registar | {siteName}",
|
||||||
"registration.fullname.label": "Nome completo",
|
"registration.fullname.label": "Nome completo",
|
||||||
"registration.email.label": "E-mail",
|
"registration.email.label": "E-mail",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "Para ativação de contas e atualizações importantes",
|
"help.text.email": "Para ativação de contas e atualizações importantes",
|
||||||
"create.account.for.free.button": "Criar uma conta gratuitamente",
|
"create.account.for.free.button": "Criar uma conta gratuitamente",
|
||||||
"registration.other.options.heading": "Ou registe-se com:",
|
"registration.other.options.heading": "Ou registe-se com:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Credenciais da instituição/campus",
|
"register.institution.login.button": "Credenciais da instituição/campus",
|
||||||
"register.institution.login.page.title": "Registo com credenciais da instituição/campus",
|
"register.institution.login.page.title": "Registo com credenciais da instituição/campus",
|
||||||
"empty.name.field.error": "Insira o seu nome completo",
|
"empty.name.field.error": "Insira o seu nome completo",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Termos do Serviço",
|
"terms.of.service": "Termos do Serviço",
|
||||||
"registration.username.suggestion.label": "Sugerido:",
|
"registration.username.suggestion.label": "Sugerido:",
|
||||||
"did.you.mean.alert.text": "Quis dizer",
|
"did.you.mean.alert.text": "Quis dizer",
|
||||||
"register.page.terms.of.service.and.honor.code": "Ao criar uma conta, concorda com o {tosAndHonorCode} e reconhece que {platformName} e cada\n Membro processa os seus dados pessoais em conformidade com a {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "Concordo com a {tosAndHonorCode} {platformName}",
|
|
||||||
"register.page.terms.of.service": "Concordo com os {termsOfService} {platformName}",
|
|
||||||
"sign.in": "Iniciar sessão",
|
"sign.in": "Iniciar sessão",
|
||||||
"reset.password.page.title": "Redefinir Palavra-passe | {siteName}",
|
"reset.password.page.title": "Redefinir Palavra-passe | {siteName}",
|
||||||
"reset.password": "Redefinir palavra-passe",
|
"reset.password": "Redefinir palavra-passe",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Demasiados pedidos.",
|
"reset.server.rate.limit.error": "Demasiados pedidos.",
|
||||||
"reset.password.success.heading": "Redefinição de palavra-passe concluída",
|
"reset.password.success.heading": "Redefinição de palavra-passe concluída",
|
||||||
"reset.password.success": "A sua palavra-passe foi redefinida. Inicie sessão na sua conta.",
|
"reset.password.success": "A sua palavra-passe foi redefinida. Inicie sessão na sua conta.",
|
||||||
"rate.limit.error": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo."
|
"rate.limit.error": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo.",
|
||||||
|
"start.learning": "Começar a aprender",
|
||||||
|
"with.site.name": "com {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Bem vindo a {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Concluído",
|
||||||
|
"complete.your.profile.2": "o seu perfil",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||||
"with.site.name": "with {siteName}",
|
|
||||||
"complete.your.profile.1": "Complete",
|
|
||||||
"complete.your.profile.2": "your profile",
|
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||||
"logistration.sign.in": "Sign in",
|
"logistration.sign.in": "Sign in",
|
||||||
"logistration.register": "Register",
|
"logistration.register": "Register",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.heading": "Reset password",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Check your email",
|
"confirmation.message.title": "Check your email",
|
||||||
"confirmation.support.link": "contact technical support",
|
"confirmation.support.link": "contact technical support",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"need.help.sign.in.text": "Need help signing in?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Sign in",
|
"sign.in.text": "Sign in",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"extend.field.errors": "{emailError} below.",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.heading": "Invalid password reset link",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Full name",
|
"registration.fullname.label": "Full name",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "For account activation and important updates",
|
"help.text.email": "For account activation and important updates",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"create.account.for.free.button": "Create an account for free",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.other.options.heading": "Or register with:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"register.institution.login.button": "Institution/campus credentials",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"empty.name.field.error": "Enter your full name",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "Terms of Service",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "Suggested:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "Did you mean",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Sign in",
|
"sign.in": "Sign in",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"reset.password.page.title": "Reset Password | {siteName}",
|
||||||
"reset.password": "Reset password",
|
"reset.password": "Reset password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"reset.server.rate.limit.error": "Too many requests.",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"reset.password.success.heading": "Password reset complete.",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||||
|
"start.learning": "Start learning",
|
||||||
|
"with.site.name": "with {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complete",
|
||||||
|
"complete.your.profile.2": "your profile",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||||
"with.site.name": "with {siteName}",
|
|
||||||
"complete.your.profile.1": "Complete",
|
|
||||||
"complete.your.profile.2": "your profile",
|
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||||
"logistration.sign.in": "Sign in",
|
"logistration.sign.in": "Sign in",
|
||||||
"logistration.register": "Register",
|
"logistration.register": "Register",
|
||||||
@@ -24,8 +20,7 @@
|
|||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.heading": "Reset password",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||||
@@ -40,7 +35,7 @@
|
|||||||
"confirmation.message.title": "Check your email",
|
"confirmation.message.title": "Check your email",
|
||||||
"confirmation.support.link": "contact technical support",
|
"confirmation.support.link": "contact technical support",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"need.help.sign.in.text": "Need help signing in?",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"sign.in.text": "Sign in",
|
"sign.in.text": "Sign in",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"extend.field.errors": "{emailError} below.",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.heading": "Invalid password reset link",
|
||||||
@@ -51,9 +46,9 @@
|
|||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||||
@@ -111,6 +106,16 @@
|
|||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "Recommendations | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "Skip for now",
|
||||||
|
"recommendation.option.trending": "Trending Now",
|
||||||
|
"recommendation.option.popular": "Most Popular",
|
||||||
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
|
"recommendation.product-card.pill-text.course": "Course",
|
||||||
|
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||||
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"register.page.title": "Register | {siteName}",
|
"register.page.title": "Register | {siteName}",
|
||||||
"registration.fullname.label": "Full name",
|
"registration.fullname.label": "Full name",
|
||||||
"registration.email.label": "Email",
|
"registration.email.label": "Email",
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
"help.text.email": "For account activation and important updates",
|
"help.text.email": "For account activation and important updates",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"create.account.for.free.button": "Create an account for free",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.other.options.heading": "Or register with:",
|
||||||
|
"create.account.cta.button": "{label}",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"register.institution.login.button": "Institution/campus credentials",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"empty.name.field.error": "Enter your full name",
|
||||||
@@ -148,9 +154,6 @@
|
|||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "Terms of Service",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "Suggested:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "Did you mean",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
|
||||||
"sign.in": "Sign in",
|
"sign.in": "Sign in",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"reset.password.page.title": "Reset Password | {siteName}",
|
||||||
"reset.password": "Reset password",
|
"reset.password": "Reset password",
|
||||||
@@ -164,5 +167,15 @@
|
|||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"reset.server.rate.limit.error": "Too many requests.",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"reset.password.success.heading": "Password reset complete.",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||||
|
"start.learning": "Start learning",
|
||||||
|
"with.site.name": "with {siteName}",
|
||||||
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||||
|
"complete.your.profile.1": "Complete",
|
||||||
|
"complete.your.profile.2": "your profile",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,168 +1,181 @@
|
|||||||
{
|
{
|
||||||
"start.learning": "Start learning",
|
"error.notfound.message": "您访问的地址不存在或有误。请检查URL后重新尝试访问。",
|
||||||
"with.site.name": "with {siteName}",
|
"institution.login.page.sub.heading": "从下面的列表中选择您的机构",
|
||||||
"complete.your.profile.1": "Complete",
|
"logistration.sign.in": "登录",
|
||||||
"complete.your.profile.2": "your profile",
|
"logistration.register": "注册",
|
||||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
"enterprisetpa.title.heading": "您要使用 {providerName} 登录吗?",
|
||||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
"enterprisetpa.login.button.text": "为我显示其他登录或注册方式",
|
||||||
"logistration.sign.in": "Sign in",
|
"enterprisetpa.login.button.text.public.account.creation.disabled": "显示其他登录方式",
|
||||||
"logistration.register": "Register",
|
"sso.sign.in.with": "使用 {providerName} 登录",
|
||||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
"sso.create.account.using": "使用 {providerName} 创建帐户",
|
||||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
"show.password": "显示密码",
|
||||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
"hide.password": "隐藏密码",
|
||||||
"sso.sign.in.with": "Sign in with {providerName}",
|
"one.letter": "1 个字母",
|
||||||
"sso.create.account.using": "Create account using {providerName}",
|
"one.number": "1个数字",
|
||||||
"show.password": "Show password",
|
"eight.characters": "8个字符",
|
||||||
"hide.password": "Hide password",
|
"password.sr.only.helping.text": "密码必须包含至少 8 个字符、至少一个字母和至少一个数字",
|
||||||
"one.letter": "1 letter",
|
"tpa.alert.heading": "快完成了!",
|
||||||
"one.number": "1 number",
|
"login.third.party.auth.account.not.linked": "您已成功登录 {currentProvider},但您的 {currentProvider} 帐户没有关联的 {platformName} 帐户。要链接您的帐户,请立即使用您的 {platformName} 密码登录。",
|
||||||
"eight.characters": "8 characters",
|
"register.third.party.auth.account.not.linked": "您已成功登录 {currentProvider}!在您开始学习 {platformName} 之前,我们只需要更多信息。",
|
||||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
"registration.using.tpa.form.heading": "完成创建您的帐户",
|
||||||
"tpa.alert.heading": "Almost done!",
|
|
||||||
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
|
|
||||||
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
|
|
||||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
|
||||||
"zendesk.supportTitle": "edX Support",
|
"zendesk.supportTitle": "edX Support",
|
||||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
"forgot.password.page.title": "忘记密码 | {siteName}",
|
||||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
"forgot.password.page.heading": "重置密码",
|
||||||
"forgot.password.page.heading": "Reset password",
|
"forgot.password.page.instructions": "请在下面输入您的电子邮件地址,我们将向您发送一封电子邮件,其中包含有关如何重置密码的说明。",
|
||||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
"forgot.password.page.invalid.email.message": "输入一个有效的电子邮件地址",
|
||||||
"forgot.password.page.invalid.email.message": "Enter a valid email address",
|
"forgot.password.page.email.field.label": "邮箱",
|
||||||
"forgot.password.page.email.field.label": "Email",
|
"forgot.password.page.submit.button": "提交",
|
||||||
"forgot.password.page.submit.button": "Submit",
|
"forgot.password.error.alert.title.": "我们无法联系到您。",
|
||||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
"forgot.password.error.message.title": "发生了一个错误。",
|
||||||
"forgot.password.error.message.title": "An error occurred.",
|
"forgot.password.request.in.progress.message": "你的前一个请求正在处理中,请稍后再尝试。",
|
||||||
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
|
"forgot.password.empty.email.field.error": "输入你的电子邮箱",
|
||||||
"forgot.password.empty.email.field.error": "Enter your email",
|
"forgot.password.email.help.text": "您用于注册 {platformName} 的电子邮件地址",
|
||||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
"confirmation.message.title": "查收您的邮件",
|
||||||
"confirmation.message.title": "Check your email",
|
"confirmation.support.link": "联系技术支持",
|
||||||
"confirmation.support.link": "contact technical support",
|
"need.help.sign.in.text": "需要帮助登录?",
|
||||||
"need.help.sign.in.text": "Need help signing in?",
|
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
"sign.in.text": "登录",
|
||||||
"sign.in.text": "Sign in",
|
"extend.field.errors": "{emailError} 如下。",
|
||||||
"extend.field.errors": "{emailError} below.",
|
"invalid.token.heading": "密码重置链接无效",
|
||||||
"invalid.token.heading": "Invalid password reset link",
|
"invalid.token.error.message": "此密码重置链接无效。它可能已经被使用过。在下面输入您的电子邮件以接收新链接。",
|
||||||
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
|
"token.validation.rate.limit.error.heading": "请求过多",
|
||||||
"token.validation.rate.limit.error.heading": "Too many requests",
|
"token.validation.rate.limit.error": "由于请求过多而发生错误。请稍后重试。",
|
||||||
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
"token.validation.internal.sever.error.heading": "验证失败",
|
||||||
"token.validation.internal.sever.error.heading": "Token validation failure",
|
"token.validation.internal.sever.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"internal.server.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"account.activation.error.message": "出了点问题,请联系{supportLink}解决这个问题。",
|
||||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
"allowed.domain.login.error": "作为 {allowedDomain} 用户,您必须使用 {allowedDomain} {tpaLink} 登录。",
|
||||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
"login.incorrect.credentials.error.attempts.text.2": "如果您忘记了密码,{resetLink}",
|
||||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
"account.locked.out.message.2": "为了安全起见,您可以先{resetLink}再试一次。",
|
||||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
"login.incorrect.credentials.error.with.reset.link": "您输入的用户名、电子邮件或密码不正确。请重试或 {resetLink}。",
|
||||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
"login.page.title": "登入 | {siteName}",
|
||||||
"login.page.title": "Login | {siteName}",
|
"login.user.identity.label": "用户名或电子邮件",
|
||||||
"login.user.identity.label": "Username or email",
|
"login.password.label": "密码",
|
||||||
"login.password.label": "Password",
|
"sign.in.button": "登录",
|
||||||
"sign.in.button": "Sign in",
|
"forgot.password": "忘记密码",
|
||||||
"forgot.password": "Forgot password",
|
"institution.login.button": "机构/校园凭证",
|
||||||
"institution.login.button": "Institution/campus credentials",
|
"institution.login.page.title": "使用机构/校园凭据登录",
|
||||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
"login.other.options.heading": "或登录:",
|
||||||
"login.other.options.heading": "Or sign in with:",
|
"non.compliant.password.title": "我们最近更改了密码要求",
|
||||||
"non.compliant.password.title": "We recently changed our password requirements",
|
"non.compliant.password.message": "您当前的密码不符合新的安全要求。我们刚刚向与此帐户关联的电子邮件地址发送了密码重置邮件。感谢您帮助我们保护您的数据安全。",
|
||||||
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
|
"account.locked.out.message.1": "为了保护您的帐户,它已被暂时锁定。请在 30 分钟后重试。",
|
||||||
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
"enterprise.login.btn.text": "单位或学校证书",
|
||||||
"enterprise.login.btn.text": "Company or school credentials",
|
"username.or.email.format.validation.less.chars.message": "用户名或电子邮件必须至少包含 3 个字符。",
|
||||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
"email.validation.message": "输入您的用户名或电子邮件",
|
||||||
"email.validation.message": "Enter your username or email",
|
"password.validation.message": "未满足密码条件",
|
||||||
"password.validation.message": "Password criteria has not been met",
|
"account.activation.success.message.title": "成功!您已激活您的帐户。",
|
||||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
"account.activation.success.message": "您现在将收到我们发送的与您注册的课程相关的电子邮件更新和提醒。登录以继续。",
|
||||||
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
|
"account.activation.info.message": "本账号已经被激活。",
|
||||||
"account.activation.info.message": "This account has already been activated.",
|
"account.activation.error.message.title": "您的帐户无法激活",
|
||||||
"account.activation.error.message.title": "Your account could not be activated",
|
"account.activation.support.link": "请联系技术支持",
|
||||||
"account.activation.support.link": "contact support",
|
"account.confirmation.success.message.title": "成功!您已确认您的电子邮件。",
|
||||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
"account.confirmation.success.message": "登录并继续。",
|
||||||
"account.confirmation.success.message": "Sign in to continue.",
|
"account.confirmation.info.message": "此电子邮件已被确认。",
|
||||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
"account.confirmation.error.message.title": "无法确认您的电子邮件",
|
||||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
"tpa.account.link": "{provider} 帐户",
|
||||||
"tpa.account.link": "{provider} account",
|
"internal.server.error.message": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"login.rate.limit.reached.message": "失败次数超过限制,请稍后再试!",
|
||||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
"login.failure.header.title": "登录失败。",
|
||||||
"login.failure.header.title": "We couldn't sign you in.",
|
"contact.support.link": "联系 {platformName} 支持",
|
||||||
"contact.support.link": "contact {platformName} support",
|
"login.incorrect.credentials.error": "您输入的用户名、电子邮件或密码不正确。请再试一次。",
|
||||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
"login.form.invalid.error.message": "请填写以下字段。",
|
||||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
"login.incorrect.credentials.error.reset.link.text": "重置你的密码",
|
||||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
"login.incorrect.credentials.error.before.account.blocked.text": "点击此处来重置它。",
|
||||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
"password.security.nudge.title": "密码安全",
|
||||||
"password.security.nudge.title": "Password security",
|
"password.security.block.title": "需要更改密码",
|
||||||
"password.security.block.title": "Password change required",
|
"password.security.nudge.body": "系统检测到您的密码存在漏洞。我们建议您更改它,以便您的帐户保持安全。",
|
||||||
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
|
"password.security.block.body": "系统检测到您的密码存在漏洞。更改您的密码,以确保您的帐户安全。",
|
||||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
"password.security.close.button": "关闭",
|
||||||
"password.security.close.button": "Close",
|
"password.security.redirect.to.reset.password.button": "重置你的密码",
|
||||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
|
||||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
"progressive.profiling.page.title": "欢迎来的 | {siteName}",
|
||||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
"progressive.profiling.page.heading": "你的问题将会帮助我们服务做的更好。",
|
||||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
"optional.fields.information.link": "详细了解我们如何使用这些信息。",
|
||||||
"optional.fields.submit.button": "Submit",
|
"optional.fields.submit.button": "提交",
|
||||||
"optional.fields.skip.button": "Skip for now",
|
"optional.fields.skip.button": "暂时跳过",
|
||||||
"optional.fields.next.button": "Next",
|
"optional.fields.next.button": "下一节",
|
||||||
"continue.to.platform": "Continue to {platformName}",
|
"continue.to.platform": "继续{platformName}",
|
||||||
"modal.title": "Thanks for letting us know.",
|
"modal.title": "感谢您指点。",
|
||||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
"modal.description": "如果您改变主意,可以随时在设置中完成您的个人资料。",
|
||||||
"welcome.page.error.heading": "We couldn't update your profile",
|
"welcome.page.error.heading": "我们无法更新您的个人资料",
|
||||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
"welcome.page.error.message": "发生错误。您可以随时在设置中完成您的个人资料。",
|
||||||
"recommendation.page.title": "Recommendations | {siteName}",
|
"recommendation.page.title": "建议 | {siteName}",
|
||||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
"recommendation.page.heading": "我们有一些建议可以帮助您入门。",
|
||||||
"recommendation.skip.button": "Skip for now",
|
"recommendation.skip.button": "暂时跳过",
|
||||||
"register.page.title": "Register | {siteName}",
|
"recommendation.option.trending": "Trending Now",
|
||||||
"registration.fullname.label": "Full name",
|
"recommendation.option.popular": "Most Popular",
|
||||||
"registration.email.label": "Email",
|
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||||
"registration.username.label": "Public username",
|
"recommendation.product-card.pill-text.course": "课程",
|
||||||
"registration.password.label": "Password",
|
"recommendation.product-card.pill-text.professional-certificate": "专业证书",
|
||||||
"registration.country.label": "Country/Region",
|
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||||
"help.text.username.1": "The name that will identify you in your courses.",
|
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||||
"help.text.username.2": "This can not be changed later.",
|
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||||
"help.text.email": "For account activation and important updates",
|
"register.page.title": "注册 | {siteName}",
|
||||||
"create.account.for.free.button": "Create an account for free",
|
"registration.fullname.label": "全名",
|
||||||
"registration.other.options.heading": "Or register with:",
|
"registration.email.label": "邮箱",
|
||||||
"register.institution.login.button": "Institution/campus credentials",
|
"registration.username.label": "公开用户名",
|
||||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
"registration.password.label": "密码",
|
||||||
"empty.name.field.error": "Enter your full name",
|
"registration.country.label": "国家/地区",
|
||||||
"empty.email.field.error": "Enter your email",
|
"registration.opt.in.label": "我同意 {siteName} 可以向我发送课程相关推广信息。",
|
||||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
"help.text.name": "您获得的任何证书都将使用此名称。",
|
||||||
"empty.password.field.error": "Password criteria has not been met",
|
"help.text.username.1": "此名称将会用于您在课程中的身份识别。",
|
||||||
"empty.country.field.error": "Select your country or region of residence",
|
"help.text.username.2": "过后无法更改。",
|
||||||
"email.do.not.match": "The email addresses do not match.",
|
"help.text.email": "用于帐户激活和重要更新",
|
||||||
"email.invalid.format.error": "Enter a valid email address",
|
"create.account.for.free.button": "免费创建一个帐户",
|
||||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
"registration.other.options.heading": "或注册:",
|
||||||
"name.validation.message": "Enter a valid name",
|
"create.account.cta.button": "{label}",
|
||||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
"register.institution.login.button": "机构/院系验证",
|
||||||
"registration.request.failure.header": "We couldn't create your account.",
|
"register.institution.login.page.title": "使用机构/院系账户注册",
|
||||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
"empty.name.field.error": "输入您的全名",
|
||||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
"empty.email.field.error": "输入你的电子邮箱",
|
||||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
"empty.username.field.error": "用户名必须介于 2 到 30 个字符之间",
|
||||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
"empty.password.field.error": "未满足密码条件",
|
||||||
|
"empty.country.field.error": "选择您居住的国家或地区",
|
||||||
|
"email.do.not.match": "邮箱不一致。",
|
||||||
|
"email.invalid.format.error": "输入一个有效的电子邮件地址",
|
||||||
|
"username.validation.message": "用户名必须介于 2 到 30 个字符之间",
|
||||||
|
"name.validation.message": "输入有效名称",
|
||||||
|
"username.format.validation.message": "用户名只能包含字母(AZ、az)、数字(0-9)、下划线(_)和连字符(-)。用户名不能包含空格",
|
||||||
|
"registration.request.failure.header": "无法创建账号。",
|
||||||
|
"registration.empty.form.submission.error": "请检查您的回复并重试。",
|
||||||
|
"registration.request.server.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||||
|
"registration.rate.limit.error": "注册尝试失败次数过多。稍后再试。",
|
||||||
|
"registration.tpa.session.expired": "使用{provider}注册已超时。",
|
||||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
"terms.of.service.and.honor.code": "服务条款和诚信准则",
|
||||||
"privacy.policy": "Privacy Policy",
|
"privacy.policy": "隐私政策",
|
||||||
"honor.code": "Honor Code",
|
"honor.code": "规则",
|
||||||
"terms.of.service": "Terms of Service",
|
"terms.of.service": "服务条款",
|
||||||
"registration.username.suggestion.label": "Suggested:",
|
"registration.username.suggestion.label": "建议:",
|
||||||
"did.you.mean.alert.text": "Did you mean",
|
"did.you.mean.alert.text": "你的意思是",
|
||||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
"sign.in": "登录",
|
||||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
"reset.password.page.title": "重设密码 | {siteName}",
|
||||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
"reset.password": "重置密码",
|
||||||
"sign.in": "Sign in",
|
"reset.password.page.instructions": "输入并确认你的新密码。",
|
||||||
"reset.password.page.title": "Reset Password | {siteName}",
|
"new.password.label": "新密码",
|
||||||
"reset.password": "Reset password",
|
"confirm.password.label": "确认密码",
|
||||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
"passwords.do.not.match": "密码不匹配",
|
||||||
"new.password.label": "New password",
|
"confirm.your.password": "确认你的密码",
|
||||||
"confirm.password.label": "Confirm password",
|
"reset.password.failure.heading": "我们无法重置您的密码。",
|
||||||
"passwords.do.not.match": "Passwords do not match",
|
"reset.password.form.submission.error": "请检查您的回复并重试。",
|
||||||
"confirm.your.password": "Confirm your password",
|
"reset.server.rate.limit.error": "请求过多。",
|
||||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
"reset.password.success.heading": "密码重置完成。",
|
||||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
"reset.password.success": "您的密码已重置。登录到您的帐户。",
|
||||||
"reset.server.rate.limit.error": "Too many requests.",
|
"rate.limit.error": "由于请求过多而发生错误。请稍后重试。",
|
||||||
"reset.password.success.heading": "Password reset complete.",
|
"start.learning": "开始学习",
|
||||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
"with.site.name": "{siteName}",
|
||||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
"your.career.turning.point": "Your career turning point",
|
||||||
|
"is.here": "is here.",
|
||||||
|
"welcome.to.platform": "欢迎来到{siteName},{username}!",
|
||||||
|
"complete.your.profile.1": "完成",
|
||||||
|
"complete.your.profile.2": "个人资料",
|
||||||
|
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||||
|
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||||
|
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@import "~@edx/brand/paragon/fonts";
|
@import "~@edx/brand/paragon/fonts";
|
||||||
@import "~@edx/brand/paragon/variables";
|
@import "~@edx/brand/paragon/variables";
|
||||||
@import "~@edx/paragon/scss/core/core";
|
@import "~@openedx/paragon/scss/core/core";
|
||||||
@import "~@edx/brand/paragon/overrides";
|
@import "~@edx/brand/paragon/overrides";
|
||||||
|
|
||||||
@import "sass/style";
|
@import "sass/style";
|
||||||
|
|||||||
@@ -2,36 +2,37 @@ import React from 'react';
|
|||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Alert } from '@edx/paragon';
|
import { Alert } from '@openedx/paragon';
|
||||||
import { CheckCircle, Error } from '@edx/paragon/icons';
|
import { CheckCircle, Error } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { ACCOUNT_ACTIVATION_MESSAGE } from './data/constants';
|
import { ACCOUNT_ACTIVATION_MESSAGE } from './data/constants';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const AccountActivationMessage = (props) => {
|
const AccountActivationMessage = ({ messageType }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { messageType } = props;
|
|
||||||
|
if (!messageType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType;
|
const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType;
|
||||||
|
const activationOrConfirmation = getConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation';
|
||||||
const activationOrVerification = getConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation';
|
|
||||||
|
|
||||||
let activationMessage;
|
|
||||||
let heading;
|
|
||||||
|
|
||||||
const iconMapping = {
|
const iconMapping = {
|
||||||
[ACCOUNT_ACTIVATION_MESSAGE.SUCCESS]: CheckCircle,
|
[ACCOUNT_ACTIVATION_MESSAGE.SUCCESS]: CheckCircle,
|
||||||
[ACCOUNT_ACTIVATION_MESSAGE.ERROR]: Error,
|
[ACCOUNT_ACTIVATION_MESSAGE.ERROR]: Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let activationMessage;
|
||||||
|
let heading;
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: {
|
case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: {
|
||||||
heading = formatMessage(messages[`account.${activationOrVerification}.success.message.title`]);
|
heading = formatMessage(messages[`account.${activationOrConfirmation}.success.message.title`]);
|
||||||
activationMessage = <span>{formatMessage(messages[`account.${activationOrVerification}.success.message`])}</span>;
|
activationMessage = <span>{formatMessage(messages[`account.${activationOrConfirmation}.success.message`])}</span>;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ACCOUNT_ACTIVATION_MESSAGE.INFO: {
|
case ACCOUNT_ACTIVATION_MESSAGE.INFO: {
|
||||||
activationMessage = formatMessage(messages[`account.${activationOrVerification}.info.message`]);
|
activationMessage = formatMessage(messages[`account.${activationOrConfirmation}.info.message`]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
||||||
@@ -41,7 +42,7 @@ const AccountActivationMessage = (props) => {
|
|||||||
</Alert.Link>
|
</Alert.Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
heading = formatMessage(messages[`account.${activationOrVerification}.error.message.title`]);
|
heading = formatMessage(messages[`account.${activationOrConfirmation}.error.message.title`]);
|
||||||
activationMessage = (
|
activationMessage = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="account.activation.error.message"
|
id="account.activation.error.message"
|
||||||
@@ -59,7 +60,7 @@ const AccountActivationMessage = (props) => {
|
|||||||
return activationMessage ? (
|
return activationMessage ? (
|
||||||
<Alert
|
<Alert
|
||||||
id="account-activation-message"
|
id="account-activation-message"
|
||||||
className="mb-4"
|
className="mb-5"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
icon={iconMapping[messageType]}
|
icon={iconMapping[messageType]}
|
||||||
>
|
>
|
||||||
@@ -70,7 +71,11 @@ const AccountActivationMessage = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
AccountActivationMessage.propTypes = {
|
AccountActivationMessage.propTypes = {
|
||||||
messageType: PropTypes.string.isRequired,
|
messageType: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountActivationMessage.defaultProps = {
|
||||||
|
messageType: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccountActivationMessage;
|
export default AccountActivationMessage;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
ActionRow, ModalDialog, useToggle,
|
ActionRow, ModalDialog, useToggle,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link, Redirect } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
|
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
|
||||||
@@ -29,10 +29,14 @@ const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const [isOpen, open, close] = useToggle(true, handlers);
|
const [isOpen, open, close] = useToggle(true, handlers);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (redirectToResetPasswordPage) {
|
||||||
|
navigate(updatePathWithQueryParams(RESET_PAGE));
|
||||||
|
}
|
||||||
|
}, [redirectToResetPasswordPage, navigate]);
|
||||||
|
|
||||||
if (redirectToResetPasswordPage) {
|
|
||||||
return <Redirect to={updatePathWithQueryParams(RESET_PAGE)} />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
title="Password security"
|
title="Password security"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { getAuthService } from '@edx/frontend-platform/auth';
|
import { getAuthService } from '@edx/frontend-platform/auth';
|
||||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { Alert, Hyperlink } from '@edx/paragon';
|
import { Alert, Hyperlink } from '@openedx/paragon';
|
||||||
import { Error } from '@edx/paragon/icons';
|
import { Error } from '@openedx/paragon/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import ChangePasswordPrompt from './ChangePasswordPrompt';
|
import ChangePasswordPrompt from './ChangePasswordPrompt';
|
||||||
@@ -23,22 +23,35 @@ import {
|
|||||||
TPA_AUTHENTICATION_FAILURE,
|
TPA_AUTHENTICATION_FAILURE,
|
||||||
} from './data/constants';
|
} from './data/constants';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
import { windowScrollTo } from '../data/utils';
|
||||||
|
|
||||||
const LoginFailureMessage = (props) => {
|
const LoginFailureMessage = (props) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { context, errorCode } = props.loginError;
|
|
||||||
|
|
||||||
const authService = getAuthService();
|
const authService = getAuthService();
|
||||||
let errorList;
|
const {
|
||||||
|
context,
|
||||||
|
errorCode,
|
||||||
|
errorCount, // This is used to trigger the useEffect, facilitating the scrolling to the top.
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||||
|
}, [errorCode, errorCount]);
|
||||||
|
|
||||||
|
if (!errorCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let resetLink = (
|
let resetLink = (
|
||||||
<Hyperlink destination="reset" isInline>
|
<Hyperlink destination="reset" isInline>
|
||||||
{formatMessage(messages['login.incorrect.credentials.error.reset.link.text'])}
|
{formatMessage(messages['login.incorrect.credentials.error.reset.link.text'])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let errorMessage;
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case NON_COMPLIANT_PASSWORD_EXCEPTION: {
|
case NON_COMPLIANT_PASSWORD_EXCEPTION: {
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<>
|
<>
|
||||||
<strong>{formatMessage(messages['non.compliant.password.title'])}</strong>
|
<strong>{formatMessage(messages['non.compliant.password.title'])}</strong>
|
||||||
<p>{formatMessage(messages['non.compliant.password.message'])}</p>
|
<p>{formatMessage(messages['non.compliant.password.message'])}</p>
|
||||||
@@ -47,7 +60,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FORBIDDEN_REQUEST:
|
case FORBIDDEN_REQUEST:
|
||||||
errorList = <p>{formatMessage(messages['login.rate.limit.reached.message'])}</p>;
|
errorMessage = <p>{formatMessage(messages['login.rate.limit.reached.message'])}</p>;
|
||||||
break;
|
break;
|
||||||
case INACTIVE_USER: {
|
case INACTIVE_USER: {
|
||||||
const supportLink = (
|
const supportLink = (
|
||||||
@@ -55,7 +68,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
{formatMessage(messages['contact.support.link'], { platformName: context.platformName })}
|
{formatMessage(messages['contact.support.link'], { platformName: context.platformName })}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="login.inactive.user.error"
|
id="login.inactive.user.error"
|
||||||
@@ -64,7 +77,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
check your spam folders or {supportLink}."
|
check your spam folders or {supportLink}."
|
||||||
values={{
|
values={{
|
||||||
lineBreak: <br />,
|
lineBreak: <br />,
|
||||||
email: <strong className="data-hj-suppress">{props.loginError.email}</strong>,
|
email: <strong className="data-hj-suppress">{context.email}</strong>,
|
||||||
supportLink,
|
supportLink,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -79,7 +92,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
{formatMessage(messages['tpa.account.link'], { provider: context.provider })}
|
{formatMessage(messages['tpa.account.link'], { provider: context.provider })}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="allowed.domain.login.error"
|
id="allowed.domain.login.error"
|
||||||
@@ -92,7 +105,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case INVALID_FORM:
|
case INVALID_FORM:
|
||||||
errorList = <p>{formatMessage(messages['login.form.invalid.error.message'])}</p>;
|
errorMessage = <p>{formatMessage(messages['login.form.invalid.error.message'])}</p>;
|
||||||
break;
|
break;
|
||||||
case FAILED_LOGIN_ATTEMPT: {
|
case FAILED_LOGIN_ATTEMPT: {
|
||||||
resetLink = (
|
resetLink = (
|
||||||
@@ -100,7 +113,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
{formatMessage(messages['login.incorrect.credentials.error.before.account.blocked.text'])}
|
{formatMessage(messages['login.incorrect.credentials.error.before.account.blocked.text'])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
);
|
);
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -124,7 +137,7 @@ const LoginFailureMessage = (props) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ACCOUNT_LOCKED_OUT: {
|
case ACCOUNT_LOCKED_OUT: {
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<>
|
<>
|
||||||
<p>{formatMessage(messages['account.locked.out.message.1'])}</p>
|
<p>{formatMessage(messages['account.locked.out.message.1'])}</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -141,9 +154,9 @@ const LoginFailureMessage = (props) => {
|
|||||||
}
|
}
|
||||||
case INCORRECT_EMAIL_PASSWORD:
|
case INCORRECT_EMAIL_PASSWORD:
|
||||||
if (context.failureCount <= 1) {
|
if (context.failureCount <= 1) {
|
||||||
errorList = <p>{formatMessage(messages['login.incorrect.credentials.error'])}</p>;
|
errorMessage = <p>{formatMessage(messages['login.incorrect.credentials.error'])}</p>;
|
||||||
} else if (context.failureCount === 2) {
|
} else if (context.failureCount === 2) {
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="login.incorrect.credentials.error.with.reset.link"
|
id="login.incorrect.credentials.error.with.reset.link"
|
||||||
@@ -161,60 +174,56 @@ const LoginFailureMessage = (props) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ChangePasswordPrompt
|
<ChangePasswordPrompt
|
||||||
redirectUrl={props.loginError.redirectUrl}
|
redirectUrl={context.redirectUrl}
|
||||||
variant="nudge"
|
variant="nudge"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case REQUIRE_PASSWORD_CHANGE:
|
case REQUIRE_PASSWORD_CHANGE:
|
||||||
return <ChangePasswordPrompt />;
|
return <ChangePasswordPrompt />;
|
||||||
case TPA_AUTHENTICATION_FAILURE:
|
case TPA_AUTHENTICATION_FAILURE:
|
||||||
errorList = (
|
errorMessage = (
|
||||||
<p>{formatMessage(messages['login.tpa.authentication.failure'], {
|
<p>
|
||||||
platform_name: getConfig().SITE_NAME,
|
{formatMessage(messages['login.tpa.authentication.failure'], {
|
||||||
lineBreak: <br />,
|
platform_name: getConfig().SITE_NAME,
|
||||||
errorMessage: context.errorMessage,
|
lineBreak: <br />,
|
||||||
})}
|
errorMessage: context.errorMessage,
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case INTERNAL_SERVER_ERROR:
|
case INTERNAL_SERVER_ERROR:
|
||||||
default:
|
default:
|
||||||
errorList = <p>{formatMessage(messages['internal.server.error.message'])}</p>;
|
errorMessage = <p>{formatMessage(messages['internal.server.error.message'])}</p>;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert id="login-failure-alert" className="mb-5" variant="danger" icon={Error}>
|
<Alert id="login-failure-alert" className="mb-5" variant="danger" icon={Error}>
|
||||||
<Alert.Heading>{formatMessage(messages['login.failure.header.title'])}</Alert.Heading>
|
<Alert.Heading>{formatMessage(messages['login.failure.header.title'])}</Alert.Heading>
|
||||||
{ errorList }
|
{ errorMessage }
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
LoginFailureMessage.defaultProps = {
|
LoginFailureMessage.defaultProps = {
|
||||||
loginError: {
|
context: {},
|
||||||
redirectUrl: null,
|
|
||||||
errorCode: null,
|
|
||||||
errorMessage: null,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoginFailureMessage.propTypes = {
|
LoginFailureMessage.propTypes = {
|
||||||
loginError: PropTypes.shape({
|
context: PropTypes.shape({
|
||||||
context: PropTypes.shape({
|
supportLink: PropTypes.string,
|
||||||
supportLink: PropTypes.string,
|
platformName: PropTypes.string,
|
||||||
platformName: PropTypes.string,
|
tpaHint: PropTypes.string,
|
||||||
tpaHint: PropTypes.string,
|
provider: PropTypes.string,
|
||||||
provider: PropTypes.string,
|
allowedDomain: PropTypes.string,
|
||||||
allowedDomain: PropTypes.string,
|
remainingAttempts: PropTypes.number,
|
||||||
remainingAttempts: PropTypes.number,
|
failureCount: PropTypes.number,
|
||||||
failureCount: PropTypes.number,
|
errorMessage: PropTypes.string,
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
}),
|
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
errorCode: PropTypes.string,
|
|
||||||
redirectUrl: PropTypes.string,
|
redirectUrl: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
|
errorCode: PropTypes.string.isRequired,
|
||||||
|
errorCount: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoginFailureMessage;
|
export default LoginFailureMessage;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Form, Hyperlink, Icon, StatefulButton,
|
Form, StatefulButton,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { Institution } from '@edx/paragon/icons';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import Skeleton from 'react-loading-skeleton';
|
import Skeleton from 'react-loading-skeleton';
|
||||||
@@ -15,350 +14,340 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import AccountActivationMessage from './AccountActivationMessage';
|
import AccountActivationMessage from './AccountActivationMessage';
|
||||||
import {
|
import {
|
||||||
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
|
backupLoginFormBegin,
|
||||||
|
dismissPasswordResetBanner,
|
||||||
|
loginRequest,
|
||||||
} from './data/actions';
|
} from './data/actions';
|
||||||
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
||||||
import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
|
|
||||||
import LoginFailureMessage from './LoginFailure';
|
import LoginFailureMessage from './LoginFailure';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import {
|
import {
|
||||||
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration,
|
FormGroup,
|
||||||
RenderInstitutionButton, SocialAuthProviders, ThirdPartyAuthAlert,
|
InstitutionLogistration,
|
||||||
|
PasswordField,
|
||||||
|
RedirectLogistration,
|
||||||
|
ThirdPartyAuthAlert,
|
||||||
} from '../common-components';
|
} from '../common-components';
|
||||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||||
|
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
||||||
import {
|
import {
|
||||||
DEFAULT_STATE, ENTERPRISE_LOGIN_URL, PENDING_STATE, RESET_PAGE,
|
DEFAULT_STATE, PENDING_STATE, RESET_PAGE,
|
||||||
} from '../data/constants';
|
} from '../data/constants';
|
||||||
import {
|
import {
|
||||||
getActivationStatus,
|
getActivationStatus,
|
||||||
getAllPossibleQueryParams,
|
getAllPossibleQueryParams,
|
||||||
getTpaHint,
|
getTpaHint,
|
||||||
getTpaProvider,
|
getTpaProvider,
|
||||||
setSurveyCookie,
|
|
||||||
updatePathWithQueryParams,
|
updatePathWithQueryParams,
|
||||||
windowScrollTo,
|
|
||||||
} from '../data/utils';
|
} from '../data/utils';
|
||||||
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
||||||
|
|
||||||
class LoginPage extends React.Component {
|
const LoginPage = (props) => {
|
||||||
constructor(props, context) {
|
const {
|
||||||
super(props, context);
|
backedUpFormData,
|
||||||
this.state = {
|
loginErrorCode,
|
||||||
password: this.props.loginFormData.password,
|
loginErrorContext,
|
||||||
emailOrUsername: this.props.loginFormData.emailOrUsername,
|
loginResult,
|
||||||
errors: {
|
shouldBackupState,
|
||||||
emailOrUsername: this.props.loginFormData.errors.emailOrUsername,
|
thirdPartyAuthContext: {
|
||||||
password: this.props.loginFormData.errors.password,
|
providers,
|
||||||
},
|
currentProvider,
|
||||||
isSubmitted: false,
|
secondaryProviders,
|
||||||
};
|
finishAuthUrl,
|
||||||
this.queryParams = getAllPossibleQueryParams();
|
platformName,
|
||||||
this.tpaHint = getTpaHint();
|
errorMessage: thirdPartyErrorMessage,
|
||||||
}
|
},
|
||||||
|
thirdPartyAuthApiStatus,
|
||||||
|
institutionLogin,
|
||||||
|
showResetPasswordSuccessBanner,
|
||||||
|
submitState,
|
||||||
|
// Actions
|
||||||
|
backupFormState,
|
||||||
|
handleInstitutionLogin,
|
||||||
|
getTPADataFromBackend,
|
||||||
|
} = props;
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const activationMsgType = getActivationStatus();
|
||||||
|
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
||||||
|
|
||||||
componentDidMount() {
|
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
|
||||||
|
const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} });
|
||||||
|
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
||||||
|
const tpaHint = getTpaHint();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
sendPageEvent('login_and_registration', 'login');
|
sendPageEvent('login_and_registration', 'login');
|
||||||
const payload = { ...this.queryParams };
|
}, []);
|
||||||
|
|
||||||
if (this.tpaHint) {
|
useEffect(() => {
|
||||||
payload.tpa_hint = this.tpaHint;
|
const payload = { ...queryParams };
|
||||||
|
if (tpaHint) {
|
||||||
|
payload.tpa_hint = tpaHint;
|
||||||
}
|
}
|
||||||
this.props.getThirdPartyAuthContext(payload);
|
getTPADataFromBackend(payload);
|
||||||
this.props.loginRequestReset();
|
}, [getTPADataFromBackend, queryParams, tpaHint]);
|
||||||
}
|
/**
|
||||||
|
* Backup the login form in redux when login page is toggled.
|
||||||
shouldComponentUpdate(nextProps) {
|
*/
|
||||||
if (nextProps.loginFormData && this.props.loginFormData !== nextProps.loginFormData) {
|
useEffect(() => {
|
||||||
// Ensuring browser's autofill user credentials get filled and their state persists in the redux store.
|
if (shouldBackupState) {
|
||||||
const nextState = {
|
backupFormState({
|
||||||
emailOrUsername: nextProps.loginFormData.emailOrUsername || this.state.emailOrUsername,
|
formFields: { ...formFields },
|
||||||
password: nextProps.loginFormData.password || this.state.password,
|
errors: { ...errors },
|
||||||
};
|
|
||||||
this.setState({
|
|
||||||
...nextProps.loginFormData,
|
|
||||||
...nextState,
|
|
||||||
});
|
});
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
}, [shouldBackupState, formFields, errors, backupFormState]);
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
useEffect(() => {
|
||||||
if (this.props.resetPassword) {
|
if (loginErrorCode) {
|
||||||
this.props.loginRemovePasswordResetBanner();
|
setErrorCode(prevState => ({
|
||||||
|
type: loginErrorCode,
|
||||||
|
count: prevState.count + 1,
|
||||||
|
context: { ...loginErrorContext },
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}, [loginErrorCode, loginErrorContext]);
|
||||||
|
|
||||||
getEnterPriseLoginURL() {
|
useEffect(() => {
|
||||||
return getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
if (thirdPartyErrorMessage) {
|
||||||
}
|
setErrorCode((prevState) => ({
|
||||||
|
type: TPA_AUTHENTICATION_FAILURE,
|
||||||
handleSubmit = (e) => {
|
count: prevState.count + 1,
|
||||||
e.preventDefault();
|
context: {
|
||||||
if (this.props.resetPassword) {
|
errorMessage: thirdPartyErrorMessage,
|
||||||
this.props.loginRemovePasswordResetBanner();
|
|
||||||
}
|
|
||||||
this.setState({ isSubmitted: true });
|
|
||||||
const { emailOrUsername, password } = this.state;
|
|
||||||
const emailValidationError = this.validateEmail(emailOrUsername);
|
|
||||||
const passwordValidationError = this.validatePassword(password);
|
|
||||||
|
|
||||||
if (emailValidationError !== '' || passwordValidationError !== '') {
|
|
||||||
this.props.setLoginFormData({
|
|
||||||
errors: {
|
|
||||||
emailOrUsername: emailValidationError,
|
|
||||||
password: passwordValidationError,
|
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
this.props.loginRequestFailure({
|
}
|
||||||
errorCode: INVALID_FORM,
|
}, [thirdPartyErrorMessage]);
|
||||||
});
|
|
||||||
|
const validateFormFields = (payload) => {
|
||||||
|
const { emailOrUsername, password } = payload;
|
||||||
|
const fieldErrors = { ...errors };
|
||||||
|
|
||||||
|
if (emailOrUsername === '') {
|
||||||
|
fieldErrors.emailOrUsername = formatMessage(messages['email.validation.message']);
|
||||||
|
} else if (emailOrUsername.length < 3) {
|
||||||
|
fieldErrors.emailOrUsername = formatMessage(messages['username.or.email.format.validation.less.chars.message']);
|
||||||
|
}
|
||||||
|
if (password === '') {
|
||||||
|
fieldErrors.password = formatMessage(messages['password.validation.message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...fieldErrors };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (showResetPasswordSuccessBanner) {
|
||||||
|
props.dismissPasswordResetBanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = { ...formFields };
|
||||||
|
const validationErrors = validateFormFields(formData);
|
||||||
|
if (validationErrors.emailOrUsername || validationErrors.password) {
|
||||||
|
setErrors({ ...validationErrors });
|
||||||
|
setErrorCode(prevState => ({ type: INVALID_FORM, count: prevState.count + 1, context: {} }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add query params to the payload
|
||||||
const payload = {
|
const payload = {
|
||||||
email_or_username: emailOrUsername, password, ...this.queryParams,
|
email_or_username: formData.emailOrUsername,
|
||||||
|
password: formData.password,
|
||||||
|
...queryParams,
|
||||||
};
|
};
|
||||||
this.props.loginRequest(payload);
|
props.loginRequest(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOnFocus = (e) => {
|
const handleOnChange = (event) => {
|
||||||
const { errors } = this.state;
|
const { name, value } = event.target;
|
||||||
errors[e.target.name] = '';
|
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
||||||
this.props.setLoginFormData({
|
|
||||||
errors,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOnBlur = (e) => {
|
const handleOnFocus = (event) => {
|
||||||
const payload = {
|
const { name } = event.target;
|
||||||
[e.target.name]: e.target.value,
|
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||||
};
|
|
||||||
this.props.setLoginFormData(payload);
|
|
||||||
};
|
};
|
||||||
|
const trackForgotPasswordLinkClick = () => {
|
||||||
handleForgotPasswordLinkClickEvent = () => {
|
|
||||||
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
||||||
};
|
};
|
||||||
|
|
||||||
validateEmail(email) {
|
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
||||||
const { errors } = this.state;
|
|
||||||
|
|
||||||
if (email === '') {
|
if (tpaHint) {
|
||||||
errors.emailOrUsername = this.props.intl.formatMessage(messages['email.validation.message']);
|
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
||||||
} else if (email.length < 3) {
|
return <Skeleton height={36} />;
|
||||||
errors.emailOrUsername = this.props.intl.formatMessage(messages['username.or.email.format.validation.less.chars.message']);
|
}
|
||||||
} else {
|
|
||||||
errors.emailOrUsername = '';
|
if (skipHintedLogin) {
|
||||||
|
window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
return <EnterpriseSSO provider={provider} />;
|
||||||
}
|
}
|
||||||
return errors.emailOrUsername;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validatePassword(password) {
|
if (institutionLogin) {
|
||||||
const { errors } = this.state;
|
|
||||||
errors.password = password.length > 0 ? '' : this.props.intl.formatMessage(messages['password.validation.message']);
|
|
||||||
|
|
||||||
return errors.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl) {
|
|
||||||
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
|
|
||||||
const isSocialAuthActive = !!providers.length && !currentProvider;
|
|
||||||
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<InstitutionLogistration
|
||||||
{(isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive))
|
secondaryProviders={secondaryProviders}
|
||||||
&& (
|
headingTitle={formatMessage(messages['institution.login.page.title'])}
|
||||||
<div className="mt-4 mb-3 h4">
|
/>
|
||||||
{intl.formatMessage(messages['login.other.options.heading'])}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(!isEnterpriseLoginDisabled && isSocialAuthActive) && (
|
|
||||||
<Hyperlink className="btn btn-link btn-sm text-body p-0 mb-4" destination={this.getEnterPriseLoginURL()}>
|
|
||||||
<Icon src={Institution} className="institute-icon" />
|
|
||||||
{intl.formatMessage(messages['enterprise.login.btn.text'])}
|
|
||||||
</Hyperlink>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{thirdPartyAuthApiStatus === PENDING_STATE ? (
|
|
||||||
<Skeleton className="tpa-skeleton mb-3" height={30} count={2} />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{(isEnterpriseLoginDisabled && isInstitutionAuthActive) && (
|
|
||||||
<RenderInstitutionButton
|
|
||||||
onSubmitHandler={this.props.handleInstitutionLogin}
|
|
||||||
buttonTitle={intl.formatMessage(messages['institution.login.button'])}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isSocialAuthActive && (
|
|
||||||
<div className="row m-0">
|
|
||||||
<SocialAuthProviders socialAuthProviders={providers} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
renderForm(
|
<>
|
||||||
currentProvider,
|
<Helmet>
|
||||||
providers,
|
<title>{formatMessage(messages['login.page.title'], { siteName: getConfig().SITE_NAME })}</title>
|
||||||
secondaryProviders,
|
</Helmet>
|
||||||
thirdPartyAuthContext,
|
<RedirectLogistration
|
||||||
thirdPartyAuthApiStatus,
|
success={loginResult.success}
|
||||||
submitState,
|
redirectUrl={loginResult.redirectUrl}
|
||||||
intl,
|
finishAuthUrl={finishAuthUrl}
|
||||||
) {
|
/>
|
||||||
const activationMsgType = getActivationStatus();
|
<div className="mw-xs mt-3 mb-2">
|
||||||
if (this.props.institutionLogin) {
|
<LoginFailureMessage
|
||||||
return (
|
errorCode={errorCode.type}
|
||||||
<InstitutionLogistration
|
errorCount={errorCode.count}
|
||||||
secondaryProviders={thirdPartyAuthContext.secondaryProviders}
|
context={errorCode.context}
|
||||||
headingTitle={intl.formatMessage(messages['institution.login.page.title'])}
|
|
||||||
/>
|
/>
|
||||||
);
|
<ThirdPartyAuthAlert
|
||||||
}
|
currentProvider={currentProvider}
|
||||||
const tpaAuthenticationError = {};
|
platformName={platformName}
|
||||||
if (thirdPartyAuthContext.errorMessage) {
|
|
||||||
tpaAuthenticationError.context = {
|
|
||||||
errorMessage: thirdPartyAuthContext.errorMessage,
|
|
||||||
};
|
|
||||||
tpaAuthenticationError.errorCode = TPA_AUTHENTICATION_FAILURE;
|
|
||||||
}
|
|
||||||
if (this.props.loginResult.success) {
|
|
||||||
setSurveyCookie('login');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>{intl.formatMessage(messages['login.page.title'],
|
|
||||||
{ siteName: getConfig().SITE_NAME })}
|
|
||||||
</title>
|
|
||||||
</Helmet>
|
|
||||||
<RedirectLogistration
|
|
||||||
success={this.props.loginResult.success}
|
|
||||||
redirectUrl={this.props.loginResult.redirectUrl}
|
|
||||||
finishAuthUrl={thirdPartyAuthContext.finishAuthUrl}
|
|
||||||
/>
|
/>
|
||||||
<div className="mw-xs mt-3">
|
<AccountActivationMessage
|
||||||
<ThirdPartyAuthAlert
|
messageType={activationMsgType}
|
||||||
currentProvider={thirdPartyAuthContext.currentProvider}
|
/>
|
||||||
platformName={thirdPartyAuthContext.platformName}
|
{showResetPasswordSuccessBanner && <ResetPasswordSuccess />}
|
||||||
|
<Form id="sign-in-form" name="sign-in-form">
|
||||||
|
<FormGroup
|
||||||
|
name="emailOrUsername"
|
||||||
|
value={formFields.emailOrUsername}
|
||||||
|
autoComplete="on"
|
||||||
|
handleChange={handleOnChange}
|
||||||
|
handleFocus={handleOnFocus}
|
||||||
|
errorMessage={errors.emailOrUsername}
|
||||||
|
floatingLabel={formatMessage(messages['login.user.identity.label'])}
|
||||||
/>
|
/>
|
||||||
{this.props.loginError ? <LoginFailureMessage loginError={this.props.loginError} /> : null}
|
<PasswordField
|
||||||
{thirdPartyAuthContext.errorMessage ? <LoginFailureMessage loginError={tpaAuthenticationError} /> : null}
|
name="password"
|
||||||
{submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null}
|
value={formFields.password}
|
||||||
{activationMsgType && <AccountActivationMessage messageType={activationMsgType} />}
|
autoComplete="off"
|
||||||
{this.props.resetPassword && !this.props.loginError ? <ResetPasswordSuccess /> : null}
|
showScreenReaderText={false}
|
||||||
<Form name="sign-in-form" id="sign-in-form">
|
showRequirements={false}
|
||||||
<FormGroup
|
handleChange={handleOnChange}
|
||||||
name="emailOrUsername"
|
handleFocus={handleOnFocus}
|
||||||
value={this.state.emailOrUsername}
|
errorMessage={errors.password}
|
||||||
autoComplete="on"
|
floatingLabel={formatMessage(messages['login.password.label'])}
|
||||||
handleChange={(e) => this.setState({ emailOrUsername: e.target.value, isSubmitted: false })}
|
/>
|
||||||
handleFocus={this.handleOnFocus}
|
<StatefulButton
|
||||||
handleBlur={this.handleOnBlur}
|
name="sign-in"
|
||||||
errorMessage={this.state.errors.emailOrUsername}
|
id="sign-in"
|
||||||
floatingLabel={intl.formatMessage(messages['login.user.identity.label'])}
|
type="submit"
|
||||||
/>
|
variant="brand"
|
||||||
<PasswordField
|
className="login-button-width"
|
||||||
name="password"
|
state={submitState}
|
||||||
value={this.state.password}
|
labels={{
|
||||||
autoComplete="off"
|
default: formatMessage(messages['sign.in.button']),
|
||||||
showRequirements={false}
|
pending: '',
|
||||||
handleChange={(e) => this.setState({ password: e.target.value, isSubmitted: false })}
|
}}
|
||||||
handleFocus={this.handleOnFocus}
|
onClick={handleSubmit}
|
||||||
handleBlur={this.handleOnBlur}
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
errorMessage={this.state.errors.password}
|
/>
|
||||||
floatingLabel={intl.formatMessage(messages['login.password.label'])}
|
<Link
|
||||||
/>
|
id="forgot-password"
|
||||||
<StatefulButton
|
name="forgot-password"
|
||||||
name="sign-in"
|
className="btn btn-link font-weight-500 text-body"
|
||||||
id="sign-in"
|
to={updatePathWithQueryParams(RESET_PAGE)}
|
||||||
type="submit"
|
onClick={trackForgotPasswordLinkClick}
|
||||||
variant="brand"
|
>
|
||||||
className="login-button-width"
|
{formatMessage(messages['forgot.password'])}
|
||||||
state={submitState}
|
</Link>
|
||||||
labels={{
|
<ThirdPartyAuth
|
||||||
default: intl.formatMessage(messages['sign.in.button']),
|
currentProvider={currentProvider}
|
||||||
pending: '',
|
providers={providers}
|
||||||
}}
|
secondaryProviders={secondaryProviders}
|
||||||
onClick={this.handleSubmit}
|
handleInstitutionLogin={handleInstitutionLogin}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
||||||
/>
|
isLoginPage
|
||||||
<Link
|
/>
|
||||||
id="forgot-password"
|
</Form>
|
||||||
name="forgot-password"
|
</div>
|
||||||
className="btn btn-link font-weight-500 text-body"
|
</>
|
||||||
to={updatePathWithQueryParams(RESET_PAGE)}
|
);
|
||||||
onClick={this.handleForgotPasswordLinkClickEvent}
|
};
|
||||||
>
|
|
||||||
{intl.formatMessage(messages['forgot.password'])}
|
|
||||||
</Link>
|
|
||||||
{this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
const mapStateToProps = state => {
|
||||||
const {
|
const loginPageState = state.login;
|
||||||
intl, submitState, thirdPartyAuthContext, thirdPartyAuthApiStatus,
|
return {
|
||||||
} = this.props;
|
backedUpFormData: loginPageState.loginFormData,
|
||||||
const { currentProvider, providers, secondaryProviders } = this.props.thirdPartyAuthContext;
|
loginErrorCode: loginPageState.loginErrorCode,
|
||||||
|
loginErrorContext: loginPageState.loginErrorContext,
|
||||||
|
loginResult: loginPageState.loginResult,
|
||||||
|
shouldBackupState: loginPageState.shouldBackupState,
|
||||||
|
showResetPasswordSuccessBanner: loginPageState.showResetPasswordSuccessBanner,
|
||||||
|
submitState: loginPageState.submitState,
|
||||||
|
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
|
||||||
|
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
if (this.tpaHint) {
|
LoginPage.propTypes = {
|
||||||
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
backedUpFormData: PropTypes.shape({
|
||||||
return <Skeleton height={36} />;
|
formFields: PropTypes.shape({}),
|
||||||
}
|
errors: PropTypes.shape({}),
|
||||||
const { provider, skipHintedLogin } = getTpaProvider(this.tpaHint, providers, secondaryProviders);
|
}),
|
||||||
if (skipHintedLogin) {
|
loginErrorCode: PropTypes.string,
|
||||||
window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl;
|
loginErrorContext: PropTypes.shape({
|
||||||
return null;
|
email: PropTypes.string,
|
||||||
}
|
redirectUrl: PropTypes.string,
|
||||||
return provider ? (<EnterpriseSSO provider={provider} intl={intl} />) : this.renderForm(
|
context: PropTypes.shape({}),
|
||||||
currentProvider,
|
}),
|
||||||
providers,
|
loginResult: PropTypes.shape({
|
||||||
secondaryProviders,
|
redirectUrl: PropTypes.string,
|
||||||
thirdPartyAuthContext,
|
success: PropTypes.bool,
|
||||||
thirdPartyAuthApiStatus,
|
}),
|
||||||
submitState,
|
shouldBackupState: PropTypes.bool,
|
||||||
intl,
|
showResetPasswordSuccessBanner: PropTypes.bool,
|
||||||
);
|
submitState: PropTypes.string,
|
||||||
}
|
thirdPartyAuthApiStatus: PropTypes.string,
|
||||||
return this.renderForm(
|
institutionLogin: PropTypes.bool.isRequired,
|
||||||
currentProvider,
|
thirdPartyAuthContext: PropTypes.shape({
|
||||||
providers,
|
currentProvider: PropTypes.string,
|
||||||
secondaryProviders,
|
errorMessage: PropTypes.string,
|
||||||
thirdPartyAuthContext,
|
platformName: PropTypes.string,
|
||||||
thirdPartyAuthApiStatus,
|
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||||
submitState,
|
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
||||||
intl,
|
finishAuthUrl: PropTypes.string,
|
||||||
);
|
}),
|
||||||
}
|
// Actions
|
||||||
}
|
backupFormState: PropTypes.func.isRequired,
|
||||||
|
dismissPasswordResetBanner: PropTypes.func.isRequired,
|
||||||
|
loginRequest: PropTypes.func.isRequired,
|
||||||
|
getTPADataFromBackend: PropTypes.func.isRequired,
|
||||||
|
handleInstitutionLogin: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
LoginPage.defaultProps = {
|
LoginPage.defaultProps = {
|
||||||
loginResult: null,
|
backedUpFormData: {
|
||||||
loginError: null,
|
formFields: {
|
||||||
loginFormData: {
|
emailOrUsername: '', password: '',
|
||||||
emailOrUsername: '',
|
},
|
||||||
password: '',
|
|
||||||
errors: {
|
errors: {
|
||||||
emailOrUsername: '',
|
emailOrUsername: '', password: '',
|
||||||
password: '',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resetPassword: false,
|
loginErrorCode: null,
|
||||||
|
loginErrorContext: {},
|
||||||
|
loginResult: {},
|
||||||
|
shouldBackupState: false,
|
||||||
|
showResetPasswordSuccessBanner: false,
|
||||||
submitState: DEFAULT_STATE,
|
submitState: DEFAULT_STATE,
|
||||||
thirdPartyAuthApiStatus: 'pending',
|
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||||
thirdPartyAuthContext: {
|
thirdPartyAuthContext: {
|
||||||
currentProvider: null,
|
currentProvider: null,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
@@ -368,68 +357,12 @@ LoginPage.defaultProps = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
LoginPage.propTypes = {
|
|
||||||
getThirdPartyAuthContext: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.shape({
|
|
||||||
formatMessage: PropTypes.func,
|
|
||||||
}).isRequired,
|
|
||||||
loginError: PropTypes.shape({}),
|
|
||||||
loginRequest: PropTypes.func.isRequired,
|
|
||||||
loginRequestFailure: PropTypes.func.isRequired,
|
|
||||||
loginRequestReset: PropTypes.func.isRequired,
|
|
||||||
setLoginFormData: PropTypes.func.isRequired,
|
|
||||||
loginRemovePasswordResetBanner: PropTypes.func.isRequired,
|
|
||||||
loginResult: PropTypes.shape({
|
|
||||||
redirectUrl: PropTypes.string,
|
|
||||||
success: PropTypes.bool,
|
|
||||||
}),
|
|
||||||
loginFormData: PropTypes.shape({
|
|
||||||
emailOrUsername: PropTypes.string,
|
|
||||||
password: PropTypes.string,
|
|
||||||
errors: PropTypes.shape({
|
|
||||||
emailOrUsername: PropTypes.string,
|
|
||||||
password: PropTypes.string,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
resetPassword: PropTypes.bool,
|
|
||||||
submitState: PropTypes.string,
|
|
||||||
thirdPartyAuthApiStatus: PropTypes.string,
|
|
||||||
thirdPartyAuthContext: PropTypes.shape({
|
|
||||||
currentProvider: PropTypes.string,
|
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
platformName: PropTypes.string,
|
|
||||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
finishAuthUrl: PropTypes.string,
|
|
||||||
}),
|
|
||||||
institutionLogin: PropTypes.bool.isRequired,
|
|
||||||
handleInstitutionLogin: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const loginResult = loginRequestSelector(state);
|
|
||||||
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
|
|
||||||
const loginError = loginErrorSelector(state);
|
|
||||||
const loginFormData = loginFormDataSelector(state);
|
|
||||||
return {
|
|
||||||
submitState: state.login.submitState,
|
|
||||||
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
|
||||||
loginError,
|
|
||||||
loginResult,
|
|
||||||
thirdPartyAuthContext,
|
|
||||||
loginFormData,
|
|
||||||
resetPassword: state.login.resetPassword,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
{
|
{
|
||||||
getThirdPartyAuthContext,
|
backupFormState: backupLoginFormBegin,
|
||||||
|
dismissPasswordResetBanner,
|
||||||
loginRequest,
|
loginRequest,
|
||||||
loginRequestFailure,
|
getTPADataFromBackend: getThirdPartyAuthContext,
|
||||||
loginRequestReset,
|
|
||||||
setLoginFormData,
|
|
||||||
loginRemovePasswordResetBanner,
|
|
||||||
},
|
},
|
||||||
)(injectIntl(LoginPage));
|
)(injectIntl(LoginPage));
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { AsyncActionType } from '../../data/utils';
|
import { AsyncActionType } from '../../data/utils';
|
||||||
|
|
||||||
|
export const BACKUP_LOGIN_DATA = new AsyncActionType('LOGIN', 'BACKUP_LOGIN_DATA');
|
||||||
export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST');
|
export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST');
|
||||||
export const LOGIN_PERSIST_FORM_DATA = 'LOGIN_PERSIST_FORM_DATA';
|
export const DISMISS_PASSWORD_RESET_BANNER = 'DISMISS_PASSWORD_RESET_BANNER';
|
||||||
export const LOGIN_REMOVE_PASSWORD_RESET_BANNER = 'LOGIN_REMOVE_PASSWORD_RESET_BANNER';
|
|
||||||
|
// Backup login form data
|
||||||
|
export const backupLoginForm = () => ({
|
||||||
|
type: BACKUP_LOGIN_DATA.BASE,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const backupLoginFormBegin = (data) => ({
|
||||||
|
type: BACKUP_LOGIN_DATA.BEGIN,
|
||||||
|
payload: { ...data },
|
||||||
|
});
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
export const loginRequest = creds => ({
|
export const loginRequest = creds => ({
|
||||||
@@ -24,15 +34,6 @@ export const loginRequestFailure = (loginError) => ({
|
|||||||
payload: { loginError },
|
payload: { loginError },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const loginRequestReset = () => ({
|
export const dismissPasswordResetBanner = () => ({
|
||||||
type: LOGIN_REQUEST.RESET,
|
type: DISMISS_PASSWORD_RESET_BANNER,
|
||||||
});
|
|
||||||
|
|
||||||
export const setLoginFormData = (formData) => ({
|
|
||||||
type: LOGIN_PERSIST_FORM_DATA,
|
|
||||||
payload: { formData },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const loginRemovePasswordResetBanner = () => ({
|
|
||||||
type: LOGIN_REMOVE_PASSWORD_RESET_BANNER,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,64 +1,69 @@
|
|||||||
import { LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from './actions';
|
import {
|
||||||
|
BACKUP_LOGIN_DATA,
|
||||||
|
DISMISS_PASSWORD_RESET_BANNER,
|
||||||
|
LOGIN_REQUEST,
|
||||||
|
} from './actions';
|
||||||
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
||||||
import { RESET_PASSWORD } from '../../reset-password';
|
import { RESET_PASSWORD } from '../../reset-password';
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
loginError: null,
|
loginErrorCode: '',
|
||||||
|
loginErrorContext: {},
|
||||||
loginResult: {},
|
loginResult: {},
|
||||||
resetPassword: false,
|
|
||||||
loginFormData: {
|
loginFormData: {
|
||||||
password: '',
|
formFields: {
|
||||||
emailOrUsername: '',
|
emailOrUsername: '', password: '',
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
emailOrUsername: '',
|
emailOrUsername: '', password: '',
|
||||||
password: '',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shouldBackupState: false,
|
||||||
|
showResetPasswordSuccessBanner: false,
|
||||||
|
submitState: DEFAULT_STATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = (state = defaultState, action = {}) => {
|
const reducer = (state = defaultState, action = {}) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case BACKUP_LOGIN_DATA.BASE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
shouldBackupState: true,
|
||||||
|
};
|
||||||
|
case BACKUP_LOGIN_DATA.BEGIN:
|
||||||
|
return {
|
||||||
|
...defaultState,
|
||||||
|
loginFormData: { ...action.payload },
|
||||||
|
};
|
||||||
case LOGIN_REQUEST.BEGIN:
|
case LOGIN_REQUEST.BEGIN:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
showResetPasswordSuccessBanner: false,
|
||||||
submitState: PENDING_STATE,
|
submitState: PENDING_STATE,
|
||||||
resetPassword: false,
|
|
||||||
};
|
};
|
||||||
case LOGIN_REQUEST.SUCCESS:
|
case LOGIN_REQUEST.SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loginResult: action.payload,
|
loginResult: action.payload,
|
||||||
};
|
};
|
||||||
case LOGIN_REQUEST.FAILURE:
|
case LOGIN_REQUEST.FAILURE: {
|
||||||
|
const { email, loginError, redirectUrl } = action.payload;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loginError: action.payload.loginError,
|
loginErrorCode: loginError.errorCode,
|
||||||
|
loginErrorContext: { ...loginError.context, email, redirectUrl },
|
||||||
submitState: DEFAULT_STATE,
|
submitState: DEFAULT_STATE,
|
||||||
};
|
};
|
||||||
case LOGIN_REQUEST.RESET:
|
}
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
loginError: null,
|
|
||||||
};
|
|
||||||
case RESET_PASSWORD.SUCCESS:
|
case RESET_PASSWORD.SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
resetPassword: true,
|
showResetPasswordSuccessBanner: true,
|
||||||
};
|
};
|
||||||
case LOGIN_PERSIST_FORM_DATA: {
|
case DISMISS_PASSWORD_RESET_BANNER: {
|
||||||
const { formData } = action.payload;
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loginFormData: {
|
showResetPasswordSuccessBanner: false,
|
||||||
...state.loginFormData,
|
|
||||||
...formData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case LOGIN_REMOVE_PASSWORD_RESET_BANNER: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
resetPassword: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
export const storeName = 'login';
|
|
||||||
|
|
||||||
export const loginSelector = state => ({ ...state[storeName] });
|
|
||||||
|
|
||||||
export const loginRequestSelector = createSelector(
|
|
||||||
loginSelector,
|
|
||||||
login => login.loginResult,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const loginErrorSelector = createSelector(
|
|
||||||
loginSelector,
|
|
||||||
login => login.loginError,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const loginFormDataSelector = createSelector(
|
|
||||||
loginSelector,
|
|
||||||
login => login.loginFormData,
|
|
||||||
);
|
|
||||||
@@ -1,57 +1,154 @@
|
|||||||
import {
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER,
|
|
||||||
} from '../actions';
|
import { DEFAULT_REDIRECT_URL, DEFAULT_STATE, PENDING_STATE } from '../../../data/constants';
|
||||||
|
import { RESET_PASSWORD } from '../../../reset-password';
|
||||||
|
import { BACKUP_LOGIN_DATA, DISMISS_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from '../actions';
|
||||||
import reducer from '../reducers';
|
import reducer from '../reducers';
|
||||||
|
|
||||||
describe('login reducer', () => {
|
describe('login reducer', () => {
|
||||||
it('should set loginFormData', () => {
|
const defaultState = {
|
||||||
const state = {
|
loginErrorCode: '',
|
||||||
loginFormData: {
|
loginErrorContext: {},
|
||||||
password: '',
|
loginResult: {},
|
||||||
emailOrUsername: '',
|
loginFormData: {
|
||||||
errors: {
|
formFields: {
|
||||||
emailOrUsername: '',
|
emailOrUsername: '', password: '',
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
resetPassword: false,
|
errors: {
|
||||||
};
|
emailOrUsername: '', password: '',
|
||||||
const formData = {
|
},
|
||||||
password: 'johndoe',
|
},
|
||||||
emailOrUsername: 'john@gmail.com',
|
shouldBackupState: false,
|
||||||
};
|
showResetPasswordSuccessBanner: false,
|
||||||
|
submitState: DEFAULT_STATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should update state to show reset password success banner', () => {
|
||||||
const action = {
|
const action = {
|
||||||
type: LOGIN_PERSIST_FORM_DATA,
|
type: RESET_PASSWORD.SUCCESS,
|
||||||
payload: { formData },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
reducer(state, action),
|
reducer(defaultState, action),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
{
|
{
|
||||||
loginFormData: {
|
...defaultState,
|
||||||
...state.loginFormData,
|
showResetPasswordSuccessBanner: true,
|
||||||
password: 'johndoe',
|
|
||||||
emailOrUsername: 'john@gmail.com',
|
|
||||||
},
|
|
||||||
resetPassword: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set resetPassword', () => {
|
it('should set the flag which keeps the login form data in redux state', () => {
|
||||||
const state = {
|
|
||||||
resetPassword: true,
|
|
||||||
};
|
|
||||||
const action = {
|
const action = {
|
||||||
type: LOGIN_REMOVE_PASSWORD_RESET_BANNER,
|
type: BACKUP_LOGIN_DATA.BASE,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
reducer(state, action),
|
reducer(defaultState, action),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
{
|
{
|
||||||
resetPassword: false,
|
...defaultState,
|
||||||
|
shouldBackupState: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should backup the login form data', () => {
|
||||||
|
const payload = {
|
||||||
|
formFields: {
|
||||||
|
emailOrUsername: 'test@exmaple.com',
|
||||||
|
password: 'test1',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
emailOrUsername: '', password: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const action = {
|
||||||
|
type: BACKUP_LOGIN_DATA.BEGIN,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
reducer(defaultState, action),
|
||||||
|
).toEqual(
|
||||||
|
{
|
||||||
|
...defaultState,
|
||||||
|
loginFormData: payload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update state to dismiss reset password banner', () => {
|
||||||
|
const action = {
|
||||||
|
type: DISMISS_PASSWORD_RESET_BANNER,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
reducer(defaultState, action),
|
||||||
|
).toEqual(
|
||||||
|
{
|
||||||
|
...defaultState,
|
||||||
|
showResetPasswordSuccessBanner: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start the login request', () => {
|
||||||
|
const action = {
|
||||||
|
type: LOGIN_REQUEST.BEGIN,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(defaultState, action)).toEqual(
|
||||||
|
{
|
||||||
|
...defaultState,
|
||||||
|
showResetPasswordSuccessBanner: false,
|
||||||
|
submitState: PENDING_STATE,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set redirect url on login success action', () => {
|
||||||
|
const payload = {
|
||||||
|
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`,
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
const action = {
|
||||||
|
type: LOGIN_REQUEST.SUCCESS,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(defaultState, action)).toEqual(
|
||||||
|
{
|
||||||
|
...defaultState,
|
||||||
|
loginResult: payload,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the error data on login request failure', () => {
|
||||||
|
const payload = {
|
||||||
|
loginError: {
|
||||||
|
success: false,
|
||||||
|
value: 'Email or password is incorrect.',
|
||||||
|
errorCode: 'incorrect-email-or-password',
|
||||||
|
context: {
|
||||||
|
failureCount: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
email: 'test@example.com',
|
||||||
|
redirectUrl: '',
|
||||||
|
};
|
||||||
|
const action = {
|
||||||
|
type: LOGIN_REQUEST.FAILURE,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(reducer(defaultState, action)).toEqual(
|
||||||
|
{
|
||||||
|
...defaultState,
|
||||||
|
loginErrorCode: payload.loginError.errorCode,
|
||||||
|
loginErrorContext: { ...payload.loginError.context, email: payload.email, redirectUrl: payload.redirectUrl },
|
||||||
|
submitState: DEFAULT_STATE,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user