Compare commits

...

259 Commits

Author SHA1 Message Date
Kyr
1e0be4e581 fix: username suggestions alignment (#1278)
Co-authored-by: Kyrylo Hudym-Levkovych <kyr.hudym@kyrs-MacBook-Pro.local>
2024-12-23 18:54:43 +05:00
Stanislav
8b2160388f fix: Prevent wrong appearance of skeleton after second tab click (#1224)
Co-authored-by: Stanislav Lunyachek <lunyachek@MacBook-Pro-M1.local>
2024-12-23 16:40:30 +05:00
Stanislav Lunyachek
ab357704f8 feat: Hide preloaders for third party auth providers if they are disabled 2024-04-19 10:56:58 +05:00
Ihor Romaniuk
2eab85e960 fix: Update custom SSO buttons to use brand colors (#1165) (#1208)
Description: Update custom SSO buttons to use brand colors
VAN-1838

Co-authored-by: Blue <ahtesham-quraish@users.noreply.github.com>
2024-03-31 23:11:48 -07:00
Dmytro
9f503eb5ef fix: Registration with password that doesn't meet the requirements (#1182)
Co-authored-by: Dima Alipov <dimaalipov@MacBook-Pro-Dima.local>
2024-03-18 12:56:15 +05:00
Attiya Ishaque
c2a55e125c fix: browser header showing null and replace authn with Authentication 2024-03-04 13:44:58 -03:00
Attiya Ishaque
abea379eb0 fix: browser header showing null (#1170) 2024-02-27 12:52:16 +05:00
Stanislav
48122ff99d fix: Missed favicon in Safari (#1080)
Co-authored-by: Stanislav Lunyachek <lunyachek@MacBook-Pro-M1.local>
2023-12-08 10:07:31 +05:00
vladislavkeblysh
3153cff4ff fix: fixed spacing in tablet version (quince.master) (#1107) 2023-11-28 11:41:06 +05:00
Ihor Romaniuk
919d98df79 fix: content centering and z-index position on adaptation (#1093) 2023-11-27 12:26:56 +05:00
Stanislav
c7adcecb8a fix: Enabling the ability to log in with a username consisting of 2 characters (#1098)
Co-authored-by: Stanislav Lunyachek <lunyachek@MacBook-Pro-M1.local>
2023-11-22 16:38:24 +05:00
Ghassan Maslamani
db4eeac266 fix: form submission when country is not requried (#1099)
When country is not required to submit, the displayValue check
will always return true. This fixes it by only check it if it's
needed through the config `SHOW_CONFIGURABLE_EDX_FIELDS`

This issue might has been discoverd at edx.org becuase edx.org
requires the Country to be filled when creating an account,
however this is not the case for Open edX by default, hence the
issue reported below

Ref: openedx/wg-build-test-release/issues/318
2023-11-17 09:25:29 -03:00
Syed Ali Abbas Zaidi
60e2119116 chore: bump frontend-platform (#1076) 2023-10-18 11:23:16 +05:00
Zainab Amir
7b4714a22a feat: fix recommendations card subtitle (#1070) 2023-10-06 15:58:29 +05:00
Zainab Amir
332d6abee7 feat: updated default value for USER_RETENTION_COOKIE_NAME (#1059)
Moved the value to internal configuration

VAN-1624
2023-09-12 14:10:43 +05:00
Zainab Amir
4df13cf0b7 feat: remove optimizely event (#1051)
VAN-1624
2023-09-12 13:32:29 +05:00
Syed Sajjad Hussain Shah
512deae883 fix: recs skip btn event gets cancelled on redirection (#1058) 2023-09-08 15:37:33 +05:00
Syed Sajjad Hussain Shah
2d11477037 fix: post registration recommendations eventing (#1057) 2023-09-08 15:04:38 +05:00
Zainab Amir
5e15969f4a Recommendations v2 (#1040)
* feat: add personalized recommendations (#1024)

* use Algolia for personalized recommendations
* show personalized recommendations to use that have consented
to functional cookies
* update tests

VAN-1599

* Revert "fix: special characters in redirect url getting decoded to space (#1029)" (#1030)

This reverts commit fc62241332.

* feat: update recommendations page design (#1036)

VAN-1598

* feat: add events for recommendations (#1039)

* feat: remove static recommendations

---------

Co-authored-by: Syed Sajjad Hussain Shah <52817156+syedsajjadkazmii@users.noreply.github.com>
2023-09-08 12:08:41 +05:00
Syed Sajjad Hussain Shah
37e811d7e5 fix: updated optional fields check based on updated response (#1055) 2023-09-07 09:31:19 +05:00
Mashal Malik
a35a1d1ba6 refactor: updated lock file version check to use new workflow (#1048)
Co-authored-by: Muhammad Abdullah Waheed <42172960+abdullahwaheed@users.noreply.github.com>
2023-09-06 14:58:18 +05:00
Syed Sajjad Hussain Shah
41a9c89d71 fix: password validation with correct password value on icon blur (#1054) 2023-09-05 17:52:35 +05:00
Syed Sajjad Hussain Shah
d469102cee refactor: registration component refactoring (#1050)
* refactor: registration component refactoring

* fix: refactored constants
2023-09-05 13:52:25 +05:00
edx-transifex-bot
c685bdd373 chore(i18n): update translations (#1052)
Co-authored-by: Jenkins <sre+jenkins@edx.org>
2023-09-04 16:23:08 +05:00
Emad Rad
daa7ae4d73 feat: Add persian language (#1020)
Co-authored-by: Zainab Amir <zainab.amir@arbisoft.com>
2023-08-30 13:09:40 +05:00
Zainab Amir
c5caaeba60 feat: add registration conversion event (#1049) 2023-08-29 15:22:07 +05:00
Syed Sajjad Hussain Shah
a473d79554 fix: forms data persistence issue (#1046) 2023-08-25 17:48:49 +05:00
Zainab Amir
1b5aa106ab feat: remove hydrate call (#1043) 2023-08-24 12:29:48 +05:00
renovate[bot]
d8b5653224 chore(deps): update dependency @edx/frontend-build to v12.9.8 2023-08-15 22:16:13 +00:00
renovate[bot]
4cc4ff6c4b fix(deps): update react-router monorepo to v6.15.0 2023-08-15 20:09:53 +00:00
renovate[bot]
48a3c57e5f fix(deps): update dependency @edx/paragon to v20.46.2 2023-08-15 15:29:28 +00:00
Syed Ali Abbas Zaidi
efdefc300e feat: upgrade react router to v6 (#936) 2023-08-15 17:23:34 +05:00
Syed Sajjad Hussain Shah
9730a4f55d Revert "fix: special characters in redirect url getting decoded to space (#1029)" (#1030)
This reverts commit fc62241332.
2023-08-15 15:45:31 +05:00
Syed Sajjad Hussain Shah
fc62241332 fix: special characters in redirect url getting decoded to space (#1029) 2023-08-15 13:24:27 +05:00
renovate[bot]
0846001b6d fix(deps): update dependency regenerator-runtime to v0.14.0 2023-08-08 01:57:57 +00:00
renovate[bot]
90658722e1 fix(deps): update dependency @edx/paragon to v20.46.0 2023-08-04 23:08:50 +00:00
renovate[bot]
240752c6cd fix(deps): update dependency @edx/paragon to v20.45.7 2023-08-04 04:27:28 +00:00
renovate[bot]
429d4547e4 fix(deps): update font awesome to v6.4.2 2023-08-03 08:52:23 +00:00
renovate[bot]
e278b5f74a chore(deps): update dependency @edx/frontend-build to v12.9.4 2023-08-02 19:32:04 +00:00
renovate[bot]
a723058bc1 fix(deps): update dependency @edx/paragon to v20.45.6 2023-08-01 16:20:29 +00:00
Shahbaz Shabbir
59fa7d5de3 fix: update username suggestions placement 2023-08-01 19:37:07 +05:00
renovate[bot]
60578189bd fix(deps): update dependency @edx/frontend-platform to v4.6.1 2023-08-01 14:15:25 +00:00
Syed Sajjad Hussain Shah
82cd11e01e refactor: refactor recs dir (#1012) 2023-07-31 16:36:47 +05:00
Blue
4a10540d4a fix: redirection issue fix (#1011)
Description:
Fixed redirection issue
VAN-1573
2023-07-31 15:29:05 +05:00
Blue
aeda262fb0 fix: add location resitriction for popular and trending courses (#1010)
Description:
Filter courses on the basis of location

VAN-1573
2023-07-31 14:47:22 +05:00
Mubbshar Anwar
dff3903617 fix: track event fire multiple times (#1009)
The recommendations group event was firing multiple times due to useEffect dependency because registrationResult is setting in above two useEffects.

VAN-1569
2023-07-31 13:00:53 +05:00
renovate[bot]
1399caf003 chore(deps): update dependency eslint-plugin-import to v2.28.0 2023-07-28 10:31:50 +00:00
renovate[bot]
2dfb6bc528 fix(deps): update dependency core-js to v3.32.0 2023-07-28 01:38:08 +00:00
Mubbshar Anwar
a392395876 feat: Add segments events (#1001)
Add segments events to track popular and trending recommendations stats.

VAN-1569

Co-authored-by: Syed Sajjad Hussain Shah <52817156+syedsajjadkazmii@users.noreply.github.com>
2023-07-27 17:04:08 +05:00
renovate[bot]
5542311c95 chore(deps): update dependency jest to v29.6.2 (#1004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-27 15:13:30 +05:00
Zainab Amir
21e6bb6eec feat: update static recommendation placement (#1002)
* feat: update static recommendation placement
* refactor: clean recommendation code (#1003)

---------

Co-authored-by: Syed Sajjad Hussain Shah <52817156+syedsajjadkazmii@users.noreply.github.com>
2023-07-27 15:04:56 +05:00
Shahbaz Shabbir
bfa7874108 fix: add popular and trending products lists (#1000) 2023-07-27 10:54:57 +05:00
mubbsharanwar
423958c899 feat: setup optimizely experiment
Setup optimizely experiment for popular and trending recommendations for post registration experience

VAN-1566
2023-07-26 18:30:38 +05:00
Blue
cb380a2031 feat: design change for recommendations (#997)
Description:

Design change for recommendations in which covered all the card type

VAN-1564
2023-07-26 10:49:47 +05:00
renovate[bot]
f4e89efdb4 chore(deps): update dependency @edx/frontend-build to v12.9.3 2023-07-25 12:57:49 +00:00
Mubbshar Anwar
f5cb7a1dbd feat: param based redirection (#993)
On welcome page CTA click redirection would be based on next query param.

VAN-1535
2023-07-25 16:03:41 +05:00
Syed Sajjad Hussain Shah
72e601948c fix: fix accessibility issues in register embedded experience (#996)
VAN-1541, VAN-1551, VAN-1543
2023-07-24 16:18:30 +05:00
renovate[bot]
29e30981ae fix(deps): update dependency @edx/paragon to v20.45.5 2023-07-21 17:03:18 +00:00
Zainab Amir
06a61e6a22 feat: add user retention cookie (#992) 2023-07-21 11:16:14 +05:00
renovate[bot]
1c83020b43 fix(deps): update dependency algoliasearch to v4.19.1 2023-07-20 16:50:53 +00:00
Mubbshar Anwar
fa4a0ac2d5 feat: param based cta and redirection (#984)
Register CTA label and authn redirection would be based on query params.

VAN-1535
2023-07-20 11:43:32 +05:00
renovate[bot]
2addf57cbd chore(deps): update dependency @edx/frontend-build to v12.9.2 2023-07-19 21:50:55 +00:00
renovate[bot]
d521fd20ec fix(deps): update dependency @edx/paragon to v20.45.4 2023-07-19 15:32:23 +00:00
renovate[bot]
38d44ac586 fix(deps): update dependency algoliasearch to v4.19.0 2023-07-18 22:47:36 +00:00
renovate[bot]
4768306f53 chore(deps): update dependency @edx/frontend-build to v12.9.1 2023-07-18 18:39:05 +00:00
renovate[bot]
6c6b527dfc fix(deps): update dependency @edx/paragon to v20.45.3 2023-07-18 12:39:08 +00:00
renovate[bot]
e14c9bd1b7 chore(deps): update dependency @edx/frontend-build to v12.8.67 2023-07-18 07:02:32 +00:00
dependabot[bot]
a2bdc4031b chore(deps): bump semver from 5.7.1 to 5.7.2 (#981)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-17 19:04:57 +05:00
renovate[bot]
8f38eb9e3a fix(deps): update dependency @edx/paragon to v20.45.2 2023-07-17 13:38:21 +00:00
renovate[bot]
c22aa58904 chore(deps): update dependency @edx/frontend-build to v12.8.66 2023-07-17 10:59:27 +00:00
edx-transifex-bot
067bddf892 chore(i18n): update translations (#978)
* chore(i18n): update translations
* feat: add base container tests (#979)

---------

Co-authored-by: Jenkins <sre+jenkins@edx.org>
Co-authored-by: Zainab Amir <zainab.amir@arbisoft.com>
2023-07-17 13:50:14 +05:00
Syed Sajjad Hussain Shah
7e4bccbc29 fix: fix rebrand experiment issue (#976) 2023-07-14 19:14:58 +05:00
renovate[bot]
f39bb35dc8 chore(deps): update dependency @edx/frontend-build to v12.8.64 2023-07-14 03:27:26 +00:00
renovate[bot]
0d760c04b7 chore(deps): update dependency @edx/frontend-build to v12.8.63 2023-07-13 00:22:45 +00:00
renovate[bot]
6f113542f5 chore(deps): update dependency @edx/frontend-build to v12.8.62 2023-07-12 11:26:38 +00:00
renovate[bot]
1e4c342703 chore(deps): update dependency @edx/frontend-build to v12.8.61 2023-07-11 15:33:06 +00:00
renovate[bot]
3ce0585d7e fix(deps): update dependency core-js to v3.31.1 2023-07-11 06:59:31 +00:00
renovate[bot]
5bf6dd6361 fix(deps): update dependency algoliasearch to v4.18.0 2023-07-11 03:33:41 +00:00
renovate[bot]
929abdff69 chore(deps): update dependency jest to v29.6.1 2023-07-11 00:57:30 +00:00
renovate[bot]
f295d69e76 fix(deps): update dependency react-loading-skeleton to v3.3.1 2023-07-10 21:54:41 +00:00
renovate[bot]
32a4c55e4a chore(deps): update dependency babel-plugin-formatjs to v10.5.3 2023-07-10 20:32:20 +00:00
renovate[bot]
615ba91bdb chore(deps): update dependency @edx/frontend-build to v12.8.60 2023-07-10 16:39:31 +00:00
renovate[bot]
dfb2f89a36 fix(deps): update dependency @edx/paragon to v20.45.0 2023-07-10 13:44:30 +00:00
Mashal Malik
c9783234cc feat: update react & react-dom to v17 (#938)
* feat: update react & react-dom to v17

* chore: add enzyme adapter pkg to devDep

* refactor: updated paragon & snapshots

* build: update frontend-platform

---------

Co-authored-by: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com>
2023-07-10 15:48:14 +05:00
Zainab Amir
0513e6c2de chore: add configs to env file (#962) 2023-06-28 23:40:41 +05:00
Zainab Amir
3cdc0234ef fix: height of image for larger layout (#961) 2023-06-28 20:06:57 +05:00
Zainab Amir
e06d12be07 feat: add optimizely web to authn (#960) 2023-06-28 16:52:25 +05:00
Zainab Amir
56394881fc feat: Add image layout for base container (#959)
- Added image layout base container
- Added config for optimizely experiment
2023-06-28 16:05:26 +05:00
Zainab Amir
61056240c4 refactor: base component name and folder structure (#958) 2023-06-27 14:17:04 +05:00
Zainab Amir
a59e7c548c Revert "feat: hide privacy banner for embedded form (#954)" (#955)
This reverts commit f63d7674e2.
2023-06-23 10:20:44 +05:00
Zainab Amir
f63d7674e2 feat: hide privacy banner for embedded form (#954) 2023-06-22 15:14:18 +05:00
Zainab Amir
fa3a70e9a9 Revert "feat: fire identify call and register event" (#953)
* Revert "feat: fire identify call and register event (#951)
2023-06-22 11:59:22 +05:00
Syed Sajjad Hussain Shah
b817a8d122 fix: hide zendesk widget for embedded experience (#952) 2023-06-21 17:39:07 +05:00
Syed Sajjad Hussain Shah
2a6668cef3 feat: fire identify call and register event (#951)
VAN-1499
2023-06-21 15:36:58 +05:00
Zainab Amir
a802821ae9 feat: keep username suggestion for embedded experience (#950)
* feat: keep username suggestion for embedded experience
2023-06-21 10:30:26 +05:00
Syed Sajjad Hussain Shah
9e13141f6b fix: render embedded experience through pathname (#949) 2023-06-19 13:44:30 +05:00
Blue
4b64ce2534 fix: make host param compulsory (#948)
Description

Make host param compulsory and host should grab from query params when calling postMessage api

VAN-1485
2023-06-16 14:44:34 +05:00
Blue
c550069e11 fix: avoid validations on blur event (#946)
Description:

When authn is embedded in iframe then validations should not runs on blur event it should only run when button is clicked.

VAN-1481
2023-06-16 10:33:59 +05:00
Mubbshar Anwar
1e10e9c89c feat: registration conversion events (#942)
Passing variant property to registration and progressive profiling events to calculate on ramp conversion rate.

VAN-1478
2023-06-16 09:42:04 +05:00
Zainab Amir
cd6c1c0e42 feat: update welcome page logic (#947) 2023-06-15 17:09:52 +05:00
Zainab Amir
5edcee9eb9 feat: allow welcome page to load for embedded experience (#944) 2023-06-15 15:46:34 +05:00
Syed Sajjad Hussain Shah
d41c06b1fd feat: allow embedded experience to open register page for authenticated users (#945)
VAN-1482
2023-06-15 14:42:09 +05:00
Blue
2a2c5abc81 fix: redirect user to welcome page (#939) (#941)
Description
Redirect the user to welcome page after registration, as Authn is embedded in iframe which is located in Hubspot so we call window.postMessage function from Authn which let the Hubspot knows about redirect URL and then reload the welcome page in parent window.

VAN-1474
2023-06-15 12:29:04 +05:00
Syed Sajjad Hussain Shah
ccdd648603 feat: add iframe resizer script (#943)
VAN-1475
2023-06-14 14:43:09 +05:00
Blue
5c1ea04970 fix: change registration form for onramp (#939)
Description:
In this we have changed the regirstation form on the basis of embed query param and hide the multiple things

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

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

Need to implement BEM convention for reset password page

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: fix eslint tests

---------

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

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

VAN-1349
2023-04-12 11:53:53 +05:00
Blue
ef66eb1c31 Merge pull request #800 from openedx/ahtesham/VAN-1348/replace-getqueryparam-with-useparams-hooks
Ahtesham/van 1348/replace getqueryparam with useparams hooks
2023-04-11 10:35:13 +05:00
ahtesham-quraish
ec8b256852 fix: remove depricated getQueryParams
Replace getQueryParams function with getAllPossibleQueryParams function

VAN-1348
2023-04-11 10:27:55 +05:00
renovate[bot]
5a715b2fb5 chore(deps): update dependency jest to v29 2023-04-11 03:13:35 +00:00
renovate[bot]
e80578e682 fix(deps): update dependency core-js to v3.30.0 2023-04-10 21:31:39 +00:00
renovate[bot]
155a73dc39 fix(deps): update dependency @edx/paragon to v20.30.1 2023-04-10 21:26:12 +00:00
renovate[bot]
f5d0b50d90 chore(deps): update dependency @edx/frontend-build to v12.8.6 2023-04-10 18:04:55 +00:00
Shahbaz Shabbir
b0745de672 fix: change keys to camelCase for mfe_context response (#835) 2023-04-10 17:24:28 +05:00
Shahbaz Shabbir
d54fdbf84f fix: issue of missing user_id on welcome page events (#828) 2023-04-10 17:15:03 +05:00
Blue
0a6432c393 Merge pull request #840 from openedx/ahtesham/VAN-1353/sso-notification-bottom-mergin
fix: margin issue for sso baner
2023-04-10 16:08:23 +05:00
ahtesham-quraish
9e91c382b3 fix: margin issue for sso baner
FIX margin issue for SSO banner on login page.

VAN-1353
2023-04-10 14:39:53 +05:00
renovate[bot]
2cf24761c0 fix(deps): update dependency react-loading-skeleton to v3 (#498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-10 13:56:08 +05:00
Syed Sajjad Hussain Shah
c2bdc31a03 fix: ripple effect of skip_registeration_form if country not coming from ip (#838)
Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-04-10 12:41:03 +05:00
Syed Sajjad Hussain Shah
9d487d7b61 fix: render tpa pipeline error from django messages on mfe (#829)
VAN-1339

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-04-10 12:36:40 +05:00
Attiya Ishaque
a2ab6c196a fix: fix design issue on progressive profiling page (#833) 2023-04-10 12:12:38 +05:00
Mashal Malik
6a5b02e8ad feat: upgraded to node v18, added .nvmrc and updated workflows (#781) 2023-04-10 11:51:45 +05:00
renovate[bot]
e76f214024 fix(deps): update dependency @edx/frontend-platform to v4 (#834)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-06 11:34:43 +05:00
Mubbshar Anwar
cb47717b09 fix: window console warning (#830)
remove console log warnings from SocialAuthProviders unit tests by mocking the location object.
Mock localStorage.

VAN-1350
2023-04-06 10:35:46 +05:00
Blue
85dbc9a6ca Merge pull request #826 from openedx/ahtesham/VAN-1351/add-test-cases-for-recommendations
fix: improve test coverage of recommendations
2023-04-03 17:34:36 +05:00
ahtesham-quraish
4aebeaffa7 fix: impmrove test coverage of recommendations
Need to update test cases for recommendations page and recommendation card

VAN-1351
2023-04-03 17:02:04 +05:00
Syed Sajjad Hussain Shah
6a84e2d5b6 feat: add support for skip_registration_form setting for SSO (#789)
VAN-1318

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-04-03 10:11:50 +05:00
Jenkins
e26620e350 chore(i18n): update translations 2023-04-02 11:16:46 -04:00
Zainab Amir
1cabd2a514 fix: sso providers position (#824) 2023-03-31 14:38:42 +05:00
dependabot[bot]
06dd70078e build(deps): bump http-cache-semantics and @edx/frontend-build (#791)
Removes [http-cache-semantics](https://github.com/kornelski/http-cache-semantics). It's no longer used after updating ancestor dependency [@edx/frontend-build](https://github.com/openedx/frontend-build). These dependencies need to be updated together.


Removes `http-cache-semantics`

Updates `@edx/frontend-build` from 11.0.2 to 12.7.0
- [Release notes](https://github.com/openedx/frontend-build/releases)
- [Commits](https://github.com/openedx/frontend-build/compare/v11.0.2...v12.7.0)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
- dependency-name: "@edx/frontend-build"
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-31 13:33:44 +05:00
renovate[bot]
4b13866e1d fix(deps): update dependency sanitize-html to v2.10.0 2023-03-31 01:38:36 +00:00
renovate[bot]
11142fda25 fix(deps): update dependency react-onclickoutside to v6.13.0 2023-03-30 23:29:26 +00:00
renovate[bot]
b4057f9588 fix(deps): update dependency core-js to v3.29.1 2023-03-30 21:26:37 +00:00
renovate[bot]
9524f030d1 fix(deps): update dependency algoliasearch to v4.16.0 2023-03-30 19:41:25 +00:00
renovate[bot]
3f10dce04f fix(deps): update dependency @edx/paragon to v20.29.0 2023-03-30 16:24:27 +00:00
renovate[bot]
5f8802272d fix(deps): update dependency @edx/brand to v1.2.0 2023-03-30 15:34:41 +00:00
renovate[bot]
0d486c2774 fix(deps): update dependency @edx/frontend-platform to v3.6.1 2023-03-30 12:53:23 +00:00
renovate[bot]
e78a1583c0 chore(deps): update dependency babel-plugin-formatjs to v10.4.0 2023-03-30 11:24:46 +00:00
Shahbaz Shabbir
ea966c48b9 chore: downgrade frontend-platform version from 3.6.1 to 3.6.0 (#812) 2023-03-30 15:11:24 +05:00
renovate[bot]
810b8d46b9 fix(deps): update dependency redux-saga to v1.2.3 2023-03-30 09:23:53 +00:00
Blue
8a00b74863 Merge pull request #792 from openedx/ahtesham/VAN-1343/integrate-zendesk-sdk
fix: add Zendesk SDK rather than loading Zendesk by network call in A…
2023-03-30 12:34:15 +05:00
ahtesham-quraish
94fafe661d fix: add Zendesk SDK
Get rid of Zendesk which is being loaded by network call and integrate Zendesk SDK

VAN-1343
2023-03-30 12:28:19 +05:00
renovate[bot]
7d58a124ab fix(deps): update dependency redux to v4.2.1 2023-03-30 07:08:14 +00:00
renovate[bot]
378a8d95f9 fix(deps): update dependency @redux-devtools/extension to v3.2.5 2023-03-30 05:05:42 +00:00
renovate[bot]
3a2e39af97 fix(deps): update dependency @optimizely/react-sdk to v2.9.2 2023-03-30 01:58:15 +00:00
renovate[bot]
6f6d725126 fix(deps): update dependency @edx/frontend-component-cookie-policy-banner to v2.2.2 2023-03-29 23:11:43 +00:00
dependabot[bot]
3d8eb34d80 Merge pull request #803 from openedx/dependabot/npm_and_yarn/decode-uri-component-0.2.2 2023-03-29 13:01:13 +00:00
dependabot[bot]
2768fc02ea build(deps): bump decode-uri-component from 0.2.0 to 0.2.2
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 12:53:40 +00:00
Shahbaz Shabbir
0902467fa6 chore: update frontend-platform version (#795) 2023-03-29 16:24:48 +05:00
renovate[bot]
1dc999070f fix(deps): update dependency form-urlencoded to v6 (#245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-29 15:54:40 +05:00
renovate[bot]
7a169715ea chore(deps): update actions/checkout action to v3 (#547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-29 15:45:07 +05:00
Blue
361f6781ee Merge pull request #799 from openedx/ahtesham/VAN-1349/autoupdate-workflow
feat: create autoupdate workflow
2023-03-29 14:55:37 +05:00
Blue
42190a89dd feat: create workflow for autoupdate
VAN-1349
2023-03-29 14:48:29 +05:00
Zainab Amir
2d4c6a1d3b feat: use intl hook for functional components (#796) 2023-03-29 12:21:12 +05:00
Syed Sajjad Hussain Shah
1dd88795c3 fix: update pull request template (#798) 2023-03-29 11:05:26 +05:00
Blue
7cff7311e1 Merge pull request #752 from DmytroAlipov/fix-redirect-reset
fix: incorrect link to password reset page
2023-03-28 20:24:38 +05:00
alipov-dm
bf93959350 fix: link to the password reset page
When using two different deployment
approaches, with one of them we get an
incorrectly working link to the password
reset page.
2023-03-28 18:03:57 +05:00
Syed Sajjad Hussain Shah
94151c2668 fix: pass user id as string in optimizely track event (#794)
Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-03-28 15:15:38 +05:00
Zainab Amir
bf650e6d4c feat: track recommendations group (#793) 2023-03-28 14:29:14 +05:00
Blue
575f195970 Merge pull request #786 from openedx/ahtesham/VAN-1284/missing-env-in-readme
docs: add missing configs in readme
2023-03-27 18:56:51 +05:00
ahtesham-quraish
c6bf6c92c1 docs: add missing configs in readme
VAN-1284
2023-03-27 11:41:47 +05:00
Jenkins
b86c31bff8 chore(i18n): update translations 2023-03-26 11:16:50 -04:00
Syed Sajjad Hussain Shah
fc37bbec1d fix: fix recommendations viewed event count anomly (#787)
Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-03-24 14:38:52 +05:00
Shahbaz Shabbir
6525c66600 fix: fix missing userId in segment page event call on welcome page (#784) 2023-03-20 18:17:57 +05:00
Attiya Ishaque
145234c5c3 fix: Update the browser title for progressive profiling page (#783) 2023-03-20 17:53:07 +05:00
Attiya Ishaque
a7f816f49a feat: hide signup page on the bases of flag (#779) 2023-03-20 10:52:17 +05:00
Jenkins
694b0a5381 chore(i18n): update translations 2023-03-19 11:16:46 -04:00
Blue
8a0947faf3 Merge pull request #777 from openedx/ahtesham/VAN-1716/validation-issue-on-change
fix: remove validation which comes from backend after clicking regis…
2023-03-15 16:48:22 +05:00
ahtesham-quraish
d1c4b20160 fix: remove validation which comes from backend after clicking registration button
VAN-1716
2023-03-15 16:44:28 +05:00
Shahbaz Shabbir
d81d8419a0 fix: make identify call to pass missing user_id (#778) 2023-03-13 16:37:26 +05:00
Blue
c6acdab7c6 Merge pull request #773 from openedx/ahtesham/VAN-1317/dropdown-issue-in-safari-browser
fix: country dropdown issue on safari
2023-03-13 10:33:47 +05:00
ahtesham-quraish
0374143148 fix: country dropdown issue on safari
VAN-1317
2023-03-13 10:27:38 +05:00
Syed Sajjad Hussain Shah
2d3c5ed761 Revert "fix: make identify call to pass missing user_id (#774)" (#776)
This reverts commit 93dcd8f16e.
2023-03-13 09:38:07 +05:00
Jenkins
a611451233 chore(i18n): update translations 2023-03-12 11:16:48 -04:00
Shahbaz Shabbir
93dcd8f16e fix: make identify call to pass missing user_id (#774) 2023-03-10 18:05:02 +05:00
Syed Sajjad Hussain Shah
294519c7a5 feat: shift recommendations experiment to optimizely full stack (#770)
VAN-1330

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-03-09 15:13:46 +05:00
Blue
f11df1f513 Merge pull request #771 from openedx/card-banner-issue-fix
Card banner image is not centered aligned
2023-03-09 13:36:28 +05:00
ahtesham-quraish
563609e10a fix: course card image fix
fix for card banner issue by adding the css property

VAN-1325
2023-03-09 12:22:32 +05:00
Dmytro
b4d4e36f72 feat: displaying a support link on the welcome page (#762)
This update adds a display dependency
support links on welcome page if variable
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK non-empty
in MFE settings.
2023-03-09 09:31:40 +05:00
212 changed files with 16853 additions and 38778 deletions

13
.env
View File

@@ -15,22 +15,27 @@ SEGMENT_KEY=''
SITE_NAME=null
INFO_EMAIL=''
# ***** Cookies *****
REGISTER_CONVERSION_COOKIE_NAME=null
USER_SURVEY_COOKIE_NAME=null
USER_RETENTION_COOKIE_NAME=null
# ***** Links *****
LOGIN_ISSUE_SUPPORT_LINK=''
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
POST_REGISTRATION_REDIRECT_URL=''
SEARCH_CATALOG_URL=''
# ***** Features flags *****
DISABLE_ENTERPRISE_LOGIN=''
ENABLE_COOKIE_POLICY_BANNER=''
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
ENABLE_PERSONALIZED_RECOMMENDATIONS=''
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
MARKETING_EMAILS_OPT_IN=''
SHOW_CONFIGURABLE_EDX_FIELDS=''
# ***** Zendesk related keys *****
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''
# ***** Base Container Images *****
BANNER_IMAGE_LARGE=''
BANNER_IMAGE_MEDIUM=''
BANNER_IMAGE_SMALL=''
BANNER_IMAGE_EXTRA_SMALL=''
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -20,16 +20,21 @@ SEGMENT_KEY=''
SITE_NAME='Your Platform Name Here'
INFO_EMAIL='info@example.com'
# ***** Cookies *****
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
SESSION_COOKIE_DOMAIN='localhost'
USER_INFO_COOKIE_NAME='edx-user-info'
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
# ***** Links *****
LOGIN_ISSUE_SUPPORT_LINK='http://localhost:18000/login-issue-support-url'
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
TOS_LINK='http://localhost:18000/tos'
PRIVACY_POLICY='http://localhost:18000/privacy'
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK='http://localhost:1999/welcome'
# ***** Base Container Images *****
BANNER_IMAGE_LARGE=''
BANNER_IMAGE_MEDIUM=''
BANNER_IMAGE_SMALL=''
BANNER_IMAGE_EXTRA_SMALL=''
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''

View File

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

View File

@@ -16,7 +16,5 @@ ORDER_HISTORY_URL='http://localhost:1996/orders'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=''
SITE_NAME='Your Platform Name Here'
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -1,16 +1,17 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint', {
rules: {
// Temporarily update the 'indent', 'template-curly-spacing' and
// 'no-multiple-empty-lines' rules since they are causing eslint
// to fail for no apparent reason since upgrading
// to fail for no apparent reason since upgrading
// @edx/frontend-build from v3 to v5:
// - TypeError: Cannot read property 'range' of null
'indent': [
indent: [
'error',
2,
{ 'ignoredNodes': ['TemplateLiteral', 'SwitchCase'] }
{ ignoredNodes: ['TemplateLiteral', 'SwitchCase'] },
],
'template-curly-spacing': 'off',
'jsx-a11y/label-has-associated-control': ['error', {
@@ -18,9 +19,9 @@ module.exports = createConfig('eslint', {
labelAttributes: [],
controlComponents: [],
assert: 'htmlFor',
depth: 25
depth: 25,
}],
'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}],
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: true }],
'import/order': [
'error',
{
@@ -46,5 +47,6 @@ module.exports = createConfig('eslint', {
},
},
],
'function-paren-newline': 'off',
},
});

View File

@@ -2,11 +2,16 @@
Include a description of your changes here, along with a link to any relevant Jira tickets and/or Github issues.
#### JIRA
[XXX-XXXX](https://2u-internal.atlassian.net/browse/XXX-XXXX)
#### How Has This Been Tested?
Please describe in detail how you tested your changes.
#### Screenshots/sandbox (optional):
Include a link to the sandbox for design changes or screenshot for before and after. **Remove this section if its not applicable.**
|Before|After|
@@ -21,4 +26,4 @@ Include a link to the sandbox for design changes or screenshot for before and af
#### Post-merge Checklist
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/vanguards** to do it.
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.

View File

@@ -0,0 +1,25 @@
name: autoupdate
on:
push:
branches:
- master
pull_request:
types: [ labeled ]
branches:
- master
jobs:
autoupdate:
name: autoupdate
runs-on: ubuntu-20.04
steps:
- uses: docker://chinthakagodawita/autoupdate-action:v1
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
DRY_RUN: "false"
PR_FILTER: "labelled"
PR_LABELS: "autoupdate"
EXCLUDED_LABELS: "dependencies,wontfix"
MERGE_MSG: "Branch was auto-updated."
RETRY_COUNT: "5"
RETRY_SLEEP: "300"
MERGE_CONFLICT_ACTION: "fail"

View File

@@ -11,17 +11,16 @@ on:
jobs:
tests:
runs-on: ubuntu-20.04
strategy:
matrix:
node: [16]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
node-version: ${{ env.NODE_VER }}
- name: Install Dependencies
run: npm ci

View File

@@ -10,4 +10,4 @@ on:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
uses: openedx/.github/.github/workflows/lockfile-check.yml@master

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18

View File

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

View File

@@ -104,6 +104,15 @@ The authentication micro-frontend also requires the following additional variabl
- Disables the enterprise login from Authn MFE.
- ``true`` | ``''`` (empty strings are falsy)
* - ``MFE_CONFIG_API_URL``
- Link of the API to get runtime mfe configuration variables from the site configuration or django settings.
- ``/api/v1/mfe_config`` | ``''`` (empty strings are falsy)
* - ``APP_ID``
- Name of MFE, this will be used by the API to get runtime configurations for the specific micro frontend. For a frontend repo `frontend-app-appName`, use `appName` as APP_ID.
- ``authn`` | ``''``
edX-specific Environment Variables
**********************************
@@ -121,6 +130,10 @@ Furthermore, there are several edX-specific environment variables that enable in
- Enables support for opting in marketing emails that helps us getting user consent for sending marketing emails.
- ``true`` | ``''`` (empty strings are falsy)
* - ``SHOW_CONFIGURABLE_EDX_FIELDS``
- For edX, country and honor code fields are required by default. This flag enables edX specific required fields.
- ``true`` | ``''`` (empty strings are falsy)
For more information see the document: `Micro-frontend applications in Open
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.

42182
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,58 +32,55 @@
"url": "https://github.com/openedx/frontend-app-authn/issues"
},
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-cookie-policy-banner": "2.2.0",
"@edx/frontend-platform": "3.2.0",
"@edx/paragon": "20.28.4",
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-brands-svg-icons": "6.2.1",
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-platform": "^5.5.4",
"@edx/paragon": "20.46.2",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-brands-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/react-fontawesome": "0.2.0",
"@redux-devtools/extension": "3.2.3",
"@optimizely/react-sdk": "^2.9.1",
"@redux-devtools/extension": "3.2.5",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"algoliasearch": "^4.14.3",
"algoliasearch-helper": "^3.14.0",
"classnames": "2.3.2",
"core-js": "3.26.1",
"extract-react-intl-messages": "4.1.1",
"core-js": "3.32.0",
"fastest-levenshtein": "1.0.16",
"form-urlencoded": "4.2.1",
"lodash.camelcase": "4.3.0",
"lodash.snakecase": "4.1.1",
"form-urlencoded": "6.1.0",
"prop-types": "15.8.1",
"query-string": "5.1.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"query-string": "7.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "6.1.0",
"react-loading-skeleton": "2.2.0",
"react-onclickoutside": "6.12.2",
"react-loading-skeleton": "3.3.1",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
"react-router": "6.15.0",
"react-router-dom": "6.15.0",
"react-zendesk": "^0.1.13",
"redux": "4.2.0",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.4",
"redux-saga": "1.2.2",
"redux-saga": "1.2.3",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.13.11",
"reselect": "4.1.7",
"sanitize-html": "2.8.0",
"semver-regex": "3.1.4",
"regenerator-runtime": "0.14.0",
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "11.0.2",
"@edx/frontend-build": "12.9.8",
"@edx/reactifex": "1.1.0",
"babel-plugin-formatjs": "10.3.35",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"babel-plugin-formatjs": "10.5.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.7",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-import": "2.28.0",
"glob": "7.2.3",
"history": "5.3.0",
"husky": "7.0.4",
"jest": "27.5.1",
"react-test-renderer": "16.14.0"
"jest": "29.6.2",
"react-test-renderer": "^17.0.2"
}
}

View File

@@ -1,9 +1,15 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Authn | <%= process.env.SITE_NAME %></title>
<title><%= (process.env.SITE_NAME && process.env.SITE_NAME != 'null') ? 'Authentication | ' + process.env.SITE_NAME : 'Authentication' %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.6/iframeResizer.contentWindow.min.js"
integrity="sha512-R7Piufj0/o6jG9ZKrAvS2dblFr2kkuG4XVQwStX+/4P+KwOLUXn2DXy0l1AJDxxqGhkM/FJllZHG2PKOAheYzg=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<% if (process.env.OPTIMIZELY_URL) { %>
<script
src="<%= process.env.OPTIMIZELY_URL %>"
@@ -13,61 +19,6 @@
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
></script>
<% } %>
<% if (process.env.ZENDESK_KEY) { %>
<script
id="ze-snippet"
src="https://static.zdassets.com/ekr/snippet.js?key=<%= process.env.ZENDESK_KEY %>"
>
</script>
<script type="text/javascript">
window.zESettings = {
cookies: true,
webWidget: {
contactOptions: {
enabled: false,
},
chat: {
suppress: false,
departments: {
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring']
}
},
contactForm: {
ticketForms: [
{
id: 360003368814,
subject: false,
fields: [
{
id: 'description',
prefill: {
'*': '',
},
},
],
},
],
selectTicketForm: {
'*': 'Please choose your request type:',
},
attachments: true,
},
helpCenter: {
originalArticleButton: true,
},
answerBot: {
suppress: false,
contactOnlyAfterQuery: true,
title: { '*': 'edX Support' },
avatar: {
url: '<%= process.env.ZENDESK_LOGO_URL %>',
name: { '*': 'edX Support' },
},
},
},
};
</script>
<% } %>
</head>
<body>
<div id="root"></div>

View File

@@ -3,10 +3,10 @@ import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { Helmet } from 'react-helmet';
import { Redirect, Route, Switch } from 'react-router-dom';
import { Navigate, Route, Routes } from 'react-router-dom';
import {
Logistration, NotFoundPage, registerIcons, UnAuthOnlyRoute,
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
} from './common-components';
import configureStore from './data/configureStore';
import {
@@ -15,14 +15,18 @@ import {
PAGE_NOT_FOUND,
PASSWORD_RESET_CONFIRM,
RECOMMENDATIONS,
REGISTER_EMBEDDED_PAGE,
REGISTER_PAGE,
RESET_PAGE,
} from './data/constants';
import { updatePathWithQueryParams } from './data/utils';
import ForgotPasswordPage from './forgot-password';
import { ForgotPasswordPage } from './forgot-password';
import Logistration from './logistration/Logistration';
import { ProgressiveProfiling } from './progressive-profiling';
import RecommendationsPage from './recommendations';
import ResetPasswordPage from './reset-password';
import { RecommendationsPage } from './recommendations';
import { RegistrationPage } from './register';
import { ResetPasswordPage } from './reset-password';
import './index.scss';
registerIcons();
@@ -32,21 +36,27 @@ const MainApp = () => (
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
<Switch>
<Route exact path="/">
<Redirect to={updatePathWithQueryParams(REGISTER_PAGE)} />
</Route>
<UnAuthOnlyRoute exact path={LOGIN_PAGE} render={() => <Logistration selectedPage={LOGIN_PAGE} />} />
<UnAuthOnlyRoute exact path={REGISTER_PAGE} component={Logistration} />
<UnAuthOnlyRoute exact path={RESET_PAGE} component={ForgotPasswordPage} />
<Route exact path={PASSWORD_RESET_CONFIRM} component={ResetPasswordPage} />
<Route exact path={AUTHN_PROGRESSIVE_PROFILING} component={ProgressiveProfiling} />
<Route exact path={RECOMMENDATIONS} component={RecommendationsPage} />
<Route path={PAGE_NOT_FOUND} component={NotFoundPage} />
<Route path="*">
<Redirect to={PAGE_NOT_FOUND} />
</Route>
</Switch>
{getConfig().ZENDESK_KEY && <Zendesk />}
<Routes>
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
<Route
path={REGISTER_EMBEDDED_PAGE}
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
/>
<Route
path={LOGIN_PAGE}
element={
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
}
/>
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
<Route path={PASSWORD_RESET_CONFIRM} element={<ResetPasswordPage />} />
<Route path={AUTHN_PROGRESSIVE_PROFILING} element={<ProgressiveProfiling />} />
<Route path={RECOMMENDATIONS} element={<RecommendationsPage />} />
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
</Routes>
</AppProvider>
);

View File

@@ -1,46 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const AuthLargeLayout = ({ intl, username }) => (
<div className="w-50 d-flex">
<div className="col-md-10 bg-light-200 p-0">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className="min-vh-100 d-flex align-items-center">
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
<div>
<h1 className="welcome-to-platform data-hj-suppress">
{intl.formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
</h1>
<h2 className="complete-your-profile">
{intl.formatMessage(messages['complete.your.profile.1'])}
<div className="text-accent-a">
{intl.formatMessage(messages['complete.your.profile.2'])}
</div>
</h2>
</div>
</div>
</div>
<div className="col-md-2 bg-white p-0">
<svg className="m1-n1 w-100 h-100 large-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(171.6)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
);
AuthLargeLayout.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
username: PropTypes.string.isRequired,
};
export default injectIntl(AuthLargeLayout);

View File

@@ -1,49 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const AuthMediumLayout = ({ intl, username }) => (
<>
<div className="w-100 medium-screen-top-stripe" />
<div className="w-100 p-0 mb-3 d-flex">
<div className="col-md-10 bg-light-200">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className="d-flex align-items-center justify-content-center mb-4 ml-5">
<div className="medium-yellow-line mt-5 mr-n2" />
<div>
<h1 className="h3 data-hj-suppress mw-320">
{intl.formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
</h1>
<h2 className="display-1">
{intl.formatMessage(messages['complete.your.profile.1'])}
<div className="text-accent-a">
{intl.formatMessage(messages['complete.your.profile.2'])}
</div>
</h2>
</div>
</div>
</div>
<div className="col-md-2 bg-white p-0">
<svg className="w-100 h-100 medium-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(168)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
</>
);
AuthMediumLayout.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
username: PropTypes.string.isRequired,
};
export default injectIntl(AuthMediumLayout);

View File

@@ -1,38 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const AuthSmallLayout = ({ intl, username }) => (
<div className="min-vw-100 bg-light-200">
<div className="col-md-12 small-screen-top-stripe" />
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
<div className="small-yellow-line mt-4.5" />
<div>
<h1 className="h5 data-hj-suppress">
{intl.formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
</h1>
<h2 className="h1">
{intl.formatMessage(messages['complete.your.profile.1'])}
<div className="text-accent-a">
{intl.formatMessage(messages['complete.your.profile.2'])}
</div>
</h2>
</div>
</div>
</div>
);
AuthSmallLayout.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
username: PropTypes.string.isRequired,
};
export default injectIntl(AuthSmallLayout);

View File

@@ -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;

View File

@@ -1,46 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import messages from './messages';
const LargeLayout = ({ intl }) => (
<div className="w-50 d-flex">
<div className="col-md-9 bg-primary-400">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="min-vh-100 d-flex align-items-center">
<div className={classNames({ 'large-yellow-line mr-n4.5': getConfig().SITE_NAME === 'edX' })} />
<h1
className={classNames(
'display-2 text-white mw-xs',
{ 'ml-6': getConfig().SITE_NAME !== 'edX' },
)}
>
{intl.formatMessage(messages['start.learning'])}
<div className="text-accent-a">
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</div>
</h1>
</div>
</div>
<div className="col-md-3 bg-white p-0">
<svg className="ml-n1 w-100 h-100 large-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(171.6)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
);
LargeLayout.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
};
export default injectIntl(LargeLayout);

View File

@@ -1,51 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import messages from './messages';
const MediumLayout = ({ intl }) => (
<>
<div className="w-100 medium-screen-top-stripe" />
<div className="w-100 p-0 mb-3 d-flex">
<div className="col-md-10 bg-primary-400">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="d-flex align-items-center justify-content-center mb-4 ">
<div className={classNames({ 'mt-1 medium-yellow-line': getConfig().SITE_NAME === 'edX' })} />
<div>
<h1
className={classNames(
'display-1 text-white mt-5 mb-5 mr-2',
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
)}
>
<span className="mr-2">{intl.formatMessage(messages['start.learning'])}</span>
<span className="text-accent-a d-inline-block">
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</h1>
</div>
</div>
</div>
<div className="col-md-2 bg-white p-0">
<svg className="w-100 h-100 medium-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(168)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
</>
);
MediumLayout.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
};
export default injectIntl(MediumLayout);

View File

@@ -1,40 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import messages from './messages';
const SmallLayout = ({ intl }) => (
<span className="bg-primary-400 w-100">
<div className="col-md-12 small-screen-top-stripe" />
<div>
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} />
<h1
className={classNames(
'text-white mt-3.5 mb-3.5',
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
)}
>
<span className="mr-1">{intl.formatMessage(messages['start.learning'])}</span>
<span className="text-accent-a d-inline-block">
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</h1>
</div>
</div>
</span>
);
SmallLayout.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
};
export default injectIntl(SmallLayout);

View File

@@ -1 +0,0 @@
export { default } from './BaseComponent';

View File

@@ -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>

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import classNames from 'classnames';
import messages from './messages';
const LargeLayout = () => {
const { formatMessage } = useIntl();
return (
<div className="w-50 d-flex">
<div className="col-md-9 bg-primary-400">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="min-vh-100 d-flex align-items-center">
<div className={classNames({ 'large-yellow-line mr-n4.5': getConfig().SITE_NAME === 'edX' })} />
<h1
className={classNames(
'display-2 text-white mw-xs',
{ 'ml-6': getConfig().SITE_NAME !== 'edX' },
)}
>
{formatMessage(messages['start.learning'])}
<div className="text-accent-a">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</div>
</h1>
</div>
</div>
<div className="col-md-3 bg-white p-0">
<svg className="ml-n1 w-100 h-100 large-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(171.6)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
);
};
export default LargeLayout;

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import classNames from 'classnames';
import messages from './messages';
const MediumLayout = () => {
const { formatMessage } = useIntl();
return (
<>
<div className="w-100 medium-screen-top-stripe" />
<div className="w-100 p-0 mb-3 d-flex">
<div className="col-md-10 bg-primary-400">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="d-flex align-items-center justify-content-center mb-4 ">
<div className={classNames({ 'mt-1 medium-yellow-line': getConfig().SITE_NAME === 'edX' })} />
<div>
<h1
className={classNames(
'display-1 text-white mt-5 mb-5 mr-2 main-heading',
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' },
)}
>
<span>
{formatMessage(messages['start.learning'])}{' '}
<span className="text-accent-a d-inline-block">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</span>
</h1>
</div>
</div>
</div>
<div className="col-md-2 bg-white p-0">
<svg className="w-100 h-100 medium-screen-svg-primary" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(168)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
</>
);
};
export default MediumLayout;

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import classNames from 'classnames';
import messages from './messages';
const SmallLayout = () => {
const { formatMessage } = useIntl();
return (
<span className="bg-primary-400 w-100">
<div className="col-md-12 small-screen-top-stripe" />
<div>
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="d-flex align-items-center m-3.5">
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} />
<h1
className={classNames(
'text-white mt-3.5 mb-3.5',
)}
>
<span>
{formatMessage(messages['start.learning'])}{' '}
<span className="text-accent-a d-inline-block">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</span>
</h1>
</div>
</div>
</span>
);
};
export default SmallLayout;

View File

@@ -0,0 +1,3 @@
export { default as DefaultLargeLayout } from './LargeLayout';
export { default as DefaultMediumLayout } from './MediumLayout';
export { default as DefaultSmallLayout } from './SmallLayout';

View 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;

View 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 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;

View 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;

View 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;

View 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;

View 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';

View 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;
}
}

View 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;

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const LargeLayout = ({ username }) => {
const { formatMessage } = useIntl();
return (
<div className="w-50 d-flex">
<div className="col-md-10 bg-light-200 p-0">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className="min-vh-100 d-flex align-items-center">
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
<div>
<h1 className="welcome-to-platform data-hj-suppress">
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
</h1>
<h2 className="complete-your-profile">
{formatMessage(messages['complete.your.profile.1'])}
<div className="text-accent-a">
{formatMessage(messages['complete.your.profile.2'])}
</div>
</h2>
</div>
</div>
</div>
<div className="col-md-2 bg-white p-0">
<svg className="m1-n1 w-100 h-100 large-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(171.6)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
);
};
LargeLayout.propTypes = {
username: PropTypes.string.isRequired,
};
export default LargeLayout;

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const MediumLayout = ({ username }) => {
const { formatMessage } = useIntl();
return (
<>
<div className="w-100 medium-screen-top-stripe" />
<div className="w-100 p-0 mb-3 d-flex">
<div className="col-md-10 bg-light-200">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className="d-flex align-items-center justify-content-center mb-4 ml-5">
<div className="medium-yellow-line mt-5 mr-n2" />
<div>
<h1 className="h3 data-hj-suppress mw-320">
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
</h1>
<h2 className="display-1">
{formatMessage(messages['complete.your.profile.1'])}
<div className="text-accent-a">
{formatMessage(messages['complete.your.profile.2'])}
</div>
</h2>
</div>
</div>
</div>
<div className="col-md-2 bg-white p-0">
<svg className="w-100 h-100 medium-screen-svg-light" preserveAspectRatio="xMaxYMin meet">
<g transform="skewX(168)">
<rect x="0" y="0" height="100%" width="100%" />
</g>
</svg>
</div>
</div>
</>
);
};
MediumLayout.propTypes = {
username: PropTypes.string.isRequired,
};
export default MediumLayout;

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const SmallLayout = ({ username }) => {
const { formatMessage } = useIntl();
return (
<div className="min-vw-100 bg-light-200">
<div className="col-md-12 small-screen-top-stripe" />
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className="d-flex align-items-center m-3.5">
<div className="small-yellow-line mt-4.5" />
<div>
<h1 className="h5 data-hj-suppress">
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username })}
</h1>
<h2 className="h1">
{formatMessage(messages['complete.your.profile.1'])}
<div className="text-accent-a">
{formatMessage(messages['complete.your.profile.2'])}
</div>
</h2>
</div>
</div>
</div>
);
};
SmallLayout.propTypes = {
username: PropTypes.string.isRequired,
};
export default SmallLayout;

View File

@@ -0,0 +1,3 @@
export { default as AuthLargeLayout } from './LargeLayout';
export { default as AuthMediumLayout } from './MediumLayout';
export { default as AuthSmallLayout } from './SmallLayout';

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
const IMAGE_LAYOUT = 'image-layout';
const DEFAULT_LAYOUT = 'default-layout';
export { DEFAULT_LAYOUT, IMAGE_LAYOUT };

View File

@@ -0,0 +1,87 @@
import React, { useEffect, useState } from 'react';
import { breakpoints } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import MediaQuery from 'react-responsive';
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './components/default-layout';
import {
ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout,
} from './components/image-layout';
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
import { DEFAULT_LAYOUT, IMAGE_LAYOUT } from './data/constants';
const BaseContainer = ({ children, showWelcomeBanner, username }) => {
const [baseContainerVersion, setBaseContainerVersion] = useState(DEFAULT_LAYOUT);
useEffect(() => {
const initRebrandExperiment = () => {
if (window.experiments?.rebrandExperiment) {
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
} else {
window.experiments = window.experiments || {};
window.experiments.rebrandExperiment = {};
window.experiments.rebrandExperiment.handleLoaded = () => {
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
};
}
};
initRebrandExperiment();
}, []);
if (baseContainerVersion === IMAGE_LAYOUT) {
return (
<div className="layout">
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <ImageExtraSmallLayout />}
</MediaQuery>
<MediaQuery minWidth={breakpoints.small.minWidth} maxWidth={breakpoints.small.maxWidth - 1}>
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <ImageSmallLayout />}
</MediaQuery>
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
{showWelcomeBanner ? <AuthMediumLayout username={username} /> : <ImageMediumLayout />}
</MediaQuery>
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
{showWelcomeBanner ? <AuthLargeLayout username={username} /> : <ImageLargeLayout />}
</MediaQuery>
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
{children}
</div>
</div>
);
}
return (
<>
<div className="col-md-12 extra-large-screen-top-stripe" />
<div className="layout">
<MediaQuery maxWidth={breakpoints.small.maxWidth - 1}>
{showWelcomeBanner ? <AuthSmallLayout username={username} /> : <DefaultSmallLayout />}
</MediaQuery>
<MediaQuery minWidth={breakpoints.medium.minWidth} maxWidth={breakpoints.large.maxWidth - 1}>
{showWelcomeBanner ? <AuthMediumLayout username={username} /> : <DefaultMediumLayout />}
</MediaQuery>
<MediaQuery minWidth={breakpoints.extraLarge.minWidth}>
{showWelcomeBanner ? <AuthLargeLayout username={username} /> : <DefaultLargeLayout />}
</MediaQuery>
<div className={classNames('content', { 'align-items-center mt-0': showWelcomeBanner })}>
{children}
</div>
</div>
</>
);
};
BaseContainer.defaultProps = {
showWelcomeBanner: false,
username: null,
};
BaseContainer.propTypes = {
children: PropTypes.node.isRequired,
showWelcomeBanner: PropTypes.bool,
username: PropTypes.string,
};
export default BaseContainer;

View 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();
});
});

View 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;

View File

@@ -1,23 +1,25 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Button, Form,
Icon,
} from '@edx/paragon';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { Login } from '@edx/paragon/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
/**
* This component renders the Single sign-on (SSO) button only for the tpa provider passed
* */
const EnterpriseSSO = (props) => {
const { intl } = props;
const { formatMessage } = useIntl();
const tpaProvider = props.provider;
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false;
const handleSubmit = (e, url) => {
e.preventDefault();
@@ -35,7 +37,7 @@ const EnterpriseSSO = (props) => {
<div className="d-flex flex-column">
<div className="mw-450">
<Form className="m-0">
<p>{intl.formatMessage(messages['enterprisetpa.title.heading'], { providerName: tpaProvider.name })}</p>
<p>{formatMessage(messages['enterprisetpa.title.heading'], { providerName: tpaProvider.name })}</p>
<Button
id={tpaProvider.id}
key={tpaProvider.id}
@@ -46,16 +48,18 @@ const EnterpriseSSO = (props) => {
>
{tpaProvider.iconImage ? (
<div aria-hidden="true">
<img className="icon-image" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
<img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
</div>
)
: (
<>
<div className="font-container" aria-hidden="true">
<FontAwesomeIcon
icon={SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? ['fab', tpaProvider.iconClass] : faSignInAlt}
/>
<div className="btn-tpa__font-container" aria-hidden="true">
{SUPPORTED_ICON_CLASSES.includes(tpaProvider.iconClass) ? (
<FontAwesomeIcon icon={['fab', tpaProvider.iconClass]} />)
: (
<Icon className="h-75" src={Login} />
)}
</div>
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
</>
@@ -64,12 +68,15 @@ const EnterpriseSSO = (props) => {
<div className="mb-4" />
<Button
type="submit"
id="other-ways-to-sign-in"
variant="outline-primary"
state="Complete"
className="w-100"
onClick={(e) => handleClick(e)}
>
{intl.formatMessage(messages['enterprisetpa.login.button.text'])}
{disablePublicAccountCreation
? formatMessage(messages['enterprisetpa.login.button.text.public.account.creation.disabled'])
: formatMessage(messages['enterprisetpa.login.button.text'])}
</Button>
</Form>
</div>
@@ -100,7 +107,6 @@ EnterpriseSSO.propTypes = {
loginUrl: PropTypes.string,
registerUrl: PropTypes.string,
}),
intl: PropTypes.objectOf(PropTypes.object).isRequired,
};
export default injectIntl(EnterpriseSSO);
export default EnterpriseSSO;

View File

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

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button, Hyperlink, Icon } from '@edx/paragon';
import { Institution } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
@@ -32,8 +32,8 @@ export const RenderInstitutionButton = props => {
* */
const InstitutionLogistration = props => {
const lmsBaseUrl = getConfig().LMS_BASE_URL;
const { formatMessage } = useIntl();
const {
intl,
secondaryProviders,
headingTitle,
} = props;
@@ -42,11 +42,11 @@ const InstitutionLogistration = props => {
<>
<div className="d-flex justify-content-left mb-4 mt-2">
<div className="flex-column">
<h4 className="mb-2 font-weight-bold institute-heading">
<h4 className="mb-2 font-weight-bold institutions__heading">
{headingTitle}
</h4>
<p className="mb-2">
{intl.formatMessage(messages['institution.login.page.sub.heading'])}
{formatMessage(messages['institution.login.page.sub.heading'])}
</p>
</div>
</div>
@@ -57,7 +57,7 @@ const InstitutionLogistration = props => {
<tr key={provider} className="pgn__data-table-row">
<td>
<Hyperlink
className="btn nav-item p-0 mb-1 secondary-provider-link"
className="btn nav-item p-0 mb-1 institutions--provider-link"
destination={lmsBaseUrl + provider.loginUrl}
>
{provider.name}
@@ -95,7 +95,6 @@ RenderInstitutionButton.defaultProps = {
InstitutionLogistration.propTypes = {
...LogistrationProps,
intl: PropTypes.objectOf(PropTypes.object).isRequired,
headingTitle: PropTypes.string,
};
InstitutionLogistration.defaultProps = {
@@ -103,4 +102,4 @@ InstitutionLogistration.defaultProps = {
headingTitle: '',
};
export default injectIntl(InstitutionLogistration);
export default InstitutionLogistration;

View File

@@ -1,139 +0,0 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthService } from '@edx/frontend-platform/auth';
import { injectIntl } from '@edx/frontend-platform/i18n';
import {
Icon,
Tab,
Tabs,
} from '@edx/paragon';
import { ChevronLeft } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import BaseComponent from '../base-component';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import { getTpaHint, getTpaProvider, updatePathWithQueryParams } from '../data/utils';
import { LoginPage } from '../login';
import { RegistrationPage } from '../register';
import { backupRegistrationForm } from '../register/data/actions';
import {
tpaProvidersSelector,
} from './data/selectors';
import messages from './messages';
const Logistration = (props) => {
const { intl, selectedPage, tpaProviders } = props;
const tpaHint = getTpaHint();
const {
providers, secondaryProviders,
} = tpaProviders;
const [institutionLogin, setInstitutionLogin] = useState(false);
const [key, setKey] = useState('');
useEffect(() => {
const authService = getAuthService();
if (authService) {
authService.getCsrfTokenService().getCsrfToken(getConfig().LMS_BASE_URL);
}
});
const handleInstitutionLogin = (e) => {
sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
if (typeof e === 'string') {
sendPageEvent('login_and_registration', e === '/login' ? 'login' : 'register');
} else {
sendPageEvent('login_and_registration', e.target.dataset.eventName);
}
setInstitutionLogin(!institutionLogin);
};
const handleOnSelect = (tabKey) => {
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
if (tabKey === LOGIN_PAGE) {
props.backupRegistrationForm();
}
setKey(tabKey);
};
const tabTitle = (
<div className="d-flex">
<Icon src={ChevronLeft} className="left-icon" />
<span className="ml-2">
{selectedPage === LOGIN_PAGE
? intl.formatMessage(messages['logistration.sign.in'])
: intl.formatMessage(messages['logistration.register'])}
</span>
</div>
);
const isValidTpaHint = () => {
const { provider } = getTpaProvider(tpaHint, providers, secondaryProviders);
return !!provider;
};
return (
<BaseComponent>
<div>
{institutionLogin
? (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={selectedPage === LOGIN_PAGE ? LOGIN_PAGE : REGISTER_PAGE} />
</Tabs>
)
: (!isValidTpaHint() && (
<>
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={handleOnSelect}>
<Tab title={intl.formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
<Tab title={intl.formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
</Tabs>
</>
))}
{ key && (
<Redirect to={updatePathWithQueryParams(key)} />
)}
<div id="main-content" className="main-content">
{selectedPage === LOGIN_PAGE
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
: <RegistrationPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />}
</div>
</div>
</BaseComponent>
);
};
Logistration.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
selectedPage: PropTypes.string,
backupRegistrationForm: PropTypes.func.isRequired,
tpaProviders: PropTypes.shape({
providers: PropTypes.array,
secondaryProviders: PropTypes.array,
}),
};
Logistration.defaultProps = {
tpaProviders: {
providers: [],
secondaryProviders: [],
},
};
Logistration.defaultProps = {
selectedPage: REGISTER_PAGE,
};
const mapStateToProps = state => ({
tpaProviders: tpaProvidersSelector(state),
});
export default connect(
mapStateToProps,
{
backupRegistrationForm,
},
)(injectIntl(Logistration));

View File

@@ -2,16 +2,16 @@ import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
export default function NotFoundPage() {
return (
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
<p className="my-0 py-5 text-muted mw-32em">
<FormattedMessage
id="error.notfound.message"
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
description="error message when a page does not exist"
/>
</p>
</div>
);
}
const NotFoundPage = () => (
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
<p className="my-0 py-5 text-muted mw-32em">
<FormattedMessage
id="error.notfound.message"
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
description="error message when a page does not exist"
/>
</p>
</div>
);
export default NotFoundPage;

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
} from '@edx/paragon';
@@ -9,33 +10,94 @@ import {
} from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
import messages from './messages';
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
import { clearRegistrationBackendError, fetchRealtimeValidations } from '../register/data/actions';
import { validatePasswordField } from '../register/data/utils';
const PasswordField = (props) => {
const { formatMessage } = props.intl;
const { formatMessage } = useIntl();
const dispatch = useDispatch();
const validationApiRateLimited = useSelector(state => state.register.validationApiRateLimited);
const [isPasswordHidden, setHiddenTrue, setHiddenFalse] = useToggle(true);
const [showTooltip, setShowTooltip] = useState(false);
const handleBlur = (e) => {
if (props.handleBlur) { props.handleBlur(e); }
const { name, value } = e.target;
if (name === props.name && e.relatedTarget?.name === 'passwordIcon') {
return; // Do not run validations on password icon click
}
let passwordValue = value;
if (name === 'passwordIcon') {
// To validate actual password value when onBlur is triggered by focusing out the password icon
passwordValue = props.value;
}
if (props.handleBlur) {
props.handleBlur({
target: {
name: props.name,
value: passwordValue,
},
});
}
setShowTooltip(props.showRequirements && false);
if (props.handleErrorChange) { // If rendering from register page
const fieldError = validatePasswordField(passwordValue, formatMessage);
if (fieldError) {
props.handleErrorChange('password', fieldError);
} else if (!validationApiRateLimited) {
dispatch(fetchRealtimeValidations({ password: passwordValue }));
}
}
};
const handleFocus = (e) => {
if (e.target?.name === 'passwordIcon') {
return; // Do not clear error on password icon focus
}
if (props.handleFocus) {
props.handleFocus(e);
}
if (props.handleErrorChange) {
props.handleErrorChange('password', '');
dispatch(clearRegistrationBackendError('password'));
}
setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
};
const HideButton = (
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={VisibilityOff} iconAs={Icon} onClick={setHiddenTrue} size="sm" variant="secondary" alt={formatMessage(messages['hide.password'])} />
<IconButton
onFocus={handleFocus}
onBlur={handleBlur}
name="passwordIcon"
src={VisibilityOff}
iconAs={Icon}
onClick={setHiddenTrue}
size="sm"
variant="secondary"
alt={formatMessage(messages['hide.password'])}
/>
);
const ShowButton = (
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={Visibility} iconAs={Icon} onClick={setHiddenFalse} size="sm" variant="secondary" alt={formatMessage(messages['show.password'])} />
<IconButton
onFocus={handleFocus}
onBlur={handleBlur}
name="passwordIcon"
src={Visibility}
iconAs={Icon}
onClick={setHiddenFalse}
size="sm"
variant="secondary"
alt={formatMessage(messages['show.password'])}
/>
);
const placement = window.innerWidth < 768 ? 'top' : 'left';
const tooltip = (
<Tooltip id={`password-requirement-${placement}`}>
@@ -59,7 +121,7 @@ const PasswordField = (props) => {
<OverlayTrigger key="tooltip" placement={placement} overlay={tooltip} show={showTooltip}>
<Form.Control
as="input"
className="form-field"
className="form-group__form-field"
type={isPasswordHidden ? 'password' : 'text'}
name={props.name}
value={props.value}
@@ -89,6 +151,7 @@ PasswordField.defaultProps = {
handleBlur: null,
handleFocus: null,
handleChange: () => {},
handleErrorChange: null,
showRequirements: true,
autoComplete: null,
};
@@ -100,11 +163,11 @@ PasswordField.propTypes = {
handleBlur: PropTypes.func,
handleFocus: PropTypes.func,
handleChange: PropTypes.func,
intl: PropTypes.objectOf(PropTypes.object).isRequired,
handleErrorChange: PropTypes.func,
name: PropTypes.string.isRequired,
showRequirements: PropTypes.bool,
value: PropTypes.string.isRequired,
autoComplete: PropTypes.string,
};
export default injectIntl(PasswordField);
export default PasswordField;

View File

@@ -1,14 +1,15 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
import { AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS } from '../data/constants';
import {
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
} from '../data/constants';
import { setCookie } from '../data/utils';
function RedirectLogistration(props) {
const RedirectLogistration = (props) => {
const {
authenticatedUser,
finishAuthUrl,
redirectUrl,
redirectToProgressiveProfilingPage,
@@ -17,6 +18,8 @@ function RedirectLogistration(props) {
redirectToRecommendationsPage,
educationLevel,
userId,
registrationEmbedded,
host,
} = props;
let finalRedirectUrl = '';
@@ -35,15 +38,24 @@ function RedirectLogistration(props) {
if (redirectToProgressiveProfilingPage) {
// TODO: Do we still need this cookie?
setCookie('van-504-returning-user', true);
if (registrationEmbedded) {
window.parent.postMessage({
action: REDIRECT,
redirectUrl: getConfig().POST_REGISTRATION_REDIRECT_URL,
}, host);
return null;
}
const registrationResult = { redirectUrl: finalRedirectUrl, success };
return (
<Redirect to={{
pathname: AUTHN_PROGRESSIVE_PROFILING,
state: {
<Navigate
to={AUTHN_PROGRESSIVE_PROFILING}
state={{
registrationResult,
optionalFields,
},
}}
authenticatedUser,
}}
replace
/>
);
}
@@ -52,24 +64,26 @@ function RedirectLogistration(props) {
if (redirectToRecommendationsPage) {
const registrationResult = { redirectUrl: finalRedirectUrl, success };
return (
<Redirect to={{
pathname: RECOMMENDATIONS,
state: {
<Navigate
to={RECOMMENDATIONS}
state={{
registrationResult,
educationLevel,
userId,
},
}}
}}
replace
/>
);
}
window.location.href = finalRedirectUrl;
}
return <></>;
}
return null;
};
RedirectLogistration.defaultProps = {
authenticatedUser: {},
educationLevel: null,
finishAuthUrl: null,
success: false,
@@ -78,9 +92,12 @@ RedirectLogistration.defaultProps = {
optionalFields: {},
redirectToRecommendationsPage: false,
userId: null,
registrationEmbedded: false,
host: '',
};
RedirectLogistration.propTypes = {
authenticatedUser: PropTypes.shape({}),
educationLevel: PropTypes.string,
finishAuthUrl: PropTypes.string,
success: PropTypes.bool,
@@ -89,6 +106,8 @@ RedirectLogistration.propTypes = {
optionalFields: PropTypes.shape({}),
redirectToRecommendationsPage: PropTypes.bool,
userId: PropTypes.number,
registrationEmbedded: PropTypes.bool,
host: PropTypes.string,
};
export default RedirectLogistration;

View File

@@ -1,16 +1,18 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import { Login } from '@edx/paragon/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
function SocialAuthProviders(props) {
const { intl, referrer, socialAuthProviders } = props;
const SocialAuthProviders = (props) => {
const { formatMessage } = useIntl();
const { referrer, socialAuthProviders } = props;
function handleSubmit(e) {
e.preventDefault();
@@ -30,29 +32,30 @@ function SocialAuthProviders(props) {
>
{provider.iconImage ? (
<div aria-hidden="true">
<img className="icon-image" src={provider.iconImage} alt={`icon ${provider.name}`} />
<img className="btn-tpa__image-icon" src={provider.iconImage} alt={`icon ${provider.name}`} />
</div>
)
: (
<>
<div className="font-container" aria-hidden="true">
<FontAwesomeIcon
icon={SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? ['fab', provider.iconClass] : faSignInAlt}
/>
</div>
</>
<div className="btn-tpa__font-container" aria-hidden="true">
{SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? (
<FontAwesomeIcon icon={['fab', provider.iconClass]} />)
: (
<Icon className="h-75" src={Login} />
)}
</div>
)}
<span id="provider-name" className="notranslate mr-auto pl-2" aria-hidden="true">{provider.name}</span>
<span className="sr-only">
{referrer === LOGIN_PAGE
? intl.formatMessage(messages['sso.sign.in.with'], { providerName: provider.name })
: intl.formatMessage(messages['sso.create.account.using'], { providerName: provider.name })}
? formatMessage(messages['sso.sign.in.with'], { providerName: provider.name })
: formatMessage(messages['sso.create.account.using'], { providerName: provider.name })}
</span>
</button>
));
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{socialAuth}</>;
}
};
SocialAuthProviders.defaultProps = {
referrer: LOGIN_PAGE,
@@ -60,7 +63,6 @@ SocialAuthProviders.defaultProps = {
};
SocialAuthProviders.propTypes = {
intl: PropTypes.objectOf(PropTypes.object).isRequired,
referrer: PropTypes.string,
socialAuthProviders: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
@@ -69,7 +71,8 @@ SocialAuthProviders.propTypes = {
iconImage: PropTypes.string,
loginUrl: PropTypes.string,
registerUrl: PropTypes.string,
skipRegistrationForm: PropTypes.bool,
})),
};
export default injectIntl(SocialAuthProviders);
export default SocialAuthProviders;

View File

@@ -1,22 +1,23 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import PropTypes from 'prop-types';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import messages from './messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
const ThirdPartyAuthAlert = (props) => {
const { currentProvider, intl, referrer } = props;
const { formatMessage } = useIntl();
const { currentProvider, referrer } = props;
const platformName = getConfig().SITE_NAME;
let message;
if (referrer === LOGIN_PAGE) {
message = intl.formatMessage(messages['login.third.party.auth.account.not.linked'], { currentProvider, platformName });
message = formatMessage(messages['login.third.party.auth.account.not.linked'], { currentProvider, platformName });
} else {
message = intl.formatMessage(messages['register.third.party.auth.account.not.linked'], { currentProvider, platformName });
message = formatMessage(messages['register.third.party.auth.account.not.linked'], { currentProvider, platformName });
}
if (!currentProvider) {
@@ -25,14 +26,14 @@ const ThirdPartyAuthAlert = (props) => {
return (
<>
<Alert id="tpa-alert" className={referrer === REGISTER_PAGE ? 'alert-success mt-n2' : 'alert-warning mt-n2'}>
<Alert id="tpa-alert" className={referrer === REGISTER_PAGE ? 'alert-success mt-n2 mb-5' : 'alert-warning mt-n2 mb-5'}>
{referrer === REGISTER_PAGE ? (
<Alert.Heading>{intl.formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
<Alert.Heading>{formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
) : null}
<p>{ message }</p>
</Alert>
{referrer === REGISTER_PAGE ? (
<h4 className="mt-4 mb-4">{intl.formatMessage(messages['registration.using.tpa.form.heading'])}</h4>
<h4 className="mt-4 mb-4">{formatMessage(messages['registration.using.tpa.form.heading'])}</h4>
) : null}
</>
);
@@ -45,8 +46,7 @@ ThirdPartyAuthAlert.defaultProps = {
ThirdPartyAuthAlert.propTypes = {
currentProvider: PropTypes.string,
intl: PropTypes.objectOf(PropTypes.object).isRequired,
referrer: PropTypes.string,
};
export default injectIntl(ThirdPartyAuthAlert);
export default ThirdPartyAuthAlert;

View File

@@ -1,16 +1,18 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import { DEFAULT_REDIRECT_URL } from '../data/constants';
import {
DEFAULT_REDIRECT_URL,
} from '../data/constants';
/**
* This wrapper redirects the requester to our default redirect url if they are
* already authenticated.
*/
const UnAuthOnlyRoute = (props) => {
const UnAuthOnlyRoute = ({ children }) => {
const [authUser, setAuthUser] = useState({});
const [isReady, setIsReady] = useState(false);
@@ -27,10 +29,14 @@ const UnAuthOnlyRoute = (props) => {
return null;
}
return <Route {...props} />;
return children;
}
return <></>;
return null;
};
UnAuthOnlyRoute.propTypes = {
children: PropTypes.node.isRequired,
};
export default UnAuthOnlyRoute;

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import Zendesk from 'react-zendesk';
import messages from './messages';
import { REGISTER_EMBEDDED_PAGE } from '../data/constants';
const ZendeskHelp = () => {
const { formatMessage } = useIntl();
const setting = {
cookies: true,
webWidget: {
contactOptions: {
enabled: false,
},
chat: {
suppress: false,
departments: {
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
},
},
contactForm: {
ticketForms: [
{
id: 360003368814,
subject: false,
fields: [{ id: 'description', prefill: { '*': '' } }],
},
],
selectTicketForm: {
'*': formatMessage(messages.selectTicketForm),
},
attachments: true,
},
helpCenter: {
originalArticleButton: true,
},
answerBot: {
suppress: false,
contactOnlyAfterQuery: true,
title: { '*': formatMessage(messages.supportTitle) },
avatar: {
url: getConfig().ZENDESK_LOGO_URL,
name: { '*': formatMessage(messages.supportTitle) },
},
},
},
};
if (window.location.pathname === REGISTER_EMBEDDED_PAGE) {
return null;
}
return (
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
);
};
export default ZendeskHelp;

View File

@@ -1,6 +1,7 @@
import { AsyncActionType } from '../../data/utils';
export const THIRD_PARTY_AUTH_CONTEXT = new AsyncActionType('THIRD_PARTY_AUTH', 'GET_THIRD_PARTY_AUTH_CONTEXT');
export const THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG = 'THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG';
// Third party auth context
export const getThirdPartyAuthContext = (urlParams) => ({
@@ -20,3 +21,7 @@ export const getThirdPartyAuthContextSuccess = (fieldDescriptions, optionalField
export const getThirdPartyAuthContextFailure = () => ({
type: THIRD_PARTY_AUTH_CONTEXT.FAILURE,
});
export const clearThirdPartyAuthContextErrorMessage = () => ({
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
});

View File

@@ -1,21 +1,29 @@
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
import { THIRD_PARTY_AUTH_CONTEXT } from './actions';
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';
export const defaultState = {
fieldDescriptions: {},
optionalFields: {},
fieldDescriptions: {
fields: {},
},
optionalFields: {
fields: {},
extended_profile: [],
},
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext: {
autoSubmitRegForm: false,
currentProvider: null,
finishAuthUrl: null,
countryCode: null,
providers: [],
secondaryProviders: [],
pipelineUserDetails: null,
errorMessage: null,
welcomePageRedirectUrl: null,
},
};
const reducer = (state = defaultState, action) => {
const reducer = (state = defaultState, action = {}) => {
switch (action.type) {
case THIRD_PARTY_AUTH_CONTEXT.BEGIN:
return {
@@ -34,7 +42,20 @@ const reducer = (state = defaultState, action) => {
case THIRD_PARTY_AUTH_CONTEXT.FAILURE:
return {
...state,
thirdPartyAuthApiStatus: COMPLETE_STATE,
thirdPartyAuthApiStatus: FAILURE_STATE,
thirdPartyAuthContext: {
...state.thirdPartyAuthContext,
errorMessage: null,
},
};
case THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG:
return {
...state,
thirdPartyAuthApiStatus: PENDING_STATE,
thirdPartyAuthContext: {
...state.thirdPartyAuthContext,
errorMessage: null,
},
};
default:
return state;

View File

@@ -1,7 +1,6 @@
import { logError } from '@edx/frontend-platform/logging';
import { call, put, takeEvery } from 'redux-saga/effects';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
import {
getThirdPartyAuthContextBegin,
getThirdPartyAuthContextFailure,
@@ -11,18 +10,17 @@ import {
import {
getThirdPartyAuthContext,
} from './service';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
export function* fetchThirdPartyAuthContext(action) {
try {
yield put(getThirdPartyAuthContextBegin());
const { fieldDescriptions, optionalFields, thirdPartyAuthContext } = yield call(
getThirdPartyAuthContext, action.payload.urlParams,
);
const {
fieldDescriptions, optionalFields, thirdPartyAuthContext,
} = yield call(getThirdPartyAuthContext, action.payload.urlParams);
yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode));
yield put(getThirdPartyAuthContextSuccess(
fieldDescriptions, optionalFields, thirdPartyAuthContext,
));
yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext));
} catch (e) {
yield put(getThirdPartyAuthContextFailure());
logError(e);

View File

@@ -1,4 +1,4 @@
import { camelCaseObject, convertKeyNames, getConfig } from '@edx/frontend-platform';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
// eslint-disable-next-line import/prefer-default-export
@@ -18,10 +18,8 @@ export async function getThirdPartyAuthContext(urlParams) {
throw (e);
});
return {
fieldDescriptions: data.registration_fields || {},
optionalFields: data.optional_fields || {},
thirdPartyAuthContext: camelCaseObject(
convertKeyNames(data.context_data, { fullname: 'name' }),
),
fieldDescriptions: data.registrationFields || {},
optionalFields: data.optionalFields || {},
thirdPartyAuthContext: data.contextData || {},
};
}

View File

@@ -1,4 +1,5 @@
import { THIRD_PARTY_AUTH_CONTEXT } from '../actions';
import { PENDING_STATE } from '../../../data/constants';
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from '../actions';
import reducer from '../reducers';
describe('common components reducer', () => {
@@ -14,6 +15,7 @@ describe('common components reducer', () => {
providers: [],
secondaryProviders: [],
pipelineUserDetails: null,
errorMessage: null,
},
};
const fieldDescriptions = {
@@ -43,4 +45,38 @@ describe('common components reducer', () => {
},
);
});
it('should clear tpa context error message', () => {
const state = {
fieldDescriptions: {},
optionalFields: {},
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
countryCode: null,
providers: [],
secondaryProviders: [],
pipelineUserDetails: null,
errorMessage: 'An error occurred',
},
};
const action = {
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
};
expect(
reducer(state, action),
).toEqual(
{
...state,
thirdPartyAuthApiStatus: PENDING_STATE,
thirdPartyAuthContext: {
...state.thirdPartyAuthContext,
errorMessage: null,
},
},
);
});
});

View File

@@ -1,5 +1,6 @@
export { default as RedirectLogistration } from './RedirectLogistration';
export { default as registerIcons } from './RegisterFaIcons';
export { default as EmbeddedRegistrationRoute } from './EmbeddedRegistrationRoute';
export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute';
export { default as NotFoundPage } from './NotFoundPage';
export { default as SocialAuthProviders } from './SocialAuthProviders';
@@ -11,4 +12,4 @@ export { default as saga } from './data/sagas';
export { storeName } from './data/selectors';
export { default as FormGroup } from './FormGroup';
export { default as PasswordField } from './PasswordField';
export { default as Logistration } from './Logistration';
export { default as Zendesk } from './Zendesk';

View File

@@ -29,6 +29,11 @@ const messages = defineMessages({
defaultMessage: 'Show me other ways to sign in or register',
description: 'Button text for login',
},
'enterprisetpa.login.button.text.public.account.creation.disabled': {
id: 'enterprisetpa.login.button.text.public.account.creation.disabled',
defaultMessage: 'Show me other ways to sign in',
description: 'Button text for login when account creation is disabled',
},
// social auth providers
'sso.sign.in.with': {
id: 'sso.sign.in.with',
@@ -97,6 +102,16 @@ const messages = defineMessages({
defaultMessage: 'Finish creating your account',
description: 'Heading that appears above form when user is trying to create account using social auth',
},
supportTitle: {
id: 'zendesk.supportTitle',
description: 'Title for the support button',
defaultMessage: 'edX Support',
},
selectTicketForm: {
id: 'zendesk.selectTicketForm',
description: 'Select ticket form',
defaultMessage: 'Please choose your request type:',
},
});
export default messages;

View File

@@ -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');
});
});

View File

@@ -1,9 +1,13 @@
import React from 'react';
import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { fetchRealtimeValidations } from '../../register/data/actions';
import FormGroup from '../FormGroup';
import PasswordField from '../PasswordField';
@@ -26,10 +30,27 @@ describe('FormGroup', () => {
});
describe('PasswordField', () => {
const mockStore = configureStore();
const IntlPasswordField = injectIntl(PasswordField);
let props = {};
let store = {};
const reduxWrapper = children => (
<IntlProvider locale="en">
<MemoryRouter>
<Provider store={store}>{children}</Provider>
</MemoryRouter>
</IntlProvider>
);
const initialState = {
register: {
validationApiRateLimited: false,
},
};
beforeEach(() => {
store = mockStore(initialState);
props = {
floatingLabel: 'Password',
name: 'password',
@@ -39,7 +60,7 @@ describe('PasswordField', () => {
});
it('should show/hide password on icon click', () => {
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('button[aria-label="Show password"]').simulate('click');
expect(passwordField.find('input').prop('type')).toEqual('text');
@@ -49,7 +70,7 @@ describe('PasswordField', () => {
});
it('should show password requirement tooltip on focus', async () => {
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
jest.useFakeTimers();
await act(async () => {
passwordField.find('input').simulate('focus');
@@ -67,7 +88,7 @@ describe('PasswordField', () => {
};
jest.useFakeTimers();
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
await act(async () => {
passwordField.find('input').simulate('focus');
jest.runAllTimers();
@@ -80,7 +101,7 @@ describe('PasswordField', () => {
});
it('should update password requirement checks', async () => {
const passwordField = mount(<IntlProvider locale="en"><IntlPasswordField {...props} /></IntlProvider>);
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
jest.useFakeTimers();
await act(async () => {
passwordField.find('input').simulate('focus');
@@ -92,4 +113,142 @@ describe('PasswordField', () => {
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon text-success mr-1');
});
it('should not run validations when blur is fired on password icon click', () => {
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('button[aria-label="Show password"]').simulate('blur', {
target: {
name: 'password',
value: 'invalid',
},
relatedTarget: {
name: 'passwordIcon',
},
});
expect(passwordField.find('div[feedback-for="password"]').exists()).toBeFalsy();
});
it('should call props handle blur if available', () => {
props = {
...props,
handleBlur: jest.fn(),
};
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('input#password').simulate('blur', {
target: {
name: 'password',
value: '',
},
});
expect(props.handleBlur).toHaveBeenCalledTimes(1);
});
it('should run validations on blur event when rendered from register page', () => {
props = {
...props,
handleErrorChange: jest.fn(),
};
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('input#password').simulate('blur', {
target: {
name: 'password',
value: '',
},
});
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'password',
'Password criteria has not been met',
);
});
it('should not clear error when focus is fired on password icon click when rendered from register page', () => {
props = {
...props,
handleErrorChange: jest.fn(),
};
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('button[aria-label="Show password"]').simulate('focus', {
target: {
name: 'passwordIcon',
value: '',
},
});
expect(props.handleErrorChange).toHaveBeenCalledTimes(0);
});
it('should clear error when focus is fired on password icon click when rendered from register page', () => {
props = {
...props,
handleErrorChange: jest.fn(),
};
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('button[aria-label="Show password"]').simulate('focus', {
target: {
name: 'password',
value: 'invalid',
},
});
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'password',
'',
);
});
it('should run backend validations when frontend validations pass on blur when rendered from register page', () => {
store.dispatch = jest.fn(store.dispatch);
props = {
...props,
handleErrorChange: jest.fn(),
};
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('button[aria-label="Show password"]').simulate('blur', {
target: {
name: 'password',
value: 'password123',
},
});
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ password: 'password123' }));
});
it('should use password value from prop when password icon is focused out (blur due to icon)', () => {
store.dispatch = jest.fn(store.dispatch);
props = {
...props,
value: 'testPassword',
handleErrorChange: jest.fn(),
handleBlur: jest.fn(),
};
const passwordField = mount(reduxWrapper(<IntlPasswordField {...props} />));
passwordField.find('button[aria-label="Show password"]').simulate('blur', {
target: {
name: 'passwordIcon',
value: undefined,
},
});
expect(props.handleBlur).toHaveBeenCalledTimes(1);
expect(props.handleBlur).toHaveBeenCalledWith({
target: {
name: 'password',
value: 'testPassword',
},
});
});
});

View File

@@ -1,13 +1,22 @@
/* eslint-disable import/no-import-module-exports */
/* eslint-disable react/function-component-definition */
import React from 'react';
import * as auth from '@edx/frontend-platform/auth';
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { mount } from 'enzyme';
import { MemoryRouter, BrowserRouter as Router, Switch } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
import { UnAuthOnlyRoute } from '..';
import { LOGIN_PAGE } from '../../data/constants';
import { REGISTER_PAGE } from '../../data/constants';
jest.mock('@edx/frontend-platform/auth');
import {
MemoryRouter, Route, BrowserRouter as Router, Routes,
} from 'react-router-dom';
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(),
fetchAuthenticatedUser: jest.fn(),
}));
const RRD = require('react-router-dom');
// Just render plain div with its children
@@ -18,16 +27,16 @@ module.exports = RRD;
const TestApp = () => (
<Router>
<div>
<Switch>
<UnAuthOnlyRoute path={LOGIN_PAGE} render={() => (<span>Login Page</span>)} />
</Switch>
<Routes>
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><span>Register Page</span></UnAuthOnlyRoute>} />
</Routes>
</div>
</Router>
);
describe('UnAuthOnlyRoute', () => {
const routerWrapper = () => (
<MemoryRouter initialEntries={[LOGIN_PAGE]}>
<MemoryRouter initialEntries={[REGISTER_PAGE]}>
<TestApp />
</MemoryRouter>
);
@@ -36,25 +45,30 @@ describe('UnAuthOnlyRoute', () => {
jest.clearAllMocks();
});
it('should have called with forceRefresh true', () => {
it('should have called with forceRefresh true', async () => {
const user = {
username: 'gonzo',
other: 'data',
};
auth.getAuthenticatedUser = jest.fn(() => user);
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
mount(routerWrapper());
getAuthenticatedUser.mockReturnValue(user);
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(user));
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
await act(async () => {
await mount(routerWrapper());
});
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: true });
});
it('should have called with forceRefresh false', () => {
auth.getAuthenticatedUser = jest.fn(() => null);
auth.fetchAuthenticatedUser = jest.fn(() => ({ then: () => auth.getAuthenticatedUser() }));
it('should have called with forceRefresh false', async () => {
getAuthenticatedUser.mockReturnValue(null);
fetchAuthenticatedUser.mockReturnValueOnce(Promise.resolve(null));
mount(routerWrapper());
await act(async () => {
await mount(routerWrapper());
});
expect(auth.fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
expect(fetchAuthenticatedUser).toBeCalledWith({ forceRefresh: false });
});
});

View File

@@ -0,0 +1,17 @@
import { IntlProvider } from '@edx/frontend-platform/i18n';
import renderer from 'react-test-renderer';
import Zendesk from '../Zendesk';
jest.mock('react-zendesk', () => 'Zendesk');
describe('Zendesk Help', () => {
it('should match login page third party auth alert message snapshot', () => {
const tree = renderer.create(
<IntlProvider locale="en">
<Zendesk />
</IntlProvider>,
).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -10,25 +10,27 @@ exports[`SocialAuthProviders should match social auth provider with default icon
>
<div
aria-hidden="true"
className="font-container"
className="btn-tpa__font-container"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-right-to-bracket "
data-icon="right-to-bracket"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
<span
className="pgn__icon h-75"
>
<path
d="M352 96h64c17.7 0 32 14.3 32 32V384c0 17.7-14.3 32-32 32H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h64c53 0 96-43 96-96V128c0-53-43-96-96-96H352c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-7.5 177.4c4.8-4.5 7.5-10.8 7.5-17.4s-2.7-12.9-7.5-17.4l-144-136c-7-6.6-17.2-8.4-26-4.6s-14.5 12.5-14.5 22v72H32c-17.7 0-32 14.3-32 32v64c0 17.7 14.3 32 32 32H160v72c0 9.6 5.7 18.2 14.5 22s19 2 26-4.6l144-136z"
fill="currentColor"
style={Object {}}
/>
</svg>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 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>

View File

@@ -2,7 +2,7 @@
exports[`ThirdPartyAuthAlert should match login page third party auth alert message snapshot 1`] = `
<div
className="fade alert-content alert-warning mt-n2 alert show"
className="fade alert-content alert-warning mt-n2 mb-5 alert show"
id="tpa-alert"
role="alert"
>
@@ -23,7 +23,7 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
exports[`ThirdPartyAuthAlert should match register page third party auth alert message snapshot 1`] = `
Array [
<div
className="fade alert-content alert-success mt-n2 alert show"
className="fade alert-content alert-success mt-n2 mb-5 alert show"
id="tpa-alert"
role="alert"
>

View File

@@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Zendesk Help should match login page third party auth alert message snapshot 1`] = `
<Zendesk
cookies={true}
defer={true}
webWidget={
Object {
"answerBot": Object {
"avatar": Object {
"name": Object {
"*": "edX Support",
},
"url": undefined,
},
"contactOnlyAfterQuery": true,
"suppress": false,
"title": Object {
"*": "edX Support",
},
},
"chat": Object {
"departments": Object {
"enabled": Array [
"account settings",
"billing and payments",
"certificates",
"deadlines",
"errors and technical issues",
"other",
"proctoring",
],
},
"suppress": false,
},
"contactForm": Object {
"attachments": true,
"selectTicketForm": Object {
"*": "Please choose your request type:",
},
"ticketForms": Array [
Object {
"fields": Array [
Object {
"id": "description",
"prefill": Object {
"*": "",
},
},
],
"id": 360003368814,
"subject": false,
},
],
},
"contactOptions": Object {
"enabled": false,
},
"helpCenter": Object {
"originalArticleButton": true,
},
}
}
/>
`;

View File

@@ -1,14 +1,12 @@
const configuration = {
// Cookies related configs
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
// Features
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
ENABLE_COOKIE_POLICY_BANNER: process.env.ENABLE_COOKIE_POLICY_BANNER || false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
ENABLE_PERSONALIZED_RECOMMENDATIONS: process.env.ENABLE_PERSONALIZED_RECOMMENDATIONS || false,
ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false,
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
// Links
@@ -16,12 +14,24 @@ const configuration = {
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK || null,
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
POST_REGISTRATION_REDIRECT_URL: process.env.POST_REGISTRATION_REDIRECT_URL || '',
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
TOS_LINK: process.env.TOS_LINK || null,
// Miscellaneous
// Base container images
BANNER_IMAGE_LARGE: process.env.BANNER_IMAGE_LARGE || '',
BANNER_IMAGE_MEDIUM: process.env.BANNER_IMAGE_MEDIUM || '',
BANNER_IMAGE_SMALL: process.env.BANNER_IMAGE_SMALL || '',
BANNER_IMAGE_EXTRA_SMALL: process.env.BANNER_IMAGE_EXTRA_SMALL || '',
// Recommendation constants
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
// Miscellaneous
INFO_EMAIL: process.env.INFO_EMAIL || '',
ZENDESK_KEY: process.env.ZENDESK_KEY,
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
};
export default configuration;

20
src/data/algolia.js Normal file
View File

@@ -0,0 +1,20 @@
import { getConfig } from '@edx/frontend-platform';
import algoliasearch from 'algoliasearch';
// initialize Algolia workers
const initializeSearchClient = () => algoliasearch(
getConfig().ALGOLIA_APP_ID,
getConfig().ALGOLIA_SEARCH_API_KEY,
);
const getLocationRestrictionFilter = (userCountry) => {
if (userCountry) {
return `NOT blocked_in:"${userCountry}" AND (allowed_in:"null" OR allowed_in:"${userCountry}")`;
}
return '';
};
export {
initializeSearchClient,
getLocationRestrictionFilter,
};

View File

@@ -1,6 +1,7 @@
// URL Paths
export const LOGIN_PAGE = '/login';
export const REGISTER_PAGE = '/register';
export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
export const RESET_PAGE = '/reset';
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
export const DEFAULT_REDIRECT_URL = '/dashboard';
@@ -23,16 +24,16 @@ export const PENDING_STATE = 'pending';
export const COMPLETE_STATE = 'complete';
export const FAILURE_STATE = 'failure';
export const FORBIDDEN_STATE = 'forbidden';
export const EMBEDDED = 'embedded';
// Regex
export const LETTER_REGEX = /[a-zA-Z]/;
export const NUMBER_REGEX = /\d/;
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
export const LETTER_REGEX = /[a-zA-Z]/;
export const NUMBER_REGEX = /\d/;
export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi; // eslint-disable-line no-useless-escape
// Query string parameters that can be passed to LMS to manage
// things like auto-enrollment upon login and registration.
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'save_for_later', 'register_for_free'];
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta'];
export const REDIRECT = 'redirect';

3
src/data/oneTrust.js Normal file
View File

@@ -0,0 +1,3 @@
const isOneTrustFunctionalCookieEnabled = () => !!window?.OnetrustActiveGroups?.includes('C0003');
export default isOneTrustFunctionalCookieEnabled;

17
src/data/optimizely.js Normal file
View File

@@ -0,0 +1,17 @@
import {
createInstance,
} from '@optimizely/react-sdk';
const OPTIMIZELY_SDK_KEY = process.env.OPTIMIZELY_FULL_STACK_SDK_KEY;
const getOptimizelyInstance = () => {
if (OPTIMIZELY_SDK_KEY) {
return createInstance({
sdkKey: OPTIMIZELY_SDK_KEY,
});
}
return null;
};
export default getOptimizelyInstance();

View File

@@ -0,0 +1,16 @@
import { getLocationRestrictionFilter } from '../algolia';
describe('algoliaUtilsTests', () => {
it('test getLocationRestrictionFilter returns filter if country is passed', () => {
const countryCode = 'PK';
const filter = getLocationRestrictionFilter(countryCode);
const expectedFilter = `NOT blocked_in:"${countryCode}" AND (allowed_in:"null" OR allowed_in:"${countryCode}")`;
expect(filter).toEqual(expectedFilter);
});
it('test getLocationRestrictionFilter returns empty string if country is not passed', () => {
const countryCode = '';
const filter = getLocationRestrictionFilter(countryCode);
const expectedFilter = '';
expect(filter).toEqual(expectedFilter);
});
});

View File

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

View File

@@ -1,4 +1,4 @@
import AsyncActionType from './reduxUtils';
import AsyncActionType from '../utils/reduxUtils';
describe('AsyncActionType', () => {
it('should return well formatted action strings', () => {

View File

@@ -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);
}
}

View File

@@ -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;
};

View File

@@ -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';

View File

@@ -142,6 +142,7 @@ FormFieldRenderer.propTypes = {
type: PropTypes.string,
label: PropTypes.string,
name: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
}).isRequired,
onChangeHandler: PropTypes.func.isRequired,
handleBlur: PropTypes.func,

View File

@@ -1 +1,2 @@
export { default } from './FieldRenderer';
/* eslint-disable import/prefer-default-export */
export { default as FormFieldRenderer } from './FieldRenderer';

View File

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

View File

@@ -1,21 +1,22 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { CheckCircle, Error } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import messages from './messages';
import {
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
} from '../data/constants';
import { PASSWORD_RESET } from '../reset-password/data/constants';
import messages from './messages';
const ForgotPasswordAlert = (props) => {
const { email, emailError, intl } = props;
const { formatMessage } = useIntl();
const { email, emailError } = props;
let message = '';
let heading = intl.formatMessage(messages['forgot.password.error.alert.title']);
let heading = formatMessage(messages['forgot.password.error.alert.title']);
let { status } = props;
if (emailError) {
@@ -24,7 +25,7 @@ const ForgotPasswordAlert = (props) => {
switch (status) {
case COMPLETE_STATE:
heading = intl.formatMessage(messages['confirmation.message.title']);
heading = formatMessage(messages['confirmation.message.title']);
message = (
<FormattedMessage
id="forgot.password.confirmation.message"
@@ -36,7 +37,7 @@ const ForgotPasswordAlert = (props) => {
email: <span className="data-hj-suppress">{email}</span>,
supportLink: (
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK} target="_blank">
{intl.formatMessage(messages['confirmation.support.link'])}
{formatMessage(messages['confirmation.support.link'])}
</Alert.Link>
),
}}
@@ -44,26 +45,26 @@ const ForgotPasswordAlert = (props) => {
);
break;
case INTERNAL_SERVER_ERROR:
message = intl.formatMessage(messages['internal.server.error']);
message = formatMessage(messages['internal.server.error']);
break;
case FORBIDDEN_STATE:
heading = intl.formatMessage(messages['forgot.password.error.message.title']);
message = intl.formatMessage(messages['forgot.password.request.in.progress.message']);
heading = formatMessage(messages['forgot.password.error.message.title']);
message = formatMessage(messages['forgot.password.request.in.progress.message']);
break;
case FORM_SUBMISSION_ERROR:
message = intl.formatMessage(messages['extend.field.errors'], { emailError });
message = formatMessage(messages['extend.field.errors'], { emailError });
break;
case PASSWORD_RESET.INVALID_TOKEN:
heading = intl.formatMessage(messages['invalid.token.heading']);
message = intl.formatMessage(messages['invalid.token.error.message']);
heading = formatMessage(messages['invalid.token.heading']);
message = formatMessage(messages['invalid.token.error.message']);
break;
case PASSWORD_RESET.FORBIDDEN_REQUEST:
heading = intl.formatMessage(messages['token.validation.rate.limit.error.heading']);
message = intl.formatMessage(messages['token.validation.rate.limit.error']);
heading = formatMessage(messages['token.validation.rate.limit.error.heading']);
message = formatMessage(messages['token.validation.rate.limit.error']);
break;
case PASSWORD_RESET.INTERNAL_SERVER_ERROR:
heading = intl.formatMessage(messages['token.validation.internal.sever.error.heading']);
message = intl.formatMessage(messages['token.validation.internal.sever.error']);
heading = formatMessage(messages['token.validation.internal.sever.error.heading']);
message = formatMessage(messages['token.validation.internal.sever.error']);
break;
default:
break;
@@ -93,8 +94,7 @@ ForgotPasswordAlert.defaultProps = {
ForgotPasswordAlert.propTypes = {
status: PropTypes.string.isRequired,
email: PropTypes.string,
intl: PropTypes.objectOf(PropTypes.object).isRequired,
emailError: PropTypes.string,
};
export default injectIntl(ForgotPasswordAlert);
export default ForgotPasswordAlert;

View File

@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form,
Hyperlink,
@@ -15,29 +15,30 @@ import {
import { ChevronLeft } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { Redirect } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import BaseComponent from '../base-component';
import { FormGroup } from '../common-components';
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import ForgotPasswordAlert from './ForgotPasswordAlert';
import messages from './messages';
import BaseContainer from '../base-container';
import { FormGroup } from '../common-components';
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
const ForgotPasswordPage = (props) => {
const platformName = getConfig().SITE_NAME;
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
const {
intl, status, submitState, emailValidationError,
status, submitState, emailValidationError,
} = props;
const { formatMessage } = useIntl();
const [email, setEmail] = useState(props.email);
const [bannerEmail, setBannerEmail] = useState('');
const [formErrors, setFormErrors] = useState('');
const [validationError, setValidationError] = useState(emailValidationError);
const [key, setKey] = useState('');
const navigate = useNavigate();
useEffect(() => {
sendPageEvent('login_and_registration', 'reset');
@@ -58,9 +59,9 @@ const ForgotPasswordPage = (props) => {
let error = '';
if (value === '') {
error = intl.formatMessage(messages['forgot.password.empty.email.field.error']);
error = formatMessage(messages['forgot.password.empty.email.field.error']);
} else if (!emailRegex.test(value)) {
error = intl.formatMessage(messages['forgot.password.page.invalid.email.message']);
error = formatMessage(messages['forgot.password.page.invalid.email.message']);
}
return error;
@@ -89,35 +90,32 @@ const ForgotPasswordPage = (props) => {
const tabTitle = (
<div className="d-inline-flex flex-wrap align-items-center">
<Icon src={ChevronLeft} />
<span className="ml-2">{intl.formatMessage(messages['sign.in.text'])}</span>
<span className="ml-2">{formatMessage(messages['sign.in.text'])}</span>
</div>
);
return (
<BaseComponent>
<BaseContainer>
<Helmet>
<title>{intl.formatMessage(messages['forgot.password.page.title'],
<title>{formatMessage(messages['forgot.password.page.title'],
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<div>
<Tabs activeKey="" id="controlled-tab" onSelect={(k) => setKey(k)}>
<Tabs activeKey="" id="controlled-tab" onSelect={(key) => navigate(updatePathWithQueryParams(key))}>
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
</Tabs>
{ key && (
<Redirect to={updatePathWithQueryParams(key)} />
)}
<div id="main-content" className="main-content">
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
<h2 className="h4">
{intl.formatMessage(messages['forgot.password.page.heading'])}
{formatMessage(messages['forgot.password.page.heading'])}
</h2>
<p className="mb-4">
{intl.formatMessage(messages['forgot.password.page.instructions'])}
{formatMessage(messages['forgot.password.page.instructions'])}
</p>
<FormGroup
floatingLabel={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
floatingLabel={formatMessage(messages['forgot.password.page.email.field.label'])}
name="email"
value={email}
autoComplete="on"
@@ -125,17 +123,17 @@ const ForgotPasswordPage = (props) => {
handleChange={(e) => setEmail(e.target.value)}
handleBlur={handleBlur}
handleFocus={handleFocus}
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
helpText={[formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
/>
<StatefulButton
id="submit-forget-password"
name="submit-forget-password"
type="submit"
variant="brand"
className="forgot-password-button-width"
className="forgot-password--button"
state={submitState}
labels={{
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
default: formatMessage(messages['forgot.password.page.submit.button']),
pending: '',
}}
onClick={handleSubmit}
@@ -150,11 +148,11 @@ const ForgotPasswordPage = (props) => {
target="_blank"
showLaunchIcon={false}
>
{intl.formatMessage(messages['need.help.sign.in.text'])}
{formatMessage(messages['need.help.sign.in.text'])}
</Hyperlink>
)}
<p className="mt-5.5 small text-gray-700">
{intl.formatMessage(messages['additional.help.text'], { platformName })}
{formatMessage(messages['additional.help.text'], { platformName })}
<span>
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
</span>
@@ -162,7 +160,7 @@ const ForgotPasswordPage = (props) => {
</Form>
</div>
</div>
</BaseComponent>
</BaseContainer>
);
};
@@ -170,7 +168,6 @@ ForgotPasswordPage.propTypes = {
email: PropTypes.string,
emailValidationError: PropTypes.string,
forgotPassword: PropTypes.func.isRequired,
intl: PropTypes.objectOf(PropTypes.object).isRequired,
setForgotPasswordFormData: PropTypes.func.isRequired,
status: PropTypes.string,
submitState: PropTypes.string,
@@ -189,4 +186,4 @@ export default connect(
forgotPassword,
setForgotPasswordFormData,
},
)(injectIntl(ForgotPasswordPage));
)(ForgotPasswordPage);

View File

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

View File

@@ -1,4 +1,4 @@
export { default } from './ForgotPasswordPage';
export { default as ForgotPasswordPage } from './ForgotPasswordPage';
export { default as reducer } from './data/reducers';
export { FORGOT_PASSWORD } from './data/actions';
export { default as saga } from './data/sagas';

View File

@@ -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);
});
});

View File

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

View File

@@ -1,14 +1,17 @@
{
"start.learning": "ابدأ التعلم ",
"with.site.name": "مع {siteName}",
"your.career.turning.point": "Your career turning point",
"is.here": "is here.",
"welcome.to.platform": "أهلا بك {username} في {siteName}",
"complete.your.profile.1": "أكمل",
"complete.your.profile.2": "ملفك الشخصي",
"welcome.to.platform": "أهلا بك {username} في {siteName}",
"institution.login.page.sub.heading": "اختر مؤسستك من القائمة أدناه",
"logistration.sign.in": "تسجيل الدخول",
"logistration.register": "التسجيل",
"enterprisetpa.title.heading": "هل ترغب في تسجيل الدخول باستخدام بيانات {providerName} الخاصة بك؟",
"enterprisetpa.login.button.text": "أرِني وسائل أخرى لتسجيل الدخول أو للتسجيل",
"enterprisetpa.login.button.text.public.account.creation.disabled": "أرني طرقًا أخرى لتسجيل الدخول",
"sso.sign.in.with": "تسجيل الدخول باستخدام {providerName}",
"sso.create.account.using": "إنشاء حساب باستخدام {providerName}",
"show.password": "إظهار كلمة المرور",
@@ -21,6 +24,8 @@
"login.third.party.auth.account.not.linked": "لقد نجحت في تسجيل الدخول إلى {currentProvider}، لكن حسابك على {currentProvider} غير موصول بأي حساب على {platformName}. لوصل حساباتك، سجّل الدخول الآن باستخدام كلمة مرورك على {platformName}.",
"register.third.party.auth.account.not.linked": "لقد سجلت دخولك بنجاح إلى {currentProvider}! نحتاج فقط قليلاً بعدُ من المعلومات قبل أن تبدأ التعلم مع {platformName}.",
"registration.using.tpa.form.heading": "إتمام إنشاء حسابك",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "الرجاء اختيار نوع الطلب الخاص بك:",
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
"forgot.password.confirmation.message": "لقد أرسلنا بريدًا إلكترونيًا إلى {email} به إرشادات لإعادة ضبط كلمة المرور الخاصة بك. إن لم تستلم رسالة إعادة ضبط كلمة المرور بعد دقيقة واحدة، فتحقق من إدخال عنوان البريد الإلكتروني الصحيح، أو تفقد مجلد الرسائل غير المرغوب فيها. إن احتجت مزيدًا من المساعدة، {supportLink}.",
"forgot.password.page.title": "نسيت كلمة المرور | {siteName}",
@@ -66,7 +71,7 @@
"non.compliant.password.message": "كلمة مرورك الحالية لا تستسجيب لمتطلبات الأمان الجديدة. لقد أرسلنا للتو رسالة لإعادة ضبط كلمة المرور إلى عنوان البريد الإلكتروني المرتبط بهذا الحساب. شكرًا لك على مساعدتنا في الحفاظ على سلامة بياناتك.",
"account.locked.out.message.1": "لحماية حسابك، تم إقفاله مؤقتًا. حاول مرة أخرى بعد 30 دقيقة.",
"enterprise.login.btn.text": "بيانات الشركة أو المدرسة",
"username.or.email.format.validation.less.chars.message": "يجب أن يحتوي اسم المستخدم أو البريد الإلكتروني على 3 أحرف على الأقل.",
"username.or.email.format.validation.less.chars.message": "يجب أن يحتوي اسم المستخدم أو البريد الإلكتروني على 2 أحرف على الأقل.",
"email.validation.message": "أدخل اسم المستخدم أو البريد الإلكتروني الخاص بك",
"password.validation.message": "لم يتم استيفاء معايير كلمة المرور",
"account.activation.success.message.title": "نجح الأمر! لقد قمت بتفعيل حسابك.",
@@ -93,20 +98,30 @@
"password.security.block.body": "اكتشف نظامنا أن كلمة مرورك صعيفة. غيّر كلمة مرورك حتى يظل حسابك آمنًا.",
"password.security.close.button": "إغلاق",
"password.security.redirect.to.reset.password.button": "إعادة ضبط كلمة المرور",
"progressive.profiling.page.title": "الحقول الاختيارية | {siteName}",
"login.tpa.authentication.failure": "عذرًا ، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
"progressive.profiling.page.title": "مرحبا بكم | {siteName}",
"progressive.profiling.page.heading": "بعض الأسئلة الموجهة لك ستساعدنا كي نزداد ذكاءً.",
"optional.fields.information.link": "معرفة المزيد عن كيفية استخدامنا لهذه المعلومات.",
"optional.fields.submit.button": "إرسال",
"optional.fields.skip.button": "التخطي مؤقتا",
"optional.fields.next.button": "Next",
"optional.fields.next.button": "التالي",
"continue.to.platform": "المواصلة إلى {platformName}",
"modal.title": "شكرا لإعلامنا.",
"modal.description": "إن غيرت رأيك، قيمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
"welcome.page.error.heading": "لم نتمكن من تحديث ملفك الشخصي",
"welcome.page.error.message": "حدث خطأ ما. يمكنك إكمال ملفك الشخصي ضمن الإعدادات في أي وقت.",
"recommendation.page.title": "Recommendations| {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"recommendation.page.title": "التوصيات | {siteName}",
"recommendation.page.heading": "لدينا بعض التوصيات لكي تبدأ.",
"recommendation.skip.button": "التخطي مؤقتا",
"recommendation.option.trending": "Trending",
"recommendation.option.popular": "Most Popular",
"recommendation.product-card.pill-text.course": "Course",
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
"recommendation.product-card.footer-text.subscription": "Subscription",
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
"register.page.title": "التسجيل | {siteName}",
"registration.fullname.label": "الاسم الكامل",
"registration.email.label": "البريد الإلكتروني",
@@ -120,6 +135,7 @@
"help.text.email": "لتفعيل الحساب و التحديثات الهامة",
"create.account.for.free.button": "إنشاء حساب مجانا",
"registration.other.options.heading": "أو سجل باستخدام:",
"create.account.cta.button": "{label}",
"register.institution.login.button": "بيانات المؤسسة / الجامعة",
"register.institution.login.page.title": "التسجيل باستخدام بيانات المؤسسة / الجامعة",
"empty.name.field.error": "أدخل اسمك الكامل",
@@ -137,6 +153,7 @@
"registration.request.server.error": "حدث خطأ ما. جرب تحديث الصفحة أو تحقق من اتصالك بالإنترنت.",
"registration.rate.limit.error": "كثرت محاولات التسجيل الفاشلة. أعد المحاولة لاحقًا.",
"registration.tpa.session.expired": "نفد وقت التسجيل باستخدام {provider}.",
"registration.tpa.authentication.failure": "عذرًا، غير مصرح لك بالوصول إلى {platform_name} عبر هذه القناة. يرجى الاتصال بمسؤول التعلم أو المدير من أجل الوصول إلى {platform_name}. {lineBreak} {lineBreak} تفاصيل الخطأ: {lineBreak} {errorMessage}",
"terms.of.service.and.honor.code": "شروط الخدمة وميثاق الشرف الأكاديمي",
"privacy.policy": "سياسة الخصوصية",
"honor.code": "ميثاق الشرف الأكاديمي",
@@ -144,8 +161,8 @@
"registration.username.suggestion.label": "مقترح:",
"did.you.mean.alert.text": "هل تقصد",
"register.page.terms.of.service.and.honor.code": "بإنشاءك حسابًا، فإنك توافق على {tosAndHonorCode} و تقر بأن {platformName} و كل عضو يعالج بياناتك الشخصية وفقًا لـ{privacyPolicy}.",
"register.page.honor.code": "I agree to the {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "I agree to the {platformName} {termsOfService}",
"register.page.honor.code": "اوافق على شروط {platformName} {tosAndHonorCode}",
"register.page.terms.of.service": "اوافق على {platformName} {termsOfService}",
"sign.in": "تسجيل الدخول",
"reset.password.page.title": "إعادة ضبط كلمة المرور | {siteName}",
"reset.password": "إعادة ضبط كلمة المرور",
@@ -160,4 +177,4 @@
"reset.password.success.heading": "تمت إعادة ضبط كلمة المرور.",
"reset.password.success": "تمت إعادة ضبط كلمة مرورك. سجل الدخول إلى حسابك.",
"rate.limit.error": "حدث خطأ بسبب كثرة الطلبات. رجاءً حاول مرة أخرى بعد مضي بعض الوقت."
}
}

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

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

View File

@@ -1,14 +1,17 @@
{
"start.learning": "Beginne zu lernen",
"with.site.name": "mit {siteName}",
"your.career.turning.point": "Your career turning point",
"is.here": "is here.",
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
"complete.your.profile.1": "Vervollständige",
"complete.your.profile.2": "dein Profil",
"welcome.to.platform": "Willkommen bei {siteName}, {username}!",
"institution.login.page.sub.heading": "Wählen Sie Ihre Institution aus der folgenden Liste aus",
"logistration.sign.in": "Anmelden",
"logistration.register": "Registrieren",
"enterprisetpa.title.heading": "Möchten Sie sich mit Ihren {providerName}-Anmeldedaten anmelden?",
"enterprisetpa.login.button.text": "Andere Möglichkeiten für die Anmeldung oder Registrierung",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"sso.sign.in.with": "Melden Sie sich mit {providerName} an",
"sso.create.account.using": "Erstellen Sie ein Konto mit {providerName}",
"show.password": "Passwort anzeigen",
@@ -21,6 +24,8 @@
"login.third.party.auth.account.not.linked": "Sie haben sich erfolgreich bei {currentProvider} angemeldet, aber Ihr {currentProvider}-Konto hat kein verknüpftes {platformName}-Konto. Um Ihre Konten zu verknüpfen, melden Sie sich jetzt mit Ihrem {platformName}-Passwort an.",
"register.third.party.auth.account.not.linked": "Sie haben sich erfolgreich bei {currentProvider} angemeldet! Wir brauchen nur ein paar mehr Informationen, bevor Sie anfangen, mit {platformName} zu lernen.",
"registration.using.tpa.form.heading": "Beenden Sie die Erstellung Ihres Kontos",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"error.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
"forgot.password.confirmation.message": "Wir haben eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts an {email} gesendet. Wenn Sie nach 1 Minute keine Nachricht zum Zurücksetzen des Passworts erhalten, überprüfen Sie, ob Sie die richtige E-Mail-Adresse eingegeben haben, oder überprüfen Sie Ihren Spam-Ordner. Wenn Sie weitere Hilfe benötigen, {supportLink}.",
"forgot.password.page.title": "Passwort vergessen | {siteName}",
@@ -66,7 +71,7 @@
"non.compliant.password.message": "Ihr aktuelles Passwort entspricht nicht den neuen Sicherheitsanforderungen. Wir haben gerade eine Nachricht zum Zurücksetzen des Passworts an die mit diesem Konto verknüpfte E-Mail-Adresse gesendet. Vielen Dank, dass Sie uns helfen, Ihre Daten zu schützen.",
"account.locked.out.message.1": "Um Ihr Konto zu schützen, wurde es vorübergehend gesperrt. Versuchen Sie es in 30 Minuten erneut.",
"enterprise.login.btn.text": "Arbeits- oder Schulzeugnisse",
"username.or.email.format.validation.less.chars.message": "Benutzername oder E-Mail müssen mindestens 3 Zeichen lang sein.",
"username.or.email.format.validation.less.chars.message": "Benutzername oder E-Mail müssen mindestens 2 Zeichen lang sein.",
"email.validation.message": "Geben Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse ein",
"password.validation.message": "Die Passwortkriterien wurden nicht erfüllt",
"account.activation.success.message.title": "Super! Sie haben Ihr Konto aktiviert.",
@@ -93,20 +98,30 @@
"password.security.block.body": "Unser System hat festgestellt, dass Ihr Passwort angreifbar ist. Ändern Sie Ihr Passwort, damit Ihr Konto sicher bleibt.",
"password.security.close.button": "Schließen",
"password.security.redirect.to.reset.password.button": "Setzen Sie Ihr Passwort zurück",
"progressive.profiling.page.title": "Optionale Felder | {siteName}",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "Ein paar Fragen an Sie helfen uns, schlauer zu werden.",
"optional.fields.information.link": "Erfahren Sie mehr darüber, wie wir diese Informationen verwenden.",
"optional.fields.submit.button": "Einreichen",
"optional.fields.skip.button": "Überspringen",
"optional.fields.next.button": "Next",
"optional.fields.next.button": "Weiter",
"continue.to.platform": "Weiter zu {platformName}",
"modal.title": "Danke, dass Sie uns das mitteilen.",
"modal.description": "Sie können Ihr Profil jederzeit in den Einstellungen vervollständigen, wenn Sie Ihre Meinung ändern.",
"welcome.page.error.heading": "Wir konnten Ihr Profil nicht aktualisieren",
"welcome.page.error.message": "Ein Fehler ist aufgetreten. Sie können Ihr Profil jederzeit in den Einstellungen vervollständigen.",
"recommendation.page.title": "Recommendations| {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"recommendation.page.title": "Empfehlungen | {siteName}",
"recommendation.page.heading": "Wir haben ein paar Empfehlungen für den Einstieg.",
"recommendation.skip.button": "Überspringen",
"recommendation.option.trending": "Trending",
"recommendation.option.popular": "Most Popular",
"recommendation.product-card.pill-text.course": "Course",
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
"recommendation.product-card.footer-text.subscription": "Subscription",
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
"register.page.title": "Registrieren | {siteName}",
"registration.fullname.label": "Vollständiger Name",
"registration.email.label": "E-Mail-Adresse",
@@ -120,6 +135,7 @@
"help.text.email": "Für die Kontoaktivierung und wichtige Updates",
"create.account.for.free.button": "Erstellen Sie kostenlos ein Benutzerkonto",
"registration.other.options.heading": "Oder registrieren Sie sich bei:",
"create.account.cta.button": "{label}",
"register.institution.login.button": "Zeugnisse der Institution/des Campus",
"register.institution.login.page.title": "Registrieren Sie sich mit Institutions-/Campus-Anmeldeinformationen",
"empty.name.field.error": "Geben Sie Ihren vollständigen Namen ein",
@@ -137,6 +153,7 @@
"registration.request.server.error": "Ein Fehler ist aufgetreten. Versuchen Sie, die Seite zu aktualisieren, oder überprüfen Sie Ihre Internetverbindung.",
"registration.rate.limit.error": "Zu viele fehlgeschlagene Registrierungsversuche. Versuchen Sie es später noch einmal.",
"registration.tpa.session.expired": "Die Registrierung mit {provider} ist abgelaufen.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Nutzungsbedingungen und Verhaltenskodex",
"privacy.policy": "Datenschutzbestimmungen",
"honor.code": "Verhaltenskodex",
@@ -160,4 +177,4 @@
"reset.password.success.heading": "Zurücksetzen des Passworts abgeschlossen.",
"reset.password.success": "Ihr Passwort wurde zurückgesetzt. Melden Sie sich bei Ihrem Konto an.",
"rate.limit.error": "Aufgrund zu vieler Anfragen ist ein Fehler aufgetreten. Bitte versuchen Sie es nach einiger Zeit erneut."
}
}

View File

@@ -1,14 +1,17 @@
{
"start.learning": "Empieza a aprender",
"with.site.name": "con {siteName}",
"your.career.turning.point": "El punto de inflexión de tu carrera",
"is.here": "es aquí.",
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
"complete.your.profile.1": "Completado",
"complete.your.profile.2": "tu perfil ",
"welcome.to.platform": "¡Bienvenido a {siteName}, {username}!",
"institution.login.page.sub.heading": "Selecciona tu institución de la lista siguiente",
"logistration.sign.in": "Iniciar sesión",
"logistration.register": "Registrarse",
"enterprisetpa.title.heading": "¿Deseas iniciar sesión con tus credenciales de {providerName}?",
"enterprisetpa.login.button.text": "Mostrar otras formas de iniciar sesión o de registrarme",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Mostrar otras formas de iniciar sesión",
"sso.sign.in.with": "Inicio de sesión con {providerName}",
"sso.create.account.using": "Crear una cuenta con {providerName}",
"show.password": "Mostrar contraseña",
@@ -21,6 +24,8 @@
"login.third.party.auth.account.not.linked": "Te has registrado correctamente en {currentProvider}, pero tu cuenta de {currentProvider} no tiene una cuenta de {platformName} asociada. Para asociar tus cuentas, inicia sesión ahora usando tu contraseña de {platformName}.",
"register.third.party.auth.account.not.linked": "¡Has iniciado sesión con éxito en {currentProvider}! Sólo necesitamos un poco más de información antes de que empieces a aprender con {platformName}.",
"registration.using.tpa.form.heading": "Termina de crear tu cuenta",
"zendesk.supportTitle": "Soporte edX",
"zendesk.selectTicketForm": "Elegir el tipo de solicitud:",
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, verifica la URL y vuelve a intentarlo.",
"forgot.password.confirmation.message": "Hemos enviado un correo electrónico a {email} con instrucciones para restablecer tu contraseña.\n Si no recibes un mensaje de restablecimiento de contraseña después de 1 minuto, verifica que has introducido\n la dirección de correo electrónico correcta, o comprueba tu carpeta de correo no deseado. Si necesitas más ayuda, {supportLink}.",
"forgot.password.page.title": "Olvidé la contraseña | {siteName}",
@@ -66,7 +71,7 @@
"non.compliant.password.message": "Tu contraseña actual no cumple con los nuevos requisitos de seguridad. Acabamos de enviar un mensaje de restablecimiento de contraseña a la dirección de correo electrónico asociada a esta cuenta. Gracias por ayudarnos a mantener tus datos seguros.",
"account.locked.out.message.1": "Para proteger tu cuenta, se ha bloqueado temporalmente. Inténtalo de nuevo en 30 minutos.",
"enterprise.login.btn.text": "Credenciales de la empresa o de la institución ",
"username.or.email.format.validation.less.chars.message": "El nombre de usuario o el correo electrónico deben tener al menos 3 caracteres.",
"username.or.email.format.validation.less.chars.message": "El nombre de usuario o el correo electrónico deben tener al menos 2 caracteres.",
"email.validation.message": "Introduce tu nombre de usuario o correo electrónico",
"password.validation.message": "No se han cumplido los criterios de la contraseña",
"account.activation.success.message.title": "Ha sido un éxito. Has activado tu cuenta.",
@@ -93,20 +98,30 @@
"password.security.block.body": "Nuestro sistema detectó que su contraseña es vulnerable. Cambie su contraseña para que su cuenta permanezca segura.",
"password.security.close.button": "Cerrar",
"password.security.redirect.to.reset.password.button": "Restablece tu contraseña",
"progressive.profiling.page.title": "Campos opcionales | {siteName}",
"login.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Bienvenido | {siteName}",
"progressive.profiling.page.heading": "Unas cuantas preguntas para ti nos ayudarán a mejorar.",
"optional.fields.information.link": "Aprende más sobre cómo usamos esta información.",
"optional.fields.submit.button": "Enviar",
"optional.fields.skip.button": "Saltar por ahora ",
"optional.fields.next.button": "Next",
"optional.fields.next.button": "Siguiente",
"continue.to.platform": "Continuar a {platformName}",
"modal.title": "Gracias por informarnos.",
"modal.description": "Puedes completar tu perfil en los ajustes en cualquier momento si cambias de opinión.",
"welcome.page.error.heading": "No hemos podido actualizar tu perfil",
"welcome.page.error.message": "Se ha producido un error. Puedes completar tu perfil en los ajustes en cualquier momento.",
"recommendation.page.title": "Recommendations| {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"recommendation.page.title": "Recomendaciones | {siteName}",
"recommendation.page.heading": "Tenemos algunas recomendaciones para empezar.",
"recommendation.skip.button": "Saltar por ahora ",
"recommendation.option.trending": "Tendencias",
"recommendation.option.popular": "Más popular",
"recommendation.product-card.pill-text.course": "Curso",
"recommendation.product-card.pill-text.professional-certificate": "Certificado profesional",
"recommendation.product-card.pill-text.emeritus": "Ofrecido en Emeritus",
"recommendation.product-card.pill-text.shorelight": "Ofrecido a través de Shorelight",
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
"recommendation.product-card.footer-text.subscription": "Suscripción",
"recommendation.product-card.launch-icon.sr-text": "Abrir un enlace en una pestaña nueva",
"register.page.title": "Register | {siteName}",
"registration.fullname.label": "Nombre completo",
"registration.email.label": "Correo electrónico",
@@ -120,6 +135,7 @@
"help.text.email": "Para la activación de la cuenta y las actualizaciones importantes",
"create.account.for.free.button": "Crea una cuenta gratis",
"registration.other.options.heading": "O regístrese con:",
"create.account.cta.button": "{label}",
"register.institution.login.button": "Credenciales de la institución/campus",
"register.institution.login.page.title": "Registro con credenciales de la institución/campus",
"empty.name.field.error": "Introduce tu nombre completo",
@@ -137,6 +153,7 @@
"registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.",
"registration.rate.limit.error": "Demasiados intentos de registro fallidos. Vuelve a intentarlo más tarde.",
"registration.tpa.session.expired": "Inscripción usando {provider} ha expirado.",
"registration.tpa.authentication.failure": "Lo sentimos, no está autorizado para acceder a {platform_name} a través de este canal. Comuníquese con su administrador o gerente de aprendizaje para acceder a {platform_name}.{lineBreak}{lineBreak}Detalles del error:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Condiciones de servicio y código de honor",
"privacy.policy": "Política de privacidad ",
"honor.code": "Código de Honor",
@@ -160,4 +177,4 @@
"reset.password.success.heading": "Restablecimiento de la contraseña completado.",
"reset.password.success": "Tu contraseña ha sido restablecida. Acceda a tu cuenta.",
"rate.limit.error": "Se ha producido un error debido a demasiadas solicitudes. Por favor, inténtalo de nuevo después de algún tiempo."
}
}

View File

@@ -0,0 +1,180 @@
{
"start.learning": "آغاز یادگیری",
"with.site.name": "با {siteName}",
"your.career.turning.point": "نقطه عطف حرفه ای شما",
"is.here": "اینجاست.",
"welcome.to.platform": "خوش آمدید به {siteName}, {username}!",
"complete.your.profile.1": "کامل",
"complete.your.profile.2": "پرونده کاربری شما",
"institution.login.page.sub.heading": "موسسه خود را از فهرست زیر برگزینید",
"logistration.sign.in": "ورود",
"logistration.register": "ثبت‌نام",
"enterprisetpa.title.heading": "آیا می‌خواهید با استفاده از اطلاعات کاربری {providerName} خود وارد سامانه شوید؟",
"enterprisetpa.login.button.text": "راه‌های دیگری برای ورود به سامانه یا ثبت‌نام به من نشان بده",
"enterprisetpa.login.button.text.public.account.creation.disabled": "راه های دیگری را برای ورود به سیستم به من نشان دهید",
"sso.sign.in.with": "با {providerName} وارد شوید",
"sso.create.account.using": "با استفاده از {providerName} حساب کاربری بسازید",
"show.password": "نمایش گذرواژه",
"hide.password": "پنهان‌سازی گذرواژه",
"one.letter": "1 نامه",
"one.number": "1 رقم",
"eight.characters": "8 نویسه",
"password.sr.only.helping.text": "گذرواژه باید حداقل 8 نویسه، حداقل یک حرف و حداقل یک عدد داشته باشد",
"tpa.alert.heading": "تقریبا تمام شد!",
"login.third.party.auth.account.not.linked": "شما با موفقیت به {currentProvider} وارد شدید، اما حساب کاربری {currentProvider} شما به حساب کاربری {platformName} پیوند ندارد. برای پیوند حساب‌های کاربری خود، اکنون با استفاده از گذرواژه {platformName} خود وارد شوید.",
"register.third.party.auth.account.not.linked": "شما با موفقیت به {currentProvider} وارد شدید! پیش از آغاز یادگیری با {platformName} فقط به کمی اطلاعات بیشتر نیاز داریم.",
"registration.using.tpa.form.heading": "ساخت حساب کاربری خود را به اتمام برسانید",
"zendesk.supportTitle": "پشتیبانی edX",
"zendesk.selectTicketForm": "لطفا نوع درخواست خود را انتخاب کنید:",
"error.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی اینترنتی وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره تلاش کنید.",
"forgot.password.confirmation.message": "ما رایانامه‌ای به‌همراه دستورالعمل بازنشانی گذرواژه به {email} ارسال کردیم. اگر پس از 1 دقیقه پیام بازنشانی را دریافت نکردید، بررسی کنید که نشانی رایانامه را صحیح وارد کرده‌اید یا پوشه هرزنامه خود را بررسی کنید. اگر به دریافت راهنمایی بیشتری، با {supportLink} تماس بگیرید.",
"forgot.password.page.title": "فراموش گذرواژه | {siteName}",
"forgot.password.page.heading": "بازتنظیم گذرواژه",
"forgot.password.page.instructions": "لطفا نشانی رایانامه خود را در قسمت زیر وارد کنید و ما رایانامه‌ای حاوی دستورالعمل نحوه بازتنظیم مجدد گذرواژه برای شما ارسال خواهیم کرد.",
"forgot.password.page.invalid.email.message": "نشانی رایانامه معتبری را وارد کنید",
"forgot.password.page.email.field.label": "رایانامه",
"forgot.password.page.submit.button": "ارسال",
"forgot.password.error.alert.title.": "ما نتوانستیم با شما تماس بگیریم.",
"forgot.password.error.message.title": "خطایی رخ داد.",
"forgot.password.request.in.progress.message": "درخواست قبلی شما در حال انجام است، لطفا چند لحظه دیگر دوباره تلاش کنید.",
"forgot.password.empty.email.field.error": "نشانی رایانامه خود را وارد کنید",
"forgot.password.email.help.text": "نشانی رایانامه‌ای که برای ثبت‌نام در {platformName} استفاده کردید",
"confirmation.message.title": "صندوق رایانامه خود را ببینید",
"confirmation.support.link": "با پشتیبانی فنی تماس بگیرید",
"need.help.sign.in.text": "برای ورود به سامانه نیاز به کمک دارید؟",
"additional.help.text": "برای دریافت راهنمایی بیشتر، با پشتیبانی {platformName} در این آدرس تماس بگیرید",
"sign.in.text": "ورود",
"extend.field.errors": "{emailError} زیر.",
"invalid.token.heading": "پیوند بازتنظیم گذرواژه معتبر نیست",
"invalid.token.error.message": "این پیوند برای بازتنظیم گذرواژه معتبر نیست. ممکن است قبلاً استفاده شده باشد. برای دریافت پیوند جدید نشانی رایانامه خود را در زیر وارد کنید.",
"token.validation.rate.limit.error.heading": "تعداد زیاد درخواست",
"token.validation.rate.limit.error": "به دلیل درخواست‌های زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید.",
"token.validation.internal.sever.error.heading": "اعتبارسنجی رمز انجام نشد",
"token.validation.internal.sever.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
"internal.server.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
"account.activation.error.message": "اشتباهی رخ داد، لطفاً برای حل این مساله ، به این قسمت{supportLink} مراجعه کنید.",
"login.inactive.user.error": "برای ورود به سامانه، باید حساب کاربری خود را فعال کنید.{lineBreak} {lineBreak}هم اکنون پیوند فعالسازی را به {email} فرستادیم. اگر رایانامه‌ای دریافت نکردید، پوشه‌های هرزنامه یا {supportLink} را بررسی کنید.",
"allowed.domain.login.error": "به عنوان کاربر {allowedDomain}، باید با {allowedDomain} {tpaLink} خود وارد شوید.",
"login.incorrect.credentials.error.attempts.text.1": "نام کاربری، نشانی رایانامه یا گذرواژه‌ای که وارد کرده‌اید نادرست است. پیش از اینکه حساب کاربری شما موقتا قفل شود، {remainingAttempts} تلاش دیگر برای ورود به سامانه دارید.",
"login.incorrect.credentials.error.attempts.text.2": "اگر گذرواژه خود را فراموش کرده‌اید، {resetLink}",
"account.locked.out.message.2": "برای حفظ امنیت، می‌توانید پیش از تلاش مجدد، {resetLink} را انجام دهید.",
"login.incorrect.credentials.error.with.reset.link": "نام کاربری، نشانی رایانامه یا گذرواژه‌ای که وارد کردید درست نیست. لطفاً دوباره امتحان کنید یا {resetLink}.",
"login.page.title": "ورود به سامانه | {siteName}",
"login.user.identity.label": "نام کاربری یا نشانی رایانامه",
"login.password.label": "گذرواژه",
"sign.in.button": "ورود به سامانه",
"forgot.password": "فراموشی گذرواژه",
"institution.login.button": "اعتبارنامه‌های موسسه/پردیس",
"institution.login.page.title": "با اعتبار موسسه/پردیس وارد شوید",
"login.other.options.heading": "یا وارد شوید با:",
"non.compliant.password.title": "ما اخیراً الزامات گذرواژه خود را تغییر دادیم",
"non.compliant.password.message": "گذرواژه فعلی شما الزامات امنیتی جدید را برآورده نمی‌کند. ما فقط یک پیام بازتنظیم گذرواژه به نشانی رایانامه مرتبط با این حساب کاربری ارسال کردیم. از اینکه به ما کمک می‌کنید تا داده‌های شما را ایمن نگه دارید متشکریم.",
"account.locked.out.message.1": "حساب کاربری شما، به دلیل حفاظت، به‌طور موقت قفل شده است. 30 دقیقه دیگر دوباره امتحان کنید.",
"enterprise.login.btn.text": "اعتبار دانشکده یا شرکت",
"username.or.email.format.validation.less.chars.message": "نام کاربری یا نشانی رایانامه حداقل باید 2 نویسه داشته باشد",
"email.validation.message": "نام کاربری یا رایانامه خود را وارد کنید",
"password.validation.message": "معیارهای گذرواژه رعایت نشده است",
"account.activation.success.message.title": "موفق شدید! شما حساب کاربری خود را فعال کردید.",
"account.activation.success.message": "اکنون رایانامه‌های مربوط به روزآمدسازی‌ها و هشدارها درباره دوره‌های آموزشی را که در آن ثبت‌نام کرده‌اید از ما دریافت خواهید کرد. برای ادامه وارد شوید.",
"account.activation.info.message": "حساب کاربری مورد نظر شما قبلاً فعال شده است.",
"account.activation.error.message.title": "امکان فعالسازی حساب کاربری شما نبود",
"account.activation.support.link": "تماس با پشتیبانی ",
"account.confirmation.success.message.title": "موفق شدید! نشانی رایانامه خود را تایید کردید.",
"account.confirmation.success.message": "برای ادامه وارد سامانه شوید.",
"account.confirmation.info.message": "این نشانی رایانامه قبلا تایید شده‌است.",
"account.confirmation.error.message.title": "امکان تایید نشانی رایانامه شما وجود ندارد",
"tpa.account.link": "حساب {provider}",
"internal.server.error.message": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
"login.rate.limit.reached.message": "شما برای ورود به حساب کاربری چند بار تلاش ناموفق داشتید. لطفا بعدا تلاش نمایید.",
"login.failure.header.title": "قادر نیستیم شما را به سامانه وارد کنیم.",
"contact.support.link": "با پشتیبانی {platformName} تماس بگیرید",
"login.incorrect.credentials.error": "نام کاربری، نشانی رایانامه یا گذرواژه‌ای که وارد کردید نادرست است. لطفا دوباره تلاش کنید.",
"login.form.invalid.error.message": "لطفا قسمت‌های زیر را پر کنید.",
"login.incorrect.credentials.error.reset.link.text": "گذرواژه را بازتنظیم کنید",
"login.incorrect.credentials.error.before.account.blocked.text": "برای بازتنظیم اینجا بزنید",
"password.security.nudge.title": "امنیت گذرواژه",
"password.security.block.title": "تغییر گذرواژه ضروری است",
"password.security.nudge.body": "سامانه ما تشخیص داده است که گذرواژه شما آسیب‌پذیر است. توصیه ما این است که آن را تغییر دهید تا حساب کاربری شما ایمن بماند.",
"password.security.block.body": "سامانه ما تشخیص داده است که گذرواژه شما ضعیف و آسیب‌پذیر است. گذرواژه خود را تغییر دهید تا حساب کاربری شما ایمن بماند.",
"password.security.close.button": "بستن",
"password.security.redirect.to.reset.password.button": "بازتنظیم گذرواژه",
"login.tpa.authentication.failure": "متأسفیم، شما مجاز به دسترسی به {platform_name} از طریق این کانال نیستید. لطفاً برای دسترسی به {platform_name} با سرپرست یا مدیر آموزشی خود تماس بگیرید.{lineBreak}{lineBreak}جزئیات خطا:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "خوش آمدید | {siteName}",
"progressive.profiling.page.heading": "چند سوال برای شما به ما کمک خواهد کرد تا باهوش‌تر شویم.",
"optional.fields.information.link": "درباره نحوه استفاده ما از این اطلاعات بیشتر بدانید.",
"optional.fields.submit.button": "ارسال",
"optional.fields.skip.button": "فعلا بگذرید",
"optional.fields.next.button": "بعدی",
"continue.to.platform": "ادامه در {platformName}",
"modal.title": "از اینکه اطلاع دادید تشکر می‌کنیم.",
"modal.description": "در صورت تغییر تصمیم‌تان، شما هر زمانی این امکان را دارید که پرونده کاربری خود را در قسمت تنظیمات تکمیل کنید. ",
"welcome.page.error.heading": "امکان روزآمدسازی پرونده کاربری شما نیست",
"welcome.page.error.message": "خطایی رخ داد. می‌توانید پرونده کاربری خود را هر زمان در قسمت تنظیمات تکمیل کنید.",
"recommendation.page.title": "توصیه ها | {siteName}",
"recommendation.page.heading": "ما چند توصیه برای شروع کار داریم.",
"recommendation.skip.button": "فعلا بگذرید",
"recommendation.option.trending": "پرطرفدار",
"recommendation.option.popular": "محبوبترین",
"recommendation.product-card.pill-text.course": "دوره آموزشی",
"recommendation.product-card.pill-text.professional-certificate": "گواهی حرفه‌ای",
"recommendation.product-card.pill-text.emeritus": "ارائه شده در Emeritus",
"recommendation.product-card.pill-text.shorelight": "از طریق Shorelight ارائه می شود",
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
"recommendation.product-card.footer-text.subscription": "اشتراک",
"recommendation.product-card.launch-icon.sr-text": "پیوندی را در یک برگه جدید باز می کند",
"register.page.title": "ثبت‌نام | {siteName}",
"registration.fullname.label": "نام و نام خانوادگی",
"registration.email.label": "رایانامه",
"registration.username.label": "نام‌کاربری عمومی",
"registration.password.label": "گذرواژه",
"registration.country.label": "کشور/منطقه",
"registration.opt.in.label": "با ارسال پیام‌های بازرگانی از سوی {siteName} موافقم.",
"help.text.name": "این نام در هر گواهی که بدست آورید استفاده خواهد شد.",
"help.text.username.1": "نامی که شما را در دوره‌های آموزشی با آن شناخته خواهید شد.",
"help.text.username.2": "این مورد بعدا قابل تغییر نیست.",
"help.text.email": "برای فعال‌سازی حساب کاربری و روزآمدسازی‌های مهم",
"create.account.for.free.button": "یک حساب کاربری رایگان بسازید",
"registration.other.options.heading": "یا ثبت‌نام کنید با:",
"create.account.cta.button": "{label}",
"register.institution.login.button": "اعتبارنامه‌های موسسه/پردیس",
"register.institution.login.page.title": "با اعتبارنامه موسسه/پردیس ثبت‌نام کنید",
"empty.name.field.error": "نام و نام خانوادگی خود را وارد کنید",
"empty.email.field.error": "نشانی رایانامه خود را وارد کنید",
"empty.username.field.error": "نام کاربری باید بین 2 تا 30 نویسه داشته باشد",
"empty.password.field.error": "معیارهای گذرواژه رعایت نشده است",
"empty.country.field.error": "کشور یا منطقه محل سکونت خود را برگزینید",
"email.do.not.match": "نشانی‌های رایانامه همخوانی ندارند.",
"email.invalid.format.error": "نشانی رایانامه معتبر وارد کنید",
"username.validation.message": "نام کاربری باید بین 2 تا 30 نویسه داشته باشد",
"name.validation.message": "نامی معتبر وارد کنید",
"username.format.validation.message": "نام کاربری فقط می‌تواند شامل حروف (A-Z، a-z)، اعداد (0-9)، خط زیر (_) و خط فاصله (-) باشد. نام کاربری نمی‌تواند حاوی فاصله باشد",
"registration.request.failure.header": "موفق به ایجاد حساب کاربری شما نشدیم.",
"registration.empty.form.submission.error": "لطفاً پاسخ‌های خود را بررسی کرده و دوباره امتحان کنید.",
"registration.request.server.error": "خطایی رخ داده است. صفحه را دوباره بارگیری کنید یا اتصال اینترنت خود را بررسی کنید.",
"registration.rate.limit.error": "تعداد دفعات تلاش ناموفق برای ثبت‌نام بسیار بوده است. بعدا دوباره تلاش کنید.",
"registration.tpa.session.expired": "مهلت ثبت‌نام {provider} به پایان رسیده‌است.",
"registration.tpa.authentication.failure": "متأسفیم، شما مجاز به دسترسی به {platform_name} از طریق این کانال نیستید. لطفاً برای دسترسی به {platform_name} با سرپرست یا مدیر آموزشی خود تماس بگیرید.{lineBreak}{lineBreak}جزئیات خطا:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "شرایط استفاده از خدمات و اصول اخلاقی",
"privacy.policy": "قواعد حفظ حریم خصوصی",
"honor.code": "اصول اخلاقی",
"terms.of.service": "شرایط استفاده از خدمات",
"registration.username.suggestion.label": "پیشنهادشده:",
"did.you.mean.alert.text": "منظور شما این بود",
"register.page.terms.of.service.and.honor.code": "با ایجاد یک حساب کاربری، با {tosAndHonorCode} موافقت می‌کنید و تصدیق می‌کنید که {platformName} و هر عضو داده‌های شخصی شما را مطابق با {privacyPolicy} پردازش می‌کنند.",
"register.page.honor.code": "من با {platformName} {tosAndHonorCode} موافقم",
"register.page.terms.of.service": "من با {platformName} {termsOfService} موافقم",
"sign.in": "ورود",
"reset.password.page.title": "بازتنظیم گذرواژه | {siteName}",
"reset.password": "بازتنظیم گذرواژه",
"reset.password.page.instructions": "گذرواژه جدید را مجددا وارد کنید ",
"new.password.label": "گذرواژه جدید",
"confirm.password.label": "تایید گذرواژه",
"passwords.do.not.match": "گذرواژه‌ها مطابقت ندارند",
"confirm.your.password": "تایید گذرواژه ",
"reset.password.failure.heading": "امکان بازتنظیم گذرواژه شما نیست.",
"reset.password.form.submission.error": "لطفاً پاسخ‌های خود را بررسی کرده و دوباره امتحان کنید.",
"reset.server.rate.limit.error": "تعداد درخواست‌ها خیلی زیاد است.",
"reset.password.success.heading": "بازتنظیم گذرواژه  تکمیل شد.",
"reset.password.success": "گذرواژه شما بازتنظیم شد. وارد حساب کاربری خود شوید",
"rate.limit.error": "به دلیل درخواست‌های زیاد، خطایی روی داده است. لطفا بعد از مدتی دوباره امتحان کنید."
}

View File

@@ -1,14 +1,17 @@
{
"start.learning": "Démarrer l'apprentissage",
"with.site.name": "avec {siteName}",
"your.career.turning.point": "Your career turning point",
"is.here": "is here.",
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
"complete.your.profile.1": "Terminé",
"complete.your.profile.2": "votre profil",
"welcome.to.platform": "Bienvenue sur {siteName}, {username}!",
"institution.login.page.sub.heading": "Sélectionner votre institution dans la liste ci-dessous",
"logistration.sign.in": "Connectez-vous",
"logistration.register": "S'inscrire",
"enterprisetpa.title.heading": "Souhaitez-vous vous connecter à l'aide de vos identifiants {providerName} ?",
"enterprisetpa.login.button.text": "Montrez-moi d'autres méthodes pour me connecter ou m'inscrire",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"sso.sign.in.with": "Connectez-vous avec {providerName}",
"sso.create.account.using": "Créer un compte avec {providerName}",
"show.password": "Afficher le mot de passe",
@@ -21,6 +24,8 @@
"login.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider}, mais votre compte {currentProvider} n'a pas de compte relié à {platformName}. Pour lier vos comptes, connectez-vous en utilisant votre mot de passe {platformName}.",
"register.third.party.auth.account.not.linked": "Vous vous êtes connecté avec succès à {currentProvider} ! Nous avons juste besoin d'un peu plus d'informations avant que vous commenciez à apprendre avec {platformName}.",
"registration.using.tpa.form.heading": "Terminer la création de votre compte",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"error.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
"forgot.password.confirmation.message": "Nous avons envoyé un courriel à {email} avec des instructions pour réinitialiser votre mot de passe.\n Si vous ne recevez pas de message de réinitialisation de mot de passe après 1 minute, vérifiez que vous avez saisi\nl'adresse courriel correctement, ou vérifiez votre dossier de courriel indésirable. Si vous avez besoin d'aide supplémentaire, {supportLink}.",
"forgot.password.page.title": " Mot de passe oublié | {siteName}",
@@ -66,7 +71,7 @@
"non.compliant.password.message": "Votre mot de passe actuel ne répond pas aux nouvelles exigences de sécurité. Nous venons d'envoyer un message de réinitialisation de mot de passe à l'adresse courriel associée à ce compte. Merci de nous aider à protéger vos données.",
"account.locked.out.message.1": "Pour protéger votre compte, il a été temporairement verrouillé. Réessayez dans 30 minutes.",
"enterprise.login.btn.text": "Identifiants de la compagnie ou de l'école",
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 3 caractères.",
"username.or.email.format.validation.less.chars.message": "Le nom d'utilisateur ou l'adresse courriel doit comporter au moins 2 caractères.",
"email.validation.message": "Entrez votre nom d'utilisateur ou votre adresse courriel",
"password.validation.message": "Les critères de mot de passe n'ont pas été remplis",
"account.activation.success.message.title": "Succès! Vous avez activé votre compte.",
@@ -93,7 +98,8 @@
"password.security.block.body": "Notre système a détecté que votre mot de passe est vulnérable. Changez votre mot de passe afin que votre compte reste sécurisé.",
"password.security.close.button": "Fermer",
"password.security.redirect.to.reset.password.button": "Réinitialiser votre mot de passe",
"progressive.profiling.page.title": "Champs optionnels | {siteName}",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "Quelques questions pour vous nous aideront à devenir plus intelligents.",
"optional.fields.information.link": "En savoir plus sur la façon dont nous utilisons ces informations.",
"optional.fields.submit.button": "Envoyez",
@@ -104,9 +110,18 @@
"modal.description": "Vous pouvez compléter votre profil dans les paramètres à tout moment si vous changez d'avis.",
"welcome.page.error.heading": "Nous n'avons pas pu mettre à jour votre profil",
"welcome.page.error.message": "Une erreur s'est produite. Vous pouvez compléter votre profil dans les paramètres à tout moment.",
"recommendation.page.title": "Recommendations| {siteName}",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"recommendation.option.trending": "Trending",
"recommendation.option.popular": "Most Popular",
"recommendation.product-card.pill-text.course": "Course",
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
"recommendation.product-card.footer-text.subscription": "Subscription",
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
"register.page.title": "S'inscrire | {siteName}",
"registration.fullname.label": "Nom complet",
"registration.email.label": "Email",
@@ -120,6 +135,7 @@
"help.text.email": "Pour l'activation du compte et les mises à jour importantes",
"create.account.for.free.button": "Créer un compte gratuitement",
"registration.other.options.heading": "Ou inscrivez-vous avec :",
"create.account.cta.button": "{label}",
"register.institution.login.button": "Identifiants de l'établissement/du campus",
"register.institution.login.page.title": "Inscription avec les crédentiels d'institution ou de campus",
"empty.name.field.error": "Saisissez votre nom complet",
@@ -137,6 +153,7 @@
"registration.request.server.error": "Une erreur est survenue. Essayer de rafraîchir la page, ou vérifier votre connexion Internet.",
"registration.rate.limit.error": "Trop de tentatives d'inscriptions ont échoué. Réessayez plus tard.",
"registration.tpa.session.expired": "L'inscription avec {provider} a échouée.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Conditions d'utilisation et Code d'honneur",
"privacy.policy": "Politique de confidentialité",
"honor.code": "Code d'honneur",
@@ -160,4 +177,4 @@
"reset.password.success.heading": "Réinitialisation du mot de passe complétée.",
"reset.password.success": "Votre mot de passe a été réinitialisé. Connectez-vous à votre compte.",
"rate.limit.error": "Une erreur s'est produite en raison d'un trop grand nombre de demandes. Veuillez réessayer après un certain temps."
}
}

View File

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

View File

@@ -1,14 +1,17 @@
{
"start.learning": "Start learning",
"with.site.name": "with {siteName}",
"your.career.turning.point": "Your career turning point",
"is.here": "is here.",
"welcome.to.platform": "Welcome to {siteName}, {username}!",
"complete.your.profile.1": "Complete",
"complete.your.profile.2": "your profile",
"welcome.to.platform": "Welcome to {siteName}, {username}!",
"institution.login.page.sub.heading": "Choose your institution from the list below",
"logistration.sign.in": "Sign in",
"logistration.register": "Register",
"enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?",
"enterprisetpa.login.button.text": "Show me other ways to sign in or register",
"enterprisetpa.login.button.text.public.account.creation.disabled": "Show me other ways to sign in",
"sso.sign.in.with": "Sign in with {providerName}",
"sso.create.account.using": "Create account using {providerName}",
"show.password": "Show password",
@@ -21,6 +24,8 @@
"login.third.party.auth.account.not.linked": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.",
"register.third.party.auth.account.not.linked": "You've successfully signed into {currentProvider}! We just need a little more information before you start learning with {platformName}.",
"registration.using.tpa.form.heading": "Finish creating your account",
"zendesk.supportTitle": "edX Support",
"zendesk.selectTicketForm": "Please choose your request type:",
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"forgot.password.confirmation.message": "We sent an email to {email} with instructions to reset your password.\n If you do not receive a password reset message after 1 minute, verify that you entered\n the correct email address, or check your spam folder. If you need further assistance, {supportLink}.",
"forgot.password.page.title": "Forgot Password | {siteName}",
@@ -66,7 +71,7 @@
"non.compliant.password.message": "Your current password does not meet the new security requirements. We just sent a password-reset message to the email address associated with this account. Thank you for helping us keep your data safe.",
"account.locked.out.message.1": "To protect your account, it's been temporarily locked. Try again in 30 minutes.",
"enterprise.login.btn.text": "Company or school credentials",
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 3 characters.",
"username.or.email.format.validation.less.chars.message": "Username or email must have at least 2 characters.",
"email.validation.message": "Enter your username or email",
"password.validation.message": "Password criteria has not been met",
"account.activation.success.message.title": "Success! You have activated your account.",
@@ -93,7 +98,8 @@
"password.security.block.body": "Our system detected that your password is vulnerable. Change your password so that your account stays secure.",
"password.security.close.button": "Close",
"password.security.redirect.to.reset.password.button": "Reset your password",
"progressive.profiling.page.title": "Optional Fields | {siteName}",
"login.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"progressive.profiling.page.title": "Welcome | {siteName}",
"progressive.profiling.page.heading": "A few questions for you will help us get smarter.",
"optional.fields.information.link": "Learn more about how we use this information.",
"optional.fields.submit.button": "Submit",
@@ -104,9 +110,18 @@
"modal.description": "You can complete your profile in settings at any time if you change your mind.",
"welcome.page.error.heading": "We couldn't update your profile",
"welcome.page.error.message": "An error occurred. You can complete your profile in settings at any time.",
"recommendation.page.title": "Recommendations| {siteName}",
"recommendation.page.title": "Recommendations | {siteName}",
"recommendation.page.heading": "We have a few recommendations to get you started.",
"recommendation.skip.button": "Skip for now",
"recommendation.option.trending": "Trending",
"recommendation.option.popular": "Most Popular",
"recommendation.product-card.pill-text.course": "Course",
"recommendation.product-card.pill-text.professional-certificate": "Professional Certificate",
"recommendation.product-card.pill-text.emeritus": "Offered on Emeritus",
"recommendation.product-card.pill-text.shorelight": "Offered through Shorelight",
"recommendation.product-card.footer-text.number-of-courses": "{length} {label}",
"recommendation.product-card.footer-text.subscription": "Subscription",
"recommendation.product-card.launch-icon.sr-text": "Opens a link in a new tab",
"register.page.title": "Register | {siteName}",
"registration.fullname.label": "Full name",
"registration.email.label": "Email",
@@ -120,6 +135,7 @@
"help.text.email": "For account activation and important updates",
"create.account.for.free.button": "Create an account for free",
"registration.other.options.heading": "Or register with:",
"create.account.cta.button": "{label}",
"register.institution.login.button": "Institution/campus credentials",
"register.institution.login.page.title": "Register with institution/campus credentials",
"empty.name.field.error": "Enter your full name",
@@ -137,6 +153,7 @@
"registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.",
"registration.rate.limit.error": "Too many failed registration attempts. Try again later.",
"registration.tpa.session.expired": "Registration using {provider} has timed out.",
"registration.tpa.authentication.failure": "We are sorry, you are not authorized to access {platform_name} via this channel. Please contact your learning administrator or manager in order to access {platform_name}.{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}",
"terms.of.service.and.honor.code": "Terms of Service and Honor Code",
"privacy.policy": "Privacy Policy",
"honor.code": "Honor Code",
@@ -160,4 +177,4 @@
"reset.password.success.heading": "Password reset complete.",
"reset.password.success": "Your password has been reset. Sign in to your account.",
"rate.limit.error": "An error has occurred because of too many requests. Please try again after some time."
}
}

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

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

Some files were not shown because too many files have changed in this diff Show More