Compare commits

...

82 Commits

Author SHA1 Message Date
Syed Sajjad Hussain Shah
db75d5046d test: temp changes for embedded authn poc 2023-06-06 18:50:45 +05:00
Attiya Ishaque
5aec091156 fix: fix console errors on running test (#930) 2023-06-02 17:49:40 +05:00
renovate[bot]
68993cc21f fix(deps): update dependency @edx/paragon to v20.40.2 2023-06-01 11:16:17 +00:00
renovate[bot]
404fc2b36b fix(deps): update dependency @edx/paragon to v20.40.1 2023-05-31 22:04:26 +00:00
renovate[bot]
7bb06d0bfa fix(deps): update dependency @edx/frontend-platform to v4.5.1 2023-05-30 06:52:39 +00:00
renovate[bot]
abbb64b9c7 fix(deps): update dependency @edx/paragon to v20.40.0 2023-05-30 00:53:33 +00:00
renovate[bot]
99ca582c1a fix(deps): update dependency @edx/frontend-platform to v4.5.0 2023-05-29 10:19:49 +00:00
renovate[bot]
3e4cfc1573 fix(deps): update dependency @edx/paragon to v20.39.3 2023-05-29 08:09:07 +00:00
renovate[bot]
5437e1b7e9 chore(deps): update dependency @edx/frontend-build to v12.8.38 2023-05-29 03:58:43 +00:00
Attiya Ishaque
4d33cd7d69 fix: fix design issue in user name suggestion field (#922) 2023-05-24 18:32:02 +05:00
Attiya Ishaque
cb82e94b53 refactor: BEM convention for modules (#907) 2023-05-23 17:21:29 +05:00
renovate[bot]
dbe14ccedf fix(deps): update dependency @edx/frontend-platform to v4.4.0 2023-05-23 00:09:52 +00:00
renovate[bot]
841edf2d24 fix(deps): update dependency @edx/paragon to v20.39.2 2023-05-22 20:34:23 +00:00
Blue
0c375cc50c refactor: BEM convention for common components (#914)
Description:
Need to apply BEM comvention for common components

VAN-1425
2023-05-22 10:47:53 +05:00
renovate[bot]
50072887d0 fix(deps): update dependency @edx/paragon to v20.39.1 2023-05-19 17:49:26 +00:00
Attiya Ishaque
976814d846 fix: fix console error in login and register's test (#909) 2023-05-19 12:39:52 +05:00
Blue
c22024cf66 refactor: BEM convention for reset password (#906)
Description:

Need to implement BEM convention for reset password page

VAN-1423
2023-05-19 11:16:49 +05:00
Syed Sajjad Hussain Shah
829a219b9f fix: bugs in country field (#913)
VAN-1415
2023-05-19 10:03:39 +05:00
renovate[bot]
13d572ab28 fix(deps): update dependency @edx/paragon to v20.39.0 2023-05-18 15:00:59 +00:00
renovate[bot]
c0c2ffa122 fix(deps): update dependency @edx/paragon to v20.37.0 2023-05-17 21:17:46 +00:00
Syed Sajjad Hussain Shah
84c563fda3 refactor: use paragon Autosuggest field for country field (#911)
VAN-1415

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-05-17 13:13:04 +05:00
Syed Sajjad Hussain Shah
97720d6a46 Revert "refactor: use paragon Autosuggest field for country field (#904)" (#910)
This reverts commit 386982d9c4.
2023-05-17 12:31:21 +05:00
Syed Sajjad Hussain Shah
386982d9c4 refactor: use paragon Autosuggest field for country field (#904)
VAN-1415

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-05-17 12:11:27 +05:00
renovate[bot]
cb38a2a148 fix(deps): update dependency @edx/paragon to v20.36.2 2023-05-16 19:17:29 +00:00
renovate[bot]
07f19209bf fix(deps): update dependency @edx/frontend-platform to v4.3.0 2023-05-16 02:33:21 +00:00
renovate[bot]
69c0ca13fc fix(deps): update dependency @edx/paragon to v20.36.1 2023-05-15 18:26:20 +00:00
Jenkins
e49c50f55c chore(i18n): update translations 2023-05-14 11:16:53 -04:00
Syed Sajjad Hussain Shah
f0105f0094 Revert "refactor: use paragon Autosuggest field for country field (#897)" (#900)
This reverts commit 3c89afea4a.
2023-05-12 13:01:22 +05:00
Syed Sajjad Hussain Shah
3c89afea4a refactor: use paragon Autosuggest field for country field (#897)
VAN-1415

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-05-12 12:45:29 +05:00
renovate[bot]
848f574f3f fix(deps): update dependency @edx/paragon to v20.36.0 2023-05-11 04:41:23 +00:00
Blue
ec9e34cea4 fix: add departments in Zendesk widget (#896)
Description:
Add departments list in Zendesk widget configurations

VAN-1426
2023-05-10 16:58:26 +05:00
Omar Al-Ithawi
f9069df4e6 feat: use atlas in make pull_translations (#890)
Changes
-------
 - Move all i18n imports into `src/i18n/index.js` so `intl-imports.js` can
   override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL`
   environment variable is set.

Refs: [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) implementing Translation Infrastructure OEP-58.
2023-05-09 11:12:36 -04:00
renovate[bot]
9035f3eb7e fix(deps): update dependency core-js to v3.30.2 2023-05-08 05:14:07 +00:00
renovate[bot]
e197e788d1 fix(deps): update dependency @edx/paragon to v20.34.0 2023-05-05 19:58:17 +00:00
renovate[bot]
49d33522a8 chore(deps): update dependency @edx/frontend-build to v12.8.27 2023-05-05 17:21:17 +00:00
renovate[bot]
06f0ec3c0b chore(deps): update dependency @edx/frontend-build to v12.8.22 2023-05-04 21:04:47 +00:00
Attiya Ishaque
54319c6949 fix: remove cookie policy banner from authn (#888) 2023-05-04 17:12:21 +05:00
renovate[bot]
86f875ec3e chore(deps): update dependency @edx/frontend-build to v12.8.17 2023-05-03 00:45:13 +00:00
renovate[bot]
754a6ddb12 fix(deps): update dependency @edx/paragon to v20.33.0 2023-05-02 19:39:19 +00:00
Shahbaz Shabbir
62e8f75b96 fix: eliminate the usage of extra OR condition in MFE context API (#882) 2023-05-02 19:17:28 +05:00
Shahbaz Shabbir
c0deb663a6 chore: upgrade the query-string version to 7.1.3 (#869) 2023-05-02 17:08:35 +05:00
Zainab Amir
ce28add152 fix: conditionally initialize optimizely (#862) 2023-05-02 16:11:55 +05:00
renovate[bot]
5f89315947 chore(deps): update dependency babel-plugin-formatjs to v10.5.1 2023-05-01 08:06:15 +00:00
renovate[bot]
56dd194a1a fix(deps): update dependency @edx/paragon to v20.32.3 2023-04-28 08:31:27 +00:00
Attiya Ishaque
adcdcc4c8b chore: remove fortawesome/free-regular-svg-icons package (#867) 2023-04-26 18:39:06 +05:00
renovate[bot]
7fd45f089d chore(deps): update dependency @edx/frontend-build to v12.8.16 2023-04-26 06:17:05 +00:00
renovate[bot]
65d82b2080 fix(deps): update dependency @edx/paragon to v20.32.0 2023-04-24 14:10:11 +00:00
renovate[bot]
f771935e20 fix(deps): update dependency @edx/paragon to v20.31.1 2023-04-24 00:46:26 +00:00
renovate[bot]
70c255fc4f fix(deps): update font awesome to v6.4.0 2023-04-21 00:49:33 +00:00
renovate[bot]
bd1396fc54 fix(deps): update dependency @edx/frontend-platform to v4.2.0 2023-04-20 18:20:03 +00:00
Stanislav
cba5395d5c fix: Remove horizontal scroll in windows (#760) 2023-04-20 17:55:05 +05:00
Zainab Amir
b42c09d919 chore: remove unused lodash dependency (#870) 2023-04-20 14:51:40 +05:00
Blue
b7af2356fa fix: Remove extract-react-intl-messages (#865)
Remove unused package extract-react-intl-messages from package.json

VAN-1378
2023-04-20 14:32:11 +05:00
Syed Sajjad Hussain Shah
22d477e55f fix: import/no-cycle eslint issue (#875)
VAN-1388

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-04-20 12:33:40 +05:00
renovate[bot]
dfa69c27bb chore(deps): update dependency babel-plugin-formatjs to v10.5.0 2023-04-20 05:12:27 +00:00
renovate[bot]
6b78158db2 fix(deps): update dependency reselect to v4.1.8 2023-04-19 23:28:48 +00:00
renovate[bot]
c92cac0eed fix(deps): update dependency react-loading-skeleton to v3.2.1 2023-04-19 19:35:03 +00:00
Attiya Ishaque
b2dce920fa chore: remove semver-regex package (#868) 2023-04-19 20:42:59 +05:00
Attiya Ishaque
44cec762fb fix: replace faSignInAlt with paragon icon (#863) 2023-04-19 16:33:54 +05:00
Shahbaz Shabbir
a98188ead8 fix: change keys to camelCase for mfe_context response (PR# 835) (#857) 2023-04-19 13:03:26 +05:00
Syed Sajjad Hussain Shah
294b1a469f chore: remove sanitize-html package (#866)
VAN-1381

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-04-19 12:21:10 +05:00
Syed Sajjad Hussain Shah
ebed588c1c chore: remove react-onclickoutside package (#864)
VAN-1380

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-04-18 15:13:10 +05:00
Blue
46f8217e1a fix: reset-password code coverage (#855)
Improve the code coverage of reset-password component

VAN-1369
2023-04-18 13:24:59 +05:00
Blue
9ab23cf485 Merge pull request #849 from openedx/ahtesham/VAN-1368/test-coverage-registation
fix: registration test coverage
2023-04-17 15:46:20 +05:00
ahtesham-quraish
7790660fe8 fix: registration test coverage
Increase test coverage of registration flow

VAN-1368
2023-04-17 15:37:53 +05:00
Jenkins
1dd2726beb chore(i18n): update translations 2023-04-16 11:16:49 -04:00
renovate[bot]
ba9ce89d1b fix(deps): update dependency core-js to v3.30.1 2023-04-14 21:03:27 +00:00
renovate[bot]
c9082ac709 chore(deps): update dependency @edx/frontend-build to v12.8.10 2023-04-14 16:39:01 +00:00
renovate[bot]
27c8fa8986 fix(deps): update dependency @edx/frontend-platform to v4.1.0 2023-04-14 16:28:37 +00:00
Syed Sajjad Hussain Shah
a04289d71b Revert "fix: change keys to camelCase for mfe_context response (#835)" (#856)
This reverts commit b0745de672.
2023-04-14 14:43:01 +05:00
Blue
321859e0f5 Merge pull request #854 from openedx/ahtesham/1349/update-conditional-logic
fix: remove conditional logic
2023-04-13 14:34:20 +05:00
ahtesham-quraish
4b4bf413c1 fix: remove conditional logic
Remove conditional logic from autoupdate label as it does not run on master push

VAN-1349
2023-04-13 14:21:14 +05:00
renovate[bot]
06c4f75b4a chore(deps): update dependency eslint-plugin-import to v2.27.5 (#811)
* chore(deps): update dependency eslint-plugin-import to v2.27.5

* fix: fix eslint tests

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: attiyaishaque <atiya.ishaq@arbisoft.com>
2023-04-13 13:41:10 +05:00
Attiya Ishaque
12dd97af61 fix: [VAN-1367] Remove Optimizely web from Authn (#843) 2023-04-13 12:54:29 +05:00
Zainab Amir
8e77197459 fix: update pull translation command (#852) 2023-04-13 12:23:48 +05:00
Zainab Amir
3cc64cada6 fix: nested components in CountryField (#851) 2023-04-13 12:05:32 +05:00
Yoiber
3ac5874df1 chore(i18n): add more languages (#747) 2023-04-13 11:24:38 +05:00
Shahbaz Shabbir
107dd6f360 fix: Update eslint configuration and resolve linting errors and test failures (#847) 2023-04-12 22:52:49 +05:00
Blue
347e0cd336 Merge pull request #850 from openedx/ahtesham/VAN-1349/update-github-token
fix: update github token
2023-04-12 12:20:07 +05:00
ahtesham-quraish
2ba6058ec7 fix: update github token
update github token in autoupdate workflow

VAN-1349
2023-04-12 12:08:59 +05:00
Blue
683aa258b8 Merge pull request #825 from openedx/ahtesham/VAN-1349/add-label-support
fix: add new flag in autoupdate action
2023-04-12 11:59:22 +05:00
ahtesham-quraish
d7ad7e314d fix: add new flag in autoupdate action
trigger autoupdate workflow when autoupdate label is added

VAN-1349
2023-04-12 11:53:53 +05:00
89 changed files with 4054 additions and 4433 deletions

1
.env
View File

@@ -22,7 +22,6 @@ LOGIN_ISSUE_SUPPORT_LINK=''
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
# ***** Features flags *****
DISABLE_ENTERPRISE_LOGIN=''
ENABLE_COOKIE_POLICY_BANNER=''
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
ENABLE_PERSONALIZED_RECOMMENDATIONS=''

View File

@@ -1,5 +1,4 @@
# Copy these to the .env.private to enable edX specific functionality on local system
ENABLE_COOKIE_POLICY_BANNER='true'
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
MARKETING_EMAILS_OPT_IN='true'
SHOW_CONFIGURABLE_EDX_FIELDS='true'

View File

@@ -48,7 +48,5 @@ module.exports = createConfig('eslint', {
},
],
'function-paren-newline': 'off',
'no-import-assign': 'off',
'react/no-unstable-nested-components': 'off',
},
});

View File

@@ -3,14 +3,18 @@ on:
push:
branches:
- master
pull_request:
types: [ labeled ]
branches:
- master
jobs:
autoupdate:
name: autoupdate
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- uses: docker://chinthakagodawita/autoupdate-action:v1
env:
GITHUB_TOKEN: "${{ secrets.CC_GITHUB_TOKEN }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
DRY_RUN: "false"
PR_FILTER: "labelled"
PR_LABELS: "autoupdate"

View File

@@ -1,6 +1,7 @@
export TRANSIFEX_RESOURCE = frontend-app-authn
transifex_langs = "ar,fr,es_419,zh_CN,it_IT,pt_PT,de_DE,uk,ru,hi"
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA,it_IT,pt_PT,de_DE"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
@@ -42,11 +43,37 @@ push_translations:
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
# This target is used by CI.
$(intl_imports) paragon frontend-app-authn
endif
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json
.PHONY: validate
validate:
make validate-no-uncommitted-package-lock-changes
npm run i18n_extract
npm run lint -- --max-warnings 0
npm run test
npm run build
.PHONY: validate.ci
validate.ci:
npm ci
make validate

View File

@@ -112,9 +112,6 @@ The authentication micro-frontend also requires the following additional variabl
- Name of MFE, this will be used by the API to get runtime configurations for the specific micro frontend. For a frontend repo `frontend-app-appName`, use `appName` as APP_ID.
- ``authn`` | ``''``
* - ``ENABLE_COOKIE_POLICY_BANNER``
- Enables support for displaying the cookies acceptance banner.
- ``true`` | ``''`` (empty strings are falsy)
edX-specific Environment Variables
**********************************

5777
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,31 +33,25 @@
},
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-cookie-policy-banner": "2.2.2",
"@edx/frontend-platform": "4.0.2",
"@edx/paragon": "20.30.1",
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-brands-svg-icons": "6.2.1",
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@edx/frontend-platform": "4.5.1",
"@edx/paragon": "20.40.2",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-brands-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@optimizely/react-sdk": "^2.9.1",
"@redux-devtools/extension": "3.2.5",
"algoliasearch": "^4.14.3",
"classnames": "2.3.2",
"core-js": "3.30.0",
"extract-react-intl-messages": "4.1.1",
"core-js": "3.30.2",
"fastest-levenshtein": "1.0.16",
"form-urlencoded": "6.1.0",
"lodash.camelcase": "4.3.0",
"lodash.snakecase": "4.1.1",
"prop-types": "15.8.1",
"query-string": "5.1.1",
"query-string": "7.1.3",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "6.1.0",
"react-loading-skeleton": "3.2.0",
"react-onclickoutside": "6.13.0",
"react-loading-skeleton": "3.2.1",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "5.3.4",
@@ -69,19 +63,17 @@
"redux-saga": "1.2.3",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.13.11",
"reselect": "4.1.7",
"sanitize-html": "2.10.0",
"semver-regex": "3.1.4",
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "12.8.6",
"@edx/frontend-build": "12.8.38",
"@edx/reactifex": "1.1.0",
"babel-plugin-formatjs": "10.4.0",
"babel-plugin-formatjs": "10.5.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.7",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.27.5",
"glob": "7.2.3",
"history": "5.3.0",
"husky": "7.0.4",

View File

@@ -4,15 +4,6 @@
<title>Authn | <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<% if (process.env.OPTIMIZELY_URL) { %>
<script
src="<%= process.env.OPTIMIZELY_URL %>"
></script>
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
<script
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
></script>
<% } %>
</head>
<body>
<div id="root"></div>

View File

@@ -6,7 +6,7 @@ import { Helmet } from 'react-helmet';
import { Redirect, Route, Switch } from 'react-router-dom';
import {
Logistration, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
} from './common-components';
import configureStore from './data/configureStore';
import {
@@ -20,6 +20,7 @@ import {
} from './data/constants';
import { updatePathWithQueryParams } from './data/utils';
import { ForgotPasswordPage } from './forgot-password';
import Logistration from './logistration/Logistration';
import { ProgressiveProfiling } from './progressive-profiling';
import { RecommendationsPage } from './recommendations';
import { ResetPasswordPage } from './reset-password';

View File

@@ -1,9 +1,6 @@
import React from 'react';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { getLocale } from '@edx/frontend-platform/i18n';
import { breakpoints } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types';
@@ -22,7 +19,6 @@ const BaseComponent = ({ children, showWelcomeBanner }) => {
return (
<>
{getConfig().ENABLE_COOKIE_POLICY_BANNER ? <CookiePolicyBanner languageCode={getLocale()} /> : null}
<div className="col-md-12 extra-large-screen-top-stripe" />
<div className="layout">
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>

View File

@@ -4,13 +4,14 @@ import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Button, Form,
Icon,
} from '@edx/paragon';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { Login } from '@edx/paragon/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
/**
* This component renders the Single sign-on (SSO) button only for the tpa provider passed
@@ -47,16 +48,18 @@ const EnterpriseSSO = (props) => {
>
{tpaProvider.iconImage ? (
<div aria-hidden="true">
<img className="icon-image" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
<img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
</div>
)
: (
<>
<div className="font-container" aria-hidden="true">
<FontAwesomeIcon
icon={SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? ['fab', tpaProvider.iconClass] : faSignInAlt}
/>
<div className="btn-tpa__font-container" aria-hidden="true">
{SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? (
<FontAwesomeIcon icon={['fab', tpaProvider.iconClass]} />)
: (
<Icon className="h-75" src={Login} />
)}
</div>
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
</>

View File

@@ -27,7 +27,7 @@ const FormGroup = (props) => {
readOnly={props.readOnly}
type={props.type}
aria-invalid={props.errorMessage !== ''}
className="form-field"
className="form-group__form-field"
autoComplete={props.autoComplete}
spellCheck={props.spellCheck}
name={props.name}

View File

@@ -42,7 +42,7 @@ const InstitutionLogistration = props => {
<>
<div className="d-flex justify-content-left mb-4 mt-2">
<div className="flex-column">
<h4 className="mb-2 font-weight-bold institute-heading">
<h4 className="mb-2 font-weight-bold institutions__heading">
{headingTitle}
</h4>
<p className="mb-2">
@@ -57,7 +57,7 @@ const InstitutionLogistration = props => {
<tr key={provider} className="pgn__data-table-row">
<td>
<Hyperlink
className="btn nav-item p-0 mb-1 secondary-provider-link"
className="btn nav-item p-0 mb-1 institutions--provider-link"
destination={lmsBaseUrl + provider.loginUrl}
>
{provider.name}

View File

@@ -9,8 +9,8 @@ import {
} from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
import messages from './messages';
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
const PasswordField = (props) => {
const { formatMessage } = useIntl();
@@ -59,7 +59,7 @@ const PasswordField = (props) => {
<OverlayTrigger key="tooltip" placement={placement} overlay={tooltip} show={showTooltip}>
<Form.Control
as="input"
className="form-field"
className="form-group__form-field"
type={isPasswordHidden ? 'password' : 'text'}
name={props.name}
value={props.value}

View File

@@ -2,12 +2,13 @@ import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { Icon } from '@edx/paragon';
import { Login } from '@edx/paragon/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
const SocialAuthProviders = (props) => {
const { formatMessage } = useIntl();
@@ -31,14 +32,16 @@ const SocialAuthProviders = (props) => {
>
{provider.iconImage ? (
<div aria-hidden="true">
<img className="icon-image" src={provider.iconImage} alt={`icon ${provider.name}`} />
<img className="btn-tpa__image-icon" src={provider.iconImage} alt={`icon ${provider.name}`} />
</div>
)
: (
<div className="font-container" aria-hidden="true">
<FontAwesomeIcon
icon={SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? ['fab', provider.iconClass] : faSignInAlt}
/>
<div className="btn-tpa__font-container" aria-hidden="true">
{SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? (
<FontAwesomeIcon icon={['fab', provider.iconClass]} />)
: (
<Icon className="h-75" src={Login} />
)}
</div>
)}
<span id="provider-name" className="notranslate mr-auto pl-2" aria-hidden="true">{provider.name}</span>

View File

@@ -5,8 +5,8 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import PropTypes from 'prop-types';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import messages from './messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
const ThirdPartyAuthAlert = (props) => {
const { formatMessage } = useIntl();

View File

@@ -23,6 +23,9 @@ const UnAuthOnlyRoute = (props) => {
if (isReady) {
if (authUser && authUser.username) {
if (true) { // check for query param
window.parent.postMessage({ authenticated: true }, 'https://discover.edx.org/social-reg');
}
global.location.href = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
return null;
}

View File

@@ -16,6 +16,9 @@ const ZendeskHelp = () => {
},
chat: {
suppress: false,
departments: {
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
},
},
contactForm: {
ticketForms: [

View File

@@ -1,5 +1,5 @@
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
export const defaultState = {
fieldDescriptions: {},

View File

@@ -1,7 +1,6 @@
import { logError } from '@edx/frontend-platform/logging';
import { call, put, takeEvery } from 'redux-saga/effects';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
import {
getThirdPartyAuthContextBegin,
getThirdPartyAuthContextFailure,
@@ -11,6 +10,7 @@ import {
import {
getThirdPartyAuthContext,
} from './service';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
export function* fetchThirdPartyAuthContext(action) {
try {

View File

@@ -18,8 +18,8 @@ export async function getThirdPartyAuthContext(urlParams) {
throw (e);
});
return {
fieldDescriptions: data.registrationFields || data.registration_fields,
optionalFields: data.optionalFields || data.optional_fields,
thirdPartyAuthContext: data.contextData || data.context_data,
fieldDescriptions: data.registrationFields || {},
optionalFields: data.optionalFields || {},
thirdPartyAuthContext: data.contextData || {},
};
}

View File

@@ -11,5 +11,4 @@ export { default as saga } from './data/sagas';
export { storeName } from './data/selectors';
export { default as FormGroup } from './FormGroup';
export { default as PasswordField } from './PasswordField';
export { default as Logistration } from './Logistration';
export { default as Zendesk } from './Zendesk';

View File

@@ -2,15 +2,19 @@
/* eslint-disable react/function-component-definition */
import React from 'react';
import * as auth from '@edx/frontend-platform/auth';
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { UnAuthOnlyRoute } from '..';
import { LOGIN_PAGE } from '../../data/constants';
import { MemoryRouter, BrowserRouter as Router, Switch } from 'react-router-dom';
jest.mock('@edx/frontend-platform/auth');
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(),
fetchAuthenticatedUser: jest.fn(),
}));
const RRD = require('react-router-dom');
// Just render plain div with its children
@@ -39,25 +43,30 @@ describe('UnAuthOnlyRoute', () => {
jest.clearAllMocks();
});
it('should have called with forceRefresh true', () => {
it('should have called with forceRefresh true', async () => {
const user = {
username: 'gonzo',
other: 'data',
};
auth.getAuthenticatedUser = jest.fn(() => user);
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
mount(routerWrapper());
getAuthenticatedUser.mockReturnValue(user);
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(user));
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
await act(async () => {
await mount(routerWrapper());
});
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
});
it('should have called with forceRefresh false', () => {
auth.getAuthenticatedUser = jest.fn(() => null);
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
it('should have called with forceRefresh false', async () => {
getAuthenticatedUser.mockReturnValue(null);
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(null));
mount(routerWrapper());
await act(async () => {
await mount(routerWrapper());
});
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
});
});

View File

@@ -10,25 +10,27 @@ exports[`SocialAuthProviders should match social auth provider with default icon
>
<div
aria-hidden="true"
className="font-container"
className="btn-tpa__font-container"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-right-to-bracket "
data-icon="right-to-bracket"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
<span
className="pgn__icon h-75"
>
<path
d="M352 96h64c17.7 0 32 14.3 32 32V384c0 17.7-14.3 32-32 32H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h64c53 0 96-43 96-96V128c0-53-43-96-96-96H352c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-7.5 177.4c4.8-4.5 7.5-10.8 7.5-17.4s-2.7-12.9-7.5-17.4l-144-136c-7-6.6-17.2-8.4-26-4.6s-14.5 12.5-14.5 22v72H32c-17.7 0-32 14.3-32 32v64c0 17.7 14.3 32 32 32H160v72c0 9.6 5.7 18.2 14.5 22s19 2 26-4.6l144-136z"
fill="currentColor"
style={Object {}}
/>
</svg>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 7L9.6 8.4l2.6 2.6H2v2h10.2l-2.6 2.6L11 17l5-5-5-5zm9 12h-8v2h10V3H12v2h8v14z"
fill="currentColor"
/>
</svg>
</span>
</div>
<span
aria-hidden="true"
@@ -55,7 +57,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
>
<div
aria-hidden="true"
className="font-container"
className="btn-tpa__font-container"
>
<svg
aria-hidden="true"
@@ -104,7 +106,7 @@ Array [
>
<img
alt="icon Apple"
className="icon-image"
className="btn-tpa__image-icon"
src="https://edx.devstack.lms/logo.png"
/>
</div>
@@ -133,7 +135,7 @@ Array [
>
<img
alt="icon Facebook"
className="icon-image"
className="btn-tpa__image-icon"
src="https://edx.devstack.lms/facebook-logo.png"
/>
</div>

View File

@@ -20,6 +20,17 @@ exports[`Zendesk Help should match login page third party auth alert message sna
},
},
"chat": Object {
"departments": Object {
"enabled": Array [
"account settings",
"billing and payments",
"certificates",
"deadlines",
"errors and technical issues",
"other",
"proctoring",
],
},
"suppress": false,
},
"contactForm": Object {

View File

@@ -5,7 +5,6 @@ const configuration = {
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
// Features
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
ENABLE_COOKIE_POLICY_BANNER: process.env.ENABLE_COOKIE_POLICY_BANNER || false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
ENABLE_PERSONALIZED_RECOMMENDATIONS: process.env.ENABLE_PERSONALIZED_RECOMMENDATIONS || false,

View File

@@ -4,8 +4,14 @@ import {
const OPTIMIZELY_SDK_KEY = process.env.OPTIMIZELY_FULL_STACK_SDK_KEY;
const optimizely = createInstance({
sdkKey: OPTIMIZELY_SDK_KEY,
});
const getOptimizelyInstance = () => {
if (OPTIMIZELY_SDK_KEY) {
return createInstance({
sdkKey: OPTIMIZELY_SDK_KEY,
});
}
export default optimizely;
return null;
};
export default getOptimizelyInstance();

View File

@@ -1,5 +1,5 @@
import { LOGIN_PAGE } from '../constants';
import { updatePathWithQueryParams } from './dataUtils';
import { LOGIN_PAGE } from '../constants';
describe('updatePathWithQueryParams', () => {
it('should append query params into the path', () => {

View File

@@ -25,7 +25,7 @@ describe('FieldRendererTests', () => {
type: 'select',
label: 'Year of Birth',
name: 'yob-field',
options: [['1997', 1997], ['1998', 1998]],
options: [['1997', '1997'], ['1998', '1998']],
};
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);

View File

@@ -6,11 +6,11 @@ import { Alert } from '@edx/paragon';
import { CheckCircle, Error } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import messages from './messages';
import {
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
} from '../data/constants';
import { PASSWORD_RESET } from '../reset-password/data/constants';
import messages from './messages';
const ForgotPasswordAlert = (props) => {
const { formatMessage } = useIntl();

View File

@@ -17,14 +17,14 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { Redirect } from 'react-router-dom';
import { BaseComponent } from '../base-component';
import { FormGroup } from '../common-components';
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import ForgotPasswordAlert from './ForgotPasswordAlert';
import messages from './messages';
import { BaseComponent } from '../base-component';
import { FormGroup } from '../common-components';
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
const ForgotPasswordPage = (props) => {
const platformName = getConfig().SITE_NAME;
@@ -133,7 +133,7 @@ const ForgotPasswordPage = (props) => {
name="submit-forget-password"
type="submit"
variant="brand"
className="forgot-password-button-width"
className="forgot-password--button"
state={submitState}
labels={{
default: formatMessage(messages['forgot.password.page.submit.button']),

View File

@@ -1,6 +1,6 @@
import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
import { INTERNAL_SERVER_ERROR, PENDING_STATE } from '../../data/constants';
import { PASSWORD_RESET_FAILURE } from '../../reset-password/data/actions';
import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
export const defaultState = {
status: '',

View File

@@ -1,10 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import * as auth from '@edx/frontend-platform/auth';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { createMemoryHistory } from 'history';
@@ -17,11 +14,12 @@ import { PASSWORD_RESET } from '../../reset-password/data/constants';
import { setForgotPasswordFormData } from '../data/actions';
import ForgotPasswordPage from '../ForgotPasswordPage';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(),
}));
jest.mock('@edx/frontend-platform/auth');
analytics.sendPageEvent = jest.fn();
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
const mockStore = configureStore();
const history = createMemoryHistory();
@@ -51,7 +49,12 @@ describe('ForgotPasswordPage', () => {
beforeEach(() => {
store = mockStore(initialState);
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(() => ({
userId: 3,
username: 'test-user',
})),
}));
configure({
loggingService: { logError: jest.fn() },
config: {
@@ -191,11 +194,6 @@ describe('ForgotPasswordPage', () => {
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
});
it('check cookie rendered', () => {
const forgotPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
expect(forgotPage.find(<CookiePolicyBanner />)).toBeTruthy();
});
it('should display success message after email is sent', () => {
store = mockStore({
...initialState,

View File

@@ -1,41 +1,39 @@
import { messages as paragonMessages } from '@edx/paragon';
import arMessages from './messages/ar.json';
import caMessages from './messages/ca.json';
// no need to import en messages-- they are in the defaultMessage field
import dedeMessages from './messages/de_DE.json';
import deMessages from './messages/de.json';
import dedeCAMessages from './messages/de_DE.json';
import es419Messages from './messages/es_419.json';
import frMessages from './messages/fr.json';
import heMessages from './messages/he.json';
import frCAMessages from './messages/fr_CA.json';
import hiMessages from './messages/hi.json';
import idMessages from './messages/id.json';
import ititMessages from './messages/it_IT.json';
import kokrMessages from './messages/ko_kr.json';
import plMessages from './messages/pl.json';
import ptbrMessages from './messages/pt_br.json';
import ptptMessages from './messages/pt_PT.json';
import itMessages from './messages/it.json';
import ititCAMessages from './messages/it_IT.json';
import ptMessages from './messages/pt.json';
import ptptCAMessages from './messages/pt_PT.json';
import ruMessages from './messages/ru.json';
import thMessages from './messages/th.json';
import ukMessages from './messages/uk.json';
import zhcnMessages from './messages/zh_CN.json';
// no need to import en messages-- they are in the defaultMessage field
const messages = {
const appMessages = {
ar: arMessages,
es: es419Messages, // Prospectus uses es language code for spanish, added `es` option and pointed to es-419 strings.
'es-419': es419Messages,
fr: frMessages,
'zh-cn': zhcnMessages,
'it-it': ititMessages,
'pt-pt': ptptMessages,
'de-de': dedeMessages,
ca: caMessages,
he: heMessages,
id: idMessages,
'ko-kr': kokrMessages,
pl: plMessages,
'pt-br': ptbrMessages,
ru: ruMessages,
th: thMessages,
uk: ukMessages,
pt: ptMessages,
it: itMessages,
de: deMessages,
hi: hiMessages,
'fr-ca': frCAMessages,
ru: ruMessages,
uk: ukMessages,
'de-de': dedeCAMessages,
'it-it': ititCAMessages,
'pt-pt': ptptCAMessages,
};
export default messages;
export default [
paragonMessages,
appMessages,
];

View File

@@ -9,7 +9,7 @@
"logistration.register": "التسجيل",
"enterprisetpa.title.heading": "هل ترغب في تسجيل الدخول باستخدام بيانات {providerName} الخاصة بك؟",
"enterprisetpa.login.button.text": "أرِني وسائل أخرى لتسجيل الدخول أو للتسجيل",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"enterprisetpa.login.button.text.public.account.creation.disabled": "أرني طرقًا أخرى لتسجيل الدخول",
"sso.sign.in.with": "تسجيل الدخول باستخدام {providerName}",
"sso.create.account.using": "إنشاء حساب باستخدام {providerName}",
"show.password": "إظهار كلمة المرور",
@@ -23,7 +23,7 @@
"register.third.party.auth.account.not.linked": "لقد سجلت دخولك بنجاح إلى {currentProvider}! نحتاج فقط قليلاً بعدُ من المعلومات قبل أن تبدأ التعلم مع {platformName}.",
"registration.using.tpa.form.heading": "إتمام إنشاء حسابك",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
"forgot.password.confirmation.message": "لقد أرسلنا بريدًا إلكترونيًا إلى {email} به إرشادات لإعادة ضبط كلمة المرور الخاصة بك. إن لم تستلم رسالة إعادة ضبط كلمة المرور بعد دقيقة واحدة، فتحقق من إدخال عنوان البريد الإلكتروني الصحيح، أو تفقد مجلد الرسائل غير المرغوب فيها. إن احتجت مزيدًا من المساعدة، {supportLink}.",
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
@@ -96,20 +96,21 @@
"password.security.block.body": "اكتشف نظامنا أن كلمة مرورك صعيفة. غيّر كلمة مرورك حتى يظل حسابك آمنًا.",
"password.security.close.button": "إغلاق",
"password.security.redirect.to.reset.password.button": "إعادة ضبط كلمة المرور",
"progressive.profiling.page.title": "Welcome | {siteName}",
"login.tpa.authentication.failure": "عذرًا ، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
"progressive.profiling.page.title": "مرحبا بكم | {siteName}",
"progressive.profiling.page.heading": "بعض الأسئلة الموجهة لك ستساعدنا كي نزداد ذكاءً.",
"optional.fields.information.link": "معرفة المزيد عن كيفية استخدامنا لهذه المعلومات.",
"optional.fields.submit.button": "إرسال",
"optional.fields.skip.button": "التخطي مؤقتا",
"optional.fields.next.button": "Next",
"optional.fields.next.button": "التالي",
"continue.to.platform": "المواصلة إلى {platformName}",
"modal.title": "شكرا لإعلامنا.",
"modal.description": "إن غيرت رأيك، قيمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
"welcome.page.error.heading": "لم نتمكن من تحديث ملفك الشخصي",
"welcome.page.error.message": "حدث خطأ ما. يمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"recommendation.page.title": "التوصيات | {siteName}",
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
"recommendation.skip.button": "التخطي مؤقتا",
"register.page.title": "التسجيل | {siteName}",
"registration.fullname.label": "الاسم الكامل",
"registration.email.label": "البريد الإلكتروني",
@@ -140,6 +141,7 @@
"registration.request.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
"registration.rate.limit.error": "كثرت محاولات التسجيل الفاشلة. أعد المحاولة لاحقًا.",
"registration.tpa.session.expired": "نفد وقت التسجيل باستخدام {provider}.",
"registration.tpa.authentication.failure": "عذرًا، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
"terms.of.service.and.honor.code": "شروط الخدمة وميثاق الشرف الأكاديمي",
"privacy.policy": "سياسة الخصوصية",
"honor.code": "ميثاق الشرف الأكاديمي",
@@ -147,8 +149,8 @@
"registration.username.suggestion.label": "مقترح:",
"did.you.mean.alert.text": "هل تقصد",
"register.page.terms.of.service.and.honor.code": "بإنشاءك حسابًا، فإنك توافق على {tosAndHonorCode} و تقر بأن {platformName} و كل عضو يعالج بياناتك الشخصية وفقًا لـ{privacyPolicy}.",
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
"register.page.honor.code": "اوافق على شروط {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "اوافق على {platformName} {termsOfService}",
"sign.in": "تسجيل الدخول",
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
"reset.password": "إعادة ضبط كلمة المرور",

168
src/i18n/messages/de.json Normal file
View File

@@ -0,0 +1,168 @@
{
"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",
"logistration.sign.in": "Sign in",
"logistration.register": "Register",
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"sso.sign.in.with": "Sign in with {providerName}",
"sso.create.account.using": "Create account using {providerName}",
"show.password": "Show password",
"hide.password": "Hide password",
"one.letter": "1 letter",
"one.number": "1 number",
"eight.characters": "8 characters",
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
"tpa.alert.heading": "Almost done!",
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
"registration.using.tpa.form.heading": "Finish creating your account",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
"forgot.password.page.title": "Forgot Password | {siteName}",
"forgot.password.page.heading": "Reset password",
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
"forgot.password.page.invalid.email.message": "Enter a valid email address",
"forgot.password.page.email.field.label": "Email",
"forgot.password.page.submit.button": "Submit",
"forgot.password.error.alert.title.": "We were unable to contact you.",
"forgot.password.error.message.title": "An error occurred.",
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
"forgot.password.empty.email.field.error": "Enter your email",
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
"confirmation.message.title": "Check your email",
"confirmation.support.link": "contact technical support",
"need.help.sign.in.text": "Need help signing in?",
"additional.help.text": "For additional help, contact {platformName} support at ",
"sign.in.text": "Sign in",
"extend.field.errors": "{emailError} below.",
"invalid.token.heading": "Invalid password reset link",
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
"token.validation.rate.limit.error.heading": "Too many requests",
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
"token.validation.internal.sever.error.heading": "Token validation failure",
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
"login.page.title": "Login | {siteName}",
"login.user.identity.label": "Username or email",
"login.password.label": "Password",
"sign.in.button": "Sign in",
"forgot.password": "Forgot password",
"institution.login.button": "Institution/campus credentials",
"institution.login.page.title": "Sign in with institution/campus credentials",
"login.other.options.heading": "Or sign in with:",
"non.compliant.password.title": "We recently changed our password requirements",
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
"enterprise.login.btn.text": "Company or school credentials",
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
"email.validation.message": "Enter your username or email",
"password.validation.message": "Password criteria has not been met",
"account.activation.success.message.title": "Success! You have activated your account.",
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
"account.activation.info.message": "This account has already been activated.",
"account.activation.error.message.title": "Your account could not be activated",
"account.activation.support.link": "contact support",
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
"account.confirmation.success.message": "Sign in to continue.",
"account.confirmation.info.message": "This email has already been confirmed.",
"account.confirmation.error.message.title": "Your email could not be confirmed",
"tpa.account.link": "{provider} account",
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
"login.failure.header.title": "We couldn't sign you in.",
"contact.support.link": "contact {platformName} support",
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
"login.form.invalid.error.message": "Please fill in the fields below.",
"login.incorrect.credentials.error.reset.link.text": "reset your password",
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
"password.security.nudge.title": "Password security",
"password.security.block.title": "Password change required",
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
"optional.fields.submit.button": "Submit",
"optional.fields.skip.button": "Skip for now",
"optional.fields.next.button": "Next",
"continue.to.platform": "Continue to {platformName}",
"modal.title": "Thanks for letting us know.",
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
"welcome.page.error.heading": "We couldn't update your profile",
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"register.page.title": "Register | {siteName}",
"registration.fullname.label": "Full name",
"registration.email.label": "Email",
"registration.username.label": "Public username",
"registration.password.label": "Password",
"registration.country.label": "Country/Region",
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
"help.text.name": "This name will be used by any certificates that you earn.",
"help.text.username.1": "The name that will identify you in your courses.",
"help.text.username.2": "This can not be changed later.",
"help.text.email": "For account activation and important updates",
"create.account.for.free.button": "Create an account for free",
"registration.other.options.heading": "Or register with:",
"register.institution.login.button": "Institution/campus credentials",
"register.institution.login.page.title": "Register with institution/campus credentials",
"empty.name.field.error": "Enter your full name",
"empty.email.field.error": "Enter your email",
"empty.username.field.error": "Username must be between 2 and 30 characters",
"empty.password.field.error": "Password criteria has not been met",
"empty.country.field.error": "Select your country or region of residence",
"email.do.not.match": "The email addresses do not match.",
"email.invalid.format.error": "Enter a valid email address",
"username.validation.message": "Username must be between 2 and 30 characters",
"name.validation.message": "Enter a valid name",
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
"registration.request.failure.header": "We couldn't create your account.",
"registration.empty.form.submission.error": "Please check your responses and try again.",
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",
"terms.of.service": "Terms of Service",
"registration.username.suggestion.label": "Suggested:",
"did.you.mean.alert.text": "Did you mean",
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
"sign.in": "Sign in",
"reset.password.page.title": "Reset Password | {siteName}",
"reset.password": "Reset password",
"reset.password.page.instructions": "Enter and confirm your new password.",
"new.password.label": "New password",
"confirm.password.label": "Confirm password",
"passwords.do.not.match": "Passwords do not match",
"confirm.your.password": "Confirm your password",
"reset.password.failure.heading": "We couldn't reset your password.",
"reset.password.form.submission.error": "Please check your responses and try again.",
"reset.server.rate.limit.error": "Too many requests.",
"reset.password.success.heading": "Password reset complete.",
"reset.password.success": "Your password has been reset. Sign in to your account.",
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
}

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Unser System hat festgestellt, dass Ihr Passwort angreifbar ist. Ändern Sie Ihr Passwort, damit Ihr Konto sicher bleibt.",
"password.security.close.button": "Schließen",
"password.security.redirect.to.reset.password.button": "Setzen Sie Ihr Passwort zurück",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "Ein paar Fragen an Sie helfen uns, schlauer zu werden.",
"optional.fields.information.link": "Erfahren Sie mehr darüber, wie wir diese Informationen verwenden.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
"registration.rate.limit.error": "Zu viele fehlgeschlagene Registrierungsversuche. Versuchen Sie es später noch einmal.",
"registration.tpa.session.expired": "Die Registrierung mit {provider} ist abgelaufen.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Nutzungsbedingungen und Verhaltenskodex",
"privacy.policy": "Datenschutzbestimmungen",
"honor.code": "Verhaltenskodex",

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Nuestro sistema detectó que su contraseña es vulnerable. Cambie su contraseña para que su cuenta permanezca segura.",
"password.security.close.button": "Cerrar",
"password.security.redirect.to.reset.password.button": "Restablece tu contraseña",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "Unas cuantas preguntas para ti nos ayudarán a mejorar.",
"optional.fields.information.link": "Aprende más sobre cómo usamos esta información.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
"registration.rate.limit.error": "Demasiados intentos de registro fallidos. Vuelve a intentarlo más tarde.",
"registration.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Condiciones de servicio y código de honor",
"privacy.policy": "Política de privacidad ",
"honor.code": "Código de Honor",

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
"password.security.close.button": "Fermer",
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
"registration.tpa.session.expired": "L'inscription avec {provider} a échouée.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Conditions d'utilisation et Code d'honneur",
"privacy.policy": "Politique de confidentialité",
"honor.code": "Code d'honneur",

View File

@@ -0,0 +1,168 @@
{
"start.learning": "Démarrer l'apprentissage",
"with.site.name": "avec {siteName}",
"complete.your.profile.1": "Complet",
"complete.your.profile.2": "votre profil",
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
"logistration.sign.in": "Connexion",
"logistration.register": "Inscription",
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName}?",
"enterprisetpa.login.button.text": "Affichez moi d'autres façons de se connecter ou de s'inscrire",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Montrez-moi d'autres façons de me connecter",
"sso.sign.in.with": "Connectez-vous avec {providerName}",
"sso.create.account.using": "Créer un compte avec {providerName}",
"show.password": "Afficher le mot de passe",
"hide.password": "Cacher le mot de passe",
"one.letter": "1 lettre",
"one.number": "1 numéro",
"eight.characters": "8 caractères",
"password.sr.only.helping.text": "Le mot de passe doit contenir au moins 8 caractères, au moins une lettre et au moins un chiffre",
"tpa.alert.heading": "Presque terminé!",
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
"zendesk.supportTitle": "Prise en charge d&#39;edX",
"zendesk.selectTicketForm": "Veuillez choisir votre type de demande :",
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe.\n Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi\nl'adresse courriel correctement, ou vérifiez votre dossier de pourriels. Si vous avez besoin d'aide supplémentaire, {supportLink}.",
"forgot.password.page.title": "Mot de passe oublié | {siteName}",
"forgot.password.page.heading": "Réinitialiser le mot de passe",
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
"forgot.password.page.invalid.email.message": "Entrez une adresse de courriel valide",
"forgot.password.page.email.field.label": "Courriel",
"forgot.password.page.submit.button": "Soumettre",
"forgot.password.error.alert.title.": "Nous n'avons pas pu vous contacter.",
"forgot.password.error.message.title": "Une erreur est survenue.",
"forgot.password.request.in.progress.message": "Votre demande précédente est en cours, veuillez réessayer dans quelques instants.",
"forgot.password.empty.email.field.error": "Saisissez votre courriel",
"forgot.password.email.help.text": "L'adresse courriel que vous avez utilisée pour vous inscrire sur {platformName}",
"confirmation.message.title": "Vérifiez votre courriel",
"confirmation.support.link": "contacter le support technique",
"need.help.sign.in.text": "Besoin d'aide pour vous connecter?",
"additional.help.text": "Pour obtenir une aide supplémentaire, contactez l'assistance {platformName} à l'adresse",
"sign.in.text": "Connexion",
"extend.field.errors": "{emailError} ci-dessous.",
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
"invalid.token.error.message": "Ce lien de réinitialisation de mot de passe n'est pas valide. Il a peut-être déjà été utilisé. Entrez votre courriel ci-dessous pour recevoir un nouveau lien.",
"token.validation.rate.limit.error.heading": "Trop de demandes",
"token.validation.rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps.",
"token.validation.internal.sever.error.heading": "Échec de la validation du jeton",
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
"login.inactive.user.error": "Pour vous connecter, vous devez activer votre compte.{lineBreak}\n {lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel,\n vérifiez vos dossiers de pourriels ou {supportLink}.",
"allowed.domain.login.error": "En tant qu'utilisateur {allowedDomain}, vous devez vous connecter avec votre {allowedDomain} {tpaLink}.",
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré sont incorrects. Vous avez {remainingAttempts} tentatives\n de connexion avant que votre compte soit temporairement verrouillé.",
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
"login.page.title": "Connexion | {siteName}",
"login.user.identity.label": "Nom d'utilisateur ou courriel",
"login.password.label": "Mot de passe",
"sign.in.button": "Connexion",
"forgot.password": "Mot de passe oublié",
"institution.login.button": "Identifiants de l'établissement/du campus",
"institution.login.page.title": "Connectez vous avec les crédentiels d'institution ou de campus",
"login.other.options.heading": "Ou se connecter avec :",
"non.compliant.password.title": "Nous avons récemment modifié nos exigences en matière de mot de passe",
"non.compliant.password.message": "Votre mot de passe actuel ne répond pas aux nouvelles exigences de sécurité. Nous venons d'envoyer un message de réinitialisation de mot de passe à l'adresse courriel associée à ce compte. Merci de nous aider à protéger vos données.",
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
"enterprise.login.btn.text": "Identifiants de la compagnie ou de l'école",
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 3 caractères.",
"email.validation.message": "Entrez votre nom d'utilisateur ou votre adresse courriel",
"password.validation.message": "Les critères de mot de passe n'ont pas été remplis",
"account.activation.success.message.title": "Succès! Vous avez activé votre compte.",
"account.activation.success.message": "Vous recevrez maintenant des mises à jour par courriel et des alertes de notre part concernant les cours auxquels vous êtes inscrit. Connectez-vous pour continuer.",
"account.activation.info.message": "Ce compte a déjà été activé.",
"account.activation.error.message.title": "Votre compte n'a pas pu être activé",
"account.activation.support.link": "contacter le support",
"account.confirmation.success.message.title": "Bravo! Vous avez confirmé votre courriel.",
"account.confirmation.success.message": "Se connecter pour continuer.",
"account.confirmation.info.message": "Ce courriel a déjà été confirmé.",
"account.confirmation.error.message.title": "Votre courriel ne peut pas être confirmé",
"tpa.account.link": "compte {provider}",
"internal.server.error.message": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
"login.rate.limit.reached.message": "Trop de tentatives d'accès refusées. Essayer plus tard.",
"login.failure.header.title": "Nous n'avons pas pu vous connecter.",
"contact.support.link": "veuillez contacter le support {platformName}",
"login.incorrect.credentials.error": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer.",
"login.form.invalid.error.message": "Veuillez remplir les champs ci-dessous.",
"login.incorrect.credentials.error.reset.link.text": "réinitialiser votre mot de passe",
"login.incorrect.credentials.error.before.account.blocked.text": "cliquez ici pour le réinitialiser.",
"password.security.nudge.title": "Sécurité du mot de passe",
"password.security.block.title": "Changement de mot de passe requis",
"password.security.nudge.body": "Notre système a détecté que votre mot de passe est vulnérable. Nous vous recommandons de le modifier afin que votre compte reste sécurisé.",
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
"password.security.close.button": "Fermer",
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
"login.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Bienvenue | {siteName}",
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
"optional.fields.submit.button": "Soumettre",
"optional.fields.skip.button": "Ignorer pour l'instant",
"optional.fields.next.button": "Suivant",
"continue.to.platform": "Continuer vers {platformName}",
"modal.title": "Merci de nous en informer.",
"modal.description": "Vous pouvez compléter votre profil dans les paramètres à tout moment si vous changez d'avis.",
"welcome.page.error.heading": "Nous n'avons pas pu mettre à jour votre profil",
"welcome.page.error.message": "Une erreur s'est produite. Vous pouvez compléter votre profil dans les paramètres à tout moment.",
"recommendation.page.title": "Recommandations | {siteName}",
"recommendation.page.heading": "Nous avons quelques recommandations pour vous aider à démarrer.",
"recommendation.skip.button": "Ignorer pour l'instant",
"register.page.title": "S'inscrire | {siteName}",
"registration.fullname.label": "Nom complet",
"registration.email.label": "Courriel",
"registration.username.label": "Nom d'utilisateur",
"registration.password.label": "Mot de passe",
"registration.country.label": "Pays/Région",
"registration.opt.in.label": "{siteName} peux m'envoyer des messages de marketing.",
"help.text.name": "Ce nom sera utilisé pour toutes les attestations que vous obtiendrez.",
"help.text.username.1": "Le nom qui vous identifiera dans vos cours.",
"help.text.username.2": "Cela ne peut pas être modifié ultérieurement.",
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
"create.account.for.free.button": "Créer un compte gratuitement",
"registration.other.options.heading": "Ou inscrivez-vous avec :",
"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.do.not.match": "Les adresses courriel ne correspondent pas.",
"email.invalid.format.error": "Entrez une adresse de courriel valide",
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
"name.validation.message": "Entrez un nom valide",
"username.format.validation.message": "Les noms d'utilisateur ne peuvent contenir que des lettres (AZ, az), des chiffres (0-9), des traits de soulignement (_) et des traits d'union (-). Les noms d'utilisateur ne peuvent pas contenir d'espaces",
"registration.request.failure.header": "Nous n'avons pas pu créer votre compte.",
"registration.empty.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
"registration.tpa.session.expired": "L'inscription à l'aide de {provider} a expiré.",
"registration.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Conditions générales du service et code d'honneur",
"privacy.policy": "Politique de confidentialité",
"honor.code": "Code d'honneur",
"terms.of.service": "Conditions générales du service",
"registration.username.suggestion.label": "Suggéré :",
"did.you.mean.alert.text": "Vouliez-vous dire",
"register.page.terms.of.service.and.honor.code": "En créant un compte, vous acceptez le {tosAndHonorCode} et vous reconnaissez que {platformName} et chaque\n membre peut traiter vos données personnelles conformément à la {privacyPolicy}.",
"register.page.honor.code": "J'accepte le {tosAndHonorCode} {platformName}",
"register.page.terms.of.service": "J'accepte les {termsOfService} {platformName}",
"sign.in": "Connexion",
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
"reset.password": "Réinitialiser le mot de passe",
"reset.password.page.instructions": "Saisir et confirmer votre nouveau mot de passe.",
"new.password.label": "Nouveau mot de passe",
"confirm.password.label": "Confirmer le mot de passe",
"passwords.do.not.match": "Les mots de passe ne correspondent pas",
"confirm.your.password": "Confirmer votre mot de passe",
"reset.password.failure.heading": "Nous n'avons pas pu réinitialiser votre mot de passe.",
"reset.password.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
"reset.server.rate.limit.error": "Trop de demandes.",
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps."
}

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",

168
src/i18n/messages/it.json Normal file
View File

@@ -0,0 +1,168 @@
{
"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",
"logistration.sign.in": "Sign in",
"logistration.register": "Register",
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"sso.sign.in.with": "Sign in with {providerName}",
"sso.create.account.using": "Create account using {providerName}",
"show.password": "Show password",
"hide.password": "Hide password",
"one.letter": "1 letter",
"one.number": "1 number",
"eight.characters": "8 characters",
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
"tpa.alert.heading": "Almost done!",
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
"registration.using.tpa.form.heading": "Finish creating your account",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
"forgot.password.page.title": "Forgot Password | {siteName}",
"forgot.password.page.heading": "Reset password",
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
"forgot.password.page.invalid.email.message": "Enter a valid email address",
"forgot.password.page.email.field.label": "Email",
"forgot.password.page.submit.button": "Submit",
"forgot.password.error.alert.title.": "We were unable to contact you.",
"forgot.password.error.message.title": "An error occurred.",
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
"forgot.password.empty.email.field.error": "Enter your email",
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
"confirmation.message.title": "Check your email",
"confirmation.support.link": "contact technical support",
"need.help.sign.in.text": "Need help signing in?",
"additional.help.text": "For additional help, contact {platformName} support at ",
"sign.in.text": "Sign in",
"extend.field.errors": "{emailError} below.",
"invalid.token.heading": "Invalid password reset link",
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
"token.validation.rate.limit.error.heading": "Too many requests",
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
"token.validation.internal.sever.error.heading": "Token validation failure",
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
"login.page.title": "Login | {siteName}",
"login.user.identity.label": "Username or email",
"login.password.label": "Password",
"sign.in.button": "Sign in",
"forgot.password": "Forgot password",
"institution.login.button": "Institution/campus credentials",
"institution.login.page.title": "Sign in with institution/campus credentials",
"login.other.options.heading": "Or sign in with:",
"non.compliant.password.title": "We recently changed our password requirements",
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
"enterprise.login.btn.text": "Company or school credentials",
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
"email.validation.message": "Enter your username or email",
"password.validation.message": "Password criteria has not been met",
"account.activation.success.message.title": "Success! You have activated your account.",
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
"account.activation.info.message": "This account has already been activated.",
"account.activation.error.message.title": "Your account could not be activated",
"account.activation.support.link": "contact support",
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
"account.confirmation.success.message": "Sign in to continue.",
"account.confirmation.info.message": "This email has already been confirmed.",
"account.confirmation.error.message.title": "Your email could not be confirmed",
"tpa.account.link": "{provider} account",
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
"login.failure.header.title": "We couldn't sign you in.",
"contact.support.link": "contact {platformName} support",
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
"login.form.invalid.error.message": "Please fill in the fields below.",
"login.incorrect.credentials.error.reset.link.text": "reset your password",
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
"password.security.nudge.title": "Password security",
"password.security.block.title": "Password change required",
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
"optional.fields.submit.button": "Submit",
"optional.fields.skip.button": "Skip for now",
"optional.fields.next.button": "Next",
"continue.to.platform": "Continue to {platformName}",
"modal.title": "Thanks for letting us know.",
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
"welcome.page.error.heading": "We couldn't update your profile",
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"register.page.title": "Register | {siteName}",
"registration.fullname.label": "Full name",
"registration.email.label": "Email",
"registration.username.label": "Public username",
"registration.password.label": "Password",
"registration.country.label": "Country/Region",
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
"help.text.name": "This name will be used by any certificates that you earn.",
"help.text.username.1": "The name that will identify you in your courses.",
"help.text.username.2": "This can not be changed later.",
"help.text.email": "For account activation and important updates",
"create.account.for.free.button": "Create an account for free",
"registration.other.options.heading": "Or register with:",
"register.institution.login.button": "Institution/campus credentials",
"register.institution.login.page.title": "Register with institution/campus credentials",
"empty.name.field.error": "Enter your full name",
"empty.email.field.error": "Enter your email",
"empty.username.field.error": "Username must be between 2 and 30 characters",
"empty.password.field.error": "Password criteria has not been met",
"empty.country.field.error": "Select your country or region of residence",
"email.do.not.match": "The email addresses do not match.",
"email.invalid.format.error": "Enter a valid email address",
"username.validation.message": "Username must be between 2 and 30 characters",
"name.validation.message": "Enter a valid name",
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
"registration.request.failure.header": "We couldn't create your account.",
"registration.empty.form.submission.error": "Please check your responses and try again.",
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",
"terms.of.service": "Terms of Service",
"registration.username.suggestion.label": "Suggested:",
"did.you.mean.alert.text": "Did you mean",
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
"sign.in": "Sign in",
"reset.password.page.title": "Reset Password | {siteName}",
"reset.password": "Reset password",
"reset.password.page.instructions": "Enter and confirm your new password.",
"new.password.label": "New password",
"confirm.password.label": "Confirm password",
"passwords.do.not.match": "Passwords do not match",
"confirm.your.password": "Confirm your password",
"reset.password.failure.heading": "We couldn't reset your password.",
"reset.password.form.submission.error": "Please check your responses and try again.",
"reset.server.rate.limit.error": "Too many requests.",
"reset.password.success.heading": "Password reset complete.",
"reset.password.success": "Your password has been reset. Sign in to your account.",
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
}

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Il nostro sistema ha rilevato che la tua password è vulnerabile. Cambia la tua password in modo che il tuo account rimanga sicuro.",
"password.security.close.button": "Chiudi",
"password.security.redirect.to.reset.password.button": "Ripristina la tua password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "Alcune domande per te ci aiuteranno a diventare più intelligenti.",
"optional.fields.information.link": "Ulteriori informazioni su come utilizziamo queste informazioni.",
@@ -140,6 +141,7 @@
"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.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Termini e Condizioni del Servizio",
"privacy.policy": "Informativa sulla privacy",
"honor.code": "Codice d'Onore",

168
src/i18n/messages/pt.json Normal file
View File

@@ -0,0 +1,168 @@
{
"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",
"logistration.sign.in": "Sign in",
"logistration.register": "Register",
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"sso.sign.in.with": "Sign in with {providerName}",
"sso.create.account.using": "Create account using {providerName}",
"show.password": "Show password",
"hide.password": "Hide password",
"one.letter": "1 letter",
"one.number": "1 number",
"eight.characters": "8 characters",
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
"tpa.alert.heading": "Almost done!",
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
"registration.using.tpa.form.heading": "Finish creating your account",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
"forgot.password.page.title": "Forgot Password | {siteName}",
"forgot.password.page.heading": "Reset password",
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
"forgot.password.page.invalid.email.message": "Enter a valid email address",
"forgot.password.page.email.field.label": "Email",
"forgot.password.page.submit.button": "Submit",
"forgot.password.error.alert.title.": "We were unable to contact you.",
"forgot.password.error.message.title": "An error occurred.",
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
"forgot.password.empty.email.field.error": "Enter your email",
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
"confirmation.message.title": "Check your email",
"confirmation.support.link": "contact technical support",
"need.help.sign.in.text": "Need help signing in?",
"additional.help.text": "For additional help, contact {platformName} support at ",
"sign.in.text": "Sign in",
"extend.field.errors": "{emailError} below.",
"invalid.token.heading": "Invalid password reset link",
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
"token.validation.rate.limit.error.heading": "Too many requests",
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
"token.validation.internal.sever.error.heading": "Token validation failure",
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
"login.page.title": "Login | {siteName}",
"login.user.identity.label": "Username or email",
"login.password.label": "Password",
"sign.in.button": "Sign in",
"forgot.password": "Forgot password",
"institution.login.button": "Institution/campus credentials",
"institution.login.page.title": "Sign in with institution/campus credentials",
"login.other.options.heading": "Or sign in with:",
"non.compliant.password.title": "We recently changed our password requirements",
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
"enterprise.login.btn.text": "Company or school credentials",
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
"email.validation.message": "Enter your username or email",
"password.validation.message": "Password criteria has not been met",
"account.activation.success.message.title": "Success! You have activated your account.",
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
"account.activation.info.message": "This account has already been activated.",
"account.activation.error.message.title": "Your account could not be activated",
"account.activation.support.link": "contact support",
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
"account.confirmation.success.message": "Sign in to continue.",
"account.confirmation.info.message": "This email has already been confirmed.",
"account.confirmation.error.message.title": "Your email could not be confirmed",
"tpa.account.link": "{provider} account",
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
"login.failure.header.title": "We couldn't sign you in.",
"contact.support.link": "contact {platformName} support",
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
"login.form.invalid.error.message": "Please fill in the fields below.",
"login.incorrect.credentials.error.reset.link.text": "reset your password",
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
"password.security.nudge.title": "Password security",
"password.security.block.title": "Password change required",
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
"optional.fields.submit.button": "Submit",
"optional.fields.skip.button": "Skip for now",
"optional.fields.next.button": "Next",
"continue.to.platform": "Continue to {platformName}",
"modal.title": "Thanks for letting us know.",
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
"welcome.page.error.heading": "We couldn't update your profile",
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"register.page.title": "Register | {siteName}",
"registration.fullname.label": "Full name",
"registration.email.label": "Email",
"registration.username.label": "Public username",
"registration.password.label": "Password",
"registration.country.label": "Country/Region",
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
"help.text.name": "This name will be used by any certificates that you earn.",
"help.text.username.1": "The name that will identify you in your courses.",
"help.text.username.2": "This can not be changed later.",
"help.text.email": "For account activation and important updates",
"create.account.for.free.button": "Create an account for free",
"registration.other.options.heading": "Or register with:",
"register.institution.login.button": "Institution/campus credentials",
"register.institution.login.page.title": "Register with institution/campus credentials",
"empty.name.field.error": "Enter your full name",
"empty.email.field.error": "Enter your email",
"empty.username.field.error": "Username must be between 2 and 30 characters",
"empty.password.field.error": "Password criteria has not been met",
"empty.country.field.error": "Select your country or region of residence",
"email.do.not.match": "The email addresses do not match.",
"email.invalid.format.error": "Enter a valid email address",
"username.validation.message": "Username must be between 2 and 30 characters",
"name.validation.message": "Enter a valid name",
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
"registration.request.failure.header": "We couldn't create your account.",
"registration.empty.form.submission.error": "Please check your responses and try again.",
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",
"terms.of.service": "Terms of Service",
"registration.username.suggestion.label": "Suggested:",
"did.you.mean.alert.text": "Did you mean",
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
"sign.in": "Sign in",
"reset.password.page.title": "Reset Password | {siteName}",
"reset.password": "Reset password",
"reset.password.page.instructions": "Enter and confirm your new password.",
"new.password.label": "New password",
"confirm.password.label": "Confirm password",
"passwords.do.not.match": "Passwords do not match",
"confirm.your.password": "Confirm your password",
"reset.password.failure.heading": "We couldn't reset your password.",
"reset.password.form.submission.error": "Please check your responses and try again.",
"reset.server.rate.limit.error": "Too many requests.",
"reset.password.success.heading": "Password reset complete.",
"reset.password.success": "Your password has been reset. Sign in to your account.",
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
}

View File

@@ -1,166 +1,168 @@
{
"start.learning": "Começar a aprender",
"with.site.name": "with {siteName}",
"with.site.name": "com {siteName}",
"complete.your.profile.1": "Concluído",
"complete.your.profile.2": "o seu perfil",
"welcome.to.platform": "Bem vindo a {siteName}, {username}!",
"institution.login.page.sub.heading": "Choose your institution from the list below",
"institution.login.page.sub.heading": "Escolha a sua instituição a partir da lista abaixo",
"logistration.sign.in": "Iniciar sessão",
"logistration.register": "Registe-se",
"enterprisetpa.title.heading": "Gostaria de iniciar sessão usando as suas {providerName} credenciais?",
"enterprisetpa.login.button.text": "Mostre-me outras formas de iniciar sessão ou registar-se",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Mostre-me outras formas de iniciar sessão",
"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!",
"show.password": "Mostrar palavra-passe",
"hide.password": "Ocultar palavra-passe",
"one.letter": "1 letra",
"one.number": "1 número",
"eight.characters": "8 caracteres",
"password.sr.only.helping.text": "A palavra-passe deve conter pelo menos 8 carateres, pelo menos uma letra, e pelo menos um número",
"tpa.alert.heading": "Quase pronto!",
"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}.",
"registration.using.tpa.form.heading": "Finish creating your account",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"register.third.party.auth.account.not.linked": "Entrou com sucesso em {currentProvider}! Só precisamos de um pouco mais de informação antes de começar a aprender com {platformName}.",
"registration.using.tpa.form.heading": "Acabe de criar a sua conta",
"zendesk.supportTitle": "Apoio edX",
"zendesk.selectTicketForm": "Por favor, escolha o seu tipo de pedido:",
"error.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\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.confirmation.message": "Enviámos um email para {email} com instruções para redefinir a sua palavra-passe.\n Se não receber uma mensagem de redefinição de palavra-passe após 1 minuto, verifique se introduziu\n o endereço de correio electrónico correto, ou verifique a sua pasta de spam. Se precisar de mais assistência, {supportLink}.",
"forgot.password.page.title": "Esqueceu a Senha | {siteName}",
"forgot.password.page.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.heading": "Redefinir palavra-passe",
"forgot.password.page.instructions": "Por favor introduza o seu endereço de email abaixo e enviar-lhe-emos um email com instruções sobre como redefinir a sua palavra-passe.",
"forgot.password.page.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.alert.title.": "Não nos foi possível contactá-lo.",
"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.empty.email.field.error": "Digite o seu email",
"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 {platformName} support at ",
"sign.in.text": "Sign in",
"extend.field.errors": "{emailError} below.",
"invalid.token.heading": "Invalid password reset link",
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
"token.validation.rate.limit.error.heading": "Too many requests",
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
"token.validation.internal.sever.error.heading": "Token validation failure",
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"confirmation.support.link": "contacto o suporte técnico",
"need.help.sign.in.text": "Precisa de ajuda para entrar?",
"additional.help.text": "Para ajuda adicional, contacte o apoio {platformName} em",
"sign.in.text": "Iniciar sessão",
"extend.field.errors": "{emailError} abaixo.",
"invalid.token.heading": "Link para Redefinir Palavra-passe inválido",
"invalid.token.error.message": "Este link de redefinição de palavra-passe é inválido. Pode já ter sido utilizada. Introduza o seu email abaixo para receber um novo link.",
"token.validation.rate.limit.error.heading": "Demasiados pedidos",
"token.validation.rate.limit.error": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo.",
"token.validation.internal.sever.error.heading": "Falha na validação do Token",
"token.validation.internal.sever.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
"internal.server.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
"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}.",
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
"allowed.domain.login.error": "Como utilizador do {allowedDomain}, deve iniciar sessão com o seu {allowedDomain} {tpaLink}.",
"login.incorrect.credentials.error.attempts.text.1": "O nome de utilizador, email ou palavra-passe que introduziu está incorreto. Tem {remainingAttempts} mais tentativas\n de login antes da sua conta ser temporariamente bloqueada.",
"login.incorrect.credentials.error.attempts.text.2": "Se esqueceu a sua palavra-passe, {resetLink}",
"account.locked.out.message.2": "Por precaução, pode {resetLink} antes de tentar novamente.",
"login.incorrect.credentials.error.with.reset.link": "O nome de utilizador, email ou senha que introduziu está incorreto. Por favor, tente novamente ou {resetLink}.",
"login.page.title": "Iniciar sessão | {siteName}",
"login.user.identity.label": "Username or email",
"login.password.label": "Password",
"login.user.identity.label": "Nome de utilizador ou e-mail",
"login.password.label": "Palavra-passe",
"sign.in.button": "Iniciar Sessão",
"forgot.password": "Forgot password",
"institution.login.button": "Institution/campus credentials",
"forgot.password": "Esqueci-me da palavra-passe",
"institution.login.button": "Credenciais da instituição/campus",
"institution.login.page.title": "Inicie sessão com credenciais de instituição/campus",
"login.other.options.heading": "Or sign in with:",
"login.other.options.heading": "Ou faça login com:",
"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.",
"enterprise.login.btn.text": "Company or school credentials",
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
"email.validation.message": "Enter your username or email",
"password.validation.message": "Password criteria has not been met",
"non.compliant.password.message": "A sua palavra-passe atual não satisfaz os novos requisitos de segurança. Acabámos de enviar uma mensagem de redefinição da palavra-passe para o endereço de email associado a esta conta. Obrigado por nos ajudar a manter os seus dados em segurança.",
"account.locked.out.message.1": "Para proteger sua conta, esta foi temporariamente bloqueada. Tente novamente dentro de 30 minutos.",
"enterprise.login.btn.text": "Credenciais da empresa ou escola",
"username.or.email.format.validation.less.chars.message": "O nome de utilizador ou email deve ter pelo menos 3 carateres.",
"email.validation.message": "Insira o seu nome de utilizador ou email",
"password.validation.message": "Os critérios de palavra-passe não foram cumpridos",
"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.info.message": "Esta conta já foi ativada.",
"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",
"tpa.account.link": "{provider} account",
"account.confirmation.success.message.title": "Sucesso! Confirmou o seu email.",
"account.confirmation.success.message": "Inicie sessão para continuar.",
"account.confirmation.info.message": "Este email já foi confirmado.",
"account.confirmation.error.message.title": "Não foi possível confirmar o seu email",
"tpa.account.link": "Conta {provider}",
"internal.server.error.message": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
"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.form.invalid.error.message": "Please fill in the fields below.",
"login.incorrect.credentials.error.reset.link.text": "reset your password",
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
"password.security.nudge.title": "Password security",
"password.security.block.title": "Password change required",
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.incorrect.credentials.error": "O nome de utilizador, email, ou palavra-passe que introduziu está incorreto. Por favor, tente novamente.",
"login.form.invalid.error.message": "Por favor, preencha os campos abaixo.",
"login.incorrect.credentials.error.reset.link.text": "redefinir a sua palavra-passe",
"login.incorrect.credentials.error.before.account.blocked.text": "clique aqui para a redefinir.",
"password.security.nudge.title": "Segurança da palavra-passe",
"password.security.block.title": "Alteração de palavra-passe necessária",
"password.security.nudge.body": "O nosso sistema detectou que a sua palavra-passe é vulnerável. Recomendamos que a altere para que a sua conta permaneça segura.",
"password.security.block.body": "O nosso sistema detectou que a sua palavra-passe é vulnerável. Altere a sua palavra-passe para que a sua conta permaneça segura.",
"password.security.close.button": "Fechar",
"password.security.redirect.to.reset.password.button": "Redefinir a sua palavra-passe",
"login.tpa.authentication.failure": "Lamentamos, mas não tem autorização para aceder à plataforma {platform_name} através deste canal. Pedimos-lhe que entre em contacto com o administrador ou gestor do curso para aceder à plataforma {platform_name}. {line_break}{line_break}Detalhes do erro: {line_break}{error_message}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
"optional.fields.submit.button": "Submit",
"optional.fields.skip.button": "Skip for now",
"optional.fields.next.button": "Next",
"continue.to.platform": "Continue to {platformName}",
"modal.title": "Thanks for letting us know.",
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
"welcome.page.error.heading": "We couldn't update your profile",
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"progressive.profiling.page.heading": "Algumas perguntas para si vão ajudar a tornar-nos mais espertos.",
"optional.fields.information.link": "Saiba mais sobre a forma como utilizamos esta informação.",
"optional.fields.submit.button": "Submeter",
"optional.fields.skip.button": "Saltar por enquanto",
"optional.fields.next.button": "Seguinte",
"continue.to.platform": "Continue para {platformName}",
"modal.title": "Obrigado por nos informar.",
"modal.description": "Pode completar o seu perfil nas configurações em qualquer altura se mudar de ideias.",
"welcome.page.error.heading": "Não foi possível atualizar o seu perfil",
"welcome.page.error.message": "Ocorreu um erro. Pode completar o seu perfil nas configurações em qualquer altura.",
"recommendation.page.title": "Recomendações | {siteName}",
"recommendation.page.heading": "Temos algumas recomendações para o ajudar a começar.",
"recommendation.skip.button": "Saltar por enquanto",
"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.for.free.button": "Create an account for free",
"registration.other.options.heading": "Or register with:",
"register.institution.login.button": "Institution/campus credentials",
"registration.fullname.label": "Nome completo",
"registration.email.label": "E-mail",
"registration.username.label": "Nome de utilizador público",
"registration.password.label": "Palavra-passe",
"registration.country.label": "País/Região",
"registration.opt.in.label": "Concordo que {siteName} pode enviar-me mensagens de marketing.",
"help.text.name": "Este nome será usado por quaisquer certificados que conseguir obter.",
"help.text.username.1": "O nome que o irá identificar nos seus cursos.",
"help.text.username.2": "Isto não pode ser alterado mais tarde.",
"help.text.email": "Para ativação de contas e atualizações importantes",
"create.account.for.free.button": "Criar uma conta gratuitamente",
"registration.other.options.heading": "Ou registe-se com:",
"register.institution.login.button": "Credenciais da instituição/campus",
"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.do.not.match": "The email addresses do not match.",
"email.invalid.format.error": "Enter a valid email address",
"username.validation.message": "Username must be between 2 and 30 characters",
"name.validation.message": "Enter a valid name",
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
"empty.name.field.error": "Insira o seu nome completo",
"empty.email.field.error": "Insira o seu email",
"empty.username.field.error": "O nome de utilizador deve ter entre 2 e 30 carateres",
"empty.password.field.error": "Os critérios de palavra-passe não foram cumpridos",
"empty.country.field.error": "Selecione o seu país ou região de residência",
"email.do.not.match": "Os endereços de email não correspondem.",
"email.invalid.format.error": "Introduzir um endereço de correio electrónico válido",
"username.validation.message": "O nome de utilizador deve ter entre 2 e 30 carateres",
"name.validation.message": "Introduza um nome válido",
"username.format.validation.message": "Os nomes de utilizador só podem conter letras (A-Z, a-z), numerais (0-9), sublinhados (_), e hífenes (-). Os nomes de utilizador não podem conter espaços",
"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.empty.form.submission.error": "Verifique as suas respostas e tente novamente.",
"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.",
"registration.rate.limit.error": "Demasiadas tentativas de registo falhadas. Tente novamente mais tarde.",
"registration.tpa.session.expired": "O tempo para registo utilizando {provider} expirou.",
"registration.tpa.authentication.failure": "Lamentamos, mas não tem autorização para aceder à plataforma {platform_name} através deste canal. Pedimos-lhe que entre em contacto com o administrador ou gestor do curso para aceder à plataforma {platform_name}. {line_break}{line_break}Detalhes do erro: {line_break}{error_message}",
"terms.of.service.and.honor.code": "Termos de Serviço e Código de Honra",
"privacy.policy": "Política de Privacidade",
"honor.code": "Honor Code",
"terms.of.service": "Terms of Service",
"registration.username.suggestion.label": "Suggested:",
"did.you.mean.alert.text": "Did you mean",
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
"sign.in": "Sign in",
"honor.code": "Código de Honra",
"terms.of.service": "Termos do Serviço",
"registration.username.suggestion.label": "Sugerido:",
"did.you.mean.alert.text": "Quis dizer",
"register.page.terms.of.service.and.honor.code": "Ao criar uma conta, concorda com o {tosAndHonorCode} e reconhece que {platformName} e cada\n Membro processa os seus dados pessoais em conformidade com a {privacyPolicy}.",
"register.page.honor.code": "Concordo com a {tosAndHonorCode} {platformName}",
"register.page.terms.of.service": "Concordo com os {termsOfService} {platformName}",
"sign.in": "Iniciar sessão",
"reset.password.page.title": "Redefinir Palavra-passe | {siteName}",
"reset.password": "Reset password",
"reset.password": "Redefinir palavra-passe",
"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",
"reset.password.failure.heading": "We couldn't reset your password.",
"reset.password.form.submission.error": "Please check your responses and try again.",
"reset.server.rate.limit.error": "Too many requests.",
"reset.password.success.heading": "Password reset complete.",
"reset.password.success": "Your password has been reset. Sign in to your account.",
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
"new.password.label": "Nova palavra-passe",
"confirm.password.label": "Confirmar palavra-passe",
"passwords.do.not.match": "As palavras-passe não coincidem",
"confirm.your.password": "Confirme a sua palavra-passe",
"reset.password.failure.heading": "Não conseguimos redefinir sua palavra-passe.",
"reset.password.form.submission.error": "Por favor, verifique as suas respostas e tente novamente.",
"reset.server.rate.limit.error": "Demasiados pedidos.",
"reset.password.success.heading": "Redefinição de palavra-passe concluída",
"reset.password.success": "A sua palavra-passe foi redefinida. Inicie sessão na sua conta.",
"rate.limit.error": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo."
}

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",

View File

@@ -96,6 +96,7 @@
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
@@ -140,6 +141,7 @@
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",

View File

@@ -8,10 +8,9 @@ import {
APP_INIT_ERROR, APP_READY, initialize, mergeConfig, subscribe,
} from '@edx/frontend-platform';
import { ErrorPage } from '@edx/frontend-platform/react';
import { messages as paragonMessages } from '@edx/paragon';
import configuration from './config';
import appMessages from './i18n';
import messages from './i18n';
import MainApp from './MainApp';
subscribe(APP_READY, () => {
@@ -31,8 +30,5 @@ initialize({
mergeConfig(configuration);
},
},
messages: [
appMessages,
paragonMessages,
],
messages,
});

View File

@@ -3,5 +3,4 @@
@import "~@edx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@import "@edx/frontend-component-cookie-policy-banner/build/_cookie-policy-banner";
@import "sass/style";

View File

@@ -9,10 +9,10 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import { Link, Redirect } from 'react-router-dom';
import messages from './messages';
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
import { updatePathWithQueryParams } from '../data/utils';
import useMobileResponsive from '../data/utils/useMobileResponsive';
import messages from './messages';
const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
const isMobileView = useMobileResponsive();

View File

@@ -13,6 +13,14 @@ import { Helmet } from 'react-helmet';
import Skeleton from 'react-loading-skeleton';
import { Link } from 'react-router-dom';
import AccountActivationMessage from './AccountActivationMessage';
import {
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
} from './data/actions';
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
import {
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration,
RenderInstitutionButton, SocialAuthProviders, ThirdPartyAuthAlert,
@@ -33,14 +41,6 @@ import {
windowScrollTo,
} from '../data/utils';
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
import AccountActivationMessage from './AccountActivationMessage';
import {
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
} from './data/actions';
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
class LoginPage extends React.Component {
constructor(props, context) {
@@ -231,14 +231,9 @@ class LoginPage extends React.Component {
tpaAuthenticationError.errorCode = TPA_AUTHENTICATION_FAILURE;
}
if (this.props.loginResult.success) {
window.parent.postMessage({ loggedin: true, redirectUrl: this.props.loginResult.redirectUrl }, 'https://discover.edx.org/social-reg');
setSurveyCookie('login');
// Fire optimizely events
window.optimizely = window.optimizely || [];
window.optimizely.push({
type: 'event',
eventName: 'authn-login-coversion',
});
return null;
}
return (
@@ -248,11 +243,11 @@ class LoginPage extends React.Component {
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<RedirectLogistration
success={this.props.loginResult.success}
redirectUrl={this.props.loginResult.redirectUrl}
finishAuthUrl={thirdPartyAuthContext.finishAuthUrl}
/>
{/*<RedirectLogistration*/}
{/* success={this.props.loginResult.success}*/}
{/* redirectUrl={this.props.loginResult.redirectUrl}*/}
{/* finishAuthUrl={thirdPartyAuthContext.finishAuthUrl}*/}
{/*/>*/}
<div className="mw-xs mt-3">
<ThirdPartyAuthAlert
currentProvider={thirdPartyAuthContext.currentProvider}
@@ -299,16 +294,16 @@ class LoginPage extends React.Component {
onClick={this.handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
<Link
id="forgot-password"
name="forgot-password"
className="btn btn-link font-weight-500 text-body"
to={updatePathWithQueryParams(RESET_PAGE)}
onClick={this.handleForgotPasswordLinkClickEvent}
>
{intl.formatMessage(messages['forgot.password'])}
</Link>
{this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
{/*<Link*/}
{/* id="forgot-password"*/}
{/* name="forgot-password"*/}
{/* className="btn btn-link font-weight-500 text-body"*/}
{/* to={updatePathWithQueryParams(RESET_PAGE)}*/}
{/* onClick={this.handleForgotPasswordLinkClickEvent}*/}
{/*>*/}
{/* {intl.formatMessage(messages['forgot.password'])}*/}
{/*</Link>*/}
{/*{this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}*/}
</Form>
</div>
</>

View File

@@ -1,6 +1,6 @@
import { LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from './actions';
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
import { RESET_PASSWORD } from '../../reset-password';
import { LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from './actions';
export const defaultState = {
loginError: null,

View File

@@ -1,6 +1,5 @@
import React from 'react';
import * as auth from '@edx/frontend-platform/auth';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
@@ -20,8 +19,9 @@ import {
} from '../data/constants';
import LoginFailureMessage from '../LoginFailure';
jest.mock('@edx/frontend-platform/auth');
auth.getAuthService = jest.fn();
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthService: jest.fn(),
}));
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);

View File

@@ -1,17 +1,15 @@
import React from 'react';
import { Provider } from 'react-redux';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import * as auth from '@edx/frontend-platform/auth';
import { sendPageEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
import { COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE } from '../../data/constants';
import {
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
} from '../data/actions';
@@ -19,12 +17,13 @@ import { INTERNAL_SERVER_ERROR } from '../data/constants';
import LoginFailureMessage from '../LoginFailure';
import LoginPage from '../LoginPage';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/auth');
analytics.sendTrackEvent = jest.fn();
analytics.sendPageEvent = jest.fn();
auth.getAuthService = jest.fn();
jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(),
}));
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthService: jest.fn(),
}));
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
const IntlLoginPage = injectIntl(LoginPage);
@@ -72,7 +71,7 @@ describe('LoginPage', () => {
const ssoProvider = {
id: 'oa2-apple-id',
name: 'Apple',
iconClass: null,
iconClass: 'apple',
iconImage: 'https://openedx.devstack.lms/logo.png',
loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard',
};
@@ -414,7 +413,7 @@ describe('LoginPage', () => {
+ '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' };
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?account_activation_status=success' };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(loginPage.find('div#account-activation-message').text()).toEqual(activationMessage);
@@ -569,8 +568,7 @@ describe('LoginPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(loginPage.find(`button#${ssoProvider.id}`).find('span').text()).toEqual(ssoProvider.name);
@@ -592,7 +590,7 @@ describe('LoginPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
secondaryProviders.iconImage = null;
mount(reduxWrapper(<IntlLoginPage {...props} />));
@@ -617,8 +615,7 @@ describe('LoginPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?next=/dashboard&tpa_hint=invalid' };
ssoProvider.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(loginPage.find(`button#${ssoProvider.id}`).find('span#provider-name').text()).toEqual(`${ssoProvider.name}`);
@@ -642,8 +639,7 @@ describe('LoginPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(loginPage.find('button#other-ways-to-sign-in').text()).toEqual('Show me other ways to sign in or register');
@@ -666,8 +662,7 @@ describe('LoginPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(loginPage.find('button#other-ways-to-sign-in').text()).toEqual('Show me other ways to sign in');
@@ -675,14 +670,9 @@ describe('LoginPage', () => {
// ******** miscellaneous tests ********
it('should render cookie banner', () => {
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(loginPage.find(<CookiePolicyBanner />)).toBeTruthy();
});
it('should send page event when login page is rendered', () => {
mount(reduxWrapper(<IntlLoginPage {...props} />));
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
});
it('tests that form is only scrollable on form submission', () => {

View File

@@ -15,16 +15,16 @@ import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { BaseComponent } from '../base-component';
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
import {
tpaProvidersSelector,
} from '../common-components/data/selectors';
import messages from '../common-components/messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import { getTpaHint, getTpaProvider, updatePathWithQueryParams } from '../data/utils';
import { LoginPage } from '../login';
import { RegistrationPage } from '../register';
import { backupRegistrationForm } from '../register/data/actions';
import { clearThirdPartyAuthContextErrorMessage } from './data/actions';
import {
tpaProvidersSelector,
} from './data/selectors';
import messages from './messages';
const Logistration = (props) => {
const { selectedPage, tpaProviders } = props;
@@ -81,56 +81,56 @@ const Logistration = (props) => {
};
return (
<BaseComponent>
<div>
{disablePublicAccountCreation
? (
<>
<Redirect to={updatePathWithQueryParams(LOGIN_PAGE)} />
{institutionLogin && (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
</Tabs>
// <BaseComponent>
<div className="d-flex justify-content-center">
{disablePublicAccountCreation
? (
<>
<Redirect to={updatePathWithQueryParams(LOGIN_PAGE)} />
{institutionLogin && (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
</Tabs>
)}
<div id="main-content" className="main-content">
{!institutionLogin && (
<h3 className="mb-4.5">{formatMessage(messages['logistration.sign.in'])}</h3>
)}
<div id="main-content" className="main-content">
{!institutionLogin && (
<h3 className="mb-4.5">{formatMessage(messages['logistration.sign.in'])}</h3>
)}
<LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
</div>
</>
)
: (
<div>
{institutionLogin
? (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={selectedPage === LOGIN_PAGE ? LOGIN_PAGE : REGISTER_PAGE} />
</Tabs>
)
: (!isValidTpaHint() && (
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={handleOnSelect}>
<Tab title={formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
<Tab title={formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
</Tabs>
))}
{ key && (
<Redirect to={updatePathWithQueryParams(key)} />
)}
<div id="main-content" className="main-content">
{selectedPage === LOGIN_PAGE
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
: (
<RegistrationPage
institutionLogin={institutionLogin}
handleInstitutionLogin={handleInstitutionLogin}
/>
)}
</div>
<LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
</div>
)}
</div>
</BaseComponent>
</>
)
: (
<div>
{institutionLogin
? (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={selectedPage === LOGIN_PAGE ? LOGIN_PAGE : REGISTER_PAGE} />
</Tabs>
)
: (!isValidTpaHint() && (
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={handleOnSelect}>
<Tab title={formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
<Tab title={formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
</Tabs>
))}
{ key && (
<Redirect to={updatePathWithQueryParams(key)} />
)}
<div id="main-content" className="main-content">
{selectedPage === LOGIN_PAGE
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
: (
<RegistrationPage
institutionLogin={institutionLogin}
handleInstitutionLogin={handleInstitutionLogin}
/>
)}
</div>
</div>
)}
</div>
// </BaseComponent>
);
};

View File

@@ -2,22 +2,23 @@ import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import * as auth from '@edx/frontend-platform/auth';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { COMPLETE_STATE, LOGIN_PAGE } from '../../data/constants';
import { backupRegistrationForm } from '../../register/data/actions';
import { clearThirdPartyAuthContextErrorMessage } from '../data/actions';
import { RenderInstitutionButton } from '../InstitutionLogistration';
import Logistration from '../Logistration';
import Logistration from './Logistration';
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
import { RenderInstitutionButton } from '../common-components/InstitutionLogistration';
import { COMPLETE_STATE, LOGIN_PAGE } from '../data/constants';
import { backupRegistrationForm } from '../register/data/actions';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(),
}));
jest.mock('@edx/frontend-platform/auth');
analytics.sendPageEvent = jest.fn();
const mockStore = configureStore();
const IntlLogistration = injectIntl(Logistration);
@@ -41,7 +42,13 @@ describe('Logistration', () => {
);
beforeEach(() => {
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(() => ({
userId: 3,
username: 'test-user',
})),
}));
configure({
loggingService: { logError: jest.fn() },
config: {
@@ -182,8 +189,8 @@ describe('Logistration', () => {
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
mergeConfig({
DISABLE_ENTERPRISE_LOGIN: '',

View File

@@ -22,6 +22,10 @@ import { Error } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { saveUserProfile } from './data/actions';
import { welcomePageSelector } from './data/selectors';
import messages from './messages';
import ProgressiveProfilingPageModal from './ProgressiveProfilingPageModal';
import { BaseComponent } from '../base-component';
import { RedirectLogistration } from '../common-components';
import {
@@ -33,10 +37,6 @@ import {
activateRecommendationsExperiment, RECOMMENDATIONS_EXP_VARIATION, trackRecommendationViewedOptimizely,
} from '../recommendations/optimizelyExperiment';
import { trackRecommendationsGroup, trackRecommendationsViewed } from '../recommendations/track';
import { saveUserProfile } from './data/actions';
import { welcomePageSelector } from './data/selectors';
import messages from './messages';
import ProgressiveProfilingPageModal from './ProgressiveProfilingPageModal';
const ProgressiveProfiling = (props) => {
const {
@@ -177,7 +177,7 @@ const ProgressiveProfiling = (props) => {
) : null}
<div className="mw-xs m-4 pp-page-content">
<div>
<h2 className="pp-page-heading text-primary">{formatMessage(messages['progressive.profiling.page.heading'])}</h2>
<h2 className="pp-page__heading text-primary">{formatMessage(messages['progressive.profiling.page.heading'])}</h2>
</div>
<hr className="border-light-700 mb-4" />
{showError ? (
@@ -189,7 +189,7 @@ const ProgressiveProfiling = (props) => {
<Form>
{formFields}
{(getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK) && (
<span className="progressive-profiling-support">
<span className="pp-page__support-link">
<Hyperlink
isInline
variant="muted"
@@ -206,7 +206,7 @@ const ProgressiveProfiling = (props) => {
<StatefulButton
type="submit"
variant="brand"
className="login-button-width"
className="pp-page__button-width"
state={submitState}
labels={{
default: showRecommendationsPage ? formatMessage(messages['optional.fields.next.button']) : formatMessage(messages['optional.fields.submit.button']),

View File

@@ -1,7 +1,7 @@
import { SAVE_USER_PROFILE } from './actions';
import {
DEFAULT_STATE, PENDING_STATE,
} from '../../data/constants';
import { SAVE_USER_PROFILE } from './actions';
export const defaultState = {
extendedProfile: [],

View File

@@ -2,10 +2,9 @@ import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import * as auth from '@edx/frontend-platform/auth';
import { identifyAuthenticatedUser, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import * as logging from '@edx/frontend-platform/logging';
import { mount } from 'enzyme';
import { createMemoryHistory } from 'history';
import { act } from 'react-dom/test-utils';
@@ -21,18 +20,20 @@ import ProgressiveProfiling from '../ProgressiveProfiling';
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();
analytics.identifyAuthenticatedUser = 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));
jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(),
identifyAuthenticatedUser: jest.fn(),
}));
jest.mock('@edx/frontend-platform/auth', () => ({
configure: jest.fn(),
ensureAuthenticatedUser: jest.fn().mockImplementation(() => Promise.resolve(true)),
hydrateAuthenticatedUser: jest.fn().mockImplementation(() => Promise.resolve(true)),
getAuthenticatedUser: jest.fn(),
}));
jest.mock('@edx/frontend-platform/logging', () => ({
getLoggingService: jest.fn(),
}));
const history = createMemoryHistory();
@@ -129,14 +130,14 @@ describe('ProgressiveProfilingTests', () => {
});
it('should make identify call to segment on progressive profiling page', async () => {
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
await getProgressiveProfilingPage();
expect(analytics.identifyAuthenticatedUser).toHaveBeenCalledWith(3);
expect(analytics.identifyAuthenticatedUser).toHaveBeenCalled();
expect(identifyAuthenticatedUser).toHaveBeenCalledWith(3);
expect(identifyAuthenticatedUser).toHaveBeenCalled();
});
it('should submit user profile details on form submission', async () => {
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
const formPayload = {
gender: 'm',
extended_profile: [{ field_name: 'company', field_value: 'test company' }],
@@ -155,14 +156,14 @@ describe('ProgressiveProfilingTests', () => {
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');
expect(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');
progressiveProfilingPage.find('.pp-page__support-link a[target="_blank"]').simulate('click');
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked');
});
it('should show error message when patch request fails', async () => {
@@ -206,7 +207,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
const progressiveProfilingPage = await getProgressiveProfilingPage();
expect(progressiveProfilingPage.find('button.btn-brand').text()).toEqual('Next');
@@ -241,7 +242,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
const progressiveProfilingPage = await getProgressiveProfilingPage();
expect(progressiveProfilingPage.find('button.btn-brand').text()).toEqual('Submit');

View File

@@ -55,7 +55,7 @@ const RecommendationCard = (props) => {
subtitle={getOwners()}
/>
<Card.Section />
<Card.Footer textElement={<small className="footer-text">Course</small>} />
<Card.Footer textElement={<small className="pgn__card__footer-text">Course</small>} />
</Card>
</Hyperlink>
</div>

View File

@@ -10,10 +10,10 @@ const RecommendationsList = (props) => {
return (
<Container id="course-recommendations" size="lg" className="recommendations-container">
<h2 className="text-sm-center mb-4 text-left recommendations-heading">
<h2 className="text-sm-center mb-4 text-left recommendations-container__heading">
{title}
</h2>
<div className="d-flex card-list">
<div className="d-flex recommendations-container__card-list">
{
recommendations.map((recommendation, idx) => (
<RecommendationCard

View File

@@ -8,13 +8,13 @@ import {
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { DEFAULT_REDIRECT_URL } from '../data/constants';
import { EDUCATION_LEVEL_MAPPING, RECOMMENDATIONS_COUNT } from './data/constants';
import getPersonalizedRecommendations from './data/service';
import { convertCourseRunKeytoCourseKey } from './data/utils';
import messages from './messages';
import RecommendationsList from './RecommendationsList';
import { trackRecommendationsViewed } from './track';
import { DEFAULT_REDIRECT_URL } from '../data/constants';
const RecommendationsPage = (props) => {
const { location } = props;
@@ -127,7 +127,7 @@ const RecommendationsPage = (props) => {
</div>
)
: (
<Spinner animation="border" variant="primary" className="centered-align-spinner" />
<Spinner animation="border" variant="primary" className="spinner--position-centered" />
)}
</div>
</>

View File

@@ -12,9 +12,9 @@ export const eventNames = {
* Activate the post registration recommendations optimizely experiment
* and return the true if the user is in variation else false.
* @param {String} userId user id of authenticated user.
* @return {string} true if the user is in variation else false
* @return {string} variation the user belong in
*/
const activateRecommendationsExperiment = (userId) => optimizelyInstance.activate(RECOMMENDATIONS_EXP_KEY, userId);
const activateRecommendationsExperiment = (userId) => optimizelyInstance?.activate(RECOMMENDATIONS_EXP_KEY, userId);
/**
* Fire an optimizely track event for post registration recommended course card clicked.
@@ -22,7 +22,7 @@ const activateRecommendationsExperiment = (userId) => optimizelyInstance.activat
* @param {Object} userAttributes Dictionary of user attributes (optional).
*/
const trackRecommendationCardClickOptimizely = (userId, userAttributes = {}) => {
optimizelyInstance.track(eventNames.recommendedCourseClicked, userId, userAttributes);
optimizelyInstance?.track(eventNames.recommendedCourseClicked, userId, userAttributes);
};
/**
@@ -31,7 +31,7 @@ const trackRecommendationCardClickOptimizely = (userId, userAttributes = {}) =>
* @param {Object} userAttributes Dictionary of user attributes (optional).
*/
const trackRecommendationViewedOptimizely = (userId, userAttributes = {}) => {
optimizelyInstance.track(eventNames.recommendationsViewed, userId, userAttributes);
optimizelyInstance?.track(eventNames.recommendationsViewed, userId, userAttributes);
};
export {

View File

@@ -2,29 +2,32 @@ import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import configureStore from 'redux-mock-store';
import { mockedGeneralRecommendations, mockedResponse } from './mockedData';
import { DEFAULT_REDIRECT_URL } from '../../data/constants';
import * as getPersonalizedRecommendations from '../data/service';
import getPersonalizedRecommendations from '../data/service';
import { trackRecommendationCardClickOptimizely } from '../optimizelyExperiment';
import RecommendationsPage from '../RecommendationsPage';
import { mockedGeneralRecommendations, mockedResponse } from './mockedData';
const IntlRecommendationsPage = injectIntl(RecommendationsPage);
const mockStore = configureStore();
jest.mock('@edx/frontend-platform/analytics');
jest.mock('../data/service');
jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
jest.mock('../data/service', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('../optimizelyExperiment', () => ({
trackRecommendationCardClickOptimizely: jest.fn(),
}));
analytics.sendTrackEvent = jest.fn();
describe('RecommendationsPageTests', () => {
mergeConfig({
GENERAL_RECOMMENDATIONS: '[]',
@@ -72,10 +75,10 @@ describe('RecommendationsPageTests', () => {
href: getConfig().BASE_URL,
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
};
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve([]));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve([]));
await getRecommendationsPage({});
expect(getPersonalizedRecommendations.default).toHaveBeenCalledTimes(0);
expect(getPersonalizedRecommendations).toHaveBeenCalledTimes(0);
expect(window.location.href).toEqual(DASHBOARD_URL);
});
it('redirects to dashboard if user click on skip button', async () => {
@@ -92,35 +95,36 @@ describe('RecommendationsPageTests', () => {
},
},
};
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve(mockedResponse));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve(mockedResponse));
const recommendationsPage = await getRecommendationsPage(props);
recommendationsPage.find('button').simulate('click');
expect(window.location.href).toEqual(DASHBOARD_URL);
});
it('should call trackRecommendationCardClickOptimizely when card is clicked', async () => {
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve(mockedResponse));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve(mockedResponse));
const recommendationsPage = await getRecommendationsPage();
recommendationsPage.find('.card-box').first().simulate('click');
expect(trackRecommendationCardClickOptimizely).toHaveBeenCalledTimes(1);
});
it('should show loading state to user', async () => {
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve(mockedResponse));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve(mockedResponse));
await act(async () => {
const recommendationsPage = mount(reduxWrapper(<IntlRecommendationsPage {...defaultProps} />));
expect(recommendationsPage.find('.centered-align-spinner').exists()).toBeTruthy();
expect(recommendationsPage.find('.spinner--position-centered').exists()).toBeTruthy();
});
});
it('should call getPersonalizedRecommendations', async () => {
delete window.location;
window.location = { assign: jest.fn() };
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve([]));
getPersonalizedRecommendations.mockClear();
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve([]));
await getRecommendationsPage();
expect(getPersonalizedRecommendations.default).toHaveBeenCalledTimes(1);
expect(analytics.sendTrackEvent).toHaveBeenCalledWith(
expect(getPersonalizedRecommendations).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith(
'edx.bi.user.recommendations.viewed',
{
page: 'authn_recommendations',
@@ -133,14 +137,14 @@ describe('RecommendationsPageTests', () => {
});
it('should display recommendations returned by Algolia', async () => {
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve(mockedResponse));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve(mockedResponse));
const recommendationsPage = await getRecommendationsPage();
expect(recommendationsPage.find('#course-recommendations').exists()).toBeTruthy();
});
it('should not display recommendations if error comes in while fetching the recommendations', async () => {
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.reject(mockedResponse));
getPersonalizedRecommendations.mockImplementation(() => Promise.reject(mockedResponse));
const recommendationsPage = await getRecommendationsPage();
expect(recommendationsPage.find('#recommendation-card').exists()).toBeFalsy();
@@ -149,7 +153,7 @@ describe('RecommendationsPageTests', () => {
it('should redirect if recommended courses count is less than RECOMMENDATIONS_COUNT', async () => {
delete window.location;
window.location = { assign: jest.fn() };
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve([mockedResponse[0]]));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve([mockedResponse[0]]));
const recommendationsPage = await getRecommendationsPage();
expect(recommendationsPage.find('#course-recommendations').exists()).toBeFalsy();
@@ -160,14 +164,14 @@ describe('RecommendationsPageTests', () => {
mergeConfig({
GENERAL_RECOMMENDATIONS: mockedGeneralRecommendations,
});
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve([]));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve([]));
const recommendationsPage = await getRecommendationsPage();
expect(recommendationsPage.find('#course-recommendations').exists()).toBeTruthy();
});
it('should display all owners for a course', async () => {
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve(mockedResponse));
getPersonalizedRecommendations.mockImplementation(() => Promise.resolve(mockedResponse));
const recommendationsPage = await getRecommendationsPage();
expect(

View File

@@ -4,12 +4,12 @@ import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { FormFieldRenderer } from '../field-renderer';
import { FIELDS } from './data/constants';
import { validateCountryField } from './data/utils';
import messages from './messages';
import { HonorCode, TermsOfService } from './registrationFields';
import CountryField from './registrationFields/CountryField';
import { FormFieldRenderer } from '../field-renderer';
/**
* Fields on registration page that are not the default required fields (name, email, username, password).
@@ -54,13 +54,13 @@ const ConfigurableRegistrationForm = (props) => {
});
const handleOnChange = (event, countryValue = null) => {
const { name, type } = event.target;
const { name } = event.target;
let value;
if (countryValue) {
value = { ...countryValue };
} else {
value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
if (type === 'checkbox') {
if (event.target.type === 'checkbox') {
setFieldErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
}
}
@@ -171,7 +171,7 @@ const ConfigurableRegistrationForm = (props) => {
name: 'marketingEmailsOptIn',
}}
value={formFields.marketingEmailsOptIn}
className="opt-checkbox"
className="form-field--checkbox"
onChangeHandler={handleOnChange}
handleBlur={handleOnBlur}
handleFocus={handleOnFocus}

View File

@@ -6,7 +6,6 @@ import { Alert } from '@edx/paragon';
import { Error } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { windowScrollTo } from '../data/utils';
import {
FORBIDDEN_REQUEST,
INTERNAL_SERVER_ERROR,
@@ -14,6 +13,7 @@ import {
TPA_SESSION_EXPIRED,
} from './data/constants';
import messages from './messages';
import { windowScrollTo } from '../data/utils';
const RegistrationFailureMessage = (props) => {
const { formatMessage } = useIntl();

View File

@@ -13,21 +13,6 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import Skeleton from 'react-loading-skeleton';
import {
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration, ThirdPartyAuthAlert,
} from '../common-components';
import { getThirdPartyAuthContext } from '../common-components/data/actions';
import {
fieldDescriptionSelector, optionalFieldsSelector, thirdPartyAuthContextSelector,
} from '../common-components/data/selectors';
import EnterpriseSSO from '../common-components/EnterpriseSSO';
import {
COMPLETE_STATE,
DEFAULT_STATE, INVALID_NAME_REGEX, LETTER_REGEX, NUMBER_REGEX, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX,
} from '../data/constants';
import {
getAllPossibleQueryParams, getTpaHint, getTpaProvider, setCookie, setSurveyCookie,
} from '../data/utils';
import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
import {
backupRegistrationFormBegin,
@@ -52,6 +37,21 @@ import messages from './messages';
import RegistrationFailure from './RegistrationFailure';
import { EmailField, UsernameField } from './registrationFields';
import ThirdPartyAuth from './ThirdPartyAuth';
import {
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration, ThirdPartyAuthAlert,
} from '../common-components';
import { getThirdPartyAuthContext } from '../common-components/data/actions';
import {
fieldDescriptionSelector, optionalFieldsSelector, thirdPartyAuthContextSelector,
} from '../common-components/data/selectors';
import EnterpriseSSO from '../common-components/EnterpriseSSO';
import {
COMPLETE_STATE,
DEFAULT_STATE, INVALID_NAME_REGEX, LETTER_REGEX, NUMBER_REGEX, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX,
} from '../data/constants';
import {
getAllPossibleQueryParams, getTpaHint, getTpaProvider, setCookie, setSurveyCookie,
} from '../data/utils';
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
const urlRegex = new RegExp(INVALID_NAME_REGEX);
@@ -190,25 +190,27 @@ const RegistrationPage = (props) => {
}, [registrationErrorCode]);
useEffect(() => {
let countryCode = '';
let countryDisplayValue = '';
if (backendCountryCode && backendCountryCode !== configurableFormFields?.country?.countryCode) {
let countryCode = '';
let countryDisplayValue = '';
const selectedCountry = countryList.find(
(country) => (country[COUNTRY_CODE_KEY].toLowerCase() === backendCountryCode.toLowerCase()),
);
if (selectedCountry) {
countryCode = selectedCountry[COUNTRY_CODE_KEY];
countryDisplayValue = selectedCountry[COUNTRY_DISPLAY_KEY];
}
setConfigurableFormFields(prevState => (
{
...prevState,
country: {
countryCode, displayValue: countryDisplayValue,
},
const selectedCountry = countryList.find(
(country) => (country[COUNTRY_CODE_KEY].toLowerCase() === backendCountryCode.toLowerCase()),
);
if (selectedCountry) {
countryCode = selectedCountry[COUNTRY_CODE_KEY];
countryDisplayValue = selectedCountry[COUNTRY_DISPLAY_KEY];
}
));
}, [backendCountryCode, countryList]);
setConfigurableFormFields(prevState => (
{
...prevState,
country: {
countryCode, displayValue: countryDisplayValue,
},
}
));
}
}, [backendCountryCode, countryList]); // eslint-disable-line react-hooks/exhaustive-deps
/**
* We need to remove the placeholder from the field, adding a space will do that.
@@ -227,13 +229,6 @@ const RegistrationPage = (props) => {
setCookie(getConfig().REGISTER_CONVERSION_COOKIE_NAME, true);
setCookie('authn-returning-user');
// Fire optimizely events
window.optimizely = window.optimizely || [];
window.optimizely.push({
type: 'event',
eventName: 'authn-register-conversion',
});
// Fire GTM event used for integration with impact.com
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
@@ -515,20 +510,24 @@ const RegistrationPage = (props) => {
/>
);
}
if (registrationResult.success) {
window.parent.postMessage({ loggedin: true, redirectUrl: registrationResult.redirectUrl }, 'https://discover.edx.org/social-reg');
return null;
}
return (
<>
<Helmet>
<title>{formatMessage(messages['register.page.title'], { siteName: getConfig().SITE_NAME })}</title>
</Helmet>
<RedirectLogistration
success={registrationResult.success}
redirectUrl={registrationResult.redirectUrl}
finishAuthUrl={finishAuthUrl}
optionalFields={optionalFields}
redirectToProgressiveProfilingPage={
getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && Object.keys(optionalFields).includes('fields')
}
/>
{/*<RedirectLogistration*/}
{/* success={registrationResult.success}*/}
{/* redirectUrl={registrationResult.redirectUrl}*/}
{/* finishAuthUrl={finishAuthUrl}*/}
{/* optionalFields={optionalFields}*/}
{/* redirectToProgressiveProfilingPage={*/}
{/* getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && Object.keys(optionalFields).includes('fields')*/}
{/* }*/}
{/*/>*/}
{autoSubmitRegisterForm && !errorCode.type ? (
<div className="mw-xs mt-5 text-center">
<Spinner animation="border" variant="primary" id="tpa-spinner" />
@@ -609,7 +608,7 @@ const RegistrationPage = (props) => {
name="register-user"
type="submit"
variant="brand"
className="register-stateful-button-width mt-4 mb-4"
className="register-button mt-4 mb-4"
state={submitState}
labels={{
default: formatMessage(messages['create.account.for.free.button']),
@@ -618,13 +617,13 @@ const RegistrationPage = (props) => {
onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
<ThirdPartyAuth
currentProvider={currentProvider}
providers={providers}
secondaryProviders={secondaryProviders}
handleInstitutionLogin={handleInstitutionLogin}
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
/>
{/*<ThirdPartyAuth*/}
{/* currentProvider={currentProvider}*/}
{/* providers={providers}*/}
{/* secondaryProviders={secondaryProviders}*/}
{/* handleInstitutionLogin={handleInstitutionLogin}*/}
{/* thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}*/}
{/*/>*/}
</Form>
</div>
)}

View File

@@ -5,6 +5,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import Skeleton from 'react-loading-skeleton';
import messages from './messages';
import {
RenderInstitutionButton,
SocialAuthProviders,
@@ -12,7 +13,6 @@ import {
import {
PENDING_STATE, REGISTER_PAGE,
} from '../data/constants';
import messages from './messages';
/**
* This component renders the Single sign-on (SSO) buttons for the providers passed.

View File

@@ -1,7 +1,3 @@
import {
DEFAULT_STATE,
PENDING_STATE,
} from '../../data/constants';
import {
BACKUP_REGISTRATION_DATA,
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
@@ -10,6 +6,10 @@ import {
REGISTER_SET_COUNTRY_CODE, REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTERATION_CLEAR_BACKEND_ERROR,
} from './actions';
import {
DEFAULT_STATE,
PENDING_STATE,
} from '../../data/constants';
export const defaultState = {
backendCountryCode: '',

View File

@@ -1,12 +1,13 @@
import { getConfig } from '@edx/frontend-platform';
import { DEFAULT_STATE } from '../../../data/constants';
import { DEFAULT_REDIRECT_URL, DEFAULT_STATE, PENDING_STATE } from '../../../data/constants';
import {
BACKUP_REGISTRATION_DATA,
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER,
REGISTER_SET_COUNTRY_CODE,
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTERATION_CLEAR_BACKEND_ERROR,
} from '../actions';
import reducer from '../reducers';
@@ -63,6 +64,42 @@ describe('Registration Reducer Tests', () => {
},
);
});
it('should set redirect url dashboard on registration success action', () => {
const payload = {
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`,
success: true,
};
const action = {
type: REGISTER_NEW_USER.SUCCESS,
payload,
};
expect(reducer(defaultState, action)).toEqual(
{
...defaultState,
registrationResult: payload,
},
);
});
it('should set the registration call and set the registration error object empty', () => {
const action = {
type: REGISTER_NEW_USER.BEGIN,
};
expect(reducer({
...defaultState,
registrationError: {
email: 'This email already exist.',
},
}, action)).toEqual(
{
...defaultState,
submitState: PENDING_STATE,
registrationError: {},
},
);
});
it('should show username suggestions returned by registration error', () => {
const payload = { usernameSuggestions: ['test12'] };
@@ -79,7 +116,68 @@ describe('Registration Reducer Tests', () => {
},
);
});
it('should set the register user when SSO pipline data is loaded', () => {
const payload = { value: true };
const action = {
type: REGISTER_SET_USER_PIPELINE_DATA_LOADED,
payload,
};
expect(reducer(defaultState, action)).toEqual(
{
...defaultState,
userPipelineDataLoaded: true,
},
);
});
it('should set country code on blur', () => {
const action = {
type: REGISTER_SET_COUNTRY_CODE,
payload: { countryCode: 'PK' },
};
expect(reducer({
...defaultState,
registrationFormData: {
...defaultState.registrationFormData,
configurableFormFields: {
...defaultState.registrationFormData.configurableFormFields,
country: {
name: 'Pakistan',
code: 'PK',
},
},
},
}, action)).toEqual(
{
...defaultState,
registrationFormData: {
...defaultState.registrationFormData,
configurableFormFields: {
...defaultState.registrationFormData.configurableFormFields,
country: {
name: 'Pakistan',
code: 'PK',
},
},
},
},
);
});
it(' registration api failure when api rate limit hits', () => {
const action = {
type: REGISTER_FORM_VALIDATIONS.FAILURE,
};
expect(reducer(defaultState, action)).toEqual(
{
...defaultState,
validationApiRateLimited: true,
validations: null,
},
);
});
it('should clear username suggestions', () => {
const state = {
...defaultState,
@@ -92,6 +190,21 @@ describe('Registration Reducer Tests', () => {
expect(reducer(state, action)).toEqual({ ...defaultState });
});
it('should take back data during form reset', () => {
const state = {
...defaultState,
shouldBackupState: true,
};
const action = {
type: BACKUP_REGISTRATION_DATA.BASE,
};
expect(reducer(state, action)).toEqual({
...defaultState,
shouldBackupState: true,
});
});
it('should not reset username suggestions and fields data during form reset', () => {
const state = {
...defaultState,

View File

@@ -97,8 +97,7 @@ export function validateCountryField(value, countryList, errorMessage) {
// evaluated and set its value as a valid value.
const selectedCountry = countryList.find(
(country) => (
// When translations apply extra space added in country value so we should
// trim that.
// When translations are applied, extra space added in country value, so we should trim that.
country[COUNTRY_DISPLAY_KEY].toLowerCase().trim() === normalizedValue
|| country[COUNTRY_CODE_KEY].toLowerCase().trim() === normalizedValue
),

View File

@@ -1,175 +1,69 @@
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon, IconButton } from '@edx/paragon';
import { ExpandLess, ExpandMore } from '@edx/paragon/icons';
import { FormAutosuggest, FormAutosuggestOption, FormControlFeedback } from '@edx/paragon';
import PropTypes from 'prop-types';
import { FormGroup } from '../../common-components';
import { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY } from '../data/constants';
import messages from '../messages';
const CountryField = (props) => {
const { countryList, selectedCountry } = props;
const dropdownRef = useRef(null);
const { formatMessage } = useIntl();
const [errorMessage, setErrorMessage] = useState(props.errorMessage);
const [dropDownItems, setDropDownItems] = useState([]);
const [displayValue, setDisplayValue] = useState('');
const [trailingIcon, setTrailingIcon] = useState(null);
const onBlurHandler = (event, itemClicked = false, countryName = '') => {
const { name } = event.target;
const relatedName = event.relatedTarget ? event.relatedTarget.name : '';
// For a better user experience, do not validate when focus out from 'country' field
// and focus on 'countryItem' or 'countryExpand' button.
if ((relatedName === 'countryItem' || relatedName === 'countryExpand') && name === 'country') {
return;
}
const countryValue = itemClicked ? countryName : displayValue;
if (props.onBlurHandler) {
props.onBlurHandler({ target: { name: 'country', value: countryValue } });
}
setTrailingIcon(<ExpandMoreButton />);
setDropDownItems([]);
const handleSelected = (value) => {
if (props.onBlurHandler) { props.onBlurHandler({ target: { name: 'country', value } }); }
};
const getDropdownItems = (countryToFind = null) => {
let updatedCountryList = countryList;
if (countryToFind) {
updatedCountryList = countryList.filter(
(option) => (option.name.toLowerCase().includes(countryToFind.toLowerCase())),
);
const onBlurHandler = (event) => {
// Do not run validations when drop-down arrow is clicked
if (event.relatedTarget && event.relatedTarget.className.includes('pgn__form-autosuggest__icon-button')) {
return;
}
return updatedCountryList.map((country) => {
const countryName = country[COUNTRY_DISPLAY_KEY];
return (
<button
type="button"
name="countryItem"
className="dropdown-item data-hj-suppress"
value={countryName}
key={country[COUNTRY_CODE_KEY]}
onClick={(event) => onBlurHandler(event, true, countryName)}
/* This event will prevent the blur event to be fired,
as blur event is having higher priority than click event and restricts the click event.
*/
onMouseDown={(event) => event.preventDefault()}
>
{countryName.length > 30 ? countryName.substring(0, 30).concat('...') : countryName}
</button>
);
});
if (props.onBlurHandler) { props.onBlurHandler(event); }
};
const onFocusHandler = (event) => {
const { name, value } = event.target;
setDropDownItems(getDropdownItems(name === 'country' ? value : displayValue));
setTrailingIcon(<ExpandLessButton />);
setErrorMessage('');
if (props.onFocusHandler) { props.onFocusHandler(event); }
};
const onChangeHandler = (event) => {
const filteredItems = getDropdownItems(event.target.value);
setDropDownItems(filteredItems);
setDisplayValue(event.target.value);
if (props.onChangeHandler) { props.onChangeHandler(event, { countryCode: '', displayValue: event.target.value }); }
};
const handleOnClickOutside = () => {
setTrailingIcon(<ExpandMoreButton />);
setDropDownItems([]);
};
const handleExpandMore = () => {
setTrailingIcon(<ExpandLessButton />);
setDropDownItems(getDropdownItems());
};
const handleExpandLess = () => {
setTrailingIcon(<ExpandMoreButton />);
setDropDownItems([]);
};
const ExpandMoreButton = () => (
<IconButton
name="countryExpand"
size="sm"
variant="secondary"
alt="expand-more"
className="expand-more"
iconAs={Icon}
src={ExpandMore}
onBlur={() => {}}
onClick={handleExpandMore}
onFocus={() => {}}
/>
);
const ExpandLessButton = () => (
<IconButton
name="countryExpand"
size="sm"
variant="secondary"
alt="expand-less"
className="expand-less"
iconAs={Icon}
src={ExpandLess}
onBlur={() => {}}
onClick={handleExpandLess}
onFocus={() => {}}
/>
);
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
handleOnClickOutside();
}
};
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, []);
useEffect(() => {
if (!trailingIcon) {
setTrailingIcon(<ExpandMoreButton />);
const onChangeHandler = (value) => {
if (props.onChangeHandler) {
props.onChangeHandler({ target: { name: 'country' } }, { countryCode: '', displayValue: value });
}
}, [trailingIcon]);
};
useEffect(() => {
if (selectedCountry.displayValue) {
setDisplayValue(selectedCountry.displayValue);
}
}, [selectedCountry]);
useEffect(() => {
setErrorMessage(props.errorMessage);
}, [props.errorMessage]);
const getCountryList = () => countryList.map((country) => (
<FormAutosuggestOption key={country[COUNTRY_CODE_KEY]}>
{country[COUNTRY_DISPLAY_KEY]}
</FormAutosuggestOption>
));
return (
<div ref={dropdownRef} className="mb-4">
<FormGroup
as="input"
name="country"
autoComplete="chrome-off"
className="mb-0"
<div className="mb-4">
<FormAutosuggest
floatingLabel={formatMessage(messages['registration.country.label'])}
trailingElement={trailingIcon}
value={displayValue}
errorMessage={errorMessage}
handleChange={onChangeHandler}
handleBlur={onBlurHandler}
handleFocus={onFocusHandler}
/>
<div className="dropdown-container">
{ dropDownItems?.length > 0 ? dropDownItems : null }
</div>
aria-label="form autosuggest"
name="country"
value={selectedCountry.displayValue || ''}
onSelected={(value) => handleSelected(value)}
onFocus={(e) => onFocusHandler(e)}
onBlur={(e) => onBlurHandler(e)}
onChange={(value) => onChangeHandler(value)}
>
{getCountryList()}
</FormAutosuggest>
{props.errorMessage !== '' && (
<FormControlFeedback
key="error"
className="form-text-size"
hasIcon={false}
feedback-for="country"
type="invalid"
>
{props.errorMessage}
</FormControlFeedback>
)}
</div>
);
};

View File

@@ -19,8 +19,8 @@ const EmailField = (props) => {
const renderEmailFeedback = () => {
if (emailSuggestion.type === 'error') {
return (
<Alert variant="danger" className="email-error-alert mt-1" icon={Error}>
<span className="alert-text">
<Alert variant="danger" className="email-suggestion-alert-error mt-1" icon={Error}>
<span className="email-suggestion__text">
{formatMessage(messages['did.you.mean.alert.text'])}{' '}
<Alert.Link
href="#"
@@ -28,7 +28,7 @@ const EmailField = (props) => {
onClick={handleSuggestionClick}
>
{emailSuggestion.suggestion}
</Alert.Link>?<Icon src={Close} className="alert-close" onClick={handleOnClose} tabIndex="0" />
</Alert.Link>?<Icon src={Close} className="email-suggestion__close" onClick={handleOnClose} tabIndex="0" />
</span>
</Alert>
);
@@ -39,7 +39,7 @@ const EmailField = (props) => {
<Alert.Link
href="#"
name="email"
className="email-warning-alert-link"
className="email-suggestion-alert-warning"
onClick={handleSuggestionClick}
>
{emailSuggestion.suggestion}

View File

@@ -48,7 +48,7 @@ const HonorCode = (props) => {
return (
<div id="honor-code" className="micro text-muted">
<Form.Checkbox
className="opt-checkbox mt-1"
className="form-field--checkbox mt-1"
id="honor-code"
checked={value}
name="honor_code"

View File

@@ -16,7 +16,7 @@ const TermsOfService = (props) => {
return (
<div id="terms-of-service" className="micro text-muted">
<Form.Checkbox
className="opt-checkbox mt-1"
className="form-field--checkbox mt-1"
id="tos"
checked={value}
name="terms_of_service"

View File

@@ -18,14 +18,14 @@ const UsernameField = (props) => {
let iconButton = null;
const suggestedUsernames = () => (
<div className={className}>
<span className="text-gray username-suggestion-label">{formatMessage(messages['registration.username.suggestion.label'])}</span>
<div className="scroll-suggested-username">
<span className="text-gray username-suggestion--label">{formatMessage(messages['registration.username.suggestion.label'])}</span>
<div className="username-scroll-suggested--form-field">
{usernameSuggestions.map((username, index) => (
<Button
type="button"
name="username"
variant="outline-dark"
className="username-suggestion data-hj-suppress"
className="username-suggestions--chip data-hj-suppress"
autoComplete={props.autoComplete}
key={`suggestion-${index.toString()}`}
onClick={(e) => handleSuggestionClick(e, 'username', username)}
@@ -38,12 +38,12 @@ const UsernameField = (props) => {
</div>
);
if (usernameSuggestions.length > 0 && errorMessage && props.value === ' ') {
className = 'suggested-username-with-error';
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="suggested-username-close-button" />;
className = 'username-suggestions__error';
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />;
suggestedUsernameDiv = suggestedUsernames();
} else if (usernameSuggestions.length > 0 && props.value === ' ') {
className = 'suggested-username';
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="suggested-username-close-button" />;
className = 'username-suggestions';
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />;
suggestedUsernameDiv = suggestedUsernames();
} else if (usernameSuggestions.length > 0 && errorMessage) {
suggestedUsernameDiv = suggestedUsernames();

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import { sendPageEvent } from '@edx/frontend-platform/analytics';
import {
configure, getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
@@ -13,7 +12,9 @@ import { Router } from 'react-router-dom';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
import {
AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
} from '../../data/constants';
import {
backupRegistrationFormBegin,
clearUsernameSuggestions,
@@ -27,15 +28,15 @@ import {
import RegistrationFailureMessage from '../RegistrationFailure';
import RegistrationPage from '../RegistrationPage';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(),
}));
jest.mock('@edx/frontend-platform/i18n', () => ({
...jest.requireActual('@edx/frontend-platform/i18n'),
getLocale: jest.fn(),
}));
analytics.sendTrackEvent = jest.fn();
analytics.sendPageEvent = jest.fn();
const IntlRegistrationPage = injectIntl(RegistrationPage);
const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage);
const mockStore = configureStore();
@@ -120,8 +121,8 @@ describe('RegistrationPage', () => {
registrationPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } });
registrationPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } });
registrationPage.find('input#country').simulate('change', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input#country').simulate('blur', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input[name="country"]').simulate('change', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: payload.country, name: 'country' } });
if (!isThirdPartyAuth) {
registrationPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } });
@@ -131,7 +132,7 @@ describe('RegistrationPage', () => {
const ssoProvider = {
id: 'oa2-apple-id',
name: 'Apple',
iconClass: null,
iconClass: 'apple',
iconImage: 'https://openedx.devstack.lms/logo.png',
loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard',
};
@@ -306,7 +307,7 @@ describe('RegistrationPage', () => {
registrationPage.find('input#password').simulate('blur', { target: { value: '', name: 'password' } });
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
registrationPage.find('input#country').simulate('blur', { target: { value: '', name: 'country' } });
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country);
});
@@ -331,7 +332,7 @@ describe('RegistrationPage', () => {
it('should run validations for focused field on form submission', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#country').simulate('focus');
registrationPage.find('input[name="country"]').simulate('focus');
registrationPage.find('button.btn-brand').simulate('click');
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country);
@@ -346,6 +347,15 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('#email-warning').text()).toEqual('Did you mean: john@hotmail.com?');
});
it('should click on email suggestions for common service provider domain typos', () => {
store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#email').simulate('change', { target: { value: 'john@yopmail.com', name: 'email' } });
registrationPage.find('input#email').simulate('blur');
registrationPage.find('.email-suggestion-alert-warning').first().simulate('click');
expect(registrationPage.find('input#email').props().value).toEqual('john@hotmail.com');
});
it('should give error for common top level domain mistakes', () => {
store.dispatch = jest.fn(store.dispatch);
@@ -384,7 +394,32 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('input#username').prop('value')).toEqual('test-user');
});
it('should remove extra character if username is more than 30 character long', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#username').simulate('change', { target: { value: 'why_this_is_not_valid_username_', name: 'username' } });
expect(registrationPage.find('input#username').prop('value')).toEqual('');
});
it('should give error with suggestion for common top level domain mistakes', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#email').simulate('change', { target: { value: 'ahtesham@hotmail', name: 'email' } });
registrationPage.find('input#email').simulate('blur');
const receievedMessage = 'Did you mean ahtesham@hotmail.com?';
expect(registrationPage.find('.email-suggestion__text').text()).toEqual(receievedMessage);
});
it('should call backend validation api for password validation', () => {
store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#password').simulate('change', { target: { value: 'aziz194@', name: 'password' } });
registrationPage.find('input#password').simulate('blur');
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({
form_field_key: 'password', email: '', name: '', username: '', password: 'aziz194@',
}));
});
// ******** test field focus in functionality ********
it('should clear field related error messages on input field Focus', () => {
@@ -408,7 +443,7 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy();
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country);
registrationPage.find('input#country').simulate('focus');
registrationPage.find('input[name="country"]').simulate('focus');
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
});
@@ -524,7 +559,7 @@ describe('RegistrationPage', () => {
});
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find('div.opt-checkbox').length).toEqual(1);
expect(registrationPage.find('div.form-field--checkbox').length).toEqual(1);
mergeConfig({
MARKETING_EMAILS_OPT_IN: '',
@@ -622,7 +657,7 @@ describe('RegistrationPage', () => {
});
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find('button.username-suggestion').length).toEqual(3);
expect(registrationPage.find('button.username-suggestions--chip').length).toEqual(3);
});
it('should show username suggestions when full name is populated', () => {
@@ -641,7 +676,26 @@ describe('RegistrationPage', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
expect(registrationPage.find('button.username-suggestion').length).toEqual(3);
expect(registrationPage.find('button.username-suggestions--chip').length).toEqual(3);
});
it('should click on username suggestions when full name is populated', () => {
store = mockStore({
...initialState,
register: {
...initialState.register,
usernameSuggestions: ['test_1', 'test_12', 'test_123'],
registrationFormData: {
...registrationFormData,
username: ' ',
},
},
});
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
registrationPage.find('.username-suggestions--chip').first().simulate('click');
expect(registrationPage.find('input#username').props().value).toEqual('test_1');
});
it('should clear username suggestions when close icon is clicked', () => {
@@ -660,7 +714,7 @@ describe('RegistrationPage', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
registrationPage.find('button.suggested-username-close-button').at(0).simulate('click');
registrationPage.find('button.username-suggestions__close__button').at(0).simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
});
@@ -803,14 +857,35 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find(`button#${ssoProvider.id}`).find('span').text()).toEqual(ssoProvider.name);
expect(registrationPage.find(`button#${ssoProvider.id}`).hasClass(`btn-tpa btn-${ssoProvider.id}`)).toEqual(true);
});
it('should render icon if icon classes are missing in providers', () => {
ssoProvider.iconClass = null;
store = mockStore({
...initialState,
commonComponents: {
...initialState.commonComponents,
thirdPartyAuthContext: {
...initialState.commonComponents.thirdPartyAuthContext,
providers: [ssoProvider],
},
thirdPartyAuthApiStatus: COMPLETE_STATE,
},
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find(`button#${ssoProvider.id}`).find('div').find('span').hasClass('pgn__icon')).toEqual(true);
});
it('should render tpa button for tpa_hint id matching one of the secondary providers', () => {
secondaryProviders.skipHintedLogin = true;
store = mockStore({
@@ -826,8 +901,7 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/register'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
secondaryProviders.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.registerUrl);
@@ -848,8 +922,7 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?next=/dashboard&tpa_hint=invalid' };
ssoProvider.iconImage = null;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find(`button#${ssoProvider.id}`).find('span#provider-name').text()).toEqual(expectedMessage);
@@ -871,14 +944,9 @@ describe('RegistrationPage', () => {
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationFormBegin({ ...registrationFormData }));
});
it('should render cookie banner', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find(<CookiePolicyBanner />)).toBeTruthy();
});
it('should send page event when register page is rendered', () => {
mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
});
it('should populate form with pipeline user details', () => {
@@ -918,7 +986,7 @@ describe('RegistrationPage', () => {
});
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find('input#country').props().value).toEqual('Pakistan');
expect(registrationPage.find('input[name="country"]').props().value).toEqual('Pakistan');
});
it('should display error message based on the error code returned by API', () => {
@@ -964,14 +1032,14 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('input#username').props().value).toEqual('john_doe');
expect(registrationPage.find('input#email').props().value).toEqual('john.doe@yopmail.com');
expect(registrationPage.find('input#password').props().value).toEqual('password1');
expect(registrationPage.find('.email-warning-alert-link').first().text()).toEqual('john.doe@hotmail.com');
expect(registrationPage.find('.email-suggestion-alert-warning').first().text()).toEqual('john.doe@hotmail.com');
});
it('should set country in component state when form is translated used i18n', () => {
getLocale.mockImplementation(() => ('ar-ae'));
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#country').simulate('focus');
registrationPage.find('input[name="country"]').simulate('click');
registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: 'أفغانستان ', name: 'countryItem' } });
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
});
@@ -994,18 +1062,6 @@ describe('RegistrationPage', () => {
registrationPage.find('input#email').simulate('change', { target: { value: 'a@gmail.com', name: 'email' } });
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeFalsy();
});
it('should set country in component state when form is translated using browser translations', () => {
getLocale.mockImplementation(() => ('en-us'));
store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#country').simulate('focus');
registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: undefined, name: undefined, parentElement: { parentElement: { value: 'Afghanistan' } } } });
expect(registrationPage.find('input#country').props().value).toEqual('Afghanistan');
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
});
});
describe('Test Configurable Fields', () => {
@@ -1034,6 +1090,7 @@ describe('RegistrationPage', () => {
});
it('should submit form with fields returned by backend in payload', () => {
getLocale.mockImplementation(() => ('en-us'));
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
store = mockStore({
...initialState,
@@ -1110,7 +1167,6 @@ describe('RegistrationPage', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#email').simulate('change', { target: { value: 'test1@gmail.com', name: 'email' } });
registrationPage.find('input#confirm_email').simulate('blur', { target: { value: 'test2@gmail.com', name: 'confirm_email' } });
expect(registrationPage.find('div#confirm_email-error').text()).toEqual('The email addresses do not match.');
});
@@ -1135,16 +1191,6 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('#profession-error').last().text()).toEqual(professionError);
});
it('should not remove errors from form fields when country is selected by clicking on expand button', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('button.btn-brand').simulate('click');
expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy();
registrationPage.find('button.expand-more').simulate('click');
registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: 'Pakistan', name: 'countryItem' } });
expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy();
});
it('should check TOS and honor code fields if they exist when auto submitting register form', () => {
getLocale.mockImplementation(() => ('en-us'));
store = mockStore({
@@ -1278,5 +1324,16 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
expect(registrationPage.find('div.alert').first().text()).toContain('An error occured');
});
it('should not run country field validation when onBlur is fired by drop-down arrow icon click', () => {
getLocale.mockImplementation(() => ('en-us'));
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input[name="country"]').simulate('blur', {
target: { value: '', name: 'country' },
relatedTarget: { type: 'button', className: 'btn-icon pgn__form-autosuggest__icon-button' },
});
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
});
});
});

View File

@@ -16,12 +16,6 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { Redirect } from 'react-router-dom';
import { BaseComponent } from '../base-component';
import { PasswordField } from '../common-components';
import {
LETTER_REGEX, LOGIN_PAGE, NUMBER_REGEX, RESET_PAGE,
} from '../data/constants';
import { getAllPossibleQueryParams, updatePathWithQueryParams, windowScrollTo } from '../data/utils';
import { resetPassword, validateToken } from './data/actions';
import {
FORM_SUBMISSION_ERROR, PASSWORD_RESET_ERROR, PASSWORD_VALIDATION_ERROR, TOKEN_STATE,
@@ -30,6 +24,12 @@ import { resetPasswordResultSelector } from './data/selectors';
import { validatePassword } from './data/service';
import messages from './messages';
import ResetPasswordFailure from './ResetPasswordFailure';
import { BaseComponent } from '../base-component';
import { PasswordField } from '../common-components';
import {
LETTER_REGEX, LOGIN_PAGE, NUMBER_REGEX, RESET_PAGE,
} from '../data/constants';
import { getAllPossibleQueryParams, updatePathWithQueryParams, windowScrollTo } from '../data/utils';
const ResetPasswordPage = (props) => {
const { formatMessage } = useIntl();
@@ -148,7 +148,7 @@ const ResetPasswordPage = (props) => {
const { token } = props.match.params;
if (token) {
props.validateToken(token);
return <Spinner animation="border" variant="primary" className="centered-align-spinner" />;
return <Spinner animation="border" variant="primary" className="spinner--position-centered" />;
}
} else if (props.status === PASSWORD_RESET_ERROR) {
return <Redirect to={updatePathWithQueryParams(RESET_PAGE)} />;
@@ -198,7 +198,7 @@ const ResetPasswordPage = (props) => {
name="submit-new-password"
type="submit"
variant="brand"
className="stateful-button-width"
className="reset-password--button"
state={props.status}
labels={{
default: formatMessage(messages['reset.password']),

View File

@@ -6,6 +6,7 @@ export const TOKEN_STATE = {
// password reset error codes
export const FORM_SUBMISSION_ERROR = 'form-submission-error';
export const PASSWORD_RESET_ERROR = 'password-reset-error';
export const SUCCESS = 'success';
export const PASSWORD_VALIDATION_ERROR = 'password-validation-failure';
export const PASSWORD_RESET = {

View File

@@ -1,8 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import * as auth from '@edx/frontend-platform/auth';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { createMemoryHistory } from 'history';
@@ -11,8 +9,10 @@ import { MemoryRouter, Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { LOGIN_PAGE } from '../../data/constants';
import { resetPassword } from '../data/actions';
import { PASSWORD_RESET, TOKEN_STATE } from '../data/constants';
import { resetPassword, validateToken } from '../data/actions';
import {
PASSWORD_RESET, PASSWORD_RESET_ERROR, SUCCESS, TOKEN_STATE,
} from '../data/constants';
import ResetPasswordPage from '../ResetPasswordPage';
jest.mock('@edx/frontend-platform/auth');
@@ -69,11 +69,13 @@ describe('ResetPasswordPage', () => {
},
});
auth.getHttpClient = jest.fn(() => ({
post: async () => ({
data: {},
catch: () => {},
}),
jest.mock('@edx/frontend-platform/auth', () => ({
getHttpClient: jest.fn(() => ({
post: async () => ({
data: {},
catch: () => {},
}),
})),
}));
store.dispatch = jest.fn(store.dispatch);
@@ -157,9 +159,65 @@ describe('ResetPasswordPage', () => {
// ******** miscellaneous tests ********
it('check cookie rendered', () => {
it('should call validation on password field when blur event fires', () => {
const resetPasswordPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
expect(resetPasswordPage.find(<CookiePolicyBanner />)).toBeTruthy();
const expectedText = 'Password criteria has not been metPassword must contain at least 8 characters, at least one letter, and at least one number';
resetPasswordPage.find('input#newPassword').simulate('change', { target: { value: 'aziz156', name: 'newPassword' } });
resetPasswordPage.find('input#newPassword').simulate('blur', { target: { value: 'aziz156', name: 'newPassword' } });
expect(resetPasswordPage.find('div[feedback-for="newPassword"]').text()).toEqual(expectedText);
});
it('should not call validation when typing and click on show icon button', async () => {
const resetPasswordPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
await act(async () => {
await resetPasswordPage.find('input#newPassword').simulate('change', { target: { value: 'aziz156@', name: 'newPassword' } });
await resetPasswordPage.find('button[aria-label="Show password"]').at(0).simulate('click');
await resetPasswordPage.find('button[aria-label="Hide password"]').at(0).simulate('blur');
});
expect(resetPasswordPage.find('div[feedback-for="newPassword"]').exists()).toBe(false);
});
it('should call validation click on show icon button and then focus out', () => {
const resetPasswordPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
resetPasswordPage.find('input#newPassword').simulate('focus', { target: { value: 'aziz156@', name: 'newPassword' } });
resetPasswordPage.find('input#newPassword').simulate('blur', { relatedTarget: { name: 'password' } });
resetPasswordPage.find('button[aria-label="Show password"]').at(0).simulate('click');
expect(resetPasswordPage.find('div[feedback-for="newPassword"]').exists()).toBe(false);
});
it('show spinner when api call is pending', () => {
store.dispatch = jest.fn(store.dispatch);
props = {
status:
TOKEN_STATE.PENDING,
match: {
params: { token: '1c-bmjdkc-5e60e084cf8113048ca7' },
},
};
mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
expect(store.dispatch).toHaveBeenCalledWith(validateToken(props.match.params.token));
});
it('should redirect the user to Reset password email screen ', async () => {
props = {
status:
PASSWORD_RESET_ERROR,
};
mount(reduxWrapper(
<Router history={history}>
<IntlResetPasswordPage {...props} />
</Router>,
));
expect(history.location.pathname).toEqual('/reset');
});
it('should redirect the user to root url of the application ', async () => {
props = {
status: SUCCESS,
};
mount(reduxWrapper(
<Router history={history}>
<IntlResetPasswordPage {...props} />
</Router>,
));
expect(history.location.pathname).toEqual('/login');
});
it('show spinner during token validation', () => {

View File

@@ -0,0 +1,3 @@
.forgot-password--button {
min-width: 6rem;
}

View File

@@ -0,0 +1,3 @@
.login-button-width {
min-width: 6rem;
}

View File

@@ -0,0 +1,26 @@
.pp-page__button-width{
min-width: 6rem;
}
.pp-page__support-link {
font-size: 0.875rem;
font-weight: 400;
}
.pp-page__heading {
line-height: 1.75rem;
font-size: 1.375rem;
margin-bottom: 0.5rem;
font-weight: 700;
@include media-breakpoint-down('md') {
line-height: 1.5rem;
font-size: 1.125rem;
}
}
@media (max-width: 464px) {
.pp-page__support-link {
font-size: 0.688rem;
font-weight: normal;
line-height: 0.938rem;
}
}

View File

@@ -1,4 +1,4 @@
.card-list {
.recommendations-container__card-list {
padding-left: 0.0625rem;
padding-bottom: 0.125rem;
@include media-breakpoint-down(xl) {
@@ -6,7 +6,7 @@
overflow-y: hidden;
}
}
.recommendations-heading {
.recommendations-container__heading {
overflow-wrap: break-word;
}
@@ -59,7 +59,7 @@
position: absolute;
padding-bottom: 1rem !important;
}
.footer-text{
.pgn__card__footer-text{
font-weight: 400;
font-size: 0.75rem;
line-height: 1.25rem;

View File

@@ -1,3 +1,91 @@
.register-stateful-button-width {
.register-button {
min-width: 14.4rem;
}
.pgn__form-autosuggest__wrapper > .pgn__form-group {
margin-bottom: 0 !important;
}
.email-suggestion-alert-error {
padding: 0.5rem 1rem;
.email-suggestion__close {
float: right;
&:hover {
cursor: pointer;
}
}
.email-suggestion__text {
font-size: 0.75rem;
line-height: 1.25rem;
}
.alert-link {
color: $primary !important;
&:hover {
text-decoration: underline;
color: $info-700 !important;
}
}
}
.email-suggestion-alert-warning {
color: $info-500 !important;
&:hover {
text-decoration: underline;
color: $info-700 !important;
}
}
.form-field--checkbox {
.pgn__form-label {
font-size: 0.75rem;
line-height: 1.25rem;
}
margin-left: 3px;
}
.username-suggestions--chip {
padding: 1px 0.5rem;
margin: 0.25rem;
border-radius: 0.375rem;
line-height: 24px;
font-size: 12px;
font-weight: normal;
color: $primary-700;
}
.username-suggestion--label {
font-size: 0.75rem;
line-height: 1.25rem;
margin-right: 0.25rem;
}
.username-suggestions {
position: relative;
margin-top: -8.7%;
margin-left: 15px;
}
.username-suggestions__close__button {
right: 1rem;
position: absolute;
}
.username-suggestions__error {
position: relative;
margin-top: -13.7%;
margin-bottom: 11%;
margin-left: 15px;
}
.username-scroll-suggested--form-field {
width: 21rem;
white-space: nowrap;
overflow-x: auto;
display: inline-flex;
}

View File

@@ -0,0 +1,3 @@
.reset-password--button {
width: 12rem;
};

View File

@@ -2,6 +2,10 @@
@import "_base_component.scss";
@import "_registration.scss";
@import "_recommendations_page.scss";
@import "_reset_password.scss";
@import "_progressive_profiling_page.scss";
@import "_login_page.scss";
@import "_forgot_password.scss";
//
// ----------------------------
// #COLORS
@@ -26,7 +30,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
min-width: 6rem;
}
.centered-align-spinner {
.spinner--position-centered {
left: 0;
right: 0;
bottom: 0;
@@ -48,10 +52,6 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
width: 12rem;
}
.login-button-width {
min-width: 6rem;
}
.tpa-skeleton {
margin-bottom: 0.75rem;
}
@@ -109,7 +109,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
height: 36px;
color: $font-blue;
.icon-image {
.btn-tpa__image-icon{
background-color: transparent;
max-height: 24px;
max-width: 24px;
@@ -124,14 +124,14 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
padding-left: 1rem;
width: 14rem;
.icon-image {
.btn-tpa__image-icon {
background-color: transparent;
max-height: 24px;
max-width: 24px;
}
}
.font-container {
.btn-tpa__font-container {
background-color: $font-blue;
color: $white;
font-size: 11px;
@@ -160,7 +160,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
border-color: $google-blue;
background-color: $google-blue;
.icon-image {
.ibtn-tpa__image-icon {
margin-left: -6px;
max-height: 34px;
max-width: 34px;
@@ -181,7 +181,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
background-color: $apple-black;
font-size: 16px;
.icon-image {
.ibtn-tpa__image-icon {
max-height: 1.8em;
max-width: 2.0em;
}
@@ -323,33 +323,12 @@ select.form-control {
}
}
#forgotpassword-success-alert {
.alert-link {
color: $gray-700 !important;
}
}
.username-suggestion {
padding: 1px 0.5rem;
margin: 0.25rem;
border-radius: 0.375rem;
line-height: 24px;
font-size: 12px;
font-weight: normal;
color: $primary-700;
}
.username-suggestion-label {
font-size: 0.75rem;
line-height: 1.25rem;
margin-right: 0.25rem;
}
.yellow-border {
border: 2px solid $accent-b;
}
.institute-heading {
.institutions__heading {
color: $primary-700;
}
@@ -362,58 +341,6 @@ select.form-control {
text-decoration: none;
}
.dropdown-item:active {
background-color: $light-300;
}
.dropdown-container {
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15), 0px 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
max-height: 200px;
font-size: 1rem;
font-weight: normal;
line-height: 1.25rem;
overflow-y: scroll;
position: absolute;
background-color: $white;
width: 464px;
z-index: 100 !important;
}
.email-error-alert {
padding: 0.5rem 1rem;
.alert-close {
float: right;
&:hover {
cursor: pointer;
}
}
.alert-text {
font-size: 0.75rem;
line-height: 1.25rem;
}
.alert-link {
color: $primary !important;
&:hover {
text-decoration: underline;
color: $info-700 !important;
}
}
}
.email-warning-alert-link {
color: $info-500 !important;
&:hover {
text-decoration: underline;
color: $info-700 !important;
}
}
#forgot-password {
&:hover {
@@ -437,12 +364,12 @@ select.form-control {
font-size: 0.75rem;
}
.form-field .form-control:focus ~ .pgn__form-control-floating-label .pgn__form-control-floating-label-content {
.form-group__form-field .form-control:focus ~ .pgn__form-control-floating-label .pgn__form-control-floating-label-content {
font-size: 16px;
color: $primary-700;
}
.form-field .form-control:not([value='']):not(:focus) ~
.form-group__form-field .form-control:not([value='']):not(:focus) ~
.pgn__form-control-floating-label .pgn__form-control-floating-label-content {
font-size: 16px;
}
@@ -456,23 +383,6 @@ select.form-control {
line-height: 1.25rem;
}
.progressive-profiling-support {
font-size: 0.875rem;
font-weight: 400;
}
.pp-page-heading {
line-height: 1.75rem;
font-size: 1.375rem;
margin-bottom: 0.5rem;
font-weight: 700;
@include media-breakpoint-down('md') {
line-height: 1.5rem;
font-size: 1.125rem;
}
}
@media (min-width: 1024px) {
.mw-500 {
width: 500px;
@@ -503,14 +413,6 @@ select.form-control {
}
}
@media (max-width: 464px) {
.dropdown-container {
width: auto;
left: 0;
right: 0;
position: relative;
}
}
.alert {
p:last-child {
@@ -518,13 +420,6 @@ select.form-control {
}
}
@media screen and (min-width: 960px) {
html {
margin-right: calc(100% - 100vw);
margin-left: 0;
}
}
// Smaller than Extra Small (Mobile Screens)
@media (max-width: 464px) {
.btn-social {
@@ -546,54 +441,19 @@ select.form-control {
min-width: unset !important;
}
}
.progressive-profiling-support {
font-size: 0.688rem;
font-weight: normal;
line-height: 0.938rem;
}
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: $light-200;
}
.secondary-provider-link {
.institutions--provider-link {
font-weight: normal;
font-size: 0.875rem;
line-height: 1.5rem;
color: $primary-700
}
.opt-checkbox {
.pgn__form-label {
font-size: 0.75rem;
line-height: 1.25rem;
}
margin-left: 3px;
}
.suggested-username {
position: relative;
margin-top: -8.7%;
margin-left: 15px;
}
.suggested-username-close-button {
right: 1rem;
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;
}
.pgn__form-control-decorator-trailing {
right: 0 !important;
}