Compare commits
162 Commits
open-relea
...
sajjad/VAN
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07f5f56509 | ||
|
|
aeec576d8c | ||
|
|
90db7ba1d8 | ||
|
|
d8b5653224 | ||
|
|
4cc4ff6c4b | ||
|
|
48a3c57e5f | ||
|
|
efdefc300e | ||
|
|
9730a4f55d | ||
|
|
fc62241332 | ||
|
|
0846001b6d | ||
|
|
90658722e1 | ||
|
|
240752c6cd | ||
|
|
429d4547e4 | ||
|
|
e278b5f74a | ||
|
|
a723058bc1 | ||
|
|
59fa7d5de3 | ||
|
|
60578189bd | ||
|
|
82cd11e01e | ||
|
|
4a10540d4a | ||
|
|
aeda262fb0 | ||
|
|
dff3903617 | ||
|
|
1399caf003 | ||
|
|
2dfb6bc528 | ||
|
|
a392395876 | ||
|
|
5542311c95 | ||
|
|
21e6bb6eec | ||
|
|
bfa7874108 | ||
|
|
423958c899 | ||
|
|
cb380a2031 | ||
|
|
f4e89efdb4 | ||
|
|
f5cb7a1dbd | ||
|
|
72e601948c | ||
|
|
29e30981ae | ||
|
|
06a61e6a22 | ||
|
|
1c83020b43 | ||
|
|
fa4a0ac2d5 | ||
|
|
2addf57cbd | ||
|
|
d521fd20ec | ||
|
|
38d44ac586 | ||
|
|
4768306f53 | ||
|
|
6c6b527dfc | ||
|
|
e14c9bd1b7 | ||
|
|
a2bdc4031b | ||
|
|
8f38eb9e3a | ||
|
|
c22aa58904 | ||
|
|
067bddf892 | ||
|
|
7e4bccbc29 | ||
|
|
f39bb35dc8 | ||
|
|
0d760c04b7 | ||
|
|
6f113542f5 | ||
|
|
1e4c342703 | ||
|
|
3ce0585d7e | ||
|
|
5bf6dd6361 | ||
|
|
929abdff69 | ||
|
|
f295d69e76 | ||
|
|
32a4c55e4a | ||
|
|
615ba91bdb | ||
|
|
dfb2f89a36 | ||
|
|
c9783234cc | ||
|
|
0513e6c2de | ||
|
|
3cdc0234ef | ||
|
|
e06d12be07 | ||
|
|
56394881fc | ||
|
|
61056240c4 | ||
|
|
a59e7c548c | ||
|
|
f63d7674e2 | ||
|
|
fa3a70e9a9 | ||
|
|
b817a8d122 | ||
|
|
2a6668cef3 | ||
|
|
a802821ae9 | ||
|
|
9e13141f6b | ||
|
|
4b64ce2534 | ||
|
|
c550069e11 | ||
|
|
1e10e9c89c | ||
|
|
cd6c1c0e42 | ||
|
|
5edcee9eb9 | ||
|
|
d41c06b1fd | ||
|
|
2a2c5abc81 | ||
|
|
ccdd648603 | ||
|
|
5c1ea04970 | ||
|
|
5ebd22f088 | ||
|
|
5aec091156 | ||
|
|
68993cc21f | ||
|
|
404fc2b36b | ||
|
|
7bb06d0bfa | ||
|
|
abbb64b9c7 | ||
|
|
99ca582c1a | ||
|
|
3e4cfc1573 | ||
|
|
5437e1b7e9 | ||
|
|
4d33cd7d69 | ||
|
|
cb82e94b53 | ||
|
|
dbe14ccedf | ||
|
|
841edf2d24 | ||
|
|
0c375cc50c | ||
|
|
50072887d0 | ||
|
|
976814d846 | ||
|
|
c22024cf66 | ||
|
|
829a219b9f | ||
|
|
13d572ab28 | ||
|
|
c0c2ffa122 | ||
|
|
84c563fda3 | ||
|
|
97720d6a46 | ||
|
|
386982d9c4 | ||
|
|
cb38a2a148 | ||
|
|
07f19209bf | ||
|
|
69c0ca13fc | ||
|
|
e49c50f55c | ||
|
|
f0105f0094 | ||
|
|
3c89afea4a | ||
|
|
848f574f3f | ||
|
|
ec9e34cea4 | ||
|
|
f9069df4e6 | ||
|
|
9035f3eb7e | ||
|
|
e197e788d1 | ||
|
|
49d33522a8 | ||
|
|
06f0ec3c0b | ||
|
|
54319c6949 | ||
|
|
86f875ec3e | ||
|
|
754a6ddb12 | ||
|
|
62e8f75b96 | ||
|
|
c0deb663a6 | ||
|
|
ce28add152 | ||
|
|
5f89315947 | ||
|
|
56dd194a1a | ||
|
|
adcdcc4c8b | ||
|
|
7fd45f089d | ||
|
|
65d82b2080 | ||
|
|
f771935e20 | ||
|
|
70c255fc4f | ||
|
|
bd1396fc54 | ||
|
|
cba5395d5c | ||
|
|
b42c09d919 | ||
|
|
b7af2356fa | ||
|
|
22d477e55f | ||
|
|
dfa69c27bb | ||
|
|
6b78158db2 | ||
|
|
c92cac0eed | ||
|
|
b2dce920fa | ||
|
|
44cec762fb | ||
|
|
a98188ead8 | ||
|
|
294b1a469f | ||
|
|
ebed588c1c | ||
|
|
46f8217e1a | ||
|
|
9ab23cf485 | ||
|
|
7790660fe8 | ||
|
|
1dd2726beb | ||
|
|
ba9ce89d1b | ||
|
|
c9082ac709 | ||
|
|
27c8fa8986 | ||
|
|
a04289d71b | ||
|
|
321859e0f5 | ||
|
|
4b4bf413c1 | ||
|
|
06c4f75b4a | ||
|
|
12dd97af61 | ||
|
|
8e77197459 | ||
|
|
3cc64cada6 | ||
|
|
3ac5874df1 | ||
|
|
107dd6f360 | ||
|
|
347e0cd336 | ||
|
|
2ba6058ec7 | ||
|
|
683aa258b8 | ||
|
|
d7ad7e314d |
11
.env
11
.env
@@ -16,21 +16,26 @@ SITE_NAME=null
|
||||
INFO_EMAIL=''
|
||||
# ***** Cookies *****
|
||||
REGISTER_CONVERSION_COOKIE_NAME=null
|
||||
USER_SURVEY_COOKIE_NAME=null
|
||||
# ***** Links *****
|
||||
LOGIN_ISSUE_SUPPORT_LINK=''
|
||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
|
||||
POST_REGISTRATION_REDIRECT_URL=''
|
||||
SEARCH_CATALOG_URL=''
|
||||
# ***** Features flags *****
|
||||
DISABLE_ENTERPRISE_LOGIN=''
|
||||
ENABLE_COOKIE_POLICY_BANNER=''
|
||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
|
||||
ENABLE_PERSONALIZED_RECOMMENDATIONS=''
|
||||
ENABLE_POPULAR_AND_TRENDING_RECOMMENDATIONS=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS=''
|
||||
# ***** Zendesk related keys *****
|
||||
ZENDESK_KEY=''
|
||||
ZENDESK_LOGO_URL=''
|
||||
# ***** Base Container Images *****
|
||||
BANNER_IMAGE_LARGE=''
|
||||
BANNER_IMAGE_MEDIUM=''
|
||||
BANNER_IMAGE_SMALL=''
|
||||
BANNER_IMAGE_EXTRA_SMALL=''
|
||||
# ***** Miscellaneous *****
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
|
||||
@@ -23,13 +23,17 @@ INFO_EMAIL='info@example.com'
|
||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
||||
SESSION_COOKIE_DOMAIN='localhost'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
# ***** Links *****
|
||||
LOGIN_ISSUE_SUPPORT_LINK='http://localhost:18000/login-issue-support-url'
|
||||
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
|
||||
TOS_LINK='http://localhost:18000/tos'
|
||||
PRIVACY_POLICY='http://localhost:18000/privacy'
|
||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK='http://localhost:1999/welcome'
|
||||
# ***** Base Container Images *****
|
||||
BANNER_IMAGE_LARGE=''
|
||||
BANNER_IMAGE_MEDIUM=''
|
||||
BANNER_IMAGE_SMALL=''
|
||||
BANNER_IMAGE_EXTRA_SMALL=''
|
||||
# ***** Miscellaneous *****
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copy these to the .env.private to enable edX specific functionality on local system
|
||||
ENABLE_COOKIE_POLICY_BANNER='true'
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
|
||||
MARKETING_EMAILS_OPT_IN='true'
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS='true'
|
||||
|
||||
@@ -16,7 +16,6 @@ ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME='Your Platform Name Here'
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
|
||||
APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
|
||||
@@ -48,7 +48,5 @@ module.exports = createConfig('eslint', {
|
||||
},
|
||||
],
|
||||
'function-paren-newline': 'off',
|
||||
'no-import-assign': 'off',
|
||||
'react/no-unstable-nested-components': 'off',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,14 +3,18 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [ labeled ]
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
autoupdate:
|
||||
name: autoupdate
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: docker://chinthakagodawita/autoupdate-action:v1
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.CC_GITHUB_TOKEN }}"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
DRY_RUN: "false"
|
||||
PR_FILTER: "labelled"
|
||||
PR_LABELS: "autoupdate"
|
||||
31
Makefile
31
Makefile
@@ -1,6 +1,7 @@
|
||||
export TRANSIFEX_RESOURCE = frontend-app-authn
|
||||
transifex_langs = "ar,fr,es_419,zh_CN,it_IT,pt_PT,de_DE,uk,ru,hi"
|
||||
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA,it_IT,pt_PT,de_DE"
|
||||
|
||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||
i18n = ./src/i18n
|
||||
transifex_input = $(i18n)/transifex_input.json
|
||||
@@ -42,11 +43,37 @@ push_translations:
|
||||
# Pushing comments to Transifex...
|
||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||
|
||||
ifeq ($(OPENEDX_ATLAS_PULL),)
|
||||
# Pulls translations from Transifex.
|
||||
pull_translations:
|
||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
||||
else
|
||||
# Experimental: OEP-58 Pulls translations using atlas
|
||||
pull_translations:
|
||||
rm -rf src/i18n/messages
|
||||
mkdir src/i18n/messages
|
||||
cd src/i18n/messages \
|
||||
&& atlas pull --filter=$(transifex_langs) \
|
||||
translations/paragon/src/i18n/messages:paragon \
|
||||
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
|
||||
|
||||
# This target is used by CI.
|
||||
$(intl_imports) paragon frontend-app-authn
|
||||
endif
|
||||
|
||||
# This target is used by Travis.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
# Checking for package-lock.json changes...
|
||||
git diff --exit-code package-lock.json
|
||||
|
||||
.PHONY: validate
|
||||
validate:
|
||||
make validate-no-uncommitted-package-lock-changes
|
||||
npm run i18n_extract
|
||||
npm run lint -- --max-warnings 0
|
||||
npm run test
|
||||
npm run build
|
||||
|
||||
.PHONY: validate.ci
|
||||
validate.ci:
|
||||
npm ci
|
||||
make validate
|
||||
|
||||
@@ -112,9 +112,6 @@ The authentication micro-frontend also requires the following additional variabl
|
||||
- Name of MFE, this will be used by the API to get runtime configurations for the specific micro frontend. For a frontend repo `frontend-app-appName`, use `appName` as APP_ID.
|
||||
- ``authn`` | ``''``
|
||||
|
||||
* - ``ENABLE_COOKIE_POLICY_BANNER``
|
||||
- Enables support for displaying the cookies acceptance banner.
|
||||
- ``true`` | ``''`` (empty strings are falsy)
|
||||
|
||||
edX-specific Environment Variables
|
||||
**********************************
|
||||
|
||||
14190
package-lock.json
generated
14190
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -33,59 +33,51 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
|
||||
"@edx/frontend-component-cookie-policy-banner": "2.2.2",
|
||||
"@edx/frontend-platform": "4.0.2",
|
||||
"@edx/paragon": "20.30.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@edx/frontend-platform": "^5.0.0",
|
||||
"@edx/paragon": "20.46.2",
|
||||
"@fortawesome/fontawesome-svg-core": "6.4.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.4.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.4.2",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@optimizely/react-sdk": "^2.9.1",
|
||||
"@redux-devtools/extension": "3.2.5",
|
||||
"algoliasearch": "^4.14.3",
|
||||
"classnames": "2.3.2",
|
||||
"core-js": "3.30.0",
|
||||
"extract-react-intl-messages": "4.1.1",
|
||||
"core-js": "3.32.0",
|
||||
"fastest-levenshtein": "1.0.16",
|
||||
"form-urlencoded": "6.1.0",
|
||||
"lodash.camelcase": "4.3.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "5.1.1",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"query-string": "7.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-loading-skeleton": "3.2.0",
|
||||
"react-onclickoutside": "6.13.0",
|
||||
"react-loading-skeleton": "3.3.1",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router": "5.3.4",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-router": "6.15.0",
|
||||
"react-router-dom": "6.15.0",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"redux": "4.2.0",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-mock-store": "1.5.4",
|
||||
"redux-saga": "1.2.3",
|
||||
"redux-thunk": "2.4.2",
|
||||
"regenerator-runtime": "0.13.11",
|
||||
"reselect": "4.1.7",
|
||||
"sanitize-html": "2.10.0",
|
||||
"semver-regex": "3.1.4",
|
||||
"regenerator-runtime": "0.14.0",
|
||||
"reselect": "4.1.8",
|
||||
"universal-cookie": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@edx/frontend-build": "12.8.6",
|
||||
"@edx/frontend-build": "12.9.8",
|
||||
"@edx/reactifex": "1.1.0",
|
||||
"babel-plugin-formatjs": "10.4.0",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
||||
"babel-plugin-formatjs": "10.5.3",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.7",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-import": "2.28.0",
|
||||
"glob": "7.2.3",
|
||||
"history": "5.3.0",
|
||||
"husky": "7.0.4",
|
||||
"jest": "29.5.0",
|
||||
"react-test-renderer": "16.14.0"
|
||||
"jest": "29.6.2",
|
||||
"react-test-renderer": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<title>Authn | <%= process.env.SITE_NAME %></title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.6/iframeResizer.contentWindow.min.js"
|
||||
integrity="sha512-R7Piufj0/o6jG9ZKrAvS2dblFr2kkuG4XVQwStX+/4P+KwOLUXn2DXy0l1AJDxxqGhkM/FJllZHG2PKOAheYzg=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer">
|
||||
</script>
|
||||
<% if (process.env.OPTIMIZELY_URL) { %>
|
||||
<script
|
||||
src="<%= process.env.OPTIMIZELY_URL %>"
|
||||
|
||||
@@ -3,10 +3,10 @@ import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Logistration, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
|
||||
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
|
||||
} from './common-components';
|
||||
import configureStore from './data/configureStore';
|
||||
import {
|
||||
@@ -15,14 +15,18 @@ import {
|
||||
PAGE_NOT_FOUND,
|
||||
PASSWORD_RESET_CONFIRM,
|
||||
RECOMMENDATIONS,
|
||||
REGISTER_EMBEDDED_PAGE,
|
||||
REGISTER_PAGE,
|
||||
RESET_PAGE,
|
||||
} from './data/constants';
|
||||
import { updatePathWithQueryParams } from './data/utils';
|
||||
import { ForgotPasswordPage } from './forgot-password';
|
||||
import Logistration from './logistration/Logistration';
|
||||
import { ProgressiveProfiling } from './progressive-profiling';
|
||||
import { RecommendationsPage } from './recommendations';
|
||||
import { EmbeddableRegistrationPage } from './register';
|
||||
import { ResetPasswordPage } from './reset-password';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
registerIcons();
|
||||
@@ -33,21 +37,26 @@ const MainApp = () => (
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
</Helmet>
|
||||
{getConfig().ZENDESK_KEY && <Zendesk />}
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Redirect to={updatePathWithQueryParams(REGISTER_PAGE)} />
|
||||
</Route>
|
||||
<UnAuthOnlyRoute exact path={LOGIN_PAGE} render={() => <Logistration selectedPage={LOGIN_PAGE} />} />
|
||||
<UnAuthOnlyRoute exact path={REGISTER_PAGE} component={Logistration} />
|
||||
<UnAuthOnlyRoute exact path={RESET_PAGE} component={ForgotPasswordPage} />
|
||||
<Route exact path={PASSWORD_RESET_CONFIRM} component={ResetPasswordPage} />
|
||||
<Route exact path={AUTHN_PROGRESSIVE_PROFILING} component={ProgressiveProfiling} />
|
||||
<Route exact path={RECOMMENDATIONS} component={RecommendationsPage} />
|
||||
<Route path={PAGE_NOT_FOUND} component={NotFoundPage} />
|
||||
<Route path="*">
|
||||
<Redirect to={PAGE_NOT_FOUND} />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
|
||||
<Route
|
||||
path={REGISTER_EMBEDDED_PAGE}
|
||||
element={<EmbeddedRegistrationRoute><EmbeddableRegistrationPage /></EmbeddedRegistrationRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={LOGIN_PAGE}
|
||||
element={
|
||||
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
|
||||
}
|
||||
/>
|
||||
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
|
||||
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
|
||||
<Route path={PASSWORD_RESET_CONFIRM} element={<ResetPasswordPage />} />
|
||||
<Route path={AUTHN_PROGRESSIVE_PROFILING} element={<ProgressiveProfiling />} />
|
||||
<Route path={RECOMMENDATIONS} element={<RecommendationsPage />} />
|
||||
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
|
||||
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
|
||||
</Routes>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { getLocale } from '@edx/frontend-platform/i18n';
|
||||
import { breakpoints } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
|
||||
import AuthLargeLayout from './AuthLargeLayout';
|
||||
import AuthMediumLayout from './AuthMediumLayout';
|
||||
import AuthSmallLayout from './AuthSmallLayout';
|
||||
import LargeLayout from './LargeLayout';
|
||||
import MediumLayout from './MediumLayout';
|
||||
import SmallLayout from './SmallLayout';
|
||||
|
||||
const BaseComponent = ({ children, showWelcomeBanner }) => {
|
||||
const authenticatedUser = showWelcomeBanner ? getAuthenticatedUser() : null;
|
||||
const username = authenticatedUser ? authenticatedUser.username : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{getConfig().ENABLE_COOKIE_POLICY_BANNER ? <CookiePolicyBanner languageCode={getLocale()} /> : null}
|
||||
<div className="col-md-12 extra-large-screen-top-stripe" />
|
||||
<div className="layout">
|
||||
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthSmallLayout username={username} /> : <SmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthMediumLayout username={username} /> : <MediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth} maxWidth={breakpoints.extraExtraLarge.maxWidth}>
|
||||
{authenticatedUser ? <AuthLargeLayout username={username} /> : <LargeLayout />}
|
||||
</MediaQuery>
|
||||
|
||||
<div className={classNames('content', { 'align-items-center mt-0': authenticatedUser })}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
BaseComponent.defaultProps = {
|
||||
showWelcomeBanner: false,
|
||||
};
|
||||
|
||||
BaseComponent.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
showWelcomeBanner: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default BaseComponent;
|
||||
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export { default as BaseComponent } from './BaseComponent';
|
||||
@@ -3,16 +3,14 @@ import React from 'react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import LargeLayout from '../LargeLayout';
|
||||
import MediumLayout from '../MediumLayout';
|
||||
import SmallLayout from '../SmallLayout';
|
||||
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index';
|
||||
|
||||
describe('ScreenLayout', () => {
|
||||
it('should display the form, pass as a child in SmallScreenLayout', () => {
|
||||
describe('Default Layout tests', () => {
|
||||
it('should display the form passed as a child in SmallScreenLayout', () => {
|
||||
const smallScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<SmallLayout />
|
||||
<DefaultSmallLayout />
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
@@ -22,11 +20,11 @@ describe('ScreenLayout', () => {
|
||||
expect(smallScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should display the form, pass as a child in MediumScreenLayout', () => {
|
||||
it('should display the form passed as a child in MediumScreenLayout', () => {
|
||||
const mediumScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<MediumLayout />
|
||||
<DefaultMediumLayout />
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
@@ -36,11 +34,11 @@ describe('ScreenLayout', () => {
|
||||
expect(mediumScreen.find('form').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should display the form, pass as a child in LargeScreenLayout', () => {
|
||||
it('should display the form passed as a child in LargeScreenLayout', () => {
|
||||
const largeScreen = mount(
|
||||
<IntlProvider locale="en">
|
||||
<div>
|
||||
<LargeLayout />
|
||||
<DefaultLargeLayout />
|
||||
<form>
|
||||
<input type="text" />
|
||||
</form>
|
||||
3
src/base-container/components/default-layout/index.jsx
Normal file
3
src/base-container/components/default-layout/index.jsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as DefaultLargeLayout } from './LargeLayout';
|
||||
export { default as DefaultMediumLayout } from './MediumLayout';
|
||||
export { default as DefaultSmallLayout } from './SmallLayout';
|
||||
16
src/base-container/components/default-layout/messages.js
Normal file
16
src/base-container/components/default-layout/messages.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'start.learning': {
|
||||
id: 'start.learning',
|
||||
defaultMessage: 'Start learning',
|
||||
description: 'Header text for logistration MFE pages',
|
||||
},
|
||||
'with.site.name': {
|
||||
id: 'with.site.name',
|
||||
defaultMessage: 'with {siteName}',
|
||||
description: 'Header text with site name for logistration MFE pages',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const ExtraSmallLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<span
|
||||
className="w-100 bg-primary-500 banner__image extra-small-layout"
|
||||
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_EXTRA_SMALL})` }}
|
||||
>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="ml-4.5 mr-1 pb-3.5 pt-3.5">
|
||||
<h1 className="banner__heading">
|
||||
<span className="text-light-500">
|
||||
{formatMessage(messages['your.career.turning.point'])}{' '}
|
||||
</span>
|
||||
<span className="text-warning-300">
|
||||
{formatMessage(messages['is.here'])}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtraSmallLayout;
|
||||
35
src/base-container/components/image-layout/LargeLayout.jsx
Normal file
35
src/base-container/components/image-layout/LargeLayout.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
|
||||
import './index.scss';
|
||||
import messages from './messages';
|
||||
|
||||
const LargeLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-50 bg-primary-500 banner__image large-layout"
|
||||
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_LARGE})` }}
|
||||
>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="min-vh-100 p-5 d-flex align-items-end">
|
||||
<h1 className="display-2 mw-sm mb-3 d-flex flex-column flex-shrink-0 justify-content-center">
|
||||
<span className="text-light-500">
|
||||
{formatMessage(messages['your.career.turning.point'])}
|
||||
</span>
|
||||
<span className="text-warning-300">
|
||||
{formatMessage(messages['is.here'])}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LargeLayout;
|
||||
35
src/base-container/components/image-layout/MediumLayout.jsx
Normal file
35
src/base-container/components/image-layout/MediumLayout.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
|
||||
import './index.scss';
|
||||
import messages from './messages';
|
||||
|
||||
const MediumLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-100 mb-3 bg-primary-500 banner__image medium-layout"
|
||||
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_MEDIUM})` }}
|
||||
>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="ml-5 pb-4 pt-4">
|
||||
<h1 className="display-2 banner__heading">
|
||||
<span className="text-light-500">
|
||||
{formatMessage(messages['your.career.turning.point'])}{' '}
|
||||
</span>
|
||||
<span className="text-warning-300 d-inline-block">
|
||||
{formatMessage(messages['is.here'])}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MediumLayout;
|
||||
34
src/base-container/components/image-layout/SmallLayout.jsx
Normal file
34
src/base-container/components/image-layout/SmallLayout.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, Image } from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const SmallLayout = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<span
|
||||
className="w-100 bg-primary-500 banner__image small-layout"
|
||||
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_SMALL})` }}
|
||||
>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="ml-5 mr-1 pb-3.5 pt-3.5">
|
||||
<h1 className="display-2">
|
||||
<span className="text-light-500">
|
||||
{formatMessage(messages['your.career.turning.point'])}{' '}
|
||||
</span>
|
||||
<span className="text-warning-300">
|
||||
{formatMessage(messages['is.here'])}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallLayout;
|
||||
4
src/base-container/components/image-layout/index.jsx
Normal file
4
src/base-container/components/image-layout/index.jsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as ImageLargeLayout } from './LargeLayout';
|
||||
export { default as ImageMediumLayout } from './MediumLayout';
|
||||
export { default as ImageSmallLayout } from './SmallLayout';
|
||||
export { default as ImageExtraSmallLayout } from './ExtraSmallLayout';
|
||||
37
src/base-container/components/image-layout/index.scss
Normal file
37
src/base-container/components/image-layout/index.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
.company-logo {
|
||||
width: 71px;
|
||||
margin-top: 2rem;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.company-logo {
|
||||
width: 44.67px;
|
||||
margin-top: 1.25rem;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.banner__image {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border:none;
|
||||
}
|
||||
|
||||
@media (min-width: 464px) and (max-width: 575.98px) {
|
||||
.banner__heading {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
line-height: 60px;
|
||||
letter-spacing: -1.2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 800px) {
|
||||
.banner__heading {
|
||||
font-size: 60px !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 60px !important;
|
||||
letter-spacing: -2px !important;
|
||||
}
|
||||
}
|
||||
16
src/base-container/components/image-layout/messages.js
Normal file
16
src/base-container/components/image-layout/messages.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'your.career.turning.point': {
|
||||
id: 'your.career.turning.point',
|
||||
defaultMessage: 'Your career turning point',
|
||||
description: 'Part of the heading "Your career turning point is here." shown on Authn MFE',
|
||||
},
|
||||
'is.here': {
|
||||
id: 'is.here',
|
||||
defaultMessage: 'is here.',
|
||||
description: 'Part of the heading "Your career turning point is here." shown on Authn MFE',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthLargeLayout = ({ username }) => {
|
||||
const LargeLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
@@ -42,8 +42,8 @@ const AuthLargeLayout = ({ username }) => {
|
||||
);
|
||||
};
|
||||
|
||||
AuthLargeLayout.propTypes = {
|
||||
LargeLayout.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AuthLargeLayout;
|
||||
export default LargeLayout;
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthMediumLayout = ({ username }) => {
|
||||
const MediumLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
@@ -45,8 +45,8 @@ const AuthMediumLayout = ({ username }) => {
|
||||
);
|
||||
};
|
||||
|
||||
AuthMediumLayout.propTypes = {
|
||||
MediumLayout.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AuthMediumLayout;
|
||||
export default MediumLayout;
|
||||
@@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthSmallLayout = ({ username }) => {
|
||||
const SmallLayout = ({ username }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
@@ -34,8 +34,8 @@ const AuthSmallLayout = ({ username }) => {
|
||||
);
|
||||
};
|
||||
|
||||
AuthSmallLayout.propTypes = {
|
||||
SmallLayout.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AuthSmallLayout;
|
||||
export default SmallLayout;
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as AuthLargeLayout } from './LargeLayout';
|
||||
export { default as AuthMediumLayout } from './MediumLayout';
|
||||
export { default as AuthSmallLayout } from './SmallLayout';
|
||||
@@ -1,17 +1,11 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'start.learning': {
|
||||
id: 'start.learning',
|
||||
defaultMessage: 'Start learning',
|
||||
description: 'Header text for logistration MFE pages',
|
||||
'welcome.to.platform': {
|
||||
id: 'welcome.to.platform',
|
||||
defaultMessage: 'Welcome to {siteName}, {username}!',
|
||||
description: 'Welcome message that appears on progressive profile page',
|
||||
},
|
||||
'with.site.name': {
|
||||
id: 'with.site.name',
|
||||
defaultMessage: 'with {siteName}',
|
||||
description: 'Header text with site name for logistration MFE pages',
|
||||
},
|
||||
// authenticated user base component text
|
||||
'complete.your.profile.1': {
|
||||
id: 'complete.your.profile.1',
|
||||
defaultMessage: 'Complete',
|
||||
@@ -22,11 +16,6 @@ const messages = defineMessages({
|
||||
defaultMessage: 'your profile',
|
||||
description: 'part of text "complete your profile"',
|
||||
},
|
||||
'welcome.to.platform': {
|
||||
id: 'welcome.to.platform',
|
||||
defaultMessage: 'Welcome to {siteName}, {username}!',
|
||||
description: 'Welcome message that appears on progressive profile page',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
4
src/base-container/data/constants.js
Normal file
4
src/base-container/data/constants.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const IMAGE_LAYOUT = 'image-layout';
|
||||
const DEFAULT_LAYOUT = 'default-layout';
|
||||
|
||||
export { DEFAULT_LAYOUT, IMAGE_LAYOUT };
|
||||
89
src/base-container/index.jsx
Normal file
89
src/base-container/index.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { breakpoints } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
|
||||
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './components/default-layout';
|
||||
import {
|
||||
ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout,
|
||||
} from './components/image-layout';
|
||||
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
|
||||
import { DEFAULT_LAYOUT, IMAGE_LAYOUT } from './data/constants';
|
||||
|
||||
const BaseContainer = ({ children, showWelcomeBanner }) => {
|
||||
const authenticatedUser = showWelcomeBanner ? getAuthenticatedUser() : null;
|
||||
const username = authenticatedUser ? authenticatedUser.username : null;
|
||||
|
||||
const [baseContainerVersion, setBaseContainerVersion] = useState(DEFAULT_LAYOUT);
|
||||
|
||||
useEffect(() => {
|
||||
const initRebrandExperiment = () => {
|
||||
if (window.experiments?.rebrandExperiment) {
|
||||
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
|
||||
} else {
|
||||
window.experiments = window.experiments || {};
|
||||
window.experiments.rebrandExperiment = {};
|
||||
window.experiments.rebrandExperiment.handleLoaded = () => {
|
||||
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
|
||||
};
|
||||
}
|
||||
};
|
||||
initRebrandExperiment();
|
||||
}, []);
|
||||
|
||||
if (baseContainerVersion === IMAGE_LAYOUT) {
|
||||
return (
|
||||
<div className="layout">
|
||||
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthSmallLayout username={username} /> : <ImageExtraSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthSmallLayout username={username} /> : <ImageSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthMediumLayout username={username} /> : <ImageMediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||
{authenticatedUser ? <AuthLargeLayout username={username} /> : <ImageLargeLayout />}
|
||||
</MediaQuery>
|
||||
<div className={classNames('content', { 'align-items-center mt-0': authenticatedUser })}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col-md-12 extra-large-screen-top-stripe" />
|
||||
<div className="layout">
|
||||
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthSmallLayout username={username} /> : <DefaultSmallLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
|
||||
{authenticatedUser ? <AuthMediumLayout username={username} /> : <DefaultMediumLayout />}
|
||||
</MediaQuery>
|
||||
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
|
||||
{authenticatedUser ? <AuthLargeLayout username={username} /> : <DefaultLargeLayout />}
|
||||
</MediaQuery>
|
||||
<div className={classNames('content', { 'align-items-center mt-0': authenticatedUser })}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
BaseContainer.defaultProps = {
|
||||
showWelcomeBanner: false,
|
||||
};
|
||||
|
||||
BaseContainer.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
showWelcomeBanner: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default BaseContainer;
|
||||
43
src/base-container/tests/BaseContainer.test.jsx
Normal file
43
src/base-container/tests/BaseContainer.test.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
|
||||
import BaseContainer from '../index';
|
||||
|
||||
const LargeScreen = {
|
||||
wrappingComponent: ResponsiveContext.Provider,
|
||||
wrappingComponentProps: { value: { width: 1200 } },
|
||||
};
|
||||
|
||||
describe('Base component tests', () => {
|
||||
it('should should default layout', () => {
|
||||
const baseContainer = mount(
|
||||
<IntlProvider locale="en">
|
||||
<BaseContainer />
|
||||
</IntlProvider>,
|
||||
LargeScreen,
|
||||
);
|
||||
|
||||
expect(baseContainer.find('.banner__image').exists()).toBeFalsy();
|
||||
expect(baseContainer.find('.large-screen-svg-primary').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('[experiment] should show image layout for treatment group', () => {
|
||||
window.experiments = {
|
||||
rebrandExperiment: {
|
||||
variation: 'image-layout',
|
||||
},
|
||||
};
|
||||
|
||||
const baseContainer = mount(
|
||||
<IntlProvider locale="en">
|
||||
<BaseContainer />
|
||||
</IntlProvider>,
|
||||
LargeScreen,
|
||||
);
|
||||
|
||||
expect(baseContainer.find('.banner__image').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
28
src/common-components/EmbeddedRegistrationRoute.jsx
Normal file
28
src/common-components/EmbeddedRegistrationRoute.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import { PAGE_NOT_FOUND } from '../data/constants';
|
||||
import { isHostAvailableInQueryParams } from '../data/utils';
|
||||
|
||||
/**
|
||||
* This wrapper redirects the requester to embedded register page only if host
|
||||
* query param is present.
|
||||
*/
|
||||
const EmbeddedRegistrationRoute = ({ children }) => {
|
||||
const registrationEmbedded = isHostAvailableInQueryParams();
|
||||
|
||||
// Show registration page for embedded experience even if the user is authenticated
|
||||
if (registrationEmbedded) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <Navigate to={PAGE_NOT_FOUND} replace />;
|
||||
};
|
||||
|
||||
EmbeddedRegistrationRoute.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default EmbeddedRegistrationRoute;
|
||||
@@ -4,13 +4,14 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Button, Form,
|
||||
Icon,
|
||||
} from '@edx/paragon';
|
||||
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Login } from '@edx/paragon/icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
|
||||
/**
|
||||
* This component renders the Single sign-on (SSO) button only for the tpa provider passed
|
||||
@@ -47,16 +48,18 @@ const EnterpriseSSO = (props) => {
|
||||
>
|
||||
{tpaProvider.iconImage ? (
|
||||
<div aria-hidden="true">
|
||||
<img className="icon-image" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
|
||||
<img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
|
||||
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className="font-container" aria-hidden="true">
|
||||
<FontAwesomeIcon
|
||||
icon={SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? ['fab', tpaProvider.iconClass] : faSignInAlt}
|
||||
/>
|
||||
<div className="btn-tpa__font-container" aria-hidden="true">
|
||||
{SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? (
|
||||
<FontAwesomeIcon icon={['fab', tpaProvider.iconClass]} />)
|
||||
: (
|
||||
<Icon className="h-75" src={Login} />
|
||||
)}
|
||||
</div>
|
||||
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
|
||||
</>
|
||||
|
||||
@@ -27,7 +27,7 @@ const FormGroup = (props) => {
|
||||
readOnly={props.readOnly}
|
||||
type={props.type}
|
||||
aria-invalid={props.errorMessage !== ''}
|
||||
className="form-field"
|
||||
className="form-group__form-field"
|
||||
autoComplete={props.autoComplete}
|
||||
spellCheck={props.spellCheck}
|
||||
name={props.name}
|
||||
|
||||
@@ -42,7 +42,7 @@ const InstitutionLogistration = props => {
|
||||
<>
|
||||
<div className="d-flex justify-content-left mb-4 mt-2">
|
||||
<div className="flex-column">
|
||||
<h4 className="mb-2 font-weight-bold institute-heading">
|
||||
<h4 className="mb-2 font-weight-bold institutions__heading">
|
||||
{headingTitle}
|
||||
</h4>
|
||||
<p className="mb-2">
|
||||
@@ -57,7 +57,7 @@ const InstitutionLogistration = props => {
|
||||
<tr key={provider} className="pgn__data-table-row">
|
||||
<td>
|
||||
<Hyperlink
|
||||
className="btn nav-item p-0 mb-1 secondary-provider-link"
|
||||
className="btn nav-item p-0 mb-1 institutions--provider-link"
|
||||
destination={lmsBaseUrl + provider.loginUrl}
|
||||
>
|
||||
{provider.name}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
@@ -9,33 +10,80 @@ import {
|
||||
} from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
|
||||
import { clearRegistertionBackendError, fetchRealtimeValidations } from '../register/data/actions';
|
||||
import { PASSWORD_FIELD_LABEL } from '../register/data/constants';
|
||||
import { validatePasswordField } from '../register/data/utils';
|
||||
|
||||
const PasswordField = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const validationApiRateLimited = useSelector(state => state.register.validationApiRateLimited);
|
||||
const [isPasswordHidden, setHiddenTrue, setHiddenFalse] = useToggle(true);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
const handleBlur = (e) => {
|
||||
if (e.target?.name === PASSWORD_FIELD_LABEL && e.relatedTarget?.name === 'passwordIcon') {
|
||||
return; // resolving a bug where validations get run on password icon focus
|
||||
}
|
||||
|
||||
if (props.handleBlur) { props.handleBlur(e); }
|
||||
setShowTooltip(props.showRequirements && false);
|
||||
if (props.handleErrorChange) { // If rendering from register page
|
||||
const fieldError = validatePasswordField(props.value, formatMessage);
|
||||
if (fieldError) {
|
||||
props.handleErrorChange(PASSWORD_FIELD_LABEL, fieldError);
|
||||
} else if (!validationApiRateLimited) {
|
||||
dispatch(fetchRealtimeValidations({ [PASSWORD_FIELD_LABEL]: props.value }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = (e) => {
|
||||
if (e.target?.name === 'passwordIcon') {
|
||||
return; // resolving a bug where error gets cleared on password icon focus
|
||||
}
|
||||
|
||||
if (props.handleFocus) {
|
||||
props.handleFocus(e);
|
||||
}
|
||||
if (props.handleErrorChange) {
|
||||
props.handleErrorChange(PASSWORD_FIELD_LABEL, '');
|
||||
dispatch(clearRegistertionBackendError(PASSWORD_FIELD_LABEL));
|
||||
}
|
||||
setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
|
||||
};
|
||||
|
||||
const HideButton = (
|
||||
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={VisibilityOff} iconAs={Icon} onClick={setHiddenTrue} size="sm" variant="secondary" alt={formatMessage(messages['hide.password'])} />
|
||||
<IconButton
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
name="passwordIcon"
|
||||
src={VisibilityOff}
|
||||
iconAs={Icon}
|
||||
onClick={setHiddenTrue}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
alt={formatMessage(messages['hide.password'])}
|
||||
/>
|
||||
);
|
||||
|
||||
const ShowButton = (
|
||||
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={Visibility} iconAs={Icon} onClick={setHiddenFalse} size="sm" variant="secondary" alt={formatMessage(messages['show.password'])} />
|
||||
<IconButton
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
name="passwordIcon"
|
||||
src={Visibility}
|
||||
iconAs={Icon}
|
||||
onClick={setHiddenFalse}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
alt={formatMessage(messages['show.password'])}
|
||||
/>
|
||||
);
|
||||
|
||||
const placement = window.innerWidth < 768 ? 'top' : 'left';
|
||||
const tooltip = (
|
||||
<Tooltip id={`password-requirement-${placement}`}>
|
||||
@@ -59,7 +107,7 @@ const PasswordField = (props) => {
|
||||
<OverlayTrigger key="tooltip" placement={placement} overlay={tooltip} show={showTooltip}>
|
||||
<Form.Control
|
||||
as="input"
|
||||
className="form-field"
|
||||
className="form-group__form-field"
|
||||
type={isPasswordHidden ? 'password' : 'text'}
|
||||
name={props.name}
|
||||
value={props.value}
|
||||
@@ -89,6 +137,7 @@ PasswordField.defaultProps = {
|
||||
handleBlur: null,
|
||||
handleFocus: null,
|
||||
handleChange: () => {},
|
||||
handleErrorChange: null,
|
||||
showRequirements: true,
|
||||
autoComplete: null,
|
||||
};
|
||||
@@ -100,6 +149,7 @@ PasswordField.propTypes = {
|
||||
handleBlur: PropTypes.func,
|
||||
handleFocus: PropTypes.func,
|
||||
handleChange: PropTypes.func,
|
||||
handleErrorChange: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
showRequirements: PropTypes.bool,
|
||||
value: PropTypes.string.isRequired,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import { AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS } from '../data/constants';
|
||||
import {
|
||||
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS,
|
||||
} from '../data/constants';
|
||||
import { setCookie } from '../data/utils';
|
||||
|
||||
const RedirectLogistration = (props) => {
|
||||
@@ -35,15 +35,16 @@ const RedirectLogistration = (props) => {
|
||||
if (redirectToProgressiveProfilingPage) {
|
||||
// TODO: Do we still need this cookie?
|
||||
setCookie('van-504-returning-user', true);
|
||||
|
||||
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
||||
return (
|
||||
<Redirect to={{
|
||||
pathname: AUTHN_PROGRESSIVE_PROFILING,
|
||||
state: {
|
||||
<Navigate
|
||||
to={AUTHN_PROGRESSIVE_PROFILING}
|
||||
state={{
|
||||
registrationResult,
|
||||
optionalFields,
|
||||
},
|
||||
}}
|
||||
}}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -52,14 +53,14 @@ const RedirectLogistration = (props) => {
|
||||
if (redirectToRecommendationsPage) {
|
||||
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
||||
return (
|
||||
<Redirect to={{
|
||||
pathname: RECOMMENDATIONS,
|
||||
state: {
|
||||
<Navigate
|
||||
to={RECOMMENDATIONS}
|
||||
state={{
|
||||
registrationResult,
|
||||
educationLevel,
|
||||
userId,
|
||||
},
|
||||
}}
|
||||
}}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Icon } from '@edx/paragon';
|
||||
import { Login } from '@edx/paragon/icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
|
||||
const SocialAuthProviders = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -31,14 +32,16 @@ const SocialAuthProviders = (props) => {
|
||||
>
|
||||
{provider.iconImage ? (
|
||||
<div aria-hidden="true">
|
||||
<img className="icon-image" src={provider.iconImage} alt={`icon ${provider.name}`} />
|
||||
<img className="btn-tpa__image-icon" src={provider.iconImage} alt={`icon ${provider.name}`} />
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="font-container" aria-hidden="true">
|
||||
<FontAwesomeIcon
|
||||
icon={SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? ['fab', provider.iconClass] : faSignInAlt}
|
||||
/>
|
||||
<div className="btn-tpa__font-container" aria-hidden="true">
|
||||
{SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? (
|
||||
<FontAwesomeIcon icon={['fab', provider.iconClass]} />)
|
||||
: (
|
||||
<Icon className="h-75" src={Login} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span id="provider-name" className="notranslate mr-auto pl-2" aria-hidden="true">{provider.name}</span>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
|
||||
const ThirdPartyAuthAlert = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { DEFAULT_REDIRECT_URL } from '../data/constants';
|
||||
import {
|
||||
DEFAULT_REDIRECT_URL,
|
||||
} from '../data/constants';
|
||||
|
||||
/**
|
||||
* This wrapper redirects the requester to our default redirect url if they are
|
||||
* already authenticated.
|
||||
*/
|
||||
const UnAuthOnlyRoute = (props) => {
|
||||
const UnAuthOnlyRoute = ({ children }) => {
|
||||
const [authUser, setAuthUser] = useState({});
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
@@ -27,10 +29,14 @@ const UnAuthOnlyRoute = (props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Route {...props} />;
|
||||
return children;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
UnAuthOnlyRoute.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default UnAuthOnlyRoute;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import Zendesk from 'react-zendesk';
|
||||
|
||||
import messages from './messages';
|
||||
import { REGISTER_EMBEDDED_PAGE } from '../data/constants';
|
||||
|
||||
const ZendeskHelp = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -16,6 +17,9 @@ const ZendeskHelp = () => {
|
||||
},
|
||||
chat: {
|
||||
suppress: false,
|
||||
departments: {
|
||||
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
|
||||
},
|
||||
},
|
||||
contactForm: {
|
||||
ticketForms: [
|
||||
@@ -45,6 +49,10 @@ const ZendeskHelp = () => {
|
||||
},
|
||||
};
|
||||
|
||||
if (window.location.pathname === REGISTER_EMBEDDED_PAGE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
|
||||
);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
|
||||
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
|
||||
export const defaultState = {
|
||||
fieldDescriptions: {},
|
||||
optionalFields: {},
|
||||
optionalFields: {
|
||||
fields: {},
|
||||
extended_profile: [],
|
||||
},
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
@@ -13,6 +16,7 @@ export const defaultState = {
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: null,
|
||||
welcomePageRedirectUrl: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,7 +39,11 @@ const reducer = (state = defaultState, action = {}) => {
|
||||
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthApiStatus: FAILURE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...state.thirdPartyAuthContext,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG:
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
|
||||
import {
|
||||
getThirdPartyAuthContextBegin,
|
||||
getThirdPartyAuthContextFailure,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
import {
|
||||
getThirdPartyAuthContext,
|
||||
} from './service';
|
||||
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
|
||||
|
||||
export function* fetchThirdPartyAuthContext(action) {
|
||||
try {
|
||||
|
||||
@@ -18,8 +18,8 @@ export async function getThirdPartyAuthContext(urlParams) {
|
||||
throw (e);
|
||||
});
|
||||
return {
|
||||
fieldDescriptions: data.registrationFields || data.registration_fields,
|
||||
optionalFields: data.optionalFields || data.optional_fields,
|
||||
thirdPartyAuthContext: data.contextData || data.context_data,
|
||||
fieldDescriptions: data.registrationFields || {},
|
||||
optionalFields: data.optionalFields || {},
|
||||
thirdPartyAuthContext: data.contextData || {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as RedirectLogistration } from './RedirectLogistration';
|
||||
export { default as registerIcons } from './RegisterFaIcons';
|
||||
export { default as EmbeddedRegistrationRoute } from './EmbeddedRegistrationRoute';
|
||||
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
|
||||
export { default as NotFoundPage } from './NotFoundPage';
|
||||
export { default as SocialAuthProviders } from './SocialAuthProviders';
|
||||
@@ -11,5 +12,4 @@ export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
export { default as FormGroup } from './FormGroup';
|
||||
export { default as PasswordField } from './PasswordField';
|
||||
export { default as Logistration } from './Logistration';
|
||||
export { default as Zendesk } from './Zendesk';
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* eslint-disable import/no-import-module-exports */
|
||||
/* eslint-disable react/function-component-definition */
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { REGISTER_EMBEDDED_PAGE } from '../../data/constants';
|
||||
import EmbeddedRegistrationRoute from '../EmbeddedRegistrationRoute';
|
||||
|
||||
import {
|
||||
MemoryRouter, Route, BrowserRouter as Router, Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
const RRD = require('react-router-dom');
|
||||
// Just render plain div with its children
|
||||
// eslint-disable-next-line react/prop-types
|
||||
RRD.BrowserRouter = ({ children }) => <div>{ children }</div>;
|
||||
module.exports = RRD;
|
||||
|
||||
const TestApp = () => (
|
||||
<Router>
|
||||
<div>
|
||||
<Routes>
|
||||
<Route
|
||||
path={REGISTER_EMBEDDED_PAGE}
|
||||
element={<EmbeddedRegistrationRoute><span>Embedded Register Page</span></EmbeddedRegistrationRoute>}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
||||
describe('EmbeddedRegistrationRoute', () => {
|
||||
const routerWrapper = () => (
|
||||
<MemoryRouter initialEntries={[REGISTER_EMBEDDED_PAGE]}>
|
||||
<TestApp />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not render embedded register page if host query param is not available in the url', async () => {
|
||||
let embeddedRegistrationPage = null;
|
||||
|
||||
await act(async () => {
|
||||
embeddedRegistrationPage = await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(embeddedRegistrationPage.find('span').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should render embedded register page if host query param is available in the url (embedded)', async () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL.concat(REGISTER_EMBEDDED_PAGE),
|
||||
search: '?host=http://localhost/host-websit',
|
||||
};
|
||||
|
||||
let embeddedRegistrationPage = null;
|
||||
|
||||
await act(async () => {
|
||||
embeddedRegistrationPage = await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(embeddedRegistrationPage.find('span').exists()).toBeTruthy();
|
||||
expect(embeddedRegistrationPage.find('span').text()).toBe('Embedded Register Page');
|
||||
});
|
||||
});
|
||||
@@ -2,15 +2,21 @@
|
||||
/* eslint-disable react/function-component-definition */
|
||||
import React from 'react';
|
||||
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { UnAuthOnlyRoute } from '..';
|
||||
import { LOGIN_PAGE } from '../../data/constants';
|
||||
import { REGISTER_PAGE } from '../../data/constants';
|
||||
|
||||
import { MemoryRouter, BrowserRouter as Router, Switch } from 'react-router-dom';
|
||||
import {
|
||||
MemoryRouter, Route, BrowserRouter as Router, Routes,
|
||||
} from 'react-router-dom';
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(),
|
||||
fetchAuthenticatedUser: jest.fn(),
|
||||
}));
|
||||
|
||||
const RRD = require('react-router-dom');
|
||||
// Just render plain div with its children
|
||||
@@ -21,16 +27,16 @@ module.exports = RRD;
|
||||
const TestApp = () => (
|
||||
<Router>
|
||||
<div>
|
||||
<Switch>
|
||||
<UnAuthOnlyRoute path={LOGIN_PAGE} render={() => (<span>Login Page</span>)} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><span>Register Page</span></UnAuthOnlyRoute>} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
||||
describe('UnAuthOnlyRoute', () => {
|
||||
const routerWrapper = () => (
|
||||
<MemoryRouter initialEntries={[LOGIN_PAGE]}>
|
||||
<MemoryRouter initialEntries={[REGISTER_PAGE]}>
|
||||
<TestApp />
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -39,25 +45,30 @@ describe('UnAuthOnlyRoute', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should have called with forceRefresh true', () => {
|
||||
it('should have called with forceRefresh true', async () => {
|
||||
const user = {
|
||||
username: 'gonzo',
|
||||
other: 'data',
|
||||
};
|
||||
auth.getAuthenticatedUser = jest.fn(() => user);
|
||||
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
|
||||
|
||||
mount(routerWrapper());
|
||||
getAuthenticatedUser.mockReturnValue(user);
|
||||
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(user));
|
||||
|
||||
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
|
||||
await act(async () => {
|
||||
await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
|
||||
});
|
||||
|
||||
it('should have called with forceRefresh false', () => {
|
||||
auth.getAuthenticatedUser = jest.fn(() => null);
|
||||
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
|
||||
it('should have called with forceRefresh false', async () => {
|
||||
getAuthenticatedUser.mockReturnValue(null);
|
||||
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(null));
|
||||
|
||||
mount(routerWrapper());
|
||||
await act(async () => {
|
||||
await mount(routerWrapper());
|
||||
});
|
||||
|
||||
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
|
||||
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,25 +10,27 @@ exports[`SocialAuthProviders should match social auth provider with default icon
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="font-container"
|
||||
className="btn-tpa__font-container"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-right-to-bracket "
|
||||
data-icon="right-to-bracket"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<span
|
||||
className="pgn__icon h-75"
|
||||
>
|
||||
<path
|
||||
d="M352 96h64c17.7 0 32 14.3 32 32V384c0 17.7-14.3 32-32 32H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h64c53 0 96-43 96-96V128c0-53-43-96-96-96H352c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-7.5 177.4c4.8-4.5 7.5-10.8 7.5-17.4s-2.7-12.9-7.5-17.4l-144-136c-7-6.6-17.2-8.4-26-4.6s-14.5 12.5-14.5 22v72H32c-17.7 0-32 14.3-32 32v64c0 17.7 14.3 32 32 32H160v72c0 9.6 5.7 18.2 14.5 22s19 2 26-4.6l144-136z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11 7 9.6 8.4l2.6 2.6H2v2h10.2l-2.6 2.6L11 17l5-5-5-5zm9 12h-8v2h10V3H12v2h8v14z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@@ -55,7 +57,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="font-container"
|
||||
className="btn-tpa__font-container"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -104,7 +106,7 @@ Array [
|
||||
>
|
||||
<img
|
||||
alt="icon Apple"
|
||||
className="icon-image"
|
||||
className="btn-tpa__image-icon"
|
||||
src="https://edx.devstack.lms/logo.png"
|
||||
/>
|
||||
</div>
|
||||
@@ -133,7 +135,7 @@ Array [
|
||||
>
|
||||
<img
|
||||
alt="icon Facebook"
|
||||
className="icon-image"
|
||||
className="btn-tpa__image-icon"
|
||||
src="https://edx.devstack.lms/facebook-logo.png"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,17 @@ exports[`Zendesk Help should match login page third party auth alert message sna
|
||||
},
|
||||
},
|
||||
"chat": Object {
|
||||
"departments": Object {
|
||||
"enabled": Array [
|
||||
"account settings",
|
||||
"billing and payments",
|
||||
"certificates",
|
||||
"deadlines",
|
||||
"errors and technical issues",
|
||||
"other",
|
||||
"proctoring",
|
||||
],
|
||||
},
|
||||
"suppress": false,
|
||||
},
|
||||
"contactForm": Object {
|
||||
|
||||
@@ -2,25 +2,33 @@ const configuration = {
|
||||
// Cookies related configs
|
||||
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
|
||||
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
|
||||
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
|
||||
// Features
|
||||
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
|
||||
ENABLE_COOKIE_POLICY_BANNER: process.env.ENABLE_COOKIE_POLICY_BANNER || false,
|
||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
|
||||
ENABLE_PERSONALIZED_RECOMMENDATIONS: process.env.ENABLE_PERSONALIZED_RECOMMENDATIONS || false,
|
||||
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
|
||||
ENABLE_POPULAR_AND_TRENDING_RECOMMENDATIONS: process.env.ENABLE_POPULAR_AND_TRENDING_RECOMMENDATIONS || false,
|
||||
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || true,
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || 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,
|
||||
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
|
||||
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
|
||||
POST_REGISTRATION_REDIRECT_URL: process.env.POST_REGISTRATION_REDIRECT_URL || '',
|
||||
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
|
||||
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
|
||||
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
|
||||
TOS_LINK: process.env.TOS_LINK || null,
|
||||
// Miscellaneous
|
||||
// Base container images
|
||||
BANNER_IMAGE_LARGE: process.env.BANNER_IMAGE_LARGE || '',
|
||||
BANNER_IMAGE_MEDIUM: process.env.BANNER_IMAGE_MEDIUM || '',
|
||||
BANNER_IMAGE_SMALL: process.env.BANNER_IMAGE_SMALL || '',
|
||||
BANNER_IMAGE_EXTRA_SMALL: process.env.BANNER_IMAGE_EXTRA_SMALL || '',
|
||||
// Recommendation constants
|
||||
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
|
||||
POPULAR_PRODUCTS: process.env.POPULAR_PRODUCTS || '[]',
|
||||
TRENDING_PRODUCTS: process.env.TRENDING_PRODUCTS || '[]',
|
||||
// Miscellaneous
|
||||
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
||||
ZENDESK_KEY: process.env.ZENDESK_KEY,
|
||||
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// URL Paths
|
||||
export const LOGIN_PAGE = '/login';
|
||||
export const REGISTER_PAGE = '/register';
|
||||
export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
|
||||
export const RESET_PAGE = '/reset';
|
||||
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
|
||||
export const DEFAULT_REDIRECT_URL = '/dashboard';
|
||||
@@ -23,16 +24,13 @@ export const PENDING_STATE = 'pending';
|
||||
export const COMPLETE_STATE = 'complete';
|
||||
export const FAILURE_STATE = 'failure';
|
||||
export const FORBIDDEN_STATE = 'forbidden';
|
||||
export const EMBEDDED = 'embedded';
|
||||
|
||||
// Regex
|
||||
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
|
||||
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
|
||||
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
|
||||
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
|
||||
export const LETTER_REGEX = /[a-zA-Z]/;
|
||||
export const NUMBER_REGEX = /\d/;
|
||||
export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi; // eslint-disable-line no-useless-escape
|
||||
|
||||
// Query string parameters that can be passed to LMS to manage
|
||||
// things like auto-enrollment upon login and registration.
|
||||
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'save_for_later', 'register_for_free', 'track', 'is_account_recovery'];
|
||||
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta'];
|
||||
export const REDIRECT = 'redirect';
|
||||
|
||||
@@ -4,8 +4,14 @@ import {
|
||||
|
||||
const OPTIMIZELY_SDK_KEY = process.env.OPTIMIZELY_FULL_STACK_SDK_KEY;
|
||||
|
||||
const optimizely = createInstance({
|
||||
sdkKey: OPTIMIZELY_SDK_KEY,
|
||||
});
|
||||
const getOptimizelyInstance = () => {
|
||||
if (OPTIMIZELY_SDK_KEY) {
|
||||
return createInstance({
|
||||
sdkKey: OPTIMIZELY_SDK_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
export default optimizely;
|
||||
return null;
|
||||
};
|
||||
|
||||
export default getOptimizelyInstance();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import Cookies from 'universal-cookie';
|
||||
|
||||
export function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||
export default function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||
const cookies = new Cookies();
|
||||
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
|
||||
if (cookieExpiry) {
|
||||
@@ -9,13 +9,3 @@ export function setCookie(cookieName, cookieValue, cookieExpiry) {
|
||||
}
|
||||
cookies.set(cookieName, cookieValue, options);
|
||||
}
|
||||
|
||||
export default function setSurveyCookie(surveyType) {
|
||||
const cookieName = getConfig().USER_SURVEY_COOKIE_NAME;
|
||||
if (cookieName) {
|
||||
const signupTimestamp = (new Date()).getTime();
|
||||
// set expiry to exactly 24 hours from now
|
||||
const cookieExpiry = new Date(signupTimestamp + 1 * 864e5);
|
||||
setCookie(cookieName, surveyType, cookieExpiry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,3 +76,8 @@ export const windowScrollTo = (options) => {
|
||||
|
||||
return window.scrollTo(options.top, options.left);
|
||||
};
|
||||
|
||||
export const isHostAvailableInQueryParams = () => {
|
||||
const queryParams = getAllPossibleQueryParams();
|
||||
return 'host' in queryParams;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LOGIN_PAGE } from '../constants';
|
||||
import { updatePathWithQueryParams } from './dataUtils';
|
||||
import { LOGIN_PAGE } from '../constants';
|
||||
|
||||
describe('updatePathWithQueryParams', () => {
|
||||
it('should append query params into the path', () => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
export {
|
||||
getTpaProvider,
|
||||
getTpaHint,
|
||||
updatePathWithQueryParams,
|
||||
getAllPossibleQueryParams,
|
||||
getActivationStatus,
|
||||
isHostAvailableInQueryParams,
|
||||
updatePathWithQueryParams,
|
||||
windowScrollTo,
|
||||
} from './dataUtils';
|
||||
export { default as AsyncActionType } from './reduxUtils';
|
||||
export { default as setSurveyCookie, setCookie } from './cookies';
|
||||
export { default as setCookie } from './cookies';
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('FieldRendererTests', () => {
|
||||
type: 'select',
|
||||
label: 'Year of Birth',
|
||||
name: 'yob-field',
|
||||
options: [['1997', 1997], ['1998', 1998]],
|
||||
options: [['1997', '1997'], ['1998', '1998']],
|
||||
};
|
||||
|
||||
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
|
||||
|
||||
@@ -6,11 +6,11 @@ import { Alert } from '@edx/paragon';
|
||||
import { CheckCircle, Error } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
import {
|
||||
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
|
||||
} from '../data/constants';
|
||||
import { PASSWORD_RESET } from '../reset-password/data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const ForgotPasswordAlert = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -15,16 +15,17 @@ import {
|
||||
import { ChevronLeft } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { BaseComponent } from '../base-component';
|
||||
import { FormGroup } from '../common-components';
|
||||
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
|
||||
import { forgotPasswordResultSelector } from './data/selectors';
|
||||
import ForgotPasswordAlert from './ForgotPasswordAlert';
|
||||
import messages from './messages';
|
||||
import BaseContainer from '../base-container';
|
||||
import { FormGroup } from '../common-components';
|
||||
import { DEFAULT_STATE, LOGIN_PAGE } from '../data/constants';
|
||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||
import { VALID_EMAIL_REGEX } from '../register/RegistrationFields/EmailField/constants';
|
||||
|
||||
const ForgotPasswordPage = (props) => {
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
@@ -38,7 +39,7 @@ const ForgotPasswordPage = (props) => {
|
||||
const [bannerEmail, setBannerEmail] = useState('');
|
||||
const [formErrors, setFormErrors] = useState('');
|
||||
const [validationError, setValidationError] = useState(emailValidationError);
|
||||
const [key, setKey] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
sendPageEvent('login_and_registration', 'reset');
|
||||
@@ -95,19 +96,16 @@ const ForgotPasswordPage = (props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseComponent>
|
||||
<BaseContainer>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div>
|
||||
<Tabs activeKey="" id="controlled-tab" onSelect={(k) => setKey(k)}>
|
||||
<Tabs activeKey="" id="controlled-tab" onSelect={(key) => navigate(updatePathWithQueryParams(key))}>
|
||||
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
|
||||
</Tabs>
|
||||
{ key && (
|
||||
<Redirect to={updatePathWithQueryParams(key)} />
|
||||
)}
|
||||
<div id="main-content" className="main-content">
|
||||
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
|
||||
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
|
||||
@@ -133,7 +131,7 @@ const ForgotPasswordPage = (props) => {
|
||||
name="submit-forget-password"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="forgot-password-button-width"
|
||||
className="forgot-password--button"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: formatMessage(messages['forgot.password.page.submit.button']),
|
||||
@@ -163,7 +161,7 @@ const ForgotPasswordPage = (props) => {
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</BaseComponent>
|
||||
</BaseContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
|
||||
import { INTERNAL_SERVER_ERROR, PENDING_STATE } from '../../data/constants';
|
||||
import { PASSWORD_RESET_FAILURE } from '../../reset-password/data/actions';
|
||||
import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
status: '',
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
|
||||
@@ -17,14 +13,20 @@ import { PASSWORD_RESET } from '../../reset-password/data/constants';
|
||||
import { setForgotPasswordFormData } from '../data/actions';
|
||||
import ForgotPasswordPage from '../ForgotPasswordPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
const mockedNavigator = jest.fn();
|
||||
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
sendTrackEvent: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...(jest.requireActual('react-router-dom')),
|
||||
useNavigate: () => mockedNavigator,
|
||||
}));
|
||||
|
||||
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
|
||||
const mockStore = configureStore();
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const initialState = {
|
||||
forgotPassword: {
|
||||
@@ -51,7 +53,12 @@ describe('ForgotPasswordPage', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore(initialState);
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(() => ({
|
||||
userId: 3,
|
||||
username: 'test-user',
|
||||
})),
|
||||
}));
|
||||
configure({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
@@ -191,11 +198,6 @@ describe('ForgotPasswordPage', () => {
|
||||
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it('check cookie rendered', () => {
|
||||
const forgotPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
expect(forgotPage.find(<CookiePolicyBanner />)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display success message after email is sent', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
@@ -227,15 +229,11 @@ describe('ForgotPasswordPage', () => {
|
||||
});
|
||||
|
||||
it('should redirect onto login page', async () => {
|
||||
const forgotPasswordPage = mount(reduxWrapper(
|
||||
<Router history={history}>
|
||||
<IntlForgotPasswordPage {...props} />
|
||||
</Router>,
|
||||
));
|
||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
|
||||
await act(async () => { await forgotPasswordPage.find('nav').find('a').first().simulate('click'); });
|
||||
|
||||
forgotPasswordPage.update();
|
||||
expect(history.location.pathname).toEqual(LOGIN_PAGE);
|
||||
expect(mockedNavigator).toHaveBeenCalledWith(LOGIN_PAGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,41 +1,39 @@
|
||||
import { messages as paragonMessages } from '@edx/paragon';
|
||||
|
||||
import arMessages from './messages/ar.json';
|
||||
import caMessages from './messages/ca.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
import dedeMessages from './messages/de_DE.json';
|
||||
import deMessages from './messages/de.json';
|
||||
import dedeCAMessages from './messages/de_DE.json';
|
||||
import es419Messages from './messages/es_419.json';
|
||||
import frMessages from './messages/fr.json';
|
||||
import heMessages from './messages/he.json';
|
||||
import frCAMessages from './messages/fr_CA.json';
|
||||
import hiMessages from './messages/hi.json';
|
||||
import idMessages from './messages/id.json';
|
||||
import ititMessages from './messages/it_IT.json';
|
||||
import kokrMessages from './messages/ko_kr.json';
|
||||
import plMessages from './messages/pl.json';
|
||||
import ptbrMessages from './messages/pt_br.json';
|
||||
import ptptMessages from './messages/pt_PT.json';
|
||||
import itMessages from './messages/it.json';
|
||||
import ititCAMessages from './messages/it_IT.json';
|
||||
import ptMessages from './messages/pt.json';
|
||||
import ptptCAMessages from './messages/pt_PT.json';
|
||||
import ruMessages from './messages/ru.json';
|
||||
import thMessages from './messages/th.json';
|
||||
import ukMessages from './messages/uk.json';
|
||||
import zhcnMessages from './messages/zh_CN.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
|
||||
const messages = {
|
||||
const appMessages = {
|
||||
ar: arMessages,
|
||||
es: es419Messages, // Prospectus uses es language code for spanish, added `es` option and pointed to es-419 strings.
|
||||
'es-419': es419Messages,
|
||||
fr: frMessages,
|
||||
'zh-cn': zhcnMessages,
|
||||
'it-it': ititMessages,
|
||||
'pt-pt': ptptMessages,
|
||||
'de-de': dedeMessages,
|
||||
ca: caMessages,
|
||||
he: heMessages,
|
||||
id: idMessages,
|
||||
'ko-kr': kokrMessages,
|
||||
pl: plMessages,
|
||||
'pt-br': ptbrMessages,
|
||||
ru: ruMessages,
|
||||
th: thMessages,
|
||||
uk: ukMessages,
|
||||
pt: ptMessages,
|
||||
it: itMessages,
|
||||
de: deMessages,
|
||||
hi: hiMessages,
|
||||
'fr-ca': frCAMessages,
|
||||
ru: ruMessages,
|
||||
uk: ukMessages,
|
||||
'de-de': dedeCAMessages,
|
||||
'it-it': ititCAMessages,
|
||||
'pt-pt': ptptCAMessages,
|
||||
};
|
||||
|
||||
export default messages;
|
||||
export default [
|
||||
paragonMessages,
|
||||
appMessages,
|
||||
];
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"start.learning": "ابدأ التعلم ",
|
||||
"with.site.name": "مع {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "أهلا بك {username} في {siteName}",
|
||||
"complete.your.profile.1": "أكمل",
|
||||
"complete.your.profile.2": "ملفك الشخصي",
|
||||
"welcome.to.platform": "أهلا بك {username} في {siteName}",
|
||||
"institution.login.page.sub.heading": "اختر مؤسستك من القائمة أدناه",
|
||||
"logistration.sign.in": "تسجيل الدخول",
|
||||
"logistration.register": "التسجيل",
|
||||
"enterprisetpa.title.heading": "هل ترغب في تسجيل الدخول باستخدام بيانات {providerName} الخاصة بك؟",
|
||||
"enterprisetpa.login.button.text": "أرِني وسائل أخرى لتسجيل الدخول أو للتسجيل",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "أرني طرقًا أخرى لتسجيل الدخول",
|
||||
"sso.sign.in.with": "تسجيل الدخول باستخدام {providerName}",
|
||||
"sso.create.account.using": "إنشاء حساب باستخدام {providerName}",
|
||||
"show.password": "إظهار كلمة المرور",
|
||||
@@ -23,7 +25,7 @@
|
||||
"register.third.party.auth.account.not.linked": "لقد سجلت دخولك بنجاح إلى {currentProvider}! نحتاج فقط قليلاً بعدُ من المعلومات قبل أن تبدأ التعلم مع {platformName}.",
|
||||
"registration.using.tpa.form.heading": "إتمام إنشاء حسابك",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
|
||||
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
|
||||
"forgot.password.confirmation.message": "لقد أرسلنا بريدًا إلكترونيًا إلى {email} به إرشادات لإعادة ضبط كلمة المرور الخاصة بك. إن لم تستلم رسالة إعادة ضبط كلمة المرور بعد دقيقة واحدة، فتحقق من إدخال عنوان البريد الإلكتروني الصحيح، أو تفقد مجلد الرسائل غير المرغوب فيها. إن احتجت مزيدًا من المساعدة، {supportLink}.",
|
||||
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
|
||||
@@ -96,20 +98,21 @@
|
||||
"password.security.block.body": "اكتشف نظامنا أن كلمة مرورك صعيفة. غيّر كلمة مرورك حتى يظل حسابك آمنًا.",
|
||||
"password.security.close.button": "إغلاق",
|
||||
"password.security.redirect.to.reset.password.button": "إعادة ضبط كلمة المرور",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"login.tpa.authentication.failure": "عذرًا ، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
|
||||
"progressive.profiling.page.title": "مرحبا بكم | {siteName}",
|
||||
"progressive.profiling.page.heading": "بعض الأسئلة الموجهة لك ستساعدنا كي نزداد ذكاءً.",
|
||||
"optional.fields.information.link": "معرفة المزيد عن كيفية استخدامنا لهذه المعلومات.",
|
||||
"optional.fields.submit.button": "إرسال",
|
||||
"optional.fields.skip.button": "التخطي مؤقتا",
|
||||
"optional.fields.next.button": "Next",
|
||||
"optional.fields.next.button": "التالي",
|
||||
"continue.to.platform": "المواصلة إلى {platformName}",
|
||||
"modal.title": "شكرا لإعلامنا.",
|
||||
"modal.description": "إن غيرت رأيك، قيمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
|
||||
"welcome.page.error.heading": "لم نتمكن من تحديث ملفك الشخصي",
|
||||
"welcome.page.error.message": "حدث خطأ ما. يمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"recommendation.page.title": "التوصيات | {siteName}",
|
||||
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
|
||||
"recommendation.skip.button": "التخطي مؤقتا",
|
||||
"register.page.title": "التسجيل | {siteName}",
|
||||
"registration.fullname.label": "الاسم الكامل",
|
||||
"registration.email.label": "البريد الإلكتروني",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
|
||||
"registration.rate.limit.error": "كثرت محاولات التسجيل الفاشلة. أعد المحاولة لاحقًا.",
|
||||
"registration.tpa.session.expired": "نفد وقت التسجيل باستخدام {provider}.",
|
||||
"registration.tpa.authentication.failure": "عذرًا، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
|
||||
"terms.of.service.and.honor.code": "شروط الخدمة وميثاق الشرف الأكاديمي",
|
||||
"privacy.policy": "سياسة الخصوصية",
|
||||
"honor.code": "ميثاق الشرف الأكاديمي",
|
||||
@@ -147,8 +151,8 @@
|
||||
"registration.username.suggestion.label": "مقترح:",
|
||||
"did.you.mean.alert.text": "هل تقصد",
|
||||
"register.page.terms.of.service.and.honor.code": "بإنشاءك حسابًا، فإنك توافق على {tosAndHonorCode} و تقر بأن {platformName} و كل عضو يعالج بياناتك الشخصية وفقًا لـ{privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"register.page.honor.code": "اوافق على شروط {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "اوافق على {platformName} {termsOfService}",
|
||||
"sign.in": "تسجيل الدخول",
|
||||
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
|
||||
"reset.password": "إعادة ضبط كلمة المرور",
|
||||
|
||||
170
src/i18n/messages/de.json
Normal file
170
src/i18n/messages/de.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"tpa.alert.heading": "Almost done!",
|
||||
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
|
||||
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||
"forgot.password.page.heading": "Reset password",
|
||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||
"forgot.password.page.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submit",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "An error occurred.",
|
||||
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
|
||||
"token.validation.rate.limit.error.heading": "Too many requests",
|
||||
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"token.validation.internal.sever.error.heading": "Token validation failure",
|
||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Sign in",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "We recently changed our password requirements",
|
||||
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
|
||||
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
||||
"login.failure.header.title": "We couldn't sign you in.",
|
||||
"contact.support.link": "contact {platformName} support",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"start.learning": "Beginne zu lernen",
|
||||
"with.site.name": "mit {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Vervollständige",
|
||||
"complete.your.profile.2": "dein Profil",
|
||||
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Wählen Sie Ihre Institution aus der folgenden Liste aus",
|
||||
"logistration.sign.in": "Anmelden",
|
||||
"logistration.register": "Registrieren",
|
||||
@@ -96,6 +98,7 @@
|
||||
"password.security.block.body": "Unser System hat festgestellt, dass Ihr Passwort angreifbar ist. Ändern Sie Ihr Passwort, damit Ihr Konto sicher bleibt.",
|
||||
"password.security.close.button": "Schließen",
|
||||
"password.security.redirect.to.reset.password.button": "Setzen Sie Ihr Passwort zurück",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "Ein paar Fragen an Sie helfen uns, schlauer zu werden.",
|
||||
"optional.fields.information.link": "Erfahren Sie mehr darüber, wie wir diese Informationen verwenden.",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
|
||||
"registration.rate.limit.error": "Zu viele fehlgeschlagene Registrierungsversuche. Versuchen Sie es später noch einmal.",
|
||||
"registration.tpa.session.expired": "Die Registrierung mit {provider} ist abgelaufen.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Nutzungsbedingungen und Verhaltenskodex",
|
||||
"privacy.policy": "Datenschutzbestimmungen",
|
||||
"honor.code": "Verhaltenskodex",
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"start.learning": "Empieza a aprender",
|
||||
"with.site.name": "con {siteName}",
|
||||
"your.career.turning.point": "El punto de inflexión de tu carrera",
|
||||
"is.here": "es aquí.",
|
||||
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Completado",
|
||||
"complete.your.profile.2": "tu perfil ",
|
||||
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
|
||||
"logistration.sign.in": "Iniciar sesión",
|
||||
"logistration.register": "Registrarse",
|
||||
"enterprisetpa.title.heading": "¿Deseas iniciar sesión con tus credenciales de {providerName}?",
|
||||
"enterprisetpa.login.button.text": "Mostrar otras formas de iniciar sesión o de registrarme",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Mostrar otras formas de iniciar sesión",
|
||||
"sso.sign.in.with": "Inicio de sesión con {providerName}",
|
||||
"sso.create.account.using": "Crear una cuenta con {providerName}",
|
||||
"show.password": "Mostrar contraseña",
|
||||
@@ -22,8 +24,8 @@
|
||||
"login.third.party.auth.account.not.linked": "Te has registrado correctamente en {currentProvider}, pero tu cuenta de {currentProvider} no tiene una cuenta de {platformName} asociada. Para asociar tus cuentas, inicia sesión ahora usando tu contraseña de {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "¡Has iniciado sesión con éxito en {currentProvider}! Sólo necesitamos un poco más de información antes de que empieces a aprender con {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"zendesk.supportTitle": "Soporte edX",
|
||||
"zendesk.selectTicketForm": "Elegir el tipo de solicitud:",
|
||||
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, verifica la URL y vuelve a intentarlo.",
|
||||
"forgot.password.confirmation.message": "Hemos enviado un correo electrónico a {email} con instrucciones para restablecer tu contraseña.\n Si no recibes un mensaje de restablecimiento de contraseña después de 1 minuto, verifica que has introducido\n la dirección de correo electrónico correcta, o comprueba tu carpeta de correo no deseado. Si necesitas más ayuda, {supportLink}.",
|
||||
"forgot.password.page.title": "Olvidé la contraseña | {siteName}",
|
||||
@@ -96,7 +98,8 @@
|
||||
"password.security.block.body": "Nuestro sistema detectó que su contraseña es vulnerable. Cambie su contraseña para que su cuenta permanezca segura.",
|
||||
"password.security.close.button": "Cerrar",
|
||||
"password.security.redirect.to.reset.password.button": "Restablece tu contraseña",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"login.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Bienvenido | {siteName}",
|
||||
"progressive.profiling.page.heading": "Unas cuantas preguntas para ti nos ayudarán a mejorar.",
|
||||
"optional.fields.information.link": "Aprende más sobre cómo usamos esta información.",
|
||||
"optional.fields.submit.button": "Enviar",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
|
||||
"registration.rate.limit.error": "Demasiados intentos de registro fallidos. Vuelve a intentarlo más tarde.",
|
||||
"registration.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
|
||||
"registration.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Condiciones de servicio y código de honor",
|
||||
"privacy.policy": "Política de privacidad ",
|
||||
"honor.code": "Código de Honor",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"start.learning": "Démarrer l'apprentissage",
|
||||
"with.site.name": "avec {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Terminé",
|
||||
"complete.your.profile.2": "votre profil",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"logistration.sign.in": "Connectez-vous",
|
||||
"logistration.register": "S'inscrire",
|
||||
@@ -96,6 +98,7 @@
|
||||
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
|
||||
"password.security.close.button": "Fermer",
|
||||
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
|
||||
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
|
||||
"registration.tpa.session.expired": "L'inscription avec {provider} a échouée.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Conditions d'utilisation et Code d'honneur",
|
||||
"privacy.policy": "Politique de confidentialité",
|
||||
"honor.code": "Code d'honneur",
|
||||
|
||||
170
src/i18n/messages/fr_CA.json
Normal file
170
src/i18n/messages/fr_CA.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"start.learning": "Démarrer l'apprentissage",
|
||||
"with.site.name": "avec {siteName}",
|
||||
"your.career.turning.point": "Votre tournant de carrière",
|
||||
"is.here": "est là.",
|
||||
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complet",
|
||||
"complete.your.profile.2": "votre profil",
|
||||
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
|
||||
"logistration.sign.in": "Connexion",
|
||||
"logistration.register": "Inscription",
|
||||
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName}?",
|
||||
"enterprisetpa.login.button.text": "Affichez moi d'autres façons de se connecter ou de s'inscrire",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Montrez-moi d'autres façons de me connecter",
|
||||
"sso.sign.in.with": "Connectez-vous avec {providerName}",
|
||||
"sso.create.account.using": "Créer un compte avec {providerName}",
|
||||
"show.password": "Afficher le mot de passe",
|
||||
"hide.password": "Cacher le mot de passe",
|
||||
"one.letter": "1 lettre",
|
||||
"one.number": "1 numéro",
|
||||
"eight.characters": "8 caractères",
|
||||
"password.sr.only.helping.text": "Le mot de passe doit contenir au moins 8 caractères, au moins une lettre et au moins un chiffre",
|
||||
"tpa.alert.heading": "Presque terminé!",
|
||||
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
|
||||
"zendesk.supportTitle": "Prise en charge d'edX",
|
||||
"zendesk.selectTicketForm": "Veuillez choisir votre type de demande :",
|
||||
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
|
||||
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe.\n Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi\nl'adresse courriel correctement, ou vérifiez votre dossier de pourriels. Si vous avez besoin d'aide supplémentaire, {supportLink}.",
|
||||
"forgot.password.page.title": "Mot de passe oublié | {siteName}",
|
||||
"forgot.password.page.heading": "Réinitialiser le mot de passe",
|
||||
"forgot.password.page.instructions": "Veuillez entrer votre adresse courriel ci-dessous et nous vous enverrons un courriel avec les instructions pour réinitialiser votre mot de passe.",
|
||||
"forgot.password.page.invalid.email.message": "Entrez une adresse de courriel valide",
|
||||
"forgot.password.page.email.field.label": "Courriel",
|
||||
"forgot.password.page.submit.button": "Soumettre",
|
||||
"forgot.password.error.alert.title.": "Nous n'avons pas pu vous contacter.",
|
||||
"forgot.password.error.message.title": "Une erreur est survenue.",
|
||||
"forgot.password.request.in.progress.message": "Votre demande précédente est en cours, veuillez réessayer dans quelques instants.",
|
||||
"forgot.password.empty.email.field.error": "Saisissez votre courriel",
|
||||
"forgot.password.email.help.text": "L'adresse courriel que vous avez utilisée pour vous inscrire sur {platformName}",
|
||||
"confirmation.message.title": "Vérifiez votre courriel",
|
||||
"confirmation.support.link": "contacter le support technique",
|
||||
"need.help.sign.in.text": "Besoin d'aide pour vous connecter?",
|
||||
"additional.help.text": "Pour obtenir une aide supplémentaire, contactez l'assistance {platformName} à l'adresse",
|
||||
"sign.in.text": "Connexion",
|
||||
"extend.field.errors": "{emailError} ci-dessous.",
|
||||
"invalid.token.heading": "Lien de réinitialisation du mot de passe non valide",
|
||||
"invalid.token.error.message": "Ce lien de réinitialisation de mot de passe n'est pas valide. Il a peut-être déjà été utilisé. Entrez votre courriel ci-dessous pour recevoir un nouveau lien.",
|
||||
"token.validation.rate.limit.error.heading": "Trop de demandes",
|
||||
"token.validation.rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps.",
|
||||
"token.validation.internal.sever.error.heading": "Échec de la validation du jeton",
|
||||
"token.validation.internal.sever.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"internal.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"account.activation.error.message": "Une erreur s'est produite, veuillez {supportLink} pour résoudre ce problème.",
|
||||
"login.inactive.user.error": "Pour vous connecter, vous devez activer votre compte.{lineBreak}\n {lineBreak}Nous venons d'envoyer un lien d'activation à {email}. Si vous ne recevez pas de courriel,\n vérifiez vos dossiers de pourriels ou {supportLink}.",
|
||||
"allowed.domain.login.error": "En tant qu'utilisateur {allowedDomain}, vous devez vous connecter avec votre {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "Le nom d'utilisateur, le courriel ou le mot de passe que vous avez entré sont incorrects. Vous avez {remainingAttempts} tentatives\n de connexion avant que votre compte soit temporairement verrouillé.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Si vous avez oublié votre mot de passe, {resetLink}",
|
||||
"account.locked.out.message.2": "Par mesure de sécurité, vous pouvez {resetLink} avant de réessayer.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer ou {resetLink}.",
|
||||
"login.page.title": "Connexion | {siteName}",
|
||||
"login.user.identity.label": "Nom d'utilisateur ou courriel",
|
||||
"login.password.label": "Mot de passe",
|
||||
"sign.in.button": "Connexion",
|
||||
"forgot.password": "Mot de passe oublié",
|
||||
"institution.login.button": "Identifiants de l'établissement/du campus",
|
||||
"institution.login.page.title": "Connectez vous avec les crédentiels d'institution ou de campus",
|
||||
"login.other.options.heading": "Ou se connecter avec :",
|
||||
"non.compliant.password.title": "Nous avons récemment modifié nos exigences en matière de mot de passe",
|
||||
"non.compliant.password.message": "Votre mot de passe actuel ne répond pas aux nouvelles exigences de sécurité. Nous venons d'envoyer un message de réinitialisation de mot de passe à l'adresse courriel associée à ce compte. Merci de nous aider à protéger vos données.",
|
||||
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
|
||||
"enterprise.login.btn.text": "Identifiants de la compagnie ou de l'école",
|
||||
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 3 caractères.",
|
||||
"email.validation.message": "Entrez votre nom d'utilisateur ou votre adresse courriel",
|
||||
"password.validation.message": "Les critères de mot de passe n'ont pas été remplis",
|
||||
"account.activation.success.message.title": "Succès! Vous avez activé votre compte.",
|
||||
"account.activation.success.message": "Vous recevrez maintenant des mises à jour par courriel et des alertes de notre part concernant les cours auxquels vous êtes inscrit. Connectez-vous pour continuer.",
|
||||
"account.activation.info.message": "Ce compte a déjà été activé.",
|
||||
"account.activation.error.message.title": "Votre compte n'a pas pu être activé",
|
||||
"account.activation.support.link": "contacter le support",
|
||||
"account.confirmation.success.message.title": "Bravo! Vous avez confirmé votre courriel.",
|
||||
"account.confirmation.success.message": "Se connecter pour continuer.",
|
||||
"account.confirmation.info.message": "Ce courriel a déjà été confirmé.",
|
||||
"account.confirmation.error.message.title": "Votre courriel ne peut pas être confirmé",
|
||||
"tpa.account.link": "compte {provider}",
|
||||
"internal.server.error.message": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"login.rate.limit.reached.message": "Trop de tentatives d'accès refusées. Essayer plus tard.",
|
||||
"login.failure.header.title": "Nous n'avons pas pu vous connecter.",
|
||||
"contact.support.link": "veuillez contacter le support {platformName}",
|
||||
"login.incorrect.credentials.error": "Le nom d'utilisateur, l'adresse courriel ou le mot de passe que vous avez saisis sont incorrects. Veuillez réessayer.",
|
||||
"login.form.invalid.error.message": "Veuillez remplir les champs ci-dessous.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "réinitialiser votre mot de passe",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "cliquez ici pour le réinitialiser.",
|
||||
"password.security.nudge.title": "Sécurité du mot de passe",
|
||||
"password.security.block.title": "Changement de mot de passe requis",
|
||||
"password.security.nudge.body": "Notre système a détecté que votre mot de passe est vulnérable. Nous vous recommandons de le modifier afin que votre compte reste sécurisé.",
|
||||
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
|
||||
"password.security.close.button": "Fermer",
|
||||
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
|
||||
"login.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Bienvenue | {siteName}",
|
||||
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
|
||||
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
|
||||
"optional.fields.submit.button": "Soumettre",
|
||||
"optional.fields.skip.button": "Ignorer pour l'instant",
|
||||
"optional.fields.next.button": "Suivant",
|
||||
"continue.to.platform": "Continuer vers {platformName}",
|
||||
"modal.title": "Merci de nous en informer.",
|
||||
"modal.description": "Vous pouvez compléter votre profil dans les paramètres à tout moment si vous changez d'avis.",
|
||||
"welcome.page.error.heading": "Nous n'avons pas pu mettre à jour votre profil",
|
||||
"welcome.page.error.message": "Une erreur s'est produite. Vous pouvez compléter votre profil dans les paramètres à tout moment.",
|
||||
"recommendation.page.title": "Recommandations | {siteName}",
|
||||
"recommendation.page.heading": "Nous avons quelques recommandations pour vous aider à démarrer.",
|
||||
"recommendation.skip.button": "Ignorer pour l'instant",
|
||||
"register.page.title": "S'inscrire | {siteName}",
|
||||
"registration.fullname.label": "Nom complet",
|
||||
"registration.email.label": "Courriel",
|
||||
"registration.username.label": "Nom d'utilisateur",
|
||||
"registration.password.label": "Mot de passe",
|
||||
"registration.country.label": "Pays/Région",
|
||||
"registration.opt.in.label": "{siteName} peux m'envoyer des messages de marketing.",
|
||||
"help.text.name": "Ce nom sera utilisé pour toutes les attestations que vous obtiendrez.",
|
||||
"help.text.username.1": "Le nom qui vous identifiera dans vos cours.",
|
||||
"help.text.username.2": "Cela ne peut pas être modifié ultérieurement.",
|
||||
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
|
||||
"create.account.for.free.button": "Créer un compte gratuitement",
|
||||
"registration.other.options.heading": "Ou inscrivez-vous avec :",
|
||||
"register.institution.login.button": "Identifiants de l'établissement/du campus",
|
||||
"register.institution.login.page.title": "Inscription avec les crédentiels d'institution ou de campus",
|
||||
"empty.name.field.error": "Saisissez votre nom complet",
|
||||
"empty.email.field.error": "Saisissez votre courriel",
|
||||
"empty.username.field.error": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"empty.password.field.error": "Les critères de mot de passe n'ont pas été remplis",
|
||||
"empty.country.field.error": "Sélectionnez votre pays ou région de résidence",
|
||||
"email.do.not.match": "Les adresses courriel ne correspondent pas.",
|
||||
"email.invalid.format.error": "Entrez une adresse de courriel valide",
|
||||
"username.validation.message": "Le nom d'utilisateur doit comporter entre 2 et 30 caractères",
|
||||
"name.validation.message": "Entrez un nom valide",
|
||||
"username.format.validation.message": "Les noms d'utilisateur ne peuvent contenir que des lettres (AZ, az), des chiffres (0-9), des traits de soulignement (_) et des traits d'union (-). Les noms d'utilisateur ne peuvent pas contenir d'espaces",
|
||||
"registration.request.failure.header": "Nous n'avons pas pu créer votre compte.",
|
||||
"registration.empty.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
||||
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
|
||||
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
|
||||
"registration.tpa.session.expired": "L'inscription à l'aide de {provider} a expiré.",
|
||||
"registration.tpa.authentication.failure": "Nous sommes désolés, vous n'êtes pas autorisé à accéder à {platform_name} via ce canal. Veuillez contacter votre administrateur ou responsable de formation pour accéder à {platform_name}.{lineBreak}{lineBreak}Détails de l'erreur :{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Conditions générales du service et code d'honneur",
|
||||
"privacy.policy": "Politique de confidentialité",
|
||||
"honor.code": "Code d'honneur",
|
||||
"terms.of.service": "Conditions générales du service",
|
||||
"registration.username.suggestion.label": "Suggéré :",
|
||||
"did.you.mean.alert.text": "Vouliez-vous dire",
|
||||
"register.page.terms.of.service.and.honor.code": "En créant un compte, vous acceptez le {tosAndHonorCode} et vous reconnaissez que {platformName} et chaque\n membre peut traiter vos données personnelles conformément à la {privacyPolicy}.",
|
||||
"register.page.honor.code": "J'accepte le {tosAndHonorCode} {platformName}",
|
||||
"register.page.terms.of.service": "J'accepte les {termsOfService} {platformName}",
|
||||
"sign.in": "Connexion",
|
||||
"reset.password.page.title": "Réinitialiser le mot de passe | {siteName}",
|
||||
"reset.password": "Réinitialiser le mot de passe",
|
||||
"reset.password.page.instructions": "Saisir et confirmer votre nouveau mot de passe.",
|
||||
"new.password.label": "Nouveau mot de passe",
|
||||
"confirm.password.label": "Confirmer le mot de passe",
|
||||
"passwords.do.not.match": "Les mots de passe ne correspondent pas",
|
||||
"confirm.your.password": "Confirmer votre mot de passe",
|
||||
"reset.password.failure.heading": "Nous n'avons pas pu réinitialiser votre mot de passe.",
|
||||
"reset.password.form.submission.error": "Veuillez vérifier vos réponses et réessayer.",
|
||||
"reset.server.rate.limit.error": "Trop de demandes.",
|
||||
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
|
||||
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
|
||||
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps."
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
@@ -96,6 +98,7 @@
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
|
||||
170
src/i18n/messages/it.json
Normal file
170
src/i18n/messages/it.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"tpa.alert.heading": "Almost done!",
|
||||
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
|
||||
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||
"forgot.password.page.heading": "Reset password",
|
||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||
"forgot.password.page.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submit",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "An error occurred.",
|
||||
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
|
||||
"token.validation.rate.limit.error.heading": "Too many requests",
|
||||
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"token.validation.internal.sever.error.heading": "Token validation failure",
|
||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Sign in",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "We recently changed our password requirements",
|
||||
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
|
||||
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
||||
"login.failure.header.title": "We couldn't sign you in.",
|
||||
"contact.support.link": "contact {platformName} support",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"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",
|
||||
"welcome.to.platform": "Benvenuto in {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Scegli il tuo istituto dall'elenco sottostante",
|
||||
"logistration.sign.in": "Accedi",
|
||||
"logistration.register": "Registrazione",
|
||||
@@ -96,6 +98,7 @@
|
||||
"password.security.block.body": "Il nostro sistema ha rilevato che la tua password è vulnerabile. Cambia la tua password in modo che il tuo account rimanga sicuro.",
|
||||
"password.security.close.button": "Chiudi",
|
||||
"password.security.redirect.to.reset.password.button": "Ripristina la tua password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "Alcune domande per te ci aiuteranno a diventare più intelligenti.",
|
||||
"optional.fields.information.link": "Ulteriori informazioni su come utilizziamo queste informazioni.",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "Si è verificato un errore. Prova ad aggiornare la pagina oppure verifica la connessione internet.",
|
||||
"registration.rate.limit.error": "Troppi tentativi di registrazione non riusciti. Prova di nuovo più tardi.",
|
||||
"registration.tpa.session.expired": "La registrazione mediante {provider} è andata in timeout.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Termini e Condizioni del Servizio",
|
||||
"privacy.policy": "Informativa sulla privacy",
|
||||
"honor.code": "Codice d'Onore",
|
||||
|
||||
170
src/i18n/messages/pt.json
Normal file
170
src/i18n/messages/pt.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"tpa.alert.heading": "Almost done!",
|
||||
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
|
||||
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||
"forgot.password.page.heading": "Reset password",
|
||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||
"forgot.password.page.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submit",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "An error occurred.",
|
||||
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
|
||||
"token.validation.rate.limit.error.heading": "Too many requests",
|
||||
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"token.validation.internal.sever.error.heading": "Token validation failure",
|
||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Sign in",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "We recently changed our password requirements",
|
||||
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
|
||||
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
||||
"login.failure.header.title": "We couldn't sign you in.",
|
||||
"contact.support.link": "contact {platformName} support",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
||||
}
|
||||
@@ -1,166 +1,170 @@
|
||||
{
|
||||
"start.learning": "Começar a aprender",
|
||||
"with.site.name": "with {siteName}",
|
||||
"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",
|
||||
"welcome.to.platform": "Bem vindo a {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"institution.login.page.sub.heading": "Escolha a sua instituição a partir da lista abaixo",
|
||||
"logistration.sign.in": "Iniciar sessão",
|
||||
"logistration.register": "Registe-se",
|
||||
"enterprisetpa.title.heading": "Gostaria de iniciar sessão usando as suas {providerName} credenciais?",
|
||||
"enterprisetpa.login.button.text": "Mostre-me outras formas de iniciar sessão ou registar-se",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Mostre-me outras formas de iniciar sessão",
|
||||
"sso.sign.in.with": "Inicie sessão com {providerName}",
|
||||
"sso.create.account.using": "Criar conta usando {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"tpa.alert.heading": "Almost done!",
|
||||
"show.password": "Mostrar palavra-passe",
|
||||
"hide.password": "Ocultar palavra-passe",
|
||||
"one.letter": "1 letra",
|
||||
"one.number": "1 número",
|
||||
"eight.characters": "8 caracteres",
|
||||
"password.sr.only.helping.text": "A palavra-passe deve conter pelo menos 8 carateres, pelo menos uma letra, e pelo menos um número",
|
||||
"tpa.alert.heading": "Quase pronto!",
|
||||
"login.third.party.auth.account.not.linked": "Iniciou sessão com sucesso em {currentProvider}, mas a sua conta {currentProvider} não está vinculada a uma conta {platformName}. Para vincular as suas contas, inicie sessão através da sua palavra-passe em {platformName}.",
|
||||
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"zendesk.supportTitle": "edX Support",
|
||||
"zendesk.selectTicketForm": "Please choose your request type:",
|
||||
"register.third.party.auth.account.not.linked": "Entrou com sucesso em {currentProvider}! Só precisamos de um pouco mais de informação antes de começar a aprender com {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Acabe de criar a sua conta",
|
||||
"zendesk.supportTitle": "Apoio edX",
|
||||
"zendesk.selectTicketForm": "Por favor, escolha o seu tipo de pedido:",
|
||||
"error.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.confirmation.message": "Enviámos um email para {email} com instruções para redefinir a sua palavra-passe.\n Se não receber uma mensagem de redefinição de palavra-passe após 1 minuto, verifique se introduziu\n o endereço de correio electrónico correto, ou verifique a sua pasta de spam. Se precisar de mais assistência, {supportLink}.",
|
||||
"forgot.password.page.title": "Esqueceu a Senha | {siteName}",
|
||||
"forgot.password.page.heading": "Reset password",
|
||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||
"forgot.password.page.heading": "Redefinir palavra-passe",
|
||||
"forgot.password.page.instructions": "Por favor introduza o seu endereço de email abaixo e enviar-lhe-emos um email com instruções sobre como redefinir a sua palavra-passe.",
|
||||
"forgot.password.page.invalid.email.message": "Introduzir um endereço de correio electrónico válido",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submeter",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.alert.title.": "Não nos foi possível contactá-lo.",
|
||||
"forgot.password.error.message.title": "Ocorreu um erro.",
|
||||
"forgot.password.request.in.progress.message": "O seu pedido anterior está a ser processado, por favor tente novamente dentro de momentos.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.empty.email.field.error": "Digite o seu email",
|
||||
"forgot.password.email.help.text": "O endereço de e-mail que usou para se registar em {platformName}",
|
||||
"confirmation.message.title": "Verifique o seu email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
|
||||
"token.validation.rate.limit.error.heading": "Too many requests",
|
||||
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"token.validation.internal.sever.error.heading": "Token validation failure",
|
||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"confirmation.support.link": "contacto o suporte técnico",
|
||||
"need.help.sign.in.text": "Precisa de ajuda para entrar?",
|
||||
"additional.help.text": "Para ajuda adicional, contacte o apoio {platformName} em",
|
||||
"sign.in.text": "Iniciar sessão",
|
||||
"extend.field.errors": "{emailError} abaixo.",
|
||||
"invalid.token.heading": "Link para Redefinir Palavra-passe inválido",
|
||||
"invalid.token.error.message": "Este link de redefinição de palavra-passe é inválido. Pode já ter sido utilizada. Introduza o seu email abaixo para receber um novo link.",
|
||||
"token.validation.rate.limit.error.heading": "Demasiados pedidos",
|
||||
"token.validation.rate.limit.error": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo.",
|
||||
"token.validation.internal.sever.error.heading": "Falha na validação do Token",
|
||||
"token.validation.internal.sever.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||
"internal.server.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||
"account.activation.error.message": "Alguma coisa correu mal, siga {supportLink} para resolver esta questão.",
|
||||
"login.inactive.user.error": "Para iniciar sessão, precisa ativar a sua conta. {lineBreak}\n {lineBreak} Acabámos de enviar um link de ativação para {email}. Se não receber um e-mail,\n verifique as suas pastas de spam ou {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"allowed.domain.login.error": "Como utilizador do {allowedDomain}, deve iniciar sessão com o seu {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "O nome de utilizador, email ou palavra-passe que introduziu está incorreto. Tem {remainingAttempts} mais tentativas\n de login antes da sua conta ser temporariamente bloqueada.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "Se esqueceu a sua palavra-passe, {resetLink}",
|
||||
"account.locked.out.message.2": "Por precaução, pode {resetLink} antes de tentar novamente.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "O nome de utilizador, email ou senha que introduziu está incorreto. Por favor, tente novamente ou {resetLink}.",
|
||||
"login.page.title": "Iniciar sessão | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"login.user.identity.label": "Nome de utilizador ou e-mail",
|
||||
"login.password.label": "Palavra-passe",
|
||||
"sign.in.button": "Iniciar Sessão",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"forgot.password": "Esqueci-me da palavra-passe",
|
||||
"institution.login.button": "Credenciais da instituição/campus",
|
||||
"institution.login.page.title": "Inicie sessão com credenciais de instituição/campus",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"login.other.options.heading": "Ou faça login com:",
|
||||
"non.compliant.password.title": "Recentemente mudámos os nossos requisitos de palavra-passe",
|
||||
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
|
||||
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"non.compliant.password.message": "A sua palavra-passe atual não satisfaz os novos requisitos de segurança. Acabámos de enviar uma mensagem de redefinição da palavra-passe para o endereço de email associado a esta conta. Obrigado por nos ajudar a manter os seus dados em segurança.",
|
||||
"account.locked.out.message.1": "Para proteger sua conta, esta foi temporariamente bloqueada. Tente novamente dentro de 30 minutos.",
|
||||
"enterprise.login.btn.text": "Credenciais da empresa ou escola",
|
||||
"username.or.email.format.validation.less.chars.message": "O nome de utilizador ou email deve ter pelo menos 3 carateres.",
|
||||
"email.validation.message": "Insira o seu nome de utilizador ou email",
|
||||
"password.validation.message": "Os critérios de palavra-passe não foram cumpridos",
|
||||
"account.activation.success.message.title": "Sucesso! Você ativou a sua conta.",
|
||||
"account.activation.success.message": "Receberá agora actualizações por e-mail e alertas nossos relacionados com os cursos em que está inscrito. Inicie a sessão para continuar.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.info.message": "Esta conta já foi ativada.",
|
||||
"account.activation.error.message.title": "A sua conta não pôde ser ativada",
|
||||
"account.activation.support.link": "contato de suporte",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"account.confirmation.success.message.title": "Sucesso! Confirmou o seu email.",
|
||||
"account.confirmation.success.message": "Inicie sessão para continuar.",
|
||||
"account.confirmation.info.message": "Este email já foi confirmado.",
|
||||
"account.confirmation.error.message.title": "Não foi possível confirmar o seu email",
|
||||
"tpa.account.link": "Conta {provider}",
|
||||
"internal.server.error.message": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||
"login.rate.limit.reached.message": "Muitas tentativas de login sem sucesso. Tente novamente mais tarde.",
|
||||
"login.failure.header.title": "O seu acesso não foi possível.",
|
||||
"contact.support.link": "contactar o suporte {platformName}",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.incorrect.credentials.error": "O nome de utilizador, email, ou palavra-passe que introduziu está incorreto. Por favor, tente novamente.",
|
||||
"login.form.invalid.error.message": "Por favor, preencha os campos abaixo.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "redefinir a sua palavra-passe",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "clique aqui para a redefinir.",
|
||||
"password.security.nudge.title": "Segurança da palavra-passe",
|
||||
"password.security.block.title": "Alteração de palavra-passe necessária",
|
||||
"password.security.nudge.body": "O nosso sistema detectou que a sua palavra-passe é vulnerável. Recomendamos que a altere para que a sua conta permaneça segura.",
|
||||
"password.security.block.body": "O nosso sistema detectou que a sua palavra-passe é vulnerável. Altere a sua palavra-passe para que a sua conta permaneça segura.",
|
||||
"password.security.close.button": "Fechar",
|
||||
"password.security.redirect.to.reset.password.button": "Redefinir a sua palavra-passe",
|
||||
"login.tpa.authentication.failure": "Lamentamos, mas não tem autorização para aceder à plataforma {platform_name} através deste canal. Pedimos-lhe que entre em contacto com o administrador ou gestor do curso para aceder à plataforma {platform_name}. {line_break}{line_break}Detalhes do erro: {line_break}{error_message}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"progressive.profiling.page.heading": "Algumas perguntas para si vão ajudar a tornar-nos mais espertos.",
|
||||
"optional.fields.information.link": "Saiba mais sobre a forma como utilizamos esta informação.",
|
||||
"optional.fields.submit.button": "Submeter",
|
||||
"optional.fields.skip.button": "Saltar por enquanto",
|
||||
"optional.fields.next.button": "Seguinte",
|
||||
"continue.to.platform": "Continue para {platformName}",
|
||||
"modal.title": "Obrigado por nos informar.",
|
||||
"modal.description": "Pode completar o seu perfil nas configurações em qualquer altura se mudar de ideias.",
|
||||
"welcome.page.error.heading": "Não foi possível atualizar o seu perfil",
|
||||
"welcome.page.error.message": "Ocorreu um erro. Pode completar o seu perfil nas configurações em qualquer altura.",
|
||||
"recommendation.page.title": "Recomendações | {siteName}",
|
||||
"recommendation.page.heading": "Temos algumas recomendações para o ajudar a começar.",
|
||||
"recommendation.skip.button": "Saltar por enquanto",
|
||||
"register.page.title": "Registar | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"registration.fullname.label": "Nome completo",
|
||||
"registration.email.label": "E-mail",
|
||||
"registration.username.label": "Nome de utilizador público",
|
||||
"registration.password.label": "Palavra-passe",
|
||||
"registration.country.label": "País/Região",
|
||||
"registration.opt.in.label": "Concordo que {siteName} pode enviar-me mensagens de marketing.",
|
||||
"help.text.name": "Este nome será usado por quaisquer certificados que conseguir obter.",
|
||||
"help.text.username.1": "O nome que o irá identificar nos seus cursos.",
|
||||
"help.text.username.2": "Isto não pode ser alterado mais tarde.",
|
||||
"help.text.email": "Para ativação de contas e atualizações importantes",
|
||||
"create.account.for.free.button": "Criar uma conta gratuitamente",
|
||||
"registration.other.options.heading": "Ou registe-se com:",
|
||||
"register.institution.login.button": "Credenciais da instituição/campus",
|
||||
"register.institution.login.page.title": "Registo com credenciais da instituição/campus",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"empty.name.field.error": "Insira o seu nome completo",
|
||||
"empty.email.field.error": "Insira o seu email",
|
||||
"empty.username.field.error": "O nome de utilizador deve ter entre 2 e 30 carateres",
|
||||
"empty.password.field.error": "Os critérios de palavra-passe não foram cumpridos",
|
||||
"empty.country.field.error": "Selecione o seu país ou região de residência",
|
||||
"email.do.not.match": "Os endereços de email não correspondem.",
|
||||
"email.invalid.format.error": "Introduzir um endereço de correio electrónico válido",
|
||||
"username.validation.message": "O nome de utilizador deve ter entre 2 e 30 carateres",
|
||||
"name.validation.message": "Introduza um nome válido",
|
||||
"username.format.validation.message": "Os nomes de utilizador só podem conter letras (A-Z, a-z), numerais (0-9), sublinhados (_), e hífenes (-). Os nomes de utilizador não podem conter espaços",
|
||||
"registration.request.failure.header": "Não foi possível criar a sua conta.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.empty.form.submission.error": "Verifique as suas respostas e tente novamente.",
|
||||
"registration.request.server.error": "Ocorreu um erro. Tente actualizar a página, ou verifique a sua ligação à Internet.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.rate.limit.error": "Demasiadas tentativas de registo falhadas. Tente novamente mais tarde.",
|
||||
"registration.tpa.session.expired": "O tempo para registo utilizando {provider} expirou.",
|
||||
"registration.tpa.authentication.failure": "Lamentamos, mas não tem autorização para aceder à plataforma {platform_name} através deste canal. Pedimos-lhe que entre em contacto com o administrador ou gestor do curso para aceder à plataforma {platform_name}. {line_break}{line_break}Detalhes do erro: {line_break}{error_message}",
|
||||
"terms.of.service.and.honor.code": "Termos de Serviço e Código de Honra",
|
||||
"privacy.policy": "Política de Privacidade",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"sign.in": "Sign in",
|
||||
"honor.code": "Código de Honra",
|
||||
"terms.of.service": "Termos do Serviço",
|
||||
"registration.username.suggestion.label": "Sugerido:",
|
||||
"did.you.mean.alert.text": "Quis dizer",
|
||||
"register.page.terms.of.service.and.honor.code": "Ao criar uma conta, concorda com o {tosAndHonorCode} e reconhece que {platformName} e cada\n Membro processa os seus dados pessoais em conformidade com a {privacyPolicy}.",
|
||||
"register.page.honor.code": "Concordo com a {tosAndHonorCode} {platformName}",
|
||||
"register.page.terms.of.service": "Concordo com os {termsOfService} {platformName}",
|
||||
"sign.in": "Iniciar sessão",
|
||||
"reset.password.page.title": "Redefinir Palavra-passe | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password": "Redefinir palavra-passe",
|
||||
"reset.password.page.instructions": "Insira e confirme a sua nova palavra-passe.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
||||
"new.password.label": "Nova palavra-passe",
|
||||
"confirm.password.label": "Confirmar palavra-passe",
|
||||
"passwords.do.not.match": "As palavras-passe não coincidem",
|
||||
"confirm.your.password": "Confirme a sua palavra-passe",
|
||||
"reset.password.failure.heading": "Não conseguimos redefinir sua palavra-passe.",
|
||||
"reset.password.form.submission.error": "Por favor, verifique as suas respostas e tente novamente.",
|
||||
"reset.server.rate.limit.error": "Demasiados pedidos.",
|
||||
"reset.password.success.heading": "Redefinição de palavra-passe concluída",
|
||||
"reset.password.success": "A sua palavra-passe foi redefinida. Inicie sessão na sua conta.",
|
||||
"rate.limit.error": "Ocorreu um erro devido a demasiados pedidos. Por favor, tente novamente após algum tempo."
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
@@ -96,6 +98,7 @@
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"your.career.turning.point": "Your career turning point",
|
||||
"is.here": "is here.",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
@@ -96,6 +98,7 @@
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
@@ -140,6 +143,7 @@
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
|
||||
@@ -1,166 +1,170 @@
|
||||
{
|
||||
"start.learning": "Start learning",
|
||||
"with.site.name": "with {siteName}",
|
||||
"complete.your.profile.1": "Complete",
|
||||
"complete.your.profile.2": "your profile",
|
||||
"welcome.to.platform": "Welcome to {siteName}, {username}!",
|
||||
"institution.login.page.sub.heading": "Choose your institution from the list below",
|
||||
"logistration.sign.in": "Sign in",
|
||||
"logistration.register": "Register",
|
||||
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
|
||||
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
|
||||
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
|
||||
"sso.sign.in.with": "Sign in with {providerName}",
|
||||
"sso.create.account.using": "Create account using {providerName}",
|
||||
"show.password": "Show password",
|
||||
"hide.password": "Hide password",
|
||||
"one.letter": "1 letter",
|
||||
"one.number": "1 number",
|
||||
"eight.characters": "8 characters",
|
||||
"password.sr.only.helping.text": "Password must contain at least 8 characters, at least one letter, and at least one number",
|
||||
"tpa.alert.heading": "Almost done!",
|
||||
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
|
||||
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
|
||||
"registration.using.tpa.form.heading": "Finish creating your account",
|
||||
"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": "个人资料",
|
||||
"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:",
|
||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
|
||||
"forgot.password.page.title": "Forgot Password | {siteName}",
|
||||
"forgot.password.page.heading": "Reset password",
|
||||
"forgot.password.page.instructions": "Please enter your email address below and we will send you an email with instructions on how to reset your password.",
|
||||
"forgot.password.page.invalid.email.message": "Enter a valid email address",
|
||||
"forgot.password.page.email.field.label": "Email",
|
||||
"forgot.password.page.submit.button": "Submit",
|
||||
"forgot.password.error.alert.title.": "We were unable to contact you.",
|
||||
"forgot.password.error.message.title": "An error occurred.",
|
||||
"forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.",
|
||||
"forgot.password.empty.email.field.error": "Enter your email",
|
||||
"forgot.password.email.help.text": "The email address you used to register with {platformName}",
|
||||
"confirmation.message.title": "Check your email",
|
||||
"confirmation.support.link": "contact technical support",
|
||||
"need.help.sign.in.text": "Need help signing in?",
|
||||
"additional.help.text": "For additional help, contact {platformName} support at ",
|
||||
"sign.in.text": "Sign in",
|
||||
"extend.field.errors": "{emailError} below.",
|
||||
"invalid.token.heading": "Invalid password reset link",
|
||||
"invalid.token.error.message": "This password reset link is invalid. It may have been used already. Enter your email below to receive a new link.",
|
||||
"token.validation.rate.limit.error.heading": "Too many requests",
|
||||
"token.validation.rate.limit.error": "An error has occurred because of too many requests. Please try again after some time.",
|
||||
"token.validation.internal.sever.error.heading": "Token validation failure",
|
||||
"token.validation.internal.sever.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"internal.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.",
|
||||
"login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.",
|
||||
"allowed.domain.login.error": "As {allowedDomain} user, You must login with your {allowedDomain} {tpaLink}.",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "The username, email or password you entered is incorrect. You have {remainingAttempts} more sign in\n attempts before your account is temporarily locked.",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "If you've forgotten your password, {resetLink}",
|
||||
"account.locked.out.message.2": "To be on the safe side, you can {resetLink} before trying again.",
|
||||
"login.incorrect.credentials.error.with.reset.link": "The username, email, or password you entered is incorrect. Please try again or {resetLink}.",
|
||||
"login.page.title": "Login | {siteName}",
|
||||
"login.user.identity.label": "Username or email",
|
||||
"login.password.label": "Password",
|
||||
"sign.in.button": "Sign in",
|
||||
"forgot.password": "Forgot password",
|
||||
"institution.login.button": "Institution/campus credentials",
|
||||
"institution.login.page.title": "Sign in with institution/campus credentials",
|
||||
"login.other.options.heading": "Or sign in with:",
|
||||
"non.compliant.password.title": "We recently changed our password requirements",
|
||||
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
|
||||
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
|
||||
"enterprise.login.btn.text": "Company or school credentials",
|
||||
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
|
||||
"email.validation.message": "Enter your username or email",
|
||||
"password.validation.message": "Password criteria has not been met",
|
||||
"account.activation.success.message.title": "Success! You have activated your account.",
|
||||
"account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.",
|
||||
"account.activation.info.message": "This account has already been activated.",
|
||||
"account.activation.error.message.title": "Your account could not be activated",
|
||||
"account.activation.support.link": "contact support",
|
||||
"account.confirmation.success.message.title": "Success! You have confirmed your email.",
|
||||
"account.confirmation.success.message": "Sign in to continue.",
|
||||
"account.confirmation.info.message": "This email has already been confirmed.",
|
||||
"account.confirmation.error.message.title": "Your email could not be confirmed",
|
||||
"tpa.account.link": "{provider} account",
|
||||
"internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"login.rate.limit.reached.message": "Too many failed login attempts. Try again later.",
|
||||
"login.failure.header.title": "We couldn't sign you in.",
|
||||
"contact.support.link": "contact {platformName} support",
|
||||
"login.incorrect.credentials.error": "The username, email, or password you entered is incorrect. Please try again.",
|
||||
"login.form.invalid.error.message": "Please fill in the fields below.",
|
||||
"login.incorrect.credentials.error.reset.link.text": "reset your password",
|
||||
"login.incorrect.credentials.error.before.account.blocked.text": "click here to reset it.",
|
||||
"password.security.nudge.title": "Password security",
|
||||
"password.security.block.title": "Password change required",
|
||||
"password.security.nudge.body": "Our system detected that your password is vulnerable. We recommend you change it so that your account stays secure.",
|
||||
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
|
||||
"password.security.close.button": "Close",
|
||||
"password.security.redirect.to.reset.password.button": "Reset your password",
|
||||
"progressive.profiling.page.title": "Welcome | {siteName}",
|
||||
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
|
||||
"optional.fields.information.link": "Learn more about how we use this information.",
|
||||
"optional.fields.submit.button": "Submit",
|
||||
"optional.fields.skip.button": "Skip for now",
|
||||
"optional.fields.next.button": "Next",
|
||||
"continue.to.platform": "Continue to {platformName}",
|
||||
"modal.title": "Thanks for letting us know.",
|
||||
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
|
||||
"welcome.page.error.heading": "We couldn't update your profile",
|
||||
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
|
||||
"recommendation.page.title": "Recommendations | {siteName}",
|
||||
"recommendation.page.heading": "We have a few recommendations to get you started.",
|
||||
"recommendation.skip.button": "Skip for now",
|
||||
"register.page.title": "Register | {siteName}",
|
||||
"registration.fullname.label": "Full name",
|
||||
"registration.email.label": "Email",
|
||||
"registration.username.label": "Public username",
|
||||
"registration.password.label": "Password",
|
||||
"registration.country.label": "Country/Region",
|
||||
"registration.opt.in.label": "I agree that {siteName} may send me marketing messages.",
|
||||
"help.text.name": "This name will be used by any certificates that you earn.",
|
||||
"help.text.username.1": "The name that will identify you in your courses.",
|
||||
"help.text.username.2": "This can not be changed later.",
|
||||
"help.text.email": "For account activation and important updates",
|
||||
"create.account.for.free.button": "Create an account for free",
|
||||
"registration.other.options.heading": "Or register with:",
|
||||
"register.institution.login.button": "Institution/campus credentials",
|
||||
"register.institution.login.page.title": "Register with institution/campus credentials",
|
||||
"empty.name.field.error": "Enter your full name",
|
||||
"empty.email.field.error": "Enter your email",
|
||||
"empty.username.field.error": "Username must be between 2 and 30 characters",
|
||||
"empty.password.field.error": "Password criteria has not been met",
|
||||
"empty.country.field.error": "Select your country or region of residence",
|
||||
"email.do.not.match": "The email addresses do not match.",
|
||||
"email.invalid.format.error": "Enter a valid email address",
|
||||
"username.validation.message": "Username must be between 2 and 30 characters",
|
||||
"name.validation.message": "Enter a valid name",
|
||||
"username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-). Usernames cannot contain spaces",
|
||||
"registration.request.failure.header": "We couldn't create your account.",
|
||||
"registration.empty.form.submission.error": "Please check your responses and try again.",
|
||||
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
|
||||
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
|
||||
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
|
||||
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
|
||||
"privacy.policy": "Privacy Policy",
|
||||
"honor.code": "Honor Code",
|
||||
"terms.of.service": "Terms of Service",
|
||||
"registration.username.suggestion.label": "Suggested:",
|
||||
"did.you.mean.alert.text": "Did you mean",
|
||||
"register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.",
|
||||
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
|
||||
"sign.in": "Sign in",
|
||||
"reset.password.page.title": "Reset Password | {siteName}",
|
||||
"reset.password": "Reset password",
|
||||
"reset.password.page.instructions": "Enter and confirm your new password.",
|
||||
"new.password.label": "New password",
|
||||
"confirm.password.label": "Confirm password",
|
||||
"passwords.do.not.match": "Passwords do not match",
|
||||
"confirm.your.password": "Confirm your password",
|
||||
"reset.password.failure.heading": "We couldn't reset your password.",
|
||||
"reset.password.form.submission.error": "Please check your responses and try again.",
|
||||
"reset.server.rate.limit.error": "Too many requests.",
|
||||
"reset.password.success.heading": "Password reset complete.",
|
||||
"reset.password.success": "Your password has been reset. Sign in to your account.",
|
||||
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
|
||||
"error.notfound.message": "您访问的地址不存在或有误。请检查URL后重新尝试访问。",
|
||||
"forgot.password.confirmation.message": "我们向 {email} 发送了一封电子邮件,其中包含重置密码的说明。如果您在 1 分钟后没有收到密码重置消息,请确认您输入了正确的电子邮件地址,或者检查您的垃圾邮件文件夹。如果您需要进一步的帮助,点击{supportLink}。",
|
||||
"forgot.password.page.title": "忘记密码 | {siteName}",
|
||||
"forgot.password.page.heading": "重置密码",
|
||||
"forgot.password.page.instructions": "请在下面输入您的电子邮件地址,我们将向您发送一封电子邮件,其中包含有关如何重置密码的说明。",
|
||||
"forgot.password.page.invalid.email.message": "输入一个有效的电子邮件地址",
|
||||
"forgot.password.page.email.field.label": "邮箱",
|
||||
"forgot.password.page.submit.button": "提交",
|
||||
"forgot.password.error.alert.title.": "我们无法联系到您。",
|
||||
"forgot.password.error.message.title": "发生了一个错误。",
|
||||
"forgot.password.request.in.progress.message": "你的前一个请求正在处理中,请稍后再尝试。",
|
||||
"forgot.password.empty.email.field.error": "输入你的电子邮箱",
|
||||
"forgot.password.email.help.text": "您用于注册 {platformName} 的电子邮件地址",
|
||||
"confirmation.message.title": "查收您的邮件",
|
||||
"confirmation.support.link": "联系技术支持",
|
||||
"need.help.sign.in.text": "需要帮助登录?",
|
||||
"additional.help.text": "如需更多帮助,请通过以下方式联系 {platformName} 支持",
|
||||
"sign.in.text": "登录",
|
||||
"extend.field.errors": "{emailError} 如下。",
|
||||
"invalid.token.heading": "密码重置链接无效",
|
||||
"invalid.token.error.message": "此密码重置链接无效。它可能已经被使用过。在下面输入您的电子邮件以接收新链接。",
|
||||
"token.validation.rate.limit.error.heading": "请求过多",
|
||||
"token.validation.rate.limit.error": "由于请求过多而发生错误。请稍后重试。",
|
||||
"token.validation.internal.sever.error.heading": "验证失败",
|
||||
"token.validation.internal.sever.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||
"internal.server.error": "发生了错误。尝试刷新页面,或检查您的互联网连接。",
|
||||
"account.activation.error.message": "出了点问题,请联系{supportLink}解决这个问题。",
|
||||
"login.inactive.user.error": "若要登录,您需要激活您的帐户。{lineBreak} {lineBreak}我们刚刚向 {email} 发送了一个激活链接。如果您没有收到电子邮件,请检查您的垃圾邮件文件夹或 {supportLink}。",
|
||||
"allowed.domain.login.error": "作为 {allowedDomain} 用户,您必须使用 {allowedDomain} {tpaLink} 登录。",
|
||||
"login.incorrect.credentials.error.attempts.text.1": "您输入的用户名、电子邮件或密码不正确。在您的帐户被暂时锁定之前,您还有 {remainingAttempts} 次登录尝试。",
|
||||
"login.incorrect.credentials.error.attempts.text.2": "如果您忘记了密码,{resetLink}",
|
||||
"account.locked.out.message.2": "为了安全起见,您可以先{resetLink}再试一次。",
|
||||
"login.incorrect.credentials.error.with.reset.link": "您输入的用户名、电子邮件或密码不正确。请重试或 {resetLink}。",
|
||||
"login.page.title": "登入 | {siteName}",
|
||||
"login.user.identity.label": "用户名或电子邮件",
|
||||
"login.password.label": "密码",
|
||||
"sign.in.button": "登录",
|
||||
"forgot.password": "忘记密码",
|
||||
"institution.login.button": "机构/校园凭证",
|
||||
"institution.login.page.title": "使用机构/校园凭据登录",
|
||||
"login.other.options.heading": "或登录:",
|
||||
"non.compliant.password.title": "我们最近更改了密码要求",
|
||||
"non.compliant.password.message": "您当前的密码不符合新的安全要求。我们刚刚向与此帐户关联的电子邮件地址发送了密码重置邮件。感谢您帮助我们保护您的数据安全。",
|
||||
"account.locked.out.message.1": "为了保护您的帐户,它已被暂时锁定。请在 30 分钟后重试。",
|
||||
"enterprise.login.btn.text": "单位或学校证书",
|
||||
"username.or.email.format.validation.less.chars.message": "用户名或电子邮件必须至少包含 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": "暂时跳过",
|
||||
"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": "或注册:",
|
||||
"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": "你的意思是",
|
||||
"register.page.terms.of.service.and.honor.code": "创建帐户即表示您同意 {tosAndHonorCode} 并承认 {platformName} 和每位会员根据 {privacyPolicy} 处理您的个人数据。",
|
||||
"register.page.honor.code": "我同意 {platformName} {tosAndHonorCode}",
|
||||
"register.page.terms.of.service": "我同意接受 {platformName} {termsOfService}",
|
||||
"sign.in": "登录",
|
||||
"reset.password.page.title": "重设密码 | {siteName}",
|
||||
"reset.password": "重置密码",
|
||||
"reset.password.page.instructions": "输入并确认你的新密码。",
|
||||
"new.password.label": "新密码",
|
||||
"confirm.password.label": "确认密码",
|
||||
"passwords.do.not.match": "密码不匹配",
|
||||
"confirm.your.password": "确认你的密码",
|
||||
"reset.password.failure.heading": "我们无法重置您的密码。",
|
||||
"reset.password.form.submission.error": "请检查您的回复并重试。",
|
||||
"reset.server.rate.limit.error": "请求过多。",
|
||||
"reset.password.success.heading": "密码重置完成。",
|
||||
"reset.password.success": "您的密码已重置。登录到您的帐户。",
|
||||
"rate.limit.error": "由于请求过多而发生错误。请稍后重试。"
|
||||
}
|
||||
@@ -8,10 +8,9 @@ import {
|
||||
APP_INIT_ERROR, APP_READY, initialize, mergeConfig, subscribe,
|
||||
} from '@edx/frontend-platform';
|
||||
import { ErrorPage } from '@edx/frontend-platform/react';
|
||||
import { messages as paragonMessages } from '@edx/paragon';
|
||||
|
||||
import configuration from './config';
|
||||
import appMessages from './i18n';
|
||||
import messages from './i18n';
|
||||
import MainApp from './MainApp';
|
||||
|
||||
subscribe(APP_READY, () => {
|
||||
@@ -31,8 +30,5 @@ initialize({
|
||||
mergeConfig(configuration);
|
||||
},
|
||||
},
|
||||
messages: [
|
||||
appMessages,
|
||||
paragonMessages,
|
||||
],
|
||||
messages,
|
||||
});
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
@import "~@edx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
@import "@edx/frontend-component-cookie-policy-banner/build/_cookie-policy-banner";
|
||||
@import "sass/style";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
} from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import messages from './messages';
|
||||
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
|
||||
import { updatePathWithQueryParams } from '../data/utils';
|
||||
import useMobileResponsive from '../data/utils/useMobileResponsive';
|
||||
import messages from './messages';
|
||||
|
||||
const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
|
||||
const isMobileView = useMobileResponsive();
|
||||
@@ -29,10 +29,14 @@ const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [isOpen, open, close] = useToggle(true, handlers);
|
||||
const { formatMessage } = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (redirectToResetPasswordPage) {
|
||||
navigate(updatePathWithQueryParams(RESET_PAGE));
|
||||
}
|
||||
}, [redirectToResetPasswordPage, navigate]);
|
||||
|
||||
if (redirectToResetPasswordPage) {
|
||||
return <Redirect to={updatePathWithQueryParams(RESET_PAGE)} />;
|
||||
}
|
||||
return (
|
||||
<ModalDialog
|
||||
title="Password security"
|
||||
|
||||
@@ -13,6 +13,14 @@ import { Helmet } from 'react-helmet';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import AccountActivationMessage from './AccountActivationMessage';
|
||||
import {
|
||||
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
|
||||
} from './data/actions';
|
||||
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
||||
import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
|
||||
import LoginFailureMessage from './LoginFailure';
|
||||
import messages from './messages';
|
||||
import {
|
||||
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration,
|
||||
RenderInstitutionButton, SocialAuthProviders, ThirdPartyAuthAlert,
|
||||
@@ -28,19 +36,10 @@ import {
|
||||
getAllPossibleQueryParams,
|
||||
getTpaHint,
|
||||
getTpaProvider,
|
||||
setSurveyCookie,
|
||||
updatePathWithQueryParams,
|
||||
windowScrollTo,
|
||||
} from '../data/utils';
|
||||
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
||||
import AccountActivationMessage from './AccountActivationMessage';
|
||||
import {
|
||||
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
|
||||
} from './data/actions';
|
||||
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
||||
import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
|
||||
import LoginFailureMessage from './LoginFailure';
|
||||
import messages from './messages';
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -230,16 +229,6 @@ class LoginPage extends React.Component {
|
||||
};
|
||||
tpaAuthenticationError.errorCode = TPA_AUTHENTICATION_FAILURE;
|
||||
}
|
||||
if (this.props.loginResult.success) {
|
||||
setSurveyCookie('login');
|
||||
|
||||
// Fire optimizely events
|
||||
window.optimizely = window.optimizely || [];
|
||||
window.optimizely.push({
|
||||
type: 'event',
|
||||
eventName: 'authn-login-coversion',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from './actions';
|
||||
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
||||
import { RESET_PASSWORD } from '../../reset-password';
|
||||
import { LOGIN_PERSIST_FORM_DATA, LOGIN_REMOVE_PASSWORD_RESET_BANNER, LOGIN_REQUEST } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
loginError: null,
|
||||
|
||||
@@ -3,15 +3,19 @@ import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { RESET_PAGE } from '../../data/constants';
|
||||
import ChangePasswordPrompt from '../ChangePasswordPrompt';
|
||||
|
||||
const IntlChangePasswordPrompt = injectIntl(ChangePasswordPrompt);
|
||||
const history = createMemoryHistory();
|
||||
const mockedNavigator = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...(jest.requireActual('react-router-dom')),
|
||||
useNavigate: () => mockedNavigator,
|
||||
}));
|
||||
|
||||
describe('ChangePasswordPromptTests', () => {
|
||||
let props = {};
|
||||
@@ -55,9 +59,7 @@ describe('ChangePasswordPromptTests', () => {
|
||||
const changePasswordPrompt = mount(
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<Router history={history}>
|
||||
<IntlChangePasswordPrompt {...props} />
|
||||
</Router>
|
||||
<IntlChangePasswordPrompt {...props} />
|
||||
</MemoryRouter>
|
||||
</IntlProvider>,
|
||||
);
|
||||
@@ -67,6 +69,6 @@ describe('ChangePasswordPromptTests', () => {
|
||||
});
|
||||
|
||||
changePasswordPrompt.update();
|
||||
expect(history.location.pathname).toEqual(RESET_PAGE);
|
||||
expect(mockedNavigator).toHaveBeenCalledWith(RESET_PAGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
@@ -20,8 +19,9 @@ import {
|
||||
} from '../data/constants';
|
||||
import LoginFailureMessage from '../LoginFailure';
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
auth.getAuthService = jest.fn();
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthService: jest.fn(),
|
||||
}));
|
||||
|
||||
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { sendPageEvent } from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import renderer from 'react-test-renderer';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
import { COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE } from '../../data/constants';
|
||||
import {
|
||||
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
|
||||
} from '../data/actions';
|
||||
@@ -19,21 +17,19 @@ import { INTERNAL_SERVER_ERROR } from '../data/constants';
|
||||
import LoginFailureMessage from '../LoginFailure';
|
||||
import LoginPage from '../LoginPage';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
|
||||
analytics.sendTrackEvent = jest.fn();
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
auth.getAuthService = jest.fn();
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
sendTrackEvent: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthService: jest.fn(),
|
||||
}));
|
||||
|
||||
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
|
||||
const IntlLoginPage = injectIntl(LoginPage);
|
||||
const mockStore = configureStore();
|
||||
|
||||
describe('LoginPage', () => {
|
||||
mergeConfig({
|
||||
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME,
|
||||
});
|
||||
let props = {};
|
||||
let store = {};
|
||||
let loginFormData = {};
|
||||
@@ -72,7 +68,7 @@ describe('LoginPage', () => {
|
||||
const ssoProvider = {
|
||||
id: 'oa2-apple-id',
|
||||
name: 'Apple',
|
||||
iconClass: null,
|
||||
iconClass: 'apple',
|
||||
iconImage: 'https://openedx.devstack.lms/logo.png',
|
||||
loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard',
|
||||
};
|
||||
@@ -414,7 +410,7 @@ describe('LoginPage', () => {
|
||||
+ 'to the courses you are enrolled in. Sign in to continue.';
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?account_activation_status=success' };
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?account_activation_status=success' };
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('div#account-activation-message').text()).toEqual(activationMessage);
|
||||
@@ -569,8 +565,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
|
||||
ssoProvider.iconImage = null;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find(`button#${ssoProvider.id}`).find('span').text()).toEqual(ssoProvider.name);
|
||||
@@ -592,7 +587,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
|
||||
secondaryProviders.iconImage = null;
|
||||
|
||||
mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
@@ -617,8 +612,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?next=/dashboard&tpa_hint=invalid' };
|
||||
ssoProvider.iconImage = null;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find(`button#${ssoProvider.id}`).find('span#provider-name').text()).toEqual(`${ssoProvider.name}`);
|
||||
@@ -642,8 +636,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?tpa_hint=${ssoProvider.id}` };
|
||||
ssoProvider.iconImage = null;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('button#other-ways-to-sign-in').text()).toEqual('Show me other ways to sign in or register');
|
||||
@@ -666,8 +659,7 @@ describe('LoginPage', () => {
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?tpa_hint=${ssoProvider.id}` };
|
||||
ssoProvider.iconImage = null;
|
||||
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('button#other-ways-to-sign-in').text()).toEqual('Show me other ways to sign in');
|
||||
@@ -675,14 +667,9 @@ describe('LoginPage', () => {
|
||||
|
||||
// ******** miscellaneous tests ********
|
||||
|
||||
it('should render cookie banner', () => {
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find(<CookiePolicyBanner />)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should send page event when login page is rendered', () => {
|
||||
mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
|
||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
|
||||
});
|
||||
|
||||
it('tests that form is only scrollable on form submission', () => {
|
||||
@@ -695,21 +682,6 @@ describe('LoginPage', () => {
|
||||
expect(loginPage.find('LoginPage').state('isSubmitted')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should set login survey cookie', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
login: {
|
||||
...initialState.login,
|
||||
loginResult: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderer.create(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(document.cookie).toMatch(`${getConfig().USER_SURVEY_COOKIE_NAME}=login`);
|
||||
});
|
||||
|
||||
it('should reset login form errors', () => {
|
||||
const errorState = { emailOrUsername: '', password: '' };
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
@@ -12,19 +12,21 @@ import {
|
||||
} from '@edx/paragon';
|
||||
import { ChevronLeft } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { Navigate, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { BaseComponent } from '../base-component';
|
||||
import BaseContainer from '../base-container';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
|
||||
import {
|
||||
tpaProvidersSelector,
|
||||
} from '../common-components/data/selectors';
|
||||
import messages from '../common-components/messages';
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
import { getTpaHint, getTpaProvider, updatePathWithQueryParams } from '../data/utils';
|
||||
import {
|
||||
getTpaHint, getTpaProvider, updatePathWithQueryParams,
|
||||
} from '../data/utils';
|
||||
import { LoginPage } from '../login';
|
||||
import { RegistrationPage } from '../register';
|
||||
import { backupRegistrationForm } from '../register/data/actions';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from './data/actions';
|
||||
import {
|
||||
tpaProvidersSelector,
|
||||
} from './data/selectors';
|
||||
import messages from './messages';
|
||||
|
||||
const Logistration = (props) => {
|
||||
const { selectedPage, tpaProviders } = props;
|
||||
@@ -35,6 +37,7 @@ const Logistration = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const [institutionLogin, setInstitutionLogin] = useState(false);
|
||||
const [key, setKey] = useState('');
|
||||
const navigate = useNavigate();
|
||||
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -44,6 +47,12 @@ const Logistration = (props) => {
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (disablePublicAccountCreation) {
|
||||
navigate(updatePathWithQueryParams(LOGIN_PAGE));
|
||||
}
|
||||
}, [navigate, disablePublicAccountCreation]);
|
||||
|
||||
const handleInstitutionLogin = (e) => {
|
||||
sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
if (typeof e === 'string') {
|
||||
@@ -81,12 +90,11 @@ const Logistration = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseComponent>
|
||||
<BaseContainer>
|
||||
<div>
|
||||
{disablePublicAccountCreation
|
||||
? (
|
||||
<>
|
||||
<Redirect to={updatePathWithQueryParams(LOGIN_PAGE)} />
|
||||
{institutionLogin && (
|
||||
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
|
||||
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
|
||||
@@ -115,7 +123,7 @@ const Logistration = (props) => {
|
||||
</Tabs>
|
||||
))}
|
||||
{ key && (
|
||||
<Redirect to={updatePathWithQueryParams(key)} />
|
||||
<Navigate to={updatePathWithQueryParams(key)} replace />
|
||||
)}
|
||||
<div id="main-content" className="main-content">
|
||||
{selectedPage === LOGIN_PAGE
|
||||
@@ -130,7 +138,7 @@ const Logistration = (props) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</BaseComponent>
|
||||
</BaseContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,22 +2,25 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { COMPLETE_STATE, LOGIN_PAGE } from '../../data/constants';
|
||||
import { backupRegistrationForm } from '../../register/data/actions';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from '../data/actions';
|
||||
import { RenderInstitutionButton } from '../InstitutionLogistration';
|
||||
import Logistration from '../Logistration';
|
||||
import Logistration from './Logistration';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions';
|
||||
import { RenderInstitutionButton } from '../common-components/InstitutionLogistration';
|
||||
import {
|
||||
COMPLETE_STATE, LOGIN_PAGE,
|
||||
} from '../data/constants';
|
||||
import { backupRegistrationForm } from '../register/data/actions';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
sendTrackEvent: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
|
||||
const mockStore = configureStore();
|
||||
const IntlLogistration = injectIntl(Logistration);
|
||||
@@ -41,7 +44,13 @@ describe('Logistration', () => {
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(() => ({
|
||||
userId: 3,
|
||||
username: 'test-user',
|
||||
})),
|
||||
}));
|
||||
|
||||
configure({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
@@ -182,8 +191,8 @@ describe('Logistration', () => {
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
@@ -21,97 +21,135 @@ import {
|
||||
import { Error } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { BaseComponent } from '../base-component';
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import {
|
||||
DEFAULT_REDIRECT_URL, DEFAULT_STATE, FAILURE_STATE,
|
||||
} from '../data/constants';
|
||||
import { getAllPossibleQueryParams } from '../data/utils';
|
||||
import { FormFieldRenderer } from '../field-renderer';
|
||||
import {
|
||||
activateRecommendationsExperiment, RECOMMENDATIONS_EXP_VARIATION, trackRecommendationViewedOptimizely,
|
||||
} from '../recommendations/optimizelyExperiment';
|
||||
import { trackRecommendationsGroup, trackRecommendationsViewed } from '../recommendations/track';
|
||||
import { saveUserProfile } from './data/actions';
|
||||
import { welcomePageSelector } from './data/selectors';
|
||||
import { welcomePageContextSelector } from './data/selectors';
|
||||
import messages from './messages';
|
||||
import ProgressiveProfilingPageModal from './ProgressiveProfilingPageModal';
|
||||
import BaseContainer from '../base-container';
|
||||
import { RedirectLogistration } from '../common-components';
|
||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||
import {
|
||||
COMPLETE_STATE,
|
||||
DEFAULT_REDIRECT_URL,
|
||||
DEFAULT_STATE,
|
||||
FAILURE_STATE,
|
||||
PENDING_STATE,
|
||||
} from '../data/constants';
|
||||
import { getAllPossibleQueryParams, isHostAvailableInQueryParams } from '../data/utils';
|
||||
import { FormFieldRenderer } from '../field-renderer';
|
||||
import {
|
||||
activateRecommendationsExperiment, RECOMMENDATIONS_EXP_VARIATION,
|
||||
} from '../recommendations/optimizelyExperiment';
|
||||
import { trackRecommendationsGroup, trackRecommendationsViewed } from '../recommendations/track';
|
||||
|
||||
const ProgressiveProfiling = (props) => {
|
||||
const {
|
||||
formRenderState, submitState, showError, location,
|
||||
} = props;
|
||||
const enablePersonalizedRecommendations = getConfig().ENABLE_PERSONALIZED_RECOMMENDATIONS;
|
||||
const registrationResponse = location.state?.registrationResult;
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
const [ready, setReady] = useState(false);
|
||||
const [registrationResult, setRegistrationResult] = useState({ redirectUrl: '' });
|
||||
const [values, setValues] = useState({});
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
const [showRecommendationsPage, setShowRecommendationsPage] = useState(false);
|
||||
const {
|
||||
getFieldDataFromBackend,
|
||||
submitState,
|
||||
showError,
|
||||
welcomePageContext,
|
||||
welcomePageContextApiStatus,
|
||||
} = props;
|
||||
const location = useLocation();
|
||||
const registrationEmbedded = isHostAvailableInQueryParams();
|
||||
|
||||
const queryParams = getAllPossibleQueryParams();
|
||||
const authenticatedUser = getAuthenticatedUser();
|
||||
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
||||
const enablePopularAndTrendingRecommendations = getConfig().ENABLE_POPULAR_AND_TRENDING_RECOMMENDATIONS;
|
||||
|
||||
const [registrationResult, setRegistrationResult] = useState({ redirectUrl: '' });
|
||||
const [formFieldData, setFormFieldData] = useState({ fields: {}, extendedProfile: [] });
|
||||
const [canViewWelcomePage, setCanViewWelcomePage] = useState(false);
|
||||
const [values, setValues] = useState({});
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showRecommendationsPage, setShowRecommendationsPage] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
configureAuth(AxiosJwtAuthService, { loggingService: getLoggingService(), config: getConfig() });
|
||||
ensureAuthenticatedUser(DASHBOARD_URL)
|
||||
.then(() => {
|
||||
hydrateAuthenticatedUser().then(() => {
|
||||
setReady(true);
|
||||
setCanViewWelcomePage(true);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
if (registrationResponse) {
|
||||
setRegistrationResult(registrationResponse);
|
||||
}
|
||||
}, [DASHBOARD_URL, registrationResponse]);
|
||||
}, [DASHBOARD_URL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && authenticatedUser?.userId) {
|
||||
const registrationResponse = location.state?.registrationResult;
|
||||
if (registrationResponse) {
|
||||
setRegistrationResult(registrationResponse);
|
||||
setFormFieldData({
|
||||
fields: location.state?.optionalFields.fields,
|
||||
extendedProfile: location.state?.optionalFields.extended_profile,
|
||||
});
|
||||
}
|
||||
}, [location.state]);
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationEmbedded) {
|
||||
getFieldDataFromBackend({ is_welcome_page: true, next: queryParams?.next });
|
||||
}
|
||||
}, [registrationEmbedded, getFieldDataFromBackend, queryParams?.next]);
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationEmbedded && Object.keys(welcomePageContext).includes('fields')) {
|
||||
setFormFieldData({
|
||||
fields: welcomePageContext.fields,
|
||||
extendedProfile: welcomePageContext.extended_profile,
|
||||
});
|
||||
const nextUrl = welcomePageContext.nextUrl ? welcomePageContext.nextUrl : getConfig().SEARCH_CATALOG_URL;
|
||||
setRegistrationResult({ redirectUrl: nextUrl });
|
||||
}
|
||||
}, [registrationEmbedded, welcomePageContext]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canViewWelcomePage && authenticatedUser?.userId) {
|
||||
identifyAuthenticatedUser(authenticatedUser.userId);
|
||||
sendPageEvent('login_and_registration', 'welcome');
|
||||
}
|
||||
}, [authenticatedUser, ready]);
|
||||
}, [authenticatedUser, canViewWelcomePage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationResponse && authenticatedUser?.userId) {
|
||||
const queryParams = getAllPossibleQueryParams(registrationResponse.redirectUrl);
|
||||
if (enablePersonalizedRecommendations && !('enrollment_action' in queryParams)) {
|
||||
if (registrationResult.redirectUrl && authenticatedUser?.userId) {
|
||||
const redirectQueryParams = getAllPossibleQueryParams(registrationResult.redirectUrl);
|
||||
if (enablePopularAndTrendingRecommendations && !('enrollment_action' in redirectQueryParams) && !queryParams?.next) {
|
||||
const userIdStr = authenticatedUser.userId.toString();
|
||||
const variation = activateRecommendationsExperiment(userIdStr);
|
||||
const showRecommendations = variation === RECOMMENDATIONS_EXP_VARIATION;
|
||||
|
||||
trackRecommendationsGroup(variation, authenticatedUser.userId);
|
||||
trackRecommendationViewedOptimizely(userIdStr);
|
||||
setShowRecommendationsPage(showRecommendations);
|
||||
if (!showRecommendations) {
|
||||
trackRecommendationsViewed([], true, authenticatedUser.userId);
|
||||
trackRecommendationsViewed([], '', true, authenticatedUser.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [authenticatedUser, enablePersonalizedRecommendations, registrationResponse]);
|
||||
}, [authenticatedUser, enablePopularAndTrendingRecommendations, registrationResult.redirectUrl, queryParams?.next]);
|
||||
|
||||
if (!location.state || !location.state.registrationResult || formRenderState === FAILURE_STATE) {
|
||||
if (
|
||||
!(location.state?.registrationResult || registrationEmbedded)
|
||||
|| welcomePageContextApiStatus === FAILURE_STATE
|
||||
|| (welcomePageContextApiStatus === COMPLETE_STATE && !Object.keys(welcomePageContext).includes('fields'))
|
||||
) {
|
||||
global.location.assign(DASHBOARD_URL);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
if (!canViewWelcomePage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const optionalFields = location.state.optionalFields.fields;
|
||||
const extendedProfile = location.state.optionalFields.extended_profile;
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
window.history.replaceState(location.state, null, '');
|
||||
const payload = { ...values, extendedProfile: [] };
|
||||
if (Object.keys(extendedProfile).length > 0) {
|
||||
extendedProfile.forEach(fieldName => {
|
||||
if (Object.keys(formFieldData.extendedProfile).length > 0) {
|
||||
formFieldData.extendedProfile.forEach(fieldName => {
|
||||
if (values[fieldName]) {
|
||||
payload.extendedProfile.push({ fieldName, fieldValue: values[fieldName] });
|
||||
}
|
||||
@@ -126,15 +164,21 @@ const ProgressiveProfiling = (props) => {
|
||||
isGenderSelected: !!values.gender,
|
||||
isYearOfBirthSelected: !!values.year_of_birth,
|
||||
isLevelOfEducationSelected: !!values.level_of_education,
|
||||
host: queryParams?.host || '',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleSkip = (e) => {
|
||||
e.preventDefault();
|
||||
window.history.replaceState(props.location.state, null, '');
|
||||
setOpenDialog(true);
|
||||
sendTrackEvent('edx.bi.welcome.page.skip.link.clicked');
|
||||
window.history.replaceState(location.state, null, '');
|
||||
setShowModal(true);
|
||||
sendTrackEvent(
|
||||
'edx.bi.welcome.page.skip.link.clicked',
|
||||
{
|
||||
host: queryParams?.host || '',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const onChangeHandler = (e) => {
|
||||
@@ -145,8 +189,8 @@ const ProgressiveProfiling = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const formFields = Object.keys(optionalFields).map((fieldName) => {
|
||||
const fieldData = optionalFields[fieldName];
|
||||
const formFields = Object.keys(formFieldData.fields).map((fieldName) => {
|
||||
const fieldData = formFieldData.fields[fieldName];
|
||||
return (
|
||||
<span key={fieldData.name}>
|
||||
<FormFieldRenderer
|
||||
@@ -159,14 +203,20 @@ const ProgressiveProfiling = (props) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseComponent showWelcomeBanner>
|
||||
<BaseContainer showWelcomeBanner>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['progressive.profiling.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<ProgressiveProfilingPageModal isOpen={openDialog} redirectUrl={registrationResult.redirectUrl} />
|
||||
{props.shouldRedirect ? (
|
||||
<ProgressiveProfilingPageModal isOpen={showModal} redirectUrl={registrationResult.redirectUrl} />
|
||||
{(props.shouldRedirect && welcomePageContext.nextUrl) && (
|
||||
<RedirectLogistration
|
||||
success
|
||||
redirectUrl={registrationResult.redirectUrl}
|
||||
/>
|
||||
)}
|
||||
{props.shouldRedirect && (
|
||||
<RedirectLogistration
|
||||
success
|
||||
redirectUrl={registrationResult.redirectUrl}
|
||||
@@ -174,10 +224,10 @@ const ProgressiveProfiling = (props) => {
|
||||
educationLevel={values?.level_of_education}
|
||||
userId={authenticatedUser?.userId}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
<div className="mw-xs m-4 pp-page-content">
|
||||
<div>
|
||||
<h2 className="pp-page-heading text-primary">{formatMessage(messages['progressive.profiling.page.heading'])}</h2>
|
||||
<h2 className="pp-page__heading text-primary">{formatMessage(messages['progressive.profiling.page.heading'])}</h2>
|
||||
</div>
|
||||
<hr className="border-light-700 mb-4" />
|
||||
{showError ? (
|
||||
@@ -189,7 +239,7 @@ const ProgressiveProfiling = (props) => {
|
||||
<Form>
|
||||
{formFields}
|
||||
{(getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK) && (
|
||||
<span className="progressive-profiling-support">
|
||||
<span className="pp-page__support-link">
|
||||
<Hyperlink
|
||||
isInline
|
||||
variant="muted"
|
||||
@@ -206,7 +256,7 @@ const ProgressiveProfiling = (props) => {
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="login-button-width"
|
||||
className="pp-page__button-width"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: showRecommendationsPage ? formatMessage(messages['optional.fields.next.button']) : formatMessage(messages['optional.fields.submit.button']),
|
||||
@@ -228,46 +278,49 @@ const ProgressiveProfiling = (props) => {
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</BaseComponent>
|
||||
</BaseContainer>
|
||||
);
|
||||
};
|
||||
|
||||
ProgressiveProfiling.propTypes = {
|
||||
formRenderState: PropTypes.string.isRequired,
|
||||
location: PropTypes.shape({
|
||||
state: PropTypes.shape({
|
||||
registrationResult: PropTypes.shape({
|
||||
redirectUrl: PropTypes.string,
|
||||
}),
|
||||
optionalFields: PropTypes.shape({
|
||||
extended_profile: PropTypes.arrayOf(PropTypes.string),
|
||||
fields: PropTypes.shape({}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
saveUserProfile: PropTypes.func.isRequired,
|
||||
showError: PropTypes.bool,
|
||||
shouldRedirect: PropTypes.bool,
|
||||
submitState: PropTypes.string,
|
||||
welcomePageContext: PropTypes.shape({
|
||||
extended_profile: PropTypes.arrayOf(PropTypes.string),
|
||||
fields: PropTypes.shape({}),
|
||||
nextUrl: PropTypes.string,
|
||||
}),
|
||||
welcomePageContextApiStatus: PropTypes.string,
|
||||
// Actions
|
||||
getFieldDataFromBackend: PropTypes.func.isRequired,
|
||||
saveUserProfile: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
ProgressiveProfiling.defaultProps = {
|
||||
location: { state: {} },
|
||||
shouldRedirect: false,
|
||||
showError: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
welcomePageContext: {},
|
||||
welcomePageContextApiStatus: PENDING_STATE,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
formRenderState: welcomePageSelector(state).formRenderState,
|
||||
shouldRedirect: welcomePageSelector(state).success,
|
||||
submitState: welcomePageSelector(state).submitState,
|
||||
showError: welcomePageSelector(state).showError,
|
||||
});
|
||||
const mapStateToProps = state => {
|
||||
const welcomePageStore = state.welcomePage;
|
||||
|
||||
return {
|
||||
shouldRedirect: welcomePageStore.success,
|
||||
showError: welcomePageStore.showError,
|
||||
submitState: welcomePageStore.submitState,
|
||||
welcomePageContext: welcomePageContextSelector(state),
|
||||
welcomePageContextApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
saveUserProfile,
|
||||
getFieldDataFromBackend: getThirdPartyAuthContext,
|
||||
},
|
||||
)(ProgressiveProfiling);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { SAVE_USER_PROFILE } from './actions';
|
||||
import {
|
||||
DEFAULT_STATE, PENDING_STATE,
|
||||
} from '../../data/constants';
|
||||
import { SAVE_USER_PROFILE } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
extendedProfile: [],
|
||||
fieldDescriptions: {},
|
||||
formRenderState: DEFAULT_STATE,
|
||||
success: false,
|
||||
submitState: DEFAULT_STATE,
|
||||
showError: false,
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
export const storeName = 'welcomePage';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
export const welcomePageSelector = state => ({ ...state[storeName] });
|
||||
export const storeName = 'commonComponents';
|
||||
|
||||
export const commonComponentsSelector = state => ({ ...state[storeName] });
|
||||
|
||||
export const welcomePageContextSelector = createSelector(
|
||||
commonComponentsSelector,
|
||||
commonComponents => ({
|
||||
fields: commonComponents.optionalFields.fields,
|
||||
extended_profile: commonComponents.optionalFields.extended_profile,
|
||||
nextUrl: commonComponents.thirdPartyAuthContext.welcomePageRedirectUrl,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const storeName = 'welcomePage';
|
||||
|
||||
export { default as ProgressiveProfiling } from './ProgressiveProfiling';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
|
||||
@@ -2,39 +2,63 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { identifyAuthenticatedUser, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import * as logging from '@edx/frontend-platform/logging';
|
||||
import { mount } from 'enzyme';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
import { MemoryRouter, mockNavigate, useLocation } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
COMPLETE_STATE, DEFAULT_REDIRECT_URL, FAILURE_STATE, RECOMMENDATIONS,
|
||||
AUTHN_PROGRESSIVE_PROFILING,
|
||||
COMPLETE_STATE, DEFAULT_REDIRECT_URL,
|
||||
EMBEDDED,
|
||||
FAILURE_STATE,
|
||||
RECOMMENDATIONS,
|
||||
} from '../../data/constants';
|
||||
import { activateRecommendationsExperiment } from '../../recommendations/optimizelyExperiment';
|
||||
import { saveUserProfile } from '../data/actions';
|
||||
import ProgressiveProfiling from '../ProgressiveProfiling';
|
||||
|
||||
const IntlProgressiveProfilingPage = injectIntl(ProgressiveProfiling);
|
||||
const mockStore = configureStore();
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('@edx/frontend-platform/logging');
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
sendTrackEvent: jest.fn(),
|
||||
identifyAuthenticatedUser: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
configure: jest.fn(),
|
||||
ensureAuthenticatedUser: jest.fn().mockImplementation(() => Promise.resolve(true)),
|
||||
hydrateAuthenticatedUser: jest.fn().mockImplementation(() => Promise.resolve(true)),
|
||||
getAuthenticatedUser: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/logging', () => ({
|
||||
getLoggingService: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../recommendations/optimizelyExperiment.js', () => ({
|
||||
activateRecommendationsExperiment: jest.fn(),
|
||||
trackRecommendationViewedOptimizely: jest.fn(),
|
||||
RECOMMENDATIONS_EXP_VARIATION: 'welcome_page_recommendations_enabled',
|
||||
}));
|
||||
jest.mock('react-router-dom', () => {
|
||||
const mockNavigation = jest.fn();
|
||||
|
||||
analytics.sendTrackEvent = jest.fn();
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
analytics.identifyAuthenticatedUser = jest.fn();
|
||||
logging.getLoggingService = jest.fn();
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const Navigate = ({ to }) => {
|
||||
mockNavigation(to);
|
||||
return <div />;
|
||||
};
|
||||
|
||||
auth.configure = jest.fn();
|
||||
auth.ensureAuthenticatedUser = jest.fn().mockImplementation(() => Promise.resolve(true));
|
||||
auth.hydrateAuthenticatedUser = jest.fn().mockImplementation(() => Promise.resolve(true));
|
||||
|
||||
const history = createMemoryHistory();
|
||||
return {
|
||||
...jest.requireActual('react-router-dom'),
|
||||
Navigate,
|
||||
mockNavigate: mockNavigation,
|
||||
useLocation: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ProgressiveProfilingTests', () => {
|
||||
mergeConfig({
|
||||
@@ -52,12 +76,16 @@ describe('ProgressiveProfilingTests', () => {
|
||||
};
|
||||
const extendedProfile = ['company'];
|
||||
const optionalFields = { fields, extended_profile: extendedProfile };
|
||||
let props = {};
|
||||
let store = {};
|
||||
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
|
||||
const initialState = {
|
||||
welcomePage: {
|
||||
formRenderState: COMPLETE_STATE,
|
||||
welcomePage: {},
|
||||
commonComponents: {
|
||||
thirdPartyAuthApiStatus: null,
|
||||
optionalFields: {},
|
||||
thirdPartyAuthContext: {
|
||||
welcomePageRedirectUrl: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,11 +98,7 @@ describe('ProgressiveProfilingTests', () => {
|
||||
);
|
||||
|
||||
const getProgressiveProfilingPage = async () => {
|
||||
const progressiveProfilingPage = mount(reduxWrapper(
|
||||
<Router history={history}>
|
||||
<IntlProgressiveProfilingPage {...props} />
|
||||
</Router>,
|
||||
));
|
||||
const progressiveProfilingPage = mount(reduxWrapper(<IntlProgressiveProfilingPage />));
|
||||
await act(async () => {
|
||||
await Promise.resolve(progressiveProfilingPage);
|
||||
await new Promise(resolve => { setImmediate(resolve); });
|
||||
@@ -94,18 +118,15 @@ describe('ProgressiveProfilingTests', () => {
|
||||
},
|
||||
messages: { 'es-419': {}, de: {}, 'en-us': {} },
|
||||
});
|
||||
props = {
|
||||
getFieldData: jest.fn(),
|
||||
location: {
|
||||
state: {
|
||||
registrationResult,
|
||||
optionalFields,
|
||||
},
|
||||
useLocation.mockReturnValue({
|
||||
state: {
|
||||
registrationResult,
|
||||
optionalFields,
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('not should display button "Learn more about how we use this information."', async () => {
|
||||
it('should not display button "Learn more about how we use this information."', async () => {
|
||||
mergeConfig({
|
||||
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: '',
|
||||
});
|
||||
@@ -123,20 +144,15 @@ describe('ProgressiveProfilingTests', () => {
|
||||
expect(progressiveProfilingPage.find('a.pgn__hyperlink').text()).toEqual('Learn more about how we use this information.');
|
||||
});
|
||||
|
||||
it('should render fields returned by backend api', async () => {
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
expect(progressiveProfilingPage.find('#gender').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should make identify call to segment on progressive profiling page', async () => {
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
await getProgressiveProfilingPage();
|
||||
expect(analytics.identifyAuthenticatedUser).toHaveBeenCalledWith(3);
|
||||
expect(analytics.identifyAuthenticatedUser).toHaveBeenCalled();
|
||||
expect(identifyAuthenticatedUser).toHaveBeenCalledWith(3);
|
||||
expect(identifyAuthenticatedUser).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should submit user profile details on form submission', async () => {
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
const formPayload = {
|
||||
gender: 'm',
|
||||
extended_profile: [{ field_name: 'company', field_value: 'test company' }],
|
||||
@@ -150,23 +166,42 @@ describe('ProgressiveProfilingTests', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(saveUserProfile('abc123', formPayload));
|
||||
});
|
||||
|
||||
it('should set host property value empty for non-embedded experience', async () => {
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
const expectedEventProperties = {
|
||||
isGenderSelected: false,
|
||||
isYearOfBirthSelected: false,
|
||||
isLevelOfEducationSelected: false,
|
||||
host: '',
|
||||
};
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
progressiveProfilingPage.find('button.btn-brand').simulate('click');
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.submit.clicked', expectedEventProperties);
|
||||
});
|
||||
|
||||
it('should open modal on pressing skip for now button', async () => {
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
progressiveProfilingPage.find('button.btn-link').simulate('click');
|
||||
expect(progressiveProfilingPage.find('.pgn__modal-content-container').exists()).toBeTruthy();
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked');
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host: '' });
|
||||
});
|
||||
|
||||
it('should send analytic event for support link click', async () => {
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
progressiveProfilingPage.find('.progressive-profiling-support a[target="_blank"]').simulate('click');
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked');
|
||||
progressiveProfilingPage.find('.pp-page__support-link a[target="_blank"]').simulate('click');
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked');
|
||||
});
|
||||
|
||||
it('should show error message when patch request fails', async () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
showError: true,
|
||||
@@ -177,75 +212,191 @@ describe('ProgressiveProfilingTests', () => {
|
||||
expect(progressiveProfilingPage.find('#pp-page-errors').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should redirect to dashboard if no form fields are configured', async () => {
|
||||
store = mockStore({
|
||||
welcomePage: {
|
||||
formRenderState: FAILURE_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL,
|
||||
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
|
||||
};
|
||||
await getProgressiveProfilingPage();
|
||||
expect(window.location.href).toBe(DASHBOARD_URL);
|
||||
});
|
||||
|
||||
describe('Recommendations test', () => {
|
||||
mergeConfig({
|
||||
ENABLE_PERSONALIZED_RECOMMENDATIONS: true,
|
||||
ENABLE_POPULAR_AND_TRENDING_RECOMMENDATIONS: true,
|
||||
});
|
||||
|
||||
it.skip('should redirect to recommendations page if recommendations are enabled', async () => {
|
||||
it('should redirect to recommendations page if recommendations are enabled', async () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
activateRecommendationsExperiment.mockImplementation(() => 'welcome_page_recommendations_enabled');
|
||||
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
expect(progressiveProfilingPage.find('button.btn-brand').text()).toEqual('Next');
|
||||
expect(history.location.pathname).toEqual(RECOMMENDATIONS);
|
||||
expect(mockNavigate).toHaveBeenCalledWith(RECOMMENDATIONS);
|
||||
});
|
||||
|
||||
it('should not redirect to recommendations page if user is on its way to enroll in a course', async () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL,
|
||||
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
|
||||
it('should fire segments recommendations viewed and variation group events', async () => {
|
||||
const viewedEventProperties = {
|
||||
page: 'authn_recommendations',
|
||||
products: [],
|
||||
recommendation_type: '',
|
||||
is_control: true,
|
||||
user_id: 3,
|
||||
};
|
||||
|
||||
const redirectUrl = `${getConfig().LMS_BASE_URL}${DEFAULT_REDIRECT_URL}?enrollment_action=1`;
|
||||
props = {
|
||||
getFieldData: jest.fn(),
|
||||
location: {
|
||||
state: {
|
||||
registrationResult: {
|
||||
redirectUrl,
|
||||
success: true,
|
||||
},
|
||||
optionalFields,
|
||||
},
|
||||
},
|
||||
const groupEventProperties = {
|
||||
page: 'authn_recommendations',
|
||||
variation: 'control',
|
||||
user_id: 3,
|
||||
};
|
||||
|
||||
activateRecommendationsExperiment.mockImplementation(() => 'control');
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'abc123' }));
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
await getProgressiveProfilingPage();
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.recommendations.group', groupEventProperties);
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.recommendations.viewed', viewedEventProperties);
|
||||
});
|
||||
|
||||
it('should not redirect to recommendations page if user is on its way to enroll in a course', async () => {
|
||||
const redirectUrl = `${getConfig().LMS_BASE_URL}${DEFAULT_REDIRECT_URL}?enrollment_action=1`;
|
||||
useLocation.mockReturnValue({
|
||||
state: {
|
||||
registrationResult: {
|
||||
redirectUrl,
|
||||
success: true,
|
||||
},
|
||||
optionalFields,
|
||||
},
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
|
||||
getAuthenticatedUser.mockReturnValue({ userId: 3, username: 'abc123' });
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
expect(progressiveProfilingPage.find('button.btn-brand').text()).toEqual('Submit');
|
||||
expect(window.location.href).toEqual(redirectUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embedded Form Workflow Test', () => {
|
||||
mergeConfig({
|
||||
SEARCH_CATALOG_URL: 'http://localhost/search',
|
||||
});
|
||||
const host = 'http://example.com';
|
||||
|
||||
beforeEach(() => {
|
||||
useLocation.mockReturnValue({
|
||||
state: {},
|
||||
});
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
optionalFields,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should set host property value embedded host for on ramp experience for skip link event', async () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
|
||||
search: `?host=${host}&variant=${EMBEDDED}`,
|
||||
};
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
|
||||
progressiveProfilingPage.find('button.btn-link').simulate('click');
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host });
|
||||
});
|
||||
|
||||
it('should set host property value to host where iframe is embedded for on ramp experience', async () => {
|
||||
const expectedEventProperties = {
|
||||
isGenderSelected: false,
|
||||
isYearOfBirthSelected: false,
|
||||
isLevelOfEducationSelected: false,
|
||||
host: 'http://example.com',
|
||||
};
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
|
||||
search: `?host=${host}`,
|
||||
};
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
progressiveProfilingPage.find('button.btn-brand').simulate('click');
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.submit.clicked', expectedEventProperties);
|
||||
});
|
||||
|
||||
it('should render fields returned by backend API', async () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
|
||||
href: getConfig().BASE_URL,
|
||||
search: `?variant=${EMBEDDED}&host=${host}`,
|
||||
};
|
||||
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
expect(progressiveProfilingPage.find('#gender').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should redirect to dashboard if API call to get form field fails', async () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
|
||||
href: getConfig().BASE_URL,
|
||||
search: `?variant=${EMBEDDED}`,
|
||||
};
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: FAILURE_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
await getProgressiveProfilingPage();
|
||||
expect(window.location.href).toBe(DASHBOARD_URL);
|
||||
});
|
||||
|
||||
it('should redirect to provided redirect url', async () => {
|
||||
const redirectUrl = 'https://redirect-test.com';
|
||||
delete window.location;
|
||||
window.location = {
|
||||
assign: jest.fn().mockImplementation((value) => { window.location.href = value; }),
|
||||
href: getConfig().BASE_URL,
|
||||
search: `?variant=${EMBEDDED}&host=${host}&next=${redirectUrl}`,
|
||||
};
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
optionalFields,
|
||||
thirdPartyAuthContext: {
|
||||
welcomePageRedirectUrl: redirectUrl,
|
||||
},
|
||||
},
|
||||
welcomePage: {
|
||||
...initialState.welcomePage,
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
|
||||
const progressiveProfilingPage = await getProgressiveProfilingPage();
|
||||
progressiveProfilingPage.find('button.btn-brand').simulate('click');
|
||||
expect(window.location.href).toBe(redirectUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
76
src/recommendations/ProductCard/BaseCard/index.jsx
Normal file
76
src/recommendations/ProductCard/BaseCard/index.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Badge, Card, Hyperlink } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { truncateText } from '../../data/utils';
|
||||
|
||||
const BaseCard = ({
|
||||
customHeaderImage,
|
||||
schoolLogo,
|
||||
title,
|
||||
uuid,
|
||||
subtitle,
|
||||
variant,
|
||||
productTypeCopy,
|
||||
footer,
|
||||
handleOnClick,
|
||||
isLoading = false,
|
||||
}) => (
|
||||
<div className="mr-4 recommendation-card" key={`container-${uuid}`}>
|
||||
<Hyperlink
|
||||
target="_blank"
|
||||
className="card-box"
|
||||
showLaunchIcon={false}
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
<Card
|
||||
className={`base-card ${variant}`}
|
||||
variant={variant}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
<Card.ImageCap
|
||||
className="base-card-image-show optanon-category-C0001"
|
||||
src={customHeaderImage}
|
||||
srcAlt={`header image for ${subtitle}`}
|
||||
logoSrc={schoolLogo}
|
||||
logoAlt={`logo for ${subtitle}`}
|
||||
imageLoadingType="lazy"
|
||||
/>
|
||||
<Card.Header
|
||||
className="mt-2"
|
||||
title={truncateText(title)}
|
||||
subtitle={truncateText(subtitle)}
|
||||
/>
|
||||
<Card.Section className="d-flex">
|
||||
<div className="product-badge">
|
||||
<Badge>
|
||||
{productTypeCopy}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="footer-content mt-2">
|
||||
{footer}
|
||||
</div>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</Hyperlink>
|
||||
</div>
|
||||
);
|
||||
|
||||
BaseCard.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
uuid: PropTypes.string.isRequired,
|
||||
footer: PropTypes.element.isRequired,
|
||||
productTypeCopy: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
variant: PropTypes.string.isRequired,
|
||||
customHeaderImage: PropTypes.string.isRequired,
|
||||
schoolLogo: PropTypes.string.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
handleOnClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
BaseCard.defaultProps = {
|
||||
isLoading: false,
|
||||
};
|
||||
export default BaseCard;
|
||||
75
src/recommendations/ProductCard/Footer/index.jsx
Normal file
75
src/recommendations/ProductCard/Footer/index.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
cardFooterMessages,
|
||||
} from '../../messages';
|
||||
|
||||
const ProductCardFooter = ({
|
||||
factoid,
|
||||
quickFacts,
|
||||
courseLength,
|
||||
cardType,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const courseLengthLabel = courseLength > 1 ? 'Courses' : 'Course';
|
||||
|
||||
if (courseLength) {
|
||||
return (
|
||||
<p className="x-small">
|
||||
{intl.formatMessage(
|
||||
cardFooterMessages[
|
||||
'recommendation.product-card.footer-text.number-of-courses'
|
||||
],
|
||||
{ length: courseLength, label: courseLengthLabel },
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (cardType === 'program') {
|
||||
if (quickFacts && quickFacts.length > 0) {
|
||||
const quickFactsCount = quickFacts.length;
|
||||
|
||||
const threeFactsArrangement = [1, 3, 0];
|
||||
const twoFactsArrangement = [0, 2];
|
||||
return (
|
||||
<>
|
||||
{(quickFactsCount > 3 ? threeFactsArrangement : twoFactsArrangement)
|
||||
.map((index) => quickFacts[index])
|
||||
.filter(Boolean)
|
||||
.map((fact, idx) => (
|
||||
<p key={fact.text} className="d-inline-block x-small">
|
||||
{idx > 0 && <span className="p-2">•</span>}
|
||||
{fact && fact.text}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (factoid) {
|
||||
return <p className="x-small">{factoid}</p>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
ProductCardFooter.propTypes = {
|
||||
cardType: PropTypes.string,
|
||||
factoid: PropTypes.string,
|
||||
quickFacts: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
courseLength: PropTypes.number,
|
||||
};
|
||||
|
||||
ProductCardFooter.defaultProps = {
|
||||
cardType: '',
|
||||
factoid: '',
|
||||
quickFacts: [],
|
||||
courseLength: undefined,
|
||||
};
|
||||
|
||||
export default ProductCardFooter;
|
||||
112
src/recommendations/ProductCard/index.jsx
Normal file
112
src/recommendations/ProductCard/index.jsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import BaseCard from './BaseCard';
|
||||
import Footer from './Footer';
|
||||
import { createCodeFriendlyProduct, getVariant, useProductType } from '../data/utils';
|
||||
import {
|
||||
cardBadgesMessages,
|
||||
} from '../messages';
|
||||
import { trackRecommendationClick } from '../track';
|
||||
|
||||
const ProductCard = ({
|
||||
product,
|
||||
userId,
|
||||
position,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const productType = useProductType(product?.courseType, product?.type);
|
||||
|
||||
const variant = getVariant(productType);
|
||||
|
||||
const headerImage = product?.cardImageUrl || product?.image?.src;
|
||||
|
||||
const schoolName = product?.organizationShortCodeOverride
|
||||
|| product?.owners?.[0]?.name
|
||||
|| product?.authoringOrganizations?.[0]?.name
|
||||
|| product?.partner;
|
||||
const schoolLogo = product?.organizationLogoOverrideUrl
|
||||
|| product?.logoFilename
|
||||
|| product?.authoringOrganizations?.[0]?.logoImageUrl
|
||||
|| product?.owners?.[0]?.logoImageUrl;
|
||||
|
||||
const { owners } = product;
|
||||
const multipleSchoolNames = [];
|
||||
const isMultipleOwner = owners?.length > 1;
|
||||
|
||||
if ((owners?.length > 1)) {
|
||||
owners.forEach((owner, index, arr) => {
|
||||
let school;
|
||||
if (index === arr.length - 1) {
|
||||
school = (
|
||||
<span key={owner.name}>{owner.name}</span>
|
||||
);
|
||||
} else {
|
||||
school = (
|
||||
<>
|
||||
<span key={owner.name}>{owner.name}</span>
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
multipleSchoolNames.push(school);
|
||||
});
|
||||
}
|
||||
|
||||
const productTypeCopy = formatMessage(
|
||||
cardBadgesMessages[
|
||||
`recommendation.product-card.pill-text.${createCodeFriendlyProduct(productType)}`
|
||||
],
|
||||
);
|
||||
const handleCardClick = () => {
|
||||
trackRecommendationClick(
|
||||
product,
|
||||
position + 1,
|
||||
false,
|
||||
userId,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseCard
|
||||
customHeaderImage={headerImage}
|
||||
schoolLogo={isMultipleOwner ? '' : schoolLogo}
|
||||
title={product.title}
|
||||
uuid={product.uuid}
|
||||
key={product.uuid}
|
||||
subtitle={isMultipleOwner ? multipleSchoolNames : schoolName}
|
||||
productTypeCopy={productTypeCopy}
|
||||
productType={productType}
|
||||
variant={variant}
|
||||
footer={(
|
||||
<Footer
|
||||
quickFacts={product.degree?.quickFacts}
|
||||
externalUrl={product.additionalMetadata?.externalUrl
|
||||
|| product.degree?.additionalMetadata?.externalUrl}
|
||||
courseLength={product.courses?.length}
|
||||
isSubscriptionView={!!product.subscriptionEligible}
|
||||
is2UDegreeProgram={product.is2UDegreeProgram}
|
||||
cardType={product.cardType}
|
||||
/>
|
||||
)}
|
||||
handleOnClick={handleCardClick}
|
||||
isSubscriptionView={!!product.subscriptionEligible}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ProductCard.propTypes = {
|
||||
product: PropTypes.shape([
|
||||
PropTypes.shape({}),
|
||||
]).isRequired,
|
||||
userId: PropTypes.number.isRequired,
|
||||
position: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
ProductCard.defaultProps = {
|
||||
};
|
||||
export default ProductCard;
|
||||
@@ -1,87 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Card, Hyperlink } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { trackRecommendationCardClickOptimizely } from './optimizelyExperiment';
|
||||
import { trackRecommendationsClicked } from './track';
|
||||
|
||||
const RecommendationCard = (props) => {
|
||||
const { recommendation, position, userId } = props;
|
||||
const showPartnerLogo = recommendation.owners.length === 1;
|
||||
|
||||
const getOwners = () => {
|
||||
if (recommendation.owners.length === 1) {
|
||||
return recommendation.owners[0].key;
|
||||
}
|
||||
|
||||
let keys = '';
|
||||
recommendation.owners.forEach((owner) => {
|
||||
keys += `${owner.key }, `;
|
||||
});
|
||||
return keys.slice(0, -2);
|
||||
};
|
||||
|
||||
const handleCardClick = () => {
|
||||
trackRecommendationCardClickOptimizely(userId?.toString());
|
||||
trackRecommendationsClicked(
|
||||
recommendation.courseKey,
|
||||
false,
|
||||
position + 1,
|
||||
userId,
|
||||
recommendation.marketingUrl,
|
||||
recommendation.recommendationType || 'algolia',
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mr-4 recommendation-card">
|
||||
<Hyperlink
|
||||
destination={recommendation.marketingUrl}
|
||||
target="_blank"
|
||||
className="card-box"
|
||||
showLaunchIcon={false}
|
||||
onClick={handleCardClick}
|
||||
>
|
||||
<Card isClickable>
|
||||
<Card.ImageCap
|
||||
src={recommendation.cardImageUrl}
|
||||
srcAlt="Card image"
|
||||
logoSrc={showPartnerLogo ? recommendation.owners[0].logoImageUrl : ''}
|
||||
logoAlt="Card logo"
|
||||
/>
|
||||
<Card.Header
|
||||
title={recommendation.title}
|
||||
subtitle={getOwners()}
|
||||
/>
|
||||
<Card.Section />
|
||||
<Card.Footer textElement={<small className="footer-text">Course</small>} />
|
||||
</Card>
|
||||
</Hyperlink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RecommendationCard.propTypes = {
|
||||
recommendation: PropTypes.shape({
|
||||
courseKey: PropTypes.string.isRequired,
|
||||
activeRunKey: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
cardImageUrl: PropTypes.string.isRequired,
|
||||
owners: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
logoImageUrl: PropTypes.string.isRequired,
|
||||
})),
|
||||
marketingUrl: PropTypes.string.isRequired,
|
||||
recommendationType: PropTypes.string,
|
||||
}).isRequired,
|
||||
position: PropTypes.number.isRequired,
|
||||
userId: PropTypes.number,
|
||||
};
|
||||
|
||||
RecommendationCard.defaultProps = {
|
||||
userId: null,
|
||||
};
|
||||
|
||||
export default RecommendationCard;
|
||||
@@ -1,48 +1,32 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Container } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import RecommendationCard from './RecommendationCard';
|
||||
import ProductCard from './ProductCard';
|
||||
|
||||
const RecommendationsList = (props) => {
|
||||
const { title, recommendations, userId } = props;
|
||||
const { recommendations, userId } = props;
|
||||
|
||||
return (
|
||||
<Container id="course-recommendations" size="lg" className="recommendations-container">
|
||||
<h2 className="text-sm-center mb-4 text-left recommendations-heading">
|
||||
{title}
|
||||
</h2>
|
||||
<div className="d-flex card-list">
|
||||
{
|
||||
recommendations.map((recommendation, idx) => (
|
||||
<RecommendationCard
|
||||
key={recommendation.activeRunKey}
|
||||
recommendation={recommendation}
|
||||
<div className="d-flex recommendations-container__card-list">
|
||||
{
|
||||
recommendations.map((recommendation, idx) => (
|
||||
<span key={recommendation.uuid}>
|
||||
<ProductCard
|
||||
product={recommendation}
|
||||
position={idx}
|
||||
userId={userId}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Container>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RecommendationsList.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
recommendations: PropTypes.arrayOf(PropTypes.shape({
|
||||
courseKey: PropTypes.string.isRequired,
|
||||
activeRunKey: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
cardImageUrl: PropTypes.string.isRequired,
|
||||
owners: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
logoImageUrl: PropTypes.string.isRequired,
|
||||
})),
|
||||
marketingUrl: PropTypes.string.isRequired,
|
||||
recommendationType: PropTypes.string,
|
||||
uuid: PropTypes.string,
|
||||
})),
|
||||
userId: PropTypes.number,
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user