Compare commits
259 Commits
ahtesham/V
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e0be4e581 | ||
|
|
8b2160388f | ||
|
|
ab357704f8 | ||
|
|
2eab85e960 | ||
|
|
9f503eb5ef | ||
|
|
c2a55e125c | ||
|
|
abea379eb0 | ||
|
|
48122ff99d | ||
|
|
3153cff4ff | ||
|
|
919d98df79 | ||
|
|
c7adcecb8a | ||
|
|
db4eeac266 | ||
|
|
60e2119116 | ||
|
|
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 | ||
|
|
5aec091156 | ||
|
|
68993cc21f | ||
|
|
404fc2b36b | ||
|
|
7bb06d0bfa | ||
|
|
abbb64b9c7 | ||
|
|
99ca582c1a | ||
|
|
3e4cfc1573 | ||
|
|
5437e1b7e9 | ||
|
|
4d33cd7d69 | ||
|
|
cb82e94b53 | ||
|
|
dbe14ccedf | ||
|
|
841edf2d24 | ||
|
|
0c375cc50c | ||
|
|
50072887d0 | ||
|
|
976814d846 | ||
|
|
c22024cf66 | ||
|
|
829a219b9f | ||
|
|
13d572ab28 | ||
|
|
c0c2ffa122 | ||
|
|
84c563fda3 | ||
|
|
97720d6a46 | ||
|
|
386982d9c4 | ||
|
|
cb38a2a148 | ||
|
|
07f19209bf | ||
|
|
69c0ca13fc | ||
|
|
e49c50f55c | ||
|
|
f0105f0094 | ||
|
|
3c89afea4a | ||
|
|
848f574f3f | ||
|
|
ec9e34cea4 | ||
|
|
f9069df4e6 | ||
|
|
9035f3eb7e | ||
|
|
e197e788d1 | ||
|
|
49d33522a8 | ||
|
|
06f0ec3c0b | ||
|
|
54319c6949 | ||
|
|
86f875ec3e | ||
|
|
754a6ddb12 | ||
|
|
62e8f75b96 | ||
|
|
c0deb663a6 | ||
|
|
ce28add152 | ||
|
|
5f89315947 | ||
|
|
56dd194a1a | ||
|
|
adcdcc4c8b | ||
|
|
7fd45f089d | ||
|
|
65d82b2080 | ||
|
|
f771935e20 | ||
|
|
70c255fc4f | ||
|
|
bd1396fc54 | ||
|
|
cba5395d5c | ||
|
|
b42c09d919 | ||
|
|
b7af2356fa | ||
|
|
22d477e55f | ||
|
|
dfa69c27bb | ||
|
|
6b78158db2 | ||
|
|
c92cac0eed | ||
|
|
b2dce920fa | ||
|
|
44cec762fb | ||
|
|
a98188ead8 | ||
|
|
294b1a469f | ||
|
|
ebed588c1c | ||
|
|
46f8217e1a | ||
|
|
9ab23cf485 | ||
|
|
7790660fe8 | ||
|
|
1dd2726beb | ||
|
|
ba9ce89d1b | ||
|
|
c9082ac709 | ||
|
|
27c8fa8986 | ||
|
|
a04289d71b | ||
|
|
321859e0f5 | ||
|
|
4b4bf413c1 | ||
|
|
06c4f75b4a | ||
|
|
12dd97af61 | ||
|
|
8e77197459 | ||
|
|
3cc64cada6 | ||
|
|
3ac5874df1 | ||
|
|
107dd6f360 | ||
|
|
347e0cd336 | ||
|
|
2ba6058ec7 | ||
|
|
683aa258b8 | ||
|
|
d7ad7e314d | ||
|
|
ef66eb1c31 | ||
|
|
ec8b256852 | ||
|
|
5a715b2fb5 | ||
|
|
e80578e682 | ||
|
|
155a73dc39 | ||
|
|
f5d0b50d90 | ||
|
|
b0745de672 | ||
|
|
d54fdbf84f | ||
|
|
0a6432c393 | ||
|
|
9e91c382b3 | ||
|
|
2cf24761c0 | ||
|
|
c2bdc31a03 | ||
|
|
9d487d7b61 | ||
|
|
a2ab6c196a | ||
|
|
6a5b02e8ad | ||
|
|
e76f214024 | ||
|
|
cb47717b09 | ||
|
|
85dbc9a6ca | ||
|
|
4aebeaffa7 | ||
|
|
6a84e2d5b6 | ||
|
|
e26620e350 | ||
|
|
1cabd2a514 | ||
|
|
06dd70078e | ||
|
|
4b13866e1d | ||
|
|
11142fda25 | ||
|
|
b4057f9588 | ||
|
|
9524f030d1 | ||
|
|
3f10dce04f | ||
|
|
5f8802272d | ||
|
|
0d486c2774 | ||
|
|
e78a1583c0 | ||
|
|
ea966c48b9 | ||
|
|
810b8d46b9 | ||
|
|
8a00b74863 | ||
|
|
94fafe661d | ||
|
|
7d58a124ab | ||
|
|
378a8d95f9 | ||
|
|
3a2e39af97 | ||
|
|
6f6d725126 | ||
|
|
3d8eb34d80 | ||
|
|
2768fc02ea | ||
|
|
0902467fa6 | ||
|
|
1dc999070f | ||
|
|
7a169715ea | ||
|
|
361f6781ee | ||
|
|
42190a89dd | ||
|
|
2d4c6a1d3b | ||
|
|
1dd88795c3 | ||
|
|
7cff7311e1 | ||
|
|
bf93959350 | ||
|
|
94151c2668 | ||
|
|
bf650e6d4c | ||
|
|
575f195970 | ||
|
|
c6bf6c92c1 | ||
|
|
b86c31bff8 | ||
|
|
fc37bbec1d | ||
|
|
6525c66600 | ||
|
|
145234c5c3 | ||
|
|
a7f816f49a | ||
|
|
694b0a5381 | ||
|
|
8a0947faf3 | ||
|
|
d1c4b20160 | ||
|
|
d81d8419a0 | ||
|
|
c6acdab7c6 | ||
|
|
0374143148 | ||
|
|
2d3c5ed761 | ||
|
|
a611451233 | ||
|
|
93dcd8f16e | ||
|
|
294519c7a5 | ||
|
|
f11df1f513 | ||
|
|
563609e10a | ||
|
|
b4d4e36f72 |
13
.env
13
.env
@@ -15,22 +15,27 @@ SEGMENT_KEY=''
|
||||
SITE_NAME=null
|
||||
INFO_EMAIL=''
|
||||
# ***** Cookies *****
|
||||
REGISTER_CONVERSION_COOKIE_NAME=null
|
||||
USER_SURVEY_COOKIE_NAME=null
|
||||
USER_RETENTION_COOKIE_NAME=null
|
||||
# ***** Links *****
|
||||
LOGIN_ISSUE_SUPPORT_LINK=''
|
||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
|
||||
POST_REGISTRATION_REDIRECT_URL=''
|
||||
SEARCH_CATALOG_URL=''
|
||||
# ***** Features flags *****
|
||||
DISABLE_ENTERPRISE_LOGIN=''
|
||||
ENABLE_COOKIE_POLICY_BANNER=''
|
||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
||||
ENABLE_PERSONALIZED_RECOMMENDATIONS=''
|
||||
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS=''
|
||||
# ***** Zendesk related keys *****
|
||||
ZENDESK_KEY=''
|
||||
ZENDESK_LOGO_URL=''
|
||||
# ***** Base Container Images *****
|
||||
BANNER_IMAGE_LARGE=''
|
||||
BANNER_IMAGE_MEDIUM=''
|
||||
BANNER_IMAGE_SMALL=''
|
||||
BANNER_IMAGE_EXTRA_SMALL=''
|
||||
# ***** Miscellaneous *****
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
|
||||
@@ -20,16 +20,21 @@ SEGMENT_KEY=''
|
||||
SITE_NAME='Your Platform Name Here'
|
||||
INFO_EMAIL='info@example.com'
|
||||
# ***** Cookies *****
|
||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
||||
SESSION_COOKIE_DOMAIN='localhost'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
# ***** Links *****
|
||||
LOGIN_ISSUE_SUPPORT_LINK='http://localhost:18000/login-issue-support-url'
|
||||
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
|
||||
TOS_LINK='http://localhost:18000/tos'
|
||||
PRIVACY_POLICY='http://localhost:18000/privacy'
|
||||
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 *****
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
ZENDESK_KEY=''
|
||||
ZENDESK_LOGO_URL=''
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copy these to the .env.private to enable edX specific functionality on local system
|
||||
ENABLE_COOKIE_POLICY_BANNER='true'
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
|
||||
MARKETING_EMAILS_OPT_IN='true'
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS='true'
|
||||
|
||||
@@ -16,7 +16,5 @@ ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME='Your Platform Name Here'
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
|
||||
12
.eslintrc.js
12
.eslintrc.js
@@ -1,16 +1,17 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const { createConfig } = require('@edx/frontend-build');
|
||||
|
||||
module.exports = createConfig('eslint', {
|
||||
rules: {
|
||||
// Temporarily update the 'indent', 'template-curly-spacing' and
|
||||
// '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:
|
||||
// - TypeError: Cannot read property 'range' of null
|
||||
'indent': [
|
||||
indent: [
|
||||
'error',
|
||||
2,
|
||||
{ 'ignoredNodes': ['TemplateLiteral', 'SwitchCase'] }
|
||||
{ ignoredNodes: ['TemplateLiteral', 'SwitchCase'] },
|
||||
],
|
||||
'template-curly-spacing': 'off',
|
||||
'jsx-a11y/label-has-associated-control': ['error', {
|
||||
@@ -18,9 +19,9 @@ module.exports = createConfig('eslint', {
|
||||
labelAttributes: [],
|
||||
controlComponents: [],
|
||||
assert: 'htmlFor',
|
||||
depth: 25
|
||||
depth: 25,
|
||||
}],
|
||||
'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}],
|
||||
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: true }],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
@@ -46,5 +47,6 @@ module.exports = createConfig('eslint', {
|
||||
},
|
||||
},
|
||||
],
|
||||
'function-paren-newline': 'off',
|
||||
},
|
||||
});
|
||||
|
||||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -2,11 +2,16 @@
|
||||
|
||||
Include a description of your changes here, along with a link to any relevant Jira tickets and/or Github issues.
|
||||
|
||||
#### JIRA
|
||||
|
||||
[XXX-XXXX](https://2u-internal.atlassian.net/browse/XXX-XXXX)
|
||||
|
||||
#### How Has This Been Tested?
|
||||
|
||||
Please describe in detail how you tested your changes.
|
||||
|
||||
#### Screenshots/sandbox (optional):
|
||||
|
||||
Include a link to the sandbox for design changes or screenshot for before and after. **Remove this section if its not applicable.**
|
||||
|
||||
|Before|After|
|
||||
@@ -21,4 +26,4 @@ Include a link to the sandbox for design changes or screenshot for before and af
|
||||
#### Post-merge Checklist
|
||||
|
||||
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/vanguards** to do it.
|
||||
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.
|
||||
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.
|
||||
|
||||
25
.github/workflows/autoupdate-pull-request.yml
vendored
Normal file
25
.github/workflows/autoupdate-pull-request.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: autoupdate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [ labeled ]
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
autoupdate:
|
||||
name: autoupdate
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: docker://chinthakagodawita/autoupdate-action:v1
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
DRY_RUN: "false"
|
||||
PR_FILTER: "labelled"
|
||||
PR_LABELS: "autoupdate"
|
||||
EXCLUDED_LABELS: "dependencies,wontfix"
|
||||
MERGE_MSG: "Branch was auto-updated."
|
||||
RETRY_COUNT: "5"
|
||||
RETRY_SLEEP: "300"
|
||||
MERGE_CONFLICT_ACTION: "fail"
|
||||
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -11,17 +11,16 @@ on:
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Nodejs Env
|
||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
|
||||
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
|
||||
|
||||
31
Makefile
31
Makefile
@@ -1,6 +1,7 @@
|
||||
export TRANSIFEX_RESOURCE = frontend-app-authn
|
||||
transifex_langs = "ar,fr,es_419,zh_CN,it_IT,pt_PT,de_DE,uk,ru,hi"
|
||||
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
|
||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||
i18n = ./src/i18n
|
||||
transifex_input = $(i18n)/transifex_input.json
|
||||
@@ -42,11 +43,37 @@ push_translations:
|
||||
# Pushing comments to Transifex...
|
||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||
|
||||
ifeq ($(OPENEDX_ATLAS_PULL),)
|
||||
# Pulls translations from Transifex.
|
||||
pull_translations:
|
||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
||||
else
|
||||
# Experimental: OEP-58 Pulls translations using atlas
|
||||
pull_translations:
|
||||
rm -rf src/i18n/messages
|
||||
mkdir src/i18n/messages
|
||||
cd src/i18n/messages \
|
||||
&& atlas pull --filter=$(transifex_langs) \
|
||||
translations/paragon/src/i18n/messages:paragon \
|
||||
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
|
||||
|
||||
# This target is used by CI.
|
||||
$(intl_imports) paragon frontend-app-authn
|
||||
endif
|
||||
|
||||
# This target is used by Travis.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
# Checking for package-lock.json changes...
|
||||
git diff --exit-code package-lock.json
|
||||
|
||||
.PHONY: validate
|
||||
validate:
|
||||
make validate-no-uncommitted-package-lock-changes
|
||||
npm run i18n_extract
|
||||
npm run lint -- --max-warnings 0
|
||||
npm run test
|
||||
npm run build
|
||||
|
||||
.PHONY: validate.ci
|
||||
validate.ci:
|
||||
npm ci
|
||||
make validate
|
||||
|
||||
13
README.rst
13
README.rst
@@ -104,6 +104,15 @@ The authentication micro-frontend also requires the following additional variabl
|
||||
- Disables the enterprise login from Authn MFE.
|
||||
- ``true`` | ``''`` (empty strings are falsy)
|
||||
|
||||
* - ``MFE_CONFIG_API_URL``
|
||||
- Link of the API to get runtime mfe configuration variables from the site configuration or django settings.
|
||||
- ``/api/v1/mfe_config`` | ``''`` (empty strings are falsy)
|
||||
|
||||
* - ``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`` | ``''``
|
||||
|
||||
|
||||
edX-specific Environment Variables
|
||||
**********************************
|
||||
|
||||
@@ -121,6 +130,10 @@ Furthermore, there are several edX-specific environment variables that enable in
|
||||
- Enables support for opting in marketing emails that helps us getting user consent for sending marketing emails.
|
||||
- ``true`` | ``''`` (empty strings are falsy)
|
||||
|
||||
* - ``SHOW_CONFIGURABLE_EDX_FIELDS``
|
||||
- For edX, country and honor code fields are required by default. This flag enables edX specific required fields.
|
||||
- ``true`` | ``''`` (empty strings are falsy)
|
||||
|
||||
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>`__.
|
||||
|
||||
|
||||
42182
package-lock.json
generated
42182
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
61
package.json
61
package.json
@@ -32,58 +32,55 @@
|
||||
"url": "https://github.com/openedx/frontend-app-authn/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||
"@edx/frontend-component-cookie-policy-banner": "2.2.0",
|
||||
"@edx/frontend-platform": "3.2.0",
|
||||
"@edx/paragon": "20.28.4",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
||||
"@edx/frontend-platform": "^5.5.4",
|
||||
"@edx/paragon": "20.46.2",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@redux-devtools/extension": "3.2.3",
|
||||
"@optimizely/react-sdk": "^2.9.1",
|
||||
"@redux-devtools/extension": "3.2.5",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"algoliasearch": "^4.14.3",
|
||||
"algoliasearch-helper": "^3.14.0",
|
||||
"classnames": "2.3.2",
|
||||
"core-js": "3.26.1",
|
||||
"extract-react-intl-messages": "4.1.1",
|
||||
"core-js": "3.32.0",
|
||||
"fastest-levenshtein": "1.0.16",
|
||||
"form-urlencoded": "4.2.1",
|
||||
"lodash.camelcase": "4.3.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"form-urlencoded": "6.1.0",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "5.1.1",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"query-string": "7.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-loading-skeleton": "2.2.0",
|
||||
"react-onclickoutside": "6.12.2",
|
||||
"react-loading-skeleton": "3.3.1",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router": "5.3.4",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-router": "6.15.0",
|
||||
"react-router-dom": "6.15.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.2.0",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-mock-store": "1.5.4",
|
||||
"redux-saga": "1.2.2",
|
||||
"redux-saga": "1.2.3",
|
||||
"redux-thunk": "2.4.2",
|
||||
"regenerator-runtime": "0.13.11",
|
||||
"reselect": "4.1.7",
|
||||
"sanitize-html": "2.8.0",
|
||||
"semver-regex": "3.1.4",
|
||||
"regenerator-runtime": "0.14.0",
|
||||
"reselect": "4.1.8",
|
||||
"universal-cookie": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@edx/frontend-build": "11.0.2",
|
||||
"@edx/frontend-build": "12.9.8",
|
||||
"@edx/reactifex": "1.1.0",
|
||||
"babel-plugin-formatjs": "10.3.35",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
||||
"babel-plugin-formatjs": "10.5.3",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.7",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-import": "2.28.0",
|
||||
"glob": "7.2.3",
|
||||
"history": "5.3.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "27.5.1",
|
||||
"react-test-renderer": "16.14.0"
|
||||
"jest": "29.6.2",
|
||||
"react-test-renderer": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<title>Authn | <%= process.env.SITE_NAME %></title>
|
||||
<title><%= (process.env.SITE_NAME && process.env.SITE_NAME != 'null') ? 'Authentication | ' + process.env.SITE_NAME : 'Authentication' %></title>
|
||||
<meta charset="utf-8">
|
||||
<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.6/iframeResizer.contentWindow.min.js"
|
||||
integrity="sha512-R7Piufj0/o6jG9ZKrAvS2dblFr2kkuG4XVQwStX+/4P+KwOLUXn2DXy0l1AJDxxqGhkM/FJllZHG2PKOAheYzg=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer">
|
||||
</script>
|
||||
<% if (process.env.OPTIMIZELY_URL) { %>
|
||||
<script
|
||||
src="<%= process.env.OPTIMIZELY_URL %>"
|
||||
@@ -13,61 +19,6 @@
|
||||
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
||||
></script>
|
||||
<% } %>
|
||||
<% if (process.env.ZENDESK_KEY) { %>
|
||||
<script
|
||||
id="ze-snippet"
|
||||
src="https://static.zdassets.com/ekr/snippet.js?key=<%= process.env.ZENDESK_KEY %>"
|
||||
>
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
window.zESettings = {
|
||||
cookies: true,
|
||||
webWidget: {
|
||||
contactOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
chat: {
|
||||
suppress: false,
|
||||
departments: {
|
||||
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring']
|
||||
}
|
||||
},
|
||||
contactForm: {
|
||||
ticketForms: [
|
||||
{
|
||||
id: 360003368814,
|
||||
subject: false,
|
||||
fields: [
|
||||
{
|
||||
id: 'description',
|
||||
prefill: {
|
||||
'*': '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectTicketForm: {
|
||||
'*': 'Please choose your request type:',
|
||||
},
|
||||
attachments: true,
|
||||
},
|
||||
helpCenter: {
|
||||
originalArticleButton: true,
|
||||
},
|
||||
answerBot: {
|
||||
suppress: false,
|
||||
contactOnlyAfterQuery: true,
|
||||
title: { '*': 'edX Support' },
|
||||
avatar: {
|
||||
url: '<%= process.env.ZENDESK_LOGO_URL %>',
|
||||
name: { '*': 'edX Support' },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -3,10 +3,10 @@ import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Logistration, NotFoundPage, registerIcons, UnAuthOnlyRoute,
|
||||
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
|
||||
} from './common-components';
|
||||
import configureStore from './data/configureStore';
|
||||
import {
|
||||
@@ -15,14 +15,18 @@ import {
|
||||
PAGE_NOT_FOUND,
|
||||
PASSWORD_RESET_CONFIRM,
|
||||
RECOMMENDATIONS,
|
||||
REGISTER_EMBEDDED_PAGE,
|
||||
REGISTER_PAGE,
|
||||
RESET_PAGE,
|
||||
} from './data/constants';
|
||||
import { updatePathWithQueryParams } from './data/utils';
|
||||
import ForgotPasswordPage from './forgot-password';
|
||||
import { ForgotPasswordPage } from './forgot-password';
|
||||
import Logistration from './logistration/Logistration';
|
||||
import { ProgressiveProfiling } from './progressive-profiling';
|
||||
import RecommendationsPage from './recommendations';
|
||||
import ResetPasswordPage from './reset-password';
|
||||
import { RecommendationsPage } from './recommendations';
|
||||
import { RegistrationPage } from './register';
|
||||
import { ResetPasswordPage } from './reset-password';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
registerIcons();
|
||||
@@ -32,21 +36,27 @@ const MainApp = () => (
|
||||
<Helmet>
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
</Helmet>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Redirect to={updatePathWithQueryParams(REGISTER_PAGE)} />
|
||||
</Route>
|
||||
<UnAuthOnlyRoute exact path={LOGIN_PAGE} render={() => <Logistration selectedPage={LOGIN_PAGE} />} />
|
||||
<UnAuthOnlyRoute exact path={REGISTER_PAGE} component={Logistration} />
|
||||
<UnAuthOnlyRoute exact path={RESET_PAGE} component={ForgotPasswordPage} />
|
||||
<Route exact path={PASSWORD_RESET_CONFIRM} component={ResetPasswordPage} />
|
||||
<Route exact path={AUTHN_PROGRESSIVE_PROFILING} component={ProgressiveProfiling} />
|
||||
<Route exact path={RECOMMENDATIONS} component={RecommendationsPage} />
|
||||
<Route path={PAGE_NOT_FOUND} component={NotFoundPage} />
|
||||
<Route path="*">
|
||||
<Redirect to={PAGE_NOT_FOUND} />
|
||||
</Route>
|
||||
</Switch>
|
||||
{getConfig().ZENDESK_KEY && <Zendesk />}
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
|
||||
<Route
|
||||
path={REGISTER_EMBEDDED_PAGE}
|
||||
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={LOGIN_PAGE}
|
||||
element={
|
||||
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
|
||||
}
|
||||
/>
|
||||
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
|
||||
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
|
||||
<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>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthLargeLayout = ({ intl, username }) => (
|
||||
<div className="w-50 d-flex">
|
||||
<div className="col-md-10 bg-light-200 p-0">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="min-vh-100 d-flex align-items-center">
|
||||
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
|
||||
<div>
|
||||
<h1 className="welcome-to-platform data-hj-suppress">
|
||||
{intl.formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="complete-your-profile">
|
||||
{intl.formatMessage(messages['complete.your.profile.1'])}
|
||||
<div className="text-accent-a">
|
||||
{intl.formatMessage(messages['complete.your.profile.2'])}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 bg-white p-0">
|
||||
<svg className="m1-n1 w-100 h-100 large-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(171.6)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
AuthLargeLayout.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AuthLargeLayout);
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthMediumLayout = ({ intl, username }) => (
|
||||
<>
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
<div className="w-100 p-0 mb-3 d-flex">
|
||||
<div className="col-md-10 bg-light-200">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center justify-content-center mb-4 ml-5">
|
||||
<div className="medium-yellow-line mt-5 mr-n2" />
|
||||
<div>
|
||||
<h1 className="h3 data-hj-suppress mw-320">
|
||||
{intl.formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="display-1">
|
||||
{intl.formatMessage(messages['complete.your.profile.1'])}
|
||||
<div className="text-accent-a">
|
||||
{intl.formatMessage(messages['complete.your.profile.2'])}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 bg-white p-0">
|
||||
<svg className="w-100 h-100 medium-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(168)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
AuthMediumLayout.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AuthMediumLayout);
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthSmallLayout = ({ intl, username }) => (
|
||||
<div className="min-vw-100 bg-light-200">
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
|
||||
<div className="small-yellow-line mt-4.5" />
|
||||
<div>
|
||||
<h1 className="h5 data-hj-suppress">
|
||||
{intl.formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="h1">
|
||||
{intl.formatMessage(messages['complete.your.profile.1'])}
|
||||
<div className="text-accent-a">
|
||||
{intl.formatMessage(messages['complete.your.profile.2'])}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
AuthSmallLayout.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AuthSmallLayout);
|
||||
@@ -1,55 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { getLocale } from '@edx/frontend-platform/i18n';
|
||||
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 (
|
||||
<>
|
||||
{getConfig().ENABLE_COOKIE_POLICY_BANNER ? <CookiePolicyBanner languageCode={getLocale()} /> : null}
|
||||
<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,46 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const LargeLayout = ({ intl }) => (
|
||||
<div className="w-50 d-flex">
|
||||
<div className="col-md-9 bg-primary-400">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="min-vh-100 d-flex align-items-center">
|
||||
<div className={classNames({ 'large-yellow-line mr-n4.5': getConfig().SITE_NAME === 'edX' })} />
|
||||
<h1
|
||||
className={classNames(
|
||||
'display-2 text-white mw-xs',
|
||||
{ 'ml-6': getConfig().SITE_NAME !== 'edX' },
|
||||
)}
|
||||
>
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<div className="text-accent-a">
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3 bg-white p-0">
|
||||
<svg className="ml-n1 w-100 h-100 large-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(171.6)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
LargeLayout.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(LargeLayout);
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const MediumLayout = ({ intl }) => (
|
||||
<>
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
<div className="w-100 p-0 mb-3 d-flex">
|
||||
<div className="col-md-10 bg-primary-400">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center justify-content-center mb-4 ">
|
||||
<div className={classNames({ 'mt-1 medium-yellow-line': getConfig().SITE_NAME === 'edX' })} />
|
||||
<div>
|
||||
<h1
|
||||
className={classNames(
|
||||
'display-1 text-white mt-5 mb-5 mr-2',
|
||||
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
|
||||
)}
|
||||
>
|
||||
<span className="mr-2">{intl.formatMessage(messages['start.learning'])}</span>
|
||||
<span className="text-accent-a d-inline-block">
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 bg-white p-0">
|
||||
<svg className="w-100 h-100 medium-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(168)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
MediumLayout.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(MediumLayout);
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const SmallLayout = ({ intl }) => (
|
||||
<span className="bg-primary-400 w-100">
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
<div>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
|
||||
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} />
|
||||
<h1
|
||||
className={classNames(
|
||||
'text-white mt-3.5 mb-3.5',
|
||||
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
|
||||
)}
|
||||
>
|
||||
<span className="mr-1">{intl.formatMessage(messages['start.learning'])}</span>
|
||||
<span className="text-accent-a d-inline-block">
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
|
||||
SmallLayout.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(SmallLayout);
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './BaseComponent';
|
||||
@@ -3,16 +3,14 @@ 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';
|
||||
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index';
|
||||
|
||||
describe('ScreenLayout', () => {
|
||||
it('should display the form, pass as a child in SmallScreenLayout', () => {
|
||||
describe('Default Layout tests', () => {
|
||||
it('should display the form passed as a child in SmallScreenLayout', () => {
|
||||
const smallScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<SmallLayout />
|
||||
<DefaultSmallLayout />
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
@@ -22,11 +20,11 @@ describe('ScreenLayout', () => {
|
||||
expect(smallScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should display the form, pass as a child in MediumScreenLayout', () => {
|
||||
it('should display the form passed as a child in MediumScreenLayout', () => {
|
||||
const mediumScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<MediumLayout />
|
||||
<DefaultMediumLayout />
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
@@ -36,11 +34,11 @@ describe('ScreenLayout', () => {
|
||||
expect(mediumScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should display the form, pass as a child in LargeScreenLayout', () => {
|
||||
it('should display the form passed as a child in LargeScreenLayout', () => {
|
||||
const largeScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<LargeLayout />
|
||||
<DefaultLargeLayout />
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
45
src/base-container/components/default-layout/LargeLayout.jsx
Normal file
45
src/base-container/components/default-layout/LargeLayout.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const LargeLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div className="w-50 d-flex">
|
||||
<div className="col-md-9 bg-primary-400">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="min-vh-100 d-flex align-items-center">
|
||||
<div className={classNames({ 'large-yellow-line mr-n4.5': getConfig().SITE_NAME === 'edX' })} />
|
||||
<h1
|
||||
className={classNames(
|
||||
'display-2 text-white mw-xs',
|
||||
{ 'ml-6': getConfig().SITE_NAME !== 'edX' },
|
||||
)}
|
||||
>
|
||||
{formatMessage(messages['start.learning'])}
|
||||
<div className="text-accent-a">
|
||||
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3 bg-white p-0">
|
||||
<svg className="ml-n1 w-100 h-100 large-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(171.6)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LargeLayout;
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const MediumLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
<div className="w-100 p-0 mb-3 d-flex">
|
||||
<div className="col-md-10 bg-primary-400">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center justify-content-center mb-4 ">
|
||||
<div className={classNames({ 'mt-1 medium-yellow-line': getConfig().SITE_NAME === 'edX' })} />
|
||||
<div>
|
||||
<h1
|
||||
className={classNames(
|
||||
'display-1 text-white mt-5 mb-5 mr-2 main-heading',
|
||||
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
{formatMessage(messages['start.learning'])}{' '}
|
||||
<span className="text-accent-a d-inline-block">
|
||||
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 bg-white p-0">
|
||||
<svg className="w-100 h-100 medium-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(168)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MediumLayout;
|
||||
40
src/base-container/components/default-layout/SmallLayout.jsx
Normal file
40
src/base-container/components/default-layout/SmallLayout.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const SmallLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<span className="bg-primary-400 w-100">
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
<div>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center m-3.5">
|
||||
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} />
|
||||
<h1
|
||||
className={classNames(
|
||||
'text-white mt-3.5 mb-3.5',
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
{formatMessage(messages['start.learning'])}{' '}
|
||||
<span className="text-accent-a d-inline-block">
|
||||
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallLayout;
|
||||
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 '@edx/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 '@edx/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 '@edx/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 '@edx/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;
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const LargeLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div className="w-50 d-flex">
|
||||
<div className="col-md-10 bg-light-200 p-0">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="min-vh-100 d-flex align-items-center">
|
||||
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
|
||||
<div>
|
||||
<h1 className="welcome-to-platform data-hj-suppress">
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="complete-your-profile">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
<div className="text-accent-a">
|
||||
{formatMessage(messages['complete.your.profile.2'])}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 bg-white p-0">
|
||||
<svg className="m1-n1 w-100 h-100 large-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(171.6)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LargeLayout.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default LargeLayout;
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const MediumLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
<div className="w-100 p-0 mb-3 d-flex">
|
||||
<div className="col-md-10 bg-light-200">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center justify-content-center mb-4 ml-5">
|
||||
<div className="medium-yellow-line mt-5 mr-n2" />
|
||||
<div>
|
||||
<h1 className="h3 data-hj-suppress mw-320">
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="display-1">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
<div className="text-accent-a">
|
||||
{formatMessage(messages['complete.your.profile.2'])}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 bg-white p-0">
|
||||
<svg className="w-100 h-100 medium-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
|
||||
<g transform="skewX(168)">
|
||||
<rect x="0" y="0" height="100%" width="100%" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MediumLayout.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default MediumLayout;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const SmallLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div className="min-vw-100 bg-light-200">
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center m-3.5">
|
||||
<div className="small-yellow-line mt-4.5" />
|
||||
<div>
|
||||
<h1 className="h5 data-hj-suppress">
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="h1">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
<div className="text-accent-a">
|
||||
{formatMessage(messages['complete.your.profile.2'])}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SmallLayout.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
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';
|
||||
|
||||
const messages = defineMessages({
|
||||
'start.learning': {
|
||||
id: 'start.learning',
|
||||
defaultMessage: 'Start learning',
|
||||
description: 'Header text for logistration MFE pages',
|
||||
'welcome.to.platform': {
|
||||
id: 'welcome.to.platform',
|
||||
defaultMessage: 'Welcome to {siteName}, {username}!',
|
||||
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': {
|
||||
id: 'complete.your.profile.1',
|
||||
defaultMessage: 'Complete',
|
||||
@@ -22,11 +16,6 @@ const messages = defineMessages({
|
||||
defaultMessage: '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;
|
||||
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 };
|
||||
87
src/base-container/index.jsx
Normal file
87
src/base-container/index.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { breakpoints } from '@edx/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, username }) => {
|
||||
const [baseContainerVersion, setBaseContainerVersion] = useState(DEFAULT_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) {
|
||||
return (
|
||||
<div className="layout">
|
||||
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <ImageExtraSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <ImageSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthMediumLayout username={username} /> : <ImageMediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||
{showWelcomeBanner ? <AuthLargeLayout username={username} /> : <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 username={username} /> : <DefaultSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthMediumLayout username={username} /> : <DefaultMediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||
{showWelcomeBanner ? <AuthLargeLayout username={username} /> : <DefaultLargeLayout />}
|
||||
</MediaQuery>
|
||||
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
BaseContainer.defaultProps = {
|
||||
showWelcomeBanner: false,
|
||||
username: null,
|
||||
};
|
||||
|
||||
BaseContainer.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
showWelcomeBanner: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
};
|
||||
|
||||
export default BaseContainer;
|
||||
43
src/base-container/tests/BaseContainer.test.jsx
Normal file
43
src/base-container/tests/BaseContainer.test.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
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 should default layout', () => {
|
||||
const baseContainer = mount(
|
||||
<IntlProvider locale="en">
|
||||
<BaseContainer />
|
||||
</IntlProvider>,
|
||||
LargeScreen,
|
||||
);
|
||||
|
||||
expect(baseContainer.find('.banner__image').exists()).toBeFalsy();
|
||||
expect(baseContainer.find('.large-screen-svg-primary').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('[experiment] should show image layout for treatment group', () => {
|
||||
window.experiments = {
|
||||
rebrandExperiment: {
|
||||
variation: 'image-layout',
|
||||
},
|
||||
};
|
||||
|
||||
const baseContainer = mount(
|
||||
<IntlProvider locale="en">
|
||||
<BaseContainer />
|
||||
</IntlProvider>,
|
||||
LargeScreen,
|
||||
);
|
||||
|
||||
expect(baseContainer.find('.banner__image').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
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;
|
||||
@@ -1,23 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Button, Form,
|
||||
Icon,
|
||||
} from '@edx/paragon';
|
||||
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Login } from '@edx/paragon/icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
|
||||
/**
|
||||
* This component renders the Single sign-on (SSO) button only for the tpa provider passed
|
||||
* */
|
||||
const EnterpriseSSO = (props) => {
|
||||
const { intl } = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const tpaProvider = props.provider;
|
||||
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false;
|
||||
|
||||
const handleSubmit = (e, url) => {
|
||||
e.preventDefault();
|
||||
@@ -35,7 +37,7 @@ const EnterpriseSSO = (props) => {
|
||||
<div className="d-flex flex-column">
|
||||
<div className="mw-450">
|
||||
<Form className="m-0">
|
||||
<p>{intl.formatMessage(messages['enterprisetpa.title.heading'], { providerName: tpaProvider.name })}</p>
|
||||
<p>{formatMessage(messages['enterprisetpa.title.heading'], { providerName: tpaProvider.name })}</p>
|
||||
<Button
|
||||
id={tpaProvider.id}
|
||||
key={tpaProvider.id}
|
||||
@@ -46,16 +48,18 @@ const EnterpriseSSO = (props) => {
|
||||
>
|
||||
{tpaProvider.iconImage ? (
|
||||
<div aria-hidden="true">
|
||||
<img className="icon-image" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
|
||||
<img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
|
||||
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className="font-container" aria-hidden="true">
|
||||
<FontAwesomeIcon
|
||||
icon={SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? ['fab', tpaProvider.iconClass] : faSignInAlt}
|
||||
/>
|
||||
<div className="btn-tpa__font-container" aria-hidden="true">
|
||||
{SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? (
|
||||
<FontAwesomeIcon icon={['fab', tpaProvider.iconClass]} />)
|
||||
: (
|
||||
<Icon className="h-75" src={Login} />
|
||||
)}
|
||||
</div>
|
||||
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
|
||||
</>
|
||||
@@ -64,12 +68,15 @@ const EnterpriseSSO = (props) => {
|
||||
<div className="mb-4" />
|
||||
<Button
|
||||
type="submit"
|
||||
id="other-ways-to-sign-in"
|
||||
variant="outline-primary"
|
||||
state="Complete"
|
||||
className="w-100"
|
||||
onClick={(e) => handleClick(e)}
|
||||
>
|
||||
{intl.formatMessage(messages['enterprisetpa.login.button.text'])}
|
||||
{disablePublicAccountCreation
|
||||
? formatMessage(messages['enterprisetpa.login.button.text.public.account.creation.disabled'])
|
||||
: formatMessage(messages['enterprisetpa.login.button.text'])}
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
@@ -100,7 +107,6 @@ EnterpriseSSO.propTypes = {
|
||||
loginUrl: PropTypes.string,
|
||||
registerUrl: PropTypes.string,
|
||||
}),
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(EnterpriseSSO);
|
||||
export default EnterpriseSSO;
|
||||
|
||||
@@ -27,7 +27,7 @@ const FormGroup = (props) => {
|
||||
readOnly={props.readOnly}
|
||||
type={props.type}
|
||||
aria-invalid={props.errorMessage !== ''}
|
||||
className="form-field"
|
||||
className="form-group__form-field"
|
||||
autoComplete={props.autoComplete}
|
||||
spellCheck={props.spellCheck}
|
||||
name={props.name}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Hyperlink, Icon } from '@edx/paragon';
|
||||
import { Institution } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -32,8 +32,8 @@ export const RenderInstitutionButton = props => {
|
||||
* */
|
||||
const InstitutionLogistration = props => {
|
||||
const lmsBaseUrl = getConfig().LMS_BASE_URL;
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
intl,
|
||||
secondaryProviders,
|
||||
headingTitle,
|
||||
} = props;
|
||||
@@ -42,11 +42,11 @@ const InstitutionLogistration = props => {
|
||||
<>
|
||||
<div className="d-flex justify-content-left mb-4 mt-2">
|
||||
<div className="flex-column">
|
||||
<h4 className="mb-2 font-weight-bold institute-heading">
|
||||
<h4 className="mb-2 font-weight-bold institutions__heading">
|
||||
{headingTitle}
|
||||
</h4>
|
||||
<p className="mb-2">
|
||||
{intl.formatMessage(messages['institution.login.page.sub.heading'])}
|
||||
{formatMessage(messages['institution.login.page.sub.heading'])}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@ const InstitutionLogistration = props => {
|
||||
<tr key={provider} className="pgn__data-table-row">
|
||||
<td>
|
||||
<Hyperlink
|
||||
className="btn nav-item p-0 mb-1 secondary-provider-link"
|
||||
className="btn nav-item p-0 mb-1 institutions--provider-link"
|
||||
destination={lmsBaseUrl + provider.loginUrl}
|
||||
>
|
||||
{provider.name}
|
||||
@@ -95,7 +95,6 @@ RenderInstitutionButton.defaultProps = {
|
||||
|
||||
InstitutionLogistration.propTypes = {
|
||||
...LogistrationProps,
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
headingTitle: PropTypes.string,
|
||||
};
|
||||
InstitutionLogistration.defaultProps = {
|
||||
@@ -103,4 +102,4 @@ InstitutionLogistration.defaultProps = {
|
||||
headingTitle: '',
|
||||
};
|
||||
|
||||
export default injectIntl(InstitutionLogistration);
|
||||
export default InstitutionLogistration;
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthService } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Icon,
|
||||
Tab,
|
||||
Tabs,
|
||||
} from '@edx/paragon';
|
||||
import { ChevronLeft } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import BaseComponent from '../base-component';
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
import { getTpaHint, getTpaProvider, updatePathWithQueryParams } from '../data/utils';
|
||||
import { LoginPage } from '../login';
|
||||
import { RegistrationPage } from '../register';
|
||||
import { backupRegistrationForm } from '../register/data/actions';
|
||||
import {
|
||||
tpaProvidersSelector,
|
||||
} from './data/selectors';
|
||||
import messages from './messages';
|
||||
|
||||
const Logistration = (props) => {
|
||||
const { intl, selectedPage, tpaProviders } = props;
|
||||
const tpaHint = getTpaHint();
|
||||
const {
|
||||
providers, secondaryProviders,
|
||||
} = tpaProviders;
|
||||
const [institutionLogin, setInstitutionLogin] = useState(false);
|
||||
const [key, setKey] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const authService = getAuthService();
|
||||
if (authService) {
|
||||
authService.getCsrfTokenService().getCsrfToken(getConfig().LMS_BASE_URL);
|
||||
}
|
||||
});
|
||||
|
||||
const handleInstitutionLogin = (e) => {
|
||||
sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
if (typeof e === 'string') {
|
||||
sendPageEvent('login_and_registration', e === '/login' ? 'login' : 'register');
|
||||
} else {
|
||||
sendPageEvent('login_and_registration', e.target.dataset.eventName);
|
||||
}
|
||||
|
||||
setInstitutionLogin(!institutionLogin);
|
||||
};
|
||||
|
||||
const handleOnSelect = (tabKey) => {
|
||||
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
|
||||
if (tabKey === LOGIN_PAGE) {
|
||||
props.backupRegistrationForm();
|
||||
}
|
||||
setKey(tabKey);
|
||||
};
|
||||
|
||||
const tabTitle = (
|
||||
<div className="d-flex">
|
||||
<Icon src={ChevronLeft} className="left-icon" />
|
||||
<span className="ml-2">
|
||||
{selectedPage === LOGIN_PAGE
|
||||
? intl.formatMessage(messages['logistration.sign.in'])
|
||||
: intl.formatMessage(messages['logistration.register'])}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const isValidTpaHint = () => {
|
||||
const { provider } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
||||
return !!provider;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseComponent>
|
||||
<div>
|
||||
{institutionLogin
|
||||
? (
|
||||
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
|
||||
<Tab title={tabTitle} eventKey={selectedPage === LOGIN_PAGE ? LOGIN_PAGE : REGISTER_PAGE} />
|
||||
</Tabs>
|
||||
)
|
||||
: (!isValidTpaHint() && (
|
||||
<>
|
||||
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={handleOnSelect}>
|
||||
<Tab title={intl.formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
|
||||
<Tab title={intl.formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
|
||||
</Tabs>
|
||||
</>
|
||||
))}
|
||||
{ key && (
|
||||
<Redirect to={updatePathWithQueryParams(key)} />
|
||||
)}
|
||||
<div id="main-content" className="main-content">
|
||||
{selectedPage === LOGIN_PAGE
|
||||
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
|
||||
: <RegistrationPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />}
|
||||
</div>
|
||||
</div>
|
||||
</BaseComponent>
|
||||
);
|
||||
};
|
||||
|
||||
Logistration.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
selectedPage: PropTypes.string,
|
||||
backupRegistrationForm: PropTypes.func.isRequired,
|
||||
tpaProviders: PropTypes.shape({
|
||||
providers: PropTypes.array,
|
||||
secondaryProviders: PropTypes.array,
|
||||
}),
|
||||
};
|
||||
|
||||
Logistration.defaultProps = {
|
||||
tpaProviders: {
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
},
|
||||
};
|
||||
|
||||
Logistration.defaultProps = {
|
||||
selectedPage: REGISTER_PAGE,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
tpaProviders: tpaProvidersSelector(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
backupRegistrationForm,
|
||||
},
|
||||
)(injectIntl(Logistration));
|
||||
@@ -2,16 +2,16 @@ import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p className="my-0 py-5 text-muted mw-32em">
|
||||
<FormattedMessage
|
||||
id="error.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const NotFoundPage = () => (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p className="my-0 py-5 text-muted mw-32em">
|
||||
<FormattedMessage
|
||||
id="error.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NotFoundPage;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
|
||||
} from '@edx/paragon';
|
||||
@@ -9,33 +10,94 @@ import {
|
||||
} from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
|
||||
import messages from './messages';
|
||||
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 { formatMessage } = props.intl;
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const validationApiRateLimited = useSelector(state => state.register.validationApiRateLimited);
|
||||
const [isPasswordHidden, setHiddenTrue, setHiddenFalse] = useToggle(true);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
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);
|
||||
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) => {
|
||||
if (e.target?.name === 'passwordIcon') {
|
||||
return; // Do not clear error on password icon focus
|
||||
}
|
||||
|
||||
if (props.handleFocus) {
|
||||
props.handleFocus(e);
|
||||
}
|
||||
if (props.handleErrorChange) {
|
||||
props.handleErrorChange('password', '');
|
||||
dispatch(clearRegistrationBackendError('password'));
|
||||
}
|
||||
setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
|
||||
};
|
||||
|
||||
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 = (
|
||||
<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 tooltip = (
|
||||
<Tooltip id={`password-requirement-${placement}`}>
|
||||
@@ -59,7 +121,7 @@ const PasswordField = (props) => {
|
||||
<OverlayTrigger key="tooltip" placement={placement} overlay={tooltip} show={showTooltip}>
|
||||
<Form.Control
|
||||
as="input"
|
||||
className="form-field"
|
||||
className="form-group__form-field"
|
||||
type={isPasswordHidden ? 'password' : 'text'}
|
||||
name={props.name}
|
||||
value={props.value}
|
||||
@@ -89,6 +151,7 @@ PasswordField.defaultProps = {
|
||||
handleBlur: null,
|
||||
handleFocus: null,
|
||||
handleChange: () => {},
|
||||
handleErrorChange: null,
|
||||
showRequirements: true,
|
||||
autoComplete: null,
|
||||
};
|
||||
@@ -100,11 +163,11 @@ PasswordField.propTypes = {
|
||||
handleBlur: PropTypes.func,
|
||||
handleFocus: PropTypes.func,
|
||||
handleChange: PropTypes.func,
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
handleErrorChange: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
showRequirements: PropTypes.bool,
|
||||
value: PropTypes.string.isRequired,
|
||||
autoComplete: PropTypes.string,
|
||||
};
|
||||
|
||||
export default injectIntl(PasswordField);
|
||||
export default PasswordField;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
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';
|
||||
|
||||
function RedirectLogistration(props) {
|
||||
const RedirectLogistration = (props) => {
|
||||
const {
|
||||
authenticatedUser,
|
||||
finishAuthUrl,
|
||||
redirectUrl,
|
||||
redirectToProgressiveProfilingPage,
|
||||
@@ -17,6 +18,8 @@ function RedirectLogistration(props) {
|
||||
redirectToRecommendationsPage,
|
||||
educationLevel,
|
||||
userId,
|
||||
registrationEmbedded,
|
||||
host,
|
||||
} = props;
|
||||
let finalRedirectUrl = '';
|
||||
|
||||
@@ -35,15 +38,24 @@ function RedirectLogistration(props) {
|
||||
if (redirectToProgressiveProfilingPage) {
|
||||
// TODO: Do we still need this cookie?
|
||||
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 };
|
||||
return (
|
||||
<Redirect to={{
|
||||
pathname: AUTHN_PROGRESSIVE_PROFILING,
|
||||
state: {
|
||||
<Navigate
|
||||
to={AUTHN_PROGRESSIVE_PROFILING}
|
||||
state={{
|
||||
registrationResult,
|
||||
optionalFields,
|
||||
},
|
||||
}}
|
||||
authenticatedUser,
|
||||
}}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -52,24 +64,26 @@ function RedirectLogistration(props) {
|
||||
if (redirectToRecommendationsPage) {
|
||||
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
||||
return (
|
||||
<Redirect to={{
|
||||
pathname: RECOMMENDATIONS,
|
||||
state: {
|
||||
<Navigate
|
||||
to={RECOMMENDATIONS}
|
||||
state={{
|
||||
registrationResult,
|
||||
educationLevel,
|
||||
userId,
|
||||
},
|
||||
}}
|
||||
}}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
window.location.href = finalRedirectUrl;
|
||||
}
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
RedirectLogistration.defaultProps = {
|
||||
authenticatedUser: {},
|
||||
educationLevel: null,
|
||||
finishAuthUrl: null,
|
||||
success: false,
|
||||
@@ -78,9 +92,12 @@ RedirectLogistration.defaultProps = {
|
||||
optionalFields: {},
|
||||
redirectToRecommendationsPage: false,
|
||||
userId: null,
|
||||
registrationEmbedded: false,
|
||||
host: '',
|
||||
};
|
||||
|
||||
RedirectLogistration.propTypes = {
|
||||
authenticatedUser: PropTypes.shape({}),
|
||||
educationLevel: PropTypes.string,
|
||||
finishAuthUrl: PropTypes.string,
|
||||
success: PropTypes.bool,
|
||||
@@ -89,6 +106,8 @@ RedirectLogistration.propTypes = {
|
||||
optionalFields: PropTypes.shape({}),
|
||||
redirectToRecommendationsPage: PropTypes.bool,
|
||||
userId: PropTypes.number,
|
||||
registrationEmbedded: PropTypes.bool,
|
||||
host: PropTypes.string,
|
||||
};
|
||||
|
||||
export default RedirectLogistration;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
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 PropTypes from 'prop-types';
|
||||
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
|
||||
function SocialAuthProviders(props) {
|
||||
const { intl, referrer, socialAuthProviders } = props;
|
||||
const SocialAuthProviders = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { referrer, socialAuthProviders } = props;
|
||||
|
||||
function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
@@ -30,29 +32,30 @@ function SocialAuthProviders(props) {
|
||||
>
|
||||
{provider.iconImage ? (
|
||||
<div aria-hidden="true">
|
||||
<img className="icon-image" src={provider.iconImage} alt={`icon ${provider.name}`} />
|
||||
<img className="btn-tpa__image-icon" src={provider.iconImage} alt={`icon ${provider.name}`} />
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className="font-container" aria-hidden="true">
|
||||
<FontAwesomeIcon
|
||||
icon={SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? ['fab', provider.iconClass] : faSignInAlt}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className="btn-tpa__font-container" aria-hidden="true">
|
||||
{SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? (
|
||||
<FontAwesomeIcon icon={['fab', provider.iconClass]} />)
|
||||
: (
|
||||
<Icon className="h-75" src={Login} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span id="provider-name" className="notranslate mr-auto pl-2" aria-hidden="true">{provider.name}</span>
|
||||
<span className="sr-only">
|
||||
{referrer === LOGIN_PAGE
|
||||
? intl.formatMessage(messages['sso.sign.in.with'], { providerName: provider.name })
|
||||
: intl.formatMessage(messages['sso.create.account.using'], { providerName: provider.name })}
|
||||
? formatMessage(messages['sso.sign.in.with'], { providerName: provider.name })
|
||||
: formatMessage(messages['sso.create.account.using'], { providerName: provider.name })}
|
||||
</span>
|
||||
</button>
|
||||
));
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{socialAuth}</>;
|
||||
}
|
||||
};
|
||||
|
||||
SocialAuthProviders.defaultProps = {
|
||||
referrer: LOGIN_PAGE,
|
||||
@@ -60,7 +63,6 @@ SocialAuthProviders.defaultProps = {
|
||||
};
|
||||
|
||||
SocialAuthProviders.propTypes = {
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
referrer: PropTypes.string,
|
||||
socialAuthProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
@@ -69,7 +71,8 @@ SocialAuthProviders.propTypes = {
|
||||
iconImage: PropTypes.string,
|
||||
loginUrl: PropTypes.string,
|
||||
registerUrl: PropTypes.string,
|
||||
skipRegistrationForm: PropTypes.bool,
|
||||
})),
|
||||
};
|
||||
|
||||
export default injectIntl(SocialAuthProviders);
|
||||
export default SocialAuthProviders;
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
|
||||
const ThirdPartyAuthAlert = (props) => {
|
||||
const { currentProvider, intl, referrer } = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const { currentProvider, referrer } = props;
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
let message;
|
||||
|
||||
if (referrer === LOGIN_PAGE) {
|
||||
message = intl.formatMessage(messages['login.third.party.auth.account.not.linked'], { currentProvider, platformName });
|
||||
message = formatMessage(messages['login.third.party.auth.account.not.linked'], { currentProvider, platformName });
|
||||
} else {
|
||||
message = intl.formatMessage(messages['register.third.party.auth.account.not.linked'], { currentProvider, platformName });
|
||||
message = formatMessage(messages['register.third.party.auth.account.not.linked'], { currentProvider, platformName });
|
||||
}
|
||||
|
||||
if (!currentProvider) {
|
||||
@@ -25,14 +26,14 @@ const ThirdPartyAuthAlert = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert id="tpa-alert" className={referrer === REGISTER_PAGE ? 'alert-success mt-n2' : 'alert-warning mt-n2'}>
|
||||
<Alert id="tpa-alert" className={referrer === REGISTER_PAGE ? 'alert-success mt-n2 mb-5' : 'alert-warning mt-n2 mb-5'}>
|
||||
{referrer === REGISTER_PAGE ? (
|
||||
<Alert.Heading>{intl.formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
|
||||
<Alert.Heading>{formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
|
||||
) : null}
|
||||
<p>{ message }</p>
|
||||
</Alert>
|
||||
{referrer === REGISTER_PAGE ? (
|
||||
<h4 className="mt-4 mb-4">{intl.formatMessage(messages['registration.using.tpa.form.heading'])}</h4>
|
||||
<h4 className="mt-4 mb-4">{formatMessage(messages['registration.using.tpa.form.heading'])}</h4>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
@@ -45,8 +46,7 @@ ThirdPartyAuthAlert.defaultProps = {
|
||||
|
||||
ThirdPartyAuthAlert.propTypes = {
|
||||
currentProvider: PropTypes.string,
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
referrer: PropTypes.string,
|
||||
};
|
||||
|
||||
export default injectIntl(ThirdPartyAuthAlert);
|
||||
export default ThirdPartyAuthAlert;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
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
|
||||
* already authenticated.
|
||||
*/
|
||||
const UnAuthOnlyRoute = (props) => {
|
||||
const UnAuthOnlyRoute = ({ children }) => {
|
||||
const [authUser, setAuthUser] = useState({});
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
@@ -27,10 +29,14 @@ const UnAuthOnlyRoute = (props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Route {...props} />;
|
||||
return children;
|
||||
}
|
||||
|
||||
return <></>;
|
||||
return null;
|
||||
};
|
||||
|
||||
UnAuthOnlyRoute.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default UnAuthOnlyRoute;
|
||||
|
||||
61
src/common-components/Zendesk.jsx
Normal file
61
src/common-components/Zendesk.jsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import Zendesk from 'react-zendesk';
|
||||
|
||||
import messages from './messages';
|
||||
import { REGISTER_EMBEDDED_PAGE } from '../data/constants';
|
||||
|
||||
const ZendeskHelp = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const setting = {
|
||||
cookies: true,
|
||||
webWidget: {
|
||||
contactOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
chat: {
|
||||
suppress: false,
|
||||
departments: {
|
||||
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
|
||||
},
|
||||
},
|
||||
contactForm: {
|
||||
ticketForms: [
|
||||
{
|
||||
id: 360003368814,
|
||||
subject: false,
|
||||
fields: [{ id: 'description', prefill: { '*': '' } }],
|
||||
},
|
||||
],
|
||||
selectTicketForm: {
|
||||
'*': formatMessage(messages.selectTicketForm),
|
||||
},
|
||||
attachments: true,
|
||||
},
|
||||
helpCenter: {
|
||||
originalArticleButton: true,
|
||||
},
|
||||
answerBot: {
|
||||
suppress: false,
|
||||
contactOnlyAfterQuery: true,
|
||||
title: { '*': formatMessage(messages.supportTitle) },
|
||||
avatar: {
|
||||
url: getConfig().ZENDESK_LOGO_URL,
|
||||
name: { '*': formatMessage(messages.supportTitle) },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (window.location.pathname === REGISTER_EMBEDDED_PAGE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ZendeskHelp;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const THIRD_PARTY_AUTH_CONTEXT = new AsyncActionType('THIRD_PARTY_AUTH', 'GET_THIRD_PARTY_AUTH_CONTEXT');
|
||||
export const THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG = 'THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG';
|
||||
|
||||
// Third party auth context
|
||||
export const getThirdPartyAuthContext = (urlParams) => ({
|
||||
@@ -20,3 +21,7 @@ export const getThirdPartyAuthContextSuccess = (fieldDescriptions, optionalField
|
||||
export const getThirdPartyAuthContextFailure = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.FAILURE,
|
||||
});
|
||||
|
||||
export const clearThirdPartyAuthContextErrorMessage = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
|
||||
});
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT } from './actions';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
|
||||
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
|
||||
export const defaultState = {
|
||||
fieldDescriptions: {},
|
||||
optionalFields: {},
|
||||
fieldDescriptions: {
|
||||
fields: {},
|
||||
},
|
||||
optionalFields: {
|
||||
fields: {},
|
||||
extended_profile: [],
|
||||
},
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext: {
|
||||
autoSubmitRegForm: false,
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
countryCode: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: null,
|
||||
welcomePageRedirectUrl: null,
|
||||
},
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
const reducer = (state = defaultState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case THIRD_PARTY_AUTH_CONTEXT.BEGIN:
|
||||
return {
|
||||
@@ -34,7 +42,20 @@ const reducer = (state = defaultState, action) => {
|
||||
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthApiStatus: FAILURE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...state.thirdPartyAuthContext,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...state.thirdPartyAuthContext,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
|
||||
import {
|
||||
getThirdPartyAuthContextBegin,
|
||||
getThirdPartyAuthContextFailure,
|
||||
@@ -11,18 +10,17 @@ import {
|
||||
import {
|
||||
getThirdPartyAuthContext,
|
||||
} from './service';
|
||||
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
|
||||
|
||||
export function* fetchThirdPartyAuthContext(action) {
|
||||
try {
|
||||
yield put(getThirdPartyAuthContextBegin());
|
||||
const { fieldDescriptions, optionalFields, thirdPartyAuthContext } = yield call(
|
||||
getThirdPartyAuthContext, action.payload.urlParams,
|
||||
);
|
||||
const {
|
||||
fieldDescriptions, optionalFields, thirdPartyAuthContext,
|
||||
} = yield call(getThirdPartyAuthContext, action.payload.urlParams);
|
||||
|
||||
yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode));
|
||||
yield put(getThirdPartyAuthContextSuccess(
|
||||
fieldDescriptions, optionalFields, thirdPartyAuthContext,
|
||||
));
|
||||
yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext));
|
||||
} catch (e) {
|
||||
yield put(getThirdPartyAuthContextFailure());
|
||||
logError(e);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { camelCaseObject, convertKeyNames, getConfig } from '@edx/frontend-platform';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
@@ -18,10 +18,8 @@ export async function getThirdPartyAuthContext(urlParams) {
|
||||
throw (e);
|
||||
});
|
||||
return {
|
||||
fieldDescriptions: data.registration_fields || {},
|
||||
optionalFields: data.optional_fields || {},
|
||||
thirdPartyAuthContext: camelCaseObject(
|
||||
convertKeyNames(data.context_data, { fullname: 'name' }),
|
||||
),
|
||||
fieldDescriptions: data.registrationFields || {},
|
||||
optionalFields: data.optionalFields || {},
|
||||
thirdPartyAuthContext: data.contextData || {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { THIRD_PARTY_AUTH_CONTEXT } from '../actions';
|
||||
import { PENDING_STATE } from '../../../data/constants';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from '../actions';
|
||||
import reducer from '../reducers';
|
||||
|
||||
describe('common components reducer', () => {
|
||||
@@ -14,6 +15,7 @@ describe('common components reducer', () => {
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
const fieldDescriptions = {
|
||||
@@ -43,4 +45,38 @@ describe('common components reducer', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear tpa context error message', () => {
|
||||
const state = {
|
||||
fieldDescriptions: {},
|
||||
optionalFields: {},
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
countryCode: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: 'An error occurred',
|
||||
},
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...state.thirdPartyAuthContext,
|
||||
errorMessage: null,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as RedirectLogistration } from './RedirectLogistration';
|
||||
export { default as registerIcons } from './RegisterFaIcons';
|
||||
export { default as EmbeddedRegistrationRoute } from './EmbeddedRegistrationRoute';
|
||||
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
|
||||
export { default as NotFoundPage } from './NotFoundPage';
|
||||
export { default as SocialAuthProviders } from './SocialAuthProviders';
|
||||
@@ -11,4 +12,4 @@ export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
export { default as FormGroup } from './FormGroup';
|
||||
export { default as PasswordField } from './PasswordField';
|
||||
export { default as Logistration } from './Logistration';
|
||||
export { default as Zendesk } from './Zendesk';
|
||||
|
||||
@@ -29,6 +29,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Show me other ways to sign in or register',
|
||||
description: 'Button text for login',
|
||||
},
|
||||
'enterprisetpa.login.button.text.public.account.creation.disabled': {
|
||||
id: 'enterprisetpa.login.button.text.public.account.creation.disabled',
|
||||
defaultMessage: 'Show me other ways to sign in',
|
||||
description: 'Button text for login when account creation is disabled',
|
||||
},
|
||||
// social auth providers
|
||||
'sso.sign.in.with': {
|
||||
id: 'sso.sign.in.with',
|
||||
@@ -97,6 +102,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Finish creating your account',
|
||||
description: 'Heading that appears above form when user is trying to create account using social auth',
|
||||
},
|
||||
supportTitle: {
|
||||
id: 'zendesk.supportTitle',
|
||||
description: 'Title for the support button',
|
||||
defaultMessage: 'edX Support',
|
||||
},
|
||||
selectTicketForm: {
|
||||
id: 'zendesk.selectTicketForm',
|
||||
description: 'Select ticket form',
|
||||
defaultMessage: 'Please choose your request type:',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* eslint-disable import/no-import-module-exports */
|
||||
/* eslint-disable react/function-component-definition */
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { mount } from 'enzyme';
|
||||
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 () => {
|
||||
embeddedRegistrationPage = await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(embeddedRegistrationPage.find('span').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
embeddedRegistrationPage = await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(embeddedRegistrationPage.find('span').exists()).toBeTruthy();
|
||||
expect(embeddedRegistrationPage.find('span').text()).toBe('Embedded Register Page');
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
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 PasswordField from '../PasswordField';
|
||||
|
||||
@@ -26,10 +30,27 @@ describe('FormGroup', () => {
|
||||
});
|
||||
|
||||
describe('PasswordField', () => {
|
||||
const mockStore = configureStore();
|
||||
const IntlPasswordField = injectIntl(PasswordField);
|
||||
let props = {};
|
||||
let store = {};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</MemoryRouter>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
register: {
|
||||
validationApiRateLimited: false,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore(initialState);
|
||||
props = {
|
||||
floatingLabel: 'Password',
|
||||
name: 'password',
|
||||
@@ -39,7 +60,7 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should show/hide password on icon click', () => {
|
||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
||||
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('button[aria-label="Show password"]').simulate('click');
|
||||
expect(passwordField.find('input').prop('type')).toEqual('text');
|
||||
@@ -49,7 +70,7 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should show password requirement tooltip on focus', async () => {
|
||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
||||
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
jest.useFakeTimers();
|
||||
await act(async () => {
|
||||
passwordField.find('input').simulate('focus');
|
||||
@@ -67,7 +88,7 @@ describe('PasswordField', () => {
|
||||
};
|
||||
|
||||
jest.useFakeTimers();
|
||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
||||
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
await act(async () => {
|
||||
passwordField.find('input').simulate('focus');
|
||||
jest.runAllTimers();
|
||||
@@ -80,7 +101,7 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should update password requirement checks', async () => {
|
||||
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
|
||||
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
jest.useFakeTimers();
|
||||
await act(async () => {
|
||||
passwordField.find('input').simulate('focus');
|
||||
@@ -92,4 +113,142 @@ describe('PasswordField', () => {
|
||||
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
|
||||
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
|
||||
});
|
||||
|
||||
it('should not run validations when blur is fired on password icon click', () => {
|
||||
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('button[aria-label="Show password"]').simulate('blur', {
|
||||
target: {
|
||||
name: 'password',
|
||||
value: 'invalid',
|
||||
},
|
||||
relatedTarget: {
|
||||
name: 'passwordIcon',
|
||||
},
|
||||
});
|
||||
|
||||
expect(passwordField.find('div[feedback-for="password"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call props handle blur if available', () => {
|
||||
props = {
|
||||
...props,
|
||||
handleBlur: jest.fn(),
|
||||
};
|
||||
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('input#password').simulate('blur', {
|
||||
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 passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('input#password').simulate('blur', {
|
||||
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 passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('button[aria-label="Show password"]').simulate('focus', {
|
||||
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 passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('button[aria-label="Show password"]').simulate('focus', {
|
||||
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 passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('button[aria-label="Show password"]').simulate('blur', {
|
||||
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 passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
|
||||
|
||||
passwordField.find('button[aria-label="Show password"]').simulate('blur', {
|
||||
target: {
|
||||
name: 'passwordIcon',
|
||||
value: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
expect(props.handleBlur).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleBlur).toHaveBeenCalledWith({
|
||||
target: {
|
||||
name: 'password',
|
||||
value: 'testPassword',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
/* eslint-disable import/no-import-module-exports */
|
||||
/* eslint-disable react/function-component-definition */
|
||||
import React from 'react';
|
||||
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter, BrowserRouter as Router, Switch } from 'react-router-dom';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { UnAuthOnlyRoute } from '..';
|
||||
import { LOGIN_PAGE } from '../../data/constants';
|
||||
import { REGISTER_PAGE } from '../../data/constants';
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
import {
|
||||
MemoryRouter, Route, BrowserRouter as Router, Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(),
|
||||
fetchAuthenticatedUser: jest.fn(),
|
||||
}));
|
||||
|
||||
const RRD = require('react-router-dom');
|
||||
// Just render plain div with its children
|
||||
@@ -18,16 +27,16 @@ module.exports = RRD;
|
||||
const TestApp = () => (
|
||||
<Router>
|
||||
<div>
|
||||
<Switch>
|
||||
<UnAuthOnlyRoute path={LOGIN_PAGE} render={() => (<span>Login Page</span>)} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><span>Register Page</span></UnAuthOnlyRoute>} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
||||
describe('UnAuthOnlyRoute', () => {
|
||||
const routerWrapper = () => (
|
||||
<MemoryRouter initialEntries={[LOGIN_PAGE]}>
|
||||
<MemoryRouter initialEntries={[REGISTER_PAGE]}>
|
||||
<TestApp />
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -36,25 +45,30 @@ describe('UnAuthOnlyRoute', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should have called with forceRefresh true', () => {
|
||||
it('should have called with forceRefresh true', async () => {
|
||||
const user = {
|
||||
username: 'gonzo',
|
||||
other: 'data',
|
||||
};
|
||||
auth.getAuthenticatedUser = jest.fn(() => user);
|
||||
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
|
||||
|
||||
mount(routerWrapper());
|
||||
getAuthenticatedUser.mockReturnValue(user);
|
||||
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(user));
|
||||
|
||||
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
|
||||
await act(async () => {
|
||||
await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
|
||||
});
|
||||
|
||||
it('should have called with forceRefresh false', () => {
|
||||
auth.getAuthenticatedUser = jest.fn(() => null);
|
||||
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
|
||||
it('should have called with forceRefresh false', async () => {
|
||||
getAuthenticatedUser.mockReturnValue(null);
|
||||
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(null));
|
||||
|
||||
mount(routerWrapper());
|
||||
await act(async () => {
|
||||
await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
|
||||
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
|
||||
});
|
||||
});
|
||||
|
||||
17
src/common-components/tests/Zendesk.test.jsx
Normal file
17
src/common-components/tests/Zendesk.test.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import Zendesk from '../Zendesk';
|
||||
|
||||
jest.mock('react-zendesk', () => 'Zendesk');
|
||||
|
||||
describe('Zendesk Help', () => {
|
||||
it('should match login page third party auth alert message snapshot', () => {
|
||||
const tree = renderer.create(
|
||||
<IntlProvider locale="en">
|
||||
<Zendesk />
|
||||
</IntlProvider>,
|
||||
).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -10,25 +10,27 @@ exports[`SocialAuthProviders should match social auth provider with default icon
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="font-container"
|
||||
className="btn-tpa__font-container"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-right-to-bracket "
|
||||
data-icon="right-to-bracket"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<span
|
||||
className="pgn__icon h-75"
|
||||
>
|
||||
<path
|
||||
d="M352 96h64c17.7 0 32 14.3 32 32V384c0 17.7-14.3 32-32 32H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h64c53 0 96-43 96-96V128c0-53-43-96-96-96H352c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-7.5 177.4c4.8-4.5 7.5-10.8 7.5-17.4s-2.7-12.9-7.5-17.4l-144-136c-7-6.6-17.2-8.4-26-4.6s-14.5 12.5-14.5 22v72H32c-17.7 0-32 14.3-32 32v64c0 17.7 14.3 32 32 32H160v72c0 9.6 5.7 18.2 14.5 22s19 2 26-4.6l144-136z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11 7 9.6 8.4l2.6 2.6H2v2h10.2l-2.6 2.6L11 17l5-5-5-5zm9 12h-8v2h10V3H12v2h8v14z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -55,7 +57,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="font-container"
|
||||
className="btn-tpa__font-container"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -104,7 +106,7 @@ Array [
|
||||
>
|
||||
<img
|
||||
alt="icon Apple"
|
||||
className="icon-image"
|
||||
className="btn-tpa__image-icon"
|
||||
src="https://edx.devstack.lms/logo.png"
|
||||
/>
|
||||
</div>
|
||||
@@ -133,7 +135,7 @@ Array [
|
||||
>
|
||||
<img
|
||||
alt="icon Facebook"
|
||||
className="icon-image"
|
||||
className="btn-tpa__image-icon"
|
||||
src="https://edx.devstack.lms/facebook-logo.png"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`ThirdPartyAuthAlert should match login page third party auth alert message snapshot 1`] = `
|
||||
<div
|
||||
className="fade alert-content alert-warning mt-n2 alert show"
|
||||
className="fade alert-content alert-warning mt-n2 mb-5 alert show"
|
||||
id="tpa-alert"
|
||||
role="alert"
|
||||
>
|
||||
@@ -23,7 +23,7 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
|
||||
exports[`ThirdPartyAuthAlert should match register page third party auth alert message snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade alert-content alert-success mt-n2 alert show"
|
||||
className="fade alert-content alert-success mt-n2 mb-5 alert show"
|
||||
id="tpa-alert"
|
||||
role="alert"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Zendesk Help should match login page third party auth alert message snapshot 1`] = `
|
||||
<Zendesk
|
||||
cookies={true}
|
||||
defer={true}
|
||||
webWidget={
|
||||
Object {
|
||||
"answerBot": Object {
|
||||
"avatar": Object {
|
||||
"name": Object {
|
||||
"*": "edX Support",
|
||||
},
|
||||
"url": undefined,
|
||||
},
|
||||
"contactOnlyAfterQuery": true,
|
||||
"suppress": false,
|
||||
"title": Object {
|
||||
"*": "edX Support",
|
||||
},
|
||||
},
|
||||
"chat": Object {
|
||||
"departments": Object {
|
||||
"enabled": Array [
|
||||
"account settings",
|
||||
"billing and payments",
|
||||
"certificates",
|
||||
"deadlines",
|
||||
"errors and technical issues",
|
||||
"other",
|
||||
"proctoring",
|
||||
],
|
||||
},
|
||||
"suppress": false,
|
||||
},
|
||||
"contactForm": Object {
|
||||
"attachments": true,
|
||||
"selectTicketForm": Object {
|
||||
"*": "Please choose your request type:",
|
||||
},
|
||||
"ticketForms": Array [
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"id": "description",
|
||||
"prefill": Object {
|
||||
"*": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
"id": 360003368814,
|
||||
"subject": false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"contactOptions": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
"helpCenter": Object {
|
||||
"originalArticleButton": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -1,14 +1,12 @@
|
||||
const configuration = {
|
||||
// Cookies related configs
|
||||
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
|
||||
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
|
||||
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
|
||||
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
|
||||
// Features
|
||||
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
|
||||
ENABLE_COOKIE_POLICY_BANNER: process.env.ENABLE_COOKIE_POLICY_BANNER || 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_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 || '',
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
|
||||
// Links
|
||||
@@ -16,12 +14,24 @@ const configuration = {
|
||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_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,
|
||||
POST_REGISTRATION_REDIRECT_URL: process.env.POST_REGISTRATION_REDIRECT_URL || '',
|
||||
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_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 || '[]',
|
||||
// Miscellaneous
|
||||
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
||||
ZENDESK_KEY: process.env.ZENDESK_KEY,
|
||||
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;
|
||||
|
||||
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
|
||||
export const LOGIN_PAGE = '/login';
|
||||
export const REGISTER_PAGE = '/register';
|
||||
export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
|
||||
export const RESET_PAGE = '/reset';
|
||||
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
|
||||
export const DEFAULT_REDIRECT_URL = '/dashboard';
|
||||
@@ -23,16 +24,16 @@ export const PENDING_STATE = 'pending';
|
||||
export const COMPLETE_STATE = 'complete';
|
||||
export const FAILURE_STATE = 'failure';
|
||||
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]+)*'
|
||||
+ '|^"([\\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})'
|
||||
+ '|\\[(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
|
||||
// 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'];
|
||||
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;
|
||||
17
src/data/optimizely.js
Normal file
17
src/data/optimizely.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
createInstance,
|
||||
} from '@optimizely/react-sdk';
|
||||
|
||||
const OPTIMIZELY_SDK_KEY = process.env.OPTIMIZELY_FULL_STACK_SDK_KEY;
|
||||
|
||||
const getOptimizelyInstance = () => {
|
||||
if (OPTIMIZELY_SDK_KEY) {
|
||||
return createInstance({
|
||||
sdkKey: OPTIMIZELY_SDK_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default getOptimizelyInstance();
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LOGIN_PAGE } from '../constants';
|
||||
import { updatePathWithQueryParams } from './dataUtils';
|
||||
import { updatePathWithQueryParams } from '../utils/dataUtils';
|
||||
|
||||
describe('updatePathWithQueryParams', () => {
|
||||
it('should append query params into the path', () => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import AsyncActionType from './reduxUtils';
|
||||
import AsyncActionType from '../utils/reduxUtils';
|
||||
|
||||
describe('AsyncActionType', () => {
|
||||
it('should return well formatted action strings', () => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import Cookies from 'universal-cookie';
|
||||
|
||||
export function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||
export default function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||
const cookies = new Cookies();
|
||||
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
|
||||
if (cookieExpiry) {
|
||||
@@ -9,13 +9,3 @@ export function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
export const isHostAvailableInQueryParams = () => {
|
||||
const queryParams = getAllPossibleQueryParams();
|
||||
return 'host' in queryParams;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
export {
|
||||
getTpaProvider,
|
||||
getTpaHint,
|
||||
updatePathWithQueryParams,
|
||||
getAllPossibleQueryParams,
|
||||
getActivationStatus,
|
||||
isHostAvailableInQueryParams,
|
||||
updatePathWithQueryParams,
|
||||
windowScrollTo,
|
||||
} from './dataUtils';
|
||||
export { default as AsyncActionType } from './reduxUtils';
|
||||
export { default as setSurveyCookie, setCookie } from './cookies';
|
||||
export { default as setCookie } from './cookies';
|
||||
|
||||
@@ -142,6 +142,7 @@ FormFieldRenderer.propTypes = {
|
||||
type: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
||||
}).isRequired,
|
||||
onChangeHandler: PropTypes.func.isRequired,
|
||||
handleBlur: PropTypes.func,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { default } from './FieldRenderer';
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export { default as FormFieldRenderer } from './FieldRenderer';
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('FieldRendererTests', () => {
|
||||
type: 'select',
|
||||
label: 'Year of Birth',
|
||||
name: 'yob-field',
|
||||
options: [['1997', 1997], ['1998', 1998]],
|
||||
options: [['1997', '1997'], ['1998', '1998']],
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
import { CheckCircle, Error } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
import {
|
||||
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
|
||||
} from '../data/constants';
|
||||
import { PASSWORD_RESET } from '../reset-password/data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const ForgotPasswordAlert = (props) => {
|
||||
const { email, emailError, intl } = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const { email, emailError } = props;
|
||||
let message = '';
|
||||
let heading = intl.formatMessage(messages['forgot.password.error.alert.title']);
|
||||
let heading = formatMessage(messages['forgot.password.error.alert.title']);
|
||||
let { status } = props;
|
||||
|
||||
if (emailError) {
|
||||
@@ -24,7 +25,7 @@ const ForgotPasswordAlert = (props) => {
|
||||
|
||||
switch (status) {
|
||||
case COMPLETE_STATE:
|
||||
heading = intl.formatMessage(messages['confirmation.message.title']);
|
||||
heading = formatMessage(messages['confirmation.message.title']);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id="forgot.password.confirmation.message"
|
||||
@@ -36,7 +37,7 @@ const ForgotPasswordAlert = (props) => {
|
||||
email: <span className="data-hj-suppress">{email}</span>,
|
||||
supportLink: (
|
||||
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK} target="_blank">
|
||||
{intl.formatMessage(messages['confirmation.support.link'])}
|
||||
{formatMessage(messages['confirmation.support.link'])}
|
||||
</Alert.Link>
|
||||
),
|
||||
}}
|
||||
@@ -44,26 +45,26 @@ const ForgotPasswordAlert = (props) => {
|
||||
);
|
||||
break;
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
message = intl.formatMessage(messages['internal.server.error']);
|
||||
message = formatMessage(messages['internal.server.error']);
|
||||
break;
|
||||
case FORBIDDEN_STATE:
|
||||
heading = intl.formatMessage(messages['forgot.password.error.message.title']);
|
||||
message = intl.formatMessage(messages['forgot.password.request.in.progress.message']);
|
||||
heading = formatMessage(messages['forgot.password.error.message.title']);
|
||||
message = formatMessage(messages['forgot.password.request.in.progress.message']);
|
||||
break;
|
||||
case FORM_SUBMISSION_ERROR:
|
||||
message = intl.formatMessage(messages['extend.field.errors'], { emailError });
|
||||
message = formatMessage(messages['extend.field.errors'], { emailError });
|
||||
break;
|
||||
case PASSWORD_RESET.INVALID_TOKEN:
|
||||
heading = intl.formatMessage(messages['invalid.token.heading']);
|
||||
message = intl.formatMessage(messages['invalid.token.error.message']);
|
||||
heading = formatMessage(messages['invalid.token.heading']);
|
||||
message = formatMessage(messages['invalid.token.error.message']);
|
||||
break;
|
||||
case PASSWORD_RESET.FORBIDDEN_REQUEST:
|
||||
heading = intl.formatMessage(messages['token.validation.rate.limit.error.heading']);
|
||||
message = intl.formatMessage(messages['token.validation.rate.limit.error']);
|
||||
heading = formatMessage(messages['token.validation.rate.limit.error.heading']);
|
||||
message = formatMessage(messages['token.validation.rate.limit.error']);
|
||||
break;
|
||||
case PASSWORD_RESET.INTERNAL_SERVER_ERROR:
|
||||
heading = intl.formatMessage(messages['token.validation.internal.sever.error.heading']);
|
||||
message = intl.formatMessage(messages['token.validation.internal.sever.error']);
|
||||
heading = formatMessage(messages['token.validation.internal.sever.error.heading']);
|
||||
message = formatMessage(messages['token.validation.internal.sever.error']);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -93,8 +94,7 @@ ForgotPasswordAlert.defaultProps = {
|
||||
ForgotPasswordAlert.propTypes = {
|
||||
status: PropTypes.string.isRequired,
|
||||
email: PropTypes.string,
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
emailError: PropTypes.string,
|
||||
};
|
||||
|
||||
export default injectIntl(ForgotPasswordAlert);
|
||||
export default ForgotPasswordAlert;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Form,
|
||||
Hyperlink,
|
||||
@@ -15,29 +15,30 @@ import {
|
||||
import { ChevronLeft } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import BaseComponent from '../base-component';
|
||||
import { FormGroup } from '../common-components';
|
||||
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
|
||||
import { forgotPasswordResultSelector } from './data/selectors';
|
||||
import ForgotPasswordAlert from './ForgotPasswordAlert';
|
||||
import messages from './messages';
|
||||
import BaseContainer from '../base-container';
|
||||
import { FormGroup } from '../common-components';
|
||||
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||
|
||||
const ForgotPasswordPage = (props) => {
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
const {
|
||||
intl, status, submitState, emailValidationError,
|
||||
status, submitState, emailValidationError,
|
||||
} = props;
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
const [email, setEmail] = useState(props.email);
|
||||
const [bannerEmail, setBannerEmail] = useState('');
|
||||
const [formErrors, setFormErrors] = useState('');
|
||||
const [validationError, setValidationError] = useState(emailValidationError);
|
||||
const [key, setKey] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
sendPageEvent('login_and_registration', 'reset');
|
||||
@@ -58,9 +59,9 @@ const ForgotPasswordPage = (props) => {
|
||||
let error = '';
|
||||
|
||||
if (value === '') {
|
||||
error = intl.formatMessage(messages['forgot.password.empty.email.field.error']);
|
||||
error = formatMessage(messages['forgot.password.empty.email.field.error']);
|
||||
} else if (!emailRegex.test(value)) {
|
||||
error = intl.formatMessage(messages['forgot.password.page.invalid.email.message']);
|
||||
error = formatMessage(messages['forgot.password.page.invalid.email.message']);
|
||||
}
|
||||
|
||||
return error;
|
||||
@@ -89,35 +90,32 @@ const ForgotPasswordPage = (props) => {
|
||||
const tabTitle = (
|
||||
<div className="d-inline-flex flex-wrap align-items-center">
|
||||
<Icon src={ChevronLeft} />
|
||||
<span className="ml-2">{intl.formatMessage(messages['sign.in.text'])}</span>
|
||||
<span className="ml-2">{formatMessage(messages['sign.in.text'])}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseComponent>
|
||||
<BaseContainer>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['forgot.password.page.title'],
|
||||
<title>{formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<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} />
|
||||
</Tabs>
|
||||
{ key && (
|
||||
<Redirect to={updatePathWithQueryParams(key)} />
|
||||
)}
|
||||
<div id="main-content" className="main-content">
|
||||
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
|
||||
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
|
||||
<h2 className="h4">
|
||||
{intl.formatMessage(messages['forgot.password.page.heading'])}
|
||||
{formatMessage(messages['forgot.password.page.heading'])}
|
||||
</h2>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['forgot.password.page.instructions'])}
|
||||
{formatMessage(messages['forgot.password.page.instructions'])}
|
||||
</p>
|
||||
<FormGroup
|
||||
floatingLabel={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
floatingLabel={formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
name="email"
|
||||
value={email}
|
||||
autoComplete="on"
|
||||
@@ -125,17 +123,17 @@ const ForgotPasswordPage = (props) => {
|
||||
handleChange={(e) => setEmail(e.target.value)}
|
||||
handleBlur={handleBlur}
|
||||
handleFocus={handleFocus}
|
||||
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
|
||||
helpText={[formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
|
||||
/>
|
||||
<StatefulButton
|
||||
id="submit-forget-password"
|
||||
name="submit-forget-password"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="forgot-password-button-width"
|
||||
className="forgot-password--button"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
|
||||
default: formatMessage(messages['forgot.password.page.submit.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
@@ -150,11 +148,11 @@ const ForgotPasswordPage = (props) => {
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{intl.formatMessage(messages['need.help.sign.in.text'])}
|
||||
{formatMessage(messages['need.help.sign.in.text'])}
|
||||
</Hyperlink>
|
||||
)}
|
||||
<p className="mt-5.5 small text-gray-700">
|
||||
{intl.formatMessage(messages['additional.help.text'], { platformName })}
|
||||
{formatMessage(messages['additional.help.text'], { platformName })}
|
||||
<span>
|
||||
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
|
||||
</span>
|
||||
@@ -162,7 +160,7 @@ const ForgotPasswordPage = (props) => {
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</BaseComponent>
|
||||
</BaseContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -170,7 +168,6 @@ ForgotPasswordPage.propTypes = {
|
||||
email: PropTypes.string,
|
||||
emailValidationError: PropTypes.string,
|
||||
forgotPassword: PropTypes.func.isRequired,
|
||||
intl: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||
setForgotPasswordFormData: PropTypes.func.isRequired,
|
||||
status: PropTypes.string,
|
||||
submitState: PropTypes.string,
|
||||
@@ -189,4 +186,4 @@ export default connect(
|
||||
forgotPassword,
|
||||
setForgotPasswordFormData,
|
||||
},
|
||||
)(injectIntl(ForgotPasswordPage));
|
||||
)(ForgotPasswordPage);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
|
||||
import { INTERNAL_SERVER_ERROR, PENDING_STATE } from '../../data/constants';
|
||||
import { PASSWORD_RESET_FAILURE } from '../../reset-password/data/actions';
|
||||
import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
status: '',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { default } from './ForgotPasswordPage';
|
||||
export { default as ForgotPasswordPage } from './ForgotPasswordPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { FORGOT_PASSWORD } from './data/actions';
|
||||
export { default as saga } from './data/sagas';
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
|
||||
@@ -17,14 +13,20 @@ import { PASSWORD_RESET } from '../../reset-password/data/constants';
|
||||
import { setForgotPasswordFormData } from '../data/actions';
|
||||
import ForgotPasswordPage from '../ForgotPasswordPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
const mockedNavigator = jest.fn();
|
||||
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
sendTrackEvent: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...(jest.requireActual('react-router-dom')),
|
||||
useNavigate: () => mockedNavigator,
|
||||
}));
|
||||
|
||||
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
|
||||
const mockStore = configureStore();
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const initialState = {
|
||||
forgotPassword: {
|
||||
@@ -51,7 +53,12 @@ describe('ForgotPasswordPage', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore(initialState);
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(() => ({
|
||||
userId: 3,
|
||||
username: 'test-user',
|
||||
})),
|
||||
}));
|
||||
configure({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
@@ -191,11 +198,6 @@ describe('ForgotPasswordPage', () => {
|
||||
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it('check cookie rendered', () => {
|
||||
const forgotPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
expect(forgotPage.find(<CookiePolicyBanner />)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display success message after email is sent', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
@@ -227,15 +229,11 @@ describe('ForgotPasswordPage', () => {
|
||||
});
|
||||
|
||||
it('should redirect onto login page', async () => {
|
||||
const forgotPasswordPage = mount(reduxWrapper(
|
||||
<Router history={history}>
|
||||
<IntlForgotPasswordPage {...props} />
|
||||
</Router>,
|
||||
));
|
||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
|
||||
await act(async () => { await forgotPasswordPage.find('nav').find('a').first().simulate('click'); });
|
||||
|
||||
forgotPasswordPage.update();
|
||||
expect(history.location.pathname).toEqual(LOGIN_PAGE);
|
||||
expect(mockedNavigator).toHaveBeenCalledWith(LOGIN_PAGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import arMessages from './messages/ar.json';
|
||||
import caMessages from './messages/ca.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
import dedeMessages from './messages/de_DE.json';
|
||||
import es419Messages from './messages/es_419.json';
|
||||
import frMessages from './messages/fr.json';
|
||||
import heMessages from './messages/he.json';
|
||||
import hiMessages from './messages/hi.json';
|
||||
import idMessages from './messages/id.json';
|
||||
import ititMessages from './messages/it_IT.json';
|
||||
import kokrMessages from './messages/ko_kr.json';
|
||||
import plMessages from './messages/pl.json';
|
||||
import ptbrMessages from './messages/pt_br.json';
|
||||
import ptptMessages from './messages/pt_PT.json';
|
||||
import ruMessages from './messages/ru.json';
|
||||
import thMessages from './messages/th.json';
|
||||
import ukMessages from './messages/uk.json';
|
||||
import zhcnMessages from './messages/zh_CN.json';
|
||||
import { messages as paragonMessages } from '@edx/paragon';
|
||||
|
||||
const messages = {
|
||||
import arMessages from './messages/ar.json';
|
||||
import deMessages from './messages/de.json';
|
||||
import deDEMessages from './messages/de_DE.json';
|
||||
import es419Messages from './messages/es_419.json';
|
||||
import faIRMessages from './messages/fa_IR.json';
|
||||
import frMessages from './messages/fr.json';
|
||||
import frCAMessages from './messages/fr_CA.json';
|
||||
import hiMessages from './messages/hi.json';
|
||||
import itMessages from './messages/it.json';
|
||||
import itITMessages from './messages/it_IT.json';
|
||||
import ptMessages from './messages/pt.json';
|
||||
import ptPTMessages from './messages/pt_PT.json';
|
||||
import ruMessages from './messages/ru.json';
|
||||
import ukMessages from './messages/uk.json';
|
||||
import zhCNMessages from './messages/zh_CN.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
|
||||
const appMessages = {
|
||||
ar: arMessages,
|
||||
es: es419Messages, // Prospectus uses es language code for spanish, added `es` option and pointed to es-419 strings.
|
||||
de: deMessages,
|
||||
'de-de': deDEMessages,
|
||||
'es-419': es419Messages,
|
||||
'fa-ir': faIRMessages,
|
||||
fr: frMessages,
|
||||
'zh-cn': zhcnMessages,
|
||||
'it-it': ititMessages,
|
||||
'pt-pt': ptptMessages,
|
||||
'de-de': dedeMessages,
|
||||
ca: caMessages,
|
||||
he: heMessages,
|
||||
id: idMessages,
|
||||
'ko-kr': kokrMessages,
|
||||
pl: plMessages,
|
||||
'pt-br': ptbrMessages,
|
||||
ru: ruMessages,
|
||||
th: thMessages,
|
||||
uk: ukMessages,
|
||||
'fr-ca': frCAMessages,
|
||||
hi: hiMessages,
|
||||
it: itMessages,
|
||||
'it-it': itITMessages,
|
||||
pt: ptMessages,
|
||||
'pt-pt': ptPTMessages,
|
||||
ru: ruMessages,
|
||||
uk: ukMessages,
|
||||
'zh-cn': zhCNMessages,
|
||||
};
|
||||
|
||||
export default messages;
|
||||
export default [
|
||||
paragonMessages,
|
||||
appMessages,
|
||||
];
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
{
|
||||
"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": "ملفك الشخصي",
|
||||
"welcome.to.platform": "أهلا بك {username} في {siteName}",
|
||||
"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": "إظهار كلمة المرور",
|
||||
@@ -21,6 +24,8 @@
|
||||
"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 Support",
|
||||
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
|
||||
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
|
||||
"forgot.password.confirmation.message": "لقد أرسلنا بريدًا إلكترونيًا إلى {email} به إرشادات لإعادة ضبط كلمة المرور الخاصة بك. إن لم تستلم رسالة إعادة ضبط كلمة المرور بعد دقيقة واحدة، فتحقق من إدخال عنوان البريد الإلكتروني الصحيح، أو تفقد مجلد الرسائل غير المرغوب فيها. إن احتجت مزيدًا من المساعدة، {supportLink}.",
|
||||
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
|
||||
@@ -66,7 +71,7 @@
|
||||
"non.compliant.password.message": "كلمة مرورك الحالية لا تستسجيب لمتطلبات الأمان الجديدة. لقد أرسلنا للتو رسالة لإعادة ضبط كلمة المرور إلى عنوان البريد الإلكتروني المرتبط بهذا الحساب. شكرًا لك على مساعدتنا في الحفاظ على سلامة بياناتك.",
|
||||
"account.locked.out.message.1": "لحماية حسابك، تم إقفاله مؤقتًا. حاول مرة أخرى بعد 30 دقيقة.",
|
||||
"enterprise.login.btn.text": "بيانات الشركة أو المدرسة",
|
||||
"username.or.email.format.validation.less.chars.message": "يجب أن يحتوي اسم المستخدم أو البريد الإلكتروني على 3 أحرف على الأقل.",
|
||||
"username.or.email.format.validation.less.chars.message": "يجب أن يحتوي اسم المستخدم أو البريد الإلكتروني على 2 أحرف على الأقل.",
|
||||
"email.validation.message": "أدخل اسم المستخدم أو البريد الإلكتروني الخاص بك",
|
||||
"password.validation.message": "لم يتم استيفاء معايير كلمة المرور",
|
||||
"account.activation.success.message.title": "نجح الأمر! لقد قمت بتفعيل حسابك.",
|
||||
@@ -93,20 +98,30 @@
|
||||
"password.security.block.body": "اكتشف نظامنا أن كلمة مرورك صعيفة. غيّر كلمة مرورك حتى يظل حسابك آمنًا.",
|
||||
"password.security.close.button": "إغلاق",
|
||||
"password.security.redirect.to.reset.password.button": "إعادة ضبط كلمة المرور",
|
||||
"progressive.profiling.page.title": "الحقول الاختيارية | {siteName}",
|
||||
"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": "Next",
|
||||
"optional.fields.next.button": "التالي",
|
||||
"continue.to.platform": "المواصلة إلى {platformName}",
|
||||
"modal.title": "شكرا لإعلامنا.",
|
||||
"modal.description": "إن غيرت رأيك، قيمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
|
||||
"welcome.page.error.heading": "لم نتمكن من تحديث ملفك الشخصي",
|
||||
"welcome.page.error.message": "حدث خطأ ما. يمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
|
||||
"recommendation.page.title": "Recommendations| {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.page.title": "التوصيات | {siteName}",
|
||||
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
|
||||
"recommendation.skip.button": "التخطي مؤقتا",
|
||||
"recommendation.option.trending": "Trending",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "الاسم الكامل",
|
||||
"registration.email.label": "البريد الإلكتروني",
|
||||
@@ -120,6 +135,7 @@
|
||||
"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": "أدخل اسمك الكامل",
|
||||
@@ -137,6 +153,7 @@
|
||||
"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": "ميثاق الشرف الأكاديمي",
|
||||
@@ -144,8 +161,8 @@
|
||||
"registration.username.suggestion.label": "مقترح:",
|
||||
"did.you.mean.alert.text": "هل تقصد",
|
||||
"register.page.terms.of.service.and.honor.code": "بإنشاءك حسابًا، فإنك توافق على {tosAndHonorCode} و تقر بأن {platformName} و كل عضو يعالج بياناتك الشخصية وفقًا لـ{privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"register.page.honor.code": "اوافق على شروط {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "اوافق على {platformName} {termsOfService}",
|
||||
"sign.in": "تسجيل الدخول",
|
||||
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
|
||||
"reset.password": "إعادة ضبط كلمة المرور",
|
||||
@@ -160,4 +177,4 @@
|
||||
"reset.password.success.heading": "تمت إعادة ضبط كلمة المرور.",
|
||||
"reset.password.success": "تمت إعادة ضبط كلمة مرورك. سجل الدخول إلى حسابك.",
|
||||
"rate.limit.error": "حدث خطأ بسبب كثرة الطلبات. رجاءً حاول مرة أخرى بعد مضي بعض الوقت."
|
||||
}
|
||||
}
|
||||
|
||||
180
src/i18n/messages/de.json
Normal file
180
src/i18n/messages/de.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"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",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"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.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.\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.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.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submit",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "An error occurred.",
|
||||
"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": "Enter your email",
|
||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"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": "Too many requests",
|
||||
"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 failure",
|
||||
"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.",
|
||||
"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}.",
|
||||
"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.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.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Sign in",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "We recently changed our password requirements",
|
||||
"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": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 2 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
"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": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
||||
"login.failure.header.title": "We couldn't sign you in.",
|
||||
"contact.support.link": "contact {platformName} support",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"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": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"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}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.option.trending": "Trending",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"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",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"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",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"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."
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
{
|
||||
"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",
|
||||
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Wählen Sie Ihre Institution aus der folgenden Liste aus",
|
||||
"logistration.sign.in": "Anmelden",
|
||||
"logistration.register": "Registrieren",
|
||||
"enterprisetpa.title.heading": "Möchten Sie sich mit Ihren {providerName}-Anmeldedaten anmelden?",
|
||||
"enterprisetpa.login.button.text": "Andere Möglichkeiten für die Anmeldung oder Registrierung",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Melden Sie sich mit {providerName} an",
|
||||
"sso.create.account.using": "Erstellen Sie ein Konto mit {providerName}",
|
||||
"show.password": "Passwort anzeigen",
|
||||
@@ -21,6 +24,8 @@
|
||||
"login.third.party.auth.account.not.linked": "Sie haben sich erfolgreich bei {currentProvider} angemeldet, aber Ihr {currentProvider}-Konto hat kein verknüpftes {platformName}-Konto. Um Ihre Konten zu verknüpfen, melden Sie sich jetzt mit Ihrem {platformName}-Passwort an.",
|
||||
"register.third.party.auth.account.not.linked": "Sie haben sich erfolgreich bei {currentProvider} angemeldet! Wir brauchen nur ein paar mehr Informationen, bevor Sie anfangen, mit {platformName} zu lernen.",
|
||||
"registration.using.tpa.form.heading": "Beenden Sie die Erstellung Ihres Kontos",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"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": "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}",
|
||||
@@ -66,7 +71,7 @@
|
||||
"non.compliant.password.message": "Ihr aktuelles Passwort entspricht nicht den neuen Sicherheitsanforderungen. Wir haben gerade eine Nachricht zum Zurücksetzen des Passworts an die mit diesem Konto verknüpfte E-Mail-Adresse gesendet. Vielen Dank, dass Sie uns helfen, Ihre Daten zu schützen.",
|
||||
"account.locked.out.message.1": "Um Ihr Konto zu schützen, wurde es vorübergehend gesperrt. Versuchen Sie es in 30 Minuten erneut.",
|
||||
"enterprise.login.btn.text": "Arbeits- oder Schulzeugnisse",
|
||||
"username.or.email.format.validation.less.chars.message": "Benutzername oder E-Mail müssen mindestens 3 Zeichen lang sein.",
|
||||
"username.or.email.format.validation.less.chars.message": "Benutzername oder E-Mail müssen mindestens 2 Zeichen lang sein.",
|
||||
"email.validation.message": "Geben Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse ein",
|
||||
"password.validation.message": "Die Passwortkriterien wurden nicht erfüllt",
|
||||
"account.activation.success.message.title": "Super! Sie haben Ihr Konto aktiviert.",
|
||||
@@ -93,20 +98,30 @@
|
||||
"password.security.block.body": "Unser System hat festgestellt, dass Ihr Passwort angreifbar ist. Ändern Sie Ihr Passwort, damit Ihr Konto sicher bleibt.",
|
||||
"password.security.close.button": "Schließen",
|
||||
"password.security.redirect.to.reset.password.button": "Setzen Sie Ihr Passwort zurück",
|
||||
"progressive.profiling.page.title": "Optionale Felder | {siteName}",
|
||||
"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.heading": "Ein paar Fragen an Sie helfen uns, schlauer zu werden.",
|
||||
"optional.fields.information.link": "Erfahren Sie mehr darüber, wie wir diese Informationen verwenden.",
|
||||
"optional.fields.submit.button": "Einreichen",
|
||||
"optional.fields.skip.button": "Überspringen",
|
||||
"optional.fields.next.button": "Next",
|
||||
"optional.fields.next.button": "Weiter",
|
||||
"continue.to.platform": "Weiter zu {platformName}",
|
||||
"modal.title": "Danke, dass Sie uns das mitteilen.",
|
||||
"modal.description": "Sie können Ihr Profil jederzeit in den Einstellungen vervollständigen, wenn Sie Ihre Meinung ändern.",
|
||||
"welcome.page.error.heading": "Wir konnten Ihr Profil nicht aktualisieren",
|
||||
"welcome.page.error.message": "Ein Fehler ist aufgetreten. Sie können Ihr Profil jederzeit in den Einstellungen vervollständigen.",
|
||||
"recommendation.page.title": "Recommendations| {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.page.title": "Empfehlungen | {siteName}",
|
||||
"recommendation.page.heading": "Wir haben ein paar Empfehlungen für den Einstieg.",
|
||||
"recommendation.skip.button": "Überspringen",
|
||||
"recommendation.option.trending": "Trending",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "Vollständiger Name",
|
||||
"registration.email.label": "E-Mail-Adresse",
|
||||
@@ -120,6 +135,7 @@
|
||||
"help.text.email": "Für die Kontoaktivierung und wichtige Updates",
|
||||
"create.account.for.free.button": "Erstellen Sie kostenlos ein Benutzerkonto",
|
||||
"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.page.title": "Registrieren Sie sich mit Institutions-/Campus-Anmeldeinformationen",
|
||||
"empty.name.field.error": "Geben Sie Ihren vollständigen Namen ein",
|
||||
@@ -137,6 +153,7 @@
|
||||
"registration.request.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||
"registration.rate.limit.error": "Zu viele fehlgeschlagene Registrierungsversuche. Versuchen Sie es später noch einmal.",
|
||||
"registration.tpa.session.expired": "Die Registrierung mit {provider} ist abgelaufen.",
|
||||
"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": "Nutzungsbedingungen und Verhaltenskodex",
|
||||
"privacy.policy": "Datenschutzbestimmungen",
|
||||
"honor.code": "Verhaltenskodex",
|
||||
@@ -160,4 +177,4 @@
|
||||
"reset.password.success.heading": "Zurücksetzen des Passworts abgeschlossen.",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
{
|
||||
"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 ",
|
||||
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
|
||||
"logistration.sign.in": "Iniciar sesión",
|
||||
"logistration.register": "Registrarse",
|
||||
"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.public.account.creation.disabled": "Mostrar otras formas de iniciar sesión",
|
||||
"sso.sign.in.with": "Inicio de sesión con {providerName}",
|
||||
"sso.create.account.using": "Crear una cuenta con {providerName}",
|
||||
"show.password": "Mostrar contraseña",
|
||||
@@ -21,6 +24,8 @@
|
||||
"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}.",
|
||||
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
|
||||
"zendesk.supportTitle": "Soporte edX",
|
||||
"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": "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}",
|
||||
@@ -66,7 +71,7 @@
|
||||
"non.compliant.password.message": "Tu contraseña actual no cumple con los nuevos requisitos de seguridad. Acabamos de enviar un mensaje de restablecimiento de contraseña a la dirección de correo electrónico asociada a esta cuenta. Gracias por ayudarnos a mantener tus datos seguros.",
|
||||
"account.locked.out.message.1": "Para proteger tu cuenta, se ha bloqueado temporalmente. Inténtalo de nuevo en 30 minutos.",
|
||||
"enterprise.login.btn.text": "Credenciales de la empresa o de la institución ",
|
||||
"username.or.email.format.validation.less.chars.message": "El nombre de usuario o el correo electrónico deben tener al menos 3 caracteres.",
|
||||
"username.or.email.format.validation.less.chars.message": "El nombre de usuario o el correo electrónico deben tener al menos 2 caracteres.",
|
||||
"email.validation.message": "Introduce tu nombre de usuario o correo electrónico",
|
||||
"password.validation.message": "No se han cumplido los criterios de la contraseña",
|
||||
"account.activation.success.message.title": "Ha sido un éxito. Has activado tu cuenta.",
|
||||
@@ -93,20 +98,30 @@
|
||||
"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.redirect.to.reset.password.button": "Restablece tu contraseña",
|
||||
"progressive.profiling.page.title": "Campos opcionales | {siteName}",
|
||||
"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": "Bienvenido | {siteName}",
|
||||
"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.submit.button": "Enviar",
|
||||
"optional.fields.skip.button": "Saltar por ahora ",
|
||||
"optional.fields.next.button": "Next",
|
||||
"optional.fields.next.button": "Siguiente",
|
||||
"continue.to.platform": "Continuar a {platformName}",
|
||||
"modal.title": "Gracias por informarnos.",
|
||||
"modal.description": "Puedes completar tu perfil en los ajustes en cualquier momento si cambias de opinión.",
|
||||
"welcome.page.error.heading": "No hemos podido actualizar tu perfil",
|
||||
"welcome.page.error.message": "Se ha producido un error. Puedes completar tu perfil en los ajustes en cualquier momento.",
|
||||
"recommendation.page.title": "Recommendations| {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.page.title": "Recomendaciones | {siteName}",
|
||||
"recommendation.page.heading": "Tenemos algunas recomendaciones para empezar.",
|
||||
"recommendation.skip.button": "Saltar por ahora ",
|
||||
"recommendation.option.trending": "Tendencias",
|
||||
"recommendation.option.popular": "Más popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "Nombre completo",
|
||||
"registration.email.label": "Correo electrónico",
|
||||
@@ -120,6 +135,7 @@
|
||||
"help.text.email": "Para la activación de la cuenta y las actualizaciones importantes",
|
||||
"create.account.for.free.button": "Crea una cuenta gratis",
|
||||
"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.page.title": "Registro con credenciales de la institución/campus",
|
||||
"empty.name.field.error": "Introduce tu nombre completo",
|
||||
@@ -137,6 +153,7 @@
|
||||
"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.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
|
||||
"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",
|
||||
"privacy.policy": "Política de privacidad ",
|
||||
"honor.code": "Código de Honor",
|
||||
@@ -160,4 +177,4 @@
|
||||
"reset.password.success.heading": "Restablecimiento de la contraseña completado.",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
||||
180
src/i18n/messages/fa_IR.json
Normal file
180
src/i18n/messages/fa_IR.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"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": "پرونده کاربری شما",
|
||||
"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": "لطفا نوع درخواست خود را انتخاب کنید:",
|
||||
"error.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی اینترنتی وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره تلاش کنید.",
|
||||
"forgot.password.confirmation.message": "ما رایانامهای بههمراه دستورالعمل بازنشانی گذرواژه به {email} ارسال کردیم. اگر پس از 1 دقیقه پیام بازنشانی را دریافت نکردید، بررسی کنید که نشانی رایانامه را صحیح وارد کردهاید یا پوشه هرزنامه خود را بررسی کنید. اگر به دریافت راهنمایی بیشتری، با {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": "برای دریافت راهنمایی بیشتر، با پشتیبانی {platformName} در این آدرس تماس بگیرید",
|
||||
"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": "برای ورود به سامانه، باید حساب کاربری خود را فعال کنید.{lineBreak} {lineBreak}هم اکنون پیوند فعالسازی را به {email} فرستادیم. اگر رایانامهای دریافت نکردید، پوشههای هرزنامه یا {supportLink} را بررسی کنید.",
|
||||
"allowed.domain.login.error": "به عنوان کاربر {allowedDomain}، باید با {allowedDomain} {tpaLink} خود وارد شوید.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "نام کاربری، نشانی رایانامه یا گذرواژهای که وارد کردهاید نادرست است. پیش از اینکه حساب کاربری شما موقتا قفل شود، {remainingAttempts} تلاش دیگر برای ورود به سامانه دارید.",
|
||||
"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": "نام کاربری یا نشانی رایانامه حداقل باید 2 نویسه داشته باشد",
|
||||
"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": "پرطرفدار",
|
||||
"recommendation.option.popular": "محبوبترین",
|
||||
"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": "منظور شما این بود",
|
||||
"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": "ورود",
|
||||
"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": "به دلیل درخواستهای زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید."
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
{
|
||||
"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",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"logistration.sign.in": "Connectez-vous",
|
||||
"logistration.register": "S'inscrire",
|
||||
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName} ?",
|
||||
"enterprisetpa.login.button.text": "Montrez-moi d'autres méthodes pour me connecter ou m'inscrire",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
||||
"sso.create.account.using": "Créer un compte avec {providerName}",
|
||||
"show.password": "Afficher le mot de passe",
|
||||
@@ -21,6 +24,8 @@
|
||||
"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}.",
|
||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"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": "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}",
|
||||
@@ -66,7 +71,7 @@
|
||||
"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.",
|
||||
"enterprise.login.btn.text": "Identifiants 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 2 caractères.",
|
||||
"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",
|
||||
"account.activation.success.message.title": "Succès! Vous avez activé votre compte.",
|
||||
@@ -93,7 +98,8 @@
|
||||
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
|
||||
"password.security.close.button": "Fermer",
|
||||
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
|
||||
"progressive.profiling.page.title": "Champs optionnels | {siteName}",
|
||||
"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.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
|
||||
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
|
||||
"optional.fields.submit.button": "Envoyez",
|
||||
@@ -104,9 +110,18 @@
|
||||
"modal.description": "Vous pouvez compléter votre profil dans les paramètres à tout moment si vous changez d'avis.",
|
||||
"welcome.page.error.heading": "Nous n'avons pas pu mettre à jour votre profil",
|
||||
"welcome.page.error.message": "Une erreur s'est produite. Vous pouvez compléter votre profil dans les paramètres à tout moment.",
|
||||
"recommendation.page.title": "Recommendations| {siteName}",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.option.trending": "Trending",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "Nom complet",
|
||||
"registration.email.label": "Email",
|
||||
@@ -120,6 +135,7 @@
|
||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||
"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.page.title": "Inscription avec les crédentiels d'institution ou de campus",
|
||||
"empty.name.field.error": "Saisissez votre nom complet",
|
||||
@@ -137,6 +153,7 @@
|
||||
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
|
||||
"registration.tpa.session.expired": "L'inscription avec {provider} a échouée.",
|
||||
"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": "Conditions d'utilisation et Code d'honneur",
|
||||
"privacy.policy": "Politique de confidentialité",
|
||||
"honor.code": "Code d'honneur",
|
||||
@@ -160,4 +177,4 @@
|
||||
"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.",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
||||
180
src/i18n/messages/fr_CA.json
Normal file
180
src/i18n/messages/fr_CA.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"start.learning": "Commencez à apprendre",
|
||||
"with.site.name": "avec {siteName}",
|
||||
"your.career.turning.point": "Votre tournant de carrière",
|
||||
"is.here": "est là.",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complet",
|
||||
"complete.your.profile.2": "votre profil",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"logistration.sign.in": "Connexion",
|
||||
"logistration.register": "Inscription",
|
||||
"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.public.account.creation.disabled": "Montrez-moi d'autres façons de me connecter",
|
||||
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
||||
"sso.create.account.using": "Créer un compte avec {providerName}",
|
||||
"show.password": "Afficher le mot de passe",
|
||||
"hide.password": "Cacher le mot de passe",
|
||||
"one.letter": "1 lettre",
|
||||
"one.number": "1 numéro",
|
||||
"eight.characters": "8 caractères",
|
||||
"password.sr.only.helping.text": "Le mot de passe doit contenir au moins 8 caractères, au moins une lettre et au moins un chiffre",
|
||||
"tpa.alert.heading": "Presque terminé!",
|
||||
"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}.",
|
||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||
"zendesk.supportTitle": "Prise en charge d'edX",
|
||||
"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.\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.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.invalid.email.message": "Entrez une adresse de courriel valide",
|
||||
"forgot.password.page.email.field.label": "Courriel",
|
||||
"forgot.password.page.submit.button": "Soumettre",
|
||||
"forgot.password.error.alert.title.": "Nous n'avons pas pu vous contacter.",
|
||||
"forgot.password.error.message.title": "Une erreur est survenue.",
|
||||
"forgot.password.request.in.progress.message": "Votre demande précédente est en cours, veuillez réessayer dans quelques instants.",
|
||||
"forgot.password.empty.email.field.error": "Saisissez votre courriel",
|
||||
"forgot.password.email.help.text": "L'adresse courriel que vous avez utilisée pour vous inscrire sur {platformName}",
|
||||
"confirmation.message.title": "Vérifiez votre courriel",
|
||||
"confirmation.support.link": "contacter le support technique",
|
||||
"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 ",
|
||||
"sign.in.text": "Connexion",
|
||||
"extend.field.errors": "{emailError} ci-dessous.",
|
||||
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
||||
"invalid.token.error.message": "Ce lien de réinitialisation de mot de passe n'est pas valide. Il a peut-être déjà été utilisé. Entrez votre courriel ci-dessous pour recevoir un nouveau lien.",
|
||||
"token.validation.rate.limit.error.heading": "Trop de demandes",
|
||||
"token.validation.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.",
|
||||
"token.validation.internal.sever.error.heading": "Échec de la validation du jeton",
|
||||
"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.",
|
||||
"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}.",
|
||||
"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.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.",
|
||||
"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.page.title": "Connexion | {siteName}",
|
||||
"login.user.identity.label": "Nom d'utilisateur ou courriel",
|
||||
"login.password.label": "Mot de passe",
|
||||
"sign.in.button": "Connexion",
|
||||
"forgot.password": "Mot de passe oublié",
|
||||
"institution.login.button": "Informations d'identification de l'établissement/du 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 :",
|
||||
"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.",
|
||||
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
|
||||
"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 2 caractères.",
|
||||
"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",
|
||||
"account.activation.success.message.title": "Succès! Vous avez activé votre compte.",
|
||||
"account.activation.success.message": "Vous recevrez maintenant des mises à jour par courriel et des alertes de notre part concernant les cours auxquels vous êtes inscrit. Connectez-vous pour continuer.",
|
||||
"account.activation.info.message": "Ce compte a déjà été activé.",
|
||||
"account.activation.error.message.title": "Votre compte n'a pas pu être activé",
|
||||
"account.activation.support.link": "contacter le support",
|
||||
"account.confirmation.success.message.title": "Bravo! Vous avez confirmé votre courriel.",
|
||||
"account.confirmation.success.message": "Se connecter pour continuer.",
|
||||
"account.confirmation.info.message": "Ce courriel a déjà été confirmé.",
|
||||
"account.confirmation.error.message.title": "Votre courriel ne peut pas être confirmé",
|
||||
"tpa.account.link": "compte {provider}",
|
||||
"internal.server.error.message": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"login.rate.limit.reached.message": "Trop de tentatives d'accès refusées. Essayer plus tard.",
|
||||
"login.failure.header.title": "Nous n'avons pas pu vous connecter.",
|
||||
"contact.support.link": "veuillez contacter le support {platformName}",
|
||||
"login.incorrect.credentials.error": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer.",
|
||||
"login.form.invalid.error.message": "Veuillez remplir les champs ci-dessous.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "réinitialiser votre mot de passe",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "cliquez ici pour le réinitialiser.",
|
||||
"password.security.nudge.title": "Sécurité du mot de passe",
|
||||
"password.security.block.title": "Changement de mot de passe requis",
|
||||
"password.security.nudge.body": "Notre système a détecté que votre mot de passe est vulnérable. Nous vous recommandons de le modifier afin que votre compte reste sécurisé.",
|
||||
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
|
||||
"password.security.close.button": "Fermer",
|
||||
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
|
||||
"login.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Bienvenue | {siteName}",
|
||||
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
|
||||
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
|
||||
"optional.fields.submit.button": "Soumettre",
|
||||
"optional.fields.skip.button": "Ignorer pour l'instant",
|
||||
"optional.fields.next.button": "Suivant",
|
||||
"continue.to.platform": "Continuer vers {platformName}",
|
||||
"modal.title": "Merci de nous en informer.",
|
||||
"modal.description": "Vous pouvez compléter votre profil dans les paramètres à tout moment si vous changez d'avis.",
|
||||
"welcome.page.error.heading": "Nous n'avons pas pu mettre à jour votre profil",
|
||||
"welcome.page.error.message": "Une erreur s'est produite. Vous pouvez compléter votre profil dans les paramètres à tout moment.",
|
||||
"recommendation.page.title": "Recommandations | {siteName}",
|
||||
"recommendation.page.heading": "Nous avons quelques recommandations pour vous aider à démarrer.",
|
||||
"recommendation.skip.button": "Ignorer pour l'instant",
|
||||
"recommendation.option.trending": "Tendance",
|
||||
"recommendation.option.popular": "Le plus populaire",
|
||||
"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 par 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}",
|
||||
"registration.fullname.label": "Nom complet",
|
||||
"registration.email.label": "Courriel",
|
||||
"registration.username.label": "Nom d'utilisateur",
|
||||
"registration.password.label": "Mot de passe",
|
||||
"registration.country.label": "Pays/Région",
|
||||
"registration.opt.in.label": "{siteName} peux m'envoyer des messages de marketing.",
|
||||
"help.text.name": "Ce nom sera utilisé pour toutes les attestations que vous obtiendrez.",
|
||||
"help.text.username.1": "Le nom qui vous identifiera dans vos cours.",
|
||||
"help.text.username.2": "Cela ne peut pas être modifié ultérieurement.",
|
||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||
"create.account.cta.button": "{label}",
|
||||
"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.email.field.error": "Saisissez votre courriel",
|
||||
"empty.username.field.error": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"empty.password.field.error": "Les critères de mot de passe n'ont pas été remplis",
|
||||
"empty.country.field.error": "Sélectionnez votre pays ou région de résidence",
|
||||
"email.do.not.match": "Les adresses courriel ne correspondent pas.",
|
||||
"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",
|
||||
"name.validation.message": "Entrez un nom valide",
|
||||
"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.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.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
|
||||
"registration.tpa.session.expired": "L'inscription à l'aide de {provider} a expiré.",
|
||||
"registration.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Conditions générales du service et code d'honneur",
|
||||
"privacy.policy": "Politique de confidentialité",
|
||||
"honor.code": "Code d'honneur",
|
||||
"terms.of.service": "Conditions générales du service",
|
||||
"registration.username.suggestion.label": "Suggéré :",
|
||||
"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",
|
||||
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
||||
"reset.password": "Réinitialiser le mot de passe",
|
||||
"reset.password.page.instructions": "Saisir et confirmer votre nouveau mot de passe.",
|
||||
"new.password.label": "Nouveau mot de passe",
|
||||
"confirm.password.label": "Confirmer le mot de passe",
|
||||
"passwords.do.not.match": "Les mots de passe ne correspondent pas",
|
||||
"confirm.your.password": "Confirmer votre mot de passe",
|
||||
"reset.password.failure.heading": "Nous n'avons pas pu réinitialiser votre mot de passe.",
|
||||
"reset.password.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
||||
"reset.server.rate.limit.error": "Trop de demandes.",
|
||||
"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.",
|
||||
"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."
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
{
|
||||
"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",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
@@ -21,6 +24,8 @@
|
||||
"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.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.\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}",
|
||||
@@ -66,7 +71,7 @@
|
||||
"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": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 2 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
@@ -93,7 +98,8 @@
|
||||
"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": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"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.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
@@ -104,9 +110,18 @@
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations| {siteName}",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.option.trending": "Trending",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
@@ -120,6 +135,7 @@
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
@@ -137,6 +153,7 @@
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"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",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
@@ -160,4 +177,4 @@
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
||||
180
src/i18n/messages/it.json
Normal file
180
src/i18n/messages/it.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"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",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"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.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.\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.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.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submit",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "An error occurred.",
|
||||
"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": "Enter your email",
|
||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"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": "Too many requests",
|
||||
"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 failure",
|
||||
"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.",
|
||||
"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}.",
|
||||
"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.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.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Sign in",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "We recently changed our password requirements",
|
||||
"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": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 2 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
"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": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
||||
"login.failure.header.title": "We couldn't sign you in.",
|
||||
"contact.support.link": "contact {platformName} support",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"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": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"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}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.option.trending": "Trending",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"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}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"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",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"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",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"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."
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user