Compare commits
1 Commits
remove-com
...
mashal-m/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cabc565af3 |
1
.env
1
.env
@@ -28,7 +28,6 @@ ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
||||
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS=''
|
||||
ENABLE_IMAGE_LAYOUT=''
|
||||
# ***** Zendesk related keys *****
|
||||
ZENDESK_KEY=''
|
||||
ZENDESK_LOGO_URL=''
|
||||
|
||||
@@ -19,9 +19,6 @@ REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME='Your Platform Name Here'
|
||||
INFO_EMAIL='info@example.com'
|
||||
# ***** Features *****
|
||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS='true'
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
|
||||
# ***** Cookies *****
|
||||
SESSION_COOKIE_DOMAIN='localhost'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -25,5 +25,5 @@ Include a link to the sandbox for design changes or screenshot for before and af
|
||||
|
||||
#### Post-merge Checklist
|
||||
|
||||
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/2u-vanguards** to do it.
|
||||
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/vanguards** to do it.
|
||||
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,4 +18,3 @@ temp/babel-plugin-react-intl
|
||||
*~
|
||||
/temp
|
||||
/.vscode
|
||||
src/i18n/messages
|
||||
9
.tx/config
Normal file
9
.tx/config
Normal file
@@ -0,0 +1,9 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[o:open-edx:p:edx-platform:r:frontend-app-authn]
|
||||
file_filter = src/i18n/messages/<lang>.json
|
||||
source_file = src/i18n/transifex_input.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# The following users are the owners of all frontend-app-authn files
|
||||
* @openedx/2u-vanguards
|
||||
* @openedx/vanguards
|
||||
|
||||
26
Makefile
26
Makefile
@@ -1,3 +1,6 @@
|
||||
export TRANSIFEX_RESOURCE = frontend-app-authn
|
||||
transifex_langs = "ar,de,de_DE,es_419,fa_IR,fr,fr_CA,hi,it,it_IT,pt,pt_PT,ru,uk,zh_CN"
|
||||
|
||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||
i18n = ./src/i18n
|
||||
@@ -29,16 +32,33 @@ detect_changed_source_translations:
|
||||
# Checking for changed translations...
|
||||
git diff --exit-code $(i18n)
|
||||
|
||||
# Pushes translations to Transifex. You must run make extract_translations first.
|
||||
push_translations:
|
||||
# Pushing strings to Transifex...
|
||||
tx push -s
|
||||
# Fetching hashes from Transifex...
|
||||
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
||||
# Writing out comments to file...
|
||||
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
||||
# 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 $(ATLAS_OPTIONS) \
|
||||
&& atlas pull --filter=$(transifex_langs) \
|
||||
translations/paragon/src/i18n/messages:paragon \
|
||||
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
||||
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
|
||||
|
||||
$(intl_imports) paragon frontend-platform frontend-app-authn
|
||||
$(intl_imports) paragon frontend-app-authn
|
||||
endif
|
||||
|
||||
# This target is used by Travis.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
|
||||
@@ -115,10 +115,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_IMAGE_LAYOUT``
|
||||
- Enables the image layout feature within the authn. When set to True, this feature allows the inclusion of images in the base container layout. For more details on configuring this feature, please refer to the `Modifying base container <docs/how_tos/modifying_base_container.rst>`_.
|
||||
- ``true`` | ``''`` (empty strings are falsy)
|
||||
|
||||
|
||||
edX-specific Environment Variables
|
||||
==================================
|
||||
@@ -187,7 +183,7 @@ All community members are expected to follow the `Open edX Code of Conduct <http
|
||||
People
|
||||
======
|
||||
The assigned maintainers for this component and other project details may be
|
||||
found in `Backstage <https://backstage.openedx.org/catalog/default/group/2u-vanguards>`_. Backstage pulls this data from the ``catalog-info.yaml``
|
||||
found in `Backstage <https://backstage.openedx.org/catalog/default/group/vanguards>`_. Backstage pulls this data from the ``catalog-info.yaml``
|
||||
file in this repo.
|
||||
|
||||
Reporting Security Issues
|
||||
|
||||
@@ -13,6 +13,6 @@ metadata:
|
||||
annotations:
|
||||
openedx.org/arch-interest-groups: ""
|
||||
spec:
|
||||
owner: group:2u-vanguards
|
||||
owner: group:vanguards
|
||||
type: 'service'
|
||||
lifecycle: 'production'
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
========================================
|
||||
Modifying the Base Container in Authn
|
||||
========================================
|
||||
|
||||
The base container in Authn serves as the fundamental layout structure for rendering different components based on configurations. This document outlines the process for modifying the base container to accommodate changes or customize layouts as needed.
|
||||
|
||||
Understanding Base Container Versions
|
||||
--------------------------------------
|
||||
|
||||
The base container supports two main versions:
|
||||
|
||||
- **Default Layout:** The default layout is the standard layout used when specific configurations do not dictate otherwise.
|
||||
.. image:: ../images/default_layout.png
|
||||
- **Image Layout:** The image layout is an alternative layout option that can be enabled based on configurations.
|
||||
.. image:: ../images/image_layout.png
|
||||
|
||||
Enabling the Image Layout
|
||||
---------------------------
|
||||
|
||||
To activate the image layout feature, navigate to your .env file and update the configurations:
|
||||
|
||||
**Update Configuration**
|
||||
|
||||
Locate the ``ENABLE_IMAGE_LAYOUT`` parameter and set its value to ``true``. Additionally, ensure that the Image configuration settings are provided. Your overall configurations should resemble the following:
|
||||
|
||||
|
||||
.. code-block::
|
||||
|
||||
# ***** Image Layout Configuration *****
|
||||
ENABLE_IMAGE_LAYOUT = True # Set to True to enable image layout feature
|
||||
|
||||
# ***** Base Container Images *****
|
||||
BANNER_IMAGE_LARGE='' # Path to the large banner image
|
||||
BANNER_IMAGE_MEDIUM='' # Path to the medium-sized banner image
|
||||
BANNER_IMAGE_SMALL='' # Path to the small banner image
|
||||
BANNER_IMAGE_EXTRA_SMALL='' # Path to the extra-small banner image
|
||||
|
||||
|
||||
This allows for the customization and adaptation of the base container layout according to specific requirements.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 252 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
@@ -3,6 +3,6 @@
|
||||
|
||||
nick: Authn MFE
|
||||
oeps: {}
|
||||
owner: openedx/2u-vanguards
|
||||
owner: openedx/vanguards
|
||||
openedx-release:
|
||||
ref: master
|
||||
|
||||
12835
package-lock.json
generated
12835
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -33,53 +33,54 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-platform": "7.1.3",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.2",
|
||||
"@edx/frontend-platform": "^5.5.4",
|
||||
"@openedx/paragon": "21.11.3",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@openedx/paragon": "^22.1.1",
|
||||
"@optimizely/react-sdk": "^2.9.1",
|
||||
"@redux-devtools/extension": "3.3.0",
|
||||
"@redux-devtools/extension": "3.2.5",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"algoliasearch": "^4.14.3",
|
||||
"algoliasearch-helper": "^3.14.0",
|
||||
"classnames": "2.5.1",
|
||||
"core-js": "3.36.1",
|
||||
"classnames": "2.3.2",
|
||||
"core-js": "3.32.0",
|
||||
"fastest-levenshtein": "1.0.16",
|
||||
"form-urlencoded": "6.1.4",
|
||||
"form-urlencoded": "6.1.0",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "7.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-loading-skeleton": "3.4.0",
|
||||
"react-loading-skeleton": "3.3.1",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router": "6.22.3",
|
||||
"react-router-dom": "6.22.3",
|
||||
"react-router": "6.15.0",
|
||||
"react-router-dom": "6.15.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.2.0",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-mock-store": "1.5.4",
|
||||
"redux-saga": "1.3.0",
|
||||
"redux-saga": "1.2.3",
|
||||
"redux-thunk": "2.4.2",
|
||||
"regenerator-runtime": "0.14.1",
|
||||
"regenerator-runtime": "0.14.0",
|
||||
"reselect": "4.1.8",
|
||||
"universal-cookie": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@openedx/frontend-build": "13.0.19",
|
||||
"@edx/reactifex": "1.1.0",
|
||||
"@openedx/frontend-build": "13.1.4",
|
||||
"babel-plugin-formatjs": "10.5.14",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
||||
"babel-plugin-formatjs": "10.5.10",
|
||||
"enzyme": "3.11.0",
|
||||
"eslint-plugin-import": "2.28.0",
|
||||
"glob": "7.2.3",
|
||||
"history": "5.3.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "29.7.0",
|
||||
"jest": "29.6.2",
|
||||
"react-test-renderer": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<title><%= (process.env.SITE_NAME && process.env.SITE_NAME != 'null') ? 'Authentication | ' + process.env.SITE_NAME : 'Authentication' %></title>
|
||||
<title>Authn | <%= process.env.SITE_NAME %></title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon"/>
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import React from 'react';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index';
|
||||
|
||||
describe('Default Layout tests', () => {
|
||||
it('should display the form passed as a child in SmallScreenLayout', () => {
|
||||
render(
|
||||
const smallScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<DefaultSmallLayout />
|
||||
<form aria-label="form">
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
</div>
|
||||
</IntlProvider>,
|
||||
);
|
||||
expect(screen.getByRole('form')).toBeDefined();
|
||||
expect(smallScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should display the form passed as a child in MediumScreenLayout', () => {
|
||||
render(
|
||||
const mediumScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<DefaultMediumLayout />
|
||||
<form aria-label="form">
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
</div>
|
||||
</IntlProvider>,
|
||||
);
|
||||
expect(screen.getByRole('form')).toBeDefined();
|
||||
expect(mediumScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should display the form passed as a child in LargeScreenLayout', () => {
|
||||
render(
|
||||
const largeScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<DefaultLargeLayout />
|
||||
<form aria-label="form">
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
</div>
|
||||
</IntlProvider>,
|
||||
);
|
||||
expect(screen.getByRole('form')).toBeDefined();
|
||||
expect(largeScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const LargeLayout = ({ fullName }) => {
|
||||
const LargeLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
@@ -20,7 +20,7 @@ const LargeLayout = ({ fullName }) => {
|
||||
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
|
||||
<div>
|
||||
<h1 className="welcome-to-platform data-hj-suppress">
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })}
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="complete-your-profile">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
@@ -43,7 +43,7 @@ const LargeLayout = ({ fullName }) => {
|
||||
};
|
||||
|
||||
LargeLayout.propTypes = {
|
||||
fullName: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default LargeLayout;
|
||||
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const MediumLayout = ({ fullName }) => {
|
||||
const MediumLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
@@ -22,7 +22,7 @@ const MediumLayout = ({ fullName }) => {
|
||||
<div className="medium-yellow-line mt-5 mr-n2" />
|
||||
<div>
|
||||
<h1 className="h3 data-hj-suppress mw-320">
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })}
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="display-1">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
@@ -46,7 +46,7 @@ const MediumLayout = ({ fullName }) => {
|
||||
};
|
||||
|
||||
MediumLayout.propTypes = {
|
||||
fullName: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default MediumLayout;
|
||||
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const SmallLayout = ({ fullName }) => {
|
||||
const SmallLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
@@ -20,7 +20,7 @@ const SmallLayout = ({ fullName }) => {
|
||||
<div className="small-yellow-line mt-4.5" />
|
||||
<div>
|
||||
<h1 className="h5 data-hj-suppress">
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })}
|
||||
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
|
||||
</h1>
|
||||
<h2 className="h1">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
@@ -35,7 +35,7 @@ const SmallLayout = ({ fullName }) => {
|
||||
};
|
||||
|
||||
SmallLayout.propTypes = {
|
||||
fullName: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default SmallLayout;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
const messages = defineMessages({
|
||||
'welcome.to.platform': {
|
||||
id: 'welcome.to.platform',
|
||||
defaultMessage: 'Welcome to {siteName}, {fullName}!',
|
||||
defaultMessage: 'Welcome to {siteName}, {username}!',
|
||||
description: 'Welcome message that appears on progressive profile page',
|
||||
},
|
||||
'complete.your.profile.1': {
|
||||
|
||||
4
src/base-container/data/constants.js
Normal file
4
src/base-container/data/constants.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const IMAGE_LAYOUT = 'image-layout';
|
||||
const DEFAULT_LAYOUT = 'default-layout';
|
||||
|
||||
export { DEFAULT_LAYOUT, IMAGE_LAYOUT };
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { breakpoints } from '@openedx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -11,24 +10,40 @@ import {
|
||||
ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout,
|
||||
} from './components/image-layout';
|
||||
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
|
||||
import { DEFAULT_LAYOUT, IMAGE_LAYOUT } from './data/constants';
|
||||
|
||||
const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
|
||||
const enableImageLayout = getConfig().ENABLE_IMAGE_LAYOUT;
|
||||
const BaseContainer = ({ children, showWelcomeBanner, username }) => {
|
||||
const [baseContainerVersion, setBaseContainerVersion] = useState(DEFAULT_LAYOUT);
|
||||
|
||||
if (enableImageLayout) {
|
||||
useEffect(() => {
|
||||
const initRebrandExperiment = () => {
|
||||
if (window.experiments?.rebrandExperiment) {
|
||||
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
|
||||
} else {
|
||||
window.experiments = window.experiments || {};
|
||||
window.experiments.rebrandExperiment = {};
|
||||
window.experiments.rebrandExperiment.handleLoaded = () => {
|
||||
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
|
||||
};
|
||||
}
|
||||
};
|
||||
initRebrandExperiment();
|
||||
}, []);
|
||||
|
||||
if (baseContainerVersion === IMAGE_LAYOUT) {
|
||||
return (
|
||||
<div className="layout">
|
||||
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <ImageExtraSmallLayout />}
|
||||
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <ImageExtraSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <ImageSmallLayout />}
|
||||
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <ImageSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthMediumLayout fullName={fullName} /> : <ImageMediumLayout />}
|
||||
{showWelcomeBanner ? <AuthMediumLayout username={username} /> : <ImageMediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||
{showWelcomeBanner ? <AuthLargeLayout fullName={fullName} /> : <ImageLargeLayout />}
|
||||
{showWelcomeBanner ? <AuthLargeLayout username={username} /> : <ImageLargeLayout />}
|
||||
</MediaQuery>
|
||||
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
|
||||
{children}
|
||||
@@ -42,13 +57,13 @@ const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
|
||||
<div className="col-md-12 extra-large-screen-top-stripe" />
|
||||
<div className="layout">
|
||||
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthSmallLayout fullName={fullName} /> : <DefaultSmallLayout />}
|
||||
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <DefaultSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{showWelcomeBanner ? <AuthMediumLayout fullName={fullName} /> : <DefaultMediumLayout />}
|
||||
{showWelcomeBanner ? <AuthMediumLayout username={username} /> : <DefaultMediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||
{showWelcomeBanner ? <AuthLargeLayout fullName={fullName} /> : <DefaultLargeLayout />}
|
||||
{showWelcomeBanner ? <AuthLargeLayout username={username} /> : <DefaultLargeLayout />}
|
||||
</MediaQuery>
|
||||
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
|
||||
{children}
|
||||
@@ -60,13 +75,13 @@ const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
|
||||
|
||||
BaseContainer.defaultProps = {
|
||||
showWelcomeBanner: false,
|
||||
fullName: null,
|
||||
username: null,
|
||||
};
|
||||
|
||||
BaseContainer.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
showWelcomeBanner: PropTypes.bool,
|
||||
fullName: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
};
|
||||
|
||||
export default BaseContainer;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
|
||||
import BaseContainer from '../index';
|
||||
@@ -13,34 +12,32 @@ const LargeScreen = {
|
||||
};
|
||||
|
||||
describe('Base component tests', () => {
|
||||
it('should show default layout', () => {
|
||||
const { container } = render(
|
||||
it('should should default layout', () => {
|
||||
const baseContainer = mount(
|
||||
<IntlProvider locale="en">
|
||||
<BaseContainer>
|
||||
<div>Test Content</div>
|
||||
</BaseContainer>
|
||||
<BaseContainer />
|
||||
</IntlProvider>,
|
||||
LargeScreen,
|
||||
);
|
||||
|
||||
expect(container.querySelector('.banner__image')).toBeNull();
|
||||
expect(container.querySelector('.large-screen-svg-primary')).toBeDefined();
|
||||
expect(baseContainer.find('.banner__image').exists()).toBeFalsy();
|
||||
expect(baseContainer.find('.large-screen-svg-primary').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => {
|
||||
mergeConfig({
|
||||
ENABLE_IMAGE_LAYOUT: true,
|
||||
});
|
||||
it('[experiment] should show image layout for treatment group', () => {
|
||||
window.experiments = {
|
||||
rebrandExperiment: {
|
||||
variation: 'image-layout',
|
||||
},
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
const baseContainer = mount(
|
||||
<IntlProvider locale="en">
|
||||
<BaseContainer showWelcomeBanner={false}>
|
||||
<div>Test Content</div>
|
||||
</BaseContainer>
|
||||
<BaseContainer />
|
||||
</IntlProvider>,
|
||||
LargeScreen,
|
||||
);
|
||||
|
||||
expect(container.querySelector('.banner__image')).toBeDefined();
|
||||
expect(baseContainer.find('.banner__image').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ const PasswordField = (props) => {
|
||||
{props.errorMessage !== '' && (
|
||||
<Form.Control.Feedback key="error" className="form-text-size" hasIcon={false} feedback-for={props.name} type="invalid">
|
||||
{props.errorMessage}
|
||||
{props.showScreenReaderText && <span className="sr-only">{formatMessage(messages['password.sr.only.helping.text'])}</span>}
|
||||
<span className="sr-only">{formatMessage(messages['password.sr.only.helping.text'])}</span>
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
</Form.Group>
|
||||
@@ -153,7 +153,6 @@ PasswordField.defaultProps = {
|
||||
handleChange: () => {},
|
||||
handleErrorChange: null,
|
||||
showRequirements: true,
|
||||
showScreenReaderText: true,
|
||||
autoComplete: null,
|
||||
};
|
||||
|
||||
@@ -169,7 +168,6 @@ PasswordField.propTypes = {
|
||||
showRequirements: PropTypes.bool,
|
||||
value: PropTypes.string.isRequired,
|
||||
autoComplete: PropTypes.string,
|
||||
showScreenReaderText: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default PasswordField;
|
||||
|
||||
@@ -112,26 +112,6 @@ const messages = defineMessages({
|
||||
description: 'Select ticket form',
|
||||
defaultMessage: 'Please choose your request type:',
|
||||
},
|
||||
'registration.other.options.heading': {
|
||||
id: 'registration.other.options.heading',
|
||||
defaultMessage: 'Or register with:',
|
||||
description: 'A message that appears above third party auth providers i.e saml, google, facebook etc',
|
||||
},
|
||||
'institution.login.button': {
|
||||
id: 'institution.login.button',
|
||||
defaultMessage: 'Institution/campus credentials',
|
||||
description: 'shows institutions list',
|
||||
},
|
||||
'login.other.options.heading': {
|
||||
id: 'login.other.options.heading',
|
||||
defaultMessage: 'Or sign in with:',
|
||||
description: 'Text that appears above other sign in options like social auth buttons',
|
||||
},
|
||||
'enterprise.login.btn.text': {
|
||||
id: 'enterprise.login.btn.text',
|
||||
defaultMessage: 'Company or school credentials',
|
||||
description: 'Company or school login link text.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { render } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { PAGE_NOT_FOUND, REGISTER_EMBEDDED_PAGE } from '../../data/constants';
|
||||
import { REGISTER_EMBEDDED_PAGE } from '../../data/constants';
|
||||
import EmbeddedRegistrationRoute from '../EmbeddedRegistrationRoute';
|
||||
|
||||
import {
|
||||
@@ -27,10 +27,6 @@ const TestApp = () => (
|
||||
path={REGISTER_EMBEDDED_PAGE}
|
||||
element={<EmbeddedRegistrationRoute><span>Embedded Register Page</span></EmbeddedRegistrationRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={PAGE_NOT_FOUND}
|
||||
element={<span>Page not found</span>}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
@@ -49,13 +45,15 @@ describe('EmbeddedRegistrationRoute', () => {
|
||||
|
||||
it('should not render embedded register page if host query param is not available in the url', async () => {
|
||||
let embeddedRegistrationPage = null;
|
||||
|
||||
await act(async () => {
|
||||
const { container } = await render(routerWrapper());
|
||||
embeddedRegistrationPage = container;
|
||||
});
|
||||
|
||||
const renderedPage = embeddedRegistrationPage.querySelector('span');
|
||||
expect(renderedPage.textContent).toBe('Page not found');
|
||||
const spanElement = embeddedRegistrationPage.querySelector('span');
|
||||
|
||||
expect(spanElement).toBeNull();
|
||||
});
|
||||
|
||||
it('should render embedded register page if host query param is available in the url (embedded)', async () => {
|
||||
@@ -66,13 +64,15 @@ describe('EmbeddedRegistrationRoute', () => {
|
||||
};
|
||||
|
||||
let embeddedRegistrationPage = null;
|
||||
|
||||
await act(async () => {
|
||||
const { container } = await render(routerWrapper());
|
||||
embeddedRegistrationPage = container;
|
||||
});
|
||||
|
||||
const renderedPage = embeddedRegistrationPage.querySelector('span');
|
||||
expect(renderedPage).toBeTruthy();
|
||||
expect(renderedPage.textContent).toBe('Embedded Register Page');
|
||||
const spanElement = embeddedRegistrationPage.querySelector('span');
|
||||
|
||||
expect(spanElement).toBeTruthy();
|
||||
expect(spanElement.textContent).toBe('Embedded Register Page');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ const configuration = {
|
||||
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
|
||||
SHOW_REGISTRATION_LINKS: process.env.SHOW_REGISTRATION_LINKS !== 'false',
|
||||
ENABLE_IMAGE_LAYOUT: process.env.ENABLE_IMAGE_LAYOUT || false,
|
||||
// Links
|
||||
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
|
||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK || null,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import FieldRenderer from '../FieldRenderer';
|
||||
|
||||
@@ -28,14 +28,13 @@ describe('FieldRendererTests', () => {
|
||||
options: [['1997', '1997'], ['1998', '1998']],
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const input = container.querySelector('select#yob-field');
|
||||
const label = container.querySelector('label');
|
||||
fireEvent.change(input, { target: { value: 1997 } });
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('select#yob-field');
|
||||
field.simulate('change', { target: { value: 1997 } });
|
||||
|
||||
expect(input.type).toEqual('select-one');
|
||||
expect(label.textContent).toContain(fieldData.label);
|
||||
expect(value).toEqual('1997');
|
||||
expect(field.type()).toEqual('select');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('Year of Birth');
|
||||
expect(value).toEqual(1997);
|
||||
});
|
||||
|
||||
it('should return null if no options are provided for select field', () => {
|
||||
@@ -45,8 +44,8 @@ describe('FieldRendererTests', () => {
|
||||
name: 'yob-field',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
expect(container.innerHTML).toEqual('');
|
||||
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
expect(fieldRenderer.html()).toBeNull();
|
||||
});
|
||||
|
||||
it('should render textarea field', () => {
|
||||
@@ -56,13 +55,12 @@ describe('FieldRendererTests', () => {
|
||||
name: 'goals-field',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const input = container.querySelector('#goals-field');
|
||||
const label = container.querySelector('label');
|
||||
fireEvent.change(input, { target: { value: 'These are my goals.' } });
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('#goals-field').last();
|
||||
field.simulate('change', { target: { value: 'These are my goals.' } });
|
||||
|
||||
expect(input.type).toEqual(fieldData.type);
|
||||
expect(label.textContent).toContain('Why do you want to join this platform?');
|
||||
expect(field.type()).toEqual('textarea');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('Why do you want to join this platform?');
|
||||
expect(value).toEqual('These are my goals.');
|
||||
});
|
||||
|
||||
@@ -73,13 +71,12 @@ describe('FieldRendererTests', () => {
|
||||
name: 'company-field',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const input = container.querySelector('input#company-field');
|
||||
const label = container.querySelector('label');
|
||||
fireEvent.change(input, { target: { value: 'ABC' } });
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('#company-field').last();
|
||||
field.simulate('change', { target: { value: 'ABC' } });
|
||||
|
||||
expect(input.type).toEqual(fieldData.type);
|
||||
expect(label.textContent).toContain(fieldData.label);
|
||||
expect(field.type()).toEqual('input');
|
||||
expect(fieldRenderer.find('label').text()).toEqual('Company');
|
||||
expect(value).toEqual('ABC');
|
||||
});
|
||||
|
||||
@@ -90,13 +87,12 @@ describe('FieldRendererTests', () => {
|
||||
name: 'marketing-emails-opt-in-field',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const input = container.querySelector('input#marketing-emails-opt-in-field');
|
||||
const label = container.querySelector('label');
|
||||
fireEvent.click(input);
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
const field = fieldRenderer.find('input#marketing-emails-opt-in-field');
|
||||
field.simulate('change', { target: { checked: true, type: 'checkbox' } });
|
||||
|
||||
expect(input.type).toEqual(fieldData.type);
|
||||
expect(label.textContent).toContain(fieldData.label);
|
||||
expect(field.prop('type')).toEqual('checkbox');
|
||||
expect(fieldRenderer.find('label').text()).toEqual(fieldData.label);
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -105,8 +101,8 @@ describe('FieldRendererTests', () => {
|
||||
type: 'unknown',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
expect(container.innerHTML).toContain('');
|
||||
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
expect(fieldRenderer.html()).toBeNull();
|
||||
});
|
||||
|
||||
it('should run onBlur and onFocus functions for a field if given', () => {
|
||||
@@ -121,7 +117,7 @@ describe('FieldRendererTests', () => {
|
||||
functionValue = `${e.target.name} focussed`;
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
const fieldRenderer = mount(
|
||||
<FieldRenderer
|
||||
handleFocus={onFocus}
|
||||
handleBlur={onBlur}
|
||||
@@ -130,19 +126,19 @@ describe('FieldRendererTests', () => {
|
||||
onChangeHandler={changeHandler}
|
||||
/>,
|
||||
);
|
||||
const input = container.querySelector('#test-field');
|
||||
const field = fieldRenderer.find('#test-field').last();
|
||||
|
||||
fireEvent.focus(input);
|
||||
field.simulate('focus');
|
||||
expect(functionValue).toEqual('test-field focussed');
|
||||
|
||||
fireEvent.blur(input);
|
||||
field.simulate('blur');
|
||||
expect(functionValue).toEqual('test-field blurred');
|
||||
});
|
||||
|
||||
it('should render error message for required text fields', () => {
|
||||
const fieldData = { type: 'text', label: 'First Name', name: 'first-name-field' };
|
||||
|
||||
const { container } = render(
|
||||
const fieldRenderer = mount(
|
||||
<FieldRenderer
|
||||
isRequired
|
||||
fieldData={fieldData}
|
||||
@@ -151,7 +147,7 @@ describe('FieldRendererTests', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('Enter your first name');
|
||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Enter your first name');
|
||||
});
|
||||
|
||||
it('should render error message for required select fields', () => {
|
||||
@@ -159,7 +155,7 @@ describe('FieldRendererTests', () => {
|
||||
type: 'select', label: 'Preference', name: 'preference-field', options: [['a', 'Opt 1'], ['b', 'Opt 2']],
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
const fieldRenderer = mount(
|
||||
<FieldRenderer
|
||||
isRequired
|
||||
fieldData={fieldData}
|
||||
@@ -168,13 +164,13 @@ describe('FieldRendererTests', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('Select your preference');
|
||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Select your preference');
|
||||
});
|
||||
|
||||
it('should render error message for required textarea fields', () => {
|
||||
const fieldData = { type: 'textarea', label: 'Goals', name: 'goals-field' };
|
||||
|
||||
const { container } = render(
|
||||
const fieldRenderer = mount(
|
||||
<FieldRenderer
|
||||
isRequired
|
||||
fieldData={fieldData}
|
||||
@@ -183,13 +179,13 @@ describe('FieldRendererTests', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('Tell us your goals');
|
||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Tell us your goals');
|
||||
});
|
||||
|
||||
it('should render error message for required checkbox fields', () => {
|
||||
const fieldData = { type: 'checkbox', label: 'Honor Code', name: 'honor-code-field' };
|
||||
|
||||
const { container } = render(
|
||||
const fieldRenderer = mount(
|
||||
<FieldRenderer
|
||||
isRequired
|
||||
fieldData={fieldData}
|
||||
@@ -198,6 +194,6 @@ describe('FieldRendererTests', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(`#${fieldData.name}-error`).textContent).toEqual('You must agree to our Honor Code');
|
||||
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('You must agree to our Honor Code');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default [];
|
||||
41
src/i18n/index.jsx
Normal file
41
src/i18n/index.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { messages as paragonMessages } from '@openedx/paragon';
|
||||
|
||||
import arMessages from './messages/ar.json';
|
||||
import deMessages from './messages/de.json';
|
||||
import deDEMessages from './messages/de_DE.json';
|
||||
import es419Messages from './messages/es_419.json';
|
||||
import faIRMessages from './messages/fa_IR.json';
|
||||
import frMessages from './messages/fr.json';
|
||||
import frCAMessages from './messages/fr_CA.json';
|
||||
import hiMessages from './messages/hi.json';
|
||||
import itMessages from './messages/it.json';
|
||||
import itITMessages from './messages/it_IT.json';
|
||||
import ptMessages from './messages/pt.json';
|
||||
import ptPTMessages from './messages/pt_PT.json';
|
||||
import ruMessages from './messages/ru.json';
|
||||
import ukMessages from './messages/uk.json';
|
||||
import zhCNMessages from './messages/zh_CN.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
|
||||
const appMessages = {
|
||||
ar: arMessages,
|
||||
de: deMessages,
|
||||
'de-de': deDEMessages,
|
||||
'es-419': es419Messages,
|
||||
'fa-ir': faIRMessages,
|
||||
fr: frMessages,
|
||||
'fr-ca': frCAMessages,
|
||||
hi: hiMessages,
|
||||
it: itMessages,
|
||||
'it-it': itITMessages,
|
||||
pt: ptMessages,
|
||||
'pt-pt': ptPTMessages,
|
||||
ru: ruMessages,
|
||||
uk: ukMessages,
|
||||
'zh-cn': zhCNMessages,
|
||||
};
|
||||
|
||||
export default [
|
||||
paragonMessages,
|
||||
appMessages,
|
||||
];
|
||||
181
src/i18n/messages/ar.json
Normal file
181
src/i18n/messages/ar.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
|
||||
"institution.login.page.sub.heading": "اختر مؤسستك من القائمة أدناه",
|
||||
"logistration.sign.in": "تسجيل الدخول",
|
||||
"logistration.register": "التسجيل",
|
||||
"enterprisetpa.title.heading": "هل ترغب في تسجيل الدخول باستخدام بيانات {providerName} الخاصة بك؟",
|
||||
"enterprisetpa.login.button.text": "أرِني وسائل أخرى لتسجيل الدخول أو للتسجيل",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "أرني طرقًا أخرى لتسجيل الدخول",
|
||||
"sso.sign.in.with": "تسجيل الدخول باستخدام {providerName}",
|
||||
"sso.create.account.using": "إنشاء حساب باستخدام {providerName}",
|
||||
"show.password": "إظهار كلمة المرور",
|
||||
"hide.password": "اخفاء كلمة المرور",
|
||||
"one.letter": "حرف واحد",
|
||||
"one.number": "رقم واحد",
|
||||
"eight.characters": "8 رموز",
|
||||
"password.sr.only.helping.text": "يجب أن تحتوي كلمة المرور على الأقل 8 رموز، منها حرف واحد و رقم واحد على الأقل.",
|
||||
"tpa.alert.heading": "انتهينا تقريبا!",
|
||||
"login.third.party.auth.account.not.linked": "لقد نجحت في تسجيل الدخول إلى {currentProvider}، لكن حسابك على {currentProvider} غير موصول بأي حساب على {platformName}. لوصل حساباتك، سجّل الدخول الآن باستخدام كلمة مرورك على {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "لقد سجلت دخولك بنجاح إلى {currentProvider}! نحتاج فقط قليلاً بعدُ من المعلومات قبل أن تبدأ التعلم مع {platformName}.",
|
||||
"registration.using.tpa.form.heading": "إتمام إنشاء حسابك",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
|
||||
"forgot.password.page.heading": "إعادة ضبط كلمة المرور",
|
||||
"forgot.password.page.instructions": "رجاءً أدخل عنوان بريدك الإلكتروني أدناه وسنرسل إليك بريدًا به إرشادات بخصوص كيفية إعادة ضبط كلمة مرورك.",
|
||||
"forgot.password.page.invalid.email.message": "أدخل عنوان بريد إلكتروني صحيح",
|
||||
"forgot.password.page.email.field.label": "البريد الإلكتروني",
|
||||
"forgot.password.page.submit.button": "إرسال",
|
||||
"forgot.password.error.alert.title.": "لم نتمكن من الاتصال بك.",
|
||||
"forgot.password.error.message.title": "حدث خطأ ما.",
|
||||
"forgot.password.request.in.progress.message": "طلبك السابق قيد التنفيذ، يرجى المحاولة مرة أخرى بعد لحظات قليلة.",
|
||||
"forgot.password.empty.email.field.error": "أدخل بريدك الإلكتروني",
|
||||
"forgot.password.email.help.text": "عنوان البريد الإلكتروني الذي استخدمته للتسجيل في {platformName}",
|
||||
"confirmation.message.title": "تفقّد بريدك الإلكتروني",
|
||||
"confirmation.support.link": "اتصل بالدعم الفني",
|
||||
"need.help.sign.in.text": "هل تحتاج مساعدة في تسجيل الدخول؟",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"sign.in.text": "تسجيل الدخول",
|
||||
"extend.field.errors": "{emailError} أدناه.",
|
||||
"invalid.token.heading": "رابط إعادة ضبط كلمة المرور غير صالح",
|
||||
"invalid.token.error.message": "رابط إعادة ضبط كلمة المرور هذا غير صالح. قد يكون مستعمَلا من قبل. أدخل بريدك الإلكتروني أدناه لتلقي رابط جديد.",
|
||||
"token.validation.rate.limit.error.heading": "طلبات أكثر مما ينبغي",
|
||||
"token.validation.rate.limit.error": "حدث خطأ بسبب كثرة الطلبات. رجاءً حاول مرة أخرى بعد مضي بعض الوقت.",
|
||||
"token.validation.internal.sever.error.heading": "فشل في التحقق من صحة الشارة",
|
||||
"token.validation.internal.sever.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
||||
"internal.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
||||
"account.activation.error.message": "شي ما لم يسر على ما يرام، يرجى {supportLink} لحل هذه المشكلة.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "كونك مستخدمًا على {allowedDomain}، فإن عليك تسجيل الدخول باستخدام {tpaLink} الخاص بـ {allowedDomain} .",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "إن نسيت كلمة مرورك، {resetLink}",
|
||||
"account.locked.out.message.2": "لتكون في مأمن، يمكنك {resetLink} قبل تكرار المحاولة.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "اسم المستخدم أو البريد الإلكتروني أو كلمة المرور التي أدخلتها غير صحيحة. يرجى تكرار المحاولة أو {resetLink}.",
|
||||
"login.page.title": "تسجيل الدخول | {siteName}",
|
||||
"login.user.identity.label": "اسم المستخدم أو البريد الإلكتروني",
|
||||
"login.password.label": "كلمة المرور",
|
||||
"sign.in.button": "تسجيل الدخول",
|
||||
"forgot.password": "نسيت كلمة المرور",
|
||||
"institution.login.button": "بيانات المؤسسة / الجامعة",
|
||||
"institution.login.page.title": "تسجيل الدخول باستخدام بيانات المؤسسة / الجامعة",
|
||||
"login.other.options.heading": "أو قم بتسجيل الدخول باستخدام:",
|
||||
"non.compliant.password.title": "لقد غيرنا متطلبات أمان كلمة المرور مؤخرًا",
|
||||
"non.compliant.password.message": "كلمة مرورك الحالية لا تستسجيب لمتطلبات الأمان الجديدة. لقد أرسلنا للتو رسالة لإعادة ضبط كلمة المرور إلى عنوان البريد الإلكتروني المرتبط بهذا الحساب. شكرًا لك على مساعدتنا في الحفاظ على سلامة بياناتك.",
|
||||
"account.locked.out.message.1": "لحماية حسابك، تم إقفاله مؤقتًا. حاول مرة أخرى بعد 30 دقيقة.",
|
||||
"enterprise.login.btn.text": "بيانات الشركة أو المدرسة",
|
||||
"username.or.email.format.validation.less.chars.message": "يجب أن يحتوي اسم المستخدم أو البريد الإلكتروني على 3 أحرف على الأقل.",
|
||||
"email.validation.message": "أدخل اسم المستخدم أو البريد الإلكتروني الخاص بك",
|
||||
"password.validation.message": "لم يتم استيفاء معايير كلمة المرور",
|
||||
"account.activation.success.message.title": "نجح الأمر! لقد قمت بتفعيل حسابك.",
|
||||
"account.activation.success.message": "ستصلك الآن تحديثات وتنبيهات عبر البريد الإلكتروني منا تتعلق بالمساقات التي قمت بالتسجيل فيها. قم بتسجيل الدخول للمتابعة.",
|
||||
"account.activation.info.message": "هذا الحساب مفعَّل من قبل.",
|
||||
"account.activation.error.message.title": "لا يمكن تفعيل حسابك",
|
||||
"account.activation.support.link": "الاتصال بالدعم",
|
||||
"account.confirmation.success.message.title": "نجحت العملية! لقد أكدت بريدك الإلكتروني.",
|
||||
"account.confirmation.success.message": "سجل دخولك للمتابعة.",
|
||||
"account.confirmation.info.message": "هذا البريد الإلكتروني مؤكد من قبل.",
|
||||
"account.confirmation.error.message.title": "لا يمكن تأكيد بريدك الإلكتروني",
|
||||
"tpa.account.link": "حساب {provider}",
|
||||
"internal.server.error.message": "حدث خطأ ما. جرّب تحديث الصفحة أو تحقق من اتصالك بالانترنت.",
|
||||
"login.rate.limit.reached.message": "كثرت محاولات تسجيل الدخول الفاشلة. رجاءً أعد المحاولة لاحقًا.",
|
||||
"login.failure.header.title": "لم نتمكّن من تسجيل دخولك.",
|
||||
"contact.support.link": "اتصل بدعم {platformName}",
|
||||
"login.incorrect.credentials.error": "اسم المستخدم أو البريد الإلكتروني أو كلمة المرور التي أدخلتها غير صحيحة. حاول مرة اخرى.",
|
||||
"login.form.invalid.error.message": "رجاءً املأ الحقول أدناه.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "إعادة ضبط كلمه المرور",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "انقر هنا لإعادة ضبطها.",
|
||||
"password.security.nudge.title": "أمان كلمة المرور",
|
||||
"password.security.block.title": "مطلوب تغيير كلمة المرور",
|
||||
"password.security.nudge.body": "اكتشف نظامنا أن كلمة مرورك ضعيفة. ننصحك بتغييرها حتى يظل حسابك آمنًا.",
|
||||
"password.security.block.body": "اكتشف نظامنا أن كلمة مرورك صعيفة. غيّر كلمة مرورك حتى يظل حسابك آمنًا.",
|
||||
"password.security.close.button": "إغلاق",
|
||||
"password.security.redirect.to.reset.password.button": "إعادة ضبط كلمة المرور",
|
||||
"login.tpa.authentication.failure": "عذرًا ، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
|
||||
"progressive.profiling.page.title": "مرحبا بكم | {siteName}",
|
||||
"progressive.profiling.page.heading": "بعض الأسئلة الموجهة لك ستساعدنا كي نزداد ذكاءً.",
|
||||
"optional.fields.information.link": "معرفة المزيد عن كيفية استخدامنا لهذه المعلومات.",
|
||||
"optional.fields.submit.button": "إرسال",
|
||||
"optional.fields.skip.button": "التخطي مؤقتا",
|
||||
"optional.fields.next.button": "التالي",
|
||||
"continue.to.platform": "المواصلة إلى {platformName}",
|
||||
"modal.title": "شكرا لإعلامنا.",
|
||||
"modal.description": "إن غيرت رأيك، قيمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
|
||||
"welcome.page.error.heading": "لم نتمكن من تحديث ملفك الشخصي",
|
||||
"welcome.page.error.message": "حدث خطأ ما. يمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
|
||||
"recommendation.page.title": "التوصيات | {siteName}",
|
||||
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
|
||||
"recommendation.skip.button": "التخطي مؤقتا",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "التسجيل | {siteName}",
|
||||
"registration.fullname.label": "الاسم الكامل",
|
||||
"registration.email.label": "البريد الإلكتروني",
|
||||
"registration.username.label": "اسم المستخدم العامّ",
|
||||
"registration.password.label": "كلمة المرور",
|
||||
"registration.country.label": "البلد / المنطقة",
|
||||
"registration.opt.in.label": "أوافق على تلقّي رسائل تسويقية من {siteName}.",
|
||||
"help.text.name": "سيتم استخدام هذا الاسم في أي شهادات تحصل عليها.",
|
||||
"help.text.username.1": "الاسم الذي ستُعرَف به في مساقاتك.",
|
||||
"help.text.username.2": "لا يمكن تغيير هذا لاحقًا.",
|
||||
"help.text.email": "لتفعيل الحساب و التحديثات الهامة",
|
||||
"create.account.for.free.button": "إنشاء حساب مجانا",
|
||||
"registration.other.options.heading": "أو سجل باستخدام:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "بيانات المؤسسة / الجامعة",
|
||||
"register.institution.login.page.title": "التسجيل باستخدام بيانات المؤسسة / الجامعة",
|
||||
"empty.name.field.error": "أدخل اسمك الكامل",
|
||||
"empty.email.field.error": "أدخل بريدك الإلكتروني",
|
||||
"empty.username.field.error": "يجب أن يتكون اسم المستخدم من 2 إلى 30 حرفًا",
|
||||
"empty.password.field.error": "لم يتم استيفاء معايير كلمة المرور",
|
||||
"empty.country.field.error": "حدد بلدك أو منطقة إقامتك",
|
||||
"email.do.not.match": "عناوين البريد الإلكتروني غير متطابقة.",
|
||||
"email.invalid.format.error": "أدخل بريدا إلكترونيا صحيحا",
|
||||
"username.validation.message": "يجب أن يتكون اسم المستخدم من 2 إلى 30 حرفًا",
|
||||
"name.validation.message": "أدخل اسمًا صحيحا",
|
||||
"username.format.validation.message": "يمكن أن تحتوي أسماء المستخدمين فقط على أحرف (A-Z، a-z)، و أرقام (0-9)، و أسطر سفلية (_)، و واصلات (-). لا يمكن أن تحتوي أسماء المستخدمين على مسافات",
|
||||
"registration.request.failure.header": "لم نتمكّن من إنشاء حسابك.",
|
||||
"registration.empty.form.submission.error": "رجاءً تحقّق من أجوبتك و حاول مجددا.",
|
||||
"registration.request.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
||||
"registration.rate.limit.error": "كثرت محاولات التسجيل الفاشلة. أعد المحاولة لاحقًا.",
|
||||
"registration.tpa.session.expired": "نفد وقت التسجيل باستخدام {provider}.",
|
||||
"registration.tpa.authentication.failure": "عذرًا، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
|
||||
"terms.of.service.and.honor.code": "شروط الخدمة وميثاق الشرف الأكاديمي",
|
||||
"privacy.policy": "سياسة الخصوصية",
|
||||
"honor.code": "ميثاق الشرف الأكاديمي",
|
||||
"terms.of.service": "شروط الخدمة",
|
||||
"registration.username.suggestion.label": "مقترح:",
|
||||
"did.you.mean.alert.text": "هل تقصد",
|
||||
"sign.in": "تسجيل الدخول",
|
||||
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
|
||||
"reset.password": "إعادة ضبط كلمة المرور",
|
||||
"reset.password.page.instructions": "قم بإدخال و تأكيد كلمة مرورك.",
|
||||
"new.password.label": "كلمة المرور الجديدة",
|
||||
"confirm.password.label": "تأكيد كلمة المرور",
|
||||
"passwords.do.not.match": "كلمتا المرور غير متطابقتين",
|
||||
"confirm.your.password": "تأكيد كلمة مرورك",
|
||||
"reset.password.failure.heading": "لم نتمكن من إعادة ضبط كلمة مرورك.",
|
||||
"reset.password.form.submission.error": "رجاءً تحقق من أجوبتك وحاول مجددًا.",
|
||||
"reset.server.rate.limit.error": "طلبات أكثر مما ينبغي.",
|
||||
"reset.password.success.heading": "تمت إعادة ضبط كلمة المرور.",
|
||||
"reset.password.success": "تمت إعادة ضبط كلمة مرورك. سجل الدخول إلى حسابك.",
|
||||
"rate.limit.error": "حدث خطأ بسبب كثرة الطلبات. رجاءً حاول مرة أخرى بعد مضي بعض الوقت.",
|
||||
"start.learning": "ابدأ التعلم ",
|
||||
"with.site.name": "مع {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "أهلا بك {username} في {siteName}",
|
||||
"complete.your.profile.1": "أكمل",
|
||||
"complete.your.profile.2": "ملفك الشخصي",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
1
src/i18n/messages/ca.json
Normal file
1
src/i18n/messages/ca.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
181
src/i18n/messages/de.json
Normal file
181
src/i18n/messages/de.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "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} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"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.",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/de_DE.json
Normal file
181
src/i18n/messages/de_DE.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"error.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
|
||||
"institution.login.page.sub.heading": "Wählen Sie Ihre Institution aus der folgenden Liste aus",
|
||||
"logistration.sign.in": "Anmelden",
|
||||
"logistration.register": "Registrieren",
|
||||
"enterprisetpa.title.heading": "Möchten Sie sich mit Ihren {providerName}-Anmeldedaten anmelden?",
|
||||
"enterprisetpa.login.button.text": "Andere Möglichkeiten für die Anmeldung oder Registrierung",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Melden Sie sich mit {providerName} an",
|
||||
"sso.create.account.using": "Erstellen Sie ein Konto mit {providerName}",
|
||||
"show.password": "Passwort anzeigen",
|
||||
"hide.password": "Passwort verbergen",
|
||||
"one.letter": "1 Buchstabe",
|
||||
"one.number": "1 Nummer",
|
||||
"eight.characters": "8 Charaktere",
|
||||
"password.sr.only.helping.text": "Das Passwort muss mindestens 8 Zeichen, mindestens einen Buchstaben und mindestens eine Zahl enthalten",
|
||||
"tpa.alert.heading": "Fast fertig!",
|
||||
"login.third.party.auth.account.not.linked": "Sie haben sich erfolgreich bei {currentProvider} angemeldet, aber Ihr {currentProvider}-Konto hat kein verknüpftes {platformName}-Konto. Um Ihre Konten zu verknüpfen, melden Sie sich jetzt mit Ihrem {platformName}-Passwort an.",
|
||||
"register.third.party.auth.account.not.linked": "Sie haben sich erfolgreich bei {currentProvider} angemeldet! Wir brauchen nur ein paar mehr Informationen, bevor Sie anfangen, mit {platformName} zu lernen.",
|
||||
"registration.using.tpa.form.heading": "Beenden Sie die Erstellung Ihres Kontos",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Passwort vergessen | {siteName}",
|
||||
"forgot.password.page.heading": "Passwort zurücksetzen",
|
||||
"forgot.password.page.instructions": "Bitte geben Sie unten Ihre E-Mail-Adresse ein und wir senden Ihnen eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts.",
|
||||
"forgot.password.page.invalid.email.message": "Geben sie eine gültige E-Mail-Adresse an",
|
||||
"forgot.password.page.email.field.label": "E-Mail Adresse",
|
||||
"forgot.password.page.submit.button": "Einreichen",
|
||||
"forgot.password.error.alert.title.": "Wir konnten Sie nicht kontaktieren.",
|
||||
"forgot.password.error.message.title": "Ein Fehler ist aufgetreten.",
|
||||
"forgot.password.request.in.progress.message": "Ihre vorherige Anfrage ist in Bearbeitung, bitte versuchen Sie es in wenigen Augenblicken erneut.",
|
||||
"forgot.password.empty.email.field.error": "Geben sie ihre E-Mail Adresse ein",
|
||||
"forgot.password.email.help.text": "Die E-Mail-Adresse, mit der Sie sich bei {platformName} registriert haben",
|
||||
"confirmation.message.title": "Prüfen Sie Ihr E-Mail-Postfach",
|
||||
"confirmation.support.link": "wenden Sie sich an den technischen Support",
|
||||
"need.help.sign.in.text": "Brauchen Sie Hilfe bei der Anmeldung?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"sign.in.text": "Anmelden",
|
||||
"extend.field.errors": "{emailError} unten.",
|
||||
"invalid.token.heading": "Ungültiger Link zum Zurücksetzen des Passworts",
|
||||
"invalid.token.error.message": "Dieser Link zum Zurücksetzen des Passwortes ist ungültig. Möglicherweise wurde es bereits verwendet. Geben Sie unten Ihre E-Mail-Adresse ein, um einen neuen Link zu erhalten.",
|
||||
"token.validation.rate.limit.error.heading": "Zu viele Anfragen",
|
||||
"token.validation.rate.limit.error": "Aufgrund von zu vieler Anfragen ist ein Fehler aufgetreten. Bitte versuchen Sie es nach einiger Zeit erneut.",
|
||||
"token.validation.internal.sever.error.heading": "Token-Validierungsfehler",
|
||||
"token.validation.internal.sever.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||
"internal.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||
"account.activation.error.message": "Etwas ist schief gelaufen, bitte {supportLink} um dieses Problem zu lösen.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "Als {allowedDomain}-Benutzer müssen Sie sich mit Ihrem {allowedDomain} {tpaLink} anmelden.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Wenn Sie Ihr Passwort vergessen haben, {resetLink}",
|
||||
"account.locked.out.message.2": "Um auf der sicheren Seite zu sein, können Sie {resetLink} tun, bevor Sie es erneut versuchen.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "Der eingegebene Benutzername, die E-Mail-Adresse oder das Passwort ist falsch. Bitte versuchen Sie es erneut oder {resetLink}.",
|
||||
"login.page.title": "Anmelden | {siteName}",
|
||||
"login.user.identity.label": "Benutzername oder E-Mail-Adresse",
|
||||
"login.password.label": "Passwort",
|
||||
"sign.in.button": "Anmelden",
|
||||
"forgot.password": "Passwort vergessen",
|
||||
"institution.login.button": "Zeugnisse der Institution/des Campus",
|
||||
"institution.login.page.title": "Melden Sie sich mit Institutions-/Campus-Anmeldeinformationen an",
|
||||
"login.other.options.heading": "Oder melden Sie sich an mit:",
|
||||
"non.compliant.password.title": "Wir haben kürzlich unsere Passwortanforderungen geändert",
|
||||
"non.compliant.password.message": "Ihr aktuelles Passwort entspricht nicht den neuen Sicherheitsanforderungen. Wir haben gerade eine Nachricht zum Zurücksetzen des Passworts an die mit diesem Konto verknüpfte E-Mail-Adresse gesendet. Vielen Dank, dass Sie uns helfen, Ihre Daten zu schützen.",
|
||||
"account.locked.out.message.1": "Um Ihr Konto zu schützen, wurde es vorübergehend gesperrt. Versuchen Sie es in 30 Minuten erneut.",
|
||||
"enterprise.login.btn.text": "Arbeits- oder Schulzeugnisse",
|
||||
"username.or.email.format.validation.less.chars.message": "Benutzername oder E-Mail müssen mindestens 3 Zeichen lang sein.",
|
||||
"email.validation.message": "Geben Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse ein",
|
||||
"password.validation.message": "Die Passwortkriterien wurden nicht erfüllt",
|
||||
"account.activation.success.message.title": "Super! Sie haben Ihr Konto aktiviert.",
|
||||
"account.activation.success.message": "Sie erhalten jetzt E-Mail-Updates und Benachrichtigungen von uns in Bezug auf die Kurse, für die Sie eingeschrieben sind. Melden Sie sich an, um fortzufahren.",
|
||||
"account.activation.info.message": "Dieses Konto wurde bereits aktiviert.",
|
||||
"account.activation.error.message.title": "Ihr Konto konnte nicht aktiviert werden",
|
||||
"account.activation.support.link": "kontaktieren Sie den Support",
|
||||
"account.confirmation.success.message.title": "Super! Sie haben Ihre E-Mail bestätigt.",
|
||||
"account.confirmation.success.message": "Melden Sie sich an, um fortzufahren.",
|
||||
"account.confirmation.info.message": "Diese E-Mail-Adresse wurde bereits bestätigt.",
|
||||
"account.confirmation.error.message.title": "Ihre E-Mail-Adresse konnte nicht bestätigt werden",
|
||||
"tpa.account.link": "{provider}-Konto",
|
||||
"internal.server.error.message": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||
"login.rate.limit.reached.message": "Zu viele fehlgeschlagene Anmeldeversuche. Bitte versuche es später noch einmal.",
|
||||
"login.failure.header.title": "Wir konnten Sie leider nicht einloggen.",
|
||||
"contact.support.link": "Wenden Sie sich an den Support der {platformName}",
|
||||
"login.incorrect.credentials.error": "Der eingegebene Benutzername, die E-Mail-Adresse oder das Passwort ist falsch. Bitte versuche es erneut.",
|
||||
"login.form.invalid.error.message": "Bitte füllen Sie die unten stehenden Felder aus.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "Setzen Sie Ihr Passwort zurück",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "Klicken Sie hier, um es zurückzusetzen.",
|
||||
"password.security.nudge.title": "Passwortsicherheit",
|
||||
"password.security.block.title": "Passwortänderung erforderlich",
|
||||
"password.security.nudge.body": "Unser System hat festgestellt, dass Ihr Passwort angreifbar ist. Wir empfehlen Ihnen, es zu ändern, damit Ihr Konto sicher bleibt.",
|
||||
"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.",
|
||||
"optional.fields.submit.button": "Einreichen",
|
||||
"optional.fields.skip.button": "Überspringen",
|
||||
"optional.fields.next.button": "Weiter",
|
||||
"continue.to.platform": "Weiter zu {platformName}",
|
||||
"modal.title": "Danke, dass Sie uns das mitteilen.",
|
||||
"modal.description": "Sie können Ihr Profil jederzeit in den Einstellungen vervollständigen, wenn Sie Ihre Meinung ändern.",
|
||||
"welcome.page.error.heading": "Wir konnten Ihr Profil nicht aktualisieren",
|
||||
"welcome.page.error.message": "Ein Fehler ist aufgetreten. Sie können Ihr Profil jederzeit in den Einstellungen vervollständigen.",
|
||||
"recommendation.page.title": "Empfehlungen | {siteName}",
|
||||
"recommendation.page.heading": "Wir haben ein paar Empfehlungen für den Einstieg.",
|
||||
"recommendation.skip.button": "Überspringen",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Registrieren | {siteName}",
|
||||
"registration.fullname.label": "Vollständiger Name",
|
||||
"registration.email.label": "E-Mail-Adresse",
|
||||
"registration.username.label": "Öffentlicher Benutzername",
|
||||
"registration.password.label": "Passwort",
|
||||
"registration.country.label": "Land/Region",
|
||||
"registration.opt.in.label": "Ich stimme zu, dass {siteName} mir Marketingmitteilungen senden darf.",
|
||||
"help.text.name": "Dieser Name wird von allen Zertifikaten verwendet, die Sie erwerben.",
|
||||
"help.text.username.1": "Der Name, der Sie in Ihren Kursen identifiziert.",
|
||||
"help.text.username.2": "Dies kann später nicht mehr geändert werden.",
|
||||
"help.text.email": "Für die Kontoaktivierung und wichtige Updates",
|
||||
"create.account.for.free.button": "Erstellen Sie kostenlos ein Benutzerkonto",
|
||||
"registration.other.options.heading": "Oder registrieren Sie sich bei:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Zeugnisse der Institution/des Campus",
|
||||
"register.institution.login.page.title": "Registrieren Sie sich mit Institutions-/Campus-Anmeldeinformationen",
|
||||
"empty.name.field.error": "Geben Sie Ihren vollständigen Namen ein",
|
||||
"empty.email.field.error": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"empty.username.field.error": "Der Benutzername muss zwischen 2 und 30 Zeichen lang sein",
|
||||
"empty.password.field.error": "Kennwortkriterien wurden nicht erfüllt",
|
||||
"empty.country.field.error": "Wählen Sie das Land oder die Region Ihres Wohnsitzes aus",
|
||||
"email.do.not.match": "Die E-Mail-Adressen stimmen nicht überein.",
|
||||
"email.invalid.format.error": "Geben sie eine gültige E-Mail-Adresse an",
|
||||
"username.validation.message": "Der Benutzername muss zwischen 2 und 30 Zeichen lang sein",
|
||||
"name.validation.message": "Geben Sie einen gültigen Namen ein",
|
||||
"username.format.validation.message": "Benutzernamen dürfen nur Buchstaben (AZ, az), Ziffern (0-9), Unterstriche (_) und Bindestriche (-) enthalten. Benutzernamen dürfen keine Leerzeichen enthalten",
|
||||
"registration.request.failure.header": "Wir konnten Ihr Konto leider nicht erstellen.",
|
||||
"registration.empty.form.submission.error": "Bitte überprüfen Sie Ihre Antworten und versuchen Sie es erneut.",
|
||||
"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",
|
||||
"terms.of.service": "Nutzungsbedingungen",
|
||||
"registration.username.suggestion.label": "Empfohlen:",
|
||||
"did.you.mean.alert.text": "Meinten Sie",
|
||||
"sign.in": "Anmelden",
|
||||
"reset.password.page.title": "Passwort zurücksetzen | {siteName}",
|
||||
"reset.password": "Passwort zurücksetzen",
|
||||
"reset.password.page.instructions": "Neues Passwort eingeben und bestätigen",
|
||||
"new.password.label": "Neues Passwort",
|
||||
"confirm.password.label": "Kennwort bestätigen",
|
||||
"passwords.do.not.match": "Passwörter stimmen nicht überein",
|
||||
"confirm.your.password": "Bestätigen Sie Ihr Passwort",
|
||||
"reset.password.failure.heading": "Wir konnten Ihr Passwort nicht zurücksetzen.",
|
||||
"reset.password.form.submission.error": "Bitte überprüfen Sie Ihre Antworten und versuchen Sie es erneut.",
|
||||
"reset.server.rate.limit.error": "Zu viele Anfragen.",
|
||||
"reset.password.success.heading": "Zurücksetzen des Passworts abgeschlossen.",
|
||||
"reset.password.success": "Ihr Passwort wurde zurückgesetzt. Melden Sie sich bei Ihrem Konto an.",
|
||||
"rate.limit.error": "Aufgrund zu vieler Anfragen ist ein Fehler aufgetreten. Bitte versuchen Sie es nach einiger Zeit erneut.",
|
||||
"start.learning": "Beginne zu lernen",
|
||||
"with.site.name": "mit {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Vervollständige",
|
||||
"complete.your.profile.2": "dein Profil",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/es_419.json
Normal file
181
src/i18n/messages/es_419.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, verifica la URL y vuelve a intentarlo.",
|
||||
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
|
||||
"logistration.sign.in": "Iniciar sesión",
|
||||
"logistration.register": "Registrarse",
|
||||
"enterprisetpa.title.heading": "¿Deseas iniciar sesión con tus credenciales de {providerName}?",
|
||||
"enterprisetpa.login.button.text": "Mostrar otras formas de iniciar sesión o de registrarme",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Mostrar otras formas de iniciar sesión",
|
||||
"sso.sign.in.with": "Inicio de sesión con {providerName}",
|
||||
"sso.create.account.using": "Crear una cuenta con {providerName}",
|
||||
"show.password": "Mostrar contraseña",
|
||||
"hide.password": "Ocultar contraseña",
|
||||
"one.letter": "1 letra",
|
||||
"one.number": "1 número",
|
||||
"eight.characters": "8 caracteres",
|
||||
"password.sr.only.helping.text": "La contraseña debe contener al menos 8 caracteres, al menos una letra y al menos un número",
|
||||
"tpa.alert.heading": "¡Ya casi has terminado!",
|
||||
"login.third.party.auth.account.not.linked": "Te has registrado correctamente en {currentProvider}, pero tu cuenta de {currentProvider} no tiene una cuenta de {platformName} asociada. Para asociar tus cuentas, inicia sesión ahora usando tu contraseña de {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "¡Has iniciado sesión con éxito en {currentProvider}! Sólo necesitamos un poco más de información antes de que empieces a aprender con {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
|
||||
"zendesk.supportTitle": "Soporte edX",
|
||||
"zendesk.selectTicketForm": "Elegir el tipo de solicitud:",
|
||||
"forgot.password.confirmation.message": "Enviamos un correo electrónico a {email} con instrucciones para restablecer su contraseña. Si no recibe un mensaje de restablecimiento de contraseña después de 1 minuto, verifique que ingresó la dirección de correo electrónico correcta o verifique su carpeta de correo no deseado. Si necesita más ayuda, {supportLink}.",
|
||||
"forgot.password.page.title": "Olvidé la contraseña | {siteName}",
|
||||
"forgot.password.page.heading": "Restablecer mi contraseña",
|
||||
"forgot.password.page.instructions": "Por favor, introduce tu dirección de correo electrónico y te enviaremos un correo electrónico con instrucciones sobre cómo restablecer tu contraseña.",
|
||||
"forgot.password.page.invalid.email.message": "Introduce una dirección de correo electrónico válida",
|
||||
"forgot.password.page.email.field.label": "Correo electrónico",
|
||||
"forgot.password.page.submit.button": "Enviar",
|
||||
"forgot.password.error.alert.title.": "No hemos podido entrar en contacto contigo.",
|
||||
"forgot.password.error.message.title": "Ha ocurrido un error.",
|
||||
"forgot.password.request.in.progress.message": "Su solicitud anterior está en progreso, por favor inténtalo de nuevo en unos minutos.",
|
||||
"forgot.password.empty.email.field.error": "Introduce tu email",
|
||||
"forgot.password.email.help.text": "El correo electrónico que utilizaste para registrarte en {platformName}",
|
||||
"confirmation.message.title": "Verifica tu correo electrónico",
|
||||
"confirmation.support.link": "entra en contacto con el equipo de soporte técnico",
|
||||
"need.help.sign.in.text": "¿Necesitas ayuda para iniciar sesión?",
|
||||
"additional.help.text": "Para obtener ayuda adicional, comuníquese con el soporte {platformName} en",
|
||||
"sign.in.text": "Iniciar sesión",
|
||||
"extend.field.errors": "{emailError} a continuación.",
|
||||
"invalid.token.heading": "Enlace de restablecimiento de contraseña inválido",
|
||||
"invalid.token.error.message": "Este enlace para restablecer la contraseña no es válido. Es posible que ya haya sido utilizado. Introduce tu correo electrónico para recibir un nuevo enlace.",
|
||||
"token.validation.rate.limit.error.heading": "Demasiadas solicitudes",
|
||||
"token.validation.rate.limit.error": "Se ha producido un error debido a demasiadas solicitudes. Por favor, inténtalo de nuevo después de algún tiempo.",
|
||||
"token.validation.internal.sever.error.heading": "Fallo de validación del token",
|
||||
"token.validation.internal.sever.error": "Se ha producido un error. Intenta actualizar la página o verifica tu conexión a Internet.",
|
||||
"internal.server.error": "Se ha producido un error. Intenta actualizar la página o verifica tu conexión a Internet.",
|
||||
"account.activation.error.message": "Algo no funcionó correctamente, por favor {supportLink} para resolver este problema.",
|
||||
"login.inactive.user.error": "Para iniciar sesión, debe activar su cuenta.{lineBreak} {lineBreak}Acabamos de enviar un enlace de activación a {email}. Si no recibe un correo electrónico, revise sus carpetas de spam o {supportLink}.",
|
||||
"allowed.domain.login.error": "Como usuario {allowedDomain}, debe iniciar sesión con su {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "El nombre de usuario, correo electrónico o contraseña que ingresó es incorrecto. Tiene {remainingAttempts} más intentos de inicio de sesión antes de que su cuenta se bloquee temporalmente.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Si has olvidado tu contraseña, {resetLink}",
|
||||
"account.locked.out.message.2": "Para estar seguro, puedes {resetLink} antes de volver a intentarlo.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "El nombre de usuario, el correo electrónico o la contraseña que has introducido son incorrectos. Por favor, inténtalo de nuevo o {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Nombre de usuario o correo electrónico",
|
||||
"login.password.label": "Contraseña",
|
||||
"sign.in.button": "Iniciar sesión",
|
||||
"forgot.password": "Olvidé mi contraseña",
|
||||
"institution.login.button": "Credenciales de la institución/campus",
|
||||
"institution.login.page.title": "Iniciar sesión con las credenciales de la institución/campus",
|
||||
"login.other.options.heading": "O bien, inicia sesión con:",
|
||||
"non.compliant.password.title": "Recientemente hemos cambiado los requisitos de las contraseñas",
|
||||
"non.compliant.password.message": "Tu contraseña actual no cumple con los nuevos requisitos de seguridad. Acabamos de enviar un mensaje de restablecimiento de contraseña a la dirección de correo electrónico asociada a esta cuenta. Gracias por ayudarnos a mantener tus datos seguros.",
|
||||
"account.locked.out.message.1": "Para proteger tu cuenta, se ha bloqueado temporalmente. Inténtalo de nuevo en 30 minutos.",
|
||||
"enterprise.login.btn.text": "Credenciales de la empresa o de la institución ",
|
||||
"username.or.email.format.validation.less.chars.message": "El nombre de usuario o el correo electrónico deben tener al menos 3 caracteres.",
|
||||
"email.validation.message": "Introduce tu nombre de usuario o correo electrónico",
|
||||
"password.validation.message": "No se han cumplido los criterios de la contraseña",
|
||||
"account.activation.success.message.title": "Ha sido un éxito. Has activado tu cuenta.",
|
||||
"account.activation.success.message": "Ahora recibirás por correo electrónico actualizaciones y alertas relacionadas con los cursos en los que estás inscrito. Inicia sesión para continuar.",
|
||||
"account.activation.info.message": "La cuenta ya ha sido activada.",
|
||||
"account.activation.error.message.title": "Tu cuenta no ha podido ser activada",
|
||||
"account.activation.support.link": "contacta al equipo de soporte de edX",
|
||||
"account.confirmation.success.message.title": "¡Éxito! Has confirmado tu correo electrónico.",
|
||||
"account.confirmation.success.message": "Inicia sesión para continuar.",
|
||||
"account.confirmation.info.message": "Este correo electrónico ya ha sido confirmado.",
|
||||
"account.confirmation.error.message.title": "Tu correo electrónico no pudo ser confirmado",
|
||||
"tpa.account.link": "{provider} cuenta",
|
||||
"internal.server.error.message": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
|
||||
"login.rate.limit.reached.message": "Demasiados intentos fallidos de inicio de sesión. Inténtelo de nuevo más tarde.",
|
||||
"login.failure.header.title": "No se ha podido iniciar tu sesión.",
|
||||
"contact.support.link": "entrar en contacto con el soporte de {platformName}",
|
||||
"login.incorrect.credentials.error": "El nombre de usuario, el correo electrónico o la contraseña que has introducido son incorrectos. Por favor, inténtalo de nuevo.",
|
||||
"login.form.invalid.error.message": "Por favor, complete los siguientes campos.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "restablecer la contraseña",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "Pulse aquí para restablecerla.",
|
||||
"password.security.nudge.title": "Seguridad de contraseña",
|
||||
"password.security.block.title": "Cambio de contraseña requerido",
|
||||
"password.security.nudge.body": "Nuestro sistema detectó que su contraseña es vulnerable. Le recomendamos que lo cambie para que su cuenta se mantenga segura.",
|
||||
"password.security.block.body": "Nuestro sistema detectó que su contraseña es vulnerable. Cambie su contraseña para que su cuenta permanezca segura.",
|
||||
"password.security.close.button": "Cerrar",
|
||||
"password.security.redirect.to.reset.password.button": "Restablece tu contraseña",
|
||||
"login.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Bienvenido | {siteName}",
|
||||
"progressive.profiling.page.heading": "Unas cuantas preguntas para ti nos ayudarán a mejorar.",
|
||||
"optional.fields.information.link": "Aprende más sobre cómo usamos esta información.",
|
||||
"optional.fields.submit.button": "Enviar",
|
||||
"optional.fields.skip.button": "Saltar por ahora ",
|
||||
"optional.fields.next.button": "Siguiente",
|
||||
"continue.to.platform": "Continuar a {platformName}",
|
||||
"modal.title": "Gracias por informarnos.",
|
||||
"modal.description": "Puedes completar tu perfil en los ajustes en cualquier momento si cambias de opinión.",
|
||||
"welcome.page.error.heading": "No hemos podido actualizar tu perfil",
|
||||
"welcome.page.error.message": "Se ha producido un error. Puedes completar tu perfil en los ajustes en cualquier momento.",
|
||||
"recommendation.page.title": "Recomendaciones | {siteName}",
|
||||
"recommendation.page.heading": "Tenemos algunas recomendaciones para empezar.",
|
||||
"recommendation.skip.button": "Saltar por ahora ",
|
||||
"recommendation.option.trending": "Tendencias",
|
||||
"recommendation.option.popular": "Más popular",
|
||||
"recommendation.option.recommended.for.you": "Recomendado para usted",
|
||||
"recommendation.product-card.pill-text.course": "Curso",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Certificado profesional",
|
||||
"recommendation.product-card.pill-text.emeritus": "Ofrecido en Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Ofrecido a través de Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Suscripción",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Abrir un enlace en una pestaña nueva",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Nombre completo",
|
||||
"registration.email.label": "Correo electrónico",
|
||||
"registration.username.label": "Nombre de usuario público",
|
||||
"registration.password.label": "Contraseña",
|
||||
"registration.country.label": "País/Región",
|
||||
"registration.opt.in.label": "Acepto que {siteName} pueda enviarme mensajes de marketing.",
|
||||
"help.text.name": "Este nombre será utilizado por los certificados que obtengas.",
|
||||
"help.text.username.1": "El nombre que te identificará en tus cursos.",
|
||||
"help.text.username.2": "Esto no puede modificarse posteriormente.",
|
||||
"help.text.email": "Para la activación de la cuenta y las actualizaciones importantes",
|
||||
"create.account.for.free.button": "Crea una cuenta gratis",
|
||||
"registration.other.options.heading": "O regístrese con:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Credenciales de la institución/campus",
|
||||
"register.institution.login.page.title": "Registro con credenciales de la institución/campus",
|
||||
"empty.name.field.error": "Introduce tu nombre completo",
|
||||
"empty.email.field.error": "Introduce tu email",
|
||||
"empty.username.field.error": "El nombre de usuario debe tener entre 2 y 30 caracteres",
|
||||
"empty.password.field.error": "No se han cumplido los criterios de la contraseña",
|
||||
"empty.country.field.error": "Selecciona tu país o región de residencia",
|
||||
"email.do.not.match": "Los correos electrónicos no son iguales.",
|
||||
"email.invalid.format.error": "Introduce una dirección de correo electrónico válida",
|
||||
"username.validation.message": "El nombre de usuario debe tener entre 2 y 30 caracteres",
|
||||
"name.validation.message": "Introduce un nombre válido",
|
||||
"username.format.validation.message": "Los nombres de usuario solo pueden contener letras (A-Z, a-z), números (0-9), guiones bajos (_) y guiones (-). Los nombres de usuario no pueden contener espacios",
|
||||
"registration.request.failure.header": "No pudimos crear tu cuenta.",
|
||||
"registration.empty.form.submission.error": "Por favor, verifica tus respuestas y vuelve a intentarlo.",
|
||||
"registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
|
||||
"registration.rate.limit.error": "Demasiados intentos de registro fallidos. Vuelve a intentarlo más tarde.",
|
||||
"registration.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
|
||||
"registration.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Condiciones de servicio y código de honor",
|
||||
"privacy.policy": "Política de privacidad ",
|
||||
"honor.code": "Código de Honor",
|
||||
"terms.of.service": "Términos de servicio",
|
||||
"registration.username.suggestion.label": "Se recomienda:",
|
||||
"did.you.mean.alert.text": "¿Quieres decir",
|
||||
"sign.in": "Iniciar sesión",
|
||||
"reset.password.page.title": "Restablecer contraseña | {siteName}",
|
||||
"reset.password": "Restablecer mi contraseña",
|
||||
"reset.password.page.instructions": "Ingresa y confirma tu nueva contraseña.",
|
||||
"new.password.label": "Nueva contraseña",
|
||||
"confirm.password.label": "Confirmar contraseña",
|
||||
"passwords.do.not.match": "Las contraseñas no coinciden",
|
||||
"confirm.your.password": "Confirma tu contraseña",
|
||||
"reset.password.failure.heading": "No hemos podido restablecer tu contraseña.",
|
||||
"reset.password.form.submission.error": "Por favor, verifica tus respuestas y vuelve a intentarlo.",
|
||||
"reset.server.rate.limit.error": "Demasiadas solicitudes.",
|
||||
"reset.password.success.heading": "Restablecimiento de la contraseña completado.",
|
||||
"reset.password.success": "Tu contraseña ha sido restablecida. Acceda a tu cuenta.",
|
||||
"rate.limit.error": "Se ha producido un error debido a demasiadas solicitudes. Por favor, inténtalo de nuevo después de algún tiempo.",
|
||||
"start.learning": "Empieza a aprender",
|
||||
"with.site.name": "con {siteName}",
|
||||
"your.career.turning.point": "El punto de inflexión de tu carrera",
|
||||
"is.here": "es aquí.",
|
||||
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Completado",
|
||||
"complete.your.profile.2": "tu perfil ",
|
||||
"register.page.terms.of.service.and.honor.code": "Al crear una cuenta, acepta {tosAndHonorCode} y reconoce que {platformName} y cada miembro procesan sus datos personales de acuerdo con {privacyPolicy}.",
|
||||
"register.page.honor.code": "Acepto las {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "Acepto las {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/fa_IR.json
Normal file
181
src/i18n/messages/fa_IR.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"error.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی اینترنتی وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره تلاش کنید.",
|
||||
"institution.login.page.sub.heading": "موسسه خود را از فهرست زیر برگزینید",
|
||||
"logistration.sign.in": "ورود",
|
||||
"logistration.register": "ثبتنام",
|
||||
"enterprisetpa.title.heading": "آیا میخواهید با استفاده از اطلاعات کاربری {providerName} خود وارد سامانه شوید؟",
|
||||
"enterprisetpa.login.button.text": "راههای دیگری برای ورود به سامانه یا ثبتنام به من نشان بده",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "راه های دیگری را برای ورود به سیستم به من نشان دهید",
|
||||
"sso.sign.in.with": "با {providerName} وارد شوید",
|
||||
"sso.create.account.using": "با استفاده از {providerName} حساب کاربری بسازید",
|
||||
"show.password": "نمایش گذرواژه",
|
||||
"hide.password": "پنهانسازی گذرواژه",
|
||||
"one.letter": "1 نامه",
|
||||
"one.number": "1 رقم",
|
||||
"eight.characters": "8 نویسه",
|
||||
"password.sr.only.helping.text": "گذرواژه باید حداقل 8 نویسه، حداقل یک حرف و حداقل یک عدد داشته باشد",
|
||||
"tpa.alert.heading": "تقریبا تمام شد!",
|
||||
"login.third.party.auth.account.not.linked": "شما با موفقیت به {currentProvider} وارد شدید، اما حساب کاربری {currentProvider} شما به حساب کاربری {platformName} پیوند ندارد. برای پیوند حسابهای کاربری خود، اکنون با استفاده از گذرواژه {platformName} خود وارد شوید.",
|
||||
"register.third.party.auth.account.not.linked": "شما با موفقیت به {currentProvider} وارد شدید! پیش از آغاز یادگیری با {platformName} فقط به کمی اطلاعات بیشتر نیاز داریم.",
|
||||
"registration.using.tpa.form.heading": "ساخت حساب کاربری خود را به اتمام برسانید",
|
||||
"zendesk.supportTitle": "پشتیبانی edX",
|
||||
"zendesk.selectTicketForm": "لطفا نوع درخواست خود را انتخاب کنید:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "فراموش گذرواژه | {siteName}",
|
||||
"forgot.password.page.heading": "بازتنظیم گذرواژه",
|
||||
"forgot.password.page.instructions": "لطفا نشانی رایانامه خود را در قسمت زیر وارد کنید و ما رایانامهای حاوی دستورالعمل نحوه بازتنظیم مجدد گذرواژه برای شما ارسال خواهیم کرد.",
|
||||
"forgot.password.page.invalid.email.message": "نشانی رایانامه معتبری را وارد کنید",
|
||||
"forgot.password.page.email.field.label": "رایانامه",
|
||||
"forgot.password.page.submit.button": "ارسال",
|
||||
"forgot.password.error.alert.title.": "ما نتوانستیم با شما تماس بگیریم.",
|
||||
"forgot.password.error.message.title": "خطایی رخ داد.",
|
||||
"forgot.password.request.in.progress.message": "درخواست قبلی شما در حال انجام است، لطفا چند لحظه دیگر دوباره تلاش کنید.",
|
||||
"forgot.password.empty.email.field.error": "نشانی رایانامه خود را وارد کنید",
|
||||
"forgot.password.email.help.text": "نشانی رایانامهای که برای ثبتنام در {platformName} استفاده کردید",
|
||||
"confirmation.message.title": "صندوق رایانامه خود را ببینید",
|
||||
"confirmation.support.link": "با پشتیبانی فنی تماس بگیرید",
|
||||
"need.help.sign.in.text": "برای ورود به سامانه نیاز به کمک دارید؟",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"sign.in.text": "ورود",
|
||||
"extend.field.errors": "{emailError} زیر.",
|
||||
"invalid.token.heading": "پیوند بازتنظیم گذرواژه معتبر نیست",
|
||||
"invalid.token.error.message": "این پیوند برای بازتنظیم گذرواژه معتبر نیست. ممکن است قبلاً استفاده شده باشد. برای دریافت پیوند جدید نشانی رایانامه خود را در زیر وارد کنید.",
|
||||
"token.validation.rate.limit.error.heading": "تعداد زیاد درخواست",
|
||||
"token.validation.rate.limit.error": "به دلیل درخواستهای زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید.",
|
||||
"token.validation.internal.sever.error.heading": "اعتبارسنجی رمز انجام نشد",
|
||||
"token.validation.internal.sever.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||
"internal.server.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||
"account.activation.error.message": "اشتباهی رخ داد، لطفاً برای حل این مساله ، به این قسمت{supportLink} مراجعه کنید.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "به عنوان کاربر {allowedDomain}، باید با {allowedDomain} {tpaLink} خود وارد شوید.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "اگر گذرواژه خود را فراموش کردهاید، {resetLink}",
|
||||
"account.locked.out.message.2": "برای حفظ امنیت، میتوانید پیش از تلاش مجدد، {resetLink} را انجام دهید.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "نام کاربری، نشانی رایانامه یا گذرواژهای که وارد کردید درست نیست. لطفاً دوباره امتحان کنید یا {resetLink}.",
|
||||
"login.page.title": "ورود به سامانه | {siteName}",
|
||||
"login.user.identity.label": "نام کاربری یا نشانی رایانامه",
|
||||
"login.password.label": "گذرواژه",
|
||||
"sign.in.button": "ورود به سامانه",
|
||||
"forgot.password": "فراموشی گذرواژه",
|
||||
"institution.login.button": "اعتبارنامههای موسسه/پردیس",
|
||||
"institution.login.page.title": "با اعتبار موسسه/پردیس وارد شوید",
|
||||
"login.other.options.heading": "یا وارد شوید با:",
|
||||
"non.compliant.password.title": "ما اخیراً الزامات گذرواژه خود را تغییر دادیم",
|
||||
"non.compliant.password.message": "گذرواژه فعلی شما الزامات امنیتی جدید را برآورده نمیکند. ما فقط یک پیام بازتنظیم گذرواژه به نشانی رایانامه مرتبط با این حساب کاربری ارسال کردیم. از اینکه به ما کمک میکنید تا دادههای شما را ایمن نگه دارید متشکریم.",
|
||||
"account.locked.out.message.1": "حساب کاربری شما، به دلیل حفاظت، بهطور موقت قفل شده است. 30 دقیقه دیگر دوباره امتحان کنید.",
|
||||
"enterprise.login.btn.text": "اعتبار دانشکده یا شرکت",
|
||||
"username.or.email.format.validation.less.chars.message": "نام کاربری یا نشانی رایانامه حداقل باید 3 نویسه داشته باشد",
|
||||
"email.validation.message": "نام کاربری یا رایانامه خود را وارد کنید",
|
||||
"password.validation.message": "معیارهای گذرواژه رعایت نشده است",
|
||||
"account.activation.success.message.title": "موفق شدید! شما حساب کاربری خود را فعال کردید.",
|
||||
"account.activation.success.message": "اکنون رایانامههای مربوط به روزآمدسازیها و هشدارها درباره دورههای آموزشی را که در آن ثبتنام کردهاید از ما دریافت خواهید کرد. برای ادامه وارد شوید.",
|
||||
"account.activation.info.message": "حساب کاربری مورد نظر شما قبلاً فعال شده است.",
|
||||
"account.activation.error.message.title": "امکان فعالسازی حساب کاربری شما نبود",
|
||||
"account.activation.support.link": "تماس با پشتیبانی ",
|
||||
"account.confirmation.success.message.title": "موفق شدید! نشانی رایانامه خود را تایید کردید.",
|
||||
"account.confirmation.success.message": "برای ادامه وارد سامانه شوید.",
|
||||
"account.confirmation.info.message": "این نشانی رایانامه قبلا تایید شدهاست.",
|
||||
"account.confirmation.error.message.title": "امکان تایید نشانی رایانامه شما وجود ندارد",
|
||||
"tpa.account.link": "حساب {provider}",
|
||||
"internal.server.error.message": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||
"login.rate.limit.reached.message": "شما برای ورود به حساب کاربری چند بار تلاش ناموفق داشتید. لطفا بعدا تلاش نمایید.",
|
||||
"login.failure.header.title": "قادر نیستیم شما را به سامانه وارد کنیم.",
|
||||
"contact.support.link": "با پشتیبانی {platformName} تماس بگیرید",
|
||||
"login.incorrect.credentials.error": "نام کاربری، نشانی رایانامه یا گذرواژهای که وارد کردید نادرست است. لطفا دوباره تلاش کنید.",
|
||||
"login.form.invalid.error.message": "لطفا قسمتهای زیر را پر کنید.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "گذرواژه را بازتنظیم کنید",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "برای بازتنظیم اینجا بزنید",
|
||||
"password.security.nudge.title": "امنیت گذرواژه",
|
||||
"password.security.block.title": "تغییر گذرواژه ضروری است",
|
||||
"password.security.nudge.body": "سامانه ما تشخیص داده است که گذرواژه شما آسیبپذیر است. توصیه ما این است که آن را تغییر دهید تا حساب کاربری شما ایمن بماند.",
|
||||
"password.security.block.body": "سامانه ما تشخیص داده است که گذرواژه شما ضعیف و آسیبپذیر است. گذرواژه خود را تغییر دهید تا حساب کاربری شما ایمن بماند.",
|
||||
"password.security.close.button": "بستن",
|
||||
"password.security.redirect.to.reset.password.button": "بازتنظیم گذرواژه",
|
||||
"login.tpa.authentication.failure": "متأسفیم، شما مجاز به دسترسی به {platform_name} از طریق این کانال نیستید. لطفاً برای دسترسی به {platform_name} با سرپرست یا مدیر آموزشی خود تماس بگیرید.{lineBreak}{lineBreak}جزئیات خطا:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "خوش آمدید | {siteName}",
|
||||
"progressive.profiling.page.heading": "چند سوال برای شما به ما کمک خواهد کرد تا باهوشتر شویم.",
|
||||
"optional.fields.information.link": "درباره نحوه استفاده ما از این اطلاعات بیشتر بدانید.",
|
||||
"optional.fields.submit.button": "ارسال",
|
||||
"optional.fields.skip.button": "فعلا بگذرید",
|
||||
"optional.fields.next.button": "بعدی",
|
||||
"continue.to.platform": "ادامه در {platformName}",
|
||||
"modal.title": "از اینکه اطلاع دادید تشکر میکنیم.",
|
||||
"modal.description": "در صورت تغییر تصمیمتان، شما هر زمانی این امکان را دارید که پرونده کاربری خود را در قسمت تنظیمات تکمیل کنید. ",
|
||||
"welcome.page.error.heading": "امکان روزآمدسازی پرونده کاربری شما نیست",
|
||||
"welcome.page.error.message": "خطایی رخ داد. میتوانید پرونده کاربری خود را هر زمان در قسمت تنظیمات تکمیل کنید.",
|
||||
"recommendation.page.title": "توصیه ها | {siteName}",
|
||||
"recommendation.page.heading": "ما چند توصیه برای شروع کار داریم.",
|
||||
"recommendation.skip.button": "فعلا بگذرید",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "محبوبترین",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "دوره آموزشی",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "گواهی حرفهای",
|
||||
"recommendation.product-card.pill-text.emeritus": "ارائه شده در Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "از طریق Shorelight ارائه می شود",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "اشتراک",
|
||||
"recommendation.product-card.launch-icon.sr-text": "پیوندی را در یک برگه جدید باز می کند",
|
||||
"register.page.title": "ثبتنام | {siteName}",
|
||||
"registration.fullname.label": "نام و نام خانوادگی",
|
||||
"registration.email.label": "رایانامه",
|
||||
"registration.username.label": "نامکاربری عمومی",
|
||||
"registration.password.label": "گذرواژه",
|
||||
"registration.country.label": "کشور/منطقه",
|
||||
"registration.opt.in.label": "با ارسال پیامهای بازرگانی از سوی {siteName} موافقم.",
|
||||
"help.text.name": "این نام در هر گواهی که بدست آورید استفاده خواهد شد.",
|
||||
"help.text.username.1": "نامی که شما را در دورههای آموزشی با آن شناخته خواهید شد.",
|
||||
"help.text.username.2": "این مورد بعدا قابل تغییر نیست.",
|
||||
"help.text.email": "برای فعالسازی حساب کاربری و روزآمدسازیهای مهم",
|
||||
"create.account.for.free.button": "یک حساب کاربری رایگان بسازید",
|
||||
"registration.other.options.heading": "یا ثبتنام کنید با:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "اعتبارنامههای موسسه/پردیس",
|
||||
"register.institution.login.page.title": "با اعتبارنامه موسسه/پردیس ثبتنام کنید",
|
||||
"empty.name.field.error": "نام و نام خانوادگی خود را وارد کنید",
|
||||
"empty.email.field.error": "نشانی رایانامه خود را وارد کنید",
|
||||
"empty.username.field.error": "نام کاربری باید بین 2 تا 30 نویسه داشته باشد",
|
||||
"empty.password.field.error": "معیارهای گذرواژه رعایت نشده است",
|
||||
"empty.country.field.error": "کشور یا منطقه محل سکونت خود را برگزینید",
|
||||
"email.do.not.match": "نشانیهای رایانامه همخوانی ندارند.",
|
||||
"email.invalid.format.error": "نشانی رایانامه معتبر وارد کنید",
|
||||
"username.validation.message": "نام کاربری باید بین 2 تا 30 نویسه داشته باشد",
|
||||
"name.validation.message": "نامی معتبر وارد کنید",
|
||||
"username.format.validation.message": "نام کاربری فقط میتواند شامل حروف (A-Z، a-z)، اعداد (0-9)، خط زیر (_) و خط فاصله (-) باشد. نام کاربری نمیتواند حاوی فاصله باشد",
|
||||
"registration.request.failure.header": "موفق به ایجاد حساب کاربری شما نشدیم.",
|
||||
"registration.empty.form.submission.error": "لطفاً پاسخهای خود را بررسی کرده و دوباره امتحان کنید.",
|
||||
"registration.request.server.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
|
||||
"registration.rate.limit.error": "تعداد دفعات تلاش ناموفق برای ثبتنام بسیار بوده است. بعدا دوباره تلاش کنید.",
|
||||
"registration.tpa.session.expired": "مهلت ثبتنام {provider} به پایان رسیدهاست.",
|
||||
"registration.tpa.authentication.failure": "متأسفیم، شما مجاز به دسترسی به {platform_name} از طریق این کانال نیستید. لطفاً برای دسترسی به {platform_name} با سرپرست یا مدیر آموزشی خود تماس بگیرید.{lineBreak}{lineBreak}جزئیات خطا:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "شرایط استفاده از خدمات و اصول اخلاقی",
|
||||
"privacy.policy": "قواعد حفظ حریم خصوصی",
|
||||
"honor.code": "اصول اخلاقی",
|
||||
"terms.of.service": "شرایط استفاده از خدمات",
|
||||
"registration.username.suggestion.label": "پیشنهادشده:",
|
||||
"did.you.mean.alert.text": "منظور شما این بود",
|
||||
"sign.in": "ورود",
|
||||
"reset.password.page.title": "بازتنظیم گذرواژه | {siteName}",
|
||||
"reset.password": "بازتنظیم گذرواژه",
|
||||
"reset.password.page.instructions": "گذرواژه جدید را مجددا وارد کنید ",
|
||||
"new.password.label": "گذرواژه جدید",
|
||||
"confirm.password.label": "تایید گذرواژه",
|
||||
"passwords.do.not.match": "گذرواژهها مطابقت ندارند",
|
||||
"confirm.your.password": "تایید گذرواژه ",
|
||||
"reset.password.failure.heading": "امکان بازتنظیم گذرواژه شما نیست.",
|
||||
"reset.password.form.submission.error": "لطفاً پاسخهای خود را بررسی کرده و دوباره امتحان کنید.",
|
||||
"reset.server.rate.limit.error": "تعداد درخواستها خیلی زیاد است.",
|
||||
"reset.password.success.heading": "بازتنظیم گذرواژه تکمیل شد.",
|
||||
"reset.password.success": "گذرواژه شما بازتنظیم شد. وارد حساب کاربری خود شوید",
|
||||
"rate.limit.error": "به دلیل درخواستهای زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید.",
|
||||
"start.learning": "آغاز یادگیری",
|
||||
"with.site.name": "با {siteName}",
|
||||
"your.career.turning.point": "نقطه عطف حرفه ای شما",
|
||||
"is.here": "اینجاست.",
|
||||
"welcome.to.platform": "خوش آمدید به {siteName}, {username}!",
|
||||
"complete.your.profile.1": "کامل",
|
||||
"complete.your.profile.2": "پرونده کاربری شما",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/fr.json
Normal file
181
src/i18n/messages/fr.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"logistration.sign.in": "Connectez-vous",
|
||||
"logistration.register": "S'inscrire",
|
||||
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName} ?",
|
||||
"enterprisetpa.login.button.text": "Montrez-moi d'autres méthodes pour me connecter ou m'inscrire",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
||||
"sso.create.account.using": "Créer un compte avec {providerName}",
|
||||
"show.password": "Afficher le mot de passe",
|
||||
"hide.password": "Cacher le mot de passe ",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Le mot de passe doit contenir au moins 8 caractères, au moins une lettre et au moins un chiffre",
|
||||
"tpa.alert.heading": "Presque fini !",
|
||||
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider} ! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": " Mot de passe oublié | {siteName}",
|
||||
"forgot.password.page.heading": "Réinitialiser le mot de passe",
|
||||
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
|
||||
"forgot.password.page.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Envoyez",
|
||||
"forgot.password.error.alert.title.": "Nous n'avons pas pu vous contacter.",
|
||||
"forgot.password.error.message.title": "Une erreur est survenue.",
|
||||
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
|
||||
"forgot.password.empty.email.field.error": "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 email",
|
||||
"confirmation.support.link": "contacter le support technique",
|
||||
"need.help.sign.in.text": "Besoin d'aide pour vous enregistrer?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"sign.in.text": "Connectez-vous",
|
||||
"extend.field.errors": "{emailError} ci-dessous.",
|
||||
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
||||
"invalid.token.error.message": "Ce lien de réinitialisation de mot de passe n'est pas valide. Il a peut-être déjà été utilisé. Entrez votre courriel ci-dessous pour recevoir un nouveau lien.",
|
||||
"token.validation.rate.limit.error.heading": "Trop de demandes",
|
||||
"token.validation.rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps.",
|
||||
"token.validation.internal.sever.error.heading": "Échec de la validation du jeton",
|
||||
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
||||
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
||||
"login.page.title": "Connexion | {siteName}",
|
||||
"login.user.identity.label": "Nom d'utilisateur ou courriel",
|
||||
"login.password.label": "Mot de passe",
|
||||
"sign.in.button": "Connectez-vous",
|
||||
"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": "Succès ! Vous avez confirmé votre courriel.",
|
||||
"account.confirmation.success.message": "Se connecter pour continuer.",
|
||||
"account.confirmation.info.message": "Ce courriel a déjà été confirmé.",
|
||||
"account.confirmation.error.message.title": "Votre courriel ne peut pas être confirmé.",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"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 de connexion échouées. Réessayez plus tard.",
|
||||
"login.failure.header.title": "Nous n'avons pas pu vous connecter.",
|
||||
"contact.support.link": "veuillez contacter le support {platformName}",
|
||||
"login.incorrect.credentials.error": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer.",
|
||||
"login.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": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
|
||||
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
|
||||
"optional.fields.submit.button": "Envoyez",
|
||||
"optional.fields.skip.button": "Ignorer pour l'instant",
|
||||
"optional.fields.next.button": "Next",
|
||||
"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": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "S'inscrire | {siteName}",
|
||||
"registration.fullname.label": "Nom complet",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Nom d'utilisateur public",
|
||||
"registration.password.label": "Mot de passe",
|
||||
"registration.country.label": "Pays/Région",
|
||||
"registration.opt.in.label": "{siteName} peux m'envoyer des messages de marketing.",
|
||||
"help.text.name": "Ce nom sera utilisé pour toutes les attestations que vous obtiendrez.",
|
||||
"help.text.username.1": "Le nom qui vous identifiera dans vos cours.",
|
||||
"help.text.username.2": "Cela ne peut pas être modifié ultérieurement.",
|
||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Identifiants de l'établissement/du campus",
|
||||
"register.institution.login.page.title": "Inscription avec les crédentiels d'institution ou de campus",
|
||||
"empty.name.field.error": "Saisissez votre nom complet",
|
||||
"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": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "Nous n'avons pas pu créer votre compte.",
|
||||
"registration.empty.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
||||
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
|
||||
"registration.tpa.session.expired": "L'inscription avec {provider} a échouée.",
|
||||
"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",
|
||||
"terms.of.service": " Conditions d'utilisation",
|
||||
"registration.username.suggestion.label": "Suggéré :",
|
||||
"did.you.mean.alert.text": "Vouliez-vous dire",
|
||||
"sign.in": "Connectez-vous",
|
||||
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
||||
"reset.password": "Réinitialiser le mot de passe",
|
||||
"reset.password.page.instructions": "Saisir et confirmer votre nouveau mot de passe.",
|
||||
"new.password.label": "Nouveau mot de passe",
|
||||
"confirm.password.label": "Confirmer le mot de passe",
|
||||
"passwords.do.not.match": "Les mots de passe ne correspondent pas",
|
||||
"confirm.your.password": "Confirmer votre mot de passe",
|
||||
"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.",
|
||||
"start.learning": "Démarrer l'apprentissage",
|
||||
"with.site.name": "avec {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Terminé",
|
||||
"complete.your.profile.2": "votre profil",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/fr_CA.json
Normal file
181
src/i18n/messages/fr_CA.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"logistration.sign.in": "Connexion",
|
||||
"logistration.register": "Inscription",
|
||||
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos informations d'identification {providerName}?",
|
||||
"enterprisetpa.login.button.text": "Affichez moi d'autres façons de se connecter ou de s'inscrire",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Montrez-moi d'autres façons de me connecter",
|
||||
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
||||
"sso.create.account.using": "Créer un compte avec {providerName}",
|
||||
"show.password": "Afficher le mot de passe",
|
||||
"hide.password": "Cacher le mot de passe",
|
||||
"one.letter": "1 lettre",
|
||||
"one.number": "1 numéro",
|
||||
"eight.characters": "8 caractères",
|
||||
"password.sr.only.helping.text": "Le mot de passe doit contenir au moins 8 caractères, au moins une lettre et au moins un chiffre",
|
||||
"tpa.alert.heading": "Presque terminé!",
|
||||
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||
"zendesk.supportTitle": "Prise en charge d'edX",
|
||||
"zendesk.selectTicketForm": "Veuillez choisir votre type de demande :",
|
||||
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe. Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi l'adresse courriel correctement, ou vérifiez votre dossier de pourriels. Si vous avez besoin d'aide supplémentaire, contactez {supportLink}.",
|
||||
"forgot.password.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 de l'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}{lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel, vérifiez vos dossiers de pourriels ou {supportLink}.",
|
||||
"allowed.domain.login.error": "En tant qu'utilisateur {allowedDomain}, vous devez vous connecter avec votre {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré sont incorrects. Vous avez {remainingAttempts} tentatives de connexion avant que votre compte soit temporairement verrouillé.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
||||
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
||||
"login.page.title": "Connexion | {siteName}",
|
||||
"login.user.identity.label": "Nom d'utilisateur ou courriel",
|
||||
"login.password.label": "Mot de passe",
|
||||
"sign.in.button": "Connexion",
|
||||
"forgot.password": "Mot de passe oublié",
|
||||
"institution.login.button": "Informations d'identification de l'établissement/du campus",
|
||||
"institution.login.page.title": "Connectez vous avec les informations d'identification d'institution ou de campus",
|
||||
"login.other.options.heading": "Ou se connecter avec :",
|
||||
"non.compliant.password.title": "Nous avons récemment modifié nos exigences en matière de mot de passe",
|
||||
"non.compliant.password.message": "Votre mot de passe actuel ne répond pas aux nouvelles exigences de sécurité. Nous venons d'envoyer un message de réinitialisation de mot de passe à l'adresse courriel associée à ce compte. Merci de nous aider à protéger vos données.",
|
||||
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
|
||||
"enterprise.login.btn.text": "Informations d'identification de la compagnie ou de l'école",
|
||||
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 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",
|
||||
"recommendation.option.trending": "Tendances actuelles",
|
||||
"recommendation.option.popular": "Le plus populaire",
|
||||
"recommendation.option.recommended.for.you": "Recommandé pour vous",
|
||||
"recommendation.product-card.pill-text.course": "Cours",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Attestation professionnelle",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offert à titre émérite",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offert via Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Abonnement",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Ouvre un lien dans un nouvel onglet",
|
||||
"register.page.title": "S'inscrire | {siteName}",
|
||||
"registration.fullname.label": "Nom complet",
|
||||
"registration.email.label": "Courriel",
|
||||
"registration.username.label": "Nom d'utilisateur",
|
||||
"registration.password.label": "Mot de passe",
|
||||
"registration.country.label": "Pays/Région",
|
||||
"registration.opt.in.label": "{siteName} peux m'envoyer des messages de marketing.",
|
||||
"help.text.name": "Ce nom sera utilisé pour toutes les attestations que vous obtiendrez.",
|
||||
"help.text.username.1": "Le nom qui vous identifiera dans vos cours.",
|
||||
"help.text.username.2": "Cela ne peut pas être modifié ultérieurement.",
|
||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Informations d'identification de l'établissement/du campus",
|
||||
"register.institution.login.page.title": "Inscription avec les informations d'identification d'institution ou de campus",
|
||||
"empty.name.field.error": "Saisissez votre nom complet",
|
||||
"empty.email.field.error": "Saisissez votre courriel",
|
||||
"empty.username.field.error": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"empty.password.field.error": "Les critères de mot de passe n'ont pas été remplis",
|
||||
"empty.country.field.error": "Sélectionnez votre pays ou région de résidence",
|
||||
"email.do.not.match": "Les adresses courriel ne correspondent pas.",
|
||||
"email.invalid.format.error": "Entrez une adresse de courriel valide",
|
||||
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"name.validation.message": "Entrez un nom valide",
|
||||
"username.format.validation.message": "Les noms d'utilisateur ne peuvent contenir que des lettres (A-Z, a-z), des chiffres (0-9), des traits de soulignement (_) et des traits d'union (-). Les noms d'utilisateur ne peuvent pas contenir d'espaces",
|
||||
"registration.request.failure.header": "Nous n'avons pas pu créer votre compte.",
|
||||
"registration.empty.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
||||
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
|
||||
"registration.tpa.session.expired": "L'inscription à l'aide de {provider} a expiré.",
|
||||
"registration.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Conditions générales du service et code d'honneur",
|
||||
"privacy.policy": "Politique de confidentialité",
|
||||
"honor.code": "Code d'honneur",
|
||||
"terms.of.service": "Conditions générales du service",
|
||||
"registration.username.suggestion.label": "Suggéré :",
|
||||
"did.you.mean.alert.text": "Vouliez-vous dire",
|
||||
"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.",
|
||||
"start.learning": "Commencez à apprendre",
|
||||
"with.site.name": "avec {siteName}",
|
||||
"your.career.turning.point": "Un tournant dans votre carrière",
|
||||
"is.here": "est là.",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complet",
|
||||
"complete.your.profile.2": "votre profil",
|
||||
"register.page.terms.of.service.and.honor.code": "En créant un compte, vous acceptez le {tosAndHonorCode} et vous reconnaissez que {platformName} et chaque membre traitent vos données personnelles conformément au {privacyPolicy}.",
|
||||
"register.page.honor.code": "J'accepte le {tosAndHonorCode} {platformName}",
|
||||
"register.page.terms.of.service": "J'accepte les {termsOfService} {platformName}"
|
||||
}
|
||||
1
src/i18n/messages/he.json
Normal file
1
src/i18n/messages/he.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
181
src/i18n/messages/hi.json
Normal file
181
src/i18n/messages/hi.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "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} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"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.",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
1
src/i18n/messages/id.json
Normal file
1
src/i18n/messages/id.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
181
src/i18n/messages/it.json
Normal file
181
src/i18n/messages/it.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "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} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"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.",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/it_IT.json
Normal file
181
src/i18n/messages/it_IT.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"error.notfound.message": "La pagina che stai cercando non è disponibile o si è verificato un errore nell'URL. Controlla l'URL e riprova. ",
|
||||
"institution.login.page.sub.heading": "Scegli il tuo istituto dall'elenco sottostante",
|
||||
"logistration.sign.in": "Accedi",
|
||||
"logistration.register": "Registrazione",
|
||||
"enterprisetpa.title.heading": "Vuoi accedere utilizzando le credenziali {providerName}?",
|
||||
"enterprisetpa.login.button.text": "Mostrami altre modalità di accesso o registrazione",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Accedi con {providerName}",
|
||||
"sso.create.account.using": "Crea un account utilizzando {providerName}",
|
||||
"show.password": "Mostra password",
|
||||
"hide.password": "Nascondi password",
|
||||
"one.letter": "1 lettera",
|
||||
"one.number": "1 numero",
|
||||
"eight.characters": "8 caratteri",
|
||||
"password.sr.only.helping.text": "La password deve contenere almeno 8 caratteri, almeno una lettera e almeno un numero",
|
||||
"tpa.alert.heading": "Quasi fatto!",
|
||||
"login.third.party.auth.account.not.linked": "Hai correttamente effettuato l'accesso in {currentProvider}, ma il tuo account {currentProvider} non ha un account {platformName} ad esso abbinato. Per collegare i tuoi account accesi utilizzando la password {platformName}. ",
|
||||
"register.third.party.auth.account.not.linked": "Hai eseguito correttamente l'accesso a {a03f0f8cfb85cz0}! Abbiamo solo bisogno di un po' più di informazioni prima di iniziare a imparare con {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Completa la creazione del tuo account",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Dimenticato la password | {siteName}",
|
||||
"forgot.password.page.heading": "Resetta la password",
|
||||
"forgot.password.page.instructions": "Inserisci il tuo indirizzo e-mail qui sotto e ti invieremo un'e-mail con le istruzioni su come reimpostare la tua password.",
|
||||
"forgot.password.page.invalid.email.message": "Inserisci un indirizzo email valido",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Invia",
|
||||
"forgot.password.error.alert.title.": "Non siamo stati in grado di contattarti.",
|
||||
"forgot.password.error.message.title": "Si è verificato un errore. ",
|
||||
"forgot.password.request.in.progress.message": "La tua richiesta precedente è in corso di elaborazione, riprova tra qualche istante. ",
|
||||
"forgot.password.empty.email.field.error": "Inserisci il tuo indirizzo email",
|
||||
"forgot.password.email.help.text": "L'indirizzo email che hai utilizzato per registrarti con {platformName}",
|
||||
"confirmation.message.title": "Controlla la tua casella di posta",
|
||||
"confirmation.support.link": "contatta il supporto tecnico",
|
||||
"need.help.sign.in.text": "Hai bisogno di aiuto per l'accesso? ",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"sign.in.text": "Accedi",
|
||||
"extend.field.errors": "{emailError} di seguito.",
|
||||
"invalid.token.heading": "Link di ripristino della password non valido",
|
||||
"invalid.token.error.message": "Questo link per reimpostare la password non è valido. Potrebbe essere stato già utilizzato. Inserisci la tua email qui sotto per ricevere un nuovo link.",
|
||||
"token.validation.rate.limit.error.heading": "Troppe richieste",
|
||||
"token.validation.rate.limit.error": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi.",
|
||||
"token.validation.internal.sever.error.heading": "Errore di convalida del token",
|
||||
"token.validation.internal.sever.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"internal.server.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"account.activation.error.message": "Si è verificato un errore, seleziona {supportLink} per risolvere il problema. ",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Se hai dimenticato la password, {resetLink}",
|
||||
"account.locked.out.message.2": "Per sicurezza, puoi {resetLink} prima di riprovare.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "Il nome utente, l'e-mail o la password inseriti non sono corretti. Riprova o {resetLink}.",
|
||||
"login.page.title": "Accesso | {siteName}",
|
||||
"login.user.identity.label": "Nome utente o email ",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Accedi",
|
||||
"forgot.password": "Password dimenticata",
|
||||
"institution.login.button": "Credenziali dell'istituto/campus",
|
||||
"institution.login.page.title": "Accedi con le credenziali dell'istituzione/campus",
|
||||
"login.other.options.heading": "Oppure accedi con:",
|
||||
"non.compliant.password.title": "Abbiamo di recente modificato i requisiti per la password ",
|
||||
"non.compliant.password.message": "La tua password attuale non soddisfa i nuovi requisiti di sicurezza. Abbiamo appena inviato un messaggio di reimpostazione della password all'indirizzo e-mail associato a questo account. Grazie per averci aiutato a mantenere i tuoi dati al sicuro.",
|
||||
"account.locked.out.message.1": "Per proteggere il tuo account, è stato temporaneamente bloccato. Riprova tra 30 minuti.",
|
||||
"enterprise.login.btn.text": "Credenziali aziendali o scolastiche",
|
||||
"username.or.email.format.validation.less.chars.message": "Il nome utente o l'e-mail deve contenere almeno 3 caratteri.",
|
||||
"email.validation.message": "Inserisci il tuo nome utente o e-mail",
|
||||
"password.validation.message": "I criteri della password non sono stati soddisfatti",
|
||||
"account.activation.success.message.title": "Completato correttamente! Hai attivato il tuo account. ",
|
||||
"account.activation.success.message": "A breve ti invieremo avvisi e aggiornamenti via email relativi al corso a cui ti sei iscritto. Accedi per proseguire.",
|
||||
"account.activation.info.message": "Questo account è già stato attivato.",
|
||||
"account.activation.error.message.title": "Impossibile attivare il tuo account.",
|
||||
"account.activation.support.link": "contatta il supporto",
|
||||
"account.confirmation.success.message.title": "Successo! Hai confermato la tua email.",
|
||||
"account.confirmation.success.message": "Accedi per continuare.",
|
||||
"account.confirmation.info.message": "Questa email è già stata confermata.",
|
||||
"account.confirmation.error.message.title": "Impossibile confermare la tua email",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"login.rate.limit.reached.message": "Troppi tentativi di login falliti. Riprova più tardi.",
|
||||
"login.failure.header.title": "Impossibile autorizzare il tuo accesso.",
|
||||
"contact.support.link": "contatta il supporto {platformName} ",
|
||||
"login.incorrect.credentials.error": "Il nome utente, l'e-mail o la password inseriti non sono corretti. Per favore riprova.",
|
||||
"login.form.invalid.error.message": "Si prega di compilare i campi sottostanti.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reimposta la tua password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "clicca qui per ripristinarlo.",
|
||||
"password.security.nudge.title": "Sicurezza della password",
|
||||
"password.security.block.title": "È richiesta la modifica della password",
|
||||
"password.security.nudge.body": "Il nostro sistema ha rilevato che la tua password è vulnerabile. Ti consigliamo di cambiarlo in modo che il tuo account rimanga sicuro.",
|
||||
"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": "Benvenuto | {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.",
|
||||
"optional.fields.submit.button": "Invia",
|
||||
"optional.fields.skip.button": "Salta per ora",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continua con {platformName}",
|
||||
"modal.title": "Grazie per averci fatto sapere.",
|
||||
"modal.description": "Puoi completare il tuo profilo nelle impostazioni in qualsiasi momento se cambi idea.",
|
||||
"welcome.page.error.heading": "Impossibile aggiornare il tuo profilo",
|
||||
"welcome.page.error.message": "Si è verificato un errore. Puoi completare il tuo profilo nelle impostazioni in qualsiasi momento.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Registrazione | {siteName}",
|
||||
"registration.fullname.label": "Nome e Cognome",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Nome utente pubblico",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Paese/regione",
|
||||
"registration.opt.in.label": "Accetto che {siteName} possa inviarmi messaggi di marketing.",
|
||||
"help.text.name": "Questo nome verrà utilizzato per tutti i certificati conseguiti.",
|
||||
"help.text.username.1": "Il nome che ti identificherà nei tuoi corsi.",
|
||||
"help.text.username.2": "Questo non può essere modificato in seguito.",
|
||||
"help.text.email": "Per l'attivazione dell'account e aggiornamenti importanti",
|
||||
"create.account.for.free.button": "Crea un account gratis",
|
||||
"registration.other.options.heading": "Oppure registrati con:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Credenziali dell'istituto/campus",
|
||||
"register.institution.login.page.title": "Registrati con le credenziali dell'istituzione/campus",
|
||||
"empty.name.field.error": "Inserisci il tuo nome e cognome",
|
||||
"empty.email.field.error": "Inserisci il tuo indirizzo email",
|
||||
"empty.username.field.error": "Il nome utente deve essere compreso tra 2 e 30 caratteri",
|
||||
"empty.password.field.error": "I criteri della password non sono stati soddisfatti",
|
||||
"empty.country.field.error": "Seleziona il tuo paese o regione di residenza",
|
||||
"email.do.not.match": "Gli indirizzi email non corrispondono.",
|
||||
"email.invalid.format.error": "Inserisci un indirizzo email valido",
|
||||
"username.validation.message": "Il nome utente deve essere compreso tra 2 e 30 caratteri",
|
||||
"name.validation.message": "Inserisci un nome valido",
|
||||
"username.format.validation.message": "I nomi utente possono contenere solo lettere (AZ, az), numeri (0-9), trattini bassi (_) e trattini (-). I nomi utente non possono contenere spazi",
|
||||
"registration.request.failure.header": "Impossibile creare il tuo account.",
|
||||
"registration.empty.form.submission.error": "Controlla le tue risposte e riprova.",
|
||||
"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",
|
||||
"terms.of.service": "Termini di Servizio",
|
||||
"registration.username.suggestion.label": "Suggerito:",
|
||||
"did.you.mean.alert.text": "Intendevi",
|
||||
"sign.in": "Accedi",
|
||||
"reset.password.page.title": "Ripristina password | {siteName}",
|
||||
"reset.password": "Resetta la password",
|
||||
"reset.password.page.instructions": "Immettere e confermare la nuova password. ",
|
||||
"new.password.label": "Nuova password",
|
||||
"confirm.password.label": "Conferma password",
|
||||
"passwords.do.not.match": "le passwords non corrispondono",
|
||||
"confirm.your.password": "Conferma la tua password",
|
||||
"reset.password.failure.heading": "Impossibile ripristinare la tua password.",
|
||||
"reset.password.form.submission.error": "Controlla le tue risposte e riprova.",
|
||||
"reset.server.rate.limit.error": "Troppe richieste.",
|
||||
"reset.password.success.heading": "Ripristino della password completato.",
|
||||
"reset.password.success": "La tua password è stata resettata. Accedi al tuo account.",
|
||||
"rate.limit.error": "Si è verificato un errore dovuto alle troppe richieste. Prova di nuovo più tardi.",
|
||||
"start.learning": "Inizia a imparare",
|
||||
"with.site.name": "con {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Benvenuto in {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Completata",
|
||||
"complete.your.profile.2": "Il tuo profilo",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
1
src/i18n/messages/ko_kr.json
Normal file
1
src/i18n/messages/ko_kr.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
src/i18n/messages/pl.json
Normal file
1
src/i18n/messages/pl.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
181
src/i18n/messages/pt.json
Normal file
181
src/i18n/messages/pt.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "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} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"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.",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/pt_PT.json
Normal file
181
src/i18n/messages/pt_PT.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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": "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": "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": "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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Esqueceu a Senha | {siteName}",
|
||||
"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.": "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": "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": "contacto o suporte técnico",
|
||||
"need.help.sign.in.text": "Precisa de ajuda para entrar?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"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": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "Como utilizador do {allowedDomain}, deve iniciar sessão com o seu {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Se esqueceu a sua palavra-passe, {resetLink}",
|
||||
"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": "Nome de utilizador ou e-mail",
|
||||
"login.password.label": "Palavra-passe",
|
||||
"sign.in.button": "Iniciar Sessão",
|
||||
"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": "Ou faça login com:",
|
||||
"non.compliant.password.title": "Recentemente mudámos os nossos requisitos de palavra-passe",
|
||||
"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": "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": "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": "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": "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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Registar | {siteName}",
|
||||
"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:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"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": "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": "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": "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": "Código de Honra",
|
||||
"terms.of.service": "Termos do Serviço",
|
||||
"registration.username.suggestion.label": "Sugerido:",
|
||||
"did.you.mean.alert.text": "Quis dizer",
|
||||
"sign.in": "Iniciar sessão",
|
||||
"reset.password.page.title": "Redefinir Palavra-passe | {siteName}",
|
||||
"reset.password": "Redefinir palavra-passe",
|
||||
"reset.password.page.instructions": "Insira e confirme a sua nova palavra-passe.",
|
||||
"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.",
|
||||
"start.learning": "Começar a aprender",
|
||||
"with.site.name": "com {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Bem vindo a {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Concluído",
|
||||
"complete.your.profile.2": "o seu perfil",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
1
src/i18n/messages/pt_br.json
Normal file
1
src/i18n/messages/pt_br.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
181
src/i18n/messages/ru.json
Normal file
181
src/i18n/messages/ru.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "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} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"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.",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
1
src/i18n/messages/th.json
Normal file
1
src/i18n/messages/th.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
181
src/i18n/messages/uk.json
Normal file
181
src/i18n/messages/uk.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"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.",
|
||||
"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:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "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} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"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",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "Course",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"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.",
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
181
src/i18n/messages/zh_CN.json
Normal file
181
src/i18n/messages/zh_CN.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"error.notfound.message": "您访问的地址不存在或有误。请检查URL后重新尝试访问。",
|
||||
"institution.login.page.sub.heading": "从下面的列表中选择您的机构",
|
||||
"logistration.sign.in": "登录",
|
||||
"logistration.register": "注册",
|
||||
"enterprisetpa.title.heading": "您要使用 {providerName} 登录吗?",
|
||||
"enterprisetpa.login.button.text": "为我显示其他登录或注册方式",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "显示其他登录方式",
|
||||
"sso.sign.in.with": "使用 {providerName} 登录",
|
||||
"sso.create.account.using": "使用 {providerName} 创建帐户",
|
||||
"show.password": "显示密码",
|
||||
"hide.password": "隐藏密码",
|
||||
"one.letter": "1 个字母",
|
||||
"one.number": "1个数字",
|
||||
"eight.characters": "8个字符",
|
||||
"password.sr.only.helping.text": "密码必须包含至少 8 个字符、至少一个字母和至少一个数字",
|
||||
"tpa.alert.heading": "快完成了!",
|
||||
"login.third.party.auth.account.not.linked": "您已成功登录 {currentProvider},但您的 {currentProvider} 帐户没有关联的 {platformName} 帐户。要链接您的帐户,请立即使用您的 {platformName} 密码登录。",
|
||||
"register.third.party.auth.account.not.linked": "您已成功登录 {currentProvider}!在您开始学习 {platformName} 之前,我们只需要更多信息。",
|
||||
"registration.using.tpa.form.heading": "完成创建您的帐户",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password. If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "忘记密码 | {siteName}",
|
||||
"forgot.password.page.heading": "重置密码",
|
||||
"forgot.password.page.instructions": "请在下面输入您的电子邮件地址,我们将向您发送一封电子邮件,其中包含有关如何重置密码的说明。",
|
||||
"forgot.password.page.invalid.email.message": "输入一个有效的电子邮件地址",
|
||||
"forgot.password.page.email.field.label": "邮箱",
|
||||
"forgot.password.page.submit.button": "提交",
|
||||
"forgot.password.error.alert.title.": "我们无法联系到您。",
|
||||
"forgot.password.error.message.title": "发生了一个错误。",
|
||||
"forgot.password.request.in.progress.message": "你的前一个请求正在处理中,请稍后再尝试。",
|
||||
"forgot.password.empty.email.field.error": "输入你的电子邮箱",
|
||||
"forgot.password.email.help.text": "您用于注册 {platformName} 的电子邮件地址",
|
||||
"confirmation.message.title": "查收您的邮件",
|
||||
"confirmation.support.link": "联系技术支持",
|
||||
"need.help.sign.in.text": "需要帮助登录?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at",
|
||||
"sign.in.text": "登录",
|
||||
"extend.field.errors": "{emailError} 如下。",
|
||||
"invalid.token.heading": "密码重置链接无效",
|
||||
"invalid.token.error.message": "此密码重置链接无效。它可能已经被使用过。在下面输入您的电子邮件以接收新链接。",
|
||||
"token.validation.rate.limit.error.heading": "请求过多",
|
||||
"token.validation.rate.limit.error": "由于请求过多而发生错误。请稍后重试。",
|
||||
"token.validation.internal.sever.error.heading": "验证失败",
|
||||
"token.validation.internal.sever.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||
"internal.server.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||
"account.activation.error.message": "出了点问题,请联系{supportLink}解决这个问题。",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak} {lineBreak}We just sent an activation link to {email}. If you do not receive an email, check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "作为 {allowedDomain} 用户,您必须使用 {allowedDomain} {tpaLink} 登录。",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "如果您忘记了密码,{resetLink}",
|
||||
"account.locked.out.message.2": "为了安全起见,您可以先{resetLink}再试一次。",
|
||||
"login.incorrect.credentials.error.with.reset.link": "您输入的用户名、电子邮件或密码不正确。请重试或 {resetLink}。",
|
||||
"login.page.title": "登入 | {siteName}",
|
||||
"login.user.identity.label": "用户名或电子邮件",
|
||||
"login.password.label": "密码",
|
||||
"sign.in.button": "登录",
|
||||
"forgot.password": "忘记密码",
|
||||
"institution.login.button": "机构/校园凭证",
|
||||
"institution.login.page.title": "使用机构/校园凭据登录",
|
||||
"login.other.options.heading": "或登录:",
|
||||
"non.compliant.password.title": "我们最近更改了密码要求",
|
||||
"non.compliant.password.message": "您当前的密码不符合新的安全要求。我们刚刚向与此帐户关联的电子邮件地址发送了密码重置邮件。感谢您帮助我们保护您的数据安全。",
|
||||
"account.locked.out.message.1": "为了保护您的帐户,它已被暂时锁定。请在 30 分钟后重试。",
|
||||
"enterprise.login.btn.text": "单位或学校证书",
|
||||
"username.or.email.format.validation.less.chars.message": "用户名或电子邮件必须至少包含 3 个字符。",
|
||||
"email.validation.message": "输入您的用户名或电子邮件",
|
||||
"password.validation.message": "未满足密码条件",
|
||||
"account.activation.success.message.title": "成功!您已激活您的帐户。",
|
||||
"account.activation.success.message": "您现在将收到我们发送的与您注册的课程相关的电子邮件更新和提醒。登录以继续。",
|
||||
"account.activation.info.message": "本账号已经被激活。",
|
||||
"account.activation.error.message.title": "您的帐户无法激活",
|
||||
"account.activation.support.link": "请联系技术支持",
|
||||
"account.confirmation.success.message.title": "成功!您已确认您的电子邮件。",
|
||||
"account.confirmation.success.message": "登录并继续。",
|
||||
"account.confirmation.info.message": "此电子邮件已被确认。",
|
||||
"account.confirmation.error.message.title": "无法确认您的电子邮件",
|
||||
"tpa.account.link": "{provider} 帐户",
|
||||
"internal.server.error.message": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||
"login.rate.limit.reached.message": "失败次数超过限制,请稍后再试!",
|
||||
"login.failure.header.title": "登录失败。",
|
||||
"contact.support.link": "联系 {platformName} 支持",
|
||||
"login.incorrect.credentials.error": "您输入的用户名、电子邮件或密码不正确。请再试一次。",
|
||||
"login.form.invalid.error.message": "请填写以下字段。",
|
||||
"login.incorrect.credentials.error.reset.link.text": "重置你的密码",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "点击此处来重置它。",
|
||||
"password.security.nudge.title": "密码安全",
|
||||
"password.security.block.title": "需要更改密码",
|
||||
"password.security.nudge.body": "系统检测到您的密码存在漏洞。我们建议您更改它,以便您的帐户保持安全。",
|
||||
"password.security.block.body": "系统检测到您的密码存在漏洞。更改您的密码,以确保您的帐户安全。",
|
||||
"password.security.close.button": "关闭",
|
||||
"password.security.redirect.to.reset.password.button": "重置你的密码",
|
||||
"login.tpa.authentication.failure": "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": "欢迎来的 | {siteName}",
|
||||
"progressive.profiling.page.heading": "你的问题将会帮助我们服务做的更好。",
|
||||
"optional.fields.information.link": "详细了解我们如何使用这些信息。",
|
||||
"optional.fields.submit.button": "提交",
|
||||
"optional.fields.skip.button": "暂时跳过",
|
||||
"optional.fields.next.button": "下一节",
|
||||
"continue.to.platform": "继续{platformName}",
|
||||
"modal.title": "感谢您指点。",
|
||||
"modal.description": "如果您改变主意,可以随时在设置中完成您的个人资料。",
|
||||
"welcome.page.error.heading": "我们无法更新您的个人资料",
|
||||
"welcome.page.error.message": "发生错误。您可以随时在设置中完成您的个人资料。",
|
||||
"recommendation.page.title": "建议 | {siteName}",
|
||||
"recommendation.page.heading": "我们有一些建议可以帮助您入门。",
|
||||
"recommendation.skip.button": "暂时跳过",
|
||||
"recommendation.option.trending": "Trending Now",
|
||||
"recommendation.option.popular": "Most Popular",
|
||||
"recommendation.option.recommended.for.you": "Recommended For You",
|
||||
"recommendation.product-card.pill-text.course": "课程",
|
||||
"recommendation.product-card.pill-text.professional-certificate": "专业证书",
|
||||
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
|
||||
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
|
||||
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
|
||||
"recommendation.product-card.footer-text.subscription": "Subscription",
|
||||
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
|
||||
"register.page.title": "注册 | {siteName}",
|
||||
"registration.fullname.label": "全名",
|
||||
"registration.email.label": "邮箱",
|
||||
"registration.username.label": "公开用户名",
|
||||
"registration.password.label": "密码",
|
||||
"registration.country.label": "国家/地区",
|
||||
"registration.opt.in.label": "我同意 {siteName} 可以向我发送课程相关推广信息。",
|
||||
"help.text.name": "您获得的任何证书都将使用此名称。",
|
||||
"help.text.username.1": "此名称将会用于您在课程中的身份识别。",
|
||||
"help.text.username.2": "过后无法更改。",
|
||||
"help.text.email": "用于帐户激活和重要更新",
|
||||
"create.account.for.free.button": "免费创建一个帐户",
|
||||
"registration.other.options.heading": "或注册:",
|
||||
"create.account.cta.button": "{label}",
|
||||
"register.institution.login.button": "机构/院系验证",
|
||||
"register.institution.login.page.title": "使用机构/院系账户注册",
|
||||
"empty.name.field.error": "输入您的全名",
|
||||
"empty.email.field.error": "输入你的电子邮箱",
|
||||
"empty.username.field.error": "用户名必须介于 2 到 30 个字符之间",
|
||||
"empty.password.field.error": "未满足密码条件",
|
||||
"empty.country.field.error": "选择您居住的国家或地区",
|
||||
"email.do.not.match": "邮箱不一致。",
|
||||
"email.invalid.format.error": "输入一个有效的电子邮件地址",
|
||||
"username.validation.message": "用户名必须介于 2 到 30 个字符之间",
|
||||
"name.validation.message": "输入有效名称",
|
||||
"username.format.validation.message": "用户名只能包含字母(AZ、az)、数字(0-9)、下划线(_)和连字符(-)。用户名不能包含空格",
|
||||
"registration.request.failure.header": "无法创建账号。",
|
||||
"registration.empty.form.submission.error": "请检查您的回复并重试。",
|
||||
"registration.request.server.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||
"registration.rate.limit.error": "注册尝试失败次数过多。稍后再试。",
|
||||
"registration.tpa.session.expired": "使用{provider}注册已超时。",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "服务条款和诚信准则",
|
||||
"privacy.policy": "隐私政策",
|
||||
"honor.code": "规则",
|
||||
"terms.of.service": "服务条款",
|
||||
"registration.username.suggestion.label": "建议:",
|
||||
"did.you.mean.alert.text": "你的意思是",
|
||||
"sign.in": "登录",
|
||||
"reset.password.page.title": "重设密码 | {siteName}",
|
||||
"reset.password": "重置密码",
|
||||
"reset.password.page.instructions": "输入并确认你的新密码。",
|
||||
"new.password.label": "新密码",
|
||||
"confirm.password.label": "确认密码",
|
||||
"passwords.do.not.match": "密码不匹配",
|
||||
"confirm.your.password": "确认你的密码",
|
||||
"reset.password.failure.heading": "我们无法重置您的密码。",
|
||||
"reset.password.form.submission.error": "请检查您的回复并重试。",
|
||||
"reset.server.rate.limit.error": "请求过多。",
|
||||
"reset.password.success.heading": "密码重置完成。",
|
||||
"reset.password.success": "您的密码已重置。登录到您的帐户。",
|
||||
"rate.limit.error": "由于请求过多而发生错误。请稍后重试。",
|
||||
"start.learning": "开始学习",
|
||||
"with.site.name": "{siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "欢迎来到{siteName},{username}!",
|
||||
"complete.your.profile.1": "完成",
|
||||
"complete.your.profile.2": "个人资料",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}"
|
||||
}
|
||||
@@ -9,30 +9,29 @@ import PropTypes from 'prop-types';
|
||||
import { ACCOUNT_ACTIVATION_MESSAGE } from './data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const AccountActivationMessage = ({ messageType }) => {
|
||||
const AccountActivationMessage = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
if (!messageType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { messageType } = props;
|
||||
const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType;
|
||||
const activationOrConfirmation = getConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation';
|
||||
|
||||
const activationOrVerification = getConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation';
|
||||
|
||||
let activationMessage;
|
||||
let heading;
|
||||
|
||||
const iconMapping = {
|
||||
[ACCOUNT_ACTIVATION_MESSAGE.SUCCESS]: CheckCircle,
|
||||
[ACCOUNT_ACTIVATION_MESSAGE.ERROR]: Error,
|
||||
};
|
||||
|
||||
let activationMessage;
|
||||
let heading;
|
||||
switch (messageType) {
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: {
|
||||
heading = formatMessage(messages[`account.${activationOrConfirmation}.success.message.title`]);
|
||||
activationMessage = <span>{formatMessage(messages[`account.${activationOrConfirmation}.success.message`])}</span>;
|
||||
heading = formatMessage(messages[`account.${activationOrVerification}.success.message.title`]);
|
||||
activationMessage = <span>{formatMessage(messages[`account.${activationOrVerification}.success.message`])}</span>;
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.INFO: {
|
||||
activationMessage = formatMessage(messages[`account.${activationOrConfirmation}.info.message`]);
|
||||
activationMessage = formatMessage(messages[`account.${activationOrVerification}.info.message`]);
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
||||
@@ -42,7 +41,7 @@ const AccountActivationMessage = ({ messageType }) => {
|
||||
</Alert.Link>
|
||||
);
|
||||
|
||||
heading = formatMessage(messages[`account.${activationOrConfirmation}.error.message.title`]);
|
||||
heading = formatMessage(messages[`account.${activationOrVerification}.error.message.title`]);
|
||||
activationMessage = (
|
||||
<FormattedMessage
|
||||
id="account.activation.error.message"
|
||||
@@ -60,7 +59,7 @@ const AccountActivationMessage = ({ messageType }) => {
|
||||
return activationMessage ? (
|
||||
<Alert
|
||||
id="account-activation-message"
|
||||
className="mb-5"
|
||||
className="mb-4"
|
||||
variant={variant}
|
||||
icon={iconMapping[messageType]}
|
||||
>
|
||||
@@ -71,11 +70,7 @@ const AccountActivationMessage = ({ messageType }) => {
|
||||
};
|
||||
|
||||
AccountActivationMessage.propTypes = {
|
||||
messageType: PropTypes.string,
|
||||
};
|
||||
|
||||
AccountActivationMessage.defaultProps = {
|
||||
messageType: null,
|
||||
messageType: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AccountActivationMessage;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthService } from '@edx/frontend-platform/auth';
|
||||
@@ -23,35 +23,22 @@ import {
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
} from './data/constants';
|
||||
import messages from './messages';
|
||||
import { windowScrollTo } from '../data/utils';
|
||||
|
||||
const LoginFailureMessage = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { context, errorCode } = props.loginError;
|
||||
|
||||
const authService = getAuthService();
|
||||
const {
|
||||
context,
|
||||
errorCode,
|
||||
errorCount, // This is used to trigger the useEffect, facilitating the scrolling to the top.
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
}, [errorCode, errorCount]);
|
||||
|
||||
if (!errorCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let errorList;
|
||||
let resetLink = (
|
||||
<Hyperlink destination="reset" isInline>
|
||||
{formatMessage(messages['login.incorrect.credentials.error.reset.link.text'])}
|
||||
</Hyperlink>
|
||||
);
|
||||
|
||||
let errorMessage;
|
||||
switch (errorCode) {
|
||||
case NON_COMPLIANT_PASSWORD_EXCEPTION: {
|
||||
errorMessage = (
|
||||
errorList = (
|
||||
<>
|
||||
<strong>{formatMessage(messages['non.compliant.password.title'])}</strong>
|
||||
<p>{formatMessage(messages['non.compliant.password.message'])}</p>
|
||||
@@ -60,7 +47,7 @@ const LoginFailureMessage = (props) => {
|
||||
break;
|
||||
}
|
||||
case FORBIDDEN_REQUEST:
|
||||
errorMessage = <p>{formatMessage(messages['login.rate.limit.reached.message'])}</p>;
|
||||
errorList = <p>{formatMessage(messages['login.rate.limit.reached.message'])}</p>;
|
||||
break;
|
||||
case INACTIVE_USER: {
|
||||
const supportLink = (
|
||||
@@ -68,7 +55,7 @@ const LoginFailureMessage = (props) => {
|
||||
{formatMessage(messages['contact.support.link'], { platformName: context.platformName })}
|
||||
</a>
|
||||
);
|
||||
errorMessage = (
|
||||
errorList = (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="login.inactive.user.error"
|
||||
@@ -77,7 +64,7 @@ const LoginFailureMessage = (props) => {
|
||||
check your spam folders or {supportLink}."
|
||||
values={{
|
||||
lineBreak: <br />,
|
||||
email: <strong className="data-hj-suppress">{context.email}</strong>,
|
||||
email: <strong className="data-hj-suppress">{props.loginError.email}</strong>,
|
||||
supportLink,
|
||||
}}
|
||||
/>
|
||||
@@ -92,7 +79,7 @@ const LoginFailureMessage = (props) => {
|
||||
{formatMessage(messages['tpa.account.link'], { provider: context.provider })}
|
||||
</a>
|
||||
);
|
||||
errorMessage = (
|
||||
errorList = (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="allowed.domain.login.error"
|
||||
@@ -105,7 +92,7 @@ const LoginFailureMessage = (props) => {
|
||||
break;
|
||||
}
|
||||
case INVALID_FORM:
|
||||
errorMessage = <p>{formatMessage(messages['login.form.invalid.error.message'])}</p>;
|
||||
errorList = <p>{formatMessage(messages['login.form.invalid.error.message'])}</p>;
|
||||
break;
|
||||
case FAILED_LOGIN_ATTEMPT: {
|
||||
resetLink = (
|
||||
@@ -113,7 +100,7 @@ const LoginFailureMessage = (props) => {
|
||||
{formatMessage(messages['login.incorrect.credentials.error.before.account.blocked.text'])}
|
||||
</Hyperlink>
|
||||
);
|
||||
errorMessage = (
|
||||
errorList = (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
@@ -137,7 +124,7 @@ const LoginFailureMessage = (props) => {
|
||||
break;
|
||||
}
|
||||
case ACCOUNT_LOCKED_OUT: {
|
||||
errorMessage = (
|
||||
errorList = (
|
||||
<>
|
||||
<p>{formatMessage(messages['account.locked.out.message.1'])}</p>
|
||||
<p>
|
||||
@@ -154,9 +141,9 @@ const LoginFailureMessage = (props) => {
|
||||
}
|
||||
case INCORRECT_EMAIL_PASSWORD:
|
||||
if (context.failureCount <= 1) {
|
||||
errorMessage = <p>{formatMessage(messages['login.incorrect.credentials.error'])}</p>;
|
||||
errorList = <p>{formatMessage(messages['login.incorrect.credentials.error'])}</p>;
|
||||
} else if (context.failureCount === 2) {
|
||||
errorMessage = (
|
||||
errorList = (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="login.incorrect.credentials.error.with.reset.link"
|
||||
@@ -174,56 +161,60 @@ const LoginFailureMessage = (props) => {
|
||||
}
|
||||
return (
|
||||
<ChangePasswordPrompt
|
||||
redirectUrl={context.redirectUrl}
|
||||
redirectUrl={props.loginError.redirectUrl}
|
||||
variant="nudge"
|
||||
/>
|
||||
);
|
||||
case REQUIRE_PASSWORD_CHANGE:
|
||||
return <ChangePasswordPrompt />;
|
||||
case TPA_AUTHENTICATION_FAILURE:
|
||||
errorMessage = (
|
||||
<p>
|
||||
{formatMessage(messages['login.tpa.authentication.failure'], {
|
||||
platform_name: getConfig().SITE_NAME,
|
||||
lineBreak: <br />,
|
||||
errorMessage: context.errorMessage,
|
||||
})}
|
||||
errorList = (
|
||||
<p>{formatMessage(messages['login.tpa.authentication.failure'], {
|
||||
platform_name: getConfig().SITE_NAME,
|
||||
lineBreak: <br />,
|
||||
errorMessage: context.errorMessage,
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
break;
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
errorMessage = <p>{formatMessage(messages['internal.server.error.message'])}</p>;
|
||||
errorList = <p>{formatMessage(messages['internal.server.error.message'])}</p>;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert id="login-failure-alert" className="mb-5" variant="danger" icon={Error}>
|
||||
<Alert.Heading>{formatMessage(messages['login.failure.header.title'])}</Alert.Heading>
|
||||
{ errorMessage }
|
||||
{ errorList }
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
LoginFailureMessage.defaultProps = {
|
||||
context: {},
|
||||
loginError: {
|
||||
redirectUrl: null,
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
|
||||
LoginFailureMessage.propTypes = {
|
||||
context: PropTypes.shape({
|
||||
supportLink: PropTypes.string,
|
||||
platformName: PropTypes.string,
|
||||
tpaHint: PropTypes.string,
|
||||
provider: PropTypes.string,
|
||||
allowedDomain: PropTypes.string,
|
||||
remainingAttempts: PropTypes.number,
|
||||
failureCount: PropTypes.number,
|
||||
errorMessage: PropTypes.string,
|
||||
loginError: PropTypes.shape({
|
||||
context: PropTypes.shape({
|
||||
supportLink: PropTypes.string,
|
||||
platformName: PropTypes.string,
|
||||
tpaHint: PropTypes.string,
|
||||
provider: PropTypes.string,
|
||||
allowedDomain: PropTypes.string,
|
||||
remainingAttempts: PropTypes.number,
|
||||
failureCount: PropTypes.number,
|
||||
errorMessage: PropTypes.string,
|
||||
}),
|
||||
email: PropTypes.string,
|
||||
errorCode: PropTypes.string,
|
||||
redirectUrl: PropTypes.string,
|
||||
}),
|
||||
errorCode: PropTypes.string.isRequired,
|
||||
errorCount: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default LoginFailureMessage;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Form, StatefulButton,
|
||||
Form, Hyperlink, Icon, StatefulButton,
|
||||
} from '@openedx/paragon';
|
||||
import { Institution } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
@@ -14,26 +15,21 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import AccountActivationMessage from './AccountActivationMessage';
|
||||
import {
|
||||
backupLoginFormBegin,
|
||||
dismissPasswordResetBanner,
|
||||
loginRequest,
|
||||
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,
|
||||
ThirdPartyAuthAlert,
|
||||
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration,
|
||||
RenderInstitutionButton, SocialAuthProviders, ThirdPartyAuthAlert,
|
||||
} from '../common-components';
|
||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
||||
import {
|
||||
DEFAULT_STATE, PENDING_STATE, RESET_PAGE,
|
||||
DEFAULT_STATE, ENTERPRISE_LOGIN_URL, PENDING_STATE, RESET_PAGE,
|
||||
} from '../data/constants';
|
||||
import {
|
||||
getActivationStatus,
|
||||
@@ -41,313 +37,324 @@ import {
|
||||
getTpaHint,
|
||||
getTpaProvider,
|
||||
updatePathWithQueryParams,
|
||||
windowScrollTo,
|
||||
} from '../data/utils';
|
||||
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
||||
|
||||
const LoginPage = (props) => {
|
||||
const {
|
||||
backedUpFormData,
|
||||
loginErrorCode,
|
||||
loginErrorContext,
|
||||
loginResult,
|
||||
shouldBackupState,
|
||||
thirdPartyAuthContext: {
|
||||
providers,
|
||||
currentProvider,
|
||||
secondaryProviders,
|
||||
finishAuthUrl,
|
||||
platformName,
|
||||
errorMessage: thirdPartyErrorMessage,
|
||||
},
|
||||
thirdPartyAuthApiStatus,
|
||||
institutionLogin,
|
||||
showResetPasswordSuccessBanner,
|
||||
submitState,
|
||||
// Actions
|
||||
backupFormState,
|
||||
handleInstitutionLogin,
|
||||
getTPADataFromBackend,
|
||||
} = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const activationMsgType = getActivationStatus();
|
||||
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
password: this.props.loginFormData.password,
|
||||
emailOrUsername: this.props.loginFormData.emailOrUsername,
|
||||
errors: {
|
||||
emailOrUsername: this.props.loginFormData.errors.emailOrUsername,
|
||||
password: this.props.loginFormData.errors.password,
|
||||
},
|
||||
isSubmitted: false,
|
||||
};
|
||||
this.queryParams = getAllPossibleQueryParams();
|
||||
this.tpaHint = getTpaHint();
|
||||
}
|
||||
|
||||
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
|
||||
const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} });
|
||||
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
||||
const tpaHint = getTpaHint();
|
||||
|
||||
useEffect(() => {
|
||||
componentDidMount() {
|
||||
sendPageEvent('login_and_registration', 'login');
|
||||
}, []);
|
||||
const payload = { ...this.queryParams };
|
||||
|
||||
useEffect(() => {
|
||||
const payload = { ...queryParams };
|
||||
if (tpaHint) {
|
||||
payload.tpa_hint = tpaHint;
|
||||
if (this.tpaHint) {
|
||||
payload.tpa_hint = this.tpaHint;
|
||||
}
|
||||
getTPADataFromBackend(payload);
|
||||
}, [getTPADataFromBackend, queryParams, tpaHint]);
|
||||
/**
|
||||
* Backup the login form in redux when login page is toggled.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (shouldBackupState) {
|
||||
backupFormState({
|
||||
formFields: { ...formFields },
|
||||
errors: { ...errors },
|
||||
this.props.getThirdPartyAuthContext(payload);
|
||||
this.props.loginRequestReset();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.loginFormData && this.props.loginFormData !== nextProps.loginFormData) {
|
||||
// Ensuring browser's autofill user credentials get filled and their state persists in the redux store.
|
||||
const nextState = {
|
||||
emailOrUsername: nextProps.loginFormData.emailOrUsername || this.state.emailOrUsername,
|
||||
password: nextProps.loginFormData.password || this.state.password,
|
||||
};
|
||||
this.setState({
|
||||
...nextProps.loginFormData,
|
||||
...nextState,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}, [shouldBackupState, formFields, errors, backupFormState]);
|
||||
return true;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loginErrorCode) {
|
||||
setErrorCode(prevState => ({
|
||||
type: loginErrorCode,
|
||||
count: prevState.count + 1,
|
||||
context: { ...loginErrorContext },
|
||||
}));
|
||||
componentWillUnmount() {
|
||||
if (this.props.resetPassword) {
|
||||
this.props.loginRemovePasswordResetBanner();
|
||||
}
|
||||
}, [loginErrorCode, loginErrorContext]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (thirdPartyErrorMessage) {
|
||||
setErrorCode((prevState) => ({
|
||||
type: TPA_AUTHENTICATION_FAILURE,
|
||||
count: prevState.count + 1,
|
||||
context: {
|
||||
errorMessage: thirdPartyErrorMessage,
|
||||
getEnterPriseLoginURL() {
|
||||
return getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (this.props.resetPassword) {
|
||||
this.props.loginRemovePasswordResetBanner();
|
||||
}
|
||||
this.setState({ isSubmitted: true });
|
||||
const { emailOrUsername, password } = this.state;
|
||||
const emailValidationError = this.validateEmail(emailOrUsername);
|
||||
const passwordValidationError = this.validatePassword(password);
|
||||
|
||||
if (emailValidationError !== '' || passwordValidationError !== '') {
|
||||
this.props.setLoginFormData({
|
||||
errors: {
|
||||
emailOrUsername: emailValidationError,
|
||||
password: passwordValidationError,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}, [thirdPartyErrorMessage]);
|
||||
|
||||
const validateFormFields = (payload) => {
|
||||
const { emailOrUsername, password } = payload;
|
||||
const fieldErrors = { ...errors };
|
||||
|
||||
if (emailOrUsername === '') {
|
||||
fieldErrors.emailOrUsername = formatMessage(messages['email.validation.message']);
|
||||
} else if (emailOrUsername.length < 2) {
|
||||
fieldErrors.emailOrUsername = formatMessage(messages['username.or.email.format.validation.less.chars.message']);
|
||||
}
|
||||
if (password === '') {
|
||||
fieldErrors.password = formatMessage(messages['password.validation.message']);
|
||||
}
|
||||
|
||||
return { ...fieldErrors };
|
||||
};
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
if (showResetPasswordSuccessBanner) {
|
||||
props.dismissPasswordResetBanner();
|
||||
}
|
||||
|
||||
const formData = { ...formFields };
|
||||
const validationErrors = validateFormFields(formData);
|
||||
if (validationErrors.emailOrUsername || validationErrors.password) {
|
||||
setErrors({ ...validationErrors });
|
||||
setErrorCode(prevState => ({ type: INVALID_FORM, count: prevState.count + 1, context: {} }));
|
||||
});
|
||||
this.props.loginRequestFailure({
|
||||
errorCode: INVALID_FORM,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// add query params to the payload
|
||||
const payload = {
|
||||
email_or_username: formData.emailOrUsername,
|
||||
password: formData.password,
|
||||
...queryParams,
|
||||
email_or_username: emailOrUsername, password, ...this.queryParams,
|
||||
};
|
||||
props.loginRequest(payload);
|
||||
this.props.loginRequest(payload);
|
||||
};
|
||||
|
||||
const handleOnChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
||||
handleOnFocus = (e) => {
|
||||
const { errors } = this.state;
|
||||
errors[e.target.name] = '';
|
||||
this.props.setLoginFormData({
|
||||
errors,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnFocus = (event) => {
|
||||
const { name } = event.target;
|
||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||
handleOnBlur = (e) => {
|
||||
const payload = {
|
||||
[e.target.name]: e.target.value,
|
||||
};
|
||||
this.props.setLoginFormData(payload);
|
||||
};
|
||||
const trackForgotPasswordLinkClick = () => {
|
||||
|
||||
handleForgotPasswordLinkClickEvent = () => {
|
||||
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
||||
};
|
||||
|
||||
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
||||
validateEmail(email) {
|
||||
const { errors } = this.state;
|
||||
|
||||
if (tpaHint) {
|
||||
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
||||
return <Skeleton height={36} />;
|
||||
}
|
||||
|
||||
if (skipHintedLogin) {
|
||||
window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (provider) {
|
||||
return <EnterpriseSSO provider={provider} />;
|
||||
if (email === '') {
|
||||
errors.emailOrUsername = this.props.intl.formatMessage(messages['email.validation.message']);
|
||||
} else if (email.length < 3) {
|
||||
errors.emailOrUsername = this.props.intl.formatMessage(messages['username.or.email.format.validation.less.chars.message']);
|
||||
} else {
|
||||
errors.emailOrUsername = '';
|
||||
}
|
||||
return errors.emailOrUsername;
|
||||
}
|
||||
|
||||
if (institutionLogin) {
|
||||
validatePassword(password) {
|
||||
const { errors } = this.state;
|
||||
errors.password = password.length > 0 ? '' : this.props.intl.formatMessage(messages['password.validation.message']);
|
||||
|
||||
return errors.password;
|
||||
}
|
||||
|
||||
renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl) {
|
||||
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
|
||||
const isSocialAuthActive = !!providers.length && !currentProvider;
|
||||
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
||||
|
||||
return (
|
||||
<InstitutionLogistration
|
||||
secondaryProviders={secondaryProviders}
|
||||
headingTitle={formatMessage(messages['institution.login.page.title'])}
|
||||
/>
|
||||
<>
|
||||
{(isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive))
|
||||
&& (
|
||||
<div className="mt-4 mb-3 h4">
|
||||
{intl.formatMessage(messages['login.other.options.heading'])}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!isEnterpriseLoginDisabled && isSocialAuthActive) && (
|
||||
<Hyperlink className="btn btn-link btn-sm text-body p-0 mb-4" destination={this.getEnterPriseLoginURL()}>
|
||||
<Icon src={Institution} className="institute-icon" />
|
||||
{intl.formatMessage(messages['enterprise.login.btn.text'])}
|
||||
</Hyperlink>
|
||||
)}
|
||||
|
||||
{thirdPartyAuthApiStatus === PENDING_STATE ? (
|
||||
<Skeleton className="tpa-skeleton mb-3" height={30} count={2} />
|
||||
) : (
|
||||
<>
|
||||
{(isEnterpriseLoginDisabled && isInstitutionAuthActive) && (
|
||||
<RenderInstitutionButton
|
||||
onSubmitHandler={this.props.handleInstitutionLogin}
|
||||
buttonTitle={intl.formatMessage(messages['institution.login.button'])}
|
||||
/>
|
||||
)}
|
||||
{isSocialAuthActive && (
|
||||
<div className="row m-0">
|
||||
<SocialAuthProviders socialAuthProviders={providers} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['login.page.title'], { siteName: getConfig().SITE_NAME })}</title>
|
||||
</Helmet>
|
||||
<RedirectLogistration
|
||||
success={loginResult.success}
|
||||
redirectUrl={loginResult.redirectUrl}
|
||||
finishAuthUrl={finishAuthUrl}
|
||||
/>
|
||||
<div className="mw-xs mt-3 mb-2">
|
||||
<LoginFailureMessage
|
||||
errorCode={errorCode.type}
|
||||
errorCount={errorCode.count}
|
||||
context={errorCode.context}
|
||||
/>
|
||||
<ThirdPartyAuthAlert
|
||||
currentProvider={currentProvider}
|
||||
platformName={platformName}
|
||||
/>
|
||||
<AccountActivationMessage
|
||||
messageType={activationMsgType}
|
||||
/>
|
||||
{showResetPasswordSuccessBanner && <ResetPasswordSuccess />}
|
||||
<Form id="sign-in-form" name="sign-in-form">
|
||||
<FormGroup
|
||||
name="emailOrUsername"
|
||||
value={formFields.emailOrUsername}
|
||||
autoComplete="on"
|
||||
handleChange={handleOnChange}
|
||||
handleFocus={handleOnFocus}
|
||||
errorMessage={errors.emailOrUsername}
|
||||
floatingLabel={formatMessage(messages['login.user.identity.label'])}
|
||||
/>
|
||||
<PasswordField
|
||||
name="password"
|
||||
value={formFields.password}
|
||||
autoComplete="off"
|
||||
showScreenReaderText={false}
|
||||
showRequirements={false}
|
||||
handleChange={handleOnChange}
|
||||
handleFocus={handleOnFocus}
|
||||
errorMessage={errors.password}
|
||||
floatingLabel={formatMessage(messages['login.password.label'])}
|
||||
/>
|
||||
<StatefulButton
|
||||
name="sign-in"
|
||||
id="sign-in"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="login-button-width"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: formatMessage(messages['sign.in.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
/>
|
||||
<Link
|
||||
id="forgot-password"
|
||||
name="forgot-password"
|
||||
className="btn btn-link font-weight-500 text-body"
|
||||
to={updatePathWithQueryParams(RESET_PAGE)}
|
||||
onClick={trackForgotPasswordLinkClick}
|
||||
>
|
||||
{formatMessage(messages['forgot.password'])}
|
||||
</Link>
|
||||
<ThirdPartyAuth
|
||||
currentProvider={currentProvider}
|
||||
providers={providers}
|
||||
secondaryProviders={secondaryProviders}
|
||||
handleInstitutionLogin={handleInstitutionLogin}
|
||||
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
||||
isLoginPage
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const loginPageState = state.login;
|
||||
return {
|
||||
backedUpFormData: loginPageState.loginFormData,
|
||||
loginErrorCode: loginPageState.loginErrorCode,
|
||||
loginErrorContext: loginPageState.loginErrorContext,
|
||||
loginResult: loginPageState.loginResult,
|
||||
shouldBackupState: loginPageState.shouldBackupState,
|
||||
showResetPasswordSuccessBanner: loginPageState.showResetPasswordSuccessBanner,
|
||||
submitState: loginPageState.submitState,
|
||||
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
|
||||
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||
};
|
||||
};
|
||||
renderForm(
|
||||
currentProvider,
|
||||
providers,
|
||||
secondaryProviders,
|
||||
thirdPartyAuthContext,
|
||||
thirdPartyAuthApiStatus,
|
||||
submitState,
|
||||
intl,
|
||||
) {
|
||||
const activationMsgType = getActivationStatus();
|
||||
if (this.props.institutionLogin) {
|
||||
return (
|
||||
<InstitutionLogistration
|
||||
secondaryProviders={thirdPartyAuthContext.secondaryProviders}
|
||||
headingTitle={intl.formatMessage(messages['institution.login.page.title'])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const tpaAuthenticationError = {};
|
||||
if (thirdPartyAuthContext.errorMessage) {
|
||||
tpaAuthenticationError.context = {
|
||||
errorMessage: thirdPartyAuthContext.errorMessage,
|
||||
};
|
||||
tpaAuthenticationError.errorCode = TPA_AUTHENTICATION_FAILURE;
|
||||
}
|
||||
|
||||
LoginPage.propTypes = {
|
||||
backedUpFormData: PropTypes.shape({
|
||||
formFields: PropTypes.shape({}),
|
||||
errors: PropTypes.shape({}),
|
||||
}),
|
||||
loginErrorCode: PropTypes.string,
|
||||
loginErrorContext: PropTypes.shape({
|
||||
email: PropTypes.string,
|
||||
redirectUrl: PropTypes.string,
|
||||
context: PropTypes.shape({}),
|
||||
}),
|
||||
loginResult: PropTypes.shape({
|
||||
redirectUrl: PropTypes.string,
|
||||
success: PropTypes.bool,
|
||||
}),
|
||||
shouldBackupState: PropTypes.bool,
|
||||
showResetPasswordSuccessBanner: PropTypes.bool,
|
||||
submitState: PropTypes.string,
|
||||
thirdPartyAuthApiStatus: PropTypes.string,
|
||||
institutionLogin: PropTypes.bool.isRequired,
|
||||
thirdPartyAuthContext: PropTypes.shape({
|
||||
currentProvider: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
platformName: PropTypes.string,
|
||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
finishAuthUrl: PropTypes.string,
|
||||
}),
|
||||
// Actions
|
||||
backupFormState: PropTypes.func.isRequired,
|
||||
dismissPasswordResetBanner: PropTypes.func.isRequired,
|
||||
loginRequest: PropTypes.func.isRequired,
|
||||
getTPADataFromBackend: PropTypes.func.isRequired,
|
||||
handleInstitutionLogin: PropTypes.func.isRequired,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['login.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<RedirectLogistration
|
||||
success={this.props.loginResult.success}
|
||||
redirectUrl={this.props.loginResult.redirectUrl}
|
||||
finishAuthUrl={thirdPartyAuthContext.finishAuthUrl}
|
||||
/>
|
||||
<div className="mw-xs mt-3">
|
||||
<ThirdPartyAuthAlert
|
||||
currentProvider={thirdPartyAuthContext.currentProvider}
|
||||
platformName={thirdPartyAuthContext.platformName}
|
||||
/>
|
||||
{this.props.loginError ? <LoginFailureMessage loginError={this.props.loginError} /> : null}
|
||||
{thirdPartyAuthContext.errorMessage ? <LoginFailureMessage loginError={tpaAuthenticationError} /> : null}
|
||||
{submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null}
|
||||
{activationMsgType && <AccountActivationMessage messageType={activationMsgType} />}
|
||||
{this.props.resetPassword && !this.props.loginError ? <ResetPasswordSuccess /> : null}
|
||||
<Form name="sign-in-form" id="sign-in-form">
|
||||
<FormGroup
|
||||
name="emailOrUsername"
|
||||
value={this.state.emailOrUsername}
|
||||
autoComplete="on"
|
||||
handleChange={(e) => this.setState({ emailOrUsername: e.target.value, isSubmitted: false })}
|
||||
handleFocus={this.handleOnFocus}
|
||||
handleBlur={this.handleOnBlur}
|
||||
errorMessage={this.state.errors.emailOrUsername}
|
||||
floatingLabel={intl.formatMessage(messages['login.user.identity.label'])}
|
||||
/>
|
||||
<PasswordField
|
||||
name="password"
|
||||
value={this.state.password}
|
||||
autoComplete="off"
|
||||
showRequirements={false}
|
||||
handleChange={(e) => this.setState({ password: e.target.value, isSubmitted: false })}
|
||||
handleFocus={this.handleOnFocus}
|
||||
handleBlur={this.handleOnBlur}
|
||||
errorMessage={this.state.errors.password}
|
||||
floatingLabel={intl.formatMessage(messages['login.password.label'])}
|
||||
/>
|
||||
<StatefulButton
|
||||
name="sign-in"
|
||||
id="sign-in"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="login-button-width"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['sign.in.button']),
|
||||
pending: '',
|
||||
}}
|
||||
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)}
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl, submitState, thirdPartyAuthContext, thirdPartyAuthApiStatus,
|
||||
} = this.props;
|
||||
const { currentProvider, providers, secondaryProviders } = this.props.thirdPartyAuthContext;
|
||||
|
||||
if (this.tpaHint) {
|
||||
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
||||
return <Skeleton height={36} />;
|
||||
}
|
||||
const { provider, skipHintedLogin } = getTpaProvider(this.tpaHint, providers, secondaryProviders);
|
||||
if (skipHintedLogin) {
|
||||
window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl;
|
||||
return null;
|
||||
}
|
||||
return provider ? (<EnterpriseSSO provider={provider} intl={intl} />) : this.renderForm(
|
||||
currentProvider,
|
||||
providers,
|
||||
secondaryProviders,
|
||||
thirdPartyAuthContext,
|
||||
thirdPartyAuthApiStatus,
|
||||
submitState,
|
||||
intl,
|
||||
);
|
||||
}
|
||||
return this.renderForm(
|
||||
currentProvider,
|
||||
providers,
|
||||
secondaryProviders,
|
||||
thirdPartyAuthContext,
|
||||
thirdPartyAuthApiStatus,
|
||||
submitState,
|
||||
intl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LoginPage.defaultProps = {
|
||||
backedUpFormData: {
|
||||
formFields: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
loginResult: null,
|
||||
loginError: null,
|
||||
loginFormData: {
|
||||
emailOrUsername: '',
|
||||
password: '',
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
emailOrUsername: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
loginErrorCode: null,
|
||||
loginErrorContext: {},
|
||||
loginResult: {},
|
||||
shouldBackupState: false,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
resetPassword: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
thirdPartyAuthApiStatus: 'pending',
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
errorMessage: null,
|
||||
@@ -357,12 +364,68 @@ LoginPage.defaultProps = {
|
||||
},
|
||||
};
|
||||
|
||||
LoginPage.propTypes = {
|
||||
getThirdPartyAuthContext: PropTypes.func.isRequired,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func,
|
||||
}).isRequired,
|
||||
loginError: PropTypes.shape({}),
|
||||
loginRequest: PropTypes.func.isRequired,
|
||||
loginRequestFailure: PropTypes.func.isRequired,
|
||||
loginRequestReset: PropTypes.func.isRequired,
|
||||
setLoginFormData: PropTypes.func.isRequired,
|
||||
loginRemovePasswordResetBanner: PropTypes.func.isRequired,
|
||||
loginResult: PropTypes.shape({
|
||||
redirectUrl: PropTypes.string,
|
||||
success: PropTypes.bool,
|
||||
}),
|
||||
loginFormData: PropTypes.shape({
|
||||
emailOrUsername: PropTypes.string,
|
||||
password: PropTypes.string,
|
||||
errors: PropTypes.shape({
|
||||
emailOrUsername: PropTypes.string,
|
||||
password: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
resetPassword: PropTypes.bool,
|
||||
submitState: PropTypes.string,
|
||||
thirdPartyAuthApiStatus: PropTypes.string,
|
||||
thirdPartyAuthContext: PropTypes.shape({
|
||||
currentProvider: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
platformName: PropTypes.string,
|
||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
finishAuthUrl: PropTypes.string,
|
||||
}),
|
||||
institutionLogin: PropTypes.bool.isRequired,
|
||||
handleInstitutionLogin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const loginResult = loginRequestSelector(state);
|
||||
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
|
||||
const loginError = loginErrorSelector(state);
|
||||
const loginFormData = loginFormDataSelector(state);
|
||||
return {
|
||||
submitState: state.login.submitState,
|
||||
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||
loginError,
|
||||
loginResult,
|
||||
thirdPartyAuthContext,
|
||||
loginFormData,
|
||||
resetPassword: state.login.resetPassword,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
backupFormState: backupLoginFormBegin,
|
||||
dismissPasswordResetBanner,
|
||||
getThirdPartyAuthContext,
|
||||
loginRequest,
|
||||
getTPADataFromBackend: getThirdPartyAuthContext,
|
||||
loginRequestFailure,
|
||||
loginRequestReset,
|
||||
setLoginFormData,
|
||||
loginRemovePasswordResetBanner,
|
||||
},
|
||||
)(injectIntl(LoginPage));
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const BACKUP_LOGIN_DATA = new AsyncActionType('LOGIN', 'BACKUP_LOGIN_DATA');
|
||||
export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST');
|
||||
export const DISMISS_PASSWORD_RESET_BANNER = 'DISMISS_PASSWORD_RESET_BANNER';
|
||||
|
||||
// Backup login form data
|
||||
export const backupLoginForm = () => ({
|
||||
type: BACKUP_LOGIN_DATA.BASE,
|
||||
});
|
||||
|
||||
export const backupLoginFormBegin = (data) => ({
|
||||
type: BACKUP_LOGIN_DATA.BEGIN,
|
||||
payload: { ...data },
|
||||
});
|
||||
export const LOGIN_PERSIST_FORM_DATA = 'LOGIN_PERSIST_FORM_DATA';
|
||||
export const LOGIN_REMOVE_PASSWORD_RESET_BANNER = 'LOGIN_REMOVE_PASSWORD_RESET_BANNER';
|
||||
|
||||
// Login
|
||||
export const loginRequest = creds => ({
|
||||
@@ -34,6 +24,15 @@ export const loginRequestFailure = (loginError) => ({
|
||||
payload: { loginError },
|
||||
});
|
||||
|
||||
export const dismissPasswordResetBanner = () => ({
|
||||
type: DISMISS_PASSWORD_RESET_BANNER,
|
||||
export const loginRequestReset = () => ({
|
||||
type: LOGIN_REQUEST.RESET,
|
||||
});
|
||||
|
||||
export const setLoginFormData = (formData) => ({
|
||||
type: LOGIN_PERSIST_FORM_DATA,
|
||||
payload: { formData },
|
||||
});
|
||||
|
||||
export const loginRemovePasswordResetBanner = () => ({
|
||||
type: LOGIN_REMOVE_PASSWORD_RESET_BANNER,
|
||||
});
|
||||
|
||||
@@ -1,69 +1,64 @@
|
||||
import {
|
||||
BACKUP_LOGIN_DATA,
|
||||
DISMISS_PASSWORD_RESET_BANNER,
|
||||
LOGIN_REQUEST,
|
||||
} from './actions';
|
||||
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';
|
||||
|
||||
export const defaultState = {
|
||||
loginErrorCode: '',
|
||||
loginErrorContext: {},
|
||||
loginError: null,
|
||||
loginResult: {},
|
||||
resetPassword: false,
|
||||
loginFormData: {
|
||||
formFields: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
password: '',
|
||||
emailOrUsername: '',
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
emailOrUsername: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
shouldBackupState: false,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case BACKUP_LOGIN_DATA.BASE:
|
||||
return {
|
||||
...state,
|
||||
shouldBackupState: true,
|
||||
};
|
||||
case BACKUP_LOGIN_DATA.BEGIN:
|
||||
return {
|
||||
...defaultState,
|
||||
loginFormData: { ...action.payload },
|
||||
};
|
||||
case LOGIN_REQUEST.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
submitState: PENDING_STATE,
|
||||
resetPassword: false,
|
||||
};
|
||||
case LOGIN_REQUEST.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
loginResult: action.payload,
|
||||
};
|
||||
case LOGIN_REQUEST.FAILURE: {
|
||||
const { email, loginError, redirectUrl } = action.payload;
|
||||
case LOGIN_REQUEST.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loginErrorCode: loginError.errorCode,
|
||||
loginErrorContext: { ...loginError.context, email, redirectUrl },
|
||||
loginError: action.payload.loginError,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
}
|
||||
case LOGIN_REQUEST.RESET:
|
||||
return {
|
||||
...state,
|
||||
loginError: null,
|
||||
};
|
||||
case RESET_PASSWORD.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
showResetPasswordSuccessBanner: true,
|
||||
resetPassword: true,
|
||||
};
|
||||
case DISMISS_PASSWORD_RESET_BANNER: {
|
||||
case LOGIN_PERSIST_FORM_DATA: {
|
||||
const { formData } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
loginFormData: {
|
||||
...state.loginFormData,
|
||||
...formData,
|
||||
},
|
||||
};
|
||||
}
|
||||
case LOGIN_REMOVE_PASSWORD_RESET_BANNER: {
|
||||
return {
|
||||
...state,
|
||||
resetPassword: false,
|
||||
};
|
||||
}
|
||||
default:
|
||||
|
||||
20
src/login/data/selectors.js
Normal file
20
src/login/data/selectors.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const storeName = 'login';
|
||||
|
||||
export const loginSelector = state => ({ ...state[storeName] });
|
||||
|
||||
export const loginRequestSelector = createSelector(
|
||||
loginSelector,
|
||||
login => login.loginResult,
|
||||
);
|
||||
|
||||
export const loginErrorSelector = createSelector(
|
||||
loginSelector,
|
||||
login => login.loginError,
|
||||
);
|
||||
|
||||
export const loginFormDataSelector = createSelector(
|
||||
loginSelector,
|
||||
login => login.loginFormData,
|
||||
);
|
||||
@@ -1,154 +1,57 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { DEFAULT_REDIRECT_URL, DEFAULT_STATE, PENDING_STATE } from '../../../data/constants';
|
||||
import { RESET_PASSWORD } from '../../../reset-password';
|
||||
import { BACKUP_LOGIN_DATA, DISMISS_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from '../actions';
|
||||
import {
|
||||
LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER,
|
||||
} from '../actions';
|
||||
import reducer from '../reducers';
|
||||
|
||||
describe('login reducer', () => {
|
||||
const defaultState = {
|
||||
loginErrorCode: '',
|
||||
loginErrorContext: {},
|
||||
loginResult: {},
|
||||
loginFormData: {
|
||||
formFields: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
},
|
||||
shouldBackupState: false,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
};
|
||||
|
||||
it('should update state to show reset password success banner', () => {
|
||||
const action = {
|
||||
type: RESET_PASSWORD.SUCCESS,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(defaultState, action),
|
||||
).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
showResetPasswordSuccessBanner: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set the flag which keeps the login form data in redux state', () => {
|
||||
const action = {
|
||||
type: BACKUP_LOGIN_DATA.BASE,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(defaultState, action),
|
||||
).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
shouldBackupState: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should backup the login form data', () => {
|
||||
const payload = {
|
||||
formFields: {
|
||||
emailOrUsername: 'test@exmaple.com',
|
||||
password: 'test1',
|
||||
},
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
};
|
||||
const action = {
|
||||
type: BACKUP_LOGIN_DATA.BEGIN,
|
||||
payload,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(defaultState, action),
|
||||
).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
loginFormData: payload,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should update state to dismiss reset password banner', () => {
|
||||
const action = {
|
||||
type: DISMISS_PASSWORD_RESET_BANNER,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(defaultState, action),
|
||||
).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should start the login request', () => {
|
||||
const action = {
|
||||
type: LOGIN_REQUEST.BEGIN,
|
||||
};
|
||||
|
||||
expect(reducer(defaultState, action)).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
showResetPasswordSuccessBanner: false,
|
||||
submitState: PENDING_STATE,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set redirect url on login success action', () => {
|
||||
const payload = {
|
||||
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`,
|
||||
success: true,
|
||||
};
|
||||
const action = {
|
||||
type: LOGIN_REQUEST.SUCCESS,
|
||||
payload,
|
||||
};
|
||||
|
||||
expect(reducer(defaultState, action)).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
loginResult: payload,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set the error data on login request failure', () => {
|
||||
const payload = {
|
||||
loginError: {
|
||||
success: false,
|
||||
value: 'Email or password is incorrect.',
|
||||
errorCode: 'incorrect-email-or-password',
|
||||
context: {
|
||||
failureCount: 0,
|
||||
it('should set loginFormData', () => {
|
||||
const state = {
|
||||
loginFormData: {
|
||||
password: '',
|
||||
emailOrUsername: '',
|
||||
errors: {
|
||||
emailOrUsername: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
email: 'test@example.com',
|
||||
redirectUrl: '',
|
||||
resetPassword: false,
|
||||
};
|
||||
const formData = {
|
||||
password: 'johndoe',
|
||||
emailOrUsername: 'john@gmail.com',
|
||||
};
|
||||
const action = {
|
||||
type: LOGIN_REQUEST.FAILURE,
|
||||
payload,
|
||||
type: LOGIN_PERSIST_FORM_DATA,
|
||||
payload: { formData },
|
||||
};
|
||||
|
||||
expect(reducer(defaultState, action)).toEqual(
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
loginErrorCode: payload.loginError.errorCode,
|
||||
loginErrorContext: { ...payload.loginError.context, email: payload.email, redirectUrl: payload.redirectUrl },
|
||||
submitState: DEFAULT_STATE,
|
||||
loginFormData: {
|
||||
...state.loginFormData,
|
||||
password: 'johndoe',
|
||||
emailOrUsername: 'john@gmail.com',
|
||||
},
|
||||
resetPassword: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set resetPassword', () => {
|
||||
const state = {
|
||||
resetPassword: true,
|
||||
};
|
||||
const action = {
|
||||
type: LOGIN_REMOVE_PASSWORD_RESET_BANNER,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
resetPassword: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export const storeName = 'login';
|
||||
|
||||
export { default as LoginPage } from './LoginPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
|
||||
@@ -42,6 +42,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Choose your institution from the list below',
|
||||
description: 'Heading of the institutions list',
|
||||
},
|
||||
'login.other.options.heading': {
|
||||
id: 'login.other.options.heading',
|
||||
defaultMessage: 'Or sign in with:',
|
||||
description: 'Text that appears above other sign in options like social auth buttons',
|
||||
},
|
||||
'non.compliant.password.title': {
|
||||
id: 'non.compliant.password.title',
|
||||
defaultMessage: 'We recently changed our password requirements',
|
||||
@@ -59,10 +64,15 @@ const messages = defineMessages({
|
||||
defaultMessage: 'To protect your account, it\'s been temporarily locked. Try again in 30 minutes.',
|
||||
description: 'Part of message for when user account has been locked out after multiple failed login attempts',
|
||||
},
|
||||
'enterprise.login.btn.text': {
|
||||
id: 'enterprise.login.btn.text',
|
||||
defaultMessage: 'Company or school credentials',
|
||||
description: 'Company or school login link text.',
|
||||
},
|
||||
'username.or.email.format.validation.less.chars.message': {
|
||||
id: 'username.or.email.format.validation.less.chars.message',
|
||||
defaultMessage: 'Username or email must have at least 2 characters.',
|
||||
description: 'Validation message that appears when username or email address is less than 2 characters',
|
||||
defaultMessage: 'Username or email must have at least 3 characters.',
|
||||
description: 'Validation message that appears when username or email address is less than 3 characters',
|
||||
},
|
||||
'email.validation.message': {
|
||||
id: 'email.validation.message',
|
||||
|
||||
@@ -2,9 +2,7 @@ import React from 'react';
|
||||
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
render, screen,
|
||||
} from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import AccountActivationMessage from '../AccountActivationMessage';
|
||||
import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants';
|
||||
@@ -19,22 +17,18 @@ describe('AccountActivationMessage', () => {
|
||||
});
|
||||
|
||||
it('should match account already activated message', () => {
|
||||
render(
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'This account has already been activated.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#account-activation-message' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(accountActivationMessage.find('#account-activation-message').find('div').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match account activated success message', () => {
|
||||
render(
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
|
||||
</IntlProvider>,
|
||||
@@ -43,15 +37,11 @@ describe('AccountActivationMessage', () => {
|
||||
const expectedMessage = 'Success! You have activated your account.'
|
||||
+ 'You will now receive email updates and alerts from us related to '
|
||||
+ 'the courses you are enrolled in. Sign in to continue.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#account-activation-message' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(accountActivationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match account activation error message', () => {
|
||||
render(
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
|
||||
</IntlProvider>,
|
||||
@@ -59,22 +49,17 @@ describe('AccountActivationMessage', () => {
|
||||
|
||||
const expectedMessage = 'Your account could not be activated'
|
||||
+ 'Something went wrong, please contact support to resolve this issue.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#account-activation-message' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(accountActivationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should not display anything for invalid message type', () => {
|
||||
const { container } = render(
|
||||
const accountActivationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType="invalid-message" />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const accountActivationMessage = container.querySelectorAll('#account-activation-message');
|
||||
expect(accountActivationMessage[0]).toBe(undefined);
|
||||
expect(accountActivationMessage).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,45 +71,36 @@ describe('EmailConfirmationMessage', () => {
|
||||
});
|
||||
|
||||
it('should match email already confirmed message', () => {
|
||||
render(
|
||||
const accountVerificationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'This email has already been confirmed.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#account-activation-message' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(accountVerificationMessage.find('#account-activation-message').find('div').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match email confirmation success message', () => {
|
||||
render(
|
||||
const accountVerificationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
const expectedMessage = 'Success! You have confirmed your email.Sign in to continue.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#account-activation-message' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
const expectedMessage = 'Success! You have confirmed your email.Sign in to continue.';
|
||||
expect(accountVerificationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match email confirmation error message', () => {
|
||||
render(
|
||||
const accountVerificationMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'Your email could not be confirmed'
|
||||
+ 'Something went wrong, please contact support to resolve this issue.';
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#account-activation-message' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(accountVerificationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,7 @@ import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
fireEvent, render, screen,
|
||||
} from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
@@ -41,7 +39,7 @@ describe('ChangePasswordPromptTests', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
render(
|
||||
const changePasswordPrompt = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlChangePasswordPrompt {...props} />
|
||||
@@ -49,7 +47,7 @@ describe('ChangePasswordPromptTests', () => {
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Close'));
|
||||
changePasswordPrompt.find('button#password-security-close').simulate('click');
|
||||
expect(window.location.href).toBe(dashboardUrl);
|
||||
});
|
||||
|
||||
@@ -58,7 +56,7 @@ describe('ChangePasswordPromptTests', () => {
|
||||
variant: 'block',
|
||||
};
|
||||
|
||||
render(
|
||||
const changePasswordPrompt = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlChangePasswordPrompt {...props} />
|
||||
@@ -67,12 +65,10 @@ describe('ChangePasswordPromptTests', () => {
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.pgn__modal-backdrop' },
|
||||
));
|
||||
await changePasswordPrompt.find('div.pgn__modal-backdrop').first().simulate('click');
|
||||
});
|
||||
|
||||
changePasswordPrompt.update();
|
||||
expect(mockedNavigator).toHaveBeenCalledWith(RESET_PAGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
render, screen,
|
||||
} from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
ACCOUNT_LOCKED_OUT,
|
||||
ALLOWED_DOMAIN_LOGIN_ERROR,
|
||||
FAILED_LOGIN_ATTEMPT,
|
||||
FORBIDDEN_REQUEST,
|
||||
@@ -42,11 +39,12 @@ describe('LoginFailureMessage', () => {
|
||||
|
||||
it('should match non compliant password error message', () => {
|
||||
props = {
|
||||
errorCode: NON_COMPLIANT_PASSWORD_EXCEPTION,
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: NON_COMPLIANT_PASSWORD_EXCEPTION,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
@@ -57,24 +55,22 @@ describe('LoginFailureMessage', () => {
|
||||
+ 'password-reset message to the email address associated with this account. '
|
||||
+ 'Thank you for helping us keep your data safe.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match inactive user error message', () => {
|
||||
props = {
|
||||
context: {
|
||||
loginError: {
|
||||
email: 'text@example.com',
|
||||
platformName: 'openedX',
|
||||
supportLink: 'http://support.openedx.test',
|
||||
errorCode: INACTIVE_USER,
|
||||
context: {
|
||||
platformName: 'openedX',
|
||||
supportLink: 'http://support.openedx.test',
|
||||
},
|
||||
},
|
||||
errorCode: INACTIVE_USER,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
@@ -84,196 +80,157 @@ describe('LoginFailureMessage', () => {
|
||||
+ 'We just sent an activation link to text@example.com. If you do not receive an email, '
|
||||
+ 'check your spam folders or contact openedX support.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
|
||||
expect(screen.getByRole('link', { name: 'contact openedX support' }).getAttribute('href')).toBe('http://support.openedx.test');
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').find('a').props().href).toEqual('http://support.openedx.test');
|
||||
});
|
||||
|
||||
it('test match failed login attempt error', () => {
|
||||
props = {
|
||||
context: {
|
||||
loginError: {
|
||||
email: 'text@example.com',
|
||||
remainingAttempts: 3,
|
||||
allowedFailureAttempts: 6,
|
||||
resetLink: '/reset',
|
||||
errorCode: FAILED_LOGIN_ATTEMPT,
|
||||
context: {
|
||||
remainingAttempts: 3,
|
||||
allowedFailureAttempts: 6,
|
||||
resetLink: '/reset',
|
||||
},
|
||||
},
|
||||
errorCode: FAILED_LOGIN_ATTEMPT,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.The username, email or password you entered is incorrect. '
|
||||
+ 'You have 3 more sign in attempts before your account is temporarily locked.If you\'ve forgotten your password, click here to reset it.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('test match failed login error first attempt', () => {
|
||||
props = {
|
||||
context: {
|
||||
loginError: {
|
||||
email: 'text@example.com',
|
||||
failureCount: 1,
|
||||
resetLink: '/reset',
|
||||
errorCode: INCORRECT_EMAIL_PASSWORD,
|
||||
context: {
|
||||
failureCount: 1,
|
||||
resetLink: '/reset',
|
||||
},
|
||||
},
|
||||
errorCode: INCORRECT_EMAIL_PASSWORD,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.The username, email, or password you entered is incorrect. Please try again.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
});
|
||||
|
||||
it('test match user account locked out', () => {
|
||||
props = {
|
||||
errorCode: ACCOUNT_LOCKED_OUT,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.To protect your account, it\'s been temporarily locked. Try again in 30 minutes.To be on the safe side, you can reset your password before trying again.';
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('test match failed login error second attempt', () => {
|
||||
props = {
|
||||
context: {
|
||||
loginError: {
|
||||
email: 'text@example.com',
|
||||
failureCount: 2,
|
||||
resetLink: '/reset',
|
||||
errorCode: INCORRECT_EMAIL_PASSWORD,
|
||||
context: {
|
||||
failureCount: 2,
|
||||
resetLink: '/reset',
|
||||
},
|
||||
},
|
||||
errorCode: INCORRECT_EMAIL_PASSWORD,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.The username, email, or password you entered is incorrect. Please try again or reset your password.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match rate limit error message', () => {
|
||||
props = {
|
||||
errorCode: FORBIDDEN_REQUEST,
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: FORBIDDEN_REQUEST,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.Too many failed login attempts. Try again later.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match internal server error message', () => {
|
||||
props = {
|
||||
errorCode: INTERNAL_SERVER_ERROR,
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match invalid form error message', () => {
|
||||
props = {
|
||||
errorCode: INVALID_FORM,
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: INVALID_FORM,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.Please fill in the fields below.';
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match internal server of error message', () => {
|
||||
props = {
|
||||
errorCode: 'invalid-error-code',
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: 'invalid-error-code',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toBe(expectedMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa authentication failed error message', () => {
|
||||
props = {
|
||||
errorCode: TPA_AUTHENTICATION_FAILURE,
|
||||
errorCount: 0,
|
||||
context: { errorMessage: 'An error occurred' },
|
||||
loginError: {
|
||||
errorCode: TPA_AUTHENTICATION_FAILURE,
|
||||
context: {
|
||||
errorMessage: 'An error occurred',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
@@ -281,24 +238,18 @@ describe('LoginFailureMessage', () => {
|
||||
|
||||
const expectedMessageSubstring = 'We are sorry, you are not authorized to access';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toContain(expectedMessageSubstring);
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toContain('An error occurred');
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toContain(expectedMessageSubstring);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toContain('An error occurred');
|
||||
});
|
||||
|
||||
it('should show modal that nudges users to change password', () => {
|
||||
props = {
|
||||
errorCode: NUDGE_PASSWORD_CHANGE,
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: NUDGE_PASSWORD_CHANGE,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
@@ -306,25 +257,21 @@ describe('LoginFailureMessage', () => {
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const message = 'Our system detected that your password is vulnerable. '
|
||||
+ 'We recommend you change it so that your account stays secure.';
|
||||
expect(screen.getByText(
|
||||
'Password security',
|
||||
{ selector: '.pgn__modal-title' },
|
||||
).textContent).toEqual('Password security');
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '.pgn__modal-body' },
|
||||
).textContent).toEqual(message);
|
||||
expect(loginFailureMessage.find('.pgn__modal-title').text()).toEqual('Password security');
|
||||
expect(loginFailureMessage.find('.pgn__modal-body').text()).toEqual(
|
||||
'Our system detected that your password is vulnerable. '
|
||||
+ 'We recommend you change it so that your account stays secure.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show modal that requires users to change password', () => {
|
||||
props = {
|
||||
errorCode: REQUIRE_PASSWORD_CHANGE,
|
||||
errorCount: 0,
|
||||
loginError: {
|
||||
errorCode: REQUIRE_PASSWORD_CHANGE,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
@@ -332,14 +279,8 @@ describe('LoginFailureMessage', () => {
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(
|
||||
'Password change required',
|
||||
{ selector: '.pgn__modal-title' },
|
||||
).textContent).toEqual('Password change required');
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '.pgn__modal-body' },
|
||||
).textContent).toEqual(
|
||||
expect(loginFailureMessage.find('.pgn__modal-title').text()).toEqual('Password change required');
|
||||
expect(loginFailureMessage.find('.pgn__modal-body').text()).toEqual(
|
||||
'Our system detected that your password is vulnerable. '
|
||||
+ 'Change your password so that your account stays secure.',
|
||||
);
|
||||
@@ -347,17 +288,18 @@ describe('LoginFailureMessage', () => {
|
||||
|
||||
it('should show message if staff user try to login through password', () => {
|
||||
props = {
|
||||
context: {
|
||||
loginError: {
|
||||
email: 'text@example.com',
|
||||
allowedDomain: 'test.com',
|
||||
provider: 'Google',
|
||||
tpaHint: 'google-auth2',
|
||||
errorCode: ALLOWED_DOMAIN_LOGIN_ERROR,
|
||||
context: {
|
||||
allowedDomain: 'test.com',
|
||||
provider: 'Google',
|
||||
tpaHint: 'google-auth2',
|
||||
},
|
||||
},
|
||||
errorCode: ALLOWED_DOMAIN_LOGIN_ERROR,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
render(
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
@@ -366,11 +308,7 @@ describe('LoginFailureMessage', () => {
|
||||
const errorMessage = "We couldn't sign you in.As test.com user, You must login with your test.com Google account.";
|
||||
const url = 'http://localhost:18000/dashboard/?tpa_hint=google-auth2';
|
||||
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toContain(errorMessage);
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Google account' }).getAttribute('href')).toBe(url);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(errorMessage);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').find('a').props().href).toEqual(url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,18 +2,19 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { sendPageEvent } from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
fireEvent, render, screen, waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
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, LOGIN_PAGE, PENDING_STATE } from '../../data/constants';
|
||||
import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from '../data/actions';
|
||||
import {
|
||||
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
|
||||
} from '../data/actions';
|
||||
import { INTERNAL_SERVER_ERROR } from '../data/constants';
|
||||
import LoginFailureMessage from '../LoginFailure';
|
||||
import LoginPage from '../LoginPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
@@ -24,14 +25,15 @@ jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthService: jest.fn(),
|
||||
}));
|
||||
|
||||
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
|
||||
const IntlLoginPage = injectIntl(LoginPage);
|
||||
const mockStore = configureStore();
|
||||
|
||||
describe('LoginPage', () => {
|
||||
let props = {};
|
||||
let store = {};
|
||||
let loginFormData = {};
|
||||
|
||||
const emptyFieldValidation = { emailOrUsername: 'Enter your username or email', password: 'Enter your password' };
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
@@ -81,151 +83,81 @@ describe('LoginPage', () => {
|
||||
handleInstitutionLogin: jest.fn(),
|
||||
institutionLogin: false,
|
||||
};
|
||||
loginFormData = {
|
||||
emailOrUsername: '',
|
||||
password: '',
|
||||
errors: {
|
||||
emailOrUsername: '',
|
||||
password: '',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// ******** test login form submission ********
|
||||
|
||||
it('should submit form for valid input', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
loginPage.find('input#emailOrUsername').simulate('change', { target: { value: 'test@example.com' } });
|
||||
loginPage.find('input#password').simulate('change', { target: { value: 'password' } });
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
fireEvent.change(screen.getByText(
|
||||
'',
|
||||
{ selector: '#emailOrUsername' },
|
||||
), { target: { value: 'test', name: 'emailOrUsername' } });
|
||||
fireEvent.change(screen.getByText(
|
||||
'',
|
||||
{ selector: '#password' },
|
||||
), { target: { value: 'test-password', name: 'password' } });
|
||||
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(loginRequest({ email_or_username: 'test', password: 'test-password' }));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(loginRequest({ email_or_username: 'test@example.com', password: 'password' }));
|
||||
});
|
||||
|
||||
it('should not dispatch loginRequest on empty form submission', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).not.toHaveBeenCalledWith(loginRequest({}));
|
||||
});
|
||||
|
||||
it('should dismiss reset password banner on form submission', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
showResetPasswordSuccessBanner: true,
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(dismissPasswordResetBanner());
|
||||
});
|
||||
|
||||
// ******** test login form validations ********
|
||||
|
||||
it('should match state for invalid email (less than 2 characters), on form submission', () => {
|
||||
it('should match state on empty form submission', () => {
|
||||
const errorState = { emailOrUsername: 'Enter your username or email', password: 'Enter your password' };
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
const loginPage = (mount(reduxWrapper(<IntlLoginPage {...props} />))).find('LoginPage');
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
fireEvent.change(screen.getByText(
|
||||
'',
|
||||
{ selector: '#password' },
|
||||
), { target: { value: 'test' } });
|
||||
fireEvent.change(screen.getByText(
|
||||
'',
|
||||
{ selector: '#emailOrUsername' },
|
||||
), { target: { value: 't' } });
|
||||
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
|
||||
expect(screen.getByText('Username or email must have at least 2 characters.')).toBeDefined();
|
||||
// Check that loginRequestFailure was dispatched and state is updated
|
||||
expect(loginPage.state('errors')).toEqual(errorState);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(loginRequestFailure({ errorCode: 'invalid-form' }));
|
||||
});
|
||||
|
||||
it('should show error messages for required fields on empty form submission', () => {
|
||||
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
|
||||
expect(container.querySelector('div[feedback-for="emailOrUsername"]').textContent).toEqual(emptyFieldValidation.emailOrUsername);
|
||||
expect(container.querySelector('div[feedback-for="password"]').textContent).toEqual(emptyFieldValidation.password);
|
||||
|
||||
const alertBanner = 'We couldn\'t sign you in.Please fill in the fields below.';
|
||||
expect(container.querySelector('#login-failure-alert').textContent).toEqual(alertBanner);
|
||||
});
|
||||
|
||||
it('should run frontend validations for emailOrUsername field on form submission', () => {
|
||||
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
fireEvent.change(screen.getByText(
|
||||
'',
|
||||
{ selector: '#emailOrUsername' },
|
||||
), { target: { value: 't', name: 'emailOrUsername' } });
|
||||
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
|
||||
expect(container.querySelector('div[feedback-for="emailOrUsername"]').textContent).toEqual('Username or email must have at least 2 characters.');
|
||||
});
|
||||
|
||||
// ******** test field focus in functionality ********
|
||||
it('should reset field related error messages on onFocus event', async () => {
|
||||
it('should match state for invalid email (less than 3 characters), on form submission', () => {
|
||||
const errorState = { emailOrUsername: 'Username or email must have at least 3 characters.', password: '' };
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
const loginPage = (mount(reduxWrapper(<IntlLoginPage {...props} />))).find('LoginPage');
|
||||
|
||||
await act(async () => {
|
||||
// clicking submit button with empty fields to make the errors appear
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '.btn-brand' },
|
||||
));
|
||||
loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } });
|
||||
loginPage.find('input#emailOrUsername').simulate('change', { target: { value: 'te', name: 'email' } });
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
// focusing the fields to verify that the errors are cleared
|
||||
fireEvent.focus(screen.getByText(
|
||||
'',
|
||||
{ selector: '#password' },
|
||||
));
|
||||
fireEvent.focus(screen.getByText(
|
||||
'',
|
||||
{ selector: '#emailOrUsername' },
|
||||
));
|
||||
});
|
||||
expect(loginPage.state('errors')).toEqual(errorState);
|
||||
});
|
||||
|
||||
// verifying that the errors are cleared
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Enter your username or email')).toBeNull();
|
||||
});
|
||||
it('should reset field related error messages on onFocus event', () => {
|
||||
const errorState = { emailOrUsername: '', password: '' };
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const loginPage = (mount(reduxWrapper(<IntlLoginPage {...props} />))).find('LoginPage');
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
loginPage.find('input#emailOrUsername').simulate('focus');
|
||||
loginPage.find('input#password').simulate('focus');
|
||||
expect(loginPage.state('errors')).toEqual(errorState);
|
||||
});
|
||||
|
||||
// ******** test form buttons and links ********
|
||||
|
||||
it('should match default button state', () => {
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText('Sign in')).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('button[type="submit"] span').first().text()).toEqual('Sign in');
|
||||
});
|
||||
|
||||
it('should match pending button state', () => {
|
||||
@@ -237,105 +169,97 @@ describe('LoginPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
const button = loginPage.find('button[type="submit"] span').first();
|
||||
|
||||
expect(screen.getByText(
|
||||
'pending',
|
||||
).textContent).toEqual('pending');
|
||||
expect(button.find('.sr-only').text()).toEqual('pending');
|
||||
});
|
||||
|
||||
it('should show forgot password link', () => {
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
expect(screen.getByText(
|
||||
'Forgot password',
|
||||
{ selector: '#forgot-password' },
|
||||
).textContent).toEqual('Forgot password');
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('a#forgot-password').text()).toEqual('Forgot password');
|
||||
});
|
||||
|
||||
it('should show single sign on provider button', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [ssoProvider],
|
||||
providers: [{
|
||||
...ssoProvider,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: `#${ssoProvider.id}` },
|
||||
)).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find(`button#${ssoProvider.id}`).length).toEqual(1);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should display sign-in header only when primary or secondary providers are available.', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Company or school credentials')).toBeNull();
|
||||
expect(queryByText('Or sign in with:')).toBeNull();
|
||||
expect(queryByText('Institution/campus credentials')).toBeNull();
|
||||
it('should not display institution login option when no secondary providers are present', () => {
|
||||
const root = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(root.text().includes('Use my university info')).toBe(false);
|
||||
});
|
||||
|
||||
it('should hide sign-in header and enterprise login upon successful SSO authentication', () => {
|
||||
it('should not show sign-in header and enterprise login once user authenticated through SSO', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [ssoProvider],
|
||||
secondaryProviders: [secondaryProviders],
|
||||
providers: [{
|
||||
...ssoProvider,
|
||||
}],
|
||||
currentProvider: 'Apple',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Company or school credentials')).toBeNull();
|
||||
expect(queryByText('Or sign in with:')).toBeNull();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Company or school credentials')).toBe(false);
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(false);
|
||||
});
|
||||
|
||||
// ******** test enterprise login enabled scenarios ********
|
||||
it('should show sign-in header providers (ENABLE ENTERPRISE LOGIN)', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
|
||||
it('should show sign-in header for enterprise login', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [ssoProvider],
|
||||
secondaryProviders: [secondaryProviders],
|
||||
providers: [{
|
||||
...ssoProvider,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Or sign in with:')).toBeDefined();
|
||||
expect(queryByText('Company or school credentials')).toBeDefined();
|
||||
expect(queryByText('Institution/campus credentials')).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(true);
|
||||
expect(loginPage.text().includes('Company or school credentials')).toBe(true);
|
||||
expect(loginPage.text().includes('Institution/campus credentials')).toBe(false);
|
||||
});
|
||||
|
||||
// ******** test enterprise login disabled scenarios ********
|
||||
|
||||
it('should show sign-in header for institution login if enterprise login is disabled', () => {
|
||||
it('should show sign-in header with providers (DISABLE ENTERPRISE LOGIN)', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: true,
|
||||
});
|
||||
@@ -346,16 +270,63 @@ describe('LoginPage', () => {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [ssoProvider],
|
||||
secondaryProviders: [secondaryProviders],
|
||||
providers: [{
|
||||
...ssoProvider,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Or sign in with:')).toBeDefined();
|
||||
expect(queryByText('Company or school credentials')).toBeNull();
|
||||
expect(queryByText('Institution/campus credentials')).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(true);
|
||||
expect(loginPage.text().includes('Company or school credentials')).toBe(false);
|
||||
expect(loginPage.text().includes('Institution/campus credentials')).toBe(false);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show sign-in header without Providers and secondary Providers (ENABLE ENTERPRISE LOGIN)', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(false);
|
||||
expect(loginPage.text().includes('Company or school credentials')).toBe(false);
|
||||
expect(loginPage.text().includes('Institution/campus credentials')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not show sign-in header without Providers and secondary Providers (DISABLE ENTERPRISE LOGIN)', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: true,
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(false);
|
||||
expect(loginPage.text().includes('Company or school credentials')).toBe(false);
|
||||
expect(loginPage.text().includes('Institution/campus credentials')).toBe(false);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
@@ -380,48 +351,40 @@ describe('LoginPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Or sign in with:')).toBeDefined();
|
||||
expect(queryByText('Institution/campus credentials')).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(true);
|
||||
expect(loginPage.text().includes('Institution/campus credentials')).toBe(true);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show sign-in header without primary or secondary providers', () => {
|
||||
it('should show sign-in header with Providers and secondary Providers', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: true,
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [{
|
||||
...ssoProvider,
|
||||
}],
|
||||
secondaryProviders: [{
|
||||
...secondaryProviders,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Or sign in with:')).toBeNull();
|
||||
expect(queryByText('Institution/campus credentials')).toBeNull();
|
||||
expect(queryByText('Company or school credentials')).toBeNull();
|
||||
});
|
||||
|
||||
it('should show enterprise login if even if only secondary providers are available', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(queryByText('Or sign in with:')).toBeDefined();
|
||||
expect(queryByText('Company or school credentials')).toBeNull();
|
||||
expect(queryByText('Institution/campus credentials')).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.text().includes('Or sign in with:')).toBe(true);
|
||||
expect(loginPage.text().includes('Company or school credentials')).toBe(false);
|
||||
expect(loginPage.text().includes('Institution/campus credentials')).toBe(true);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
@@ -430,22 +393,30 @@ describe('LoginPage', () => {
|
||||
|
||||
// ******** test alert messages ********
|
||||
|
||||
it('should match login internal server error message', () => {
|
||||
const expectedMessage = 'We couldn\'t sign you in.'
|
||||
+ 'An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||
it('should match login error message', () => {
|
||||
const errorMessage = 'An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginErrorCode: INTERNAL_SERVER_ERROR,
|
||||
loginError: { errorCode: INTERNAL_SERVER_ERROR },
|
||||
},
|
||||
});
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toEqual(`${expectedMessage}`);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#login-failure-alert').first().text()).toEqual(`We couldn't sign you in.${errorMessage}`);
|
||||
});
|
||||
|
||||
it('should match account activation message', () => {
|
||||
const activationMessage = 'Success! You have activated your account.'
|
||||
+ 'You will now receive email updates and alerts from us related '
|
||||
+ 'to the courses you are enrolled in. Sign in to continue.';
|
||||
|
||||
delete window.location;
|
||||
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);
|
||||
});
|
||||
|
||||
it('should match third party auth alert', () => {
|
||||
@@ -465,14 +436,11 @@ describe('LoginPage', () => {
|
||||
+ 'linked '}${ getConfig().SITE_NAME } account. To link your accounts, sign in now using your ${
|
||||
getConfig().SITE_NAME } password.`;
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#tpa-alert' },
|
||||
).textContent).toEqual(expectedMessage);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#tpa-alert').find('p').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should show third party authentication failure message', () => {
|
||||
it('should show tpa authentication fails error message', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
@@ -484,11 +452,9 @@ describe('LoginPage', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toContain('An error occurred');
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#login-failure-alert').find('p').text()).toContain('An error occurred');
|
||||
});
|
||||
|
||||
it('should match invalid login form error message', () => {
|
||||
@@ -497,36 +463,33 @@ describe('LoginPage', () => {
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginErrorCode: 'invalid-form',
|
||||
loginError: { errorCode: 'invalid-form' },
|
||||
},
|
||||
});
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: '#login-failure-alert' },
|
||||
).textContent).toContain(errorMessage);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#login-failure-alert p').first().text()).toEqual(errorMessage);
|
||||
});
|
||||
|
||||
// ******** test redirection ********
|
||||
|
||||
it('should redirect to url returned by login endpoint after successful authentication', () => {
|
||||
const dashboardURL = 'https://test.com/testing-dashboard/';
|
||||
it('should redirect to url returned by login endpoint', () => {
|
||||
const dashboardUrl = 'http://localhost:18000/enterprise/select/active/?success_url=/dashboard';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginResult: {
|
||||
success: true,
|
||||
redirectUrl: dashboardURL,
|
||||
redirectUrl: dashboardUrl,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(dashboardURL);
|
||||
renderer.create(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(dashboardUrl);
|
||||
});
|
||||
|
||||
it('should redirect to finishAuthUrl upon successful login via SSO', () => {
|
||||
@@ -552,18 +515,26 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
renderer.create(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
|
||||
});
|
||||
|
||||
it('should redirect to social auth provider url on SSO button click', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: 'true',
|
||||
});
|
||||
|
||||
const loginUrl = '/auth/login/apple-id/?auth_entry=login&next=/dashboard';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [ssoProvider],
|
||||
providers: [{
|
||||
...ssoProvider,
|
||||
loginUrl,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -571,37 +542,14 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
fireEvent.click(screen.getByText(
|
||||
'',
|
||||
{ selector: '#oa2-apple-id' },
|
||||
));
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + ssoProvider.loginUrl);
|
||||
});
|
||||
loginPage.find('button#oa2-apple-id').simulate('click');
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + loginUrl);
|
||||
|
||||
it('should redirect to finishAuthUrl upon successful authentication via SSO', () => {
|
||||
const finishAuthUrl = '/auth/complete/google-oauth2/';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginResult: { success: true, redirectUrl: '' },
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
finishAuthUrl,
|
||||
},
|
||||
},
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + finishAuthUrl);
|
||||
});
|
||||
|
||||
// ******** test hinted third party auth ********
|
||||
@@ -622,35 +570,9 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: `#${ssoProvider.id}` },
|
||||
).textContent).toEqual(ssoProvider.name);
|
||||
expect(screen.getByText(
|
||||
'',
|
||||
{ selector: `.btn-${ssoProvider.id}` },
|
||||
)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the skeleton when third party status is pending', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
providers: [ssoProvider],
|
||||
},
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(container.querySelector('.react-loading-skeleton')).toBeTruthy();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find(`button#${ssoProvider.id}`).find('span').text()).toEqual(ssoProvider.name);
|
||||
expect(loginPage.find(`button#${ssoProvider.id}`).hasClass(`btn-tpa btn-${ssoProvider.id}`)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should render tpa button for tpa_hint id matching one of the secondary providers', () => {
|
||||
@@ -671,11 +593,15 @@ describe('LoginPage', () => {
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
|
||||
secondaryProviders.iconImage = null;
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.loginUrl);
|
||||
});
|
||||
|
||||
it('should render regular tpa button for invalid tpa_hint value', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: 'true',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
@@ -691,15 +617,15 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(container.querySelector(`#${ssoProvider.id}`).querySelector('#provider-name').textContent).toEqual(`${ssoProvider.name}`);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find(`button#${ssoProvider.id}`).find('span#provider-name').text()).toEqual(`${ssoProvider.name}`);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should render "other ways to sign in" button on the tpa_hint page', () => {
|
||||
it('should render other ways to sign in button', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
@@ -715,17 +641,14 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'Show me other ways to sign in or register',
|
||||
).textContent).toBeDefined();
|
||||
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');
|
||||
});
|
||||
|
||||
it('should render other ways to sign in button when public account creation is disabled', () => {
|
||||
it('should render other ways to sign in button when public account creation disabled', () => {
|
||||
mergeConfig({
|
||||
ALLOW_PUBLIC_ACCOUNT_CREATION: false,
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
@@ -741,93 +664,106 @@ describe('LoginPage', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(screen.getByText(
|
||||
'Show me other ways to sign in',
|
||||
).textContent).toBeDefined();
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('button#other-ways-to-sign-in').text()).toEqual('Show me other ways to sign in');
|
||||
});
|
||||
|
||||
// ******** miscellaneous tests ********
|
||||
|
||||
it('should send page event when login page is rendered', () => {
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
|
||||
});
|
||||
|
||||
it('tests that form is in invalid state when it is submitted', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
shouldBackupState: true,
|
||||
},
|
||||
});
|
||||
it('tests that form is only scrollable on form submission', () => {
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin(
|
||||
{
|
||||
formFields: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
},
|
||||
));
|
||||
loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } });
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(loginPage.find(<IntlLoginFailureMessage />)).toBeTruthy();
|
||||
expect(loginPage.find('LoginPage').state('isSubmitted')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should send track event when forgot password link is clicked', () => {
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
fireEvent.click(screen.getByText(
|
||||
'Forgot password',
|
||||
{ selector: '#forgot-password' },
|
||||
));
|
||||
it('should reset login form errors', () => {
|
||||
const errorState = { emailOrUsername: '', password: '' };
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
||||
const loginPage = (mount(reduxWrapper(<IntlLoginPage {...props} />))).find('LoginPage');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(loginRequestReset());
|
||||
expect(loginPage.state('errors')).toEqual(errorState);
|
||||
});
|
||||
|
||||
it('should backup the login form state when shouldBackupState is true', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
shouldBackupState: true,
|
||||
},
|
||||
});
|
||||
// persists form data tests
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin(
|
||||
{
|
||||
formFields: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
it('should set errors in redux store on submit form for invalid input', () => {
|
||||
const formData = {
|
||||
errors: {
|
||||
emailOrUsername: 'Enter your username or email',
|
||||
password: 'Enter your password',
|
||||
},
|
||||
));
|
||||
};
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
|
||||
loginPage.find('input#emailOrUsername').simulate('change', { target: { value: '' } });
|
||||
loginPage.find('input#password').simulate('change', { target: { value: '' } });
|
||||
loginPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(setLoginFormData(formData));
|
||||
});
|
||||
|
||||
it('should set form data in redux store on onBlur', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
loginPage.find('input#emailOrUsername').simulate('blur');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(setLoginFormData({ emailOrUsername: '' }));
|
||||
});
|
||||
|
||||
it('should clear form field errors in redux store on onFocus', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
loginPage.find('input#emailOrUsername').simulate('focus');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(setLoginFormData({
|
||||
errors: {
|
||||
...loginFormData.errors,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should update form fields state if updated in redux store', () => {
|
||||
const nextProps = {
|
||||
loginFormData: {
|
||||
emailOrUsername: 'john_doe',
|
||||
password: 'password1',
|
||||
},
|
||||
};
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
loginPage.find('LoginPage').instance().shouldComponentUpdate(nextProps);
|
||||
|
||||
expect(loginPage.find('LoginPage').state('emailOrUsername')).toEqual('john_doe');
|
||||
expect(loginPage.find('LoginPage').state('password')).toEqual('password1');
|
||||
});
|
||||
|
||||
it('should update reset password value when unmount called', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginFormData: {
|
||||
formFields: {
|
||||
emailOrUsername: 'john_doe', password: 'test-password',
|
||||
},
|
||||
errors: {
|
||||
emailOrUsername: '', password: '',
|
||||
},
|
||||
},
|
||||
resetPassword: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(container.querySelector('input#emailOrUsername').value).toEqual('john_doe');
|
||||
expect(container.querySelector('input#password').value).toEqual('test-password');
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
loginPage.unmount();
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(loginRemovePasswordResetBanner());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
getTpaHint, getTpaProvider, updatePathWithQueryParams,
|
||||
} from '../data/utils';
|
||||
import { LoginPage } from '../login';
|
||||
import { backupLoginForm } from '../login/data/actions';
|
||||
import { RegistrationPage } from '../register';
|
||||
import { backupRegistrationForm } from '../register/data/actions';
|
||||
|
||||
@@ -66,16 +65,11 @@ const Logistration = (props) => {
|
||||
setInstitutionLogin(!institutionLogin);
|
||||
};
|
||||
|
||||
const handleOnSelect = (tabKey, currentTab) => {
|
||||
if (tabKey === currentTab) {
|
||||
return;
|
||||
}
|
||||
const handleOnSelect = (tabKey) => {
|
||||
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
|
||||
props.clearThirdPartyAuthContextErrorMessage();
|
||||
if (tabKey === LOGIN_PAGE) {
|
||||
props.backupRegistrationForm();
|
||||
} else if (tabKey === REGISTER_PAGE) {
|
||||
props.backupLoginForm();
|
||||
}
|
||||
setKey(tabKey);
|
||||
};
|
||||
@@ -124,7 +118,7 @@ const Logistration = (props) => {
|
||||
</Tabs>
|
||||
)
|
||||
: (!isValidTpaHint() && !hideRegistrationLink && (
|
||||
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={(tabKey) => handleOnSelect(tabKey, selectedPage)}>
|
||||
<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>
|
||||
@@ -156,7 +150,6 @@ const Logistration = (props) => {
|
||||
|
||||
Logistration.propTypes = {
|
||||
selectedPage: PropTypes.string,
|
||||
backupLoginForm: PropTypes.func.isRequired,
|
||||
backupRegistrationForm: PropTypes.func.isRequired,
|
||||
clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired,
|
||||
tpaProviders: PropTypes.shape({
|
||||
@@ -183,7 +176,6 @@ const mapStateToProps = state => ({
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
backupLoginForm,
|
||||
backupRegistrationForm,
|
||||
clearThirdPartyAuthContextErrorMessage,
|
||||
},
|
||||
|
||||
@@ -4,16 +4,16 @@ import { Provider } from 'react-redux';
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import Logistration from './Logistration';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
|
||||
import { RenderInstitutionButton } from '../common-components/InstitutionLogistration';
|
||||
import {
|
||||
COMPLETE_STATE, LOGIN_PAGE, REGISTER_PAGE,
|
||||
} from '../data/constants';
|
||||
import { backupLoginForm } from '../login/data/actions';
|
||||
import { backupRegistrationForm } from '../register/data/actions';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
@@ -94,29 +94,21 @@ describe('Logistration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing when user clicks on the same tab (login/register) again', () => {
|
||||
const { container } = render(reduxWrapper(<IntlLogistration />));
|
||||
// While staying on the registration form, clicking the register tab again
|
||||
fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]'));
|
||||
|
||||
expect(sendTrackEvent).not.toHaveBeenCalledWith('edx.bi.register_form.toggled', { category: 'user-engagement' });
|
||||
});
|
||||
|
||||
it('should render registration page', () => {
|
||||
mergeConfig({
|
||||
ALLOW_PUBLIC_ACCOUNT_CREATION: true,
|
||||
});
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlLogistration />));
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration />));
|
||||
|
||||
expect(container.querySelector('RegistrationPage')).toBeDefined();
|
||||
expect(logistration.find('#main-content').find('RegistrationPage').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render login page', () => {
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
|
||||
expect(container.querySelector('LoginPage')).toBeDefined();
|
||||
expect(logistration.find('#main-content').find('LoginPage').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render login/register headings when show registration links is disabled', () => {
|
||||
@@ -125,18 +117,18 @@ describe('Logistration', () => {
|
||||
});
|
||||
|
||||
let props = { selectedPage: LOGIN_PAGE };
|
||||
const { rerender } = render(reduxWrapper(<IntlLogistration {...props} />));
|
||||
let logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
|
||||
// verifying sign in heading
|
||||
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in');
|
||||
expect(logistration.find('#main-content').find('h3').text()).toEqual('Sign in');
|
||||
|
||||
// register page is still accessible when SHOW_REGISTRATION_LINKS is false
|
||||
// but it needs to be accessed directly
|
||||
props = { selectedPage: REGISTER_PAGE };
|
||||
rerender(reduxWrapper(<IntlLogistration {...props} />));
|
||||
logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
|
||||
// verifying register heading
|
||||
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Register');
|
||||
expect(logistration.find('#main-content').find('h3').text()).toEqual('Register');
|
||||
});
|
||||
|
||||
it('should render only login page when public account creation is disabled', () => {
|
||||
@@ -160,14 +152,14 @@ describe('Logistration', () => {
|
||||
});
|
||||
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
|
||||
// verifying sign in heading for institution login false
|
||||
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in');
|
||||
expect(logistration.find('#main-content').find('h3').text()).toEqual('Sign in');
|
||||
|
||||
// verifying tabs heading for institution login true
|
||||
fireEvent.click(screen.getByRole('link'));
|
||||
expect(container.querySelector('#controlled-tab')).toBeDefined();
|
||||
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
expect(logistration.find('#controlled-tab').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display institution login option when secondary providers are present', () => {
|
||||
@@ -190,12 +182,12 @@ describe('Logistration', () => {
|
||||
});
|
||||
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
render(reduxWrapper(<IntlLogistration {...props} />));
|
||||
expect(screen.getByText('Institution/campus credentials')).toBeDefined();
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
expect(logistration.text().includes('Institution/campus credentials')).toBe(true);
|
||||
|
||||
// on clicking "Institution/campus credentials" button, it should display institution login page
|
||||
fireEvent.click(screen.getByText('Institution/campus credentials'));
|
||||
expect(screen.getByText('Test University')).toBeDefined();
|
||||
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
expect(logistration.text().includes('Test University')).toBe(true);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
@@ -221,8 +213,8 @@ describe('Logistration', () => {
|
||||
});
|
||||
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
render(reduxWrapper(<IntlLogistration {...props} />));
|
||||
fireEvent.click(screen.getByText('Institution/campus credentials'));
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
|
||||
@@ -253,9 +245,9 @@ describe('Logistration', () => {
|
||||
delete window.location;
|
||||
window.location = { hostname: getConfig().SITE_NAME, href: getConfig().BASE_URL };
|
||||
|
||||
render(reduxWrapper(<IntlLogistration />));
|
||||
fireEvent.click(screen.getByText('Institution/campus credentials'));
|
||||
expect(screen.getByText('Test University')).toBeDefined();
|
||||
const root = mount(reduxWrapper(<IntlLogistration />));
|
||||
root.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
expect(root.text().includes('Test University')).toBe(true);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
@@ -264,23 +256,15 @@ describe('Logistration', () => {
|
||||
|
||||
it('should fire action to backup registration form on tab click', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { container } = render(reduxWrapper(<IntlLogistration />));
|
||||
fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]'));
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration />));
|
||||
logistration.find('a[data-rb-event-key="/login"]').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationForm());
|
||||
});
|
||||
|
||||
it('should fire action to backup login form on tab click', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
|
||||
fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]'));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(backupLoginForm());
|
||||
});
|
||||
|
||||
it('should clear tpa context errorMessage tab click', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { container } = render(reduxWrapper(<IntlLogistration />));
|
||||
fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]'));
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration />));
|
||||
logistration.find('a[data-rb-event-key="/login"]').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,7 +156,6 @@ const ProgressiveProfiling = (props) => {
|
||||
isGenderSelected: !!values.gender,
|
||||
isYearOfBirthSelected: !!values.year_of_birth,
|
||||
isLevelOfEducationSelected: !!values.level_of_education,
|
||||
isWorkExperienceSelected: !!values.work_experience,
|
||||
host: queryParams?.host || '',
|
||||
},
|
||||
);
|
||||
@@ -196,7 +195,7 @@ const ProgressiveProfiling = (props) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseContainer showWelcomeBanner fullName={authenticatedUser?.fullName || authenticatedUser?.name}>
|
||||
<BaseContainer showWelcomeBanner username={authenticatedUser?.username}>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['progressive.profiling.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
@@ -285,7 +284,6 @@ ProgressiveProfiling.propTypes = {
|
||||
authenticatedUser: PropTypes.shape({
|
||||
username: PropTypes.string,
|
||||
userId: PropTypes.number,
|
||||
fullName: PropTypes.string,
|
||||
}),
|
||||
showError: PropTypes.bool,
|
||||
shouldRedirect: PropTypes.bool,
|
||||
|
||||
@@ -105,7 +105,7 @@ describe('ProgressiveProfilingTests', () => {
|
||||
optionalFields,
|
||||
},
|
||||
});
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123', name: 'Test User' });
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
});
|
||||
|
||||
// ******** test form links and modal ********
|
||||
@@ -173,7 +173,6 @@ describe('ProgressiveProfilingTests', () => {
|
||||
isGenderSelected: false,
|
||||
isYearOfBirthSelected: false,
|
||||
isLevelOfEducationSelected: false,
|
||||
isWorkExperienceSelected: false,
|
||||
host: '',
|
||||
};
|
||||
delete window.location;
|
||||
@@ -347,7 +346,6 @@ describe('ProgressiveProfilingTests', () => {
|
||||
isGenderSelected: false,
|
||||
isYearOfBirthSelected: false,
|
||||
isLevelOfEducationSelected: false,
|
||||
isWorkExperienceSelected: false,
|
||||
host: 'http://example.com',
|
||||
};
|
||||
delete window.location;
|
||||
|
||||
@@ -16,14 +16,12 @@ const BaseCard = ({
|
||||
footer,
|
||||
handleOnClick,
|
||||
isLoading,
|
||||
redirectUrl,
|
||||
}) => (
|
||||
<div className="recommendation-card" key={`container-${uuid}`}>
|
||||
<Hyperlink
|
||||
target="_blank"
|
||||
className="card-box d-inline"
|
||||
className="card-box"
|
||||
showLaunchIcon={false}
|
||||
destination={redirectUrl}
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
<Card
|
||||
@@ -69,7 +67,6 @@ BaseCard.propTypes = {
|
||||
customHeaderImage: PropTypes.string.isRequired,
|
||||
schoolLogo: PropTypes.string.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
redirectUrl: PropTypes.string.isRequired,
|
||||
handleOnClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -63,8 +63,7 @@ const ProductCard = ({
|
||||
`recommendation.product-card.pill-text.${createCodeFriendlyProduct(productType)}`
|
||||
],
|
||||
);
|
||||
const handleCardClick = (event) => {
|
||||
event.preventDefault();
|
||||
const handleCardClick = () => {
|
||||
trackRecommendationClick(
|
||||
product,
|
||||
position + 1,
|
||||
@@ -84,7 +83,6 @@ const ProductCard = ({
|
||||
productType={productType}
|
||||
variant={variant}
|
||||
isLoading={isLoading}
|
||||
redirectUrl={product.url || product?.activeCourseRun?.marketingUrl}
|
||||
footer={(
|
||||
<Footer
|
||||
quickFacts={product.degree?.quickFacts}
|
||||
|
||||
@@ -14,12 +14,7 @@ const mockedRecommendedProducts = [
|
||||
image: {
|
||||
src: 'test_src',
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
name: 'Test Org',
|
||||
logoImageUrl: 'http://logourl.com',
|
||||
},
|
||||
],
|
||||
owners: [],
|
||||
title: 'test_title',
|
||||
uuid: 'test_uuid',
|
||||
objectID: 'course-test_uuid',
|
||||
@@ -42,12 +37,7 @@ const mockedRecommendedProducts = [
|
||||
image: {
|
||||
src: 'test_src',
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
name: 'Test Org',
|
||||
logoImageUrl: 'http://logourl.com',
|
||||
},
|
||||
],
|
||||
owners: [],
|
||||
title: 'test_title',
|
||||
uuid: 'test_uuid2',
|
||||
objectID: 'course-test_uuid',
|
||||
@@ -70,12 +60,7 @@ const mockedRecommendedProducts = [
|
||||
image: {
|
||||
src: 'test_src',
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
name: 'Test Org',
|
||||
logoImageUrl: 'http://logourl.com',
|
||||
},
|
||||
],
|
||||
owners: [],
|
||||
title: 'test_title',
|
||||
uuid: 'test_uuid3',
|
||||
objectID: 'course-test_uuid',
|
||||
@@ -98,12 +83,7 @@ const mockedRecommendedProducts = [
|
||||
image: {
|
||||
src: 'test_src',
|
||||
},
|
||||
owners: [
|
||||
{
|
||||
name: 'Test Org',
|
||||
logoImageUrl: 'http://logourl.com',
|
||||
},
|
||||
],
|
||||
owners: [],
|
||||
title: 'test_title',
|
||||
uuid: 'test_uuid4',
|
||||
objectID: 'course-test_uuid',
|
||||
|
||||
@@ -3,8 +3,8 @@ import processCourseSearchResult from '../algoliaResultsParser';
|
||||
|
||||
describe('AlgoliaResultsParserTests', () => {
|
||||
const dataToBeProcessed = {
|
||||
active_run_key: 'course-v1:TEST_COURSE_RUN',
|
||||
active_run_type: 'test_course_run_type',
|
||||
activeRunKey: 'course-v1:TEST_COURSE_RUN',
|
||||
activeRunType: 'test_course_run_type',
|
||||
marketingUrl: 'test_marketingUrl',
|
||||
minEffort: 1,
|
||||
maxEffort: 2,
|
||||
@@ -12,12 +12,7 @@ describe('AlgoliaResultsParserTests', () => {
|
||||
allowedIn: [],
|
||||
blockedIn: [],
|
||||
cardImageUrl: 'test_src',
|
||||
owners: [
|
||||
{
|
||||
name: 'Test Org',
|
||||
logoImageUrl: 'http://logourl.com',
|
||||
},
|
||||
],
|
||||
owners: [],
|
||||
title: 'test_title',
|
||||
uuid: 'test_uuid',
|
||||
recentEnrollmentCount: 1,
|
||||
|
||||
@@ -31,13 +31,6 @@ const CountryField = (props) => {
|
||||
} = props;
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const countryFieldValue = {
|
||||
userProvidedText: selectedCountry.displayValue,
|
||||
selectionValue: selectedCountry.displayValue,
|
||||
selectionId: selectedCountry.countryCode,
|
||||
};
|
||||
|
||||
const backendCountryCode = useSelector(state => state.register.backendCountryCode);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -56,11 +49,6 @@ const CountryField = (props) => {
|
||||
{ target: { name: 'country' } },
|
||||
{ countryCode, displayValue: countryDisplayValue },
|
||||
);
|
||||
} else if (!selectedCountry.displayValue) {
|
||||
onChangeHandler(
|
||||
{ target: { name: 'country' } },
|
||||
{ countryCode: '', displayValue: '' },
|
||||
);
|
||||
}
|
||||
}, [backendCountryCode, countryList]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -72,12 +60,18 @@ const CountryField = (props) => {
|
||||
|
||||
const { value } = event.target;
|
||||
|
||||
const { error } = validateCountryField(
|
||||
const { countryCode, displayValue, error } = validateCountryField(
|
||||
value.trim(), countryList, formatMessage(messages['empty.country.field.error']), formatMessage(messages['invalid.country.field.error']),
|
||||
);
|
||||
|
||||
onChangeHandler({ target: { name: 'country' } }, { countryCode, displayValue });
|
||||
handleErrorChange('country', error);
|
||||
};
|
||||
|
||||
const handleSelected = (value) => {
|
||||
handleOnBlur({ target: { name: 'country', value } });
|
||||
};
|
||||
|
||||
const handleOnFocus = (event) => {
|
||||
handleErrorChange('country', '');
|
||||
dispatch(clearRegistrationBackendError('country'));
|
||||
@@ -85,19 +79,11 @@ const CountryField = (props) => {
|
||||
};
|
||||
|
||||
const handleOnChange = (value) => {
|
||||
onChangeHandler({ target: { name: 'country' } }, { countryCode: value.selectionId, displayValue: value.userProvidedText });
|
||||
|
||||
// We have put this check because proviously we also had onSelected event handler and we call
|
||||
// the onBlur on that event handler but now there is no such handler and we only have
|
||||
// onChange so we check the is there is proper sectionId which only be
|
||||
// proper one when we select it from dropdown's item otherwise its null.
|
||||
if (value.selectionId !== '') {
|
||||
handleOnBlur({ target: { name: 'country', value: value.userProvidedText } });
|
||||
}
|
||||
onChangeHandler({ target: { name: 'country' } }, { countryCode: '', displayValue: value });
|
||||
};
|
||||
|
||||
const getCountryList = () => countryList.map((country) => (
|
||||
<FormAutosuggestOption key={country[COUNTRY_CODE_KEY]} id={country[COUNTRY_CODE_KEY]}>
|
||||
<FormAutosuggestOption key={country[COUNTRY_CODE_KEY]}>
|
||||
{country[COUNTRY_DISPLAY_KEY]}
|
||||
</FormAutosuggestOption>
|
||||
));
|
||||
@@ -108,8 +94,9 @@ const CountryField = (props) => {
|
||||
floatingLabel={formatMessage(messages['registration.country.label'])}
|
||||
aria-label="form autosuggest"
|
||||
name="country"
|
||||
value={countryFieldValue || {}}
|
||||
value={selectedCountry.displayValue || ''}
|
||||
className={classNames({ 'form-field-error': props.errorMessage })}
|
||||
onSelected={(value) => handleSelected(value)}
|
||||
onFocus={(e) => handleOnFocus(e)}
|
||||
onBlur={(e) => handleOnBlur(e)}
|
||||
onChange={(value) => handleOnChange(value)}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Provider } from 'react-redux';
|
||||
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -82,13 +82,8 @@ describe('CountryField', () => {
|
||||
};
|
||||
|
||||
it('should run country field validation when onBlur is fired', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
|
||||
fireEvent.blur(countryInput, {
|
||||
target: { value: '', name: 'country' },
|
||||
});
|
||||
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
countryField.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'country',
|
||||
@@ -97,13 +92,8 @@ describe('CountryField', () => {
|
||||
});
|
||||
|
||||
it('should run country field validation when country name is invalid', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
|
||||
fireEvent.blur(countryInput, {
|
||||
target: { value: 'Pak', name: 'country' },
|
||||
});
|
||||
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
countryField.find('input[name="country"]').simulate('blur', { target: { value: 'Pak', name: 'country' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'country',
|
||||
@@ -112,36 +102,34 @@ describe('CountryField', () => {
|
||||
});
|
||||
|
||||
it('should not run country field validation when onBlur is fired by drop-down arrow icon click', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
const dropdownArrowIcon = container.querySelector('.btn-icon.pgn__form-autosuggest__icon-button');
|
||||
|
||||
fireEvent.blur(countryInput, {
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
countryField.find('input[name="country"]').simulate('blur', {
|
||||
target: { value: '', name: 'country' },
|
||||
relatedTarget: dropdownArrowIcon,
|
||||
relatedTarget: { type: 'button', className: 'btn-icon pgn__form-autosuggest__icon-button' },
|
||||
});
|
||||
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should update errors for frontend validations', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
|
||||
fireEvent.blur(countryInput, { target: { value: '', name: 'country' } });
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
|
||||
countryField.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith('country', emptyFieldValidation.country);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'country',
|
||||
emptyFieldValidation.country,
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear error on focus', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
|
||||
fireEvent.focus(countryInput);
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
|
||||
countryField.find('input[name="country"]').simulate('focus', { target: { value: '', name: 'country' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith('country', '');
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'country',
|
||||
'',
|
||||
);
|
||||
});
|
||||
|
||||
it('should update state from country code present in redux store', () => {
|
||||
@@ -153,9 +141,7 @@ describe('CountryField', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
|
||||
container.querySelector('input[name="country"]');
|
||||
mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
expect(props.onChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(props.onChangeHandler).toHaveBeenCalledWith(
|
||||
{ target: { name: 'country' } },
|
||||
@@ -164,15 +150,12 @@ describe('CountryField', () => {
|
||||
});
|
||||
|
||||
it('should set option on dropdown menu item click', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
|
||||
const dropdownButton = container.querySelector('.pgn__form-autosuggest__icon-button');
|
||||
fireEvent.click(dropdownButton);
|
||||
countryField.find('.pgn__form-autosuggest__icon-button').first().simulate('click');
|
||||
countryField.find('.dropdown-item').first().simulate('click');
|
||||
|
||||
const dropdownItem = container.querySelector('.dropdown-item');
|
||||
fireEvent.click(dropdownItem);
|
||||
|
||||
expect(props.onChangeHandler).toHaveBeenCalledTimes(2);
|
||||
expect(props.onChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(props.onChangeHandler).toHaveBeenCalledWith(
|
||||
{ target: { name: 'country' } },
|
||||
{ countryCode: 'PK', displayValue: 'Pakistan' },
|
||||
@@ -180,14 +163,13 @@ describe('CountryField', () => {
|
||||
});
|
||||
|
||||
it('should set value on change', () => {
|
||||
const { container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlCountryField {...props} />)),
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
|
||||
countryField.find('input[name="country"]').simulate(
|
||||
'change', { target: { value: 'pak', name: 'country' } },
|
||||
);
|
||||
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
fireEvent.change(countryInput, { target: { value: 'pak', name: 'country' } });
|
||||
|
||||
expect(props.onChangeHandler).toHaveBeenCalledTimes(2);
|
||||
expect(props.onChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(props.onChangeHandler).toHaveBeenCalledWith(
|
||||
{ target: { name: 'country' } },
|
||||
{ countryCode: '', displayValue: 'pak' },
|
||||
@@ -200,11 +182,9 @@ describe('CountryField', () => {
|
||||
errorMessage: 'country error message',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
const countryField = mount(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
|
||||
|
||||
const feedbackElement = container.querySelector('div[feedback-for="country"]');
|
||||
expect(feedbackElement).toBeTruthy();
|
||||
expect(feedbackElement.textContent).toEqual('country error message');
|
||||
expect(countryField.find('div[feedback-for="country"]').text()).toEqual('country error message');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -9,9 +9,9 @@ import PropTypes from 'prop-types';
|
||||
import validateEmail from './validator';
|
||||
import { FormGroup } from '../../../common-components';
|
||||
import {
|
||||
backupRegistrationFormBegin,
|
||||
clearRegistrationBackendError,
|
||||
fetchRealtimeValidations,
|
||||
setEmailSuggestionInStore,
|
||||
} from '../../data/actions';
|
||||
import messages from '../../messages';
|
||||
|
||||
@@ -37,15 +37,13 @@ const EmailField = (props) => {
|
||||
confirmEmailValue,
|
||||
} = props;
|
||||
|
||||
const backedUpFormData = useSelector(state => state.register.registrationFormData);
|
||||
const validationApiRateLimited = useSelector(state => state.register.validationApiRateLimited);
|
||||
const {
|
||||
registrationFormData: backedUpFormData,
|
||||
validationApiRateLimited,
|
||||
} = useSelector(state => state.register);
|
||||
|
||||
const [emailSuggestion, setEmailSuggestion] = useState({ ...backedUpFormData?.emailSuggestion });
|
||||
|
||||
useEffect(() => {
|
||||
setEmailSuggestion(backedUpFormData.emailSuggestion);
|
||||
}, [backedUpFormData.emailSuggestion]);
|
||||
|
||||
const handleOnBlur = (e) => {
|
||||
const { value } = e.target;
|
||||
const { fieldError, confirmEmailError, suggestion } = validateEmail(value, confirmEmailValue, formatMessage);
|
||||
@@ -54,7 +52,10 @@ const EmailField = (props) => {
|
||||
handleErrorChange('confirm_email', confirmEmailError);
|
||||
}
|
||||
|
||||
dispatch(setEmailSuggestionInStore(suggestion));
|
||||
dispatch(backupRegistrationFormBegin({
|
||||
...backedUpFormData,
|
||||
emailSuggestion: { ...suggestion },
|
||||
}));
|
||||
setEmailSuggestion(suggestion);
|
||||
|
||||
if (fieldError) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -46,14 +46,7 @@ describe('EmailField', () => {
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
register: {
|
||||
registrationFormData: {
|
||||
emailSuggestion: {
|
||||
suggestion: 'example@gmail.com',
|
||||
type: 'warning',
|
||||
},
|
||||
},
|
||||
},
|
||||
register: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -80,10 +73,9 @@ describe('EmailField', () => {
|
||||
};
|
||||
|
||||
it('should run email field validation when onBlur is fired', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: '', name: 'email' } });
|
||||
emailField.find('input#email').simulate('blur', { target: { value: '', name: 'email' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'email',
|
||||
@@ -92,11 +84,9 @@ describe('EmailField', () => {
|
||||
});
|
||||
|
||||
it('should update errors for frontend validations', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'ab', name: 'email' } });
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
emailField.find('input#email').simulate('blur', { target: { value: 'ab', name: 'email' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'email',
|
||||
@@ -105,11 +95,9 @@ describe('EmailField', () => {
|
||||
});
|
||||
|
||||
it('should clear error on focus', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.focus(emailInput, { target: { value: '', name: 'email' } });
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
emailField.find('input#email').simulate('focus', { target: { value: '', name: 'email' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'email',
|
||||
@@ -119,34 +107,26 @@ describe('EmailField', () => {
|
||||
|
||||
it('should call backend validation api on blur event, if frontend validations have passed', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
// Enter a valid email so that frontend validations are passed
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'test@gmail.com', name: 'email' } });
|
||||
|
||||
emailField.find('input#email').simulate('blur', { target: { value: 'test@gmail.com', name: 'email' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ email: 'test@gmail.com' }));
|
||||
});
|
||||
|
||||
it('should give email suggestions for common service provider domain typos', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'john@yopmail.com', name: 'email' } });
|
||||
emailField.find('input#email').simulate('blur', { target: { value: 'john@yopmail.com', name: 'email' } });
|
||||
|
||||
const emailWarning = container.querySelector('#email-warning');
|
||||
expect(emailWarning.textContent).toEqual('Did you mean: john@hotmail.com?');
|
||||
expect(emailField.find('#email-warning').text()).toEqual('Did you mean: john@hotmail.com?');
|
||||
});
|
||||
|
||||
it('should be able to click on email suggestions and set it as value', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'john@yopmail.com', name: 'email' } });
|
||||
|
||||
const emailSuggestion = container.querySelector('.email-suggestion-alert-warning');
|
||||
fireEvent.click(emailSuggestion);
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
emailField.find('input#email').simulate('blur', { target: { value: 'john@yopmail.com', name: 'email' } });
|
||||
emailField.find('.email-suggestion-alert-warning').first().simulate('click');
|
||||
expect(props.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleChange).toHaveBeenCalledWith(
|
||||
{ target: { name: 'email', value: 'john@hotmail.com' } },
|
||||
@@ -154,24 +134,21 @@ describe('EmailField', () => {
|
||||
});
|
||||
|
||||
it('should give error for common top level domain mistakes', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'john@gmail.mistake', name: 'email' } });
|
||||
|
||||
const errorElement = container.querySelector('.alert-danger');
|
||||
expect(errorElement.textContent).toEqual('Did you mean john@gmail.com?');
|
||||
emailField.find('input#email').simulate(
|
||||
'blur', { target: { value: 'john@gmail.mistake', name: 'email' } },
|
||||
);
|
||||
expect(emailField.find('.alert-danger').text()).toEqual('Did you mean john@gmail.com?');
|
||||
});
|
||||
|
||||
it('should give error and suggestion for invalid email', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'john@gmail', name: 'email' } });
|
||||
|
||||
const errorElement = container.querySelector('.alert-danger');
|
||||
expect(errorElement.textContent).toEqual('Did you mean john@gmail.com?');
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
emailField.find('input#email').simulate(
|
||||
'blur', { target: { value: 'john@gmail', name: 'email' } },
|
||||
);
|
||||
expect(emailField.find('.alert-danger').text()).toEqual('Did you mean john@gmail.com?');
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'email',
|
||||
@@ -193,29 +170,21 @@ describe('EmailField', () => {
|
||||
});
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.focus(emailInput, { target: { value: 'a@gmail.com', name: 'email' } });
|
||||
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
emailField.find('input#email').simulate('focus', { target: { value: 'a@gmail.com', name: 'email' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('email'));
|
||||
});
|
||||
|
||||
it('should clear email suggestions when close icon is clicked', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'john@gmail.mistake', name: 'email' } });
|
||||
emailField.find('input#email').simulate(
|
||||
'blur', { target: { value: 'john@gmail.mistake', name: 'email' } },
|
||||
);
|
||||
expect(emailField.find('.alert-danger').text()).toEqual('Did you mean john@gmail.com?');
|
||||
|
||||
const suggestionText = container.querySelector('.alert-danger');
|
||||
expect(suggestionText.textContent).toEqual('Did you mean john@gmail.com?');
|
||||
|
||||
const closeButton = container.querySelector('.email-suggestion__close');
|
||||
fireEvent.click(closeButton);
|
||||
|
||||
const closedSuggestionText = container.querySelector('.alert-danger');
|
||||
expect(closedSuggestionText).toBeNull();
|
||||
emailField.find('.email-suggestion__close').at(0).simulate('click');
|
||||
expect(emailField.find('.alert-danger').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set confirm email error if it exist', () => {
|
||||
@@ -224,10 +193,10 @@ describe('EmailField', () => {
|
||||
confirmEmailValue: 'confirmEmail@yopmail.com',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.blur(emailInput, { target: { value: 'differentEmail@yopmail.com', name: 'email' } });
|
||||
|
||||
const emailField = mount(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
|
||||
emailField.find('input#email').simulate(
|
||||
'blur', { target: { value: 'differentEmail@yopmail.com', name: 'email' } },
|
||||
);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'confirm_email',
|
||||
|
||||
@@ -91,7 +91,7 @@ export const validateEmailAddress = (value, username, domainName) => {
|
||||
const validateEmail = (value, confirmEmailValue, formatMessage) => {
|
||||
let fieldError = '';
|
||||
let confirmEmailError = '';
|
||||
let emailSuggestion = { suggestion: '', type: '' };
|
||||
let emailSuggestion = {};
|
||||
|
||||
if (!value) {
|
||||
fieldError = formatMessage(messages['empty.email.field.error']);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { HonorCode } from '../index';
|
||||
|
||||
@@ -13,7 +13,6 @@ describe('HonorCodeTest', () => {
|
||||
PRIVACY_POLICY: 'http://privacy-policy.com',
|
||||
TOS_AND_HONOR_CODE: 'http://tos-and-honot-code.com',
|
||||
});
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let value = false;
|
||||
|
||||
const changeHandler = (e) => {
|
||||
@@ -26,7 +25,7 @@ describe('HonorCodeTest', () => {
|
||||
|
||||
it('should render error msg if honor code is not checked', () => {
|
||||
const errorMessage = `You must agree to the ${getConfig().SITE_NAME} Honor Code`;
|
||||
const { container } = render(
|
||||
const honorCode = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlHonorCode
|
||||
errorMessage={errorMessage}
|
||||
@@ -34,27 +33,24 @@ describe('HonorCodeTest', () => {
|
||||
/>
|
||||
</IntlProvider>,
|
||||
);
|
||||
const errorElement = container.querySelector('.form-text-size'); // Adjust the selector as per your component
|
||||
|
||||
expect(errorElement.textContent).toEqual(errorMessage);
|
||||
expect(honorCode.find('.form-text-size').last().text()).toEqual(errorMessage);
|
||||
});
|
||||
|
||||
it('should render Honor code field', () => {
|
||||
const expectedMsg = 'I agree to the Your Platform Name Here\u00a0Honor Codein a new tab';
|
||||
const { container } = render(
|
||||
const honorCode = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlHonorCode onChangeHandler={changeHandler} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const honorCodeField = container.querySelector('#honor-code');
|
||||
honorCodeField.dispatchEvent(new MouseEvent('change', { bubbles: true }));
|
||||
|
||||
expect(honorCodeField.querySelector('label').textContent).toEqual(expectedMsg);
|
||||
honorCode.find('#honor-code').last().simulate('change', { target: { checked: true, type: 'checkbox' } });
|
||||
expect(honorCode.find('#honor-code').find('label').text()).toEqual(expectedMsg);
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
|
||||
it('should render Terms of Service and Honor code field', () => {
|
||||
const { container } = render(
|
||||
const HonorCodeProps = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlHonorCode fieldType="tos_and_honor_code" onChangeHandler={changeHandler} />
|
||||
</IntlProvider>,
|
||||
@@ -62,7 +58,7 @@ describe('HonorCodeTest', () => {
|
||||
const expectedMsg = 'By creating an account, you agree to the Terms of Service and Honor Code and you '
|
||||
+ 'acknowledge that Your Platform Name Here and each Member process your personal data in '
|
||||
+ 'accordance with the Privacy Policy.';
|
||||
const honorCodeField = container.querySelector('#honor-code');
|
||||
expect(honorCodeField.textContent).toEqual(expectedMsg);
|
||||
const field = HonorCodeProps.find('#honor-code');
|
||||
expect(field.text()).toEqual(expectedMsg);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -69,11 +69,9 @@ describe('NameField', () => {
|
||||
const fieldValidation = { name: 'Enter your full name' };
|
||||
|
||||
it('should run name field validation when onBlur is fired', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
const nameInput = container.querySelector('input#name');
|
||||
fireEvent.blur(nameInput, { target: { value: '', name: 'name' } });
|
||||
const nameField = mount(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
nameField.find('input#name').simulate('blur', { target: { value: '', name: 'name' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'name',
|
||||
@@ -82,11 +80,11 @@ describe('NameField', () => {
|
||||
});
|
||||
|
||||
it('should update errors for frontend validations', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
const nameInput = container.querySelector('input#name');
|
||||
fireEvent.blur(nameInput, { target: { value: 'https://invalid-name.com', name: 'name' } });
|
||||
const nameField = mount(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
nameField.find('input#name').simulate(
|
||||
'blur', { target: { value: 'https://invalid-name.com', name: 'name' } },
|
||||
);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'name',
|
||||
@@ -95,11 +93,9 @@ describe('NameField', () => {
|
||||
});
|
||||
|
||||
it('should clear error on focus', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
const nameInput = container.querySelector('input#name');
|
||||
fireEvent.focus(nameInput, { target: { value: '', name: 'name' } });
|
||||
const nameField = mount(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
nameField.find('input#name').simulate('focus', { target: { value: '', name: 'name' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'name',
|
||||
@@ -113,12 +109,10 @@ describe('NameField', () => {
|
||||
...props,
|
||||
shouldFetchUsernameSuggestions: true,
|
||||
};
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
const nameField = mount(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
const nameInput = container.querySelector('input#name');
|
||||
// Enter a valid name so that frontend validations are passed
|
||||
fireEvent.blur(nameInput, { target: { value: 'test', name: 'name' } });
|
||||
|
||||
nameField.find('input#name').simulate('blur', { target: { value: 'test', name: 'name' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ name: 'test' }));
|
||||
});
|
||||
|
||||
@@ -135,12 +129,8 @@ describe('NameField', () => {
|
||||
});
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
|
||||
const nameInput = container.querySelector('input#name');
|
||||
|
||||
fireEvent.focus(nameInput, { target: { value: 'test', name: 'name' } });
|
||||
|
||||
const nameField = mount(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
|
||||
nameField.find('input#name').simulate('focus', { target: { value: 'test', name: 'name' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('name'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ export const HTML_REGEX = /<|>/u;
|
||||
export const INVALID_NAME_REGEX = /https?:\/\/(?:[-\w.]|(?:%[\da-fA-F]{2}))*/g;
|
||||
|
||||
const validateName = (value, formatMessage) => {
|
||||
let fieldError = '';
|
||||
let fieldError;
|
||||
if (!value.trim()) {
|
||||
fieldError = formatMessage(messages['empty.name.field.error']);
|
||||
} else if (URL_REGEX.test(value) || HTML_REGEX.test(value) || INVALID_NAME_REGEX.test(value)) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { TermsOfService } from '../index';
|
||||
|
||||
@@ -21,38 +21,33 @@ describe('TermsOfServiceTest', () => {
|
||||
|
||||
it('should render error msg if Terms of Service checkbox is not checked', () => {
|
||||
const errorMessage = `You must agree to the ${getConfig().SITE_NAME} Terms of Service`;
|
||||
const { container } = render(
|
||||
const termsOfService = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlTermsOfService errorMessage={errorMessage} onChangeHandler={changeHandler} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
const errorElement = container.querySelector('.form-text-size');
|
||||
expect(errorElement.textContent).toEqual(errorMessage);
|
||||
expect(termsOfService.find('.form-text-size').last().text()).toEqual(errorMessage);
|
||||
});
|
||||
|
||||
it('should render Terms of Service field', () => {
|
||||
const { container } = render(
|
||||
const termsOfService = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlTermsOfService onChangeHandler={changeHandler} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMsg = 'I agree to the Your Platform Name Here\u00a0Terms of Servicein a new tab';
|
||||
|
||||
const termsOfServiceLabel = container.querySelector('#terms-of-service label');
|
||||
expect(termsOfServiceLabel.textContent).toEqual(expectedMsg);
|
||||
|
||||
expect(termsOfService.find('#terms-of-service').find('label').text()).toEqual(expectedMsg);
|
||||
expect(value).toEqual(false);
|
||||
});
|
||||
|
||||
it('should change value when Terms of Service field is checked', () => {
|
||||
const { container } = render(
|
||||
const termsOfService = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlTermsOfService onChangeHandler={changeHandler} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
const field = container.querySelector('input#tos');
|
||||
fireEvent.click(field);
|
||||
const field = termsOfService.find('input#tos');
|
||||
field.simulate('change', { target: { checked: true, type: 'checkbox' } });
|
||||
expect(value).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,8 +41,7 @@ const UsernameField = (props) => {
|
||||
let className = '';
|
||||
let suggestedUsernameDiv = null;
|
||||
let iconButton = null;
|
||||
const usernameSuggestions = useSelector(state => state.register.usernameSuggestions);
|
||||
const validationApiRateLimited = useSelector(state => state.register.validationApiRateLimited);
|
||||
const { usernameSuggestions, validationApiRateLimited } = useSelector(state => state.register);
|
||||
|
||||
/**
|
||||
* We need to remove the placeholder from the field, adding a space will do that.
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -73,11 +73,9 @@ describe('UsernameField', () => {
|
||||
};
|
||||
|
||||
it('should run username field validation when onBlur is fired', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.blur(usernameField, { target: { value: '', name: 'username' } });
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
usernameField.find('input#username').simulate('blur', { target: { value: '', name: 'username' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'username',
|
||||
@@ -86,11 +84,9 @@ describe('UsernameField', () => {
|
||||
});
|
||||
|
||||
it('should update errors for frontend validations', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.blur(usernameField, { target: { value: 'user#', name: 'username' } });
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
usernameField.find('input#username').simulate('blur', { target: { value: 'user#', name: 'username' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'username',
|
||||
@@ -99,11 +95,9 @@ describe('UsernameField', () => {
|
||||
});
|
||||
|
||||
it('should clear error on focus', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.focus(usernameField, { target: { value: '', name: 'username' } });
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
usernameField.find('input#username').simulate('focus', { target: { value: '', name: 'username' } });
|
||||
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleErrorChange).toHaveBeenCalledWith(
|
||||
'username',
|
||||
@@ -112,11 +106,9 @@ describe('UsernameField', () => {
|
||||
});
|
||||
|
||||
it('should remove space from field on focus if space exists', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.focus(usernameField, { target: { value: ' ', name: 'username' } });
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
usernameField.find('input#username').simulate('focus', { target: { value: ' ', name: 'username' } });
|
||||
expect(props.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleChange).toHaveBeenCalledWith(
|
||||
{ target: { name: 'username', value: '' } },
|
||||
@@ -125,19 +117,18 @@ describe('UsernameField', () => {
|
||||
|
||||
it('should call backend validation api on blur event, if frontend validations have passed', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
// Enter a valid username so that frontend validations are passed
|
||||
fireEvent.blur(usernameField, { target: { value: 'test', name: 'username' } });
|
||||
|
||||
usernameField.find('input#username').simulate('blur', { target: { value: 'test', name: 'username' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ username: 'test' }));
|
||||
});
|
||||
|
||||
it('should remove space from the start of username on change', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.change(usernameField, { target: { value: ' test-user', name: 'username' } });
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('input#username').simulate(
|
||||
'change', { target: { value: ' test-user', name: 'username' } },
|
||||
);
|
||||
|
||||
expect(props.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleChange).toHaveBeenCalledWith(
|
||||
@@ -146,10 +137,10 @@ describe('UsernameField', () => {
|
||||
});
|
||||
|
||||
it('should not set username if it is more than 30 character long', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.change(usernameField, { target: { value: 'why_this_is_not_valid_username_', name: 'username' } });
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('input#username').simulate(
|
||||
'change', { target: { value: 'why_this_is_not_valid_username_', name: 'username' } },
|
||||
);
|
||||
|
||||
expect(props.handleChange).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
@@ -157,10 +148,8 @@ describe('UsernameField', () => {
|
||||
it('should clear username suggestions when username field is focused in', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.focus(usernameField);
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('input#username').simulate('focus');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
|
||||
});
|
||||
@@ -179,9 +168,8 @@ describe('UsernameField', () => {
|
||||
errorMessage: 'It looks like this username is already taken',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
const usernameSuggestions = container.querySelectorAll('button.username-suggestions--chip');
|
||||
expect(usernameSuggestions.length).toEqual(3);
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
expect(usernameField.find('button.username-suggestions--chip').length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should show username suggestions when they are populated in redux', () => {
|
||||
@@ -198,9 +186,8 @@ describe('UsernameField', () => {
|
||||
value: ' ',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
const usernameSuggestions = container.querySelectorAll('button.username-suggestions--chip');
|
||||
expect(usernameSuggestions.length).toEqual(3);
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
expect(usernameField.find('button.username-suggestions--chip').length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should show username suggestions even if there is an error in field', () => {
|
||||
@@ -218,9 +205,8 @@ describe('UsernameField', () => {
|
||||
errorMessage: 'username error',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
const usernameSuggestions = container.querySelectorAll('button.username-suggestions--chip');
|
||||
expect(usernameSuggestions.length).toEqual(3);
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
expect(usernameField.find('button.username-suggestions--chip').length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should put space in username field if suggestions are populated in redux', () => {
|
||||
@@ -232,7 +218,7 @@ describe('UsernameField', () => {
|
||||
},
|
||||
});
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
expect(props.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleChange).toHaveBeenCalledWith(
|
||||
{ target: { name: 'username', value: ' ' } },
|
||||
@@ -253,9 +239,8 @@ describe('UsernameField', () => {
|
||||
value: ' ',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
const usernameSuggestion = container.querySelector('.username-suggestions--chip');
|
||||
fireEvent.click(usernameSuggestion);
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('.username-suggestions--chip').first().simulate('click');
|
||||
expect(props.handleChange).toHaveBeenCalledTimes(1);
|
||||
expect(props.handleChange).toHaveBeenCalledWith(
|
||||
{ target: { name: 'username', value: 'test_1' } },
|
||||
@@ -277,9 +262,8 @@ describe('UsernameField', () => {
|
||||
value: ' ',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
let closeButton = container.querySelector('button.username-suggestions__close__button');
|
||||
fireEvent.click(closeButton);
|
||||
let usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('button.username-suggestions__close__button').at(0).simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
|
||||
|
||||
props = {
|
||||
@@ -287,9 +271,8 @@ describe('UsernameField', () => {
|
||||
errorMessage: 'username error',
|
||||
};
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
closeButton = container.querySelector('button.username-suggestions__close__button');
|
||||
fireEvent.click(closeButton);
|
||||
usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('button.username-suggestions__close__button').at(0).simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
|
||||
});
|
||||
|
||||
@@ -308,12 +291,8 @@ describe('UsernameField', () => {
|
||||
});
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
|
||||
const usernameField = container.querySelector('input#username');
|
||||
fireEvent.focus(usernameField, { target: { value: 'test', name: 'username' } });
|
||||
|
||||
const usernameField = mount(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
|
||||
usernameField.find('input#username').simulate('focus', { target: { value: 'test', name: 'username' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('username'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,32 +14,25 @@ import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import ConfigurableRegistrationForm from './components/ConfigurableRegistrationForm';
|
||||
import RegistrationFailure from './components/RegistrationFailure';
|
||||
import ThirdPartyAuth from './components/ThirdPartyAuth';
|
||||
import {
|
||||
backupRegistrationFormBegin,
|
||||
clearRegistrationBackendError,
|
||||
registerNewUser,
|
||||
setEmailSuggestionInStore,
|
||||
setUserPipelineDataLoaded,
|
||||
} from './data/actions';
|
||||
import {
|
||||
FORM_SUBMISSION_ERROR,
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
} from './data/constants';
|
||||
import getBackendValidations from './data/selectors';
|
||||
import {
|
||||
isFormValid, prepareRegistrationPayload,
|
||||
} from './data/utils';
|
||||
import { getBackendValidations, isFormValid, prepareRegistrationPayload } from './data/utils';
|
||||
import messages from './messages';
|
||||
import { EmailField, NameField, UsernameField } from './RegistrationFields';
|
||||
import {
|
||||
InstitutionLogistration,
|
||||
PasswordField,
|
||||
RedirectLogistration,
|
||||
ThirdPartyAuthAlert,
|
||||
InstitutionLogistration, PasswordField, RedirectLogistration, ThirdPartyAuthAlert,
|
||||
} from '../common-components';
|
||||
import { getThirdPartyAuthContext as getRegistrationDataFromBackend } from '../common-components/data/actions';
|
||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
||||
import {
|
||||
COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
|
||||
} from '../data/constants';
|
||||
@@ -66,26 +59,38 @@ const RegistrationPage = (props) => {
|
||||
institutionLogin,
|
||||
} = props;
|
||||
|
||||
const backedUpFormData = useSelector(state => state.register.registrationFormData);
|
||||
const registrationError = useSelector(state => state.register.registrationError);
|
||||
const registrationErrorCode = registrationError?.errorCode;
|
||||
const registrationResult = useSelector(state => state.register.registrationResult);
|
||||
const shouldBackupState = useSelector(state => state.register.shouldBackupState);
|
||||
const userPipelineDataLoaded = useSelector(state => state.register.userPipelineDataLoaded);
|
||||
const submitState = useSelector(state => state.register.submitState);
|
||||
const {
|
||||
registrationFormData: backedUpFormData,
|
||||
registrationError,
|
||||
registrationError: {
|
||||
errorCode: registrationErrorCode,
|
||||
} = {},
|
||||
registrationResult,
|
||||
shouldBackupState,
|
||||
userPipelineDataLoaded,
|
||||
submitState,
|
||||
validations,
|
||||
} = useSelector(state => state.register);
|
||||
|
||||
const fieldDescriptions = useSelector(state => state.commonComponents.fieldDescriptions);
|
||||
const optionalFields = useSelector(state => state.commonComponents.optionalFields);
|
||||
const thirdPartyAuthApiStatus = useSelector(state => state.commonComponents.thirdPartyAuthApiStatus);
|
||||
const autoSubmitRegForm = useSelector(state => state.commonComponents.thirdPartyAuthContext.autoSubmitRegForm);
|
||||
const thirdPartyAuthErrorMessage = useSelector(state => state.commonComponents.thirdPartyAuthContext.errorMessage);
|
||||
const finishAuthUrl = useSelector(state => state.commonComponents.thirdPartyAuthContext.finishAuthUrl);
|
||||
const currentProvider = useSelector(state => state.commonComponents.thirdPartyAuthContext.currentProvider);
|
||||
const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers);
|
||||
const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders);
|
||||
const pipelineUserDetails = useSelector(state => state.commonComponents.thirdPartyAuthContext.pipelineUserDetails);
|
||||
const {
|
||||
fieldDescriptions,
|
||||
optionalFields,
|
||||
thirdPartyAuthApiStatus,
|
||||
thirdPartyAuthContext,
|
||||
thirdPartyAuthContext: {
|
||||
autoSubmitRegForm,
|
||||
errorMessage: thirdPartyAuthErrorMessage,
|
||||
finishAuthUrl,
|
||||
currentProvider,
|
||||
providers,
|
||||
secondaryProviders,
|
||||
pipelineUserDetails,
|
||||
},
|
||||
} = useSelector(state => state.commonComponents);
|
||||
|
||||
const backendValidations = useSelector(getBackendValidations);
|
||||
const backendValidations = useMemo(
|
||||
() => getBackendValidations(registrationError, validations), [registrationError, validations],
|
||||
);
|
||||
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
||||
const tpaHint = useMemo(() => getTpaHint(), []);
|
||||
|
||||
@@ -119,9 +124,7 @@ const RegistrationPage = (props) => {
|
||||
}
|
||||
}
|
||||
}, [ // eslint-disable-line react-hooks/exhaustive-deps
|
||||
thirdPartyAuthApiStatus,
|
||||
thirdPartyAuthErrorMessage,
|
||||
pipelineUserDetails,
|
||||
thirdPartyAuthContext,
|
||||
userPipelineDataLoaded,
|
||||
]);
|
||||
|
||||
@@ -182,8 +185,8 @@ const RegistrationPage = (props) => {
|
||||
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
|
||||
if (registrationError[name]) {
|
||||
dispatch(clearRegistrationBackendError(name));
|
||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||
}
|
||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
||||
};
|
||||
|
||||
@@ -217,7 +220,7 @@ const RegistrationPage = (props) => {
|
||||
}
|
||||
|
||||
// Validating form data before submitting
|
||||
const { isValid, fieldErrors, emailSuggestion } = isFormValid(
|
||||
const { isValid, fieldErrors } = isFormValid(
|
||||
payload,
|
||||
registrationEmbedded ? temporaryErrors : errors,
|
||||
configurableFormFields,
|
||||
@@ -225,7 +228,6 @@ const RegistrationPage = (props) => {
|
||||
formatMessage,
|
||||
);
|
||||
setErrors({ ...fieldErrors });
|
||||
dispatch(setEmailSuggestionInStore(emailSuggestion));
|
||||
|
||||
// returning if not valid
|
||||
if (!isValid) {
|
||||
|
||||
@@ -6,8 +6,9 @@ import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'
|
||||
import {
|
||||
configure, getLocale, injectIntl, IntlProvider,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mockNavigate, BrowserRouter as Router } from 'react-router-dom';
|
||||
import renderer from 'react-test-renderer';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
@@ -100,7 +101,6 @@ describe('RegistrationPage', () => {
|
||||
registrationError: {},
|
||||
registrationFormData,
|
||||
usernameSuggestions: [],
|
||||
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthApiStatus: null,
|
||||
@@ -134,16 +134,16 @@ describe('RegistrationPage', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => {
|
||||
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } });
|
||||
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
|
||||
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
|
||||
const populateRequiredFields = (registrationPage, payload, isThirdPartyAuth = false) => {
|
||||
registrationPage.find('input#name').simulate('change', { target: { value: payload.name, name: 'name' } });
|
||||
registrationPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } });
|
||||
registrationPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } });
|
||||
|
||||
fireEvent.change(getByLabelText('Country/Region'), { target: { value: payload.country, name: 'country' } });
|
||||
fireEvent.blur(getByLabelText('Country/Region'), { 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) {
|
||||
fireEvent.change(getByLabelText('Password'), { target: { value: payload.password, name: 'password' } });
|
||||
registrationPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -181,11 +181,9 @@ describe('RegistrationPage', () => {
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(getByLabelText, payload);
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(registrationPage, payload);
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
});
|
||||
|
||||
@@ -213,62 +211,13 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
populateRequiredFields(registrationPage, formPayload, true);
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...formPayload, country: 'PK' }));
|
||||
});
|
||||
|
||||
it('should display an error when form is submitted with an invalid email', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
const emailError = "We couldn't create your account.Please check your responses and try again.";
|
||||
|
||||
const formPayload = {
|
||||
name: 'Petro',
|
||||
username: 'petro_qa',
|
||||
email: 'petro @example.com',
|
||||
password: 'password1',
|
||||
country: 'Ukraine',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(emailError);
|
||||
});
|
||||
|
||||
it('should display an error when form is submitted with an invalid username', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
const usernameError = "We couldn't create your account.Please check your responses and try again.";
|
||||
|
||||
const formPayload = {
|
||||
name: 'Petro',
|
||||
username: 'petro qa',
|
||||
email: 'petro@example.com',
|
||||
password: 'password1',
|
||||
country: 'Ukraine',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(usernameError);
|
||||
});
|
||||
|
||||
it('should submit form with marketing email opt in value', () => {
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
@@ -288,10 +237,9 @@ describe('RegistrationPage', () => {
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(getByLabelText, payload);
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(registrationPage, payload);
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
|
||||
mergeConfig({
|
||||
@@ -302,30 +250,25 @@ describe('RegistrationPage', () => {
|
||||
it('should not dispatch registerNewUser on empty form Submission', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).not.toHaveBeenCalledWith(registerNewUser({}));
|
||||
});
|
||||
|
||||
// ******** test registration form validations ********
|
||||
|
||||
it('should show error messages for required fields on empty form submission', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
Object.entries(emptyFieldValidation).forEach(([fieldName, validationMessage]) => {
|
||||
const feedbackElement = container.querySelector(`div[feedback-for="${fieldName}"]`);
|
||||
expect(feedbackElement.textContent).toContain(validationMessage);
|
||||
});
|
||||
expect(registrationPage.find('div[feedback-for="name"]').text()).toEqual(emptyFieldValidation.name);
|
||||
expect(registrationPage.find('div[feedback-for="username"]').text()).toEqual(emptyFieldValidation.username);
|
||||
expect(registrationPage.find('div[feedback-for="email"]').text()).toEqual(emptyFieldValidation.email);
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country);
|
||||
|
||||
const alertBanner = 'We couldn\'t create your account.Please check your responses and try again.';
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(alertBanner);
|
||||
expect(registrationPage.find('#validation-errors').first().text()).toEqual(alertBanner);
|
||||
});
|
||||
|
||||
it('should set errors with validations returned by registration api', () => {
|
||||
@@ -341,28 +284,23 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlProvider locale="en"><IntlRegistrationPage {...props} /></IntlProvider>)));
|
||||
const usernameFeedback = container.querySelector('div[feedback-for="username"]');
|
||||
const emailFeedback = container.querySelector('div[feedback-for="email"]');
|
||||
|
||||
expect(usernameFeedback.textContent).toContain(usernameError);
|
||||
expect(emailFeedback.textContent).toContain(emailError);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />))).find('RegistrationPage');
|
||||
expect(
|
||||
registrationPage.find('div[feedback-for="username"]').text(),
|
||||
).toEqual(usernameError);
|
||||
expect(
|
||||
registrationPage.find('div[feedback-for="email"]').text(),
|
||||
).toEqual(emailError);
|
||||
});
|
||||
|
||||
it('should clear error on focus', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
const submitButton = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(submitButton);
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
|
||||
const passwordFeedback = container.querySelector('div[feedback-for="password"]');
|
||||
expect(passwordFeedback.textContent).toContain(emptyFieldValidation.password);
|
||||
|
||||
const passwordField = container.querySelector('input#password');
|
||||
fireEvent.focus(passwordField);
|
||||
|
||||
const isFeedbackPresent = container.contains(passwordFeedback);
|
||||
expect(isFeedbackPresent).toBeFalsy();
|
||||
registrationPage.find('input#password').simulate('focus');
|
||||
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear registration backend error on change', () => {
|
||||
@@ -378,21 +316,19 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(
|
||||
<IntlRegistrationPage {...props} />,
|
||||
)));
|
||||
))).find('RegistrationPage');
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
fireEvent.change(emailInput, { target: { value: 'test1@gmail.com', name: 'email' } });
|
||||
registrationPage.find('input#email').simulate('change', { target: { value: 'test1@gmail.com', name: 'email' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('email'));
|
||||
});
|
||||
|
||||
// ******** test form buttons and fields ********
|
||||
|
||||
it('should match default button state', () => {
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const button = container.querySelector('button[type="submit"] span');
|
||||
expect(button.textContent).toEqual('Create an account for free');
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('button[type="submit"] span').first().text()).toEqual('Create an account for free');
|
||||
});
|
||||
|
||||
it('should match pending button state', () => {
|
||||
@@ -404,10 +340,10 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const button = registrationPage.find('button[type="submit"] span').first();
|
||||
|
||||
const button = container.querySelector('button[type="submit"] span.sr-only');
|
||||
expect(button.textContent).toEqual('pending');
|
||||
expect(button.find('.sr-only').text()).toEqual('pending');
|
||||
});
|
||||
|
||||
it('should display opt-in/opt-out checkbox', () => {
|
||||
@@ -415,9 +351,8 @@ describe('RegistrationPage', () => {
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
});
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const checkboxDivs = container.querySelectorAll('div.form-field--checkbox');
|
||||
expect(checkboxDivs.length).toEqual(1);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('div.form-field--checkbox').length).toEqual(1);
|
||||
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: '',
|
||||
@@ -428,12 +363,8 @@ describe('RegistrationPage', () => {
|
||||
const buttonLabel = 'Register';
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL, search: `?cta=${buttonLabel}` };
|
||||
const { container } = render(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
const button = container.querySelector('button[type="submit"] span');
|
||||
|
||||
const buttonText = button.textContent;
|
||||
|
||||
expect(buttonText).toEqual(buttonLabel);
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('button[type="submit"] span').first().text()).toEqual(buttonLabel);
|
||||
});
|
||||
|
||||
it('should check user retention cookie', () => {
|
||||
@@ -447,7 +378,7 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(document.cookie).toMatch(`${getConfig().USER_RETENTION_COOKIE_NAME}=true`);
|
||||
});
|
||||
|
||||
@@ -465,7 +396,7 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(window.location.href).toBe(dashboardURL);
|
||||
});
|
||||
|
||||
@@ -492,7 +423,7 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(window.location.href).toBe(dashboardUrl);
|
||||
});
|
||||
|
||||
@@ -521,11 +452,12 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
render(reduxWrapper(
|
||||
const progressiveProfilingPage = mount(reduxWrapper(
|
||||
<Router>
|
||||
<IntlRegistrationPage {...props} />
|
||||
</Router>,
|
||||
));
|
||||
progressiveProfilingPage.update();
|
||||
expect(mockNavigate).toHaveBeenCalledWith(AUTHN_PROGRESSIVE_PROFILING);
|
||||
});
|
||||
|
||||
@@ -541,12 +473,12 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationFormBegin({ ...registrationFormData }));
|
||||
});
|
||||
|
||||
it('should send page event when register page is rendered', () => {
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
|
||||
});
|
||||
|
||||
@@ -564,7 +496,7 @@ describe('RegistrationPage', () => {
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', {});
|
||||
});
|
||||
|
||||
@@ -588,17 +520,10 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { container } = render(reduxWrapper(
|
||||
<Router>
|
||||
<IntlRegistrationPage {...props} />
|
||||
</Router>,
|
||||
));
|
||||
|
||||
const emailInput = container.querySelector('input#email');
|
||||
const usernameInput = container.querySelector('input#username');
|
||||
|
||||
expect(emailInput.value).toEqual('test@example.com');
|
||||
expect(usernameInput.value).toEqual('test');
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />))).find('RegistrationPage');
|
||||
expect(registrationPage.find('input#email').props().value).toEqual('test@example.com');
|
||||
expect(registrationPage.find('input#username').props().value).toEqual('test');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(setUserPipelineDataLoaded(true));
|
||||
});
|
||||
|
||||
@@ -613,9 +538,8 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const validationErrors = container.querySelector('div#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />))).find('RegistrationPage');
|
||||
expect(registrationPage.find('div#validation-errors').first().text()).toContain(
|
||||
'An error has occurred. Try refreshing the page, or check your internet connection.',
|
||||
);
|
||||
});
|
||||
@@ -640,19 +564,15 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(
|
||||
<IntlRegistrationPage {...props} />,
|
||||
))).find('RegistrationPage');
|
||||
|
||||
const fullNameInput = container.querySelector('input#name');
|
||||
const usernameInput = container.querySelector('input#username');
|
||||
const emailInput = container.querySelector('input#email');
|
||||
const passwordInput = container.querySelector('input#password');
|
||||
const emailSuggestion = container.querySelector('.email-suggestion-alert-warning');
|
||||
|
||||
expect(fullNameInput.value).toEqual('John Doe');
|
||||
expect(usernameInput.value).toEqual('john_doe');
|
||||
expect(emailInput.value).toEqual('john.doe@yopmail.com');
|
||||
expect(passwordInput.value).toEqual('password1');
|
||||
expect(emailSuggestion.textContent).toEqual('john.doe@hotmail.com');
|
||||
expect(registrationPage.find('input#name').props().value).toEqual('John Doe');
|
||||
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-suggestion-alert-warning').first().text()).toEqual('john.doe@hotmail.com');
|
||||
});
|
||||
|
||||
// ********* Embedded experience tests *********/
|
||||
@@ -686,22 +606,23 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const progressiveProfilingPage = mount(reduxWrapper(
|
||||
<IntlRegistrationPage {...props} />,
|
||||
));
|
||||
progressiveProfilingPage.update();
|
||||
expect(window.parent.postMessage).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not display validations error on blur event when embedded variant is rendered', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
|
||||
const { container } = render(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
|
||||
const usernameInput = container.querySelector('input#username');
|
||||
fireEvent.blur(usernameInput, { target: { value: '', name: 'username' } });
|
||||
expect(container.querySelector('div[feedback-for="username"]')).toBeFalsy();
|
||||
registrationPage.find('input#username').simulate('blur', { target: { value: '', name: 'username' } });
|
||||
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeFalsy();
|
||||
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
fireEvent.blur(countryInput, { target: { value: '', name: 'country' } });
|
||||
expect(container.querySelector('div[feedback-for="country"]')).toBeFalsy();
|
||||
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
|
||||
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set errors in temporary state when validations are returned by registration api', () => {
|
||||
@@ -720,15 +641,12 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { container } = render(routerWrapper(reduxWrapper(
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(
|
||||
<IntlRegistrationPage {...props} />),
|
||||
));
|
||||
)).find('RegistrationPage');
|
||||
|
||||
const usernameFeedback = container.querySelector('div[feedback-for="username"]');
|
||||
const emailFeedback = container.querySelector('div[feedback-for="email"]');
|
||||
|
||||
expect(usernameFeedback).toBeNull();
|
||||
expect(emailFeedback).toBeNull();
|
||||
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeFalsy();
|
||||
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear error on focus for embedded experience also', () => {
|
||||
@@ -738,18 +656,13 @@ describe('RegistrationPage', () => {
|
||||
search: '?host=http://localhost/host-website',
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const submitButton = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(submitButton);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
const passwordFeedback = container.querySelector('div[feedback-for="password"]');
|
||||
expect(passwordFeedback.textContent).toContain(emptyFieldValidation.password);
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
|
||||
const passwordField = container.querySelector('input#password');
|
||||
fireEvent.focus(passwordField);
|
||||
|
||||
const updatedPasswordFeedback = container.querySelector('div[feedback-for="password"]');
|
||||
expect(updatedPasswordFeedback).toBeNull();
|
||||
registrationPage.find('input#password').simulate('focus');
|
||||
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show spinner instead of form while registering if autoSubmitRegForm is true', () => {
|
||||
@@ -779,12 +692,9 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const spinnerElement = container.querySelector('#tpa-spinner');
|
||||
const registrationFormElement = container.querySelector('#registration-form');
|
||||
|
||||
expect(spinnerElement).toBeTruthy();
|
||||
expect(registrationFormElement).toBeFalsy();
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('#tpa-spinner').exists()).toBeTruthy();
|
||||
expect(registrationPage.find('#registration-form').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should auto register if autoSubmitRegForm is true and pipeline details are loaded', () => {
|
||||
@@ -830,7 +740,7 @@ describe('RegistrationPage', () => {
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({
|
||||
name: 'John Doe',
|
||||
username: 'john_doe',
|
||||
|
||||
@@ -46,6 +46,12 @@ const ConfigurableRegistrationForm = (props) => {
|
||||
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!formFields.country) {
|
||||
setFormFields(prevState => ({ ...prevState, country: { countryCode: '', displayValue: '' } }));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If auto submitting register form, we will check tos and honor code fields if they exist for feature parity.
|
||||
*/
|
||||
|
||||
@@ -5,14 +5,14 @@ import { mergeConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
getLocale, injectIntl, IntlProvider,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { registerNewUser } from '../../data/actions';
|
||||
import { FIELDS } from '../../data/constants';
|
||||
import RegistrationPage from '../../RegistrationPage';
|
||||
import ConfigurableRegistrationForm from '../ConfigurableRegistrationForm';
|
||||
import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
|
||||
import { registerNewUser } from '../data/actions';
|
||||
import { FIELDS } from '../data/constants';
|
||||
import RegistrationPage from '../RegistrationPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
@@ -127,16 +127,16 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => {
|
||||
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } });
|
||||
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
|
||||
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
|
||||
const populateRequiredFields = (registrationPage, payload, isThirdPartyAuth = false) => {
|
||||
registrationPage.find('input#name').simulate('change', { target: { value: payload.name, name: 'name' } });
|
||||
registrationPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } });
|
||||
registrationPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } });
|
||||
|
||||
fireEvent.change(getByLabelText('Country/Region'), { target: { value: payload.country, name: 'country' } });
|
||||
fireEvent.blur(getByLabelText('Country/Region'), { 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) {
|
||||
fireEvent.change(getByLabelText('Password'), { target: { value: payload.password, name: 'password' } });
|
||||
registrationPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -157,12 +157,12 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
},
|
||||
};
|
||||
|
||||
render(routerWrapper(reduxWrapper(
|
||||
const configurableRegistrationForm = mount(routerWrapper(reduxWrapper(
|
||||
<IntlConfigurableRegistrationForm {...props} />,
|
||||
)));
|
||||
|
||||
expect(document.querySelector('#profession')).toBeTruthy();
|
||||
expect(document.querySelector('#tos')).toBeTruthy();
|
||||
expect(configurableRegistrationForm.find('#profession').exists()).toBeTruthy();
|
||||
expect(configurableRegistrationForm.find('#tos').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should check TOS and honor code fields if they exist when auto submitting register form', () => {
|
||||
@@ -187,7 +187,7 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
autoSubmitRegistrationForm: true,
|
||||
};
|
||||
|
||||
render(routerWrapper(reduxWrapper(
|
||||
mount(routerWrapper(reduxWrapper(
|
||||
<IntlConfigurableRegistrationForm {...props} />,
|
||||
)));
|
||||
|
||||
@@ -215,9 +215,9 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(document.querySelector('#profession')).toBeTruthy();
|
||||
expect(document.querySelector('#tos')).toBeTruthy();
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('#profession').exists()).toBeTruthy();
|
||||
expect(registrationPage.find('#tos').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should submit form with fields returned by backend in payload', () => {
|
||||
@@ -249,17 +249,11 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
populateRequiredFields(getByLabelText, payload);
|
||||
|
||||
const professionInput = getByLabelText('Profession');
|
||||
fireEvent.change(professionInput, { target: { value: 'Engineer', name: 'profession' } });
|
||||
|
||||
const submitButton = container.querySelector('button.btn-brand');
|
||||
|
||||
fireEvent.click(submitButton);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
populateRequiredFields(registrationPage, payload);
|
||||
registrationPage.find('input#profession').simulate('change', { target: { value: 'Engineer', name: 'profession' } });
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
});
|
||||
|
||||
@@ -284,18 +278,12 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const submitButton = container.querySelector('button.btn-brand');
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
const professionErrorElement = container.querySelector('#profession-error');
|
||||
const countryErrorElement = container.querySelector('div[feedback-for="country"]');
|
||||
const confirmEmailErrorElement = container.querySelector('#confirm_email-error');
|
||||
|
||||
expect(professionErrorElement.textContent).toEqual(professionError);
|
||||
expect(countryErrorElement.textContent).toEqual(countryError);
|
||||
expect(confirmEmailErrorElement.textContent).toEqual(confirmEmailError);
|
||||
expect(registrationPage.find('#profession-error').last().text()).toEqual(professionError);
|
||||
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(countryError);
|
||||
expect(registrationPage.find('#confirm_email-error').last().text()).toEqual(confirmEmailError);
|
||||
});
|
||||
|
||||
it('should show country field validation when country name is invalid', () => {
|
||||
@@ -310,17 +298,11 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const countryInput = container.querySelector('input[name="country"]');
|
||||
fireEvent.change(countryInput, { target: { value: 'Pak', name: 'country' } });
|
||||
fireEvent.blur(countryInput, { target: { value: 'Pak', name: 'country' } });
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: 'Pak', name: 'country' } });
|
||||
|
||||
const submitButton = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
const countryErrorElement = container.querySelector('div[feedback-for="country"]');
|
||||
|
||||
expect(countryErrorElement.textContent).toEqual(invalidCountryError);
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(invalidCountryError);
|
||||
});
|
||||
|
||||
it('should show error if email and confirm email fields do not match', () => {
|
||||
@@ -335,60 +317,10 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
const emailInput = getByLabelText('Email');
|
||||
const confirmEmailInput = getByLabelText('Confirm Email');
|
||||
|
||||
fireEvent.change(emailInput, { target: { value: 'test1@gmail.com', name: 'email' } });
|
||||
fireEvent.blur(confirmEmailInput, { target: { value: 'test2@gmail.com', name: 'confirm_email' } });
|
||||
|
||||
const confirmEmailErrorElement = container.querySelector('div#confirm_email-error');
|
||||
|
||||
expect(confirmEmailErrorElement.textContent).toEqual('The email addresses do not match.');
|
||||
});
|
||||
|
||||
it('should show error if email and confirm email fields do not match on submit click', () => {
|
||||
const formPayload = {
|
||||
name: 'Petro',
|
||||
username: 'petro_qa',
|
||||
email: 'petro@example.com',
|
||||
password: 'password1',
|
||||
country: 'Ukraine',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
confirm_email: {
|
||||
name: 'confirm_email', type: 'text', label: 'Confirm Email',
|
||||
},
|
||||
country: { name: 'country' },
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
fireEvent.change(
|
||||
getByLabelText('Confirm Email'),
|
||||
{ target: { value: 'test2@gmail.com', name: 'confirm_email' } },
|
||||
);
|
||||
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
const confirmEmailErrorElement = container.querySelector('div#confirm_email-error');
|
||||
expect(confirmEmailErrorElement.textContent).toEqual('The email addresses do not match.');
|
||||
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(
|
||||
"We couldn't create your account.Please check your responses and try again.",
|
||||
);
|
||||
const registrationPage = mount(routerWrapper(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.');
|
||||
});
|
||||
|
||||
it('should run validations for configurable focused field on form submission', () => {
|
||||
@@ -405,19 +337,11 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { getByLabelText, container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
|
||||
);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('input#profession').simulate('focus', { target: { value: '', name: 'profession' } });
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
const professionInput = getByLabelText('Profession');
|
||||
fireEvent.focus(professionInput);
|
||||
|
||||
const submitButton = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
const professionErrorElement = container.querySelector('#profession-error');
|
||||
|
||||
expect(professionErrorElement.textContent).toEqual(professionError);
|
||||
expect(registrationPage.find('#profession-error').last().text()).toEqual(professionError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,15 +5,15 @@ import { mergeConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
configure, getLocale, injectIntl, IntlProvider,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import RegistrationFailureMessage from './RegistrationFailure';
|
||||
import {
|
||||
FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR, TPA_AUTHENTICATION_FAILURE, TPA_SESSION_EXPIRED,
|
||||
} from '../../data/constants';
|
||||
import RegistrationPage from '../../RegistrationPage';
|
||||
import RegistrationFailureMessage from '../RegistrationFailure';
|
||||
} from '../data/constants';
|
||||
import RegistrationPage from '../RegistrationPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
@@ -137,13 +137,9 @@ describe('RegistrationFailure', () => {
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
|
||||
const alertHeading = container.querySelectorAll('div.alert-heading');
|
||||
expect(alertHeading.length).toEqual(1);
|
||||
|
||||
const alert = container.querySelector('div.alert');
|
||||
expect(alert.textContent).toContain(expectedMessage);
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match registration api rate limit error message', () => {
|
||||
@@ -153,13 +149,9 @@ describe('RegistrationFailure', () => {
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
|
||||
const alertHeading = container.querySelectorAll('div.alert-heading');
|
||||
expect(alertHeading.length).toEqual(1);
|
||||
|
||||
const alert = container.querySelector('div.alert');
|
||||
expect(alert.textContent).toContain(expectedMessage);
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa session expired error message', () => {
|
||||
@@ -172,13 +164,9 @@ describe('RegistrationFailure', () => {
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
|
||||
const alertHeading = container.querySelectorAll('div.alert-heading');
|
||||
expect(alertHeading.length).toEqual(1);
|
||||
|
||||
const alert = container.querySelector('div.alert');
|
||||
expect(alert.textContent).toContain(expectedMessage);
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa authentication failed error message', () => {
|
||||
@@ -191,13 +179,9 @@ describe('RegistrationFailure', () => {
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
|
||||
const alertHeading = container.querySelectorAll('div.alert-heading');
|
||||
expect(alertHeading.length).toEqual(1);
|
||||
|
||||
const alert = container.querySelector('div.alert');
|
||||
expect(alert.textContent).toContain(expectedMessageSubstring);
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toContain(expectedMessageSubstring);
|
||||
});
|
||||
|
||||
it('should display error message based on the error code returned by API', () => {
|
||||
@@ -211,10 +195,10 @@ describe('RegistrationFailure', () => {
|
||||
},
|
||||
});
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const validationError = screen.queryByText('An error has occurred. Try refreshing the page, or check your internet connection.');
|
||||
|
||||
expect(validationError).not.toBeNull();
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />))).find('RegistrationPage');
|
||||
expect(registrationPage.find('div#validation-errors').first().text()).toContain(
|
||||
'An error has occurred. Try refreshing the page, or check your internet connection.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,23 +2,17 @@ import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Hyperlink, Icon,
|
||||
} from '@openedx/paragon';
|
||||
import { Institution } from '@openedx/paragon/icons';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import messages from './messages';
|
||||
import {
|
||||
ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
|
||||
} from '../data/constants';
|
||||
|
||||
import {
|
||||
RenderInstitutionButton,
|
||||
SocialAuthProviders,
|
||||
} from './index';
|
||||
} from '../../common-components';
|
||||
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.
|
||||
@@ -26,59 +20,33 @@ import {
|
||||
const ThirdPartyAuth = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
providers,
|
||||
secondaryProviders,
|
||||
currentProvider,
|
||||
handleInstitutionLogin,
|
||||
thirdPartyAuthApiStatus,
|
||||
isLoginPage,
|
||||
providers, secondaryProviders, currentProvider, handleInstitutionLogin, thirdPartyAuthApiStatus,
|
||||
} = props;
|
||||
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
|
||||
const isSocialAuthActive = !!providers.length && !currentProvider;
|
||||
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
||||
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
||||
|
||||
return (
|
||||
<>
|
||||
{((isEnterpriseLoginDisabled && isInstitutionAuthActive) || isSocialAuthActive) && (
|
||||
<div className="mt-4 mb-3 h4">
|
||||
{isLoginPage
|
||||
? formatMessage(messages['login.other.options.heading'])
|
||||
: formatMessage(messages['registration.other.options.heading'])}
|
||||
{formatMessage(messages['registration.other.options.heading'])}
|
||||
</div>
|
||||
)}
|
||||
{(isLoginPage && !isEnterpriseLoginDisabled && isSocialAuthActive) && (
|
||||
<Hyperlink
|
||||
className={classNames(
|
||||
'btn btn-link btn-sm text-body p-0',
|
||||
{ 'mb-0': thirdPartyAuthApiStatus === PENDING_STATE },
|
||||
{ 'mb-4': thirdPartyAuthApiStatus !== PENDING_STATE },
|
||||
)}
|
||||
destination={enterpriseLoginURL}
|
||||
>
|
||||
<Icon src={Institution} className="institute-icon" />
|
||||
{formatMessage(messages['enterprise.login.btn.text'])}
|
||||
</Hyperlink>
|
||||
)}
|
||||
|
||||
{thirdPartyAuthApiStatus === PENDING_STATE ? (
|
||||
<div className="mt-4">
|
||||
<Skeleton className="tpa-skeleton" height={36} count={2} />
|
||||
</div>
|
||||
<Skeleton className="tpa-skeleton" height={36} count={2} />
|
||||
) : (
|
||||
<>
|
||||
{(isEnterpriseLoginDisabled && isInstitutionAuthActive) && (
|
||||
<RenderInstitutionButton
|
||||
onSubmitHandler={handleInstitutionLogin}
|
||||
buttonTitle={formatMessage(messages['institution.login.button'])}
|
||||
buttonTitle={formatMessage(messages['register.institution.login.button'])}
|
||||
/>
|
||||
)}
|
||||
{isSocialAuthActive && (
|
||||
<div className="row m-0">
|
||||
<SocialAuthProviders
|
||||
socialAuthProviders={providers}
|
||||
referrer={isLoginPage ? LOGIN_PAGE : REGISTER_PAGE}
|
||||
/>
|
||||
<SocialAuthProviders socialAuthProviders={providers} referrer={REGISTER_PAGE} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -91,8 +59,7 @@ ThirdPartyAuth.defaultProps = {
|
||||
currentProvider: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
isLoginPage: false,
|
||||
thirdPartyAuthApiStatus: 'pending',
|
||||
};
|
||||
|
||||
ThirdPartyAuth.propTypes = {
|
||||
@@ -119,7 +86,6 @@ ThirdPartyAuth.propTypes = {
|
||||
}),
|
||||
),
|
||||
thirdPartyAuthApiStatus: PropTypes.string,
|
||||
isLoginPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ThirdPartyAuth;
|
||||
@@ -5,14 +5,15 @@ import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
configure, getLocale, injectIntl, IntlProvider,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import renderer from 'react-test-renderer';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
|
||||
} from '../../../data/constants';
|
||||
import RegistrationPage from '../../RegistrationPage';
|
||||
} from '../../data/constants';
|
||||
import RegistrationPage from '../RegistrationPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
@@ -156,13 +157,8 @@ describe('ThirdPartyAuth', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByLabelText } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />, { store })),
|
||||
);
|
||||
|
||||
const passwordField = queryByLabelText('Password');
|
||||
|
||||
expect(passwordField).toBeNull();
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('input#password').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should render tpa button for tpa_hint id matching one of the primary providers', () => {
|
||||
@@ -181,15 +177,9 @@ describe('ThirdPartyAuth', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
const { container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
|
||||
);
|
||||
const tpaButton = container.querySelector(`button#${ssoProvider.id}`);
|
||||
|
||||
expect(tpaButton).toBeTruthy();
|
||||
expect(tpaButton.textContent).toEqual(ssoProvider.name);
|
||||
expect(tpaButton.classList.contains('btn-tpa')).toBe(true);
|
||||
expect(tpaButton.classList.contains(`btn-${ssoProvider.id}`)).toBe(true);
|
||||
const registrationPage = mount(routerWrapper(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 display skeleton if tpa_hint is true and thirdPartyAuthContext is pending', () => {
|
||||
@@ -207,10 +197,8 @@ describe('ThirdPartyAuth', () => {
|
||||
search: `?next=/dashboard&tpa_hint=${ssoProvider.id}`,
|
||||
};
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const skeletonElement = container.querySelector('.react-loading-skeleton');
|
||||
|
||||
expect(skeletonElement).toBeTruthy();
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('.react-loading-skeleton').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render icon if icon classes are missing in providers', () => {
|
||||
@@ -231,10 +219,8 @@ describe('ThirdPartyAuth', () => {
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
|
||||
ssoProvider.iconImage = null;
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const iconElement = container.querySelector(`button#${ssoProvider.id} div span.pgn__icon`);
|
||||
|
||||
expect(iconElement).toBeTruthy();
|
||||
const registrationPage = mount(routerWrapper(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', () => {
|
||||
@@ -254,7 +240,7 @@ describe('ThirdPartyAuth', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.registerUrl);
|
||||
});
|
||||
|
||||
@@ -275,10 +261,8 @@ describe('ThirdPartyAuth', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const providerButton = container.querySelector(`button#${ssoProvider.id} span#provider-name`);
|
||||
|
||||
expect(providerButton.textContent).toEqual(expectedMessage);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find(`button#${ssoProvider.id}`).find('span#provider-name').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should show single sign on provider button', () => {
|
||||
@@ -293,13 +277,8 @@ describe('ThirdPartyAuth', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />, { store })),
|
||||
);
|
||||
|
||||
const buttonsWithId = container.querySelectorAll(`button#${ssoProvider.id}`);
|
||||
|
||||
expect(buttonsWithId.length).toEqual(1);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find(`button#${ssoProvider.id}`).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should show single sign on provider button', () => {
|
||||
@@ -314,13 +293,8 @@ describe('ThirdPartyAuth', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
|
||||
);
|
||||
|
||||
const buttonsWithId = container.querySelectorAll(`button#${ssoProvider.id}`);
|
||||
|
||||
expect(buttonsWithId.length).toEqual(1);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find(`button#${ssoProvider.id}`).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should display InstitutionLogistration if insitutionLogin prop is true', () => {
|
||||
@@ -329,9 +303,8 @@ describe('ThirdPartyAuth', () => {
|
||||
institutionLogin: true,
|
||||
};
|
||||
|
||||
const { getByText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const headingElement = getByText('Register with institution/campus credentials');
|
||||
expect(headingElement).toBeTruthy();
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('.institutions__heading').text()).toEqual('Register with institution/campus credentials');
|
||||
});
|
||||
|
||||
it('should redirect to social auth provider url on SSO button click', () => {
|
||||
@@ -353,13 +326,9 @@ describe('ThirdPartyAuth', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
const { container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
|
||||
);
|
||||
|
||||
const ssoButton = container.querySelector('button#oa2-apple-id');
|
||||
fireEvent.click(ssoButton);
|
||||
const loginPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
loginPage.find('button#oa2-apple-id').simulate('click');
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + registerUrl);
|
||||
});
|
||||
|
||||
@@ -385,7 +354,7 @@ describe('ThirdPartyAuth', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
|
||||
});
|
||||
|
||||
@@ -406,11 +375,9 @@ describe('ThirdPartyAuth', () => {
|
||||
const expectedMessage = `${'You\'ve successfully signed into Apple! We just need a little more information before '
|
||||
+ 'you start learning with '}${ getConfig().SITE_NAME }.`;
|
||||
|
||||
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
const tpaAlert = container.querySelector('#tpa-alert p');
|
||||
expect(tpaAlert.textContent).toEqual(expectedMessage);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('#tpa-alert').find('p').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should display errorMessage if third party authentication fails', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
@@ -436,15 +403,9 @@ describe('ThirdPartyAuth', () => {
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const { container } = render(
|
||||
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
|
||||
);
|
||||
|
||||
const alertHeading = container.querySelector('div.alert-heading');
|
||||
expect(alertHeading).toBeTruthy();
|
||||
|
||||
const alert = container.querySelector('div.alert');
|
||||
expect(alert.textContent).toContain('An error occurred');
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toContain('An error occurred');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@ export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_
|
||||
export const REGISTRATION_CLEAR_BACKEND_ERROR = 'REGISTRATION_CLEAR_BACKEND_ERROR';
|
||||
export const REGISTER_SET_COUNTRY_CODE = 'REGISTER_SET_COUNTRY_CODE';
|
||||
export const REGISTER_SET_USER_PIPELINE_DATA_LOADED = 'REGISTER_SET_USER_PIPELINE_DATA_LOADED';
|
||||
export const REGISTER_SET_EMAIL_SUGGESTIONS = 'REGISTER_SET_EMAIL_SUGGESTIONS';
|
||||
|
||||
// Backup registration form
|
||||
export const backupRegistrationForm = () => ({
|
||||
@@ -38,12 +37,6 @@ export const fetchRealtimeValidationsFailure = () => ({
|
||||
type: REGISTER_FORM_VALIDATIONS.FAILURE,
|
||||
});
|
||||
|
||||
// Set email field frontend validations
|
||||
export const setEmailSuggestionInStore = (emailSuggestion) => ({
|
||||
type: REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
payload: { emailSuggestion },
|
||||
});
|
||||
|
||||
// Register
|
||||
export const registerNewUser = registrationInfo => ({
|
||||
type: REGISTER_NEW_USER.BASE,
|
||||
|
||||
@@ -3,9 +3,7 @@ import {
|
||||
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
|
||||
REGISTER_FORM_VALIDATIONS,
|
||||
REGISTER_NEW_USER,
|
||||
REGISTER_SET_COUNTRY_CODE,
|
||||
REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||
REGISTER_SET_COUNTRY_CODE, REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||
REGISTRATION_CLEAR_BACKEND_ERROR,
|
||||
} from './actions';
|
||||
import {
|
||||
@@ -50,7 +48,7 @@ const reducer = (state = defaultState, action = {}) => {
|
||||
};
|
||||
case BACKUP_REGISTRATION_DATA.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
...defaultState,
|
||||
usernameSuggestions: state.usernameSuggestions,
|
||||
registrationFormData: { ...action.payload },
|
||||
userPipelineDataLoaded: state.userPipelineDataLoaded,
|
||||
@@ -121,14 +119,6 @@ const reducer = (state = defaultState, action = {}) => {
|
||||
userPipelineDataLoaded: value,
|
||||
};
|
||||
}
|
||||
case REGISTER_SET_EMAIL_SUGGESTIONS:
|
||||
return {
|
||||
...state,
|
||||
registrationFormData: {
|
||||
...state.registrationFormData,
|
||||
emailSuggestion: action.payload.emailSuggestion,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Selector for backend validations which processes the api output and generates a
|
||||
* key value dict for field errors.
|
||||
* @returns {{username: string}|{name: string}|*|{}|null}
|
||||
*/
|
||||
const getRegistrationError = state => state.register.registrationError;
|
||||
const getValidations = state => state.register.validations;
|
||||
|
||||
const getBackendValidations = createSelector(
|
||||
[getRegistrationError, getValidations],
|
||||
(registrationError, validations) => {
|
||||
if (validations) {
|
||||
return validations.validationDecisions;
|
||||
}
|
||||
|
||||
if (Object.keys(registrationError).length > 0) {
|
||||
const fields = Object.keys(registrationError).filter(
|
||||
(fieldName) => !(fieldName in ['errorCode', 'usernameSuggestions']),
|
||||
);
|
||||
|
||||
const validationDecisions = {};
|
||||
fields.forEach(field => {
|
||||
validationDecisions[field] = registrationError[field][0].userMessage || '';
|
||||
});
|
||||
return validationDecisions;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export default getBackendValidations;
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
REGISTER_FORM_VALIDATIONS,
|
||||
REGISTER_NEW_USER,
|
||||
REGISTER_SET_COUNTRY_CODE,
|
||||
REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||
REGISTRATION_CLEAR_BACKEND_ERROR,
|
||||
} from '../actions';
|
||||
@@ -65,29 +64,6 @@ describe('Registration Reducer Tests', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set email suggestions', () => {
|
||||
const emailSuggestion = {
|
||||
type: 'test type',
|
||||
suggestion: 'test suggestion',
|
||||
};
|
||||
const action = {
|
||||
type: REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
payload: { emailSuggestion },
|
||||
};
|
||||
|
||||
expect(reducer(defaultState, action)).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
registrationFormData: {
|
||||
...defaultState.registrationFormData,
|
||||
emailSuggestion: {
|
||||
type: 'test type', suggestion: 'test suggestion',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should set redirect url dashboard on registration success action', () => {
|
||||
const payload = {
|
||||
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
|
||||
import { LETTER_REGEX, NUMBER_REGEX } from '../../data/constants';
|
||||
import messages from '../messages';
|
||||
import validateEmail from '../RegistrationFields/EmailField/validator';
|
||||
import validateName from '../RegistrationFields/NameField/validator';
|
||||
import validateUsername from '../RegistrationFields/UsernameField/validator';
|
||||
|
||||
/**
|
||||
* It validates the password field value
|
||||
@@ -38,61 +35,36 @@ export const isFormValid = (
|
||||
) => {
|
||||
const fieldErrors = { ...errors };
|
||||
let isValid = true;
|
||||
let emailSuggestion = { suggestion: '', type: '' };
|
||||
|
||||
Object.keys(payload).forEach(key => {
|
||||
switch (key) {
|
||||
case 'name':
|
||||
fieldErrors.name = validateName(payload.name, formatMessage);
|
||||
if (fieldErrors.name) { isValid = false; }
|
||||
break;
|
||||
case 'email': {
|
||||
const {
|
||||
fieldError, confirmEmailError, suggestion,
|
||||
} = validateEmail(payload.email, configurableFormFields?.confirm_email, formatMessage);
|
||||
if (fieldError) {
|
||||
fieldErrors.email = fieldError;
|
||||
isValid = false;
|
||||
}
|
||||
if (confirmEmailError) {
|
||||
fieldErrors.confirm_email = confirmEmailError;
|
||||
isValid = false;
|
||||
}
|
||||
emailSuggestion = suggestion;
|
||||
break;
|
||||
if (!payload[key]) {
|
||||
fieldErrors[key] = formatMessage(messages[`empty.${key}.field.error`]);
|
||||
}
|
||||
case 'username':
|
||||
fieldErrors.username = validateUsername(payload.username, formatMessage);
|
||||
if (fieldErrors.username) { isValid = false; }
|
||||
break;
|
||||
case 'password':
|
||||
fieldErrors.password = validatePasswordField(payload.password, formatMessage);
|
||||
if (fieldErrors.password) { isValid = false; }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (fieldErrors[key]) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Don't validate when country field is optional or hidden and not present on registration form
|
||||
if (configurableFormFields?.country && !configurableFormFields.country?.displayValue) {
|
||||
fieldErrors.country = formatMessage(messages['empty.country.field.error']);
|
||||
isValid = false;
|
||||
} else if (configurableFormFields?.country && !configurableFormFields.country?.countryCode) {
|
||||
fieldErrors.country = formatMessage(messages['invalid.country.field.error']);
|
||||
isValid = false;
|
||||
if (getConfig().SHOW_CONFIGURABLE_EDX_FIELDS) {
|
||||
if (!configurableFormFields?.country?.displayValue) {
|
||||
fieldErrors.country = formatMessage(messages['empty.country.field.error']);
|
||||
isValid = false;
|
||||
} else if (!configurableFormFields?.country?.countryCode) {
|
||||
fieldErrors.country = formatMessage(messages['invalid.country.field.error']);
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(fieldDescriptions).forEach(key => {
|
||||
if (key === 'country' && !configurableFormFields?.country?.displayValue) {
|
||||
if (key === 'country' && !configurableFormFields.country.displayValue) {
|
||||
fieldErrors[key] = formatMessage(messages['empty.country.field.error']);
|
||||
} else if (!configurableFormFields[key]) {
|
||||
fieldErrors[key] = fieldDescriptions[key].error_message;
|
||||
}
|
||||
if (fieldErrors[key]) { isValid = false; }
|
||||
if (fieldErrors[key]) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return { isValid, fieldErrors, emailSuggestion };
|
||||
return { isValid, fieldErrors };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -132,3 +104,30 @@ export const prepareRegistrationPayload = (
|
||||
payload = { ...payload, ...queryParams };
|
||||
return payload;
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper for backend validations selector. It processes the api output and generates a
|
||||
* key value dict for field errors.
|
||||
* @param registrationError
|
||||
* @param validations
|
||||
* @returns {{username: string}|{name: string}|*|{}|null}
|
||||
*/
|
||||
export const getBackendValidations = (registrationError, validations) => {
|
||||
if (validations) {
|
||||
return validations.validationDecisions;
|
||||
}
|
||||
|
||||
if (Object.keys(registrationError).length > 0) {
|
||||
const fields = Object.keys(registrationError).filter(
|
||||
(fieldName) => !(fieldName in ['errorCode', 'usernameSuggestions']),
|
||||
);
|
||||
|
||||
const validationDecisions = {};
|
||||
fields.forEach(field => {
|
||||
validationDecisions[field] = registrationError[field][0].userMessage || '';
|
||||
});
|
||||
return validationDecisions;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -64,12 +64,22 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Create an account for free',
|
||||
description: 'Label text for registration form submission button',
|
||||
},
|
||||
'registration.other.options.heading': {
|
||||
id: 'registration.other.options.heading',
|
||||
defaultMessage: 'Or register with:',
|
||||
description: 'A message that appears above third party auth providers i.e saml, google, facebook etc',
|
||||
},
|
||||
'create.account.cta.button': {
|
||||
id: 'create.account.cta.button',
|
||||
defaultMessage: '{label}',
|
||||
description: 'Label text for registration form submission button for those users who are landing through redirections',
|
||||
},
|
||||
// Institution login
|
||||
'register.institution.login.button': {
|
||||
id: 'register.institution.login.button',
|
||||
defaultMessage: 'Institution/campus credentials',
|
||||
description: 'shows institutions list',
|
||||
},
|
||||
'register.institution.login.page.title': {
|
||||
id: 'register.institution.login.page.title',
|
||||
defaultMessage: 'Register with institution/campus credentials',
|
||||
|
||||
@@ -9,7 +9,7 @@ const ResetPasswordSuccess = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Alert id="reset-password-success" variant="success" className="mb-5">
|
||||
<Alert id="reset-password-success" variant="success" className="mb-4">
|
||||
<Alert.Heading>
|
||||
{formatMessage(messages['reset.password.success.heading'])}
|
||||
</Alert.Heading>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n
|
||||
import {
|
||||
fireEvent, render, screen,
|
||||
} from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
@@ -103,9 +102,7 @@ describe('ResetPasswordPage', () => {
|
||||
fireEvent.change(confirmPasswordInput, { target: { value: password } });
|
||||
|
||||
const resetPasswordButton = screen.getByRole('button', { name: /Reset password/i, id: 'submit-new-password' });
|
||||
await act(async () => {
|
||||
fireEvent.click(resetPasswordButton);
|
||||
});
|
||||
fireEvent.click(resetPasswordButton);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(
|
||||
resetPassword({ new_password1: password, new_password2: password }, props.token, {}),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user