Compare commits
148 Commits
recruitmes
...
fixing-san
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f57f39187 | ||
|
|
0e8c8f5412 | ||
|
|
2099e62bb4 | ||
|
|
7f7931fec5 | ||
|
|
1d039de652 | ||
|
|
21d24d517f | ||
|
|
3581007e59 | ||
|
|
df5e11d806 | ||
|
|
75f460bd17 | ||
|
|
66261c4cea | ||
|
|
ea6854374b | ||
|
|
e0385ce20d | ||
|
|
3f53b3057b | ||
|
|
517a390b2f | ||
|
|
6dc04be641 | ||
|
|
d852f217cb | ||
|
|
178fb43a5a | ||
|
|
76d3a13169 | ||
|
|
ce9c6e24a3 | ||
|
|
6dd9d2a1dc | ||
|
|
b493a09c63 | ||
|
|
5df7130310 | ||
|
|
69ede3f005 | ||
|
|
ea5ae8f2af | ||
|
|
d352773a4e | ||
|
|
3fa3954383 | ||
|
|
182fb34a03 | ||
|
|
6e99e1e72c | ||
|
|
7e82785c7b | ||
|
|
74aa00f9cc | ||
|
|
561be05bac | ||
|
|
b97e6292dd | ||
|
|
aa6f36a293 | ||
|
|
0b21ca3f51 | ||
|
|
a04872d7bf | ||
|
|
526da727ec | ||
|
|
747dac26d7 | ||
|
|
a956ccaa93 | ||
|
|
b37a059af7 | ||
|
|
3117c9abd4 | ||
|
|
211c6afc60 | ||
|
|
261e4cfe86 | ||
|
|
dd7ed6d9ee | ||
|
|
9f38be3318 | ||
|
|
7415a0c0e6 | ||
|
|
ac9a390d98 | ||
|
|
61c839bb09 | ||
|
|
cd3cda3c4f | ||
|
|
242629fbdd | ||
|
|
214c383071 | ||
|
|
b8f0615434 | ||
|
|
387e9aedf1 | ||
|
|
d1b5ba74e8 | ||
|
|
1c599d8d04 | ||
|
|
5303b7b95c | ||
|
|
f64d9dd940 | ||
|
|
3a68403773 | ||
|
|
95c869a6ef | ||
|
|
35ee6b5a7b | ||
|
|
55e44e5881 | ||
|
|
738af2aaac | ||
|
|
0f333b4a03 | ||
|
|
06bb7182fb | ||
|
|
4b8677f34d | ||
|
|
f0df248ac2 | ||
|
|
9151cf46aa | ||
|
|
d7f6b1ae0c | ||
|
|
d7c1e119fb | ||
|
|
35d6c9d7bc | ||
|
|
9f9fe2ed1a | ||
|
|
96ec8c3943 | ||
|
|
23bac18450 | ||
|
|
094573be1f | ||
|
|
b2f2760774 | ||
|
|
933e8f85fd | ||
|
|
d9b9c4d290 | ||
|
|
892f3d33ca | ||
|
|
c1b402ebd6 | ||
|
|
a081544440 | ||
|
|
6fee02110a | ||
|
|
aef3f6a179 | ||
|
|
d7391dfd66 | ||
|
|
4f7fbf82bf | ||
|
|
0d01a9fea3 | ||
|
|
88a6e1b260 | ||
|
|
b142e159b6 | ||
|
|
2a079e484f | ||
|
|
1210fe01ae | ||
|
|
7400da7ba6 | ||
|
|
a86f5c43ad | ||
|
|
531b82745f | ||
|
|
ebdaeb80f1 | ||
|
|
488b364215 | ||
|
|
619c75a4b7 | ||
|
|
be783fed99 | ||
|
|
59d5a400f4 | ||
|
|
e0e0c1011e | ||
|
|
2070877a84 | ||
|
|
8c8cd2bcd5 | ||
|
|
a5e7a3a592 | ||
|
|
007dfa82a8 | ||
|
|
8223f0c988 | ||
|
|
335f9fcaf8 | ||
|
|
31a2a8f402 | ||
|
|
54fb55fe8f | ||
|
|
c34360f2fb | ||
|
|
82d339c91b | ||
|
|
4d9b508e19 | ||
|
|
60e51a8f25 | ||
|
|
350a6b10ae | ||
|
|
4ba9e1194b | ||
|
|
6cb470522b | ||
|
|
a3953b672f | ||
|
|
e91a8a4dfa | ||
|
|
17048f5caf | ||
|
|
955cf26860 | ||
|
|
ee094a1330 | ||
|
|
75269a3372 | ||
|
|
2ac0f83e87 | ||
|
|
b0df7a0593 | ||
|
|
14e89575d1 | ||
|
|
4e7003ca5e | ||
|
|
6bc11b01f5 | ||
|
|
b5ada5d2d5 | ||
|
|
21ff9b81f9 | ||
|
|
cdf0c29d01 | ||
|
|
57cc5a4692 | ||
|
|
7cb58d4f26 | ||
|
|
b1e9f631dc | ||
|
|
0a25ec2037 | ||
|
|
3164afa46a | ||
|
|
bff75b811d | ||
|
|
d7b729cc98 | ||
|
|
8917b88a5a | ||
|
|
53e40480f2 | ||
|
|
9c20e91563 | ||
|
|
582af7a8b0 | ||
|
|
62ed8a631e | ||
|
|
bca605c632 | ||
|
|
c9cf7c5190 | ||
|
|
e29e35c093 | ||
|
|
85276767eb | ||
|
|
1de911122a | ||
|
|
68cc93da1c | ||
|
|
419915d752 | ||
|
|
63572d43f6 | ||
|
|
498325e6e7 | ||
|
|
5c8d682a83 |
4
.env
4
.env
@@ -16,7 +16,6 @@ SITE_NAME=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
AUTHN_MINIMAL_HEADER=true
|
||||
LOGIN_ISSUE_SUPPORT_LINK=''
|
||||
REGISTRATION_OPTIONAL_FIELDS=''
|
||||
USER_SURVEY_COOKIE_NAME=null
|
||||
COOKIE_DOMAIN=null
|
||||
WELCOME_PAGE_SUPPORT_LINK=null
|
||||
@@ -24,3 +23,6 @@ INFO_EMAIL=''
|
||||
DISABLE_ENTERPRISE_LOGIN=''
|
||||
REGISTER_CONVERSION_COOKIE_NAME=null
|
||||
ENABLE_PROGRESSIVE_PROFILING=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
SHOW_DYNAMIC_PROFILING_PAGE=''
|
||||
|
||||
@@ -17,16 +17,17 @@ MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME='edX'
|
||||
SITE_NAME='Your Platform Name Here'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
AUTHN_MINIMAL_HEADER=true
|
||||
LOGIN_ISSUE_SUPPORT_LINK='/login-issue-support-url'
|
||||
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
|
||||
PRIVACY_POLICY='http://localhost:18000/privacy'
|
||||
REGISTRATION_OPTIONAL_FIELDS=''
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
COOKIE_DOMAIN='localhost'
|
||||
WELCOME_PAGE_SUPPORT_LINK='http://localhost:1999/welcome'
|
||||
INFO_EMAIL='info@edx.org'
|
||||
DISABLE_ENTERPRISE_LOGIN=''
|
||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
|
||||
@@ -15,10 +15,12 @@ MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME='edX'
|
||||
SITE_NAME='Your Platform Name Here'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
LOGIN_ISSUE_SUPPORT_LINK='https://login-issue-support-url.com'
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
WELCOME_PAGE_SUPPORT_LINK='http://localhost:1999/welcome'
|
||||
DISABLE_ENTERPRISE_LOGIN=''
|
||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
|
||||
19
.github/workflows/add-depr-ticket-to-depr-board.yml
vendored
Normal file
19
.github/workflows/add-depr-ticket-to-depr-board.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Run the workflow that adds new tickets that are either:
|
||||
# - labelled "DEPR"
|
||||
# - title starts with "[DEPR]"
|
||||
# - body starts with "Proposal Date" (this is the first template field)
|
||||
# to the org-wide DEPR project board
|
||||
|
||||
name: Add newly created DEPR issues to the DEPR project board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
routeissue:
|
||||
uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master
|
||||
secrets:
|
||||
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
||||
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
||||
48
.github/workflows/ci.yml
vendored
Normal file
48
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: node_CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
node: [12, 14, 16]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Verify No Uncommitted Package-Lock Changes
|
||||
run: make validate-no-uncommitted-package-lock-changes
|
||||
|
||||
- name: Run i18n_extract
|
||||
run: npm run i18n_extract
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Verify Es5
|
||||
run: npm run is-es5
|
||||
|
||||
- name: Run Code Coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
10
.github/workflows/commitlint.yml
vendored
Normal file
10
.github/workflows/commitlint.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Run commitlint on the commit messages in a pull request.
|
||||
|
||||
name: Lint Commit Messages
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
uses: edx/.github/.github/workflows/commitlint.yml@master
|
||||
@@ -1,7 +1,6 @@
|
||||
.eslintignore
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.travis.yml
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
Makefile
|
||||
|
||||
13
.travis.yml
13
.travis.yml
@@ -1,13 +0,0 @@
|
||||
language: node_js
|
||||
node_js: 12
|
||||
install:
|
||||
- npm ci
|
||||
script:
|
||||
- make validate-no-uncommitted-package-lock-changes
|
||||
- npm run i18n_extract
|
||||
- npm run lint
|
||||
- npm run test
|
||||
- npm run build
|
||||
- npm run is-es5
|
||||
after_success:
|
||||
- codecov
|
||||
@@ -1,8 +1,9 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[edx-platform.frontend-app-authn]
|
||||
[o:open-edx:p:edx-platform:r:frontend-app-authn]
|
||||
file_filter = src/i18n/messages/<lang>.json
|
||||
source_file = src/i18n/transifex_input.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
type = KEYVALUEJSON
|
||||
|
||||
|
||||
16
Makefile
16
Makefile
@@ -1,11 +1,9 @@
|
||||
transifex_resource = frontend-app-authn
|
||||
transifex_langs = "ar,fr,es_419,zh_CN"
|
||||
export TRANSIFEX_RESOURCE = frontend-app-authn
|
||||
transifex_langs = "ar,fr,es_419,zh_CN,it_IT,pt_PT,de_DE,uk,ru,hi"
|
||||
|
||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||
i18n = ./src/i18n
|
||||
transifex_input = $(i18n)/transifex_input.json
|
||||
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
|
||||
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
|
||||
|
||||
# This directory must match .babelrc .
|
||||
transifex_temp = ./temp/babel-plugin-react-intl
|
||||
@@ -38,17 +36,17 @@ push_translations:
|
||||
# Pushing strings to Transifex...
|
||||
tx push -s
|
||||
# Fetching hashes from Transifex...
|
||||
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
|
||||
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
||||
# Writing out comments to file...
|
||||
$(transifex_utils) $(transifex_temp) --comments
|
||||
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
||||
# Pushing comments to Transifex...
|
||||
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
|
||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||
|
||||
# Pulls translations from Transifex.
|
||||
pull_translations:
|
||||
tx pull -f --mode reviewed --language=$(transifex_langs)
|
||||
tx pull -f --mode reviewed --languages=$(transifex_langs)
|
||||
|
||||
# This target is used by Travis.
|
||||
# This target is used by CI.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
# Checking for package-lock.json changes...
|
||||
git diff --exit-code package-lock.json
|
||||
|
||||
@@ -8,5 +8,6 @@ module.exports = createConfig('jest', {
|
||||
'src/setupTest.js',
|
||||
'src/i18n',
|
||||
'src/index.jsx',
|
||||
'MainApp.jsx',
|
||||
],
|
||||
});
|
||||
|
||||
51774
package-lock.json
generated
51774
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
65
package.json
65
package.json
@@ -35,55 +35,58 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||
"@edx/frontend-component-cookie-policy-banner": "2.1.12",
|
||||
"@edx/frontend-platform": "1.12.0",
|
||||
"@edx/paragon": "16.6.1",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.32",
|
||||
"@fortawesome/free-brands-svg-icons": "5.15.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.13",
|
||||
"classnames": "2.2.6",
|
||||
"core-js": "3.9.1",
|
||||
"@edx/frontend-component-cookie-policy-banner": "2.1.14",
|
||||
"@edx/frontend-platform": "1.15.5",
|
||||
"@edx/paragon": "19.10.2",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"classnames": "2.3.1",
|
||||
"clipboard": "2.0.10",
|
||||
"core-js": "3.21.1",
|
||||
"extract-react-intl-messages": "4.1.1",
|
||||
"fastest-levenshtein": "1.0.12",
|
||||
"form-urlencoded": "4.2.1",
|
||||
"formik": "2.2.6",
|
||||
"formik": "2.2.9",
|
||||
"lodash.camelcase": "4.3.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"prop-types": "15.7.2",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "5.1.1",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-loading-skeleton": "2.2.0",
|
||||
"react-onclickoutside": "6.11.2",
|
||||
"react-redux": "7.2.3",
|
||||
"react-onclickoutside": "6.12.1",
|
||||
"react-redux": "7.2.6",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"redux": "4.0.5",
|
||||
"redux-devtools-extension": "2.13.8",
|
||||
"react-router": "5.2.1",
|
||||
"react-router-dom": "5.3.0",
|
||||
"redux": "4.1.2",
|
||||
"@redux-devtools/extension": "3.2.2",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-mock-store": "1.5.4",
|
||||
"redux-saga": "1.1.3",
|
||||
"redux-thunk": "2.3.0",
|
||||
"redux-thunk": "2.4.1",
|
||||
"regenerator-runtime": "0.13.9",
|
||||
"reselect": "4.0.0",
|
||||
"universal-cookie": "^4.0.4"
|
||||
"reselect": "4.1.5",
|
||||
"sanitize-html": "2.7.0",
|
||||
"semver-regex": "3.1.3",
|
||||
"universal-cookie": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/frontend-build": "5.6.11",
|
||||
"babel-plugin-react-intl": "8.2.25",
|
||||
"codecov": "3.8.1",
|
||||
"@edx/frontend-build": "9.1.2",
|
||||
"@edx/reactifex": "1.0.3",
|
||||
"babel-plugin-formatjs": "10.3.18",
|
||||
"codecov": "3.8.2",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.6",
|
||||
"es-check": "5.2.3",
|
||||
"glob": "7.1.6",
|
||||
"history": "5.0.0",
|
||||
"husky": "4.3.8",
|
||||
"jest": "26.6.3",
|
||||
"react-test-renderer": "16.14.0",
|
||||
"reactifex": "1.1.1"
|
||||
"es-check": "6.2.1",
|
||||
"glob": "7.2.0",
|
||||
"history": "5.3.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "27.5.1",
|
||||
"react-test-renderer": "16.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
"config:base",
|
||||
":automergeLinters",
|
||||
":automergeTesters",
|
||||
":automergeMinor",
|
||||
":noUnscheduledUpdates",
|
||||
":semanticCommits"
|
||||
],
|
||||
"patch": {
|
||||
"automerge": true
|
||||
},
|
||||
"rebaseStalePrs": true
|
||||
"rebaseStalePrs": true,
|
||||
"schedule": [
|
||||
"every weekday"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["node", "npm"],
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import {
|
||||
@@ -13,7 +14,7 @@ import configureStore from './data/configureStore';
|
||||
import { updatePathWithQueryParams } from './data/utils';
|
||||
import ForgotPasswordPage from './forgot-password';
|
||||
import ResetPasswordPage from './reset-password';
|
||||
import WelcomePage from './welcome';
|
||||
import WelcomePage, { ProgressiveProfiling } from './welcome';
|
||||
import './index.scss';
|
||||
|
||||
registerIcons();
|
||||
@@ -28,7 +29,11 @@ const MainApp = () => (
|
||||
<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={WELCOME_PAGE} component={WelcomePage} />
|
||||
<Route
|
||||
exact
|
||||
path={WELCOME_PAGE}
|
||||
component={(getConfig().SHOW_DYNAMIC_PROFILING_PAGE) ? ProgressiveProfiling : WelcomePage}
|
||||
/>
|
||||
<Route path={PAGE_NOT_FOUND} component={NotFoundPage} />
|
||||
<Route path="*">
|
||||
<Redirect to={PAGE_NOT_FOUND} />
|
||||
|
||||
@@ -15,6 +15,15 @@ $apple-black: #000000;
|
||||
$apple-focus-black: $apple-black;
|
||||
$accent-a-light: #c9f2f5;
|
||||
|
||||
.centered-align-spinner {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@extend .pt-4;
|
||||
min-width: 464px !important;
|
||||
@@ -28,6 +37,10 @@ $accent-a-light: #c9f2f5;
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.stateful-button-variation1-width {
|
||||
width: 16.4rem;
|
||||
}
|
||||
|
||||
.login-button-width {
|
||||
width: 6rem;
|
||||
}
|
||||
@@ -351,6 +364,10 @@ select.form-control {
|
||||
height: 282px;
|
||||
}
|
||||
|
||||
.variation1-medium-screen {
|
||||
height: 300px !important;
|
||||
}
|
||||
|
||||
.medium-screen-svg-light,
|
||||
.medium-screen-svg-primary {
|
||||
fill: $light-200;
|
||||
@@ -421,7 +438,7 @@ select.form-control {
|
||||
}
|
||||
|
||||
.small-screen-top-stripe {
|
||||
height: 0.5rem;
|
||||
height: 0.25rem;
|
||||
background-image: linear-gradient(
|
||||
102.02deg,
|
||||
$brand-700,
|
||||
@@ -486,6 +503,14 @@ select.form-control {
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.variation1-bar-color {
|
||||
stroke: $brand !important;
|
||||
}
|
||||
|
||||
.variation2-bar-color {
|
||||
stroke: $accent-a !important;
|
||||
}
|
||||
|
||||
.medium-screen-svg-line {
|
||||
padding-top: 0.5rem;
|
||||
stroke: $accent-b;
|
||||
@@ -501,6 +526,21 @@ select.form-control {
|
||||
width: 4em;
|
||||
height: 72px;
|
||||
}
|
||||
.dicount-heading{
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.hover-text:hover {
|
||||
color: $black !important;
|
||||
|
||||
.hover-icon {
|
||||
color: $black !important;
|
||||
}
|
||||
}
|
||||
|
||||
.hover-discount-icon:hover {
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
.large-heading {
|
||||
margin-left: 7px;
|
||||
@@ -670,6 +710,15 @@ select.form-control {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.discount-banner {
|
||||
background-color: #03C7E8;
|
||||
}
|
||||
|
||||
.dashed-border {
|
||||
border-style: dashed;
|
||||
border-width: thin;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.mw-500 {
|
||||
@@ -706,6 +755,12 @@ select.form-control {
|
||||
flex-direction:column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dashed-border {
|
||||
border-style: dashed;
|
||||
border-width: thin;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) and (min-width: 768px) {
|
||||
@@ -759,6 +814,12 @@ select.form-control {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.variation2-text-alignment {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
// Smaller than Extra Small (Mobile Screens)
|
||||
@media (max-width: 464px) {
|
||||
.btn-social {
|
||||
@@ -790,3 +851,33 @@ select.form-control {
|
||||
line-height: 1.5rem;
|
||||
color: $primary-700
|
||||
}
|
||||
|
||||
.opt-checkbox {
|
||||
.pgn__form-label {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
margin-top: 1rem;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.suggested-username {
|
||||
position: relative;
|
||||
margin-top: -8.7%;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.suggested-username-close-button {
|
||||
right: 0;
|
||||
position: absolute;
|
||||
}
|
||||
.suggested-username-with-error {
|
||||
position: relative;
|
||||
margin-top: -13.7%;
|
||||
margin-bottom: 11%;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.scroll-suggested-username {
|
||||
width: 21rem;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const AuthExtraLargeLayout = (props) => {
|
||||
<div className="container row p-0 m-0 large-screen-container">
|
||||
<div className="col-md-9 p-0 screen-header-light">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt="edx" className="logo position-absolute" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Image alt={getConfig().SITE_NAME} className="logo position-absolute" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="min-vh-100 d-flex align-items-center">
|
||||
<div>
|
||||
|
||||
@@ -17,7 +17,7 @@ const AuthMediumLayout = (props) => {
|
||||
<div className="container row p-0 mb-3 medium-container">
|
||||
<div className="col-md-10 p-0 screen-header-light">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center justify-content-center ml-6">
|
||||
<div>
|
||||
|
||||
@@ -17,7 +17,7 @@ const AuthSmallLayout = (props) => {
|
||||
return (
|
||||
<div className="small-screen-header-light">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className={classNames('d-flex mt-3', { 'pl-6': variant === 'sm' })}>
|
||||
<div>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import {
|
||||
ExtraSmall, Small, Medium, Large, ExtraLarge, ExtraExtraLarge,
|
||||
} from '@edx/paragon';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { breakpoints } from '@edx/paragon';
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import { getLocale } from '@edx/frontend-platform/i18n';
|
||||
|
||||
@@ -16,43 +15,66 @@ import SmallLayout from './SmallLayout';
|
||||
import AuthExtraLargeLayout from './AuthExtraLargeLayout';
|
||||
import AuthMediumLayout from './AuthMediumLayout';
|
||||
import AuthSmallLayout from './AuthSmallLayout';
|
||||
import DiscountExperimentBanner from './DiscountBanner';
|
||||
|
||||
const BaseComponent = ({ children }) => {
|
||||
const authenticatedUser = getAuthenticatedUser();
|
||||
const BaseComponent = ({ children, isRegistrationPage, showWelcomeBanner }) => {
|
||||
const authenticatedUser = showWelcomeBanner ? getAuthenticatedUser() : null;
|
||||
const [optimizelyExperimentName, setOptimizelyExperimentName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const { experimentName } = window;
|
||||
|
||||
if (experimentName) {
|
||||
setOptimizelyExperimentName(experimentName);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRegistrationPage && optimizelyExperimentName === 'variation2' ? <DiscountExperimentBanner /> : null}
|
||||
<CookiePolicyBanner languageCode={getLocale()} />
|
||||
<ExtraLarge>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth} maxWidth={breakpoints.extraLarge.maxWidth}>
|
||||
<div className="col-md-12 extra-large-screen-top-stripe" />
|
||||
</ExtraLarge>
|
||||
<ExtraExtraLarge>
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraExtraLarge.minWidth} maxWidth={breakpoints.extraExtraLarge.maxWidth}>
|
||||
<div className="col-md-12 extra-large-screen-top-stripe" />
|
||||
</ExtraExtraLarge>
|
||||
</MediaQuery>
|
||||
|
||||
<div className={classNames('layout', { authenticated: authenticatedUser })}>
|
||||
<ExtraSmall>
|
||||
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth}>
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
{authenticatedUser ? <AuthSmallLayout variant="xs" username={authenticatedUser.username} /> : <SmallLayout />}
|
||||
</ExtraSmall>
|
||||
<Small>
|
||||
{authenticatedUser ? <AuthSmallLayout variant="xs" username={authenticatedUser.username} /> : (
|
||||
<SmallLayout experimentName={optimizelyExperimentName} isRegistrationPage={isRegistrationPage} />
|
||||
)}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth}>
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
{authenticatedUser ? <AuthSmallLayout username={authenticatedUser.username} /> : <SmallLayout />}
|
||||
</Small>
|
||||
<Medium>
|
||||
{authenticatedUser ? <AuthSmallLayout username={authenticatedUser.username} /> : (
|
||||
<SmallLayout experimentName={optimizelyExperimentName} isRegistrationPage={isRegistrationPage} />
|
||||
)}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.medium.maxWidth}>
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : <MediumLayout />}
|
||||
</Medium>
|
||||
<Large>
|
||||
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : (
|
||||
<MediumLayout experimentName={optimizelyExperimentName} isRegistrationPage={isRegistrationPage} />
|
||||
)}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.large.minWidth} maxWidth={breakpoints.large.maxWidth}>
|
||||
<div className="w-100 large-screen-top-stripe" />
|
||||
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : <MediumLayout />}
|
||||
</Large>
|
||||
<ExtraLarge>
|
||||
{authenticatedUser ? <AuthExtraLargeLayout username={authenticatedUser.username} /> : <LargeLayout />}
|
||||
</ExtraLarge>
|
||||
<ExtraExtraLarge>
|
||||
{authenticatedUser ? <AuthExtraLargeLayout variant="xxl" username={authenticatedUser.username} /> : <LargeLayout />}
|
||||
</ExtraExtraLarge>
|
||||
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : (
|
||||
<MediumLayout experimentName={optimizelyExperimentName} isRegistrationPage={isRegistrationPage} />
|
||||
)}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth} maxWidth={breakpoints.extraLarge.maxWidth}>
|
||||
{authenticatedUser ? <AuthExtraLargeLayout username={authenticatedUser.username} /> : (
|
||||
<LargeLayout experimentName={optimizelyExperimentName} isRegistrationPage={isRegistrationPage} />
|
||||
)}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraExtraLarge.minWidth} maxWidth={breakpoints.extraExtraLarge.maxWidth}>
|
||||
{authenticatedUser ? <AuthExtraLargeLayout variant="xxl" username={authenticatedUser.username} /> : (
|
||||
<LargeLayout experimentName={optimizelyExperimentName} isRegistrationPage={isRegistrationPage} />
|
||||
)}
|
||||
</MediaQuery>
|
||||
|
||||
<div className={classNames('content', { 'align-items-center mt-0': authenticatedUser })}>
|
||||
{children}
|
||||
@@ -62,8 +84,15 @@ const BaseComponent = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
BaseComponent.defaultProps = {
|
||||
isRegistrationPage: false,
|
||||
showWelcomeBanner: false,
|
||||
};
|
||||
|
||||
BaseComponent.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
isRegistrationPage: PropTypes.bool,
|
||||
showWelcomeBanner: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default BaseComponent;
|
||||
|
||||
71
src/base-component/DiscountBanner.jsx
Normal file
71
src/base-component/DiscountBanner.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ClipboardJS from 'clipboard';
|
||||
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { faCut } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { Toast, PageBanner } from '@edx/paragon';
|
||||
import messages from './messages';
|
||||
|
||||
const DiscountExperimentBanner = (props) => {
|
||||
const { intl } = props;
|
||||
const [show, setShow] = useState(true);
|
||||
const [showToast, setToastShow] = useState(false);
|
||||
new ClipboardJS('.copyIcon'); // eslint-disable-line no-new
|
||||
const getDiscountText = () => (
|
||||
<strong>
|
||||
15% <FormattedMessage
|
||||
id="top.discount.message.15.off"
|
||||
defaultMessage="off"
|
||||
description="Text used with discounts e.g. 15% off"
|
||||
/>
|
||||
</strong>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast
|
||||
onClose={() => setToastShow(false)}
|
||||
show={showToast}
|
||||
>
|
||||
{intl.formatMessage(messages['code.copied'])}
|
||||
</Toast>
|
||||
<PageBanner
|
||||
show={show}
|
||||
dismissible
|
||||
onDismiss={() => { setShow(false); }}
|
||||
>
|
||||
<span className="text-primary-700 small variation2-text-alignment">
|
||||
<span className="mr-3">
|
||||
<FormattedMessage
|
||||
id="top.discount.message.body"
|
||||
defaultMessage="Get {discount} your first verified certificate* with code"
|
||||
description="Message body for edX discount"
|
||||
values={{
|
||||
discount: getDiscountText(),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span className="hover-text dashed-border p-1 d-inline-flex flex-wrap align-items-center">
|
||||
<span id="edx-welcome" className="font-weight-bold ">EDXWELCOME</span>
|
||||
<FontAwesomeIcon
|
||||
className="text-dark-200 copyIcon ml-2 hover-icon"
|
||||
icon={faCut}
|
||||
data-clipboard-action="copy"
|
||||
data-clipboard-target="#edx-welcome"
|
||||
onClick={() => setToastShow(true)}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</PageBanner>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DiscountExperimentBanner.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
|
||||
};
|
||||
export default injectIntl(DiscountExperimentBanner);
|
||||
@@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
|
||||
import LargeScreenLeftLayout from './LargeLeftLayout';
|
||||
|
||||
const LargeLayout = () => (
|
||||
const LargeLayout = ({ experimentName, isRegistrationPage }) => (
|
||||
<div className="container row p-0 m-0 large-screen-container">
|
||||
<div className="col-md-9 p-0 screen-header-primary">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt="edx" className="logo position-absolute" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Image alt={getConfig().SITE_NAME} className="logo position-absolute" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<LargeScreenLeftLayout />
|
||||
<LargeScreenLeftLayout experimentName={experimentName} isRegistrationPage={isRegistrationPage} />
|
||||
</div>
|
||||
<div className="col-md-3 p-0 screen-polygon">
|
||||
<svg
|
||||
@@ -28,4 +29,14 @@ const LargeLayout = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
LargeLayout.defaultProps = {
|
||||
experimentName: '',
|
||||
isRegistrationPage: false,
|
||||
};
|
||||
|
||||
LargeLayout.propTypes = {
|
||||
experimentName: PropTypes.string,
|
||||
isRegistrationPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default LargeLayout;
|
||||
|
||||
@@ -1,30 +1,84 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import PropTypes from 'prop-types';
|
||||
import ClipboardJS from 'clipboard';
|
||||
|
||||
import { faCut } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Toast } from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
import SideDiscountBanner from './SideDiscountBanner';
|
||||
|
||||
const LargeLeftLayout = (props) => {
|
||||
const { intl } = props;
|
||||
const { intl, isRegistrationPage, experimentName } = props;
|
||||
const [showToast, setToastShow] = useState(false);
|
||||
new ClipboardJS('.copyIcon'); // eslint-disable-line no-new
|
||||
|
||||
return (
|
||||
<div className="min-vh-100 pr-0 mt-lg-n2 d-flex align-items-center">
|
||||
<svg className="large-screen-svg-line ml-5">
|
||||
<Toast
|
||||
onClose={() => setToastShow(false)}
|
||||
show={showToast}
|
||||
>
|
||||
{intl.formatMessage(messages['code.copied'])}
|
||||
</Toast>
|
||||
<svg className={classNames(
|
||||
'large-screen-svg-line',
|
||||
{
|
||||
'variation1-bar-color mt-n6 pt-0 ml-5': experimentName === 'variation1' && isRegistrationPage,
|
||||
'variation2-bar-color': experimentName === 'variation2' && isRegistrationPage,
|
||||
'ml-5': experimentName !== 'variation1' || !isRegistrationPage,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<line x1="50" y1="0" x2="10" y2="215" />
|
||||
</svg>
|
||||
<h1 className="large-heading">
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<span className="text-accent-a"><br />
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
<div className={classNames({ 'pl-4': experimentName === 'variation1' && isRegistrationPage })}>
|
||||
<h1 className={classNames('large-heading', { 'mb-4.5': experimentName === 'variation1' && isRegistrationPage })}>
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<span
|
||||
className={((experimentName === 'variation1' || experimentName === 'variation2') && isRegistrationPage) ? 'text-accent-b' : 'text-accent-a'}
|
||||
>
|
||||
<br />
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
{experimentName === 'variation1' && isRegistrationPage ? (
|
||||
<span className="text-light-300 dicount-heading">
|
||||
<span className="lead mr-3">
|
||||
<SideDiscountBanner />
|
||||
</span>
|
||||
<span className="dashed-border d-inline-flex flex-wrap align-items-center">
|
||||
<span id="edx-welcome" className="text-white edx-welcome font-weight-bold mr-1">EDXWELCOME</span>
|
||||
<FontAwesomeIcon
|
||||
className="text-light-700 copyIcon ml-1.5 hover-discount-icon"
|
||||
icon={faCut}
|
||||
data-clipboard-action="copy"
|
||||
data-clipboard-target="#edx-welcome"
|
||||
onClick={() => setToastShow(true)}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LargeLeftLayout.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
experimentName: PropTypes.string,
|
||||
isRegistrationPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
LargeLeftLayout.defaultProps = {
|
||||
experimentName: '',
|
||||
isRegistrationPage: false,
|
||||
};
|
||||
|
||||
export default injectIntl(LargeLeftLayout);
|
||||
|
||||
@@ -1,31 +1,81 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import { Hyperlink, Image, Toast } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ClipboardJS from 'clipboard';
|
||||
|
||||
import { faCut } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import messages from './messages';
|
||||
import SideDiscountBanner from './SideDiscountBanner';
|
||||
|
||||
const MediumLayout = (props) => {
|
||||
const { intl } = props;
|
||||
const { intl, isRegistrationPage, experimentName } = props;
|
||||
const [showToast, setToastShow] = useState(false);
|
||||
new ClipboardJS('.copyIcon'); // eslint-disable-line no-new
|
||||
|
||||
return (
|
||||
<div className="container row p-0 mb-3 medium-screen-container">
|
||||
<div className={classNames(
|
||||
'container row p-0 mb-3 medium-screen-container',
|
||||
{
|
||||
'variation1-medium-screen': experimentName === 'variation1' && isRegistrationPage,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Toast
|
||||
onClose={() => setToastShow(false)}
|
||||
show={showToast}
|
||||
>
|
||||
{intl.formatMessage(messages['code.copied'])}
|
||||
</Toast>
|
||||
<div className="col-md-10 p-0 screen-header-primary">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="row mt-4 justify-content-center">
|
||||
<svg className="medium-screen-svg-line pl-5">
|
||||
<svg className={classNames(
|
||||
'medium-screen-svg-line pl-5',
|
||||
{
|
||||
'variation1-bar-color': experimentName === 'variation1' && isRegistrationPage,
|
||||
'variation2-bar-color': experimentName === 'variation2' && isRegistrationPage,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<line x1="50" y1="0" x2="10" y2="215" />
|
||||
</svg>
|
||||
<h1 className="medium-heading pb-4">
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<span className="text-accent-a"><br />
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
<div className="pb-4">
|
||||
<h1 className="medium-heading">
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<span
|
||||
className={((experimentName === 'variation1' || experimentName === 'variation2') && isRegistrationPage) ? 'text-accent-b' : 'text-accent-a'}
|
||||
>
|
||||
<br />
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
{experimentName === 'variation1' && isRegistrationPage ? (
|
||||
<div className="text-light-300 pl-3">
|
||||
<SideDiscountBanner />
|
||||
<span className="dashed-border h5 text-white">
|
||||
<span id="edx-welcome" className="edx-welcome">EDXWELCOME </span>
|
||||
<FontAwesomeIcon
|
||||
className="text-light-700 copyIcon ml-1 hover-discount-icon"
|
||||
icon={faCut}
|
||||
data-clipboard-action="copy"
|
||||
data-clipboard-target="#edx-welcome"
|
||||
onClick={() => setToastShow(true)}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div className="col-md-2 p-0 screen-polygon">
|
||||
<svg width="100%" height="100%" className="medium-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
|
||||
@@ -40,6 +90,13 @@ const MediumLayout = (props) => {
|
||||
|
||||
MediumLayout.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
experimentName: PropTypes.string,
|
||||
isRegistrationPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
MediumLayout.defaultProps = {
|
||||
experimentName: '',
|
||||
isRegistrationPage: false,
|
||||
};
|
||||
|
||||
export default injectIntl(MediumLayout);
|
||||
|
||||
38
src/base-component/SideDiscountBanner.jsx
Normal file
38
src/base-component/SideDiscountBanner.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
export default function SideDiscountBanner() {
|
||||
const getDiscountText = () => (
|
||||
<span className="text-accent-a h3">
|
||||
15% <FormattedMessage
|
||||
id="side.discount.message.15.off"
|
||||
defaultMessage="off"
|
||||
description="Text used with discounts e.g. 15% off"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
const getCerificateMsg = () => (
|
||||
<span className="dicount-heading">
|
||||
<FormattedMessage
|
||||
id="certificate.message"
|
||||
defaultMessage="certificate* with code"
|
||||
description="Text with certificate"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<span className="mr-1.5">
|
||||
<FormattedMessage
|
||||
id="side.discount.message.body"
|
||||
defaultMessage="Get {discountText} your first verified {lineBreak} {certificateMsg}"
|
||||
description="Message body for edX discount"
|
||||
values={{
|
||||
discountText: getDiscountText(),
|
||||
lineBreak: <br />,
|
||||
certificateMsg: getCerificateMsg(),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +1,74 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
import { Hyperlink, Image, Toast } from '@edx/paragon';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ClipboardJS from 'clipboard';
|
||||
|
||||
import { faCut } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import messages from './messages';
|
||||
import SideDiscountBanner from './SideDiscountBanner';
|
||||
|
||||
const SmallLayout = (props) => {
|
||||
const { intl } = props;
|
||||
const { intl, isRegistrationPage, experimentName } = props;
|
||||
const [showToast, setToastShow] = useState(false);
|
||||
new ClipboardJS('.copyIcon'); // eslint-disable-line no-new
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="small-screen-header-primary">
|
||||
<Toast
|
||||
onClose={() => setToastShow(false)}
|
||||
show={showToast}
|
||||
>
|
||||
{intl.formatMessage(messages['code.copied'])}
|
||||
</Toast>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex mt-3">
|
||||
<svg className="small-screen-svg-line">
|
||||
<svg className={classNames(
|
||||
'small-screen-svg-line',
|
||||
{
|
||||
'variation1-bar-color': experimentName === 'variation1' && isRegistrationPage,
|
||||
'variation2-bar-color': experimentName === 'variation2' && isRegistrationPage,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<line x1="55" y1="0" x2="40" y2="65" />
|
||||
</svg>
|
||||
<h1 className="small-heading pb-3">
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<br />
|
||||
<span className="text-accent-a">
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
<div className="pb-3">
|
||||
<h1 className="small-heading">
|
||||
{intl.formatMessage(messages['start.learning'])}
|
||||
<br />
|
||||
<span
|
||||
className={((experimentName === 'variation1' || experimentName === 'variation2') && isRegistrationPage) ? 'text-accent-b' : 'text-accent-a'}
|
||||
>
|
||||
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
</span>
|
||||
</h1>
|
||||
{(experimentName === 'variation1' && isRegistrationPage) ? (
|
||||
<div className="small text-light-300 pl-2">
|
||||
<SideDiscountBanner />
|
||||
<span className="dashed-border h6 text-white d-inline-flex flex-wrap align-items-center">
|
||||
<span id="edx-welcome" className="edx-welcome mr-1">EDXWELCOME</span>
|
||||
<FontAwesomeIcon
|
||||
className="text-light-700 copyIcon ml-1 hover-discount-icon"
|
||||
icon={faCut}
|
||||
data-clipboard-action="copy"
|
||||
data-clipboard-target="#edx-welcome"
|
||||
onClick={() => setToastShow(true)}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -34,6 +77,14 @@ const SmallLayout = (props) => {
|
||||
|
||||
SmallLayout.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
experimentName: PropTypes.string,
|
||||
isRegistrationPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
SmallLayout.defaultProps = {
|
||||
experimentName: '',
|
||||
isRegistrationPage: false,
|
||||
|
||||
};
|
||||
|
||||
export default injectIntl(SmallLayout);
|
||||
|
||||
@@ -11,6 +11,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'with {siteName}',
|
||||
description: 'Header text with site name for logistration MFE pages',
|
||||
},
|
||||
'code.copied': {
|
||||
id: 'code.copied',
|
||||
defaultMessage: 'Code copied',
|
||||
description: 'part of 15% discount code copied',
|
||||
},
|
||||
// authenticated user base component text
|
||||
'complete.your.profile.1': {
|
||||
id: 'complete.your.profile.1',
|
||||
|
||||
@@ -24,6 +24,7 @@ const FormGroup = (props) => {
|
||||
<Form.Group controlId={props.name} className={props.className} isInvalid={props.errorMessage !== ''}>
|
||||
<Form.Control
|
||||
as={props.as}
|
||||
readOnly={props.readOnly}
|
||||
type={props.type}
|
||||
className="form-field"
|
||||
autoComplete={props.autoComplete}
|
||||
@@ -65,6 +66,7 @@ FormGroup.defaultProps = {
|
||||
errorMessage: '',
|
||||
borderClass: '',
|
||||
autoComplete: null,
|
||||
readOnly: false,
|
||||
handleBlur: null,
|
||||
handleChange: () => {},
|
||||
handleFocus: null,
|
||||
@@ -82,6 +84,7 @@ FormGroup.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
borderClass: PropTypes.string,
|
||||
autoComplete: PropTypes.string,
|
||||
readOnly: PropTypes.bool,
|
||||
floatingLabel: PropTypes.string.isRequired,
|
||||
handleBlur: PropTypes.func,
|
||||
handleChange: PropTypes.func,
|
||||
|
||||
@@ -70,7 +70,7 @@ const LogistrationDefaultProps = {
|
||||
};
|
||||
const LogistrationProps = {
|
||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequried,
|
||||
name: PropTypes.string.isRequired,
|
||||
loginUrl: PropTypes.string.isRequired,
|
||||
})),
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthService } from '@edx/frontend-platform/auth';
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
@@ -24,6 +26,13 @@ const Logistration = (props) => {
|
||||
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') {
|
||||
@@ -52,7 +61,7 @@ const Logistration = (props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseComponent>
|
||||
<BaseComponent isRegistrationPage={selectedPage === REGISTER_PAGE}>
|
||||
<div>
|
||||
{institutionLogin
|
||||
? (
|
||||
|
||||
@@ -40,15 +40,15 @@ const PasswordField = (props) => {
|
||||
const tooltip = (
|
||||
<Tooltip id={`password-requirement-${placement}`}>
|
||||
<span id="letter-check" className="d-flex position-relative align-content-start">
|
||||
{LETTER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1" src={Remove} />}
|
||||
{LETTER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1 text-light-700" src={Remove} />}
|
||||
{formatMessage(messages['one.letter'])}
|
||||
</span>
|
||||
<span id="number-check" className="d-flex position-relative align-content-start">
|
||||
{NUMBER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1" src={Remove} />}
|
||||
{NUMBER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1 text-light-700" src={Remove} />}
|
||||
{formatMessage(messages['one.number'])}
|
||||
</span>
|
||||
<span id="characters-check" className="d-flex position-relative align-content-start">
|
||||
{props.value.length >= 8 ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1" src={Remove} />}
|
||||
{props.value.length >= 8 ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1 text-light-700" src={Remove} />}
|
||||
{formatMessage(messages['eight.characters'])}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -42,7 +42,7 @@ function SocialAuthProviders(props) {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<span id="provider-name" className="mr-auto pl-2" aria-hidden="true">{provider.name}</span>
|
||||
<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 })
|
||||
|
||||
@@ -74,9 +74,9 @@ describe('PasswordField', () => {
|
||||
});
|
||||
passwordField.update();
|
||||
|
||||
expect(passwordField.find('#letter-check span').prop('className')).toEqual('pgn__icon mr-1');
|
||||
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon mr-1');
|
||||
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon mr-1');
|
||||
expect(passwordField.find('#letter-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
|
||||
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
|
||||
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
|
||||
});
|
||||
|
||||
it('should update password requirement checks', async () => {
|
||||
|
||||
@@ -32,7 +32,7 @@ exports[`SocialAuthProviders should match social auth provider with default icon
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="mr-auto pl-2"
|
||||
className="notranslate mr-auto pl-2"
|
||||
id="provider-name"
|
||||
>
|
||||
Apple
|
||||
@@ -77,7 +77,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="mr-auto pl-2"
|
||||
className="notranslate mr-auto pl-2"
|
||||
id="provider-name"
|
||||
>
|
||||
Apple
|
||||
@@ -110,7 +110,7 @@ Array [
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="mr-auto pl-2"
|
||||
className="notranslate mr-auto pl-2"
|
||||
id="provider-name"
|
||||
>
|
||||
Apple
|
||||
@@ -139,7 +139,7 @@ Array [
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="mr-auto pl-2"
|
||||
className="notranslate mr-auto pl-2"
|
||||
id="provider-name"
|
||||
>
|
||||
Facebook
|
||||
|
||||
@@ -13,7 +13,7 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
|
||||
className="alert-message-content"
|
||||
>
|
||||
<p>
|
||||
You have successfully signed into Google, but your Google account does not have a linked edX account. To link your accounts, sign in now using your edX password.
|
||||
You have successfully signed into Google, but your Google account does not have a linked Your Platform Name Here account. To link your accounts, sign in now using your Your Platform Name Here password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@ exports[`ThirdPartyAuthAlert should match register page third party auth alert m
|
||||
Almost done!
|
||||
</div>
|
||||
<p>
|
||||
You've successfully signed into Google! We just need a little more information before you start learning with edX.
|
||||
You've successfully signed into Google! We just need a little more information before you start learning with Your Platform Name Here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { applyMiddleware, createStore, compose } from 'redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import { composeWithDevTools } from '@redux-devtools/extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export const API_RATELIMIT_ERROR = 'api-ratelimit-error';
|
||||
export const DEFAULT_STATE = 'default';
|
||||
export const PENDING_STATE = 'pending';
|
||||
export const COMPLETE_STATE = 'complete';
|
||||
export const FAILURE_STATE = 'failure';
|
||||
|
||||
// Regex
|
||||
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
|
||||
@@ -27,7 +28,8 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
|
||||
+ '|\\[(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 VALID_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'];
|
||||
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'save_for_later', 'register_for_free'];
|
||||
|
||||
24
src/data/utils/useMobileResponsive.js
Normal file
24
src/data/utils/useMobileResponsive.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { breakpoints } from '@edx/paragon';
|
||||
|
||||
/**
|
||||
* A react hook used to determine if the current window is mobile or not.
|
||||
* returns true if the window is of mobile size.
|
||||
* Code source: https://github.com/edx/prospectus/blob/master/src/utils/useMobileResponsive.js
|
||||
*/
|
||||
const useMobileResponsive = (breakpoint) => {
|
||||
const [isMobileWindow, setIsMobileWindow] = useState();
|
||||
const checkForMobile = () => {
|
||||
setIsMobileWindow(window.matchMedia(`(max-width: ${breakpoint || breakpoints.small.maxWidth}px)`).matches);
|
||||
};
|
||||
useEffect(() => {
|
||||
checkForMobile();
|
||||
window.addEventListener('resize', checkForMobile);
|
||||
// return this function here to clean up the event listener
|
||||
return () => window.removeEventListener('resize', checkForMobile);
|
||||
}, []);
|
||||
return isMobileWindow;
|
||||
};
|
||||
|
||||
export default useMobileResponsive;
|
||||
98
src/field-renderer/FieldRenderer.jsx
Normal file
98
src/field-renderer/FieldRenderer.jsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { Form, Icon } from '@edx/paragon';
|
||||
import { ExpandMore } from '@edx/paragon/icons';
|
||||
|
||||
const FormFieldRenderer = (props) => {
|
||||
let formField = null;
|
||||
const { fieldData, onChangeHandler, value } = props;
|
||||
|
||||
switch (fieldData.type) {
|
||||
case 'select': {
|
||||
if (!fieldData.options) {
|
||||
return null;
|
||||
}
|
||||
formField = (
|
||||
<Form.Group controlId={fieldData.name} className="mb-3">
|
||||
<Form.Control
|
||||
as="select"
|
||||
name={fieldData.name}
|
||||
value={value}
|
||||
onChange={(e) => onChangeHandler(e)}
|
||||
trailingElement={<Icon src={ExpandMore} />}
|
||||
floatingLabel={fieldData.label}
|
||||
>
|
||||
<option key="default" value="">{fieldData.label}</option>
|
||||
{fieldData.options.map(option => (
|
||||
<option className="data-hj-suppress" key={option[0]} value={option[0]}>{option[1]}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'textarea': {
|
||||
formField = (
|
||||
<Form.Group controlId={fieldData.name} className="mb-3">
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
name={fieldData.name}
|
||||
value={value}
|
||||
onChange={(e) => onChangeHandler(e)}
|
||||
floatingLabel={fieldData.label}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'text': {
|
||||
formField = (
|
||||
<Form.Group controlId={fieldData.name} className="mb-3">
|
||||
<Form.Control
|
||||
name={fieldData.name}
|
||||
value={value}
|
||||
onChange={(e) => onChangeHandler(e)}
|
||||
floatingLabel={fieldData.label}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'checkbox': {
|
||||
formField = (
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Checkbox
|
||||
id={fieldData.name}
|
||||
checked={!!value}
|
||||
name={fieldData.name}
|
||||
value={value}
|
||||
onChange={(e) => onChangeHandler(e)}
|
||||
>
|
||||
{fieldData.label}
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return formField;
|
||||
};
|
||||
FormFieldRenderer.defaultProps = {
|
||||
value: '',
|
||||
};
|
||||
|
||||
FormFieldRenderer.propTypes = {
|
||||
fieldData: PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
}).isRequired,
|
||||
onChangeHandler: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default FormFieldRenderer;
|
||||
1
src/field-renderer/index.jsx
Normal file
1
src/field-renderer/index.jsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './FieldRenderer';
|
||||
105
src/field-renderer/tests/FieldRenderer.test.jsx
Normal file
105
src/field-renderer/tests/FieldRenderer.test.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import FieldRenderer from '../FieldRenderer';
|
||||
|
||||
describe('FieldRendererTests', () => {
|
||||
let value = '';
|
||||
|
||||
const changeHandler = (e) => {
|
||||
if (e.target.type === 'checkbox') {
|
||||
value = e.target.checked;
|
||||
} else {
|
||||
value = e.target.value;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
value = '';
|
||||
});
|
||||
|
||||
it('should render select field type', () => {
|
||||
const fieldData = {
|
||||
type: 'select',
|
||||
label: 'Year of Birth',
|
||||
name: 'yob-field',
|
||||
options: [['1997', 1997], ['1998', 1998]],
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('select#yob-field');
|
||||
field.simulate('change', { target: { value: 1997 } });
|
||||
|
||||
expect(field.type()).toEqual('select');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('Year of Birth');
|
||||
expect(value).toEqual(1997);
|
||||
});
|
||||
|
||||
it('should return null if no options are provided for select field', () => {
|
||||
const fieldData = {
|
||||
type: 'select',
|
||||
label: 'Year of Birth',
|
||||
name: 'yob-field',
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
expect(fieldRenderer.html()).toBeNull();
|
||||
});
|
||||
|
||||
it('should render textarea field', () => {
|
||||
const fieldData = {
|
||||
type: 'textarea',
|
||||
label: 'Why do you want to join this platform?',
|
||||
name: 'goals-field',
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('#goals-field').last();
|
||||
field.simulate('change', { target: { value: 'These are my goals.' } });
|
||||
|
||||
expect(field.type()).toEqual('textarea');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('Why do you want to join this platform?');
|
||||
expect(value).toEqual('These are my goals.');
|
||||
});
|
||||
|
||||
it('should render an input field', () => {
|
||||
const fieldData = {
|
||||
type: 'text',
|
||||
label: 'Company',
|
||||
name: 'company-field',
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('#company-field').last();
|
||||
field.simulate('change', { target: { value: 'ABC' } });
|
||||
|
||||
expect(field.type()).toEqual('input');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('Company');
|
||||
expect(value).toEqual('ABC');
|
||||
});
|
||||
|
||||
it('should render checkbox field', () => {
|
||||
const fieldData = {
|
||||
type: 'checkbox',
|
||||
label: 'I agree that edX may send me marketing messages.',
|
||||
name: 'marketing-emails-opt-in-field',
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('input#marketing-emails-opt-in-field');
|
||||
field.simulate('change', { target: { checked: true, type: 'checkbox' } });
|
||||
|
||||
expect(field.prop('type')).toEqual('checkbox');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('I agree that edX may send me marketing messages.');
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return null if field type is unknown', () => {
|
||||
const fieldData = {
|
||||
type: 'unknown',
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
expect(fieldRenderer.html()).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,10 @@ import caMessages from './messages/ca.json';
|
||||
import es419Messages from './messages/es_419.json';
|
||||
import frMessages from './messages/fr.json';
|
||||
import zhcnMessages from './messages/zh_CN.json';
|
||||
import ititMessages from './messages/it_IT.json';
|
||||
import ptptMessages from './messages/pt_PT.json';
|
||||
import dedeMessages from './messages/de_DE.json';
|
||||
import hiMessages from './messages/hi.json';
|
||||
import heMessages from './messages/he.json';
|
||||
import idMessages from './messages/id.json';
|
||||
import kokrMessages from './messages/ko_kr.json';
|
||||
@@ -19,6 +23,9 @@ const messages = {
|
||||
'es-419': es419Messages,
|
||||
fr: frMessages,
|
||||
'zh-cn': zhcnMessages,
|
||||
'it-it': ititMessages,
|
||||
'pt-pt': ptptMessages,
|
||||
'de-de': dedeMessages,
|
||||
ca: caMessages,
|
||||
he: heMessages,
|
||||
id: idMessages,
|
||||
@@ -28,6 +35,7 @@ const messages = {
|
||||
ru: ruMessages,
|
||||
th: thMessages,
|
||||
uk: ukMessages,
|
||||
hi: hiMessages,
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
@@ -90,9 +96,13 @@
|
||||
"sign.in.heading": "Sign in",
|
||||
"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.already.activated.message": "This account has already been activated.",
|
||||
"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",
|
||||
"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",
|
||||
@@ -102,28 +112,39 @@
|
||||
"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",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).",
|
||||
"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.",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
@@ -148,9 +169,10 @@
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Available:",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
|
||||
221
src/i18n/messages/de_DE.json
Normal file
221
src/i18n/messages/de_DE.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
"forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.sso.button.title": "Sign in using {providerName}",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"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}.",
|
||||
"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.invalid.email": "An error occurred.",
|
||||
"forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.",
|
||||
"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 edX 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.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"login.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",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Need help signing in?",
|
||||
"forgot.password.link": "Forgot my password",
|
||||
"forgot.password": "Forgot password",
|
||||
"other.sign.in.issues": "Other sign in issues",
|
||||
"need.other.help.signing.in.collapsible.menu": "Need other help signing in?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"institution.login.page.back.button": "Back to sign in",
|
||||
"create.an.account": "Create an account",
|
||||
"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.",
|
||||
"first.time.here": "First time here?",
|
||||
"email.help.message": "The email address you used to register with edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "The email address you've provided isn't formatted correctly.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Create an account",
|
||||
"sign.in.heading": "Sign in",
|
||||
"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",
|
||||
"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.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.",
|
||||
"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",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"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.",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"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.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"registration.year.of.birth.label": "Year of birth (optional)",
|
||||
"registration.field.gender.options.label": "Gender (optional)",
|
||||
"registration.goals.label": "Tell us why you're interested in edX (optional)",
|
||||
"registration.field.gender.options.f": "Female",
|
||||
"registration.field.gender.options.m": "Male",
|
||||
"registration.field.gender.options.o": "Other/Prefer not to say",
|
||||
"registration.field.education.levels.label": "Highest level of education completed (optional)",
|
||||
"registration.field.education.levels.p": "Doctorate",
|
||||
"registration.field.education.levels.m": "Master's or professional degree",
|
||||
"registration.field.education.levels.b": "Bachelor's degree",
|
||||
"registration.field.education.levels.a": "Associate's degree",
|
||||
"registration.field.education.levels.hs": "Secondary/high school",
|
||||
"registration.field.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "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",
|
||||
"forgot.password.confirmation.sign.in.link": "sign in",
|
||||
"reset.password.request.forgot.password.text": "Forgot password",
|
||||
"reset.password.request.invalid.token.header": "Invalid password reset link",
|
||||
"reset.password.empty.new.password.field.error": "Please enter your new password.",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Failed to reset password",
|
||||
"reset.password.token.validation.sever.error": "Token validation failure",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Gender (optional)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Other/Prefer not to say",
|
||||
"education.levels.label": "Highest level of education completed (optional)",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Secondary/high school",
|
||||
"education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Year of birth (optional)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
{
|
||||
"top.discount.message.15.off": "de descuento",
|
||||
"top.discount.message.body": "Obtén un {discount} en tu primer certificado* con un código",
|
||||
"start.learning": "Empieza a aprender",
|
||||
"with.site.name": "con {siteName}",
|
||||
"code.copied": "Código copiado",
|
||||
"complete.your.profile.1": "Completado",
|
||||
"complete.your.profile.2": "tu perfil ",
|
||||
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "de descuento",
|
||||
"certificate.message": "Certificado* con código",
|
||||
"side.discount.message.body": "Obtén {discountText} tu primer {lineBreak} {certificateMsg} verificado",
|
||||
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
|
||||
"forgot.password.confirmation.title": "Verifica tu correo electrónico",
|
||||
"forgot.password.confirmation.support.link": "contacta con el equipo de soporte técnico",
|
||||
@@ -90,9 +96,13 @@
|
||||
"sign.in.heading": "Iniciar sesión",
|
||||
"account.activation.success.message.title": "Ha sido un éxito. Has activado tu cuenta.",
|
||||
"account.activation.success.message": "Ahora recibirás por correo electrónico actualizaciones y alertas relacionadas con los cursos en los que estás inscrito. Inicia sesión para continuar.",
|
||||
"account.already.activated.message": "La cuenta ya ha sido activada.",
|
||||
"account.activation.info.message": "La cuenta ya ha sido activada.",
|
||||
"account.activation.error.message.title": "Tu cuenta no ha podido ser activada",
|
||||
"account.activation.support.link": "contacta al equipo de soporte de edX",
|
||||
"account.confirmation.success.message.title": "¡Éxito! Has confirmado tu correo electrónico.",
|
||||
"account.confirmation.success.message": "Inicia sesión para continuar.",
|
||||
"account.confirmation.info.message": "Este correo electrónico ya ha sido confirmado.",
|
||||
"account.confirmation.error.message.title": "Tu correo electrónico no pudo ser confirmado",
|
||||
"login.rate.limit.reached.message": "Demasiados intentos fallidos de inicio de sesión. Inténtelo de nuevo más tarde.",
|
||||
"login.failure.header.title": "No se ha podido iniciar tu sesión.",
|
||||
"contact.support.link": "entrar en contacto con el soporte de {platformName}",
|
||||
@@ -102,28 +112,39 @@
|
||||
"login.form.invalid.error.message": "Por favor, complete los siguientes campos.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "restablecer la contraseña",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "Pulse aquí para restablecerla.",
|
||||
"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",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Nombre completo",
|
||||
"registration.email.label": "Correo electrónico",
|
||||
"registration.username.label": "Nombre de usuario público",
|
||||
"registration.password.label": "Contraseña",
|
||||
"registration.country.label": "País/Región",
|
||||
"registration.opt.in.label": "Acepto que {siteName} pueda enviarme mensajes de marketing.",
|
||||
"help.text.name": "Este nombre será utilizado por los certificados que obtengas.",
|
||||
"help.text.username.1": "El nombre que te identificará en tus cursos.",
|
||||
"help.text.username.2": "Esto no puede modificarse posteriormente.",
|
||||
"help.text.email": "Para la activación de la cuenta y las actualizaciones importantes",
|
||||
"create.account.button": "Crear una cuenta",
|
||||
"create.account.for.free.button": "Crea una cuenta gratis",
|
||||
"create.an.account.btn.pending.state": "Cargando",
|
||||
"registration.other.options.heading": "O regístrese con:",
|
||||
"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",
|
||||
"empty.email.field.error": "Introduce tu email",
|
||||
"empty.username.field.error": "El nombre de usuario debe tener entre 2 y 30 caracteres",
|
||||
"empty.password.field.error": "No se han cumplido los criterios de la contraseña",
|
||||
"empty.country.field.error": "Selecciona tu país o región de residencia",
|
||||
"email.invalid.format.error": "Introduce una dirección de correo electrónico válida",
|
||||
"email.ratelimit.less.chars.validation.message": "El correo electrónico debe tener 3 caracteres.",
|
||||
"username.validation.message": "El nombre de usuario debe tener entre 2 y 30 caracteres",
|
||||
"username.format.validation.message": "Los nombres de usuario únicamente pueden contener las letras (A-Z, a-z), números (0-9), guión bajo (_) y guiones (-).",
|
||||
"name.validation.message": "Introduce un nombre válido",
|
||||
"username.format.validation.message": "Los nombres de usuario solo pueden contener letras (A-Z, a-z), números (0-9), guiones bajos (_) y guiones (-). Los nombres de usuario no pueden contener espacios.",
|
||||
"support.education.research": "Apoya la investigación sobre educación proporcionando información adicional. (Opcional)",
|
||||
"registration.request.failure.header": "No pudimos crear tu cuenta.",
|
||||
"registration.empty.form.submission.error": "Por favor, verifica tus respuestas y vuelve a intentarlo.",
|
||||
@@ -148,9 +169,10 @@
|
||||
"registration.field.education.levels.el": "Enseñanza primaria",
|
||||
"registration.field.education.levels.none": "Ninguna educación formal",
|
||||
"registration.field.education.levels.other": "Otra educación",
|
||||
"registration.username.suggestion.label": "Disponible:",
|
||||
"registration.username.suggestion.label": "Se recomienda:",
|
||||
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
|
||||
"did.you.mean.alert.text": "¿Quieres decir",
|
||||
"certificate.msg": "*No se incluyen los programas de: MicroMasters en Analytics: Essential Tools and Methods de GTx, Certificación Profesional de Corporate Finance de ColumbiaX o cursos o programas ofrecidos por Wharton y NYIF en esta oferta.",
|
||||
"register.page.terms.of.service.and.honor.code": "Al crear una cuenta, aceptas el {tosAndHonorCode} y reconoces que {platformName} y cada\n Miembro procesa tus datos personales de acuerdo con la {privacyPolicy}.",
|
||||
"sign.in": "Iniciar sesión",
|
||||
"reset.password.page.title": "Restablecer contraseña | {siteName}",
|
||||
|
||||
@@ -1,199 +1,221 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
"forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.sso.button.title": "Sign in using {providerName}",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"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}.",
|
||||
"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.",
|
||||
"top.discount.message.15.off": "arrêt",
|
||||
"top.discount.message.body": "Obtenez {discount} votre première attestation vérifiée* avec ce code",
|
||||
"start.learning": "Démarrer l'apprentissage",
|
||||
"with.site.name": "avec {siteName}",
|
||||
"code.copied": "Code copié",
|
||||
"complete.your.profile.1": "Terminé",
|
||||
"complete.your.profile.2": "votre profil",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "arrêt",
|
||||
"certificate.message": "attestation* avec code",
|
||||
"side.discount.message.body": "Obtenez {discountText} votre première {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"forgot.password.confirmation.title": "Vérifiez votre email",
|
||||
"forgot.password.confirmation.support.link": "contacter le support technique",
|
||||
"forgot.password.confirmation.info": "Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez entré la bonne adresse courriel ou vérifiez votre dossier de pourriels.",
|
||||
"logistration.sign.in": "Connectez-vous",
|
||||
"logistration.register": "S'inscrire",
|
||||
"internal.server.error.message": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"server.ratelimit.error.message": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps.",
|
||||
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName} ?",
|
||||
"enterprisetpa.sso.button.title": "Connectez-vous avec {providerName}",
|
||||
"enterprisetpa.login.button.text": "Montrez-moi d'autres méthodes pour me connecter ou m'inscrire",
|
||||
"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 nombre",
|
||||
"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 fini !",
|
||||
"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}.",
|
||||
"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}",
|
||||
"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": "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.page.submit.button": "Envoyez",
|
||||
"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": "Your previous request is in progress, please try again in a few moments.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.invalid.email": "An error occurred.",
|
||||
"forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.",
|
||||
"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 edX 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.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"login.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",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Need help signing in?",
|
||||
"forgot.password.link": "Forgot my password",
|
||||
"forgot.password": "Forgot password",
|
||||
"other.sign.in.issues": "Other sign in issues",
|
||||
"need.other.help.signing.in.collapsible.menu": "Need other help signing in?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"institution.login.page.back.button": "Back to sign in",
|
||||
"create.an.account": "Create an account",
|
||||
"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.",
|
||||
"first.time.here": "First time here?",
|
||||
"email.help.message": "The email address you used to register with edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "The email address you've provided isn't formatted correctly.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Create an account",
|
||||
"sign.in.heading": "Sign in",
|
||||
"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.already.activated.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"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.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.",
|
||||
"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.",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"forgot.password.empty.email.field.error": "Saisissez votre courriel",
|
||||
"forgot.password.invalid.email": "Une erreur est survenue.",
|
||||
"forgot.password.invalid.email.message": "L'adresse email que vous avez fournie est incorrecte.",
|
||||
"forgot.password.email.help.text": "L'adresse courriel que vous avez utilisée pour vous inscrire sur {platformName}",
|
||||
"confirmation.message.title": "Vérifiez votre email",
|
||||
"confirmation.support.link": "contacter le support technique",
|
||||
"need.help.sign.in.text": "Besoin d'aide pour vous enregistrer?",
|
||||
"additional.help.text": "Pour une aide supplémentaire, contactez le support edX à ",
|
||||
"sign.in.text": "Connectez-vous",
|
||||
"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.",
|
||||
"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.",
|
||||
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
||||
"login.inactive.user.error": "Pour vous connecter, vous devez activer votre compte.{lineBreak}\n {lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel,\n vérifiez vos dossiers de spam ou {supportLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré est incorrect. Vous avez {remainingAttempts} tentatives\n de connexion avant que votre compte soit temporairement verrouillé.",
|
||||
"login.incorrect.credentials.error.attempts.text.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": "Connectez-vous",
|
||||
"sign.in.btn.pending.state": "Chargement en cours",
|
||||
"need.help.signing.in.collapsible.menu": "Besoin d'aide pour vous enregistrer?",
|
||||
"forgot.password.link": "J'ai oublié mon mot de passe",
|
||||
"forgot.password": "Mot de passe oublié",
|
||||
"other.sign.in.issues": "Autres problèmes de connexion",
|
||||
"need.other.help.signing.in.collapsible.menu": "Encore besoin d'aide pour vous enregistrer?",
|
||||
"institution.login.button": "Identifiants de l'établissement/du campus",
|
||||
"institution.login.page.title": "Connectez vous avec les crédentiels d'institution ou de campus",
|
||||
"institution.login.page.back.button": "Retour à la connexion",
|
||||
"create.an.account": "Créer un compte",
|
||||
"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.",
|
||||
"first.time.here": "C'est votre première visite ?",
|
||||
"email.help.message": "L'adresse électronique que vous avez utilisée pour vous inscrire à edX.",
|
||||
"enterprise.login.btn.text": "Identifiants de la compagnie ou de l'école",
|
||||
"email.format.validation.message": "L'adresse email que vous avez fournie est incorrecte.",
|
||||
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 3 caractères.",
|
||||
"email.validation.message": "Entrez votre nom d'utilisateur ou votre adresse courriel",
|
||||
"password.validation.message": "Les critères de mot de passe n'ont pas été remplis",
|
||||
"register.link": "Créer un compte",
|
||||
"sign.in.heading": "Connectez-vous",
|
||||
"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": "Succès ! 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é.",
|
||||
"login.rate.limit.reached.message": "Trop de tentatives de connexion échouées. Réessayez 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.failed.attempt.error": "Il vous reste {remainingAttempts} tentatives de connexion supplémentaires avant que votre compte ne soit temporairement verrouillé.",
|
||||
"login.locked.out.error.message": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans {lockedOutPeriod} minutes.",
|
||||
"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": "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",
|
||||
"register.page.title": "S'inscrire | {siteName}",
|
||||
"registration.fullname.label": "Nom complet",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"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.button": "Create an account",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.country.field.error": "Select your country or region of residence",
|
||||
"registration.username.label": "Nom d'utilisateur public",
|
||||
"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.button": "Créer un compte",
|
||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||
"create.an.account.btn.pending.state": "Chargement en cours",
|
||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||
"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",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"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.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"registration.year.of.birth.label": "Year of birth (optional)",
|
||||
"registration.field.gender.options.label": "Gender (optional)",
|
||||
"registration.goals.label": "Tell us why you're interested in edX (optional)",
|
||||
"registration.field.gender.options.f": "Female",
|
||||
"registration.field.gender.options.m": "Male",
|
||||
"registration.field.gender.options.o": "Other/Prefer not to say",
|
||||
"registration.field.education.levels.label": "Highest level of education completed (optional)",
|
||||
"registration.field.education.levels.p": "Doctorate",
|
||||
"registration.field.education.levels.m": "Master's or professional degree",
|
||||
"registration.field.education.levels.b": "Bachelor's degree",
|
||||
"registration.field.education.levels.a": "Associate's degree",
|
||||
"registration.field.education.levels.hs": "Secondary/high school",
|
||||
"registration.field.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Available:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"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}.",
|
||||
"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",
|
||||
"forgot.password.confirmation.sign.in.link": "sign in",
|
||||
"reset.password.request.forgot.password.text": "Forgot password",
|
||||
"reset.password.request.invalid.token.header": "Invalid password reset link",
|
||||
"reset.password.empty.new.password.field.error": "Please enter your new password.",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Failed to reset password",
|
||||
"reset.password.token.validation.sever.error": "Token validation failure",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Gender (optional)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Other/Prefer not to say",
|
||||
"education.levels.label": "Highest level of education completed (optional)",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Secondary/high school",
|
||||
"education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Year of birth (optional)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
"email.ratelimit.less.chars.validation.message": "Le courriel doit comporter 3 caractères.",
|
||||
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Les noms d'utilisateur peuvent seulement contenir des lettres (A-Z, a-z), des chiffres (0-9), des tirets bas (_) et des traits d'union (-). Les noms d'utilisateur ne doivent pas contenir d'espaces.",
|
||||
"support.education.research": "Soutenez la recherche en éducation en fournissant des informations additionnelles. (Optionel)",
|
||||
"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 avec {provider} a échouée.",
|
||||
"terms.of.service.and.honor.code": "Conditions d'utilisation et Code d'honneur",
|
||||
"privacy.policy": "Politique de confidentialité",
|
||||
"registration.year.of.birth.label": "Année de naissance (facultatif)",
|
||||
"registration.field.gender.options.label": "Sexe (facultatif)",
|
||||
"registration.goals.label": "Dites-nous pourquoi vous êtes intéressé par edX (facultatif)",
|
||||
"registration.field.gender.options.f": "Femme",
|
||||
"registration.field.gender.options.m": "Homme",
|
||||
"registration.field.gender.options.o": "Autre / Préfère ne pas répondre",
|
||||
"registration.field.education.levels.label": "Plus haut niveau de scolarité atteint (facultatif)",
|
||||
"registration.field.education.levels.p": "Doctorat",
|
||||
"registration.field.education.levels.m": "Master ou diplôme professionnel",
|
||||
"registration.field.education.levels.b": "Diplôme de premier cycle supérieur",
|
||||
"registration.field.education.levels.a": "Grade de l'associé",
|
||||
"registration.field.education.levels.hs": "Lycée / enseignement secondaire",
|
||||
"registration.field.education.levels.jhs": "Collège / enseignement secondaire inférieur",
|
||||
"registration.field.education.levels.el": "Enseignement primaire",
|
||||
"registration.field.education.levels.none": "Sans diplôme",
|
||||
"registration.field.education.levels.other": "Autre niveau d'étude",
|
||||
"registration.username.suggestion.label": "Suggéré :",
|
||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||
"did.you.mean.alert.text": "Vouliez-vous dire",
|
||||
"certificate.msg": "*L'offre n'est pas éligible au programme GTx Analytics: Essential Tools and Methods MicroMasters, au programme de certificat professionnel en finance d'entreprise de ColumbiaX, ni aux cours ou programmes proposés par Wharton et NYIF.",
|
||||
"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}.",
|
||||
"sign.in": "Connectez-vous",
|
||||
"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",
|
||||
"forgot.password.confirmation.sign.in.link": "connexion",
|
||||
"reset.password.request.forgot.password.text": "Mot de passe oublié",
|
||||
"reset.password.request.invalid.token.header": "Lien de réinitialisation du mot de passe non valide",
|
||||
"reset.password.empty.new.password.field.error": "Veuillez entrer votre nouveau 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.password.request.server.error": "Échec de la réinitialisation du mot de passe",
|
||||
"reset.password.token.validation.sever.error": "Échec de la validation du jeton",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Champs optionnels | {siteName}",
|
||||
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
|
||||
"gender.options.label": "Sexe (facultatif)",
|
||||
"gender.options.f": "Femme",
|
||||
"gender.options.m": "Homme",
|
||||
"gender.options.o": "Autre / Préfère ne pas répondre",
|
||||
"education.levels.label": "Plus haut niveau de scolarité atteint (facultatif)",
|
||||
"education.levels.p": "Doctorat",
|
||||
"education.levels.m": "Master ou diplôme professionnel",
|
||||
"education.levels.b": "Diplôme de premier cycle supérieur",
|
||||
"education.levels.a": "Grade de l'associé",
|
||||
"education.levels.hs": "Lycée / enseignement secondaire",
|
||||
"education.levels.jhs": "Collège / enseignement secondaire inférieur",
|
||||
"education.levels.el": "Enseignement primaire",
|
||||
"education.levels.none": "Sans diplôme",
|
||||
"education.levels.other": "Autre niveau d'étude",
|
||||
"year.of.birth.label": "Année de naissance (facultatif)",
|
||||
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
|
||||
"optional.fields.submit.button": "Envoyez",
|
||||
"optional.fields.skip.button": "Ignorer pour l'instant",
|
||||
"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."
|
||||
}
|
||||
221
src/i18n/messages/hi.json
Normal file
221
src/i18n/messages/hi.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
"forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.sso.button.title": "Sign in using {providerName}",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"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}.",
|
||||
"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.invalid.email": "An error occurred.",
|
||||
"forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.",
|
||||
"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 edX 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.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"login.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",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Need help signing in?",
|
||||
"forgot.password.link": "Forgot my password",
|
||||
"forgot.password": "Forgot password",
|
||||
"other.sign.in.issues": "Other sign in issues",
|
||||
"need.other.help.signing.in.collapsible.menu": "Need other help signing in?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"institution.login.page.back.button": "Back to sign in",
|
||||
"create.an.account": "Create an account",
|
||||
"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.",
|
||||
"first.time.here": "First time here?",
|
||||
"email.help.message": "The email address you used to register with edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "The email address you've provided isn't formatted correctly.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Create an account",
|
||||
"sign.in.heading": "Sign in",
|
||||
"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",
|
||||
"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.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.",
|
||||
"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",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"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.",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"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.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"registration.year.of.birth.label": "Year of birth (optional)",
|
||||
"registration.field.gender.options.label": "Gender (optional)",
|
||||
"registration.goals.label": "Tell us why you're interested in edX (optional)",
|
||||
"registration.field.gender.options.f": "Female",
|
||||
"registration.field.gender.options.m": "Male",
|
||||
"registration.field.gender.options.o": "Other/Prefer not to say",
|
||||
"registration.field.education.levels.label": "Highest level of education completed (optional)",
|
||||
"registration.field.education.levels.p": "Doctorate",
|
||||
"registration.field.education.levels.m": "Master's or professional degree",
|
||||
"registration.field.education.levels.b": "Bachelor's degree",
|
||||
"registration.field.education.levels.a": "Associate's degree",
|
||||
"registration.field.education.levels.hs": "Secondary/high school",
|
||||
"registration.field.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "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",
|
||||
"forgot.password.confirmation.sign.in.link": "sign in",
|
||||
"reset.password.request.forgot.password.text": "Forgot password",
|
||||
"reset.password.request.invalid.token.header": "Invalid password reset link",
|
||||
"reset.password.empty.new.password.field.error": "Please enter your new password.",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Failed to reset password",
|
||||
"reset.password.token.validation.sever.error": "Token validation failure",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Gender (optional)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Other/Prefer not to say",
|
||||
"education.levels.label": "Highest level of education completed (optional)",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Secondary/high school",
|
||||
"education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Year of birth (optional)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
}
|
||||
221
src/i18n/messages/it_IT.json
Normal file
221
src/i18n/messages/it_IT.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contatta il supporto tecnico",
|
||||
"forgot.password.confirmation.info": "Se non ricevi un messaggio di reimpostazione della password entro 1 minuto, verifica di aver inserito l'indirizzo e-mail corretto o controlla la cartella della posta indesiderata.",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"internal.server.error.message": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"server.ratelimit.error.message": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi.",
|
||||
"enterprisetpa.title.heading": "Vuoi accedere utilizzando le credenziali {providerName}?",
|
||||
"enterprisetpa.sso.button.title": "Accedi utilizzando {providerName}",
|
||||
"enterprisetpa.login.button.text": "Mostrami altre modalità di accesso o registrazione",
|
||||
"sso.sign.in.with": "Accedi con {providerName}",
|
||||
"sso.create.account.using": "Crea un account utilizzando {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": "Hai correttamente effettuato l'accesso in {currentProvider}, ma il tuo account {currentProvider} non ha un account {platformName} ad esso abbinato. Per collegare i tuoi account accesi utilizzando la password {platformName}. ",
|
||||
"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}.",
|
||||
"error.notfound.message": "La pagina che stai cercando non è disponibile o si è verificato un errore nell'URL. Controlla l'URL e riprova. ",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\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": "Dimenticato la 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": "Inserisci un indirizzo email valido",
|
||||
"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": "Si è verificato un errore. ",
|
||||
"forgot.password.request.in.progress.message": "La tua richiesta precedente è in corso di elaborazione, riprova tra qualche istante. ",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.invalid.email": "Si è verificato un errore. ",
|
||||
"forgot.password.invalid.email.message": "L'indirizzo email che hai fornito non è formattato correttamente. ",
|
||||
"forgot.password.email.help.text": "L'indirizzo email che hai utilizzato per registrarti con {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contatta il supporto tecnico",
|
||||
"need.help.sign.in.text": "Hai bisogno di aiuto per l'accesso? ",
|
||||
"additional.help.text": "For additional help, contact edX support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Link di ripristino della password non valido",
|
||||
"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": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi.",
|
||||
"token.validation.internal.sever.error.heading": "Errore di convalida del token",
|
||||
"token.validation.internal.sever.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"internal.server.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"rate.limit.error": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi.",
|
||||
"account.activation.error.message": "Si è verificato un errore, seleziona {supportLink} per risolvere il problema. ",
|
||||
"login.inactive.user.error": "Per accedere, devi attivare il tuo account.{lineBreak} {lineBreak}Abbiamo appena inviato un link di attivazione a {email}. Se non ricevi un'email, controlla la cartella della posta indesiderata oppure seleziona {supportLink}.",
|
||||
"login.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": "Accesso | {siteName}",
|
||||
"login.user.identity.label": "Nome utente o email ",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Accedi",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Hai bisogno di aiuto per l'accesso?",
|
||||
"forgot.password.link": "Ho dimenticato la mia password",
|
||||
"forgot.password": "Password dimenticata",
|
||||
"other.sign.in.issues": "Altri problemi legati all'accesso",
|
||||
"need.other.help.signing.in.collapsible.menu": "Hai bisogno di ulteriore aiuto per l'accesso?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Accedi con le credenziali dell'istituzione/campus",
|
||||
"institution.login.page.back.button": "Torna all'accesso",
|
||||
"create.an.account": "Create an account",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "Abbiamo di recente modificato i requisiti per la password ",
|
||||
"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.",
|
||||
"first.time.here": "È la prima volta che ci visiti?",
|
||||
"email.help.message": "L'indirizzo email che hai utilizzato per registrarti con edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "L'indirizzo email che hai fornito non è formattato correttamente. ",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Crea un account",
|
||||
"sign.in.heading": "Sign in",
|
||||
"account.activation.success.message.title": "Completato correttamente! Hai attivato il tuo account. ",
|
||||
"account.activation.success.message": "A breve ti invieremo avvisi e aggiornamenti via email relativi al corso a cui ti sei iscritto. Accedi per proseguire.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Impossibile attivare il tuo account.",
|
||||
"account.activation.support.link": "contatta il supporto",
|
||||
"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",
|
||||
"login.rate.limit.reached.message": "Troppi tentativi di login falliti. Riprova più tardi.",
|
||||
"login.failure.header.title": "Impossibile autorizzare il tuo accesso.",
|
||||
"contact.support.link": "contatta il supporto {platformName} ",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.failed.attempt.error": "Hai a disposizione altri {remainingAttempts} tentativi di accesso prima che il tuo account venga temporaneamente bloccato.",
|
||||
"login.locked.out.error.message": "Il tuo account è stato temporaneamente bloccato per motivi di sicurezza. Riprova tra {lockedOutPeriod} minuti.",
|
||||
"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",
|
||||
"register.page.title": "Registrazione | {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": "Questo nome verrà utilizzato per tutti i certificati conseguiti.",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Registrati con le credenziali dell'istituzione/campus",
|
||||
"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.invalid.format.error": "Inserisci un indirizzo email valido",
|
||||
"email.ratelimit.less.chars.validation.message": "Email deve avere 3 caratteri.",
|
||||
"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.",
|
||||
"support.education.research": "Supportare la ricerca del livello di istruzione fornendo informazioni aggiuntive. (Facoltativo)",
|
||||
"registration.request.failure.header": "Impossibile creare il tuo account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"registration.rate.limit.error": "Troppi tentativi di registrazione non riusciti. Prova di nuovo più tardi.",
|
||||
"registration.tpa.session.expired": "La registrazione mediante {provider} è andata in timeout.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"registration.year.of.birth.label": "Anno di nascita (facoltativo)",
|
||||
"registration.field.gender.options.label": "Genere (facoltativo)",
|
||||
"registration.goals.label": "Dicci perché sei interessato a edX (facoltativo)",
|
||||
"registration.field.gender.options.f": "Female",
|
||||
"registration.field.gender.options.m": "Male",
|
||||
"registration.field.gender.options.o": "Altro/Preferisco non dire",
|
||||
"registration.field.education.levels.label": "Livello di istruzione più elevato raggiunto (opzionale) ",
|
||||
"registration.field.education.levels.p": "Doctorate",
|
||||
"registration.field.education.levels.m": "Master's or professional degree",
|
||||
"registration.field.education.levels.b": "Bachelor's degree",
|
||||
"registration.field.education.levels.a": "Associate's degree",
|
||||
"registration.field.education.levels.hs": "Scuola Superiore/Liceo",
|
||||
"registration.field.education.levels.jhs": "Scuola Media",
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Ripristina password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Immettere e confermare la nuova password. ",
|
||||
"new.password.label": "Nuova password",
|
||||
"confirm.password.label": "Conferma password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"forgot.password.confirmation.sign.in.link": "sign in",
|
||||
"reset.password.request.forgot.password.text": "Dimenticato la password",
|
||||
"reset.password.request.invalid.token.header": "Link di ripristino della password non valido",
|
||||
"reset.password.empty.new.password.field.error": "Immetti la nuova password.",
|
||||
"reset.password.failure.heading": "Impossibile ripristinare la tua password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Ripristino della password non riuscito",
|
||||
"reset.password.token.validation.sever.error": "Errore di convalida del token",
|
||||
"reset.server.rate.limit.error": "Troppe richieste.",
|
||||
"reset.password.success.heading": "Ripristino della password completato.",
|
||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Genere (facoltativo)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Altro/Preferisco non dire",
|
||||
"education.levels.label": "Livello di istruzione più elevato raggiunto (opzionale) ",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Scuola Superiore/Liceo",
|
||||
"education.levels.jhs": "Scuola Media",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Anno di nascita (facoltativo)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
}
|
||||
221
src/i18n/messages/pt_PT.json
Normal file
221
src/i18n/messages/pt_PT.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"top.discount.message.15.off": "desligado",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Começar a aprender",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Código copiado",
|
||||
"complete.your.profile.1": "Concluído",
|
||||
"complete.your.profile.2": "o seu perfil",
|
||||
"welcome.to.platform": "Bem vindo a {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "desligado",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Verifique o seu email",
|
||||
"forgot.password.confirmation.support.link": "contacto o suporte técnico",
|
||||
"forgot.password.confirmation.info": "Se não receber uma mensagem para alterar a palavra-passe após 1 minuto, verifique se introduziu o endereço de correio electrónico correcto, ou verifique a sua pasta de spam.",
|
||||
"logistration.sign.in": "Iniciar sessão",
|
||||
"logistration.register": "Registe-se",
|
||||
"internal.server.error.message": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||
"server.ratelimit.error.message": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo.",
|
||||
"enterprisetpa.title.heading": "Gostaria de iniciar sessão usando as suas {providerName} credenciais?",
|
||||
"enterprisetpa.sso.button.title": "Inicie a sessão utilizando {providerName}",
|
||||
"enterprisetpa.login.button.text": "Mostre-me outras formas de iniciar sessão ou registar-se",
|
||||
"sso.sign.in.with": "Inicie sessão com {providerName}",
|
||||
"sso.create.account.using": "Criar conta usando {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": "Iniciou sessão com sucesso em {currentProvider}, mas a sua conta {currentProvider} não está vinculada a uma conta {platformName}. Para vincular as suas contas, inicie sessão através da sua palavra-passe em {platformName}.",
|
||||
"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}.",
|
||||
"error.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\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": "Esqueceu a Senha | {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": "Introduzir um endereço de correio electrónico válido",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submeter",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "Ocorreu um erro.",
|
||||
"forgot.password.request.in.progress.message": "O seu pedido anterior está a ser processado, por favor tente novamente dentro de momentos.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.invalid.email": "Ocorreu um erro.",
|
||||
"forgot.password.invalid.email.message": "O endereço de email fornecido não está formatado correctamente.",
|
||||
"forgot.password.email.help.text": "O endereço de e-mail que usou para se registar em {platformName}",
|
||||
"confirmation.message.title": "Verifique o seu email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact edX 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.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"account.activation.error.message": "Alguma coisa correu mal, siga {supportLink} para resolver esta questão.",
|
||||
"login.inactive.user.error": "Para iniciar sessão, precisa ativar a sua conta. {lineBreak}\n {lineBreak} Acabámos de enviar um link de ativação para {email}. Se não receber um e-mail,\n verifique as suas pastas de spam ou {supportLink}.",
|
||||
"login.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": "Iniciar sessão | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Iniciar Sessão",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Precisa de ajuda para entrar?",
|
||||
"forgot.password.link": "Esqueci-me da minha palavra-passe",
|
||||
"forgot.password": "Forgot password",
|
||||
"other.sign.in.issues": "Outros problemas de inicio de sessão",
|
||||
"need.other.help.signing.in.collapsible.menu": "Precisa de outra ajuda para entrar?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Inicie sessão com credenciais de instituição/campus",
|
||||
"institution.login.page.back.button": "Voltar para iniciar sessão",
|
||||
"create.an.account": "Criar uma conta",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "Recentemente mudámos os nossos requisitos de palavra-passe",
|
||||
"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.",
|
||||
"first.time.here": "Está a entrar pela primeira vez?",
|
||||
"email.help.message": "O endereço de e-mail que usou para se registrar no edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "O endereço de e-mail fornecido não está formatado correctamente.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Criar uma conta",
|
||||
"sign.in.heading": "Iniciar Sessão",
|
||||
"account.activation.success.message.title": "Sucesso! Você ativou a sua conta.",
|
||||
"account.activation.success.message": "Receberá agora actualizações por e-mail e alertas nossos relacionados com os cursos em que está inscrito. Inicie a sessão para continuar.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "A sua conta não pôde ser ativada",
|
||||
"account.activation.support.link": "contato de suporte",
|
||||
"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",
|
||||
"login.rate.limit.reached.message": "Muitas tentativas de login sem sucesso. Tente novamente mais tarde.",
|
||||
"login.failure.header.title": "O seu acesso não foi possível.",
|
||||
"contact.support.link": "contactar o suporte {platformName}",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.failed.attempt.error": "Tem mais {remainingAttempts} tentativas de inicio sessão antes que a sua conta seja temporariamente bloqueada.",
|
||||
"login.locked.out.error.message": "Para proteger a sua conta, esta foi temporariamente bloqueada. Tente novamente em {lockedOutPeriod} minutos.",
|
||||
"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",
|
||||
"register.page.title": "Registar | {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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Registo com credenciais da instituição/campus",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "O e-mail deve ter 3 carateres.",
|
||||
"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.",
|
||||
"support.education.research": "Apoie a pesquisa em educação fornecendo informações adicionais. (Opcional)",
|
||||
"registration.request.failure.header": "Não foi possível criar a sua conta.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"terms.of.service.and.honor.code": "Termos de Serviço e Código de Honra",
|
||||
"privacy.policy": "Política de Privacidade",
|
||||
"registration.year.of.birth.label": "Ano de Nascimento (opcional)",
|
||||
"registration.field.gender.options.label": "Género (opcional)",
|
||||
"registration.goals.label": "Diga-nos porque está interessado no edX (opcional)",
|
||||
"registration.field.gender.options.f": "Feminino",
|
||||
"registration.field.gender.options.m": "Masculino",
|
||||
"registration.field.gender.options.o": "Outros/Prefere não dizer",
|
||||
"registration.field.education.levels.label": "Nível mais elevado de escolaridade concluído (opcional)",
|
||||
"registration.field.education.levels.p": "Doutoramento",
|
||||
"registration.field.education.levels.m": "Mestrado ou Grau Profissional",
|
||||
"registration.field.education.levels.b": "Licenciatura",
|
||||
"registration.field.education.levels.a": "Pós-graduação",
|
||||
"registration.field.education.levels.hs": "Secundário",
|
||||
"registration.field.education.levels.jhs": "2ªciclo/3ºciclo",
|
||||
"registration.field.education.levels.el": "Primária",
|
||||
"registration.field.education.levels.none": "Sem estudos",
|
||||
"registration.field.education.levels.other": "Outra educação",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Redefinir Palavra-passe | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Insira e confirme a sua nova palavra-passe.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"forgot.password.confirmation.sign.in.link": "inicie a sessão",
|
||||
"reset.password.request.forgot.password.text": "Esqueci-me da palavra-passe",
|
||||
"reset.password.request.invalid.token.header": "Link para Redefinir Palavra-passe inválido",
|
||||
"reset.password.empty.new.password.field.error": "Por favor, introduza a sua nova palavra-passe.",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Falha na redefinição da palavra-passe",
|
||||
"reset.password.token.validation.sever.error": "Falha na validação do Token",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Gender (optional)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Other/Prefer not to say",
|
||||
"education.levels.label": "Highest level of education completed (optional)",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Secondary/high school",
|
||||
"education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Year of birth (optional)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
}
|
||||
@@ -1 +1,221 @@
|
||||
{}
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
"forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.sso.button.title": "Sign in using {providerName}",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"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}.",
|
||||
"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.invalid.email": "An error occurred.",
|
||||
"forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.",
|
||||
"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 edX 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.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"login.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",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Need help signing in?",
|
||||
"forgot.password.link": "Forgot my password",
|
||||
"forgot.password": "Forgot password",
|
||||
"other.sign.in.issues": "Other sign in issues",
|
||||
"need.other.help.signing.in.collapsible.menu": "Need other help signing in?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"institution.login.page.back.button": "Back to sign in",
|
||||
"create.an.account": "Create an account",
|
||||
"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.",
|
||||
"first.time.here": "First time here?",
|
||||
"email.help.message": "The email address you used to register with edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "The email address you've provided isn't formatted correctly.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Create an account",
|
||||
"sign.in.heading": "Sign in",
|
||||
"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",
|
||||
"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.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.",
|
||||
"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",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"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.",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"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.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"registration.year.of.birth.label": "Year of birth (optional)",
|
||||
"registration.field.gender.options.label": "Gender (optional)",
|
||||
"registration.goals.label": "Tell us why you're interested in edX (optional)",
|
||||
"registration.field.gender.options.f": "Female",
|
||||
"registration.field.gender.options.m": "Male",
|
||||
"registration.field.gender.options.o": "Other/Prefer not to say",
|
||||
"registration.field.education.levels.label": "Highest level of education completed (optional)",
|
||||
"registration.field.education.levels.p": "Doctorate",
|
||||
"registration.field.education.levels.m": "Master's or professional degree",
|
||||
"registration.field.education.levels.b": "Bachelor's degree",
|
||||
"registration.field.education.levels.a": "Associate's degree",
|
||||
"registration.field.education.levels.hs": "Secondary/high school",
|
||||
"registration.field.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "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",
|
||||
"forgot.password.confirmation.sign.in.link": "sign in",
|
||||
"reset.password.request.forgot.password.text": "Forgot password",
|
||||
"reset.password.request.invalid.token.header": "Invalid password reset link",
|
||||
"reset.password.empty.new.password.field.error": "Please enter your new password.",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Failed to reset password",
|
||||
"reset.password.token.validation.sever.error": "Token validation failure",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Gender (optional)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Other/Prefer not to say",
|
||||
"education.levels.label": "Highest level of education completed (optional)",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Secondary/high school",
|
||||
"education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Year of birth (optional)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
}
|
||||
@@ -1 +1,221 @@
|
||||
{}
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
"forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.sso.button.title": "Sign in using {providerName}",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"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}.",
|
||||
"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.invalid.email": "An error occurred.",
|
||||
"forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.",
|
||||
"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 edX 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.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"login.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",
|
||||
"sign.in.btn.pending.state": "Loading",
|
||||
"need.help.signing.in.collapsible.menu": "Need help signing in?",
|
||||
"forgot.password.link": "Forgot my password",
|
||||
"forgot.password": "Forgot password",
|
||||
"other.sign.in.issues": "Other sign in issues",
|
||||
"need.other.help.signing.in.collapsible.menu": "Need other help signing in?",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"institution.login.page.back.button": "Back to sign in",
|
||||
"create.an.account": "Create an account",
|
||||
"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.",
|
||||
"first.time.here": "First time here?",
|
||||
"email.help.message": "The email address you used to register with edX.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"email.format.validation.message": "The email address you've provided isn't formatted correctly.",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"register.link": "Create an account",
|
||||
"sign.in.heading": "Sign in",
|
||||
"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",
|
||||
"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.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.",
|
||||
"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",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"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.",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"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.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"registration.year.of.birth.label": "Year of birth (optional)",
|
||||
"registration.field.gender.options.label": "Gender (optional)",
|
||||
"registration.goals.label": "Tell us why you're interested in edX (optional)",
|
||||
"registration.field.gender.options.f": "Female",
|
||||
"registration.field.gender.options.m": "Male",
|
||||
"registration.field.gender.options.o": "Other/Prefer not to say",
|
||||
"registration.field.education.levels.label": "Highest level of education completed (optional)",
|
||||
"registration.field.education.levels.p": "Doctorate",
|
||||
"registration.field.education.levels.m": "Master's or professional degree",
|
||||
"registration.field.education.levels.b": "Bachelor's degree",
|
||||
"registration.field.education.levels.a": "Associate's degree",
|
||||
"registration.field.education.levels.hs": "Secondary/high school",
|
||||
"registration.field.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "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",
|
||||
"forgot.password.confirmation.sign.in.link": "sign in",
|
||||
"reset.password.request.forgot.password.text": "Forgot password",
|
||||
"reset.password.request.invalid.token.header": "Invalid password reset link",
|
||||
"reset.password.empty.new.password.field.error": "Please enter your new password.",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.password.request.server.error": "Failed to reset password",
|
||||
"reset.password.token.validation.sever.error": "Token validation failure",
|
||||
"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.",
|
||||
"progressive.profiling.page.title": "Optional Fields | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"gender.options.label": "Gender (optional)",
|
||||
"gender.options.f": "Female",
|
||||
"gender.options.m": "Male",
|
||||
"gender.options.o": "Other/Prefer not to say",
|
||||
"education.levels.label": "Highest level of education completed (optional)",
|
||||
"education.levels.p": "Doctorate",
|
||||
"education.levels.m": "Master's or professional degree",
|
||||
"education.levels.b": "Bachelor's degree",
|
||||
"education.levels.a": "Associate's degree",
|
||||
"education.levels.hs": "Secondary/high school",
|
||||
"education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"education.levels.el": "Elementary/primary school",
|
||||
"education.levels.none": "No formal education",
|
||||
"education.levels.other": "Other education",
|
||||
"year.of.birth.label": "Year of birth (optional)",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"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."
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
{
|
||||
"top.discount.message.15.off": "off",
|
||||
"top.discount.message.body": "Get {discount} your first verified certificate* with code",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"code.copied": "Code copied",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"side.discount.message.15.off": "off",
|
||||
"certificate.message": "certificate* with code",
|
||||
"side.discount.message.body": "Get {discountText} your first verified {lineBreak} {certificateMsg}",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"forgot.password.confirmation.title": "Check your email",
|
||||
"forgot.password.confirmation.support.link": "contact technical support",
|
||||
@@ -90,9 +96,13 @@
|
||||
"sign.in.heading": "Sign in",
|
||||
"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.already.activated.message": "This account has already been activated.",
|
||||
"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",
|
||||
"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",
|
||||
@@ -102,28 +112,39 @@
|
||||
"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",
|
||||
"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.button": "Create an account",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"create.an.account.btn.pending.state": "Loading",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"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.invalid.format.error": "Enter a valid email address",
|
||||
"email.ratelimit.less.chars.validation.message": "Email must have 3 characters.",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).",
|
||||
"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.",
|
||||
"support.education.research": "Support education research by providing additional information. (Optional)",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
@@ -148,9 +169,10 @@
|
||||
"registration.field.education.levels.el": "Elementary/primary school",
|
||||
"registration.field.education.levels.none": "No formal education",
|
||||
"registration.field.education.levels.other": "Other education",
|
||||
"registration.username.suggestion.label": "Available:",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"certificate.msg": "*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
|
||||
@@ -31,7 +31,6 @@ initialize({
|
||||
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
|
||||
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
|
||||
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
|
||||
REGISTRATION_OPTIONAL_FIELDS: process.env.REGISTRATION_OPTIONAL_FIELDS || '',
|
||||
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
|
||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||
WELCOME_PAGE_SUPPORT_LINK: process.env.WELCOME_PAGE_SUPPORT_LINK || null,
|
||||
@@ -39,6 +38,9 @@ initialize({
|
||||
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
||||
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
|
||||
ENABLE_PROGRESSIVE_PROFILING: process.env.ENABLE_PROGRESSIVE_PROFILING || false,
|
||||
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
||||
ENABLE_COPPA_COMPLIANCE: process.env.ENABLE_COPPA_COMPLIANCE || '',
|
||||
SHOW_DYNAMIC_PROFILING_PAGE: process.env.SHOW_DYNAMIC_PROFILING_PAGE || false,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,6 +13,8 @@ const AccountActivationMessage = (props) => {
|
||||
const { intl, messageType } = props;
|
||||
const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType;
|
||||
|
||||
const activationOrVerification = getConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation';
|
||||
|
||||
let activationMessage;
|
||||
let heading;
|
||||
|
||||
@@ -23,12 +25,12 @@ const AccountActivationMessage = (props) => {
|
||||
|
||||
switch (messageType) {
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: {
|
||||
heading = intl.formatMessage(messages['account.activation.success.message.title']);
|
||||
activationMessage = intl.formatMessage(messages['account.activation.success.message']);
|
||||
heading = intl.formatMessage(messages[`account.${activationOrVerification}.success.message.title`]);
|
||||
activationMessage = <span>{intl.formatMessage(messages[`account.${activationOrVerification}.success.message`])}</span>;
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.INFO: {
|
||||
activationMessage = intl.formatMessage(messages['account.already.activated.message']);
|
||||
activationMessage = intl.formatMessage(messages[`account.${activationOrVerification}.info.message`]);
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
||||
@@ -38,7 +40,7 @@ const AccountActivationMessage = (props) => {
|
||||
</Alert.Link>
|
||||
);
|
||||
|
||||
heading = intl.formatMessage(messages['account.activation.error.message.title']);
|
||||
heading = intl.formatMessage(messages[`account.${activationOrVerification}.error.message.title`]);
|
||||
activationMessage = (
|
||||
<FormattedMessage
|
||||
id="account.activation.error.message"
|
||||
|
||||
89
src/login/ChangePasswordPrompt.jsx
Normal file
89
src/login/ChangePasswordPrompt.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow, ModalDialog, useToggle,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
|
||||
import { updatePathWithQueryParams } from '../data/utils';
|
||||
import useMobileResponsive from '../data/utils/useMobileResponsive';
|
||||
|
||||
const ChangePasswordPrompt = ({ intl, variant, redirectUrl }) => {
|
||||
const isMobileView = useMobileResponsive();
|
||||
const [redirectToResetPasswordPage, setRedirectToResetPasswordPage] = useState(false);
|
||||
const handlers = {
|
||||
handleToggleOff: () => {
|
||||
if (variant === 'block') {
|
||||
setRedirectToResetPasswordPage(true);
|
||||
} else {
|
||||
window.location.href = redirectUrl || getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
||||
}
|
||||
},
|
||||
};
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [isOpen, open, close] = useToggle(true, handlers);
|
||||
|
||||
if (redirectToResetPasswordPage) {
|
||||
return <Redirect to={updatePathWithQueryParams(RESET_PAGE)} />;
|
||||
}
|
||||
return (
|
||||
<ModalDialog
|
||||
title="Password security"
|
||||
isOpen={isOpen}
|
||||
onClose={close}
|
||||
size={isMobileView ? 'sm' : 'md'}
|
||||
hasCloseButton={false}
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
{intl.formatMessage(messages[`password.security.${variant}.title`])}
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
{intl.formatMessage(messages[`password.security.${variant}.body`])}
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow className={classNames(
|
||||
{ 'd-flex flex-column': isMobileView },
|
||||
)}
|
||||
>
|
||||
{variant === 'nudge' ? (
|
||||
<ModalDialog.CloseButton id="password-security-close" variant="tertiary">
|
||||
{intl.formatMessage(messages['password.security.close.button'])}
|
||||
</ModalDialog.CloseButton>
|
||||
) : null}
|
||||
<Link
|
||||
id="password-security-reset-password"
|
||||
className={classNames(
|
||||
'btn btn-primary',
|
||||
{ 'w-100': isMobileView },
|
||||
)}
|
||||
to={updatePathWithQueryParams(RESET_PAGE)}
|
||||
>
|
||||
{intl.formatMessage(messages['password.security.redirect.to.reset.password.button'])}
|
||||
</Link>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
ChangePasswordPrompt.defaultProps = {
|
||||
variant: 'block',
|
||||
redirectUrl: null,
|
||||
};
|
||||
|
||||
ChangePasswordPrompt.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
variant: PropTypes.oneOf(['nudge', 'block']),
|
||||
redirectUrl: PropTypes.string,
|
||||
};
|
||||
|
||||
export default injectIntl(ChangePasswordPrompt);
|
||||
@@ -15,8 +15,11 @@ import {
|
||||
INTERNAL_SERVER_ERROR,
|
||||
INVALID_FORM,
|
||||
NON_COMPLIANT_PASSWORD_EXCEPTION,
|
||||
NUDGE_PASSWORD_CHANGE,
|
||||
REQUIRE_PASSWORD_CHANGE,
|
||||
} from './data/constants';
|
||||
import messages from './messages';
|
||||
import ChangePasswordPrompt from './ChangePasswordPrompt';
|
||||
|
||||
const LoginFailureMessage = (props) => {
|
||||
const { intl } = props;
|
||||
@@ -118,7 +121,7 @@ const LoginFailureMessage = (props) => {
|
||||
}
|
||||
case INCORRECT_EMAIL_PASSWORD:
|
||||
if (context.failureCount <= 1) {
|
||||
errorList = intl.formatMessage(messages['login.incorrect.credentials.error']);
|
||||
errorList = <p>{intl.formatMessage(messages['login.incorrect.credentials.error'])}</p>;
|
||||
} else if (context.failureCount === 2) {
|
||||
errorList = (
|
||||
<p>
|
||||
@@ -131,6 +134,15 @@ const LoginFailureMessage = (props) => {
|
||||
);
|
||||
}
|
||||
break;
|
||||
case NUDGE_PASSWORD_CHANGE:
|
||||
return (
|
||||
<ChangePasswordPrompt
|
||||
redirectUrl={props.loginError.redirectUrl}
|
||||
variant="nudge"
|
||||
/>
|
||||
);
|
||||
case REQUIRE_PASSWORD_CHANGE:
|
||||
return <ChangePasswordPrompt />;
|
||||
default:
|
||||
// TODO: use errorCode instead of processing error messages on frontend
|
||||
errorList = value.trim().split('\n');
|
||||
@@ -165,6 +177,7 @@ const LoginFailureMessage = (props) => {
|
||||
|
||||
LoginFailureMessage.defaultProps = {
|
||||
loginError: {
|
||||
redirectUrl: null,
|
||||
errorCode: null,
|
||||
value: '',
|
||||
},
|
||||
@@ -176,6 +189,7 @@ LoginFailureMessage.propTypes = {
|
||||
email: PropTypes.string,
|
||||
errorCode: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
redirectUrl: PropTypes.string,
|
||||
}),
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@ export const FORBIDDEN_REQUEST = 'forbidden-request';
|
||||
export const FAILED_LOGIN_ATTEMPT = 'failed-login-attempt';
|
||||
export const ACCOUNT_LOCKED_OUT = 'account-locked-out';
|
||||
export const INCORRECT_EMAIL_PASSWORD = 'incorrect-email-or-password';
|
||||
export const NUDGE_PASSWORD_CHANGE = 'nudge-password-change';
|
||||
export const REQUIRE_PASSWORD_CHANGE = 'require-password-change';
|
||||
|
||||
// Account Activation Message
|
||||
export const ACCOUNT_ACTIVATION_MESSAGE = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import querystring from 'querystring';
|
||||
import * as QueryString from 'query-string';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function loginRequest(creds) {
|
||||
@@ -12,7 +12,7 @@ export async function loginRequest(creds) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/api/user/v2/account/login_session/`,
|
||||
querystring.stringify(creds),
|
||||
QueryString.stringify(creds),
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
|
||||
@@ -20,7 +20,7 @@ const messages = defineMessages({
|
||||
'sign.in.button': {
|
||||
id: 'sign.in.button',
|
||||
defaultMessage: 'Sign in',
|
||||
description: 'Button label that appears on login page',
|
||||
description: 'Sign in button label that appears on login page',
|
||||
},
|
||||
'sign.in.btn.pending.state': {
|
||||
id: 'sign.in.btn.pending.state',
|
||||
@@ -155,8 +155,8 @@ const messages = defineMessages({
|
||||
defaultMessage: 'You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.',
|
||||
description: 'Message show to learners when their account has been activated successfully',
|
||||
},
|
||||
'account.already.activated.message': {
|
||||
id: 'account.already.activated.message',
|
||||
'account.activation.info.message': {
|
||||
id: 'account.activation.info.message',
|
||||
defaultMessage: 'This account has already been activated.',
|
||||
description: 'Message shown when learner account has already been activated',
|
||||
},
|
||||
@@ -170,6 +170,27 @@ const messages = defineMessages({
|
||||
defaultMessage: 'contact support',
|
||||
description: 'Link text used in account activation error message to go to learner help center',
|
||||
},
|
||||
// Email Confirmation Strings
|
||||
'account.confirmation.success.message.title': {
|
||||
id: 'account.confirmation.success.message.title',
|
||||
defaultMessage: 'Success! You have confirmed your email.',
|
||||
description: 'Account verification success message title',
|
||||
},
|
||||
'account.confirmation.success.message': {
|
||||
id: 'account.confirmation.success.message',
|
||||
defaultMessage: 'Sign in to continue.',
|
||||
description: 'Message show to learners when their account has been activated successfully',
|
||||
},
|
||||
'account.confirmation.info.message': {
|
||||
id: 'account.confirmation.info.message',
|
||||
defaultMessage: 'This email has already been confirmed.',
|
||||
description: 'Message shown when learner account has already been verified',
|
||||
},
|
||||
'account.confirmation.error.message.title': {
|
||||
id: 'account.confirmation.error.message.title',
|
||||
defaultMessage: 'Your email could not be confirmed',
|
||||
description: 'Account verification error message title',
|
||||
},
|
||||
'internal.server.error.message': {
|
||||
id: 'internal.server.error.message',
|
||||
defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.',
|
||||
@@ -220,6 +241,39 @@ const messages = defineMessages({
|
||||
defaultMessage: 'click here to reset it.',
|
||||
description: 'Reset password link text for incorrect email or password credentials before blocking account',
|
||||
},
|
||||
// Vulnerable password change prompt
|
||||
'password.security.nudge.title': {
|
||||
id: 'password.security.nudge.title',
|
||||
defaultMessage: 'Password security',
|
||||
description: 'Title for prompt that nudges user to change their vulnerable password',
|
||||
},
|
||||
'password.security.block.title': {
|
||||
id: 'password.security.block.title',
|
||||
defaultMessage: 'Password change required',
|
||||
description: 'Title for prompt that asks user to change their vulnerable password',
|
||||
},
|
||||
'password.security.nudge.body': {
|
||||
id: 'password.security.nudge.body',
|
||||
defaultMessage: 'Our system detected that your password is vulnerable. '
|
||||
+ 'We recommend you change it so that your account stays secure.',
|
||||
description: 'Message copy for prompt that nudges user to change their vulnerable password',
|
||||
},
|
||||
'password.security.block.body': {
|
||||
id: 'password.security.block.body',
|
||||
defaultMessage: 'Our system detected that your password is vulnerable. '
|
||||
+ 'Change your password so that your account stays secure.',
|
||||
description: 'Message copy for prompt that asks user to change their vulnerable password',
|
||||
},
|
||||
'password.security.close.button': {
|
||||
id: 'password.security.close.button',
|
||||
defaultMessage: 'Close',
|
||||
description: 'Button to close popup',
|
||||
},
|
||||
'password.security.redirect.to.reset.password.button': {
|
||||
id: 'password.security.redirect.to.reset.password.button',
|
||||
defaultMessage: 'Reset your password',
|
||||
description: 'Button to redirect users to Reset Password page',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import AccountActivationMessage from '../AccountActivationMessage';
|
||||
@@ -9,6 +10,12 @@ import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants';
|
||||
const IntlAccountActivationMessage = injectIntl(AccountActivationMessage);
|
||||
|
||||
describe('AccountActivationMessage', () => {
|
||||
beforeEach(() => {
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should match account already activated message', () => {
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
@@ -55,3 +62,45 @@ describe('AccountActivationMessage', () => {
|
||||
expect(accountActivationMessage).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmailConfirmationMessage', () => {
|
||||
beforeEach(() => {
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
});
|
||||
});
|
||||
|
||||
it('should match email already confirmed message', () => {
|
||||
const accountVerificationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'This email has already been confirmed.';
|
||||
expect(accountVerificationMessage.find('#account-activation-message').find('div').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match email confirmation success message', () => {
|
||||
const accountVerificationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'Success! You have confirmed your email.Sign in to continue.';
|
||||
expect(accountVerificationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match email confirmation error message', () => {
|
||||
const accountVerificationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'Your email could not be confirmed'
|
||||
+ 'Something went wrong, please contact support to resolve this issue.';
|
||||
expect(accountVerificationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
});
|
||||
|
||||
73
src/login/tests/ChangePasswordPrompt.test.jsx
Normal file
73
src/login/tests/ChangePasswordPrompt.test.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import ChangePasswordPrompt from '../ChangePasswordPrompt';
|
||||
import { RESET_PAGE } from '../../data/constants';
|
||||
|
||||
const IntlChangePasswordPrompt = injectIntl(ChangePasswordPrompt);
|
||||
const history = createMemoryHistory();
|
||||
|
||||
describe('ChangePasswordPromptTests', () => {
|
||||
let props = {};
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: query,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
it('[nudge modal] should redirect to next url when user clicks close button', () => {
|
||||
const dashboardUrl = getConfig().BASE_URL.concat('/dashboard');
|
||||
props = {
|
||||
variant: 'nudge',
|
||||
redirectUrl: dashboardUrl,
|
||||
};
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
const changePasswordPrompt = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlChangePasswordPrompt {...props} />
|
||||
</MemoryRouter>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
changePasswordPrompt.find('button#password-security-close').simulate('click');
|
||||
expect(window.location.href).toBe(dashboardUrl);
|
||||
});
|
||||
|
||||
it('[block modal] should redirect to reset password page when user clicks outside modal', async () => {
|
||||
props = {
|
||||
variant: 'block',
|
||||
};
|
||||
|
||||
const changePasswordPrompt = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<Router history={history}>
|
||||
<IntlChangePasswordPrompt {...props} />
|
||||
</Router>
|
||||
</MemoryRouter>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await changePasswordPrompt.find('div.pgn__modal-backdrop').first().simulate('click');
|
||||
});
|
||||
|
||||
changePasswordPrompt.update();
|
||||
expect(history.location.pathname).toEqual(RESET_PAGE);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
@@ -12,6 +13,8 @@ import {
|
||||
NON_COMPLIANT_PASSWORD_EXCEPTION,
|
||||
FAILED_LOGIN_ATTEMPT,
|
||||
INCORRECT_EMAIL_PASSWORD,
|
||||
NUDGE_PASSWORD_CHANGE,
|
||||
REQUIRE_PASSWORD_CHANGE,
|
||||
} from '../data/constants';
|
||||
|
||||
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
|
||||
@@ -19,6 +22,15 @@ const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
|
||||
describe('LoginFailureMessage', () => {
|
||||
let props = {};
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: query,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
it('should match non compliant password error message', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
@@ -221,4 +233,48 @@ describe('LoginFailureMessage', () => {
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').find('a').props().href).toEqual('/reset');
|
||||
});
|
||||
|
||||
it('should show modal that nudges users to change password', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
errorCode: NUDGE_PASSWORD_CHANGE,
|
||||
},
|
||||
};
|
||||
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</MemoryRouter>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
expect(loginFailureMessage.find('.pgn__modal-title').text()).toEqual('Password security');
|
||||
expect(loginFailureMessage.find('.pgn__modal-body').text()).toEqual(
|
||||
'Our system detected that your password is vulnerable. '
|
||||
+ 'We recommend you change it so that your account stays secure.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show modal that requires users to change password', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
errorCode: REQUIRE_PASSWORD_CHANGE,
|
||||
},
|
||||
};
|
||||
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</MemoryRouter>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
expect(loginFailureMessage.find('.pgn__modal-title').text()).toEqual('Password change required');
|
||||
expect(loginFailureMessage.find('.pgn__modal-body').text()).toEqual(
|
||||
'Our system detected that your password is vulnerable. '
|
||||
+ 'Change your password so that your account stays secure.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -215,8 +215,8 @@ describe('LoginPage', () => {
|
||||
|
||||
it('should match account activation message', () => {
|
||||
const activationMessage = 'Success! You have activated your account.'
|
||||
+ 'You will now receive email updates and alerts from us related '
|
||||
+ 'to the courses you are enrolled in. Sign in to continue.';
|
||||
+ 'You will now receive email updates and alerts from us related '
|
||||
+ 'to the courses you are enrolled in. Sign in to continue.';
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?account_activation_status=success' };
|
||||
@@ -238,8 +238,9 @@ describe('LoginPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const expectedMessage = 'You have successfully signed into Apple, but your Apple account does not have a '
|
||||
+ 'linked edX account. To link your accounts, sign in now using your edX password.';
|
||||
const expectedMessage = `${'You have successfully signed into Apple, but your Apple account does not have a '
|
||||
+ 'linked '}${ getConfig().SITE_NAME } account. To link your accounts, sign in now using your ${
|
||||
getConfig().SITE_NAME } password.`;
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#tpa-alert').find('p').text()).toEqual(expectedMessage);
|
||||
@@ -262,14 +263,14 @@ describe('LoginPage', () => {
|
||||
// ******** test redirection ********
|
||||
|
||||
it('should redirect to url returned by login endpoint', () => {
|
||||
const dasboardUrl = 'http://localhost:18000/enterprise/select/active/?success_url=/dashboard';
|
||||
const dashboardUrl = 'http://localhost:18000/enterprise/select/active/?success_url=/dashboard';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginResult: {
|
||||
success: true,
|
||||
redirectUrl: dasboardUrl,
|
||||
redirectUrl: dashboardUrl,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -277,7 +278,7 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
renderer.create(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(dasboardUrl);
|
||||
expect(window.location.href).toBe(dashboardUrl);
|
||||
});
|
||||
|
||||
it('should redirect to finishAuthUrl upon successful login via SSO', () => {
|
||||
|
||||
@@ -180,7 +180,8 @@ class CountryDropdown extends React.Component {
|
||||
<FormGroup
|
||||
as="input"
|
||||
name={this.props.name}
|
||||
autoComplete="off"
|
||||
readOnly={this.props.readOnly}
|
||||
autoComplete="chrome-off"
|
||||
className="mb-0"
|
||||
floatingLabel={this.props.floatingLabel}
|
||||
trailingElement={this.state.icon}
|
||||
@@ -208,6 +209,7 @@ CountryDropdown.defaultProps = {
|
||||
value: null,
|
||||
errorMessage: null,
|
||||
errorCode: null,
|
||||
readOnly: false,
|
||||
};
|
||||
|
||||
CountryDropdown.propTypes = {
|
||||
@@ -222,6 +224,7 @@ CountryDropdown.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
errorCode: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
readOnly: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default onClickOutside(CountryDropdown);
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Form, Icon } from '@edx/paragon';
|
||||
import { ExpandMore } from '@edx/paragon/icons';
|
||||
|
||||
import { EDUCATION_LEVELS, GENDER_OPTIONS, YEAR_OF_BIRTH_OPTIONS } from './data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const OptionalFields = (props) => {
|
||||
const {
|
||||
intl, optionalFields, onChangeHandler, values,
|
||||
} = props;
|
||||
|
||||
const getOptions = () => ({
|
||||
yearOfBirthOptions: YEAR_OF_BIRTH_OPTIONS.map(({ value, label }) => (
|
||||
<option className="data-hj-suppress" key={value} value={value}>{label}</option>
|
||||
)),
|
||||
educationLevelOptions: EDUCATION_LEVELS.map(key => (
|
||||
<option className="data-hj-suppress" key={key} value={key}>
|
||||
{intl.formatMessage(messages[`registration.field.education.levels.${key || 'label'}`])}
|
||||
</option>
|
||||
)),
|
||||
genderOptions: GENDER_OPTIONS.map(key => (
|
||||
<option className="data-hj-suppress" key={key} value={key}>
|
||||
{intl.formatMessage(messages[`registration.field.gender.options.${key || 'label'}`])}
|
||||
</option>
|
||||
)),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-3">
|
||||
{optionalFields.includes('gender') && (
|
||||
<Form.Group controlId="gender">
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="gender"
|
||||
value={values.gender}
|
||||
onChange={(e) => onChangeHandler('gender', e.target.value)}
|
||||
trailingElement={<Icon src={ExpandMore} />}
|
||||
floatingLabel={intl.formatMessage(messages['registration.field.gender.options.label'])}
|
||||
>
|
||||
{getOptions().genderOptions}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
)}
|
||||
{optionalFields.includes('yearOfBirth') && (
|
||||
<Form.Group controlId="yearOfBirth">
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="yearOfBirth"
|
||||
value={values.yearOfBirth}
|
||||
onChange={(e) => onChangeHandler('yearOfBirth', e.target.value)}
|
||||
trailingElement={<Icon src={ExpandMore} />}
|
||||
floatingLabel={intl.formatMessage(messages['registration.year.of.birth.label'])}
|
||||
>
|
||||
<option value="">{intl.formatMessage(messages['registration.year.of.birth.label'])}</option>
|
||||
{getOptions().yearOfBirthOptions}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
)}
|
||||
{optionalFields.includes('levelOfEducation') && (
|
||||
<Form.Group controlId="levelOfEducation">
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="levelOfEducation"
|
||||
value={values.levelOfEducation}
|
||||
onChange={(e) => onChangeHandler('levelOfEducation', e.target.value)}
|
||||
trailingElement={<Icon src={ExpandMore} />}
|
||||
floatingLabel={intl.formatMessage(messages['registration.field.education.levels.label'])}
|
||||
>
|
||||
{getOptions().educationLevelOptions}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
)}
|
||||
{optionalFields.includes('goals') && (
|
||||
<Form.Group controlId="goals">
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
name="goals"
|
||||
value={values.goals}
|
||||
onChange={(e) => onChangeHandler('goals', e.target.value)}
|
||||
floatingLabel={intl.formatMessage(messages['registration.goals.label'])}
|
||||
/>
|
||||
</Form.Group>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
OptionalFields.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
optionalFields: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onChangeHandler: PropTypes.func.isRequired,
|
||||
values: PropTypes.shape({
|
||||
gender: PropTypes.string,
|
||||
goals: PropTypes.string,
|
||||
levelOfEducation: PropTypes.string,
|
||||
yearOfBirth: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(OptionalFields);
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
import { connect } from 'react-redux';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import PropTypes, { string } from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
registrationErrorSelector, registrationRequestSelector, validationsSelector, usernameSuggestionsSelector,
|
||||
} from './data/selectors';
|
||||
import messages from './messages';
|
||||
import OptionalFields from './OptionalFields';
|
||||
import RegistrationFailure from './RegistrationFailure';
|
||||
import UsernameField from './UsernameField';
|
||||
|
||||
@@ -38,7 +37,7 @@ import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||
import {
|
||||
DEFAULT_STATE, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX, LETTER_REGEX, NUMBER_REGEX,
|
||||
DEFAULT_STATE, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX, LETTER_REGEX, NUMBER_REGEX, VALID_NAME_REGEX,
|
||||
} from '../data/constants';
|
||||
import {
|
||||
getTpaProvider, getTpaHint, getAllPossibleQueryParam, setSurveyCookie, setCookie,
|
||||
@@ -50,7 +49,6 @@ class RegistrationPage extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
sendPageEvent('login_and_registration', 'register');
|
||||
const optionalFields = getConfig().REGISTRATION_OPTIONAL_FIELDS ? getConfig().REGISTRATION_OPTIONAL_FIELDS.split(',') : [];
|
||||
this.handleOnClose = this.handleOnClose.bind(this);
|
||||
|
||||
this.queryParams = getAllPossibleQueryParam();
|
||||
@@ -61,6 +59,7 @@ class RegistrationPage extends React.Component {
|
||||
name: '',
|
||||
password: '',
|
||||
username: '',
|
||||
marketingOptIn: true,
|
||||
errors: {
|
||||
email: '',
|
||||
name: '',
|
||||
@@ -72,16 +71,18 @@ class RegistrationPage extends React.Component {
|
||||
emailWarningSuggestion: null,
|
||||
errorCode: null,
|
||||
failureCount: 0,
|
||||
optionalFields,
|
||||
optionalFieldsState: {},
|
||||
showOptionalField: false,
|
||||
startTime: Date.now(),
|
||||
totalRegistrationTime: 0,
|
||||
optimizelyExperimentName: '', // eslint-disable-line react/no-unused-state
|
||||
optimizelyExperimentName: '',
|
||||
readOnly: true,
|
||||
validatePassword: false,
|
||||
// TODO: Remove after VAN-876 experimentation is complete.
|
||||
registerRenameExpVariation: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const payload = { ...this.queryParams };
|
||||
window.optimizely = window.optimizely || [];
|
||||
window.optimizely.push({
|
||||
type: 'page',
|
||||
@@ -89,19 +90,39 @@ class RegistrationPage extends React.Component {
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const payload = { ...this.queryParams };
|
||||
if (payload.register_for_free === 'true') {
|
||||
window.optimizely.push({
|
||||
type: 'event',
|
||||
eventName: 'van-876-authn-registration-page',
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.save_for_later === 'true') {
|
||||
sendTrackEvent('edx.bi.user.saveforlater.course.enroll.clicked', { category: 'save-for-later' });
|
||||
}
|
||||
|
||||
if (this.tpaHint) {
|
||||
payload.tpa_hint = this.tpaHint;
|
||||
}
|
||||
this.props.resetRegistrationForm();
|
||||
this.props.getThirdPartyAuthContext(payload);
|
||||
this.getExperiments();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (this.props.usernameSuggestions.length > 0 && this.state.username === '') {
|
||||
this.setState({
|
||||
username: ' ',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (this.props.validationDecisions !== nextProps.validationDecisions) {
|
||||
const state = { errors: { ...this.state.errors, ...nextProps.validationDecisions } };
|
||||
let validatePassword = false;
|
||||
|
||||
if (state.errors.password) {
|
||||
validatePassword = true;
|
||||
}
|
||||
if (nextProps.registrationErrorCode) {
|
||||
state.errorCode = nextProps.registrationErrorCode;
|
||||
}
|
||||
@@ -124,6 +145,7 @@ class RegistrationPage extends React.Component {
|
||||
suggestedTopLevelDomain,
|
||||
suggestedSldMessage,
|
||||
suggestedServiceLevelDomain,
|
||||
validatePassword,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -152,35 +174,22 @@ class RegistrationPage extends React.Component {
|
||||
}
|
||||
|
||||
getExperiments = () => {
|
||||
const { optimizelyExperimentName } = window;
|
||||
const { experimentName, renameRegisterExperiment } = window;
|
||||
|
||||
if (optimizelyExperimentName) {
|
||||
// eslint-disable-next-line react/no-unused-state
|
||||
this.setState({ optimizelyExperimentName });
|
||||
if (experimentName) {
|
||||
this.setState({ optimizelyExperimentName: experimentName });
|
||||
}
|
||||
|
||||
if (renameRegisterExperiment) {
|
||||
this.setState({ registerRenameExpVariation: renameRegisterExperiment });
|
||||
}
|
||||
};
|
||||
|
||||
getOptionalFields() {
|
||||
return (
|
||||
<OptionalFields
|
||||
optionalFields={this.state.optionalFields}
|
||||
values={this.state.optionalFieldsState}
|
||||
onChangeHandler={
|
||||
(fieldName, value) => {
|
||||
this.setState(prevState => ({
|
||||
optionalFieldsState: { ...prevState.optionalFieldsState, [fieldName]: value },
|
||||
}));
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const { startTime } = this.state;
|
||||
const totalRegistrationTime = (Date.now() - startTime) / 1000;
|
||||
let payload = {
|
||||
const payload = {
|
||||
name: this.state.name,
|
||||
username: this.state.username,
|
||||
email: this.state.email,
|
||||
@@ -195,12 +204,7 @@ class RegistrationPage extends React.Component {
|
||||
payload.password = this.state.password;
|
||||
}
|
||||
|
||||
let errors = {};
|
||||
Object.keys(payload).forEach(key => {
|
||||
errors = this.validateInput(key, payload[key], { ...payload, form_field_key: key });
|
||||
});
|
||||
|
||||
if (!this.isFormValid(errors)) {
|
||||
if (!this.isFormValid(payload)) {
|
||||
this.setState(prevState => ({
|
||||
errorCode: FORM_SUBMISSION_ERROR,
|
||||
failureCount: prevState.failureCount + 1,
|
||||
@@ -208,14 +212,9 @@ class RegistrationPage extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since optional fields and query params are not validated we can add it to payload after
|
||||
// required fields have been validated. This will save us unwanted calls to validateInput()
|
||||
payload = { ...payload, ...this.queryParams };
|
||||
this.state.optionalFields.forEach((key) => {
|
||||
if (this.state.optionalFieldsState[key]) {
|
||||
payload[snakeCase(key)] = this.state.optionalFieldsState[key];
|
||||
}
|
||||
});
|
||||
if (getConfig().MARKETING_EMAILS_OPT_IN) {
|
||||
payload.marketing_emails_opt_in = this.state.marketingOptIn;
|
||||
}
|
||||
|
||||
payload.totalRegistrationTime = totalRegistrationTime;
|
||||
this.setState({
|
||||
@@ -245,12 +244,7 @@ class RegistrationPage extends React.Component {
|
||||
}
|
||||
|
||||
handleOnChange = (e) => {
|
||||
if (e.target.name === 'optionalFields') {
|
||||
sendTrackEvent('edx.bi.user.register.optional_fields_selected', {});
|
||||
this.setState({
|
||||
showOptionalField: e.target.checked,
|
||||
});
|
||||
} else if (!(e.target.name === 'username' && e.target.value.length > 30)) {
|
||||
if (!(e.target.name === 'username' && e.target.value.length > 30)) {
|
||||
this.setState({
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
@@ -264,6 +258,9 @@ class RegistrationPage extends React.Component {
|
||||
if (e.target.name === 'username') {
|
||||
this.props.clearUsernameSuggestions();
|
||||
}
|
||||
if (e.target.name === 'country') {
|
||||
state.readOnly = false;
|
||||
}
|
||||
if (e.target.name === 'passwordValidation') {
|
||||
state.errors.password = '';
|
||||
}
|
||||
@@ -292,15 +289,36 @@ class RegistrationPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
isFormValid(validations) {
|
||||
const keyValidList = Object.entries(validations).map(([key]) => !validations[key]);
|
||||
return keyValidList.every((current) => current === true);
|
||||
handleUsernameSuggestionClose = () => {
|
||||
this.setState({
|
||||
username: '',
|
||||
});
|
||||
this.props.clearUsernameSuggestions();
|
||||
}
|
||||
|
||||
isFormValid(payload) {
|
||||
const { errors } = this.state;
|
||||
let isValid = true;
|
||||
|
||||
Object.keys(payload).forEach(key => {
|
||||
if (!payload[key]) {
|
||||
errors[key] = this.props.intl.formatMessage(messages[`empty.${key}.field.error`]);
|
||||
}
|
||||
// Mark form invalid, if there was already a validation error for this key or we added empty field error
|
||||
if (errors[key]) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ errors });
|
||||
return isValid;
|
||||
}
|
||||
|
||||
validateInput(fieldName, value, payload) {
|
||||
const { errors } = this.state;
|
||||
const { intl, statusCode } = this.props;
|
||||
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
const urlRegex = new RegExp(VALID_NAME_REGEX);
|
||||
|
||||
switch (fieldName) {
|
||||
case 'email':
|
||||
@@ -359,11 +377,22 @@ class RegistrationPage extends React.Component {
|
||||
case 'name':
|
||||
if (!value) {
|
||||
errors.name = intl.formatMessage(messages['empty.name.field.error']);
|
||||
} else if (value && value.match(urlRegex)) {
|
||||
errors.name = intl.formatMessage(messages['name.validation.message']);
|
||||
} else {
|
||||
errors.name = '';
|
||||
}
|
||||
|
||||
if (!this.state.username.trim() && value) {
|
||||
// fetch username suggestions based on the full name
|
||||
this.props.fetchRealtimeValidations(payload);
|
||||
}
|
||||
break;
|
||||
case 'username':
|
||||
if (value === ' ' && this.props.usernameSuggestions.length > 0) {
|
||||
errors.username = '';
|
||||
break;
|
||||
}
|
||||
if (!value || value.length <= 1 || value.length > 30) {
|
||||
errors.username = intl.formatMessage(messages['username.validation.message']);
|
||||
} else if (!value.match(/^[a-zA-Z0-9_-]*$/i)) {
|
||||
@@ -373,14 +402,16 @@ class RegistrationPage extends React.Component {
|
||||
} else {
|
||||
errors.username = '';
|
||||
}
|
||||
if (this.state.validatePassword) {
|
||||
this.props.fetchRealtimeValidations({ ...payload, form_field_key: 'password' });
|
||||
}
|
||||
break;
|
||||
case 'password':
|
||||
errors.password = '';
|
||||
if (!value || !LETTER_REGEX.test(value) || !NUMBER_REGEX.test(value) || value.length < 8) {
|
||||
errors.password = intl.formatMessage(messages['password.validation.message']);
|
||||
} else if (payload && statusCode !== 403) {
|
||||
this.props.fetchRealtimeValidations(payload);
|
||||
} else {
|
||||
errors.password = '';
|
||||
}
|
||||
break;
|
||||
case 'country':
|
||||
@@ -492,6 +523,7 @@ class RegistrationPage extends React.Component {
|
||||
setSurveyCookie('register');
|
||||
setCookie(getConfig().REGISTER_CONVERSION_COOKIE_NAME, true);
|
||||
setCookie('authn-returning-user');
|
||||
const payload = { ...this.queryParams };
|
||||
|
||||
// Fire optimizely events
|
||||
window.optimizely = window.optimizely || [];
|
||||
@@ -502,6 +534,13 @@ class RegistrationPage extends React.Component {
|
||||
value: this.state.totalRegistrationTime,
|
||||
},
|
||||
});
|
||||
|
||||
if (payload.register_for_free === 'true') {
|
||||
window.optimizely.push({
|
||||
type: 'event',
|
||||
eventName: 'van-876-authn-register-for-free-conversion',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -546,18 +585,6 @@ class RegistrationPage extends React.Component {
|
||||
helpText={[intl.formatMessage(messages['help.text.name'])]}
|
||||
floatingLabel={intl.formatMessage(messages['registration.fullname.label'])}
|
||||
/>
|
||||
<UsernameField
|
||||
name="username"
|
||||
value={this.state.username}
|
||||
handleBlur={this.handleOnBlur}
|
||||
handleChange={this.handleOnChange}
|
||||
handleFocus={this.handleOnFocus}
|
||||
errorMessage={this.state.errors.username}
|
||||
helpText={[intl.formatMessage(messages['help.text.username.1']), intl.formatMessage(messages['help.text.username.2'])]}
|
||||
floatingLabel={intl.formatMessage(messages['registration.username.label'])}
|
||||
handleSuggestionClick={this.handleSuggestionClick}
|
||||
usernameSuggestions={this.props.usernameSuggestions}
|
||||
/>
|
||||
<FormGroup
|
||||
name="email"
|
||||
value={this.state.email}
|
||||
@@ -572,6 +599,20 @@ class RegistrationPage extends React.Component {
|
||||
{this.renderEmailFeedback()}
|
||||
</FormGroup>
|
||||
|
||||
<UsernameField
|
||||
name="username"
|
||||
value={this.state.username}
|
||||
handleBlur={this.handleOnBlur}
|
||||
handleChange={this.handleOnChange}
|
||||
handleFocus={this.handleOnFocus}
|
||||
errorMessage={this.state.errors.username}
|
||||
helpText={[intl.formatMessage(messages['help.text.username.1']), intl.formatMessage(messages['help.text.username.2'])]}
|
||||
floatingLabel={intl.formatMessage(messages['registration.username.label'])}
|
||||
handleSuggestionClick={this.handleSuggestionClick}
|
||||
usernameSuggestions={this.props.usernameSuggestions}
|
||||
handleUsernameSuggestionClose={this.handleUsernameSuggestionClose}
|
||||
/>
|
||||
|
||||
{!currentProvider && (
|
||||
<PasswordField
|
||||
name="password"
|
||||
@@ -595,7 +636,19 @@ class RegistrationPage extends React.Component {
|
||||
errorMessage={intl.formatMessage(messages['empty.country.field.error'])}
|
||||
handleChange={(value) => this.setState({ country: value })}
|
||||
errorCode={this.state.errorCode}
|
||||
readOnly={this.state.readOnly}
|
||||
/>
|
||||
{(getConfig().MARKETING_EMAILS_OPT_IN)
|
||||
&& (
|
||||
<Form.Checkbox
|
||||
className="opt-checkbox"
|
||||
name="marketing_emails_opt_in"
|
||||
checked={this.state.marketingOptIn}
|
||||
onChange={(e) => this.setState({ marketingOptIn: e.target.checked })}
|
||||
>
|
||||
{intl.formatMessage(messages['registration.opt.in.label'], { siteName: getConfig().SITE_NAME })}
|
||||
</Form.Checkbox>
|
||||
)}
|
||||
<div id="honor-code" className="micro text-muted mt-4">
|
||||
<FormattedMessage
|
||||
id="register.page.terms.of.service.and.honor.code"
|
||||
@@ -617,27 +670,19 @@ class RegistrationPage extends React.Component {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{getConfig().REGISTRATION_OPTIONAL_FIELDS ? (
|
||||
<Form.Group className="mb-0 mt-2 small">
|
||||
<Form.Check
|
||||
id="optional-field-checkbox"
|
||||
type="checkbox"
|
||||
name="optionalFields"
|
||||
value={this.state.showOptionalField}
|
||||
onClick={this.handleOnChange}
|
||||
onChange={this.handleOnChange}
|
||||
label={intl.formatMessage(messages['support.education.research'])}
|
||||
/>
|
||||
</Form.Group>
|
||||
) : null}
|
||||
{ this.state.showOptionalField ? this.getOptionalFields() : null }
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="stateful-button-width mt-4 mb-4"
|
||||
className={classNames(
|
||||
'mt-4 mb-4',
|
||||
{ 'stateful-button-variation1-width': this.state.registerRenameExpVariation === 'variation1' },
|
||||
{ 'stateful-button-width': this.state.registerRenameExpVariation !== 'variation1' },
|
||||
)}
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['create.account.button']),
|
||||
default: this.state.registerRenameExpVariation === 'variation1' ? (
|
||||
intl.formatMessage(messages['create.account.for.free.button'])
|
||||
) : intl.formatMessage(messages['create.account.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={this.handleSubmit}
|
||||
@@ -649,6 +694,13 @@ class RegistrationPage extends React.Component {
|
||||
thirdPartyAuthApiStatus,
|
||||
intl)}
|
||||
</Form>
|
||||
{(this.state.optimizelyExperimentName === 'variation1' || this.state.optimizelyExperimentName === 'variation2')
|
||||
? (
|
||||
<div id="certificate-msg" className="mt-4 mb-3 micro text-gray-500">
|
||||
{intl.formatMessage(messages['certificate.msg'])}
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -771,10 +823,10 @@ const mapStateToProps = state => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
clearUsernameSuggestions,
|
||||
getThirdPartyAuthContext,
|
||||
fetchRealtimeValidations,
|
||||
registerNewUser,
|
||||
resetRegistrationForm,
|
||||
clearUsernameSuggestions,
|
||||
},
|
||||
)(injectIntl(RegistrationPage));
|
||||
|
||||
@@ -2,33 +2,51 @@ import React from 'react';
|
||||
import PropTypes, { string } from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Button } from '@edx/paragon';
|
||||
import { Button, IconButton, Icon } from '@edx/paragon';
|
||||
import { Close } from '@edx/paragon/icons';
|
||||
|
||||
import { FormGroup } from '../common-components';
|
||||
import messages from './messages';
|
||||
|
||||
const UsernameField = (props) => {
|
||||
const { intl, usernameSuggestions, errorMessage } = props;
|
||||
|
||||
let className = '';
|
||||
let suggestedUsernameDiv = <></>;
|
||||
let iconButton = <></>;
|
||||
const suggestedUsernames = () => (
|
||||
<div className={className}>
|
||||
<span className="text-gray username-suggestion-label">{intl.formatMessage(messages['registration.username.suggestion.label'])}</span>
|
||||
<div className="scroll-suggested-username">
|
||||
{usernameSuggestions.map((username, index) => (
|
||||
<Button
|
||||
type="button"
|
||||
name="username"
|
||||
variant="outline-dark"
|
||||
className="username-suggestion data-hj-suppress"
|
||||
key={`suggestion-${index.toString()}`}
|
||||
onClick={(e) => props.handleSuggestionClick(e, username)}
|
||||
>
|
||||
{username}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{iconButton}
|
||||
</div>
|
||||
);
|
||||
if (usernameSuggestions.length > 0 && errorMessage && props.value === ' ') {
|
||||
className = 'suggested-username-with-error';
|
||||
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => props.handleUsernameSuggestionClose()} variant="black" size="sm" className="suggested-username-close-button" />;
|
||||
suggestedUsernameDiv = suggestedUsernames();
|
||||
} else if (usernameSuggestions.length > 0 && props.value === ' ') {
|
||||
className = 'suggested-username';
|
||||
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => props.handleUsernameSuggestionClose()} variant="black" size="sm" className="suggested-username-close-button" />;
|
||||
suggestedUsernameDiv = suggestedUsernames();
|
||||
} else if (usernameSuggestions.length > 0 && errorMessage) {
|
||||
suggestedUsernameDiv = suggestedUsernames();
|
||||
}
|
||||
return (
|
||||
<FormGroup {...props}>
|
||||
{usernameSuggestions.length > 0 && errorMessage ? (
|
||||
<div>
|
||||
<span className="text-gray username-suggestion-label">{intl.formatMessage(messages['registration.username.suggestion.label'])}</span>
|
||||
{usernameSuggestions.map((username, index) => (
|
||||
<Button
|
||||
type="button"
|
||||
name="username"
|
||||
variant="outline-dark"
|
||||
className="username-suggestion data-hj-suppress"
|
||||
key={`suggestion-${index.toString()}`}
|
||||
onClick={(e) => props.handleSuggestionClick(e, username)}
|
||||
>
|
||||
{username}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
) : <></>}
|
||||
{suggestedUsernameDiv}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
@@ -36,12 +54,14 @@ const UsernameField = (props) => {
|
||||
UsernameField.defaultProps = {
|
||||
usernameSuggestions: [],
|
||||
handleSuggestionClick: () => {},
|
||||
handleUsernameSuggestionClose: () => {},
|
||||
errorMessage: '',
|
||||
};
|
||||
|
||||
UsernameField.propTypes = {
|
||||
usernameSuggestions: PropTypes.arrayOf(string),
|
||||
handleSuggestionClick: PropTypes.func,
|
||||
handleUsernameSuggestionClose: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
||||
@@ -35,10 +35,10 @@ export const GENDER_OPTIONS = ['', 'f', 'm', 'o'];
|
||||
export const FORM_FIELDS = ['name', 'email', 'password', 'username', 'country'];
|
||||
|
||||
export const COMMON_EMAIL_PROVIDERS = [
|
||||
'hotmail.com', 'yahoo.com', 'outlook.com', 'live.com', 'gmail.com', 'aol.com',
|
||||
'hotmail.com', 'yahoo.com', 'outlook.com', 'live.com', 'gmail.com',
|
||||
];
|
||||
|
||||
export const DEFAULT_SERVICE_PROVIDER_DOMAINS = ['yahoo', 'aol', 'hotmail', 'live', 'outlook', 'gmail'];
|
||||
export const DEFAULT_SERVICE_PROVIDER_DOMAINS = ['yahoo', 'hotmail', 'live', 'outlook', 'gmail'];
|
||||
|
||||
export const DEFAULT_TOP_LEVEL_DOMAINS = [
|
||||
'aaa', 'aarp', 'abarth', 'abb', 'abbott', 'abbvie', 'abc', 'able', 'abogado', 'abudhabi', 'ac', 'academy',
|
||||
|
||||
@@ -24,7 +24,6 @@ export function* handleNewUserRegistration(action) {
|
||||
yield put(registerNewUserBegin());
|
||||
|
||||
const { redirectUrl, success } = yield call(registerRequest, action.payload.registrationInfo);
|
||||
|
||||
yield put(registerNewUserSuccess(
|
||||
redirectUrl,
|
||||
success,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getHttpClient, getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import querystring from 'querystring';
|
||||
import * as QueryString from 'query-string';
|
||||
|
||||
export async function registerRequest(registrationInformation) {
|
||||
const requestConfig = {
|
||||
@@ -11,7 +11,7 @@ export async function registerRequest(registrationInformation) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/api/user/v2/account/registration/`,
|
||||
querystring.stringify(registrationInformation),
|
||||
QueryString.stringify(registrationInformation),
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
@@ -32,7 +32,7 @@ export async function getFieldsValidations(formPayload) {
|
||||
const { data } = await getHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/api/user/v1/validation/registration`,
|
||||
querystring.stringify(formPayload),
|
||||
QueryString.stringify(formPayload),
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
|
||||
@@ -32,6 +32,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Country/Region',
|
||||
description: 'Placeholder for the country options dropdown.',
|
||||
},
|
||||
'registration.opt.in.label': {
|
||||
id: 'registration.opt.in.label',
|
||||
defaultMessage: 'I agree that {siteName} may send me marketing messages.',
|
||||
description: 'Text for opt in option on register page.',
|
||||
},
|
||||
// Help text
|
||||
'help.text.name': {
|
||||
id: 'help.text.name',
|
||||
@@ -59,6 +64,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Create an account',
|
||||
description: 'Button label that appears on register page',
|
||||
},
|
||||
'create.account.for.free.button': {
|
||||
id: 'create.account.for.free.button',
|
||||
defaultMessage: 'Create an account for free',
|
||||
description: 'Label text for registration form submission button',
|
||||
},
|
||||
'create.an.account.btn.pending.state': {
|
||||
id: 'create.an.account.btn.pending.state',
|
||||
defaultMessage: 'Loading',
|
||||
@@ -96,6 +106,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Enter your email',
|
||||
description: 'Error message for empty email field',
|
||||
},
|
||||
'empty.username.field.error': {
|
||||
id: 'empty.username.field.error',
|
||||
defaultMessage: 'Username must be between 2 and 30 characters',
|
||||
description: 'Error message for empty username field',
|
||||
},
|
||||
'empty.password.field.error': {
|
||||
id: 'empty.password.field.error',
|
||||
defaultMessage: 'Password criteria has not been met',
|
||||
description: 'Error message for empty password field',
|
||||
},
|
||||
'empty.country.field.error': {
|
||||
id: 'empty.country.field.error',
|
||||
defaultMessage: 'Select your country or region of residence',
|
||||
@@ -116,6 +136,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Username must be between 2 and 30 characters',
|
||||
description: 'Error message for empty username field',
|
||||
},
|
||||
'name.validation.message': {
|
||||
id: 'name.validation.message',
|
||||
defaultMessage: 'Enter a valid name',
|
||||
description: 'Validation message that appears when fullname contain URL',
|
||||
},
|
||||
'password.validation.message': {
|
||||
id: 'password.validation.message',
|
||||
defaultMessage: 'Password criteria has not been met',
|
||||
@@ -123,7 +148,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'username.format.validation.message': {
|
||||
id: 'username.format.validation.message',
|
||||
defaultMessage: 'Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).',
|
||||
defaultMessage: 'Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces.',
|
||||
description: 'Validation message that appears when username format is invalid',
|
||||
},
|
||||
'support.education.research': {
|
||||
@@ -252,8 +277,8 @@ const messages = defineMessages({
|
||||
// miscellaneous strings
|
||||
'registration.username.suggestion.label': {
|
||||
id: 'registration.username.suggestion.label',
|
||||
defaultMessage: 'Available:',
|
||||
description: 'Available usernames label text.',
|
||||
defaultMessage: 'Suggested:',
|
||||
description: 'Suggested usernames label text.',
|
||||
},
|
||||
'registration.using.tpa.form.heading': {
|
||||
id: 'registration.using.tpa.form.heading',
|
||||
@@ -265,6 +290,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Did you mean',
|
||||
description: 'Did you mean alert suggestion',
|
||||
},
|
||||
'certificate.msg': {
|
||||
id: 'certificate.msg',
|
||||
defaultMessage: '*Offer not eligible for GTx’s Analytics: Essential Tools and Methods MicroMasters Program, ColumbiaX’s Corporate Finance Professional Certificate Program, or courses or programs offered by Wharton, and NYIF.',
|
||||
description: 'Text for the 15% discount experiment',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -34,7 +34,6 @@ const mockStore = configureStore();
|
||||
describe('RegistrationPage', () => {
|
||||
mergeConfig({
|
||||
PRIVACY_POLICY: 'http://privacy-policy.com',
|
||||
REGISTRATION_OPTIONAL_FIELDS: 'gender,goals,levelOfEducation,yearOfBirth',
|
||||
TOS_AND_HONOR_CODE: 'http://tos-and-honot-code.com',
|
||||
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME,
|
||||
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME,
|
||||
@@ -207,15 +206,16 @@ describe('RegistrationPage', () => {
|
||||
|
||||
it('should update errors for frontend validations', () => {
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
registrationPage.find('input#name').simulate('blur', { target: { value: 'http://test.com', name: 'name' } });
|
||||
|
||||
registrationPage.find('input#password').simulate('blur', { target: { value: 'pas', name: 'password' } });
|
||||
expect(registrationPage.find('RegistrationPage').state('errors')).toEqual({
|
||||
email: '', name: '', username: '', password: 'Password criteria has not been met', country: '',
|
||||
email: '', name: 'Enter a valid name', username: '', password: 'Password criteria has not been met', country: '',
|
||||
});
|
||||
|
||||
registrationPage.find('input#password').simulate('blur', { target: { value: 'invalid-email', name: 'email' } });
|
||||
expect(registrationPage.find('RegistrationPage').state('errors')).toEqual({
|
||||
email: 'Enter a valid email address', name: '', username: '', password: 'Password criteria has not been met', country: '',
|
||||
email: 'Enter a valid email address', name: 'Enter a valid name', username: '', password: 'Password criteria has not been met', country: '',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -270,14 +270,14 @@ describe('RegistrationPage', () => {
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
username: [{ userMessage: 'It looks like this username is already taken' }],
|
||||
email: [{ userMessage: 'It looks like this email address is already registered' }],
|
||||
email: [{ userMessage: `This email is already associated with an existing or previous ${ getConfig().SITE_NAME } account` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />)).find('RegistrationPage');
|
||||
expect(registrationPage.prop('validationDecisions')).toEqual({
|
||||
country: '',
|
||||
email: 'It looks like this email address is already registered',
|
||||
email: `This email is already associated with an existing or previous ${ getConfig().SITE_NAME } account`,
|
||||
name: '',
|
||||
password: '',
|
||||
username: 'It looks like this username is already taken',
|
||||
@@ -334,6 +334,12 @@ describe('RegistrationPage', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
|
||||
});
|
||||
|
||||
it('should set readOnly state false if focus on country field', () => {
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
registrationPage.find('input#country').simulate('focus');
|
||||
expect(registrationPage.find('RegistrationPage').state('readOnly')).toEqual(false);
|
||||
});
|
||||
|
||||
// ******** test alert messages ********
|
||||
|
||||
it('should match third party auth alert', () => {
|
||||
@@ -348,8 +354,8 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const expectedMessage = 'You\'ve successfully signed into Apple! We just need a little more information before '
|
||||
+ 'you start learning with edX.';
|
||||
const expectedMessage = `${'You\'ve successfully signed into Apple! We just need a little more information before '
|
||||
+ 'you start learning with '}${ getConfig().SITE_NAME }.`;
|
||||
|
||||
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registerPage.find('#tpa-alert').find('p').text()).toEqual(expectedMessage);
|
||||
@@ -500,13 +506,47 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
|
||||
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
registerPage.find('RegistrationPage').instance().shouldComponentUpdate(props);
|
||||
registerPage.find('RegistrationPage').setState({ errors: { username: 'It looks like this username is already taken' } });
|
||||
|
||||
expect(registerPage.find('button.username-suggestion').length).toEqual(3);
|
||||
registerPage.find('button.username-suggestion').at(0).simulate('click');
|
||||
expect(registerPage.find('RegistrationPage').state('username')).toEqual('test_1');
|
||||
});
|
||||
|
||||
it('should show username suggestions when full name is populated', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
usernameSuggestions: ['testname', 't.name', 'test_0'],
|
||||
},
|
||||
});
|
||||
|
||||
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
registerPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
|
||||
|
||||
expect(registerPage.find('button.username-suggestion').length).toEqual(3);
|
||||
registerPage.find('button.username-suggestion').at(0).simulate('click');
|
||||
expect(registerPage.find('RegistrationPage').state('username')).toEqual('testname');
|
||||
});
|
||||
|
||||
it('should clear username suggestions when close icon is clicked', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
usernameSuggestions: ['testname', 't.name', 'test_0'],
|
||||
},
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
registerPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
|
||||
|
||||
registerPage.find('button.suggested-username-close-button').at(0).simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
|
||||
});
|
||||
|
||||
it('should redirect to url returned in registration result after successful account creation', () => {
|
||||
const dasboardUrl = 'http://test.com/testing-dashboard/';
|
||||
store = mockStore({
|
||||
@@ -662,6 +702,14 @@ describe('RegistrationPage', () => {
|
||||
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
|
||||
});
|
||||
|
||||
it('should send track event for save_for_later param', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/register'), search: '?save_for_later=true' };
|
||||
renderer.create(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.saveforlater.course.enroll.clicked',
|
||||
{ category: 'save-for-later' });
|
||||
});
|
||||
|
||||
// ******** shouldComponentUpdate tests ********
|
||||
|
||||
it('should populate form with pipeline user details', () => {
|
||||
@@ -711,87 +759,18 @@ describe('RegistrationPage', () => {
|
||||
|
||||
expect(registrationPage.find('RegistrationPage').state('errorCode')).toEqual(INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TestOptionalFields', () => {
|
||||
it('should toggle optional fields state', () => {
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
|
||||
registrationPage.find('input#optional-field-checkbox').simulate('click', { target: { name: 'optionalFields', checked: true } });
|
||||
expect(registrationPage.find('RegistrationPage').state('showOptionalField')).toEqual(true);
|
||||
|
||||
// it should also works when change is made directly instead of click
|
||||
registrationPage.find('input#optional-field-checkbox').simulate('change', { target: { name: 'optionalFields', checked: false } });
|
||||
expect(registrationPage.find('RegistrationPage').state('showOptionalField')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should show optional fields section on optional check enabled', () => {
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
registrationPage.find('input#optional-field-checkbox').simulate('change', { target: { name: 'optionalFields', checked: true } });
|
||||
registrationPage.update();
|
||||
|
||||
expect(registrationPage.find('textarea#goals').length).toEqual(1);
|
||||
expect(registrationPage.find('select#levelOfEducation').length).toEqual(1);
|
||||
expect(registrationPage.find('select#yearOfBirth').length).toEqual(1);
|
||||
expect(registrationPage.find('select#gender').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should show optional field check based on environment variable', () => {
|
||||
it('should display opt-in/opt-out checkbox', () => {
|
||||
mergeConfig({
|
||||
REGISTRATION_OPTIONAL_FIELDS: '',
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
});
|
||||
let registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('input#optional-field-checkbox').length).toEqual(0);
|
||||
|
||||
mergeConfig({
|
||||
REGISTRATION_OPTIONAL_FIELDS: 'gender,goals,levelOfEducation,yearOfBirth',
|
||||
});
|
||||
|
||||
registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('input#optional-field-checkbox').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('send tracking event on optional checkbox enabled', () => {
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
|
||||
registrationPage.find('input#optional-field-checkbox').simulate('change', { target: { name: 'optionalFields', checked: true } });
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.register.optional_fields_selected', {});
|
||||
});
|
||||
|
||||
it('should submit form with optional fields', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
|
||||
const payload = {
|
||||
name: 'John Doe',
|
||||
username: 'john_doe',
|
||||
email: 'john.doe@example.com',
|
||||
password: 'password1',
|
||||
country: 'Pakistan',
|
||||
gender: 'm',
|
||||
year_of_birth: '1997',
|
||||
level_of_education: 'other',
|
||||
goals: 'edX goals',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
is_authn_mfe: true,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
populateRequiredFields(registerPage, payload);
|
||||
expect(registerPage.find('div.opt-checkbox').length).toEqual(1);
|
||||
|
||||
// submit optional fields
|
||||
registerPage.find('input#optional-field-checkbox').simulate('change', { target: { name: 'optionalFields', checked: true } });
|
||||
registerPage.find('select#gender').simulate('change', { target: { value: 'm', name: 'gender' } });
|
||||
registerPage.find('select#yearOfBirth').simulate('change', { target: { value: '1997', name: 'yearOfBirth' } });
|
||||
registerPage.find('select#levelOfEducation').simulate('change', { target: { value: 'other', name: 'levelOfEducation' } });
|
||||
registerPage.find('textarea#goals').simulate('change', { target: { value: 'edX goals', name: 'goals' } });
|
||||
|
||||
registerPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,11 @@ const ResetPasswordPage = (props) => {
|
||||
const validatePasswordFromBackend = async (password) => {
|
||||
let errorMessage = '';
|
||||
try {
|
||||
errorMessage = await validatePassword(password);
|
||||
const payload = {
|
||||
reset_password_page: true,
|
||||
password,
|
||||
};
|
||||
errorMessage = await validatePassword(payload);
|
||||
} catch (err) {
|
||||
errorMessage = '';
|
||||
}
|
||||
@@ -85,6 +89,24 @@ const ResetPasswordPage = (props) => {
|
||||
return !Object.values(formErrors).some(x => (x !== ''));
|
||||
};
|
||||
|
||||
const handleOnBlur = (event) => {
|
||||
let { name, value } = event.target;
|
||||
|
||||
// Do not validate when focus out from 'newPassword' and focus on 'passwordValidation' icon
|
||||
// for better user experience.
|
||||
if (event.relatedTarget
|
||||
&& event.relatedTarget.name === 'passwordValidation'
|
||||
&& name === 'newPassword'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (name === 'passwordValidation') {
|
||||
name = 'newPassword';
|
||||
value = newPassword;
|
||||
}
|
||||
validateInput(name, value);
|
||||
};
|
||||
|
||||
const handleConfirmPasswordChange = (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
@@ -126,7 +148,7 @@ const ResetPasswordPage = (props) => {
|
||||
const { token } = props.match.params;
|
||||
if (token) {
|
||||
props.validateToken(token);
|
||||
return <Spinner animation="border" variant="primary" className="mt-5" />;
|
||||
return <Spinner animation="border" variant="primary" className="centered-align-spinner" />;
|
||||
}
|
||||
} else if (props.status === PASSWORD_RESET_ERROR) {
|
||||
return <Redirect to={updatePathWithQueryParams(RESET_PAGE)} />;
|
||||
@@ -157,7 +179,7 @@ const ResetPasswordPage = (props) => {
|
||||
name="newPassword"
|
||||
value={newPassword}
|
||||
handleChange={(e) => setNewPassword(e.target.value)}
|
||||
handleBlur={(e) => validateInput(e.target.name, e.target.value)}
|
||||
handleBlur={handleOnBlur}
|
||||
handleFocus={handleOnFocus}
|
||||
errorMessage={formErrors.newPassword}
|
||||
floatingLabel={intl.formatMessage(messages['new.password.label'])}
|
||||
|
||||
@@ -39,14 +39,14 @@ export async function resetPassword(payload, token, queryParams) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function validatePassword(password) {
|
||||
export async function validatePassword(payload) {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
};
|
||||
const { data } = await getHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/api/user/v1/validation/registration`,
|
||||
formurlencoded({ password }),
|
||||
formurlencoded(payload),
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
|
||||
237
src/welcome/ProgressiveProfiling.jsx
Normal file
237
src/welcome/ProgressiveProfiling.jsx
Normal file
@@ -0,0 +1,237 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import {
|
||||
configure as configureAuth,
|
||||
AxiosJwtAuthService,
|
||||
ensureAuthenticatedUser,
|
||||
hydrateAuthenticatedUser,
|
||||
getAuthenticatedUser,
|
||||
} from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getLoggingService } from '@edx/frontend-platform/logging';
|
||||
import {
|
||||
Alert,
|
||||
Form,
|
||||
StatefulButton,
|
||||
Hyperlink,
|
||||
Spinner,
|
||||
} from '@edx/paragon';
|
||||
import { Error } from '@edx/paragon/icons';
|
||||
|
||||
import { getFieldData, saveUserProfile } from './data/actions';
|
||||
import { welcomePageSelector } from './data/selectors';
|
||||
import messages from './messages';
|
||||
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import {
|
||||
DEFAULT_REDIRECT_URL, DEFAULT_STATE, FAILURE_STATE, COMPLETE_STATE,
|
||||
} from '../data/constants';
|
||||
import FormFieldRenderer from '../field-renderer';
|
||||
import WelcomePageModal from './WelcomePageModal';
|
||||
import BaseComponent from '../base-component';
|
||||
|
||||
const ProgressiveProfiling = (props) => {
|
||||
const {
|
||||
extendedProfile, fieldDescriptions, formRenderState, intl, submitState, showError,
|
||||
} = props;
|
||||
|
||||
const [ready, setReady] = useState(false);
|
||||
const [registrationResult, setRegistrationResult] = useState({ redirectUrl: '' });
|
||||
const [values, setValues] = useState({});
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
|
||||
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
||||
|
||||
useEffect(() => {
|
||||
configureAuth(AxiosJwtAuthService, { loggingService: getLoggingService(), config: getConfig() });
|
||||
ensureAuthenticatedUser(DASHBOARD_URL).then(() => {
|
||||
hydrateAuthenticatedUser().then(() => {
|
||||
props.getFieldData();
|
||||
setReady(true);
|
||||
});
|
||||
});
|
||||
|
||||
if (props.location.state && props.location.state.registrationResult) {
|
||||
setRegistrationResult(props.location.state.registrationResult);
|
||||
sendPageEvent('login_and_registration', 'welcome');
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!props.location.state || !props.location.state.registrationResult || formRenderState === FAILURE_STATE) {
|
||||
global.location.assign(DASHBOARD_URL);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const authenticatedUser = getAuthenticatedUser();
|
||||
const payload = { ...values, extendedProfile: [] };
|
||||
extendedProfile.forEach(fieldName => {
|
||||
if (values[fieldName]) {
|
||||
payload.extendedProfile.push({ fieldName, fieldValue: values[fieldName] });
|
||||
}
|
||||
delete payload[fieldName];
|
||||
});
|
||||
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
|
||||
|
||||
sendTrackEvent(
|
||||
'edx.bi.welcome.page.submit.clicked',
|
||||
{
|
||||
isGenderSelected: !!values.gender,
|
||||
isYearOfBirthSelected: !!values.year_of_birth,
|
||||
isLevelOfEducationSelected: !!values.level_of_education,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleSkip = (e) => {
|
||||
e.preventDefault();
|
||||
setOpenDialog(true);
|
||||
sendTrackEvent('edx.bi.welcome.page.skip.link.clicked');
|
||||
};
|
||||
|
||||
const onChangeHandler = (e) => {
|
||||
if (e.target.type === 'checkbox') {
|
||||
setValues({ ...values, [e.target.name]: e.target.checked });
|
||||
} else {
|
||||
setValues({ ...values, [e.target.name]: e.target.value });
|
||||
}
|
||||
};
|
||||
|
||||
const formFields = Object.keys(fieldDescriptions).map((fieldName) => {
|
||||
const fieldData = fieldDescriptions[fieldName];
|
||||
return (
|
||||
<span key={fieldData.name}>
|
||||
<FormFieldRenderer
|
||||
fieldData={fieldData}
|
||||
value={values[fieldData.name]}
|
||||
onChangeHandler={onChangeHandler}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
if (formRenderState === COMPLETE_STATE) {
|
||||
return (
|
||||
<>
|
||||
<BaseComponent showWelcomeBanner>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['progressive.profiling.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<WelcomePageModal isOpen={openDialog} redirectUrl={registrationResult.redirectUrl} />
|
||||
{props.shouldRedirect ? (
|
||||
<RedirectLogistration
|
||||
success
|
||||
redirectUrl={registrationResult.redirectUrl}
|
||||
/>
|
||||
) : null}
|
||||
<div className="mw-xs pp-page-content">
|
||||
<div className="pp-page-heading">
|
||||
<h2 className="h3 text-primary">{intl.formatMessage(messages['progressive.profiling.page.heading'])}</h2>
|
||||
</div>
|
||||
<hr className="border-light-700 mb-4" />
|
||||
{showError ? (
|
||||
<Alert id="pp-page-errors" className="mb-3" variant="danger" icon={Error}>
|
||||
<Alert.Heading>{intl.formatMessage(messages['welcome.page.error.heading'])}</Alert.Heading>
|
||||
<p>{intl.formatMessage(messages['welcome.page.error.message'])}</p>
|
||||
</Alert>
|
||||
) : null}
|
||||
<Form>
|
||||
{formFields}
|
||||
<span className="progressive-profiling-support">
|
||||
<Hyperlink
|
||||
isInline
|
||||
variant="muted"
|
||||
destination={getConfig().WELCOME_PAGE_SUPPORT_LINK}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
onClick={() => (sendTrackEvent('edx.bi.welcome.page.support.link.clicked'))}
|
||||
>
|
||||
{intl.formatMessage(messages['optional.fields.information.link'])}
|
||||
</Hyperlink>
|
||||
</span>
|
||||
<div className="d-flex mt-4 mb-3">
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="login-button-width"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['optional.fields.submit.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
<StatefulButton
|
||||
className="text-gray-700 font-weight-500"
|
||||
type="submit"
|
||||
variant="link"
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['optional.fields.skip.button']),
|
||||
}}
|
||||
onClick={handleSkip}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</BaseComponent>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <Spinner id="loader" animation="border" variant="primary" className="centered-align-spinner" />;
|
||||
};
|
||||
|
||||
ProgressiveProfiling.propTypes = {
|
||||
extendedProfile: PropTypes.arrayOf(PropTypes.string),
|
||||
fieldDescriptions: PropTypes.shape({}),
|
||||
formRenderState: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
location: PropTypes.shape({
|
||||
state: PropTypes.object,
|
||||
}),
|
||||
getFieldData: PropTypes.func.isRequired,
|
||||
saveUserProfile: PropTypes.func.isRequired,
|
||||
showError: PropTypes.bool,
|
||||
shouldRedirect: PropTypes.bool,
|
||||
submitState: PropTypes.string,
|
||||
};
|
||||
|
||||
ProgressiveProfiling.defaultProps = {
|
||||
extendedProfile: [],
|
||||
fieldDescriptions: {},
|
||||
location: { state: {} },
|
||||
shouldRedirect: false,
|
||||
showError: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
extendedProfile: welcomePageSelector(state).extendedProfile,
|
||||
fieldDescriptions: welcomePageSelector(state).fieldDescriptions,
|
||||
formRenderState: welcomePageSelector(state).formRenderState,
|
||||
shouldRedirect: welcomePageSelector(state).success,
|
||||
submitState: welcomePageSelector(state).submitState,
|
||||
showError: welcomePageSelector(state).showError,
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
saveUserProfile,
|
||||
getFieldData,
|
||||
},
|
||||
)(injectIntl(ProgressiveProfiling));
|
||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { sendPageEvent } from '@edx/frontend-platform/analytics';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import {
|
||||
configure as configureAuth,
|
||||
AxiosJwtAuthService,
|
||||
@@ -68,6 +68,11 @@ const WelcomePage = (props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (getConfig().ENABLE_COPPA_COMPLIANCE && EDUCATION_LEVELS) {
|
||||
const index = EDUCATION_LEVELS.indexOf('el');
|
||||
EDUCATION_LEVELS.splice(index, 1);
|
||||
}
|
||||
|
||||
const getOptions = (fieldName) => {
|
||||
const options = {
|
||||
yearOfBirth: YEAR_OF_BIRTH_OPTIONS.map(({ value, label }) => (
|
||||
@@ -100,6 +105,16 @@ const WelcomePage = (props) => {
|
||||
});
|
||||
|
||||
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
|
||||
|
||||
sendTrackEvent(
|
||||
'edx.bi.welcome.page.submit.clicked',
|
||||
{
|
||||
isGenderSelected: !!payload.gender,
|
||||
isYearOfBirthSelected: !!payload.yearOfBirth,
|
||||
isLevelOfEducationSelected: !!payload.levelOfEducation,
|
||||
},
|
||||
);
|
||||
|
||||
window.optimizely.push({
|
||||
type: 'event',
|
||||
eventName: 'authn_welcome_page_submit_btn_clicked',
|
||||
@@ -109,6 +124,7 @@ const WelcomePage = (props) => {
|
||||
const handleSkip = (e) => {
|
||||
e.preventDefault();
|
||||
setOpenDialog(true);
|
||||
sendTrackEvent('edx.bi.welcome.page.skip.link.clicked');
|
||||
|
||||
window.optimizely.push({
|
||||
type: 'event',
|
||||
@@ -122,7 +138,7 @@ const WelcomePage = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseComponent>
|
||||
<BaseComponent showWelcomeBanner>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['progressive.profiling.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
@@ -159,19 +175,22 @@ const WelcomePage = (props) => {
|
||||
{getOptions('levelOfEducation')}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="yearOfBirth">
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="yearOfBirth"
|
||||
value={values.yearOfBirth}
|
||||
onChange={(e) => onChangeHandler(e)}
|
||||
trailingElement={<Icon src={ExpandMore} />}
|
||||
floatingLabel={intl.formatMessage(messages['year.of.birth.label'])}
|
||||
>
|
||||
<option value="">{intl.formatMessage(messages['year.of.birth.label'])}</option>
|
||||
{getOptions('yearOfBirth')}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
{(!getConfig().ENABLE_COPPA_COMPLIANCE)
|
||||
&& (
|
||||
<Form.Group controlId="yearOfBirth">
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="yearOfBirth"
|
||||
value={values.yearOfBirth}
|
||||
onChange={(e) => onChangeHandler(e)}
|
||||
trailingElement={<Icon src={ExpandMore} />}
|
||||
floatingLabel={intl.formatMessage(messages['year.of.birth.label'])}
|
||||
>
|
||||
<option value="">{intl.formatMessage(messages['year.of.birth.label'])}</option>
|
||||
{getOptions('yearOfBirth')}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
)}
|
||||
<Form.Group controlId="gender" className="mb-3">
|
||||
<Form.Control
|
||||
as="select"
|
||||
@@ -191,6 +210,7 @@ const WelcomePage = (props) => {
|
||||
destination={getConfig().WELCOME_PAGE_SUPPORT_LINK}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
onClick={() => (sendTrackEvent('edx.bi.welcome.page.support.link.clicked'))}
|
||||
>
|
||||
{intl.formatMessage(messages['optional.fields.information.link'])}
|
||||
</Hyperlink>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const GET_FIELDS_DATA = new AsyncActionType('FIELD_DESCRIPTION', 'GET_FIELDS_DATA');
|
||||
export const SAVE_USER_PROFILE = new AsyncActionType('USER_PROFILE', 'SAVE_USER_PROFILE');
|
||||
|
||||
// save additional user information
|
||||
@@ -19,3 +20,21 @@ export const saveUserProfileSuccess = () => ({
|
||||
export const saveUserProfileFailure = () => ({
|
||||
type: SAVE_USER_PROFILE.FAILURE,
|
||||
});
|
||||
|
||||
// get field data from platform
|
||||
export const getFieldData = () => ({
|
||||
type: GET_FIELDS_DATA.BASE,
|
||||
});
|
||||
|
||||
export const getFieldDataBegin = () => ({
|
||||
type: GET_FIELDS_DATA.BEGIN,
|
||||
});
|
||||
|
||||
export const getFieldDataSuccess = (data, extendedProfile) => ({
|
||||
type: GET_FIELDS_DATA.SUCCESS,
|
||||
payload: { data, extendedProfile },
|
||||
});
|
||||
|
||||
export const getFieldDataFailure = () => ({
|
||||
type: GET_FIELDS_DATA.FAILURE,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { SAVE_USER_PROFILE } from './actions';
|
||||
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
||||
import { GET_FIELDS_DATA, SAVE_USER_PROFILE } from './actions';
|
||||
import {
|
||||
DEFAULT_STATE, PENDING_STATE, COMPLETE_STATE, FAILURE_STATE,
|
||||
} from '../../data/constants';
|
||||
|
||||
export const defaultState = {
|
||||
extendedProfile: [],
|
||||
fieldDescriptions: {},
|
||||
formRenderState: DEFAULT_STATE,
|
||||
success: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
showError: false,
|
||||
@@ -9,6 +14,23 @@ export const defaultState = {
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case GET_FIELDS_DATA.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
formRenderState: PENDING_STATE,
|
||||
};
|
||||
case GET_FIELDS_DATA.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
extendedProfile: action.payload.extendedProfile,
|
||||
fieldDescriptions: action.payload.data,
|
||||
formRenderState: COMPLETE_STATE,
|
||||
};
|
||||
case GET_FIELDS_DATA.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
formRenderState: FAILURE_STATE,
|
||||
};
|
||||
case SAVE_USER_PROFILE.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import {
|
||||
GET_FIELDS_DATA,
|
||||
getFieldDataBegin,
|
||||
getFieldDataFailure,
|
||||
getFieldDataSuccess,
|
||||
SAVE_USER_PROFILE,
|
||||
saveUserProfileBegin,
|
||||
saveUserProfileFailure,
|
||||
saveUserProfileSuccess,
|
||||
} from './actions';
|
||||
|
||||
import patchAccount from './service';
|
||||
import { patchAccount, getOptionalFieldData } from './service';
|
||||
|
||||
export function* saveUserProfileInformation(action) {
|
||||
try {
|
||||
@@ -20,6 +24,17 @@ export function* saveUserProfileInformation(action) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* getFieldData() {
|
||||
try {
|
||||
yield put(getFieldDataBegin());
|
||||
const data = yield call(getOptionalFieldData);
|
||||
yield put(getFieldDataSuccess(data.fields, data.extended_profile));
|
||||
} catch (e) {
|
||||
yield put(getFieldDataFailure());
|
||||
}
|
||||
}
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(SAVE_USER_PROFILE.BASE, saveUserProfileInformation);
|
||||
yield takeEvery(GET_FIELDS_DATA.BASE, getFieldData);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
export default async function patchAccount(username, commitValues) {
|
||||
export async function patchAccount(username, commitValues) {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/merge-patch+json' },
|
||||
};
|
||||
@@ -16,3 +16,20 @@ export default async function patchAccount(username, commitValues) {
|
||||
throw (error);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOptionalFieldData() {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(
|
||||
`${getConfig().LMS_BASE_URL}/api/optional_fields`,
|
||||
requestConfig,
|
||||
)
|
||||
.catch((e) => {
|
||||
throw (e);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default } from './WelcomePage';
|
||||
export { default as ProgressiveProfiling } from './ProgressiveProfiling';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
|
||||
200
src/welcome/tests/ProgressiveProfiling.test.jsx
Normal file
200
src/welcome/tests/ProgressiveProfiling.test.jsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import React from 'react';
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import * as logging from '@edx/frontend-platform/logging';
|
||||
import { injectIntl, IntlProvider, configure } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { getFieldData, saveUserProfile } from '../data/actions';
|
||||
import ProgressiveProfiling from '../ProgressiveProfiling';
|
||||
import {
|
||||
COMPLETE_STATE, DEFAULT_REDIRECT_URL, FAILURE_STATE, PENDING_STATE,
|
||||
} from '../../data/constants';
|
||||
|
||||
const IntlProgressiveProfilingPage = injectIntl(ProgressiveProfiling);
|
||||
const mockStore = configureStore();
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('@edx/frontend-platform/logging');
|
||||
|
||||
analytics.sendTrackEvent = jest.fn();
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
logging.getLoggingService = jest.fn();
|
||||
|
||||
auth.configure = jest.fn();
|
||||
auth.ensureAuthenticatedUser = jest.fn().mockImplementation(() => Promise.resolve(true));
|
||||
auth.hydrateAuthenticatedUser = jest.fn().mockImplementation(() => Promise.resolve(true));
|
||||
|
||||
describe('ProgressiveProfilingTests', () => {
|
||||
mergeConfig({
|
||||
WELCOME_PAGE_SUPPORT_LINK: 'http://localhost:1999/welcome',
|
||||
});
|
||||
|
||||
const registrationResult = { redirectUrl: 'http://localhost:18000/dashboard', success: true };
|
||||
let props = {};
|
||||
let store = {};
|
||||
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
||||
const initialState = {
|
||||
welcomePage: {
|
||||
formRenderState: COMPLETE_STATE,
|
||||
},
|
||||
};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
const getProgressiveProfilingPage = async () => {
|
||||
const progressiveProfilingPage = mount(reduxWrapper(<IntlProgressiveProfilingPage {...props} />));
|
||||
await act(async () => {
|
||||
await Promise.resolve(progressiveProfilingPage);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
progressiveProfilingPage.update();
|
||||
});
|
||||
|
||||
return progressiveProfilingPage;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore(initialState);
|
||||
configure({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
ENVIRONMENT: 'production',
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum',
|
||||
},
|
||||
messages: { 'es-419': {}, de: {}, 'en-us': {} },
|
||||
});
|
||||
props = {
|
||||
getFieldData: jest.fn(),
|
||||
location: {
|
||||
state: {
|
||||
registrationResult,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should fire action to get form fields', async () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
progressiveProfilingPage.find('button.btn-link').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(getFieldData());
|
||||
});
|
||||
|
||||
it('should show spinner until fields are fetched', async () => {
|
||||
store = mockStore({
|
||||
welcomePage: {
|
||||
formRenderState: PENDING_STATE,
|
||||
},
|
||||
});
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
expect(progressiveProfilingPage.find('#loader').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render fields returned by backend api', async () => {
|
||||
store = mockStore({
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
fieldDescriptions: {
|
||||
gender: {
|
||||
name: 'gender',
|
||||
type: 'select',
|
||||
label: 'Gender',
|
||||
options: [['m', 'Male'], ['f', 'Female'], ['o', 'Other/Prefer Not to Say']],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
expect(progressiveProfilingPage.find('#gender').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should submit user profile details on form submission', async () => {
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
|
||||
const formPayload = {
|
||||
gender: 'm',
|
||||
extended_profile: [{ field_name: 'company', field_value: 'edx' }],
|
||||
};
|
||||
store = mockStore({
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
extendedProfile: ['company'],
|
||||
fieldDescriptions: {
|
||||
gender: {
|
||||
name: 'gender',
|
||||
type: 'select',
|
||||
label: 'Gender',
|
||||
options: [['m', 'Male'], ['f', 'Female'], ['o', 'Other/Prefer Not to Say']],
|
||||
},
|
||||
company: {
|
||||
name: 'company',
|
||||
type: 'text',
|
||||
label: 'Company',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
progressiveProfilingPage.find('select#gender').simulate('change', { target: { value: 'm', name: 'gender' } });
|
||||
progressiveProfilingPage.find('input#company').simulate('change', { target: { value: 'edx', name: 'company' } });
|
||||
|
||||
progressiveProfilingPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(saveUserProfile('abc123', formPayload));
|
||||
});
|
||||
|
||||
it('should open modal on pressing skip for now button', async () => {
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
progressiveProfilingPage.find('button.btn-link').simulate('click');
|
||||
expect(progressiveProfilingPage.find('.pgn__modal-content-container').exists()).toBeTruthy();
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked');
|
||||
});
|
||||
|
||||
it('should send analytic event for support link click', async () => {
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
progressiveProfilingPage.find('.progressive-profiling-support a[target="_blank"]').simulate('click');
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked');
|
||||
});
|
||||
|
||||
it('should show error message when patch request fails', async () => {
|
||||
store = mockStore({
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
showError: true,
|
||||
},
|
||||
});
|
||||
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
expect(progressiveProfilingPage.find('#pp-page-errors').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should redirect to dashboard if no form fields are configured', async () => {
|
||||
store = mockStore({
|
||||
welcomePage: {
|
||||
formRenderState: FAILURE_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL,
|
||||
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
|
||||
};
|
||||
await getProgressiveProfilingPage();
|
||||
expect(window.location.href).toBe(DASHBOARD_URL);
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,17 @@ describe('WelcomePageTests', () => {
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
const getWelcomePage = async () => {
|
||||
const welcomePage = mount(reduxWrapper(<IntlWelcomePage {...props} />));
|
||||
await act(async () => {
|
||||
await Promise.resolve(welcomePage);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
welcomePage.update();
|
||||
});
|
||||
|
||||
return welcomePage;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({});
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'edX' }));
|
||||
@@ -66,14 +77,8 @@ describe('WelcomePageTests', () => {
|
||||
level_of_education: 'other',
|
||||
gender: 'm',
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const welcomePage = mount(reduxWrapper(<IntlWelcomePage {...props} />));
|
||||
await act(async () => {
|
||||
await Promise.resolve(welcomePage);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
welcomePage.update();
|
||||
});
|
||||
const welcomePage = await getWelcomePage();
|
||||
|
||||
welcomePage.find('select#gender').simulate('change', { target: { value: 'm', name: 'gender' } });
|
||||
welcomePage.find('select#yearOfBirth').simulate('change', { target: { value: 1997, name: 'yearOfBirth' } });
|
||||
@@ -81,18 +86,28 @@ describe('WelcomePageTests', () => {
|
||||
|
||||
welcomePage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(saveUserProfile('edX', formPayload));
|
||||
|
||||
const customProps = {
|
||||
isGenderSelected: true,
|
||||
isYearOfBirthSelected: true,
|
||||
isLevelOfEducationSelected: true,
|
||||
};
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.submit.clicked', customProps);
|
||||
});
|
||||
|
||||
it('should open modal on pressing skip for now button', async () => {
|
||||
const welcomePage = mount(reduxWrapper(<IntlWelcomePage {...props} />));
|
||||
await act(async () => {
|
||||
await Promise.resolve(welcomePage);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
welcomePage.update();
|
||||
});
|
||||
const welcomePage = await getWelcomePage();
|
||||
|
||||
welcomePage.find('button.btn-link').simulate('click');
|
||||
expect(welcomePage.find('.pgn__modal-content-container').exists()).toBeTruthy();
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked');
|
||||
});
|
||||
|
||||
it('should send analytic event for support link click', async () => {
|
||||
const welcomePage = await getWelcomePage();
|
||||
|
||||
welcomePage.find('.progressive-profiling-support a[target="_blank"]').simulate('click');
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked');
|
||||
});
|
||||
|
||||
it('should show error message when patch request fails', async () => {
|
||||
@@ -101,12 +116,7 @@ describe('WelcomePageTests', () => {
|
||||
showError: true,
|
||||
},
|
||||
});
|
||||
const welcomePage = mount(reduxWrapper(<IntlWelcomePage {...props} />));
|
||||
await act(async () => {
|
||||
await Promise.resolve(welcomePage);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
welcomePage.update();
|
||||
});
|
||||
const welcomePage = await getWelcomePage();
|
||||
expect(welcomePage.find('#welcome-page-errors').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user