Compare commits

...

415 Commits

Author SHA1 Message Date
Mubbshar Anwar
17e18e9efb fix: update username suggestions placement (#1037)
Co-authored-by: Shahbaz Shabbir <shbzshbr@gmail.com>
2023-08-17 15:05:38 +05:00
Sagirov Eugeniy
cfb839d617 chore: update frontend-platform version to v4.2.0 2023-05-02 17:15:30 -03: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
Zainab Amir
f291efc428 fix: bottom padding missing unit (#765) 2023-03-01 19:42:56 +05:00
Zainab Amir
1a61ba3cc7 feat: add support for fallback recs in case of error (#764)
* feat: add support for fallback recs in case of error
* feat: update segment event
2023-03-01 17:49:38 +05:00
Zainab Amir
3d10cea137 feat: implement fallback recommendations (#758) 2023-02-27 20:30:41 +05:00
Feanil Patel
6c72e9dad4 Merge pull request #756 from openedx/repo_checks/ensure_workflows
Update standard workflow files.
2023-02-27 09:26:28 -05:00
Feanil Patel
c5d4f6b94d build: Updating a missing workflow file add-depr-ticket-to-depr-board.yml.
The .github/workflows/add-depr-ticket-to-depr-board.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-27 09:22:34 -05:00
Feanil Patel
c52d7b6de5 build: Creating a missing workflow file add-remove-label-on-comment.yml.
The .github/workflows/add-remove-label-on-comment.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-27 09:22:34 -05:00
Feanil Patel
6ade0a837f build: Creating a missing workflow file self-assign-issue.yml.
The .github/workflows/self-assign-issue.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-27 09:22:34 -05:00
Syed Sajjad Hussain Shah
81d69c8e72 feat: add optimizely exp for recommendations page (#754)
VAN-1294

Co-authored-by: Syed Sajjad  Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
2023-02-27 15:39:08 +05:00
Jenkins
ca333e895f chore(i18n): update translations 2023-02-26 10:16:43 -05:00
Zainab Amir
8e527efd07 zamir/van 1295/add tests for recommender (#755)
* feat: VAN-1295 add tests for recommendations
* feat: add test for loading state

VAN-1295
2023-02-23 13:59:21 +05:00
Mubbshar Anwar
c31c03f5a9 feat: personalized recommendations (#751)
configured algolia for recommendations based on user location or education level
VAN-1287

Co-authored-by: Mubbshar Anwar <mubbsharanwar@users.noreply.github.com>
2023-02-22 10:59:00 +05:00
Dmytro
fe34acf314 feat: display or sign in with (#749) 2023-02-20 13:36:04 +05:00
Attiya Ishaque
1f21a874b8 feat: [VAN-1291] add recommendation page (#743) 2023-02-17 17:02:25 +05:00
renovate[bot]
ee6a6f0d2d fix(deps): update dependency @edx/paragon to v20.28.4 2023-02-14 21:01:30 +00:00
Dmytro
8d0181ccca feat: don't show support button (#744)
Do not show the button "Need help logging in?"
if there is no support page for sign-in issues.
2023-02-14 17:18:56 +05:00
Jenkins
e8dba05920 chore(i18n): update translations 2023-02-12 10:16:41 -05:00
Mubbshar Anwar
6652e2f15c fix: syntext error fixed (#742)
A closing bracket was missing in zen-desk settings.
2023-02-06 19:22:08 +05:00
Mubbshar Anwar
578eec8a2d fix: Customiz Zendesk (#741)
Customize the Zendesk Web Widget in authn for separating departments.

VAN-1290
2023-02-06 18:14:36 +05:00
Jenkins
8b116d2234 chore(i18n): update translations 2023-02-05 10:16:42 -05:00
Jenkins
b9aa110440 chore(i18n): update translations 2023-01-29 10:16:39 -05:00
Shahbaz Shabbir
9f50bbda79 fix: Progressive Profiling can't convert undefined to object (#737) 2023-01-23 13:06:04 +05:00
Jenkins
6f2ce69b77 chore(i18n): update translations 2023-01-22 10:16:39 -05:00
Zainab Amir
8ad2678ce2 feat: pick required fields from tpa context (#736) 2023-01-17 15:18:23 +05:00
Syed Sajjad Hussain Shah
49c91262fd fix: update MFE context (#731)
VAN-1231
2023-01-16 18:36:49 +05:00
Zainab Amir
15378682ab feat: add debugging logs (#735) 2023-01-16 11:06:18 +05:00
Zainab Amir
296861ce3a feat: add GTM fired for integration with impact.com (#734) 2023-01-13 15:07:16 +05:00
Zainab Amir
aa59acf0bc fix: sso button not appearing on login page (#733) 2023-01-12 18:32:52 +05:00
dependabot[bot]
83e204f3f8 build(deps): bump json5 from 1.0.1 to 1.0.2 (#732)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 15:00:44 +05:00
Jenkins
497d60e244 chore(i18n): update translations 2023-01-08 10:16:36 -05:00
Syed Sajjad Hussain Shah
e8282d6d4a fix: uncaught exception on progressive profiling (#729)
VAN-1224
2023-01-06 16:42:08 +05:00
Syed Sajjad Hussain Shah
72510047d8 Revert "fix: exception at progressive profiling page (#727)" (#728)
This reverts commit d06290642b.
2023-01-06 15:23:50 +05:00
Attiya Ishaque
7dee21eb72 fix: Remove the deprecated intlShape import and replace its occurrences with PropType.object. (#724) 2023-01-06 15:01:57 +05:00
Syed Sajjad Hussain Shah
d06290642b fix: exception at progressive profiling page (#727)
VAN-1224
2023-01-06 14:39:37 +05:00
Zainab Amir
525abe6b88 feat: update configs (#721) 2023-01-06 10:49:47 +05:00
Syed Sajjad Hussain Shah
a7704edb9c Revert "fix: uncaught exception on progressive profiling (#722)" (#725)
This reverts commit 78693f4fc6.
2023-01-05 17:36:46 +05:00
Syed Sajjad Hussain Shah
78693f4fc6 fix: uncaught exception on progressive profiling (#722)
VAN-1224
2023-01-05 16:55:58 +05:00
Zainab Amir
186484defa chore: remove unused message string (#723) 2023-01-04 13:11:26 +05:00
Attiya Ishaque
f8fe704c42 fix: rename welcome pages and flag to progressive profiling (#720) 2022-12-30 14:00:24 +05:00
Mubbshar Anwar
82857db236 fix: unable to resubmit form (#719)
by replacing url history user will again redirect to dashboard instead progressive profile page.

VAN-1214
2022-12-23 21:41:30 +05:00
Attiya Ishaque
661914b0db fix: add proper error handling for progressive profiling page. (#716) 2022-12-22 12:35:20 +05:00
Edward Zarecor
057299de2b fix: Use repo name, spaces are not allowed. (#717)
Co-authored-by: Edward Zarecor <ed@tcril.org>
2022-12-20 21:55:45 +05:00
Attiya Ishaque
93db1a23e2 fix: fix error banner position on registartion page. (#711) 2022-12-19 14:39:51 +05:00
Jenkins
36ec03b24b chore(i18n): update translations 2022-12-18 10:16:36 -05:00
Attiya Ishaque
1d6f47c49e docs: Update README (#697) 2022-12-15 11:40:02 +05:00
Zainab Amir
10dfab7127 fix: update catalog-info.yaml 2022-12-14 15:55:41 +05:00
Mubbshar Anwar
a8ebe2c096 Revert "test: for stage-frontend-app-authn-e2e-tests job testing (#709)" (#710)
This reverts commit b3dc1c1513.
2022-12-14 14:06:46 +05:00
Mubbshar Anwar
b3dc1c1513 test: for stage-frontend-app-authn-e2e-tests job testing (#709)
- We are creating this PR for stage-frontend-app-authn-e2e-tests gocd pipeline testing.
- We will revert these changes after testing.

VAN-989
2022-12-14 13:22:53 +05:00
Zainab Amir
7cdae09a94 fix: add policy banner settings to config (#708) 2022-12-14 12:14:48 +05:00
Zainab Amir
59c2c2fd5d feat: make policy banner configurable (#707) 2022-12-14 00:57:37 +05:00
Mubbshar Anwar
70000aab75 Revert "test: for stage-frontend-app-authn-e2e-tests job testing (#705)" (#706)
This reverts commit 059d79302d.
2022-12-13 17:53:06 +05:00
Mubbshar Anwar
059d79302d test: for stage-frontend-app-authn-e2e-tests job testing (#705)
- We are creating this PR for stage-frontend-app-authn-e2e-tests gocd pipeline testing.
- We will revert these changes after testing.

VAN-989
2022-12-13 16:20:44 +05:00
Zainab Amir
ee300466aa fix: update set state functions to use prevState (#696) 2022-12-13 11:35:05 +05:00
Attiya Ishaque
c4fb3f72e5 fix: Fix selecting country error while applying translations (#693) 2022-12-13 11:35:05 +05:00
Zainab Amir
ed0da96076 feat: add tests and fix bugs 2022-12-13 11:35:05 +05:00
Zainab Amir
85fbc54384 feat: refactor registration page 2022-12-13 11:35:05 +05:00
renovate[bot]
a6c282520a fix(deps): update dependency sanitize-html to v2.8.0 2022-12-13 03:33:32 +00:00
renovate[bot]
aa4faba4a3 fix(deps): update dependency @edx/paragon to v20.21.2 2022-12-13 03:23:33 +00:00
renovate[bot]
5e864c4ff1 fix(deps): update dependency @edx/frontend-platform to v3.2.0 2022-12-12 21:25:54 +00:00
renovate[bot]
0c4e612e39 fix(deps): update dependency redux-saga to v1.2.2 2022-12-12 21:14:28 +00:00
renovate[bot]
92faa846ac chore(deps): update dependency babel-plugin-formatjs to v10.3.35 2022-12-12 21:04:37 +00:00
Zainab Amir
5bfdd8e1ef fix: remove edx keyword from tests (#698) 2022-12-12 21:23:12 +05:00
Attiya Ishaque
5af93a57c7 feat: make authn-app compliant with OEP-55 (#695) 2022-12-12 19:36:56 +05:00
Attiya Ishaque
8549bf2fcf fix: double header bar on 2 specfic screen dimension (#694) 2022-12-09 17:28:53 +05:00
Attiya Ishaque
762da62a29 fix: add token invalid case in reset password. (#691) 2022-12-06 17:09:32 +05:00
Shahbaz Shabbir
7d536e6cdb fix: update missing accent-a-light color (#688) 2022-12-02 11:21:21 +05:00
Mashal Malik
166d7953c9 fix: removed depreciated package codecov (#689) 2022-12-02 11:02:05 +05:00
Diana Olarte
8ea457f949 feat: allow runtime configuration (#654) 2022-11-30 11:13:10 +05:00
Syed Sajjad Hussain Shah
35dfc720c8 feat: add enable social auth locally docs (#687)
VAN-1180
2022-11-29 16:03:20 +05:00
Shahbaz Shabbir
3ca2739fce fix: use paragon branding variables for colors (#681) 2022-11-28 19:57:34 +05:00
Syed Sajjad Hussain Shah
5df3d8f6e2 fix: remove extra badges (#686) 2022-11-28 12:37:53 +05:00
renovate[bot]
980a4c4003 chore(deps): update codecov/codecov-action action to v3 (#680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-28 12:19:28 +05:00
Syed Sajjad Hussain Shah
c3f5374c6e feat: update README (#684)
VAN-1164
2022-11-28 12:02:24 +05:00
edX requirements bot
fcccc19fc5 fix: -t flag added in pull translation command (#682) 2022-11-25 09:25:27 +05:00
renovate[bot]
c2609a3316 fix(deps): update dependency @edx/paragon to v20.20.0 (#683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-23 19:44:14 +05:00
renovate[bot]
34f3240c03 fix(deps): update react-router monorepo to v5.3.4 2022-11-21 12:13:57 +00:00
renovate[bot]
dba4792a7d fix(deps): update dependency redux-saga to v1.2.1 2022-11-21 09:52:19 +00:00
renovate[bot]
ad13f7e5c6 fix(deps): update dependency redux to v4.2.0 2022-11-21 03:24:32 +00:00
renovate[bot]
20476b9445 fix(deps): update dependency core-js to v3.26.1 2022-11-21 03:15:56 +00:00
renovate[bot]
21ad3d78a4 fix(deps): update font awesome 2022-11-21 03:06:38 +00:00
renovate[bot]
8b8e41fa1b fix(deps): update dependency sanitize-html to v2.7.3 2022-11-18 20:35:41 +00:00
renovate[bot]
f997cd0b47 fix(deps): update dependency reselect to v4.1.7 2022-11-18 20:26:41 +00:00
renovate[bot]
9241be296f fix(deps): update dependency regenerator-runtime to v0.13.11 2022-11-18 20:17:23 +00:00
renovate[bot]
47c2d5c15e fix(deps): update dependency redux-thunk to v2.4.2 2022-11-18 14:20:34 +00:00
renovate[bot]
ed89d8546c fix(deps): update dependency react-redux to v7.2.9 2022-11-18 14:11:08 +00:00
renovate[bot]
19de32cb5d fix(deps): update dependency classnames to v2.3.2 2022-11-18 14:01:53 +00:00
renovate[bot]
08a859c002 fix(deps): update dependency @edx/paragon to v20.18.2 2022-11-18 11:53:59 +00:00
renovate[bot]
ff1db82c56 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.7 2022-11-18 11:44:53 +00:00
renovate[bot]
c122afefb9 chore(deps): update dependency babel-plugin-formatjs to v10.3.31 2022-11-18 11:35:20 +00:00
Attiya Ishaque
689542947e fix: [VAN-544] Add autocomplete attribute fir input fields. (#663) 2022-11-18 12:55:39 +05:00
Zainab Amir
f64aeff6b5 fix(deps): update dependency @edx/paragon to v20 (#664)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-18 10:37:11 +05:00
Syed Sajjad Hussain Shah
4dd12f6230 fix: segment page event not sent (#665)
VAN-1160
2022-11-17 09:27:52 +05:00
dependabot[bot]
4571d35102 build(deps): bump minimatch and recursive-readdir (#662)
Bumps [minimatch](https://github.com/isaacs/minimatch) and [recursive-readdir](https://github.com/jergason/recursive-readdir). These dependencies needed to be updated together.

Updates `minimatch` from 3.0.4 to 3.1.2
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

Updates `recursive-readdir` from 2.2.2 to 2.2.3
- [Release notes](https://github.com/jergason/recursive-readdir/releases)
- [Changelog](https://github.com/jergason/recursive-readdir/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jergason/recursive-readdir/commits/v2.2.3)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
- dependency-name: recursive-readdir
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 15:02:00 +05:00
dependabot[bot]
ed7aca7bc5 build(deps): bump loader-utils from 1.4.0 to 1.4.2 (#661)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-11 15:16:53 +05:00
renovate[bot]
f96dc974f3 fix(deps): update dependency @edx/frontend-platform to v3 (#657) 2022-11-11 14:40:16 +05:00
Zainab Amir
81f3dbf584 build: use shared browserslist configuration (#660) 2022-11-11 14:27:38 +05:00
Abderraouf Mehdi Bouhali
4f91b30ee1 fix(rtl): mirror skewed svg rectangle (#639)
fixes layout for
- unauthenticated users (login-register) and
- authenticated users (progressive profiling)
2022-11-10 11:10:46 +05:00
Attiya Ishaque
51272c0fc4 fix: [VAN-1135] Fix selecting country error while creating account (#656) 2022-11-02 18:03:13 +05:00
Jenkins
27d4a26ae8 chore(i18n): update translations 2022-10-23 11:16:33 -04:00
Attiya Ishaque
2fdf630ed3 fix: Add yellow slanted line back for edX only (#650) 2022-10-11 18:20:29 +05:00
Jenkins
dc056a6cc0 chore(i18n): update translations 2022-10-09 11:16:30 -04:00
Attiya Ishaque
54015223a2 fix: Password reset success msg has been shown (#652) 2022-10-07 11:44:19 +05:00
Jenkins
345777cb72 chore(i18n): update translations 2022-10-02 11:17:42 -04:00
Mubbshar Anwar
347493fe6b fix: display error message (#647)
display error message on login page if staff user try to login through password.

VAN-1082
2022-09-23 15:07:42 +05:00
Attiya Ishaque
766438dcf3 fix: Use ProgressiveProfiling instead of WelcomePage (#644) 2022-09-19 14:21:41 +05:00
Sarina Canelake
f2989e836a Merge pull request #638 from openedx/tcril/fix-gh-org-url
Fix github url strings (org edx -> openedx)
2022-09-14 10:19:07 -04:00
Sarina Canelake
34118bfc90 fix: update path to .github workflows to read from openedx org 2022-09-13 20:49:22 -04:00
Sarina Canelake
91a74e309a fix: fix github url strings (org edx -> openedx) 2022-09-13 20:49:22 -04:00
Shafqat Farhan
0c32b98e85 fix: VAN-1090 - Handled country field's errors on empty field (#643) 2022-09-13 14:17:15 +05:00
Zainab Amir
e059fab172 fix: username prefixed with space character (#642) 2022-09-13 11:27:54 +05:00
Shafqat Farhan
08a8bc0e4f fix: VAN-1047 - Handled country field's errors and state persistence (#640) 2022-09-12 13:56:23 +05:00
Jenkins
3dc0cd97ca chore(i18n): update translations 2022-09-11 11:14:44 -04:00
Syed Sajjad Hussain Shah
697adecc1d fix: fix console warnings while running tests [VAN-1063] (#636) 2022-09-06 12:27:48 +05:00
renovate[bot]
1063945825 fix(deps): update dependency @edx/frontend-component-cookie-policy-banner to v2.2.0 2022-09-05 20:07:08 +00:00
renovate[bot]
8aaf4c676f chore(deps): update dependency @edx/reactifex to v1.1.0 2022-09-05 04:52:11 +00:00
renovate[bot]
10e6abad18 fix(deps): update dependency sanitize-html to v2.7.1 2022-09-05 04:43:49 +00:00
renovate[bot]
a69aa06123 fix(deps): update dependency reselect to v4.1.6 2022-09-05 04:35:22 +00:00
Jenkins
81a5857d07 chore(i18n): update translations 2022-09-04 11:14:52 -04:00
renovate[bot]
cc5cc30279 fix(deps): update dependency react-redux to v7.2.8 2022-09-02 17:19:45 +00:00
renovate[bot]
0d492cc9d9 fix(deps): update dependency react-onclickoutside to v6.12.2 2022-09-02 17:11:44 +00:00
renovate[bot]
5440de3684 fix(deps): update dependency fastest-levenshtein to v1.0.16 2022-09-02 17:03:28 +00:00
renovate[bot]
42768f1f09 fix(deps): update dependency @redux-devtools/extension to v3.2.3 2022-09-02 16:55:01 +00:00
renovate[bot]
0d168d79b2 fix(deps): update dependency @edx/frontend-platform to v1.15.6 2022-09-02 16:47:05 +00:00
renovate[bot]
c90b8b702b chore(deps): update dependency glob to v7.2.3 2022-09-02 13:53:47 +00:00
renovate[bot]
db64ff2098 chore(deps): update dependency babel-plugin-formatjs to v10.3.28 2022-09-02 13:45:51 +00:00
renovate[bot]
1d96fa6146 chore(deps): pin dependency eslint-plugin-import to 2.26.0 2022-09-02 13:37:48 +00:00
renovate[bot]
ce72bfdb68 fix(deps): update font awesome to v6 (#549) 2022-09-02 15:04:08 +05:00
Zainab Amir
7ac8f6a40a feat: VAN-1056 - remove formik (#621) 2022-08-31 16:04:09 +05:00
Mubbshar Anwar
1cb0cfa113 fix: sign-in header on login page (#616)
- sign-in header on login page only show when social authn is active.

VAN-1055
2022-08-29 11:05:14 +05:00
Zainab Amir
e2899e66d1 feat: update base component (#615) 2022-08-29 10:07:33 +05:00
Attiya Ishaque
7229d87fd1 fix: [VAN-628] Google logo is centerally aligned. (#619) 2022-08-26 17:52:55 +05:00
Syed Sajjad Hussain Shah
fcbabe95ea fix: Backend validations not showing on frontend for dynamic fields [VAN-1037] (#617) 2022-08-26 12:05:56 +05:00
Attiya Ishaque
4a0b23b7a1 fix: [VAN-1058] Fix the checkbox placment UX issue. (#618) 2022-08-25 16:58:14 +05:00
Shafqat Farhan
f9190549ff fix: VAN-1025 - Handle validation error on field focus (#614) 2022-08-16 15:32:12 +05:00
edx-semantic-release
79774b1d47 chore(i18n): update translations 2022-08-14 11:15:23 -04:00
Syed Sajjad Hussain Shah
fce5b23763 fix: Saved user credentials not filling [VAN-1027] (#610) 2022-08-12 16:13:10 +05:00
Zainab Amir
0af61b1387 fix: remove autocomplete from username field (#612) 2022-08-12 13:21:23 +05:00
Syed Sajjad Hussain Shah
e48ab11d5b fix: warning on password field popover margin (#611) 2022-08-12 12:00:32 +05:00
Zainab Amir
9f92c412b4 fix: turn autocomplete on (#609) 2022-08-11 17:02:44 +05:00
Zainab Amir
2b30e0998c fix: update tooltip size for smaller screens (#608) 2022-08-11 16:33:53 +05:00
Attiya Ishaque
b8443d517e fix: Small changes in extended profile (#606) 2022-08-11 15:39:42 +05:00
edx-semantic-release
e7dd047eb5 chore(i18n): update translations 2022-08-07 11:15:33 -04:00
Zainab Amir
3541c66637 feat: turn auto-complete off (#605)
Turned auto-complete off for:
- username and password field on registration form.
- username field on login form

VAN-1028
2022-08-03 16:15:35 +05:00
Syed Sajjad Hussain Shah
028e0b0dfc fix: Persist Register/Sign in form states on tab switch [VAN-618] (#585) 2022-07-26 15:28:39 +05:00
edx-semantic-release
1ce7962fa5 chore(i18n): update translations 2022-07-24 11:15:48 -04:00
Zainab Amir
70a20726a9 feat: remove spellcheck from username (#603) 2022-07-22 12:02:02 +05:00
Waheed Ahmed
f83c24c020 fix: eslint issues 2022-07-21 14:45:56 +05:00
renovate[bot]
d4bfdf699b chore(deps): update dependency ejs to 3.1.7 [security] 2022-07-21 14:45:56 +05:00
Zainab Amir
1ee501f8b9 feat: add query params to the payload (#602) 2022-07-19 23:01:08 -07:00
Zainab Amir
13d67eb2a3 feat: Remove discount banner code (#601) 2022-07-19 04:14:04 -07:00
Syed Sajjad Hussain Shah
a9558cb296 fix: password popover rendering issue (#598) 2022-07-15 14:19:43 +05:00
Syed Sajjad Hussain Shah
7706d44667 fix: password popover should use eleveation level 2 shadow (#599) 2022-07-13 19:02:49 +05:00
Syed Sajjad Hussain Shah
3486aead7f Merge pull request #597 from openedx/sajjad/VAN-577
fix: password tooltip should use extra small font size
2022-07-13 17:58:49 +05:00
Syed Sajjad Hussain Shah
2947784f00 Merge branch 'master' into sajjad/VAN-577 2022-07-13 16:57:09 +05:00
Syed Sajjad Hussain Shah
7acb102b43 fix: password tooltip should use extra small font fize 2022-07-13 16:54:37 +05:00
edx-semantic-release
4bd1f3de7d chore(i18n): update translations 2022-07-03 11:16:16 -04:00
Zainab Amir
5f2570c440 feat: update logistration pages (#591)
- removed few unused classes
- updated login page to rely on error messages generated
from the frontend only.

VAN-138
2022-06-30 17:02:04 +05:00
Syed Sajjad Hussain Shah
f066880c7c Merge pull request #596 from openedx/sajjad/VAN-510
fix: decorative image takes focus
2022-06-28 11:05:14 +05:00
Syed Sajjad Hussain Shah
1eded91f24 fix: decorative image takes focus
Decorative image on /login, /register and /reset page was taking focus on IE11

VAN-510
2022-06-27 12:23:33 +05:00
edx-semantic-release
029f201a46 chore(i18n): update translations 2022-06-19 11:16:36 -04:00
Syed Sajjad Hussain Shah
6f5a6f6500 Merge pull request #590 from openedx/sajjad/VAN-48
fix: Manage imports eslint [VAN-48]
2022-06-17 15:37:15 +05:00
Syed Sajjad Hussain Shah
9c555c06be fix: Manage imports eslint [VAN-48] 2022-06-17 15:32:41 +05:00
Attiya Ishaque
9198b122ec fix: [VAN-975] add confirm email field to registration form (#586) 2022-06-16 18:15:43 +05:00
Attiya Ishaque
5a3ee877d6 feat: [VAN-953] update progressive profiling according to MFE API (#581) 2022-06-15 18:44:25 +05:00
edx-semantic-release
3c049ab65a chore(i18n): update translations 2022-06-12 11:16:41 -04:00
Régis Behmo
ad8be560b4 build: this MFE should be included in the open releases 2022-06-09 09:27:01 -04:00
dependabot[bot]
5dea66540d build(deps): bump async from 2.6.3 to 2.6.4 (#587)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-08 11:20:59 +05:00
renovate[bot]
400648f09f chore(deps): update dependency semver-regex to 3.1.4 [security] (#584)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-08 11:12:29 +05:00
edx-semantic-release
f781f311a2 chore(i18n): update translations 2022-06-03 12:57:48 -04:00
Waheed Ahmad
9469adc386 Revert "fix: Persist Register/Sign in form states on tab switch [VAN-618]" (#582) 2022-06-03 19:22:52 +05:00
Syed Sajjad Hussain Shah
691a248477 Merge pull request #576 from openedx/sajjad/VAN-618
fix: Persist Register/Sign in form states on tab switch [VAN-618]
2022-06-02 16:28:47 +05:00
Syed Sajjad Hussain Shah
4bf0021982 fix: Persist Register/Sign in form states on tab switch [VAN-618] 2022-06-02 16:22:40 +05:00
Mubbshar Anwar
b04257a62d feat: add zendesk help (#580)
Add zendesk js library on frontend-app-authn to connect users with help desk.

VAN-958
2022-06-01 15:24:37 +05:00
Attiya Ishaque
bce8aa712d feat: add required field support (#557) 2022-05-19 18:22:51 +05:00
Attiya Ishaque
25bc11ea8d chore: [VAN-948] Add name and id to the forms (#573) 2022-05-17 15:51:02 +05:00
Zainab Amir
bcece267bc fix: update yellow arc position (#572) 2022-05-13 10:41:00 +05:00
Syed Sajjad Hussain Shah
1575034ed3 Merge pull request #571 from openedx/sajjad/VAN-548
fix: Add aria-Invalid="true" to error-state fields
2022-05-11 11:00:49 +05:00
Syed Sajjad Hussain Shah
922f1e2ac4 Merge branch 'master' into sajjad/VAN-548 2022-05-11 10:56:52 +05:00
Syed Sajjad Hussain Shah
b03fe545ba fix: Add aria-Invalid=true to invalid fields 2022-05-09 18:08:56 +05:00
Jawayria
47fce8aa7d feat: Add check for package-lock file version (#568) 2022-05-09 18:03:18 +05:00
Jawayria
5fd6754025 Merge pull request #556 from openedx/jenkins/drop-node-12-2099e62
chore!: Dropped support for Node 12
2022-05-09 13:17:39 +05:00
Jawayria
428372d198 Merge branch 'master' into jenkins/drop-node-12-2099e62 2022-05-09 13:13:08 +05:00
edx-semantic-release
4f83726387 chore(i18n): update translations 2022-05-01 11:16:39 -04:00
edX requirements bot
40cb949d4e chore!: Dropped support for Node 12 2022-04-29 16:46:06 +05:00
Syed Sajjad Hussain Shah
daa54be1c8 Merge pull request #569 from openedx/sajjad/VAN-930
fix: Styling issue in fields trailing element
2022-04-29 11:55:55 +05:00
Syed Sajjad Hussain Shah
b126635981 fix: Styling issue in fields trailing element 2022-04-28 14:54:34 +05:00
Syed Sajjad Hussain Shah
94e1207d37 Merge pull request #567 from openedx/sajjad/VAN-936
fix: Update position of cross icon on username field
2022-04-26 17:42:25 +05:00
Syed Sajjad Hussain Shah
9ec6e1cf05 fix: Update position of cross icon
Cross icon was overlapping the border the username field
when username suggestions were generated,
This PR fixes the position of cross icon of username
suggestions field

VAN-936
2022-04-26 17:28:47 +05:00
Shafqat Farhan
eddce35a37 feat: VAN-935 - Launch rename register (#566) 2022-04-26 14:13:35 +05:00
Zainab Amir
38cbd4d0d8 fix: headers hierarchy on reset page (#564) 2022-04-25 14:37:29 +05:00
edx-semantic-release
b962b3f4ca chore(i18n): update translations 2022-04-24 11:16:50 -04:00
Mubbshar Anwar
ad9b40d9e9 fix: button width (#563)
text fit in login button.
VAM-889
2022-04-22 15:28:53 +05:00
Shafqat Farhan
7990fe7483 fix: VAN-909 - Fixed the form submission with expired CSRF token (#562) 2022-04-14 17:45:37 +05:00
Mohammad Ahtasham ul Hassan
def1415f14 Upgrade frontend-build (#560)
* fix: upgrade frontend-build
2022-04-11 20:06:09 +05:00
edx-semantic-release
3f57f39187 chore(i18n): update translations 2022-04-10 11:21:30 -04:00
Usama Sadiq
0e8c8f5412 build: update transifex pull translations command (#554) 2022-04-04 17:45:25 +05:00
Zainab Amir
2099e62bb4 feat: add password change modal (#552)
* feat: add password change modal

Based on the error code sent from the platform, the modal will either
be to nudge users to change password or block users from logging in.

VAN-667
VAN-668
2022-04-04 16:42:07 +05:00
dependabot[bot]
7f7931fec5 build(deps): bump minimist from 1.2.5 to 1.2.6 (#555)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 15:27:21 +05:00
Usama Sadiq
1d039de652 Merge pull request #540 from openedx/jenkins/transifex-client-migration-69ede3f
fix: transifex migration to new client
2022-04-04 14:11:13 +05:00
Usama Sadiq
21d24d517f Merge branch 'master' into jenkins/transifex-client-migration-69ede3f 2022-04-04 14:07:35 +05:00
edx-semantic-release
3581007e59 chore(i18n): update translations 2022-04-03 11:20:44 -04:00
Usama Sadiq
df5e11d806 Merge branch 'master' into jenkins/transifex-client-migration-69ede3f 2022-03-30 21:10:55 +05:00
Ivo Branco
75f460bd17 chore(i18n): add more languages (#463)
Add languages it_IT, pt_PT, de_DE, uk, ru, hi
2022-03-29 17:04:00 +05:00
Shafqat Farhan
66261c4cea fix: Fixed the className condition to apply styling properly (#551) 2022-03-25 14:07:56 +05:00
Zainab Amir
ea6854374b fix: password reset page spinner position (#550) 2022-03-24 15:23:56 +05:00
Renovate Bot
e0385ce20d chore(deps): update dependency history to v5.3.0 2022-03-22 12:44:49 +00:00
Renovate Bot
3f53b3057b fix(deps): update dependency core-js to v3.21.1 2022-03-22 12:35:08 +00:00
Renovate Bot
517a390b2f fix(deps): update dependency @redux-devtools/extension to v3.2.2 2022-03-22 12:24:35 +00:00
renovate[bot]
6dc04be641 fix(deps): update dependency @edx/frontend-platform to v1.15.5 (#543)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-22 16:30:17 +05:00
renovate[bot]
d852f217cb fix(deps): update dependency @edx/paragon to v19.10.2 (#520)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Waheed Ahmad <waheed.ahmed@arbisoft.com>
2022-03-22 14:23:07 +05:00
Renovate Bot
178fb43a5a chore(deps): update dependency @edx/frontend-build to v9.1.2 2022-03-22 14:14:58 +05:00
Muhammad Soban Javed
76d3a13169 Merge pull request #524 from openedx/jawayria/node-16
build: Added support for Node 16
2022-03-22 14:03:50 +05:00
Muhammad Soban Javed
ce9c6e24a3 Merge branch 'master' into jawayria/node-16 2022-03-21 20:28:14 +05:00
edX Transifex Bot
6dd9d2a1dc chore(i18n): update translations 2022-03-20 11:20:15 -04:00
Jawayria
b493a09c63 feat: add support for node v16 2022-03-18 14:44:22 +05:00
edX requirements bot
5df7130310 fix: transifex migration to new client 2022-03-17 08:25:46 -04:00
Renovate Bot
69ede3f005 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.18 2022-03-17 01:11:13 +00:00
edX Transifex Bot
ea5ae8f2af chore(i18n): update translations 2022-03-15 10:46:44 -04:00
edX Transifex Bot
d352773a4e chore(i18n): update translations 2022-03-13 11:20:01 -04:00
Waheed Ahmed
3fa3954383 chore: update transifex scripts to api v3
As of April 7th, 2022 the API versions 2 and 2.5 will no longer
be operational and relevant requests will begin to fail.

VAN-894
2022-03-10 11:31:06 +05:00
Shafqat Farhan
182fb34a03 feat: VAN-876 - Rename register experiment with improved metrics (#537) 2022-03-09 16:18:51 +05:00
Zainab Amir
6e99e1e72c feat: add dynamic optional fields support (#534)
Added a new component that renders fields based on the
field descriptions returned from backend

VAN-835
2022-03-01 16:00:37 +05:00
Sarina Canelake
7e82785c7b Merge DEPR automation workflow
Add DEPR workflow automation
2022-02-24 15:22:59 -05:00
Sarina Canelake
74aa00f9cc build: add DEPR workflow automation 2022-02-23 14:40:40 -05:00
Renovate Bot
561be05bac chore(deps): update dependency follow-redirects to 1.14.8 [security] 2022-02-23 13:56:16 +05:00
Renovate Bot
b97e6292dd chore(deps): update dependency jest to v27.5.1 2022-02-09 14:32:48 +00:00
Renovate Bot
aa6f36a293 fix(deps): update dependency sanitize-html to v2.7.0 2022-02-09 14:15:03 +00:00
Zainab Amir
0b21ca3f51 feat: remove optional fields from register form (#528)
* feat: remove optional fields from register form

This is part of the larger ticket where we will move optional
fields to progressive profiling page.

VAN-837
2022-02-09 15:27:18 +05:00
edX Transifex Bot
a04872d7bf chore(i18n): update translations 2022-02-03 00:26:41 -05:00
Renovate Bot
526da727ec fix(deps): update dependency clipboard to v2.0.10 2022-02-02 18:53:12 +00:00
Renovate Bot
747dac26d7 fix(deps): update dependency core-js to v3.21.0 2022-02-01 20:14:11 +00:00
Renovate Bot
a956ccaa93 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.17 2022-01-31 03:10:51 +00:00
Renovate Bot
b37a059af7 chore(deps): update dependency es-check to v6.2.1 2022-01-31 02:43:59 +00:00
Renovate Bot
3117c9abd4 fix(deps): update dependency clipboard to v2.0.9 2022-01-28 05:18:18 +00:00
Zainab Amir
211c6afc60 feat: improve register response time (#507)
Removed validation call from the submission function

VAN-711
2022-01-26 15:48:43 +05:00
Renovate Bot
261e4cfe86 fix(deps): update dependency sanitize-html to v2.6.1 2022-01-25 15:33:49 +00:00
Waheed Ahmed
dd7ed6d9ee chore(deps): update dependency node-forge to 1.0.0 [security] 2022-01-25 16:26:13 +05:00
Waheed Ahmed
9f38be3318 chore: pin dependencies to fix security vulnerabilities 2022-01-25 16:17:08 +05:00
Renovate Bot
7415a0c0e6 fix(deps): update dependency core-js to v3.20.3 2022-01-24 16:38:03 +00:00
dependabot[bot]
ac9a390d98 build(deps): bump node-fetch from 2.6.6 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.6 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.6...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-24 17:22:16 +05:00
Renovate Bot
61c839bb09 chore(deps): update dependency node-forge to 1.0.0 [security] 2022-01-24 17:14:23 +05:00
Zainab Amir
cd3cda3c4f feat: update save_for_later event name (#511)
updated event names to reflect level of hierarchy in the
events.

VAN-830
2022-01-24 16:41:42 +05:00
Renovate Bot
242629fbdd chore(deps): update dependency follow-redirects to 1.14.7 [security] 2022-01-24 13:08:20 +05:00
edX Transifex Bot
214c383071 chore(i18n): update translations 2022-01-23 10:18:35 -05:00
Mubbshar Anwar
b8f0615434 perf: get csrf token (#504)
do csrf token call on page load time to reduce token call before login and register apis.
VAN-715
2022-01-13 19:39:34 +05:00
Renovate Bot
387e9aedf1 chore(deps): update dependency @edx/frontend-build to v9.0.6 2022-01-11 18:18:47 +00:00
Renovate Bot
d1b5ba74e8 chore(deps): update dependency jest to v27.4.7 2022-01-11 09:32:05 +00:00
Renovate Bot
1c599d8d04 fix(deps): update dependency prop-types to v15.8.1 2022-01-05 06:18:47 +00:00
Renovate Bot
5303b7b95c chore(deps): update dependency jest to v27.4.6 2022-01-05 05:57:56 +00:00
Renovate Bot
f64d9dd940 fix(deps): update dependency core-js to v3.20.2 2022-01-03 02:10:37 +00:00
Renovate Bot
3a68403773 chore(deps): update dependency es-check to v6 2021-12-30 12:25:30 +05:00
Renovate Bot
95c869a6ef chore(deps): update dependency husky to v7 2021-12-30 11:25:51 +05:00
Renovate Bot
35ee6b5a7b fix(deps): update dependency reselect to v4.1.5 2021-12-29 18:08:36 +00:00
Renovate Bot
55e44e5881 fix(deps): update dependency redux-thunk to v2.4.1 2021-12-29 17:52:18 +00:00
Renovate Bot
738af2aaac fix(deps): update dependency redux to v4.1.2 2021-12-29 17:34:08 +00:00
Renovate Bot
0f333b4a03 fix(deps): update dependency react-onclickoutside to v6.12.1 2021-12-29 17:14:26 +00:00
Renovate Bot
06bb7182fb fix(deps): update dependency prop-types to v15.8.0 2021-12-29 16:56:47 +00:00
Renovate Bot
4b8677f34d fix(deps): update dependency core-js to v3.20.1 2021-12-29 16:37:30 +00:00
Renovate Bot
f0df248ac2 chore(deps): update dependency glob to v7.2.0 2021-12-29 16:15:57 +00:00
Renovate Bot
9151cf46aa fix(deps): update react-router monorepo 2021-12-29 15:56:00 +00:00
Renovate Bot
d7f6b1ae0c fix(deps): update font awesome 2021-12-29 15:33:29 +00:00
Renovate Bot
d7c1e119fb chore(deps): update dependency @edx/frontend-build to v9.0.5 2021-12-29 15:10:30 +00:00
Renovate Bot
35d6c9d7bc fix(deps): update dependency react-redux to v7.2.6 2021-12-29 14:46:36 +00:00
Renovate Bot
9f9fe2ed1a fix(deps): update dependency formik to v2.2.9 2021-12-29 14:23:06 +00:00
Renovate Bot
96ec8c3943 chore(deps): update dependency es-check to v5.2.4 2021-12-29 13:58:56 +00:00
Renovate Bot
23bac18450 chore(deps): update dependency codecov to v3.8.2 2021-12-29 13:32:55 +00:00
Renovate Bot
094573be1f chore(deps): update dependency jest to v27 2021-12-29 09:13:32 +00:00
Renovate Bot
b2f2760774 fix(deps): pin dependencies 2021-12-29 11:25:00 +05:00
Renovate Bot
933e8f85fd chore(deps): update dependency history to v5.2.0 2021-12-29 11:17:28 +05:00
Waheed Ahmed
d9b9c4d290 chore: update renovate config 2021-12-28 15:13:22 +05:00
Renovate Bot
892f3d33ca fix(deps): update dependency @edx/frontend-component-cookie-policy-banner to v2.1.14 2021-12-28 15:01:16 +05:00
Waheed Ahmed
c1b402ebd6 fix: npm build issues 2021-12-28 14:43:57 +05:00
Renovate Bot
a081544440 chore(deps): update dependency immer to 9.0.6 [security] 2021-12-28 14:43:57 +05:00
Renovate Bot
6fee02110a fix(deps): update dependency redux-devtools-extension to v2.13.9 2021-12-28 13:46:24 +05:00
Renovate Bot
aef3f6a179 chore(deps): update dependency ws to 7.4.6 [security] 2021-12-28 12:41:38 +05:00
Renovate Bot
d7391dfd66 chore(deps): update dependency hosted-git-info to 2.8.9 [security] 2021-12-28 12:19:11 +05:00
Renovate Bot
4f7fbf82bf chore(deps): update dependency postcss to 7.0.36 [security] 2021-12-28 12:14:52 +05:00
edX Transifex Bot
0d01a9fea3 chore(i18n): update translations 2021-12-26 10:17:45 -05:00
Renovate Bot
88a6e1b260 fix(deps): update dependency classnames to v2.3.1 2021-12-25 02:08:44 +00:00
Waheed Ahmed
b142e159b6 chore: update renovate config 2021-12-23 15:10:42 +05:00
Attiya Ishaque
2a079e484f fix: Replace hardcoded edX name with SITE_NAME env variable. (#486) 2021-12-22 13:10:34 +05:00
edX Transifex Bot
1210fe01ae chore(i18n): update translations 2021-12-12 10:17:23 -05:00
dependabot[bot]
7400da7ba6 build(deps): bump lodash from 4.17.15 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-08 19:33:39 +05:00
Renovate Bot
a86f5c43ad chore(deps): update dependency @edx/frontend-build to v5.6.14 2021-12-08 17:57:22 +05:00
Renovate Bot
531b82745f chore(deps): update dependency ansi-regex to 5.0.1 [security] 2021-12-08 17:42:19 +05:00
Waheed Ahmed
ebdaeb80f1 chore: bump frontend-platform to v1.14.2 2021-12-08 16:35:01 +05:00
Zainab Amir
488b364215 refactor: update account verification messages 2021-12-07 15:16:21 +05:00
Shafqat Farhan
619c75a4b7 feat: VAN-666 - Reject new password that is detected as vulnerable (#471) 2021-12-07 13:46:53 +05:00
Mubbshar Anwar
be783fed99 feat: fire track event (#456)
fire track event for save for latter course
VAN-741
2021-11-22 14:47:42 +05:00
Attiya Ishaque
59d5a400f4 fix: remove oal domain (#469) 2021-11-17 13:08:05 +05:00
Waheed Ahmed
e0e0c1011e feat: enable full name based username suggestions
Enable full name based username suggestions and remove experiment
related changes.
2021-11-15 15:57:10 +05:00
edX Transifex Bot
2070877a84 chore(i18n): update translations 2021-11-14 20:16:47 +05:00
Waheed Ahmed
8c8cd2bcd5 chore: remove opt-in/opt-out experiment code
Removed opt-in/opt-out experiment code to enable it for all users
using feature flag only.
2021-11-10 17:13:58 +05:00
Zainab Amir
a5e7a3a592 feat: add rename register experiment details (#464) 2021-11-10 10:45:06 +05:00
Waheed Ahmed
007dfa82a8 chore: bump frontend-platform version to v1.13.1 2021-11-02 11:40:46 +05:00
edX Transifex Bot
8223f0c988 chore(i18n): update translations 2021-10-31 20:16:50 +05:00
Adeel Ehsan
335f9fcaf8 Merge pull request #459 from edx/aehsan/760/remove_education_level_using_coppa
Remove el education level based on coppa flag
2021-10-28 15:07:17 +05:00
adeelehsan
31a2a8f402 feat: Remove el option from education
remove el option from education levels if coppa flag is set true.

VAN-760
2021-10-28 15:02:56 +05:00
Adeel Ehsan
54fb55fe8f Merge pull request #458 from edx/aehsan/scroll_added_for_username_suggestion
scroll added for username suggestions
2021-10-27 23:56:02 +05:00
adeelehsan
c34360f2fb scroll added for username suggestions 2021-10-27 23:51:42 +05:00
Mubbshar Anwar
82d339c91b fix: update opt in text (#457)
update opt in checkbox text on register page.
VAN-740
2021-10-27 18:09:30 +05:00
Adeel Ehsan
4d9b508e19 Merge pull request #455 from edx/aehsan/username_suggestions_issue_fixed
username suggestions issue fixed
2021-10-26 17:27:24 +05:00
adeelehsan
60e51a8f25 username suggestions issue fixed 2021-10-26 17:21:07 +05:00
Adeel Ehsan
350a6b10ae Merge pull request #440 from edx/aehsan/702/design_updated_for_username_suggestions
username design changed.
2021-10-26 12:21:17 +05:00
adeelehsan
4ba9e1194b username design changed according to the VAN-702 2021-10-25 12:27:31 +05:00
edX Transifex Bot
6cb470522b chore(i18n): update translations 2021-10-24 20:16:43 +05:00
Attiya Ishaque
a3953b672f fix: [VAN-747] Fix tests in which registered email msg changed (#453) 2021-10-22 18:16:46 +05:00
Usama Sadiq
e91a8a4dfa Merge pull request #452 from edx/aht/bom-2901-switch-ci-to-github-actions
BOM-2901: Replace travis with github CI
2021-10-22 15:13:11 +05:00
Usama Sadiq
17048f5caf build: replace travis with github ci 2021-10-22 14:52:30 +05:00
Attiya Ishaque
955cf26860 feat: [VAN-655] User's year of birth field behind the feature flag (#449) 2021-10-20 15:40:27 +05:00
edX Transifex Bot
ee094a1330 chore(i18n): update translations 2021-10-17 20:17:36 +05:00
Attiya Ishaque
75269a3372 feat: Add feature flag for year of birth field (#447) 2021-10-11 21:19:47 +05:00
Mubbshar Anwar
2ac0f83e87 fix: A/B test marketing opt-in (#448)
fire optimizely event to record opt-in/out for marketing emails

VAN-738
2021-10-11 19:12:42 +05:00
edX Transifex Bot
b0df7a0593 chore(i18n): update translations 2021-10-10 20:17:31 +05:00
Mubbshar Anwar
14e89575d1 feat: record opt in/out (#445)
add opt field in register form and record it for marketing emails.

VAN-738
2021-10-08 19:00:18 +05:00
Ned Batchelder
4e7003ca5e build: use the organization commitlint check 2021-10-07 13:50:58 -04:00
Shafqat Farhan
6bc11b01f5 fix: VAN-719 - Handled password field error violation on username change (#444) 2021-10-05 11:58:14 +05:00
edX Transifex Bot
b5ada5d2d5 chore(i18n): update translations 2021-09-26 20:17:37 +05:00
Uzair Rasheed
21ff9b81f9 Merge pull request #441 from edx/update-error-msg-for-username
update error msg for username
2021-09-22 12:13:02 +05:00
uzairr
cdf0c29d01 update error msg for username 2021-09-22 04:29:59 +05:00
Shafqat Farhan
57cc5a4692 fix: VAN-719 - Handled password field error violation (#439) 2021-09-21 16:58:14 +05:00
Waheed Ahmed
7cb58d4f26 bump frontend-platform version to 1.12.7 2021-09-17 18:52:05 +05:00
Attiya Ishaque
b1e9f631dc chore: update paragon version (#436) 2021-09-16 14:46:28 +05:00
edX Transifex Bot
0a25ec2037 chore(i18n): update translations 2021-09-14 13:52:20 +05:00
Attiya Ishaque
3164afa46a fix: SSO *Apple* translated in authn pages. (#434) 2021-09-13 17:39:17 +05:00
edX Transifex Bot
bff75b811d chore(i18n): update translations 2021-09-12 20:17:35 +05:00
Attiya Ishaque
d7b729cc98 Fix: Some design issue on register page. (#431) 2021-09-08 15:32:24 +05:00
Attiya Ishaque
8917b88a5a fix: design issue in discount experiment (#428) 2021-09-07 17:21:37 +05:00
edX Transifex Bot
53e40480f2 fix(i18n): update translations 2021-09-05 20:17:30 +05:00
Waheed Ahmed
9c20e91563 fix: fix translations for 15% discount strings 2021-09-02 20:01:32 +05:00
Zainab Amir
582af7a8b0 fix: use correct experiment variable (#427) 2021-09-01 17:35:15 +05:00
Attiya Ishaque
62ed8a631e chore: add 15% discount incentive to increase registrations. (#426) 2021-09-01 16:50:08 +05:00
Attiya Ishaque
bca605c632 fix: [VAN-685] Failed to execute removeChild on Node (#425) 2021-08-26 17:29:30 +05:00
Waheed Ahmed
c9cf7c5190 chore: upgrade frontend-platform to 1.12.4 2021-08-23 15:14:31 +05:00
Waheed Ahmed
e29e35c093 chore: send analytic events for Welcome page
VAN-679
2021-08-17 15:47:26 +05:00
edX Transifex Bot
85276767eb fix(i18n): update translations 2021-08-15 20:17:29 +05:00
Zainab Amir
1de911122a fix: reset password banner image changed (#420)
In addition to the check for authenticated user, added a welcome
banner check to restrict the welcome banner for only progressive
profiling page.

VAN-688
2021-08-12 18:26:05 +05:00
Attiya Ishaque
68cc93da1c fix: [VAN-332] Full name validation on registration page. (#416) 2021-08-12 13:25:58 +05:00
Waheed Ahmed
419915d752 chore: bump frontend-platform version 2021-08-12 13:00:42 +05:00
Waheed Ahmed
63572d43f6 Revert "chore: upgrade frontend-platform version to 1.12.1"
This reverts commit 5c8d682a83.
2021-08-11 19:54:56 +05:00
Mubbshar Anwar
498325e6e7 fix: stop auto complete country (#415)
fix to stop auto complete country field when address is saved in browser

VAN-671
2021-08-11 16:17:10 +05:00
Waheed Ahmed
5c8d682a83 chore: upgrade frontend-platform version to 1.12.1 2021-08-11 15:25:04 +05:00
196 changed files with 33307 additions and 28550 deletions

28
.env
View File

@@ -13,14 +13,24 @@ ORDER_HISTORY_URL=null
REFRESH_ACCESS_TOKEN_ENDPOINT=null
SEGMENT_KEY=''
SITE_NAME=null
USER_INFO_COOKIE_NAME=null
AUTHN_MINIMAL_HEADER=true
LOGIN_ISSUE_SUPPORT_LINK=''
REGISTRATION_OPTIONAL_FIELDS=''
USER_SURVEY_COOKIE_NAME=null
COOKIE_DOMAIN=null
WELCOME_PAGE_SUPPORT_LINK=null
INFO_EMAIL=''
DISABLE_ENTERPRISE_LOGIN=''
# ***** Cookies *****
REGISTER_CONVERSION_COOKIE_NAME=null
ENABLE_PROGRESSIVE_PROFILING=''
USER_SURVEY_COOKIE_NAME=null
# ***** Links *****
LOGIN_ISSUE_SUPPORT_LINK=''
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
# ***** Features flags *****
DISABLE_ENTERPRISE_LOGIN=''
ENABLE_COOKIE_POLICY_BANNER=''
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
ENABLE_PERSONALIZED_RECOMMENDATIONS=''
MARKETING_EMAILS_OPT_IN=''
SHOW_CONFIGURABLE_EDX_FIELDS=''
# ***** Zendesk related keys *****
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -17,16 +17,21 @@ MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='http://localhost:1996/orders'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=''
SITE_NAME='edX'
USER_INFO_COOKIE_NAME='edx-user-info'
AUTHN_MINIMAL_HEADER=true
LOGIN_ISSUE_SUPPORT_LINK='/login-issue-support-url'
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
PRIVACY_POLICY='http://localhost:18000/privacy'
REGISTRATION_OPTIONAL_FIELDS=''
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
COOKIE_DOMAIN='localhost'
WELCOME_PAGE_SUPPORT_LINK='http://localhost:1999/welcome'
INFO_EMAIL='info@edx.org'
DISABLE_ENTERPRISE_LOGIN=''
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'
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''

5
.env.private.example Normal file
View File

@@ -0,0 +1,5 @@
# 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

@@ -15,10 +15,8 @@ MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='http://localhost:1996/orders'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=''
SITE_NAME='edX'
USER_INFO_COOKIE_NAME='edx-user-info'
LOGIN_ISSUE_SUPPORT_LINK='https://login-issue-support-url.com'
SITE_NAME='Your Platform Name Here'
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
WELCOME_PAGE_SUPPORT_LINK='http://localhost:1999/welcome'
DISABLE_ENTERPRISE_LOGIN=''
REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion'
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -1,5 +1,6 @@
coverage/*
dist/
docs
node_modules/
__mocks__/
__snapshots__/

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,7 +19,36 @@ module.exports = createConfig('eslint', {
labelAttributes: [],
controlComponents: [],
assert: 'htmlFor',
depth: 25
depth: 25,
}],
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: true }],
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
['sibling', 'parent'],
'index',
],
pathGroups: [
{
pattern: '@(react|react-dom|react-redux)',
group: 'external',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'function-paren-newline': 'off',
'no-import-assign': 'off',
'react/no-unstable-nested-components': 'off',
},
});

29
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,29 @@
### Description
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|
|-------|-----|
| | |
#### Merge Checklist
* [ ] If your update includes visual changes, have they been reviewed by a designer? Send them a link to the Sandbox, if applicable.
* [ ] Is there adequate test coverage for your changes?
#### Post-merge Checklist
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/vanguards** to do it.
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.

View File

@@ -0,0 +1,19 @@
# Run the workflow that adds new tickets that are either:
# - labelled "DEPR"
# - title starts with "[DEPR]"
# - body starts with "Proposal Date" (this is the first template field)
# to the org-wide DEPR project board
name: Add newly created DEPR issues to the DEPR project board
on:
issues:
types: [opened]
jobs:
routeissue:
uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}

View File

@@ -0,0 +1,20 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "label: " it tries to apply
# the label indicated in rest of comment.
# If the comment starts with "remove label: ", it tries
# to remove the indicated label.
# Note: Labels are allowed to have spaces and this script does
# not parse spaces (as often a space is legitimate), so the command
# "label: really long lots of words label" will apply the
# label "really long lots of words label"
name: Allows for the adding and removing of labels via comment
on:
issue_comment:
types: [created]
jobs:
add_remove_labels:
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master

21
.github/workflows/autoupdate.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: autoupdate
on:
push:
branches:
- master
jobs:
autoupdate:
name: autoupdate
runs-on: ubuntu-22.04
steps:
- uses: docker://chinthakagodawita/autoupdate-action:v1
env:
GITHUB_TOKEN: "${{ secrets.CC_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"

44
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: node_CI
on:
push:
branches:
- master
pull_request:
branches:
- '**'
jobs:
tests:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VER }}
- name: Install Dependencies
run: npm ci
- name: Verify No Uncommitted Package-Lock Changes
run: make validate-no-uncommitted-package-lock-changes
- name: Run i18n_extract
run: npm run i18n_extract
- name: Lint
run: npm run lint
- name: Test
run: npm run test
- name: Build
run: npm run build
- name: Run Code Coverage
uses: codecov/codecov-action@v3

10
.github/workflows/commitlint.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
# Run commitlint on the commit messages in a pull request.
name: Lint Commit Messages
on:
- pull_request
jobs:
commitlint:
uses: openedx/.github/.github/workflows/commitlint.yml@master

View File

@@ -0,0 +1,13 @@
#check package-lock file version
name: Lockfile Version check
on:
push:
branches:
- master
pull_request:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master

12
.github/workflows/self-assign-issue.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "assign me" it assigns the author to the
# ticket (case insensitive)
name: Assign comment author to ticket if they say "assign me"
on:
issue_comment:
types: [created]
jobs:
self_assign_by_comment:
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ node_modules
npm-debug.log
coverage
module.config.js
.env.private
dist/
src/i18n/transifex_input.json

View File

@@ -1,7 +1,6 @@
.eslintignore
.eslintrc.json
.gitignore
.travis.yml
docker-compose.yml
Dockerfile
Makefile

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18

View File

@@ -1,13 +0,0 @@
language: node_js
node_js: 12
install:
- npm ci
script:
- make validate-no-uncommitted-package-lock-changes
- npm run i18n_extract
- npm run lint
- npm run test
- npm run build
- npm run is-es5
after_success:
- codecov

View File

@@ -1,8 +1,9 @@
[main]
host = https://www.transifex.com
[edx-platform.frontend-app-authn]
[o:open-edx:p:edx-platform:r:frontend-app-authn]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON
type = KEYVALUEJSON

View File

@@ -1,2 +1,2 @@
# The following users are the owners of all frontend-app-authn files
* @edx/vanguards
* @openedx/vanguards

16
Makefile Executable file → Normal file
View File

@@ -1,11 +1,9 @@
transifex_resource = frontend-app-authn
transifex_langs = "ar,fr,es_419,zh_CN"
export TRANSIFEX_RESOURCE = frontend-app-authn
transifex_langs = "ar,fr,es_419,zh_CN,it_IT,pt_PT,de_DE,uk,ru,hi"
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
@@ -38,17 +36,17 @@ push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --language=$(transifex_langs)
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
# This target is used by Travis.
# This target is used by CI.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json

View File

@@ -1,48 +1,193 @@
|Build Status| |Codecov| |license|
|Build Status| |ci-badge| |Codecov| |semantic-release|
frontend-app-authn
=================================
====================
Please tag **@openedx/vanguards** on any PRs or issues. Thanks!
Introduction
------------
This is a micro-frontend application responsible for the login, registration and password reset functionality.
Development
-----------
**What is the domain of this MFE?**
Start Devstack
^^^^^^^^^^^^^^
- Register page
To use this application `devstack <https://github.com/edx/devstack>`__ must be running.
- Login page
- Start devstack
- Forgot password page
Start the development server
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Reset password page
In this project, install requirements and start the development server by running:
- Progressive profiling page
.. code:: bash
npm install
npm start # The server will run on port 1999
Installation
------------
Once the dev server is up visit http://localhost:1999/login.
This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
Configuration and Deployment
----------------------------
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
This MFE is configured via node environment variables supplied at build time. See the .env file for the list of required environment variables. Example build syntax with a single environment variable:
2. Start up LMS, if it's not already started.
.. code:: bash
4. Within this project (frontend-app-authn), install requirements and start the development server:
NODE_ENV=development ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
.. code-block::
npm install
npm start # The server will run on port 1999
5. Once the dev server is up, visit http://localhost:1999 to access the MFE
.. image:: ./docs/images/frontend-app-authn-localhost-preview.png
**Note:** Follow `Enable social auth locally <docs/how_tos/enable_social_auth.rst>`_ for enabling Social Sign-on Buttons (SSO) locally
Environment Variables/Setup Notes
---------------------------------
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
The authentication micro-frontend also requires the following additional variable:
.. list-table:: Environment Variables
:widths: 30 50 20
:header-rows: 1
* - Name
- Description / Usage
- Example
* - ``LOGIN_ISSUE_SUPPORT_LINK``
- The fully-qualified URL to the login issue support page in the target environment.
- ``https://support.example.com``
* - ``ACTIVATION_EMAIL_SUPPORT_LINK``
- The fully-qualified URL to the activation email support page in the target environment.
- ``https://support.example.com``
* - ``PASSWORD_RESET_SUPPORT_LINK``
- The fully-qualified URL to the password reset support page in the target environment.
- ``https://support.example.com``
* - ``AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK``
- The fully-qualified URL to the progressive profiling support page in the target environment.
- ``https://support.example.com``
* - ``TOS_AND_HONOR_CODE``
- The fully-qualified URL to the Honor code page in the target environment.
- ``https://example.com/honor``
* - ``TOS_LINK``
- The fully-qualified URL to the Terms of service page in the target environment.
- ``https://example.com/tos``
* - ``PRIVACY_POLICY``
- The fully-qualified URL to the Privacy policy page in the target environment.
- ``https://example.com/privacy``
* - ``INFO_EMAIL``
- The valid email address for information query regarding the target environment.
- ``info@example.com``
* - ``ENABLE_DYNAMIC_REGISTRATION_FIELDS``
- Enables support for configurable registration fields on the MFE. This flag must be enabled to show any required registration field besides the default fields (name, email, username, password).
- ``true`` | ``''`` (empty strings are falsy)
* - ``ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN``
- Enables support for progressive profiling. If enabled, users are redirected to a second page where data for optional registration fields can be collected.
- ``true`` | ``''`` (empty strings are falsy)
* - ``DISABLE_ENTERPRISE_LOGIN``
- 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`` | ``''``
* - ``ENABLE_COOKIE_POLICY_BANNER``
- Enables support for displaying the cookies acceptance banner.
- ``true`` | ``''`` (empty strings are falsy)
edX-specific Environment Variables
**********************************
Furthermore, there are several edX-specific environment variables that enable integrations with closed-source services private to the edX organization, and might be unsupported in Open edX.
.. list-table:: edX-specific Environment Variables
:widths: 30 50 20
:header-rows: 1
* - Name
- Description / Usage
- Example
* - ``MARKETING_EMAILS_OPT_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://github.com/edx/edx-developer-docs/blob/5191e800bf16cf42f25c58c58f983bdaf7f9305d/docs/micro-frontends-in-open-edx.rst>`__.
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
How To Contribute
------------
Contributions are very welcome, and strongly encouraged! We've
put together `some documentation that describes our contribution process <https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html>`_.
Even though they were written with edx-platform in mind, the guidelines should be followed for Open edX code in general.
PR description template should be automatically applied if you are sending PR from github interface; otherwise you
can find it it at `PULL_REQUEST_TEMPLATE.md <https://github.com/openedx/frontend-app-authn/blob/master/.github/pull_request_template.md>`_
This project is currently accepting all types of contributions, bug fixes and security fixes.
Open edX Code of Conduct
------------------------
All community members are expected to follow the `Open edX Code of Conduct <https://openedx.org/code-of-conduct/>`_.
People
------
The assigned maintainers for this component and other project details may be
found in `Backstage <https://backstage.openedx.org/catalog/default/group/vanguards>`_. Backstage pulls this data from the ``catalog-info.yaml``
file in this repo.
Reporting Security Issues
-------------------------
Please do not report security issues in public. Please email security@edx.org.
Known Issues
------------
None
License
-------
The code in this repository is licensed under the GNU Affero General Public License v3.0, unless
otherwise noted.
Please see `LICENSE <https://github.com/openedx/frontend-app-authn/blob/master/LICENSE>`_ for details.
==============================
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-app-authn.svg?branch=master
:target: https://travis-ci.com/edx/frontend-app-authn
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-authn
:target: https://codecov.io/gh/edx/frontend-app-authn
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-authn.svg
:target: @edx/frontend-app-authn
.. |ci-badge| image:: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml/badge.svg
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
:alt: Continuous Integration
.. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
:target: https://github.com/semantic-release/semantic-release

18
catalog-info.yaml Normal file
View File

@@ -0,0 +1,18 @@
# This file records information about this repo. Its use is described in OEP-55:
# https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0055-proc-project-maintainers.html
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: 'frontend-app-authn'
description: "Micro-frontend for authentication service. It contains views for login, registration and password reset functionality."
links:
- url: 'https://github.com/openedx/frontend-app-authn/blob/master/README.rst'
title: 'Documentation'
icon: 'Article'
annotations:
openedx.org/arch-interest-groups: ""
spec:
owner: group:vanguards
type: 'service'
lifecycle: 'production'

View File

@@ -91,7 +91,7 @@ In the data sub-directory, the file names describe what each piece of code does.
/ProfilePhotoUploader.jsx // supporting view
/data // Note: most files here are named with a plural, as they contain many of the things in question.
/actions.js
/constants.js
/mockedData.js
/reducers.js
/sagas.js
/selectors.js

View File

@@ -0,0 +1,14 @@
Enable Social Auth Locally
--------------------------
Please follow the steps below to enable social auth (SSO) locally.
1. Follow `Enabling Third Party Authentication <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/tpa/index.html>`_ for backend configuration.
2. Authn has a component for rendering Social Auth providers at frontend which goes through each provider.
* If the provider has an ``iconImage``, then it will be rendered as image in SSO button.
* If ``iconImage`` is not available in provider, but the provider's ``iconClass`` is from the supported icon classes ``['apple', 'facebook', 'google', 'microsoft']`` then it is used as icon image.
* If ``iconClass`` doesn't match the supported icon classes then the ``faSignInAlt`` from font awesome icons is used as icon image for SSO button.

View File

@@ -2,4 +2,4 @@
React App i18n HOWTO
####################
This document has moved to the frontend-platform repo: https://github.com/edx/frontend-platform/blob/master/docs/how_tos/i18n.rst
This document has moved to the frontend-platform repo: https://github.com/openedx/frontend-platform/blob/master/docs/how_tos/i18n.rst

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

View File

@@ -8,5 +8,7 @@ module.exports = createConfig('jest', {
'src/setupTest.js',
'src/i18n',
'src/index.jsx',
'MainApp.jsx',
],
testEnvironment: 'jsdom',
});

View File

@@ -3,7 +3,6 @@
nick: Authn MFE
oeps: {}
owner: edx/vanguards
owner: openedx/vanguards
openedx-release:
maybe: true # Delete this "maybe" line when you have decided about Open edX inclusion.
ref: master

47949
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,16 +4,14 @@
"description": "Frontend application template",
"repository": {
"type": "git",
"url": "git+https://github.com/edx/frontend-app-authn.git"
"url": "git+https://github.com/openedx/frontend-app-authn.git"
},
"browserslist": [
"last 2 versions",
"ie 11"
"extends @edx/browserslist-config"
],
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"is-es5": "es-check es5 ./dist/*.js",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
@@ -26,64 +24,68 @@
},
"author": "edX",
"license": "AGPL-3.0",
"homepage": "https://github.com/edx/frontend-app-authn#readme",
"homepage": "https://github.com/openedx/frontend-app-authn#readme",
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/edx/frontend-app-authn/issues"
"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.1.12",
"@edx/frontend-platform": "1.12.0",
"@edx/paragon": "16.6.1",
"@fortawesome/fontawesome-svg-core": "1.2.32",
"@fortawesome/free-brands-svg-icons": "5.15.1",
"@fortawesome/free-regular-svg-icons": "5.15.1",
"@fortawesome/free-solid-svg-icons": "5.15.1",
"@fortawesome/react-fontawesome": "0.1.13",
"classnames": "2.2.6",
"core-js": "3.9.1",
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-cookie-policy-banner": "2.2.2",
"@edx/frontend-platform": "4.2.0",
"@edx/paragon": "20.30.1",
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-brands-svg-icons": "6.2.1",
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@optimizely/react-sdk": "^2.9.1",
"@redux-devtools/extension": "3.2.5",
"algoliasearch": "^4.14.3",
"classnames": "2.3.2",
"core-js": "3.30.0",
"extract-react-intl-messages": "4.1.1",
"fastest-levenshtein": "1.0.12",
"form-urlencoded": "4.2.1",
"formik": "2.2.6",
"fastest-levenshtein": "1.0.16",
"form-urlencoded": "6.1.0",
"lodash.camelcase": "4.3.0",
"lodash.snakecase": "4.1.1",
"prop-types": "15.7.2",
"prop-types": "15.8.1",
"query-string": "5.1.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "6.1.0",
"react-loading-skeleton": "2.2.0",
"react-onclickoutside": "6.11.2",
"react-redux": "7.2.3",
"react-loading-skeleton": "3.2.0",
"react-onclickoutside": "6.13.0",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"redux": "4.0.5",
"redux-devtools-extension": "2.13.8",
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
"react-zendesk": "^0.1.13",
"redux": "4.2.0",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.4",
"redux-saga": "1.1.3",
"redux-thunk": "2.3.0",
"regenerator-runtime": "0.13.9",
"reselect": "4.0.0",
"universal-cookie": "^4.0.4"
"redux-saga": "1.2.3",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.13.11",
"reselect": "4.1.7",
"sanitize-html": "2.10.0",
"semver-regex": "3.1.4",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@edx/frontend-build": "5.6.11",
"babel-plugin-react-intl": "8.2.25",
"codecov": "3.8.1",
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "12.8.6",
"@edx/reactifex": "1.1.0",
"babel-plugin-formatjs": "10.4.0",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"es-check": "5.2.3",
"glob": "7.1.6",
"history": "5.0.0",
"husky": "4.3.8",
"jest": "26.6.3",
"react-test-renderer": "16.14.0",
"reactifex": "1.1.1"
"enzyme-adapter-react-16": "1.15.7",
"eslint-plugin-import": "2.26.0",
"glob": "7.2.3",
"history": "5.3.0",
"husky": "7.0.4",
"jest": "29.5.0",
"react-test-renderer": "16.14.0"
}
}

View File

@@ -1,10 +1,9 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Authn | edX</title>
<title>Authn | <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<% if (process.env.OPTIMIZELY_URL) { %>
<script
src="<%= process.env.OPTIMIZELY_URL %>"

View File

@@ -1,9 +1,20 @@
{
"extends": [
"config:base"
"config:base",
":automergeLinters",
":automergeTesters",
":automergeMinor",
":noUnscheduledUpdates",
":semanticCommits"
],
"patch": {
"automerge": true
},
"rebaseStalePrs": true
"rebaseStalePrs": true,
"schedule": [
"every weekday"
],
"packageRules": [
{
"matchPackageNames": ["node", "npm"],
"enabled": false
}
]
}

View File

@@ -1,25 +1,38 @@
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 { AppProvider } from '@edx/frontend-platform/react';
import {
UnAuthOnlyRoute, registerIcons, NotFoundPage, Logistration,
Logistration, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
} from './common-components';
import {
LOGIN_PAGE, PAGE_NOT_FOUND, REGISTER_PAGE, RESET_PAGE, PASSWORD_RESET_CONFIRM, WELCOME_PAGE,
} from './data/constants';
import configureStore from './data/configureStore';
import {
AUTHN_PROGRESSIVE_PROFILING,
LOGIN_PAGE,
PAGE_NOT_FOUND,
PASSWORD_RESET_CONFIRM,
RECOMMENDATIONS,
REGISTER_PAGE,
RESET_PAGE,
} from './data/constants';
import { updatePathWithQueryParams } from './data/utils';
import ForgotPasswordPage from './forgot-password';
import ResetPasswordPage from './reset-password';
import WelcomePage from './welcome';
import { ForgotPasswordPage } from './forgot-password';
import { ProgressiveProfiling } from './progressive-profiling';
import { RecommendationsPage } from './recommendations';
import { ResetPasswordPage } from './reset-password';
import './index.scss';
registerIcons();
const MainApp = () => (
<AppProvider store={configureStore()}>
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
{getConfig().ZENDESK_KEY && <Zendesk />}
<Switch>
<Route exact path="/">
<Redirect to={updatePathWithQueryParams(REGISTER_PAGE)} />
@@ -28,7 +41,8 @@ const MainApp = () => (
<UnAuthOnlyRoute exact path={REGISTER_PAGE} component={Logistration} />
<UnAuthOnlyRoute exact path={RESET_PAGE} component={ForgotPasswordPage} />
<Route exact path={PASSWORD_RESET_CONFIRM} component={ResetPasswordPage} />
<Route exact path={WELCOME_PAGE} component={WelcomePage} />
<Route exact path={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} />

View File

@@ -1,97 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Col, Hyperlink, Image, Row,
} from '@edx/paragon';
import messages from './messages';
const AuthExtraLargeLayout = (props) => {
const { intl, username, variant } = props;
return (
<div className="container row p-0 m-0 large-screen-container">
<div className="col-md-9 p-0 screen-header-light">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image alt="edx" className="logo position-absolute" src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="min-vh-100 d-flex align-items-center">
<div>
<Row>
<Col xs={3}>
<svg className={classNames(
'ml-5 mt-5',
{
'extra-large-svg-line': variant === 'xl',
'extra-extra-large-svg-line': variant === 'xxl',
},
)}
>
<line x1="60" y1="0" x2="5" y2="220" />
</svg>
</Col>
<Col xs={9}>
<div className={classNames(
'data-hj-suppress',
{
h3: variant === 'xl',
h2: variant === 'xxl',
},
)}
>
{intl.formatMessage(
messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username },
)}
</div>
<div
className={classNames(
'text-primary',
{
'display-1': variant === 'xl',
'display-2': variant === 'xxl',
},
)}
>
{intl.formatMessage(messages['complete.your.profile.1'])}
<span className="text-accent-a">
<br />
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</div>
</Col>
</Row>
</div>
</div>
</div>
<div className="col-md-3 p-0 screen-polygon">
<svg
width="100%"
height="100%"
className="m1-n1 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>
);
};
AuthExtraLargeLayout.defaultProps = {
variant: 'xl',
};
AuthExtraLargeLayout.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
variant: PropTypes.oneOf(['xl', 'xxl']),
};
export default injectIntl(AuthExtraLargeLayout);

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 AuthLargeLayout = ({ 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>
);
};
AuthLargeLayout.propTypes = {
username: PropTypes.string.isRequired,
};
export default AuthLargeLayout;

View File

@@ -1,69 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import {
Col, Hyperlink, Image, Row,
} from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const AuthMediumLayout = (props) => {
const { intl, username } = props;
const AuthMediumLayout = ({ username }) => {
const { formatMessage } = useIntl();
return (
<div className="container row p-0 mb-3 medium-container">
<div className="col-md-10 p-0 screen-header-light">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="d-flex align-items-center justify-content-center ml-6">
<div>
<Row>
<Col xs={3}>
<svg className="medium-svg-line ml-5 mt-5">
<line x1="60" y1="0" x2="5" y2="220" />
</svg>
</Col>
<Col xs={9}>
<h3 className="data-hj-suppress">
{intl.formatMessage(
messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username },
)}
</h3>
<div className="display-1 text-primary">
{intl.formatMessage(messages['complete.your.profile.1'])}
<span className="text-accent-a">
<br />
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
<>
<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>
</Col>
</Row>
</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>
<div className="col-md-2 p-0 screen-polygon">
<svg
width="100%"
height="100%"
className="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: intlShape.isRequired,
username: PropTypes.string.isRequired,
};
export default injectIntl(AuthMediumLayout);
export default AuthMediumLayout;

View File

@@ -1,68 +1,41 @@
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Col, Hyperlink, Image, Row,
} from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
const AuthSmallLayout = (props) => {
const { intl, username, variant } = props;
const AuthSmallLayout = ({ username }) => {
const { formatMessage } = useIntl();
return (
<div className="small-screen-header-light">
<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 alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
<div className={classNames('d-flex mt-3', { 'pl-6': variant === 'sm' })}>
<div className="d-flex align-items-center mb-3 mt-3 mr-3">
<div className="small-yellow-line mt-4.5" />
<div>
<Row>
<Col xs={3}>
<svg className={classNames(
'mt-4\.5', // eslint-disable-line no-useless-escape
{
'extra-small-svg-line': variant === 'xs',
'small-svg-line': variant === 'sm',
},
)}
>
<line x1="60" y1="0" x2="5" y2="220" />
</svg>
</Col>
<Col xs={9}>
<h5 className="data-hj-suppress">
{intl.formatMessage(
messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username },
)}
</h5>
<h1>
{intl.formatMessage(messages['complete.your.profile.1'])}
<br />
<span className="text-accent-a">
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</h1>
</Col>
</Row>
<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>
);
};
AuthSmallLayout.defaultProps = {
variant: 'sm',
};
AuthSmallLayout.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
variant: PropTypes.oneOf(['sm', 'xs']),
};
export default injectIntl(AuthSmallLayout);
export default AuthSmallLayout;

View File

@@ -1,58 +1,39 @@
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 { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import {
ExtraSmall, Small, Medium, Large, ExtraLarge, ExtraExtraLarge,
} from '@edx/paragon';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { getLocale } from '@edx/frontend-platform/i18n';
import AuthLargeLayout from './AuthLargeLayout';
import AuthMediumLayout from './AuthMediumLayout';
import AuthSmallLayout from './AuthSmallLayout';
import LargeLayout from './LargeLayout';
import MediumLayout from './MediumLayout';
import SmallLayout from './SmallLayout';
import AuthExtraLargeLayout from './AuthExtraLargeLayout';
import AuthMediumLayout from './AuthMediumLayout';
import AuthSmallLayout from './AuthSmallLayout';
const BaseComponent = ({ children }) => {
const authenticatedUser = getAuthenticatedUser();
const BaseComponent = ({ children, showWelcomeBanner }) => {
const authenticatedUser = showWelcomeBanner ? getAuthenticatedUser() : null;
const username = authenticatedUser ? authenticatedUser.username : null;
return (
<>
<CookiePolicyBanner languageCode={getLocale()} />
<ExtraLarge>
<div className="col-md-12 extra-large-screen-top-stripe" />
</ExtraLarge>
<ExtraExtraLarge>
<div className="col-md-12 extra-large-screen-top-stripe" />
</ExtraExtraLarge>
<div className={classNames('layout', { authenticated: authenticatedUser })}>
<ExtraSmall>
<div className="col-md-12 small-screen-top-stripe" />
{authenticatedUser ? <AuthSmallLayout variant="xs" username={authenticatedUser.username} /> : <SmallLayout />}
</ExtraSmall>
<Small>
<div className="col-md-12 small-screen-top-stripe" />
{authenticatedUser ? <AuthSmallLayout username={authenticatedUser.username} /> : <SmallLayout />}
</Small>
<Medium>
<div className="w-100 medium-screen-top-stripe" />
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : <MediumLayout />}
</Medium>
<Large>
<div className="w-100 large-screen-top-stripe" />
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : <MediumLayout />}
</Large>
<ExtraLarge>
{authenticatedUser ? <AuthExtraLargeLayout username={authenticatedUser.username} /> : <LargeLayout />}
</ExtraLarge>
<ExtraExtraLarge>
{authenticatedUser ? <AuthExtraLargeLayout variant="xxl" username={authenticatedUser.username} /> : <LargeLayout />}
</ExtraExtraLarge>
{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}
@@ -62,8 +43,13 @@ const BaseComponent = ({ children }) => {
);
};
BaseComponent.defaultProps = {
showWelcomeBanner: false,
};
BaseComponent.propTypes = {
children: PropTypes.node.isRequired,
showWelcomeBanner: PropTypes.bool,
};
export default BaseComponent;

View File

@@ -1,31 +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 LargeScreenLeftLayout from './LargeLeftLayout';
import messages from './messages';
const LargeLayout = () => (
<div className="container row p-0 m-0 large-screen-container">
<div className="col-md-9 p-0 screen-header-primary">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image alt="edx" className="logo position-absolute" src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<LargeScreenLeftLayout />
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>
<div className="col-md-3 p-0 screen-polygon">
<svg
width="100%"
height="100%"
className="ml-n1 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

@@ -1,30 +0,0 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
const LargeLeftLayout = (props) => {
const { intl } = props;
return (
<div className="min-vh-100 pr-0 mt-lg-n2 d-flex align-items-center">
<svg className="large-screen-svg-line ml-5">
<line x1="50" y1="0" x2="10" y2="215" />
</svg>
<h1 className="large-heading">
{intl.formatMessage(messages['start.learning'])}
<span className="text-accent-a"><br />
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</h1>
</div>
);
};
LargeLeftLayout.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(LargeLeftLayout);

View File

@@ -1,45 +1,50 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
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 = (props) => {
const { intl } = props;
const MediumLayout = () => {
const { formatMessage } = useIntl();
return (
<div className="container row p-0 mb-3 medium-screen-container">
<div className="col-md-10 p-0 screen-header-primary">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="row mt-4 justify-content-center">
<svg className="medium-screen-svg-line pl-5">
<line x1="50" y1="0" x2="10" y2="215" />
<>
<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">{formatMessage(messages['start.learning'])}</span>
<span className="text-accent-a d-inline-block">
{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>
<h1 className="medium-heading pb-4">
{intl.formatMessage(messages['start.learning'])}
<span className="text-accent-a"><br />
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</h1>
</div>
</div>
<div className="col-md-2 p-0 screen-polygon">
<svg width="100%" height="100%" className="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: intlShape.isRequired,
};
export default injectIntl(MediumLayout);
export default MediumLayout;

View File

@@ -1,39 +1,39 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
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 = (props) => {
const { intl } = props;
const SmallLayout = () => {
const { formatMessage } = useIntl();
return (
<>
<div className="small-screen-header-primary">
<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 alt="edx" className="logo" src={getConfig().LOGO_WHITE_URL} />
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
</Hyperlink>
<div className="d-flex mt-3">
<svg className="small-screen-svg-line">
<line x1="55" y1="0" x2="40" y2="65" />
</svg>
<h1 className="small-heading pb-3">
{intl.formatMessage(messages['start.learning'])}
<br />
<span className="text-accent-a">
{intl.formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
<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">{formatMessage(messages['start.learning'])}</span>
<span className="text-accent-a d-inline-block">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
</span>
</h1>
</div>
</div>
</>
</span>
);
};
SmallLayout.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(SmallLayout);
export default SmallLayout;

View File

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

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import LargeLayout from '../LargeLayout';
import MediumLayout from '../MediumLayout';

View File

@@ -1,21 +1,24 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form, Button,
Button, Form,
} from '@edx/paragon';
import { faSignInAlt } from '@fortawesome/free-solid-svg-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';
/**
* 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();
@@ -33,7 +36,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}
@@ -62,12 +65,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>
@@ -98,7 +104,6 @@ EnterpriseSSO.propTypes = {
loginUrl: PropTypes.string,
registerUrl: PropTypes.string,
}),
intl: intlShape.isRequired,
};
export default injectIntl(EnterpriseSSO);
export default EnterpriseSSO;

View File

@@ -24,9 +24,12 @@ const FormGroup = (props) => {
<Form.Group controlId={props.name} className={props.className} isInvalid={props.errorMessage !== ''}>
<Form.Control
as={props.as}
readOnly={props.readOnly}
type={props.type}
aria-invalid={props.errorMessage !== ''}
className="form-field"
autoComplete={props.autoComplete}
spellCheck={props.spellCheck}
name={props.name}
value={props.value}
onFocus={handleFocus}
@@ -34,7 +37,6 @@ const FormGroup = (props) => {
onClick={handleClick}
onChange={props.handleChange}
controlClassName={props.borderClass}
trailingElement={props.trailingElement}
floatingLabel={props.floatingLabel}
>
@@ -62,39 +64,43 @@ const FormGroup = (props) => {
FormGroup.defaultProps = {
as: 'input',
errorMessage: '',
borderClass: '',
autoComplete: null,
handleBlur: null,
handleChange: () => {},
handleFocus: null,
handleClick: null,
helpText: [],
options: null,
trailingElement: null,
type: 'text',
borderClass: '',
children: null,
className: '',
errorMessage: '',
handleBlur: null,
handleChange: () => {},
handleClick: null,
handleFocus: null,
helpText: [],
options: null,
readOnly: false,
spellCheck: null,
trailingElement: null,
type: 'text',
};
FormGroup.propTypes = {
as: PropTypes.string,
errorMessage: PropTypes.string,
borderClass: PropTypes.string,
autoComplete: PropTypes.string,
borderClass: PropTypes.string,
children: PropTypes.element,
className: PropTypes.string,
errorMessage: PropTypes.string,
floatingLabel: PropTypes.string.isRequired,
handleBlur: PropTypes.func,
handleChange: PropTypes.func,
handleFocus: PropTypes.func,
handleClick: PropTypes.func,
handleFocus: PropTypes.func,
helpText: PropTypes.arrayOf(PropTypes.string),
name: PropTypes.string.isRequired,
options: PropTypes.func,
readOnly: PropTypes.bool,
spellCheck: PropTypes.string,
trailingElement: PropTypes.element,
type: PropTypes.string,
value: PropTypes.string.isRequired,
children: PropTypes.element,
className: PropTypes.string,
};
export default FormGroup;

View File

@@ -1,11 +1,16 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
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';
import messages from './messages';
/**
* This component renders the Institution login button
* */
export const RenderInstitutionButton = props => {
const { onSubmitHandler, buttonTitle } = props;
@@ -22,10 +27,13 @@ export const RenderInstitutionButton = props => {
);
};
/**
* This component renders the page list of available institutions for login
* */
const InstitutionLogistration = props => {
const lmsBaseUrl = getConfig().LMS_BASE_URL;
const { formatMessage } = useIntl();
const {
intl,
secondaryProviders,
headingTitle,
} = props;
@@ -38,7 +46,7 @@ const InstitutionLogistration = props => {
{headingTitle}
</h4>
<p className="mb-2">
{intl.formatMessage(messages['institution.login.page.sub.heading'])}
{formatMessage(messages['institution.login.page.sub.heading'])}
</p>
</div>
</div>
@@ -70,7 +78,7 @@ const LogistrationDefaultProps = {
};
const LogistrationProps = {
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequried,
name: PropTypes.string.isRequired,
loginUrl: PropTypes.string.isRequired,
})),
};
@@ -87,7 +95,6 @@ RenderInstitutionButton.defaultProps = {
InstitutionLogistration.propTypes = {
...LogistrationProps,
intl: intlShape.isRequired,
headingTitle: PropTypes.string,
};
InstitutionLogistration.defaultProps = {
@@ -95,4 +102,4 @@ InstitutionLogistration.defaultProps = {
headingTitle: '',
};
export default injectIntl(InstitutionLogistration);
export default InstitutionLogistration;

View File

@@ -1,28 +1,48 @@
import React, { useState } from 'react';
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 { useIntl } 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 { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Tabs,
Tab,
Icon,
} from '@edx/paragon';
import { ChevronLeft } from '@edx/paragon/icons';
import messages from './messages';
import { BaseComponent } from '../base-component';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import { updatePathWithQueryParams, getTpaHint } from '../data/utils';
import { getTpaHint, getTpaProvider, updatePathWithQueryParams } from '../data/utils';
import { LoginPage } from '../login';
import { RegistrationPage } from '../register';
import BaseComponent from '../base-component';
import { backupRegistrationForm } from '../register/data/actions';
import { clearThirdPartyAuthContextErrorMessage } from './data/actions';
import {
tpaProvidersSelector,
} from './data/selectors';
import messages from './messages';
const Logistration = (props) => {
const { intl, selectedPage } = props;
const tpa = getTpaHint();
const { selectedPage, tpaProviders } = props;
const tpaHint = getTpaHint();
const {
providers, secondaryProviders,
} = tpaProviders;
const { formatMessage } = useIntl();
const [institutionLogin, setInstitutionLogin] = useState(false);
const [key, setKey] = useState('');
const disablePublicAccountCreation = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false;
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' });
@@ -37,6 +57,10 @@ const Logistration = (props) => {
const handleOnSelect = (tabKey) => {
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
props.clearThirdPartyAuthContextErrorMessage();
if (tabKey === LOGIN_PAGE) {
props.backupRegistrationForm();
}
setKey(tabKey);
};
@@ -45,51 +69,100 @@ const Logistration = (props) => {
<Icon src={ChevronLeft} className="left-icon" />
<span className="ml-2">
{selectedPage === LOGIN_PAGE
? intl.formatMessage(messages['logistration.sign.in'])
: intl.formatMessage(messages['logistration.register'])}
? formatMessage(messages['logistration.sign.in'])
: formatMessage(messages['logistration.register'])}
</span>
</div>
);
const isValidTpaHint = () => {
const { provider } = getTpaProvider(tpaHint, providers, secondaryProviders);
return !!provider;
};
return (
<BaseComponent>
<div>
{institutionLogin
{disablePublicAccountCreation
? (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={selectedPage === LOGIN_PAGE ? LOGIN_PAGE : REGISTER_PAGE} />
</Tabs>
)
: (
<>
{!tpa && (
<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} />
<Redirect to={updatePathWithQueryParams(LOGIN_PAGE)} />
{institutionLogin && (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
</Tabs>
)}
<div id="main-content" className="main-content">
{!institutionLogin && (
<h3 className="mb-4.5">{formatMessage(messages['logistration.sign.in'])}</h3>
)}
<LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
</div>
</>
)
: (
<div>
{institutionLogin
? (
<Tabs defaultActiveKey="" id="controlled-tab" onSelect={handleInstitutionLogin}>
<Tab title={tabTitle} eventKey={selectedPage === LOGIN_PAGE ? LOGIN_PAGE : REGISTER_PAGE} />
</Tabs>
)
: (!isValidTpaHint() && (
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={handleOnSelect}>
<Tab title={formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
<Tab title={formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
</Tabs>
))}
{ key && (
<Redirect to={updatePathWithQueryParams(key)} />
)}
<div id="main-content" className="main-content">
{selectedPage === LOGIN_PAGE
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
: (
<RegistrationPage
institutionLogin={institutionLogin}
handleInstitutionLogin={handleInstitutionLogin}
/>
)}
</div>
</div>
)}
{ 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: intlShape.isRequired,
selectedPage: PropTypes.string,
backupRegistrationForm: PropTypes.func.isRequired,
clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired,
tpaProviders: PropTypes.shape({
providers: PropTypes.arrayOf(PropTypes.shape({})),
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
}),
};
Logistration.defaultProps = {
tpaProviders: {
providers: [],
secondaryProviders: [],
},
};
Logistration.defaultProps = {
selectedPage: REGISTER_PAGE,
};
export default injectIntl(Logistration);
const mapStateToProps = state => ({
tpaProviders: tpaProvidersSelector(state),
});
export default connect(
mapStateToProps,
{
backupRegistrationForm,
clearThirdPartyAuthContextErrorMessage,
},
)(Logistration);

View File

@@ -1,16 +1,17 @@
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,19 +1,19 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form, IconButton, useToggle, Tooltip, OverlayTrigger, Icon,
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
} from '@edx/paragon';
import {
Check, Remove, Visibility, VisibilityOff,
} from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import messages from './messages';
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
import messages from './messages';
const PasswordField = (props) => {
const { formatMessage } = props.intl;
const { formatMessage } = useIntl();
const [isPasswordHidden, setHiddenTrue, setHiddenFalse] = useToggle(true);
const [showTooltip, setShowTooltip] = useState(false);
@@ -30,25 +30,25 @@ const PasswordField = (props) => {
};
const HideButton = (
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="passwordValidation" src={VisibilityOff} iconAs={Icon} onClick={setHiddenTrue} size="sm" variant="secondary" alt={formatMessage(messages['hide.password'])} />
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" src={VisibilityOff} iconAs={Icon} onClick={setHiddenTrue} size="sm" variant="secondary" alt={formatMessage(messages['hide.password'])} />
);
const ShowButton = (
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="passwordValidation" src={Visibility} iconAs={Icon} onClick={setHiddenFalse} size="sm" variant="secondary" alt={formatMessage(messages['show.password'])} />
<IconButton onFocus={handleFocus} onBlur={handleBlur} name="password" 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}`}>
<span id="letter-check" className="d-flex position-relative align-content-start">
{LETTER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1" src={Remove} />}
<span id="letter-check" className="d-flex align-items-center">
{LETTER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1 text-light-700" src={Remove} />}
{formatMessage(messages['one.letter'])}
</span>
<span id="number-check" className="d-flex position-relative align-content-start">
{NUMBER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1" src={Remove} />}
<span id="number-check" className="d-flex align-items-center">
{NUMBER_REGEX.test(props.value) ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1 text-light-700" src={Remove} />}
{formatMessage(messages['one.number'])}
</span>
<span id="characters-check" className="d-flex position-relative align-content-start">
{props.value.length >= 8 ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1" src={Remove} />}
<span id="characters-check" className="d-flex align-items-center">
{props.value.length >= 8 ? <Icon className="text-success mr-1" src={Check} /> : <Icon className="mr-1 text-light-700" src={Remove} />}
{formatMessage(messages['eight.characters'])}
</span>
</Tooltip>
@@ -63,6 +63,8 @@ const PasswordField = (props) => {
type={isPasswordHidden ? 'password' : 'text'}
name={props.name}
value={props.value}
autoComplete={props.autoComplete}
aria-invalid={props.errorMessage !== ''}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={props.handleChange}
@@ -88,6 +90,7 @@ PasswordField.defaultProps = {
handleFocus: null,
handleChange: () => {},
showRequirements: true,
autoComplete: null,
};
PasswordField.propTypes = {
@@ -97,10 +100,10 @@ PasswordField.propTypes = {
handleBlur: PropTypes.func,
handleFocus: PropTypes.func,
handleChange: PropTypes.func,
intl: intlShape.isRequired,
name: PropTypes.string.isRequired,
showRequirements: PropTypes.bool,
value: PropTypes.string.isRequired,
autoComplete: PropTypes.string,
};
export default injectIntl(PasswordField);
export default PasswordField;

View File

@@ -1,16 +1,22 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { WELCOME_PAGE } from '../data/constants';
import { AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS } from '../data/constants';
import { setCookie } from '../data/utils';
function RedirectLogistration(props) {
const RedirectLogistration = (props) => {
const {
finishAuthUrl, redirectUrl, redirectToWelcomePage, success,
finishAuthUrl,
redirectUrl,
redirectToProgressiveProfilingPage,
success,
optionalFields,
redirectToRecommendationsPage,
educationLevel,
userId,
} = props;
let finalRedirectUrl = '';
@@ -25,31 +31,65 @@ function RedirectLogistration(props) {
finalRedirectUrl = redirectUrl;
}
if (redirectToWelcomePage) {
// Redirect to Progressive Profiling after successful registration
if (redirectToProgressiveProfilingPage) {
// TODO: Do we still need this cookie?
setCookie('van-504-returning-user', true);
// use this component to redirect WelcomePage after successful registration
// return <Redirect to={WELCOME_PAGE} />;
const registrationResult = { redirectUrl: finalRedirectUrl, success };
return <Redirect to={{ pathname: WELCOME_PAGE, state: { registrationResult } }} />;
return (
<Redirect to={{
pathname: AUTHN_PROGRESSIVE_PROFILING,
state: {
registrationResult,
optionalFields,
},
}}
/>
);
}
// Redirect to Recommendation page
if (redirectToRecommendationsPage) {
const registrationResult = { redirectUrl: finalRedirectUrl, success };
return (
<Redirect to={{
pathname: RECOMMENDATIONS,
state: {
registrationResult,
educationLevel,
userId,
},
}}
/>
);
}
window.location.href = finalRedirectUrl;
}
return <></>;
}
return null;
};
RedirectLogistration.defaultProps = {
educationLevel: null,
finishAuthUrl: null,
success: false,
redirectUrl: '',
redirectToWelcomePage: false,
redirectToProgressiveProfilingPage: false,
optionalFields: {},
redirectToRecommendationsPage: false,
userId: null,
};
RedirectLogistration.propTypes = {
educationLevel: PropTypes.string,
finishAuthUrl: PropTypes.string,
success: PropTypes.bool,
redirectUrl: PropTypes.string,
redirectToWelcomePage: PropTypes.bool,
redirectToProgressiveProfilingPage: PropTypes.bool,
optionalFields: PropTypes.shape({}),
redirectToRecommendationsPage: PropTypes.bool,
userId: PropTypes.number,
};
export default RedirectLogistration;

View File

@@ -1,16 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useIntl } from '@edx/frontend-platform/i18n';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
function SocialAuthProviders(props) {
const { intl, referrer, socialAuthProviders } = props;
const SocialAuthProviders = (props) => {
const { formatMessage } = useIntl();
const { referrer, socialAuthProviders } = props;
function handleSubmit(e) {
e.preventDefault();
@@ -34,25 +35,24 @@ function SocialAuthProviders(props) {
</div>
)
: (
<>
<div className="font-container" aria-hidden="true">
<FontAwesomeIcon
icon={SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? ['fab', provider.iconClass] : faSignInAlt}
/>
</div>
</>
<div className="font-container" aria-hidden="true">
<FontAwesomeIcon
icon={SUPPORTED_ICON_CLASSES.includes(provider.iconClass) ? ['fab', provider.iconClass] : faSignInAlt}
/>
</div>
)}
<span id="provider-name" className="mr-auto pl-2" aria-hidden="true">{provider.name}</span>
<span id="provider-name" className="notranslate mr-auto pl-2" aria-hidden="true">{provider.name}</span>
<span className="sr-only">
{referrer === LOGIN_PAGE
? intl.formatMessage(messages['sso.sign.in.with'], { providerName: provider.name })
: 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 +60,6 @@ SocialAuthProviders.defaultProps = {
};
SocialAuthProviders.propTypes = {
intl: intlShape.isRequired,
referrer: PropTypes.string,
socialAuthProviders: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
@@ -69,7 +68,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,61 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TransitionReplace } from '@edx/paragon';
const onChildExit = (htmlNode) => {
// If the leaving child has focus, take control and redirect it
if (htmlNode.contains(document.activeElement)) {
// Get the newly entering sibling.
// It's the previousSibling, but not for any explicit reason. So checking for both.
const enteringChild = htmlNode.previousSibling || htmlNode.nextSibling;
// There's no replacement, do nothing.
if (!enteringChild) return; // eslint-disable-line curly
// Get all the focusable elements in the entering child and focus the first one
const focusableElements = enteringChild.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusableElements.length) {
focusableElements[0].focus();
}
}
};
function SwitchContent({ expression, cases, className }) {
const getContent = (caseKey) => {
if (cases[caseKey]) {
if (typeof cases[caseKey] === 'string') {
return getContent(cases[caseKey]);
}
return React.cloneElement(cases[caseKey], { key: caseKey });
} else if (cases.default) { // eslint-disable-line no-else-return
if (typeof cases.default === 'string') {
return getContent(cases.default);
}
React.cloneElement(cases.default, { key: 'default' });
}
return null;
};
return (
<TransitionReplace
className={className}
onChildExit={onChildExit}
>
{getContent(expression)}
</TransitionReplace>
);
}
SwitchContent.propTypes = {
expression: PropTypes.string,
cases: PropTypes.objectOf(PropTypes.node).isRequired,
className: PropTypes.string,
};
SwitchContent.defaultProps = {
expression: null,
className: null,
};
export default SwitchContent;

View File

@@ -1,42 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import PropTypes from 'prop-types';
import messages from './messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import messages from './messages';
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) {
return null;
}
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>{formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
) : null}
<p>{ message }</p>
</Alert>
{referrer === REGISTER_PAGE ? (
<Alert.Heading>{intl.formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
<h4 className="mt-4 mb-4">{formatMessage(messages['registration.using.tpa.form.heading'])}</h4>
) : null}
<p>{ message }</p>
</Alert>
</>
);
};
ThirdPartyAuthAlert.defaultProps = {
currentProvider: '',
referrer: LOGIN_PAGE,
};
ThirdPartyAuthAlert.propTypes = {
currentProvider: PropTypes.string.isRequired,
intl: intlShape.isRequired,
currentProvider: PropTypes.string,
referrer: PropTypes.string,
};
export default injectIntl(ThirdPartyAuthAlert);
export default ThirdPartyAuthAlert;

View File

@@ -1,7 +1,9 @@
import React, { useEffect, useState } from 'react';
import { Route } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { Route } from 'react-router-dom';
import { DEFAULT_REDIRECT_URL } from '../data/constants';
/**
@@ -28,7 +30,7 @@ const UnAuthOnlyRoute = (props) => {
return <Route {...props} />;
}
return <></>;
return null;
};
export default UnAuthOnlyRoute;

View File

@@ -0,0 +1,53 @@
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';
const ZendeskHelp = () => {
const { formatMessage } = useIntl();
const setting = {
cookies: true,
webWidget: {
contactOptions: {
enabled: false,
},
chat: {
suppress: false,
},
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) },
},
},
},
};
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) => ({
@@ -12,11 +13,15 @@ export const getThirdPartyAuthContextBegin = () => ({
type: THIRD_PARTY_AUTH_CONTEXT.BEGIN,
});
export const getThirdPartyAuthContextSuccess = (thirdPartyAuthContext) => ({
export const getThirdPartyAuthContextSuccess = (fieldDescriptions, optionalFields, thirdPartyAuthContext) => ({
type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS,
payload: { thirdPartyAuthContext },
payload: { fieldDescriptions, optionalFields, thirdPartyAuthContext },
});
export const getThirdPartyAuthContextFailure = () => ({
type: THIRD_PARTY_AUTH_CONTEXT.FAILURE,
});
export const clearThirdPartyAuthContextErrorMessage = () => ({
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
});

View File

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

View File

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

View File

@@ -8,3 +8,21 @@ export const thirdPartyAuthContextSelector = createSelector(
commonComponentsSelector,
commonComponents => commonComponents.thirdPartyAuthContext,
);
export const fieldDescriptionSelector = createSelector(
commonComponentsSelector,
commonComponents => commonComponents.fieldDescriptions,
);
export const optionalFieldsSelector = createSelector(
commonComponentsSelector,
commonComponents => commonComponents.optionalFields,
);
export const tpaProvidersSelector = createSelector(
commonComponentsSelector,
commonComponents => ({
providers: commonComponents.thirdPartyAuthContext.providers,
secondaryProviders: commonComponents.thirdPartyAuthContext.secondaryProviders,
}),
);

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,6 +18,8 @@ export async function getThirdPartyAuthContext(urlParams) {
throw (e);
});
return {
thirdPartyAuthContext: camelCaseObject(convertKeyNames(data, { fullname: 'name' })),
fieldDescriptions: data.registrationFields || data.registration_fields,
optionalFields: data.optionalFields || data.optional_fields,
thirdPartyAuthContext: data.contextData || data.context_data,
};
}

View File

@@ -0,0 +1,82 @@
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', () => {
it('test mfe context response', () => {
const state = {
fieldDescriptions: {},
optionalFields: {},
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
countryCode: null,
providers: [],
secondaryProviders: [],
pipelineUserDetails: null,
errorMessage: null,
},
};
const fieldDescriptions = {
fields: [],
};
const optionalFields = {
fields: [],
extended_profile: {},
};
const thirdPartyAuthContext = { ...state.thirdPartyAuthContext };
const action = {
type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS,
payload: { fieldDescriptions, optionalFields, thirdPartyAuthContext },
};
expect(
reducer(state, action),
).toEqual(
{
...state,
fieldDescriptions: [],
optionalFields: {
fields: [],
extended_profile: {},
},
thirdPartyAuthApiStatus: 'complete',
},
);
});
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 occured',
},
};
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,9 +1,10 @@
import { runSaga } from 'redux-saga';
import { setCountryFromThirdPartyAuthContext } from '../../../register/data/actions';
import initializeMockLogging from '../../../setupTest';
import * as actions from '../actions';
import { fetchThirdPartyAuthContext } from '../sagas';
import * as api from '../service';
import initializeMockLogging from '../../../setupTest';
const { loggingService } = initializeMockLogging();
@@ -26,7 +27,11 @@ describe('fetchThirdPartyAuthContext', () => {
it('should call service and dispatch success action', async () => {
const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext')
.mockImplementation(() => Promise.resolve({ thirdPartyAuthContext: data }));
.mockImplementation(() => Promise.resolve({
thirdPartyAuthContext: data,
fieldDescriptions: {},
optionalFields: {},
}));
const dispatched = [];
await runSaga(
@@ -38,7 +43,8 @@ describe('fetchThirdPartyAuthContext', () => {
expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1);
expect(dispatched).toEqual([
actions.getThirdPartyAuthContextBegin(),
actions.getThirdPartyAuthContextSuccess(data),
setCountryFromThirdPartyAuthContext(),
actions.getThirdPartyAuthContextSuccess({}, {}, data),
]);
getThirdPartyAuthContext.mockClear();
});

View File

@@ -12,3 +12,4 @@ 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

@@ -1,29 +1,13 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
// institution login strings
'institution.login.page.sub.heading': {
id: 'institution.login.page.sub.heading',
defaultMessage: 'Choose your institution from the list below',
description: 'Heading of the institutions list',
},
// Confirmation Alert Message
'forgot.password.confirmation.title': {
id: 'forgot.password.confirmation.title',
defaultMessage: 'Check your email',
description: 'Forgot password confirmation message title',
},
'forgot.password.confirmation.support.link': {
id: 'forgot.password.confirmation.support.link',
defaultMessage: 'contact technical support',
description: 'Technical support link text',
},
'forgot.password.confirmation.info': {
id: 'forgot.password.confirmation.info',
defaultMessage: 'If you do not receive a password reset message after 1 minute, verify that you entered the correct '
+ 'email address, or check your spam folder.',
description: 'Part of message that appears after user requests password change',
},
// Logistration strinsg
// logistration strings
'logistration.sign.in': {
id: 'logistration.sign.in',
defaultMessage: 'Sign in',
@@ -34,32 +18,22 @@ const messages = defineMessages({
defaultMessage: 'Register',
description: 'Text that appears on the tab to switch between login and register',
},
'internal.server.error.message': {
id: 'internal.server.error.message',
defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.',
description: 'Error message that appears when server responds with 500 error code',
},
'server.ratelimit.error.message': {
id: 'server.ratelimit.error.message',
defaultMessage: 'An error has occurred because of too many requests. Please try again after some time.',
description: 'Error message that appears when server responds with 429 error code',
},
// enterprise sso strings
'enterprisetpa.title.heading': {
id: 'enterprisetpa.title.heading',
defaultMessage: 'Would you like to sign in using your {providerName} credentials?',
description: 'Header text used in enterprise third party authentication',
},
'enterprisetpa.sso.button.title': {
id: 'enterprisetpa.sso.button.title',
defaultMessage: 'Sign in using {providerName}',
description: 'Text for third party auth provider buttons',
},
'enterprisetpa.login.button.text': {
id: 'enterprisetpa.login.button.text',
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',
@@ -84,17 +58,17 @@ const messages = defineMessages({
},
'one.letter': {
id: 'one.letter',
defaultMessage: '1 Letter',
defaultMessage: '1 letter',
description: 'password requirement to have 1 letter',
},
'one.number': {
id: 'one.number',
defaultMessage: '1 Number',
defaultMessage: '1 number',
description: 'password requirement to have 1 number',
},
'eight.characters': {
id: 'eight.characters',
defaultMessage: '8 Characters',
defaultMessage: '8 characters',
description: 'password requirement to have a minimum of 8 characters',
},
'password.sr.only.helping.text': {
@@ -123,6 +97,21 @@ const messages = defineMessages({
description: 'Message that appears on register page if user has successfully authenticated with TPA '
+ 'but no associated platform account exists',
},
'registration.using.tpa.form.heading': {
id: 'registration.using.tpa.form.heading',
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

@@ -1,8 +1,8 @@
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import FormGroup from '../FormGroup';
import PasswordField from '../PasswordField';
@@ -74,9 +74,9 @@ describe('PasswordField', () => {
});
passwordField.update();
expect(passwordField.find('#letter-check span').prop('className')).toEqual('pgn__icon mr-1');
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon mr-1');
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon mr-1');
expect(passwordField.find('#letter-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
expect(passwordField.find('#number-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
expect(passwordField.find('#characters-check span').prop('className')).toEqual('pgn__icon mr-1 text-light-700');
});
it('should update password requirement checks', async () => {

View File

@@ -1,17 +1,19 @@
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import * as auth from '@edx/frontend-platform/auth';
import Logistration from '../Logistration';
import { RenderInstitutionButton } from '../InstitutionLogistration';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { COMPLETE_STATE, LOGIN_PAGE } from '../../data/constants';
import { backupRegistrationForm } from '../../register/data/actions';
import { clearThirdPartyAuthContextErrorMessage } from '../data/actions';
import { RenderInstitutionButton } from '../InstitutionLogistration';
import Logistration from '../Logistration';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/auth');
@@ -39,9 +41,7 @@ describe('Logistration', () => {
);
beforeEach(() => {
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'edX' }));
});
it('should render registration page', () => {
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
configure({
loggingService: { logError: jest.fn() },
config: {
@@ -50,14 +50,22 @@ describe('Logistration', () => {
},
messages: { 'es-419': {}, de: {}, 'en-us': {} },
});
});
it('should render registration page', () => {
mergeConfig({
ALLOW_PUBLIC_ACCOUNT_CREATION: true,
});
store = mockStore({
register: {
registrationResult: { success: false, redirectUrl: '' },
registrationError: {},
},
commonComponents: {
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext: {
providers: [],
secondaryProviders: [],
},
},
});
const logistration = mount(reduxWrapper(<IntlLogistration />));
@@ -71,7 +79,10 @@ describe('Logistration', () => {
loginResult: { success: false, redirectUrl: '' },
},
commonComponents: {
thirdPartyAuthApiStatus: null,
thirdPartyAuthContext: {
providers: [],
secondaryProviders: [],
},
},
});
@@ -81,9 +92,42 @@ describe('Logistration', () => {
expect(logistration.find('#main-content').find('LoginPage').exists()).toBeTruthy();
});
it('should render only login page when public account creation is disabled', () => {
mergeConfig({
ALLOW_PUBLIC_ACCOUNT_CREATION: false,
DISABLE_ENTERPRISE_LOGIN: 'true',
});
store = mockStore({
login: {
loginResult: { success: false, redirectUrl: '' },
},
commonComponents: {
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
providers: [],
secondaryProviders: [secondaryProviders],
},
thirdPartyAuthApiStatus: COMPLETE_STATE,
},
});
const props = { selectedPage: LOGIN_PAGE };
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
// verifying sign in heading for institution login false
expect(logistration.find('#main-content').find('h3').text()).toEqual('Sign in');
// verifying tabs heading for institution login true
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
expect(logistration.find('#controlled-tab').exists()).toBeTruthy();
});
it('should display institution login option when secondary providers are present', () => {
mergeConfig({
DISABLE_ENTERPRISE_LOGIN: 'true',
ALLOW_PUBLIC_ACCOUNT_CREATION: 'true',
});
store = mockStore({
@@ -178,4 +222,50 @@ describe('Logistration', () => {
DISABLE_ENTERPRISE_LOGIN: '',
});
});
it('should fire action to backup registration form on tab click', () => {
store = mockStore({
login: {
loginResult: { success: false, redirectUrl: '' },
},
register: {
registrationResult: { success: false, redirectUrl: '' },
registrationError: {},
},
commonComponents: {
thirdPartyAuthContext: {
providers: [],
secondaryProviders: [],
},
},
});
store.dispatch = jest.fn(store.dispatch);
const logistration = mount(reduxWrapper(<IntlLogistration />));
logistration.find('a[data-rb-event-key="/login"]').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationForm());
});
it('should clear tpa context errorMessage tab click', () => {
store = mockStore({
login: {
loginResult: { success: false, redirectUrl: '' },
},
register: {
registrationResult: { success: false, redirectUrl: '' },
registrationError: {},
},
commonComponents: {
thirdPartyAuthContext: {
providers: [],
secondaryProviders: [],
},
},
});
store.dispatch = jest.fn(store.dispatch);
const logistration = mount(reduxWrapper(<IntlLogistration />));
logistration.find('a[data-rb-event-key="/login"]').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage());
});
});

View File

@@ -1,9 +1,10 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import SocialAuthProviders from '../SocialAuthProviders';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import renderer from 'react-test-renderer';
import registerIcons from '../RegisterFaIcons';
import SocialAuthProviders from '../SocialAuthProviders';
registerIcons();

View File

@@ -1,9 +1,10 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import ThirdPartyAuthAlert from '../ThirdPartyAuthAlert';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import renderer from 'react-test-renderer';
import { REGISTER_PAGE } from '../../data/constants';
import ThirdPartyAuthAlert from '../ThirdPartyAuthAlert';
describe('ThirdPartyAuthAlert', () => {
let props = {};

View File

@@ -1,12 +1,15 @@
/* eslint-disable import/no-import-module-exports */
/* eslint-disable react/function-component-definition */
import React from 'react';
import { mount } from 'enzyme';
import { BrowserRouter as Router, MemoryRouter, Switch } from 'react-router-dom';
import * as auth from '@edx/frontend-platform/auth';
import { mount } from 'enzyme';
import { UnAuthOnlyRoute } from '..';
import { LOGIN_PAGE } from '../../data/constants';
import { MemoryRouter, BrowserRouter as Router, Switch } from 'react-router-dom';
jest.mock('@edx/frontend-platform/auth');
const RRD = require('react-router-dom');

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

@@ -14,8 +14,8 @@ exports[`SocialAuthProviders should match social auth provider with default icon
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-sign-in-alt fa-w-16 "
data-icon="sign-in-alt"
className="svg-inline--fa fa-right-to-bracket "
data-icon="right-to-bracket"
data-prefix="fas"
focusable="false"
role="img"
@@ -24,7 +24,7 @@ exports[`SocialAuthProviders should match social auth provider with default icon
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M416 448h-84c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h84c17.7 0 32-14.3 32-32V160c0-17.7-14.3-32-32-32h-84c-6.6 0-12-5.4-12-12V76c0-6.6 5.4-12 12-12h84c53 0 96 43 96 96v192c0 53-43 96-96 96zm-47-201L201 79c-15-15-41-4.5-41 17v96H24c-13.3 0-24 10.7-24 24v96c0 13.3 10.7 24 24 24h136v96c0 21.5 26 32 41 17l168-168c9.3-9.4 9.3-24.6 0-34z"
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 {}}
/>
@@ -32,7 +32,7 @@ exports[`SocialAuthProviders should match social auth provider with default icon
</div>
<span
aria-hidden="true"
className="mr-auto pl-2"
className="notranslate mr-auto pl-2"
id="provider-name"
>
Apple
@@ -59,7 +59,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-google fa-w-16 "
className="svg-inline--fa fa-google "
data-icon="google"
data-prefix="fab"
focusable="false"
@@ -77,7 +77,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
</div>
<span
aria-hidden="true"
className="mr-auto pl-2"
className="notranslate mr-auto pl-2"
id="provider-name"
>
Apple
@@ -110,7 +110,7 @@ Array [
</div>
<span
aria-hidden="true"
className="mr-auto pl-2"
className="notranslate mr-auto pl-2"
id="provider-name"
>
Apple
@@ -139,7 +139,7 @@ Array [
</div>
<span
aria-hidden="true"
className="mr-auto pl-2"
className="notranslate mr-auto pl-2"
id="provider-name"
>
Facebook

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"
>
@@ -13,7 +13,7 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
className="alert-message-content"
>
<p>
You have successfully signed into Google, but your Google account does not have a linked edX account. To link your accounts, sign in now using your edX password.
You have successfully signed into Google, but your Google account does not have a linked Your Platform Name Here account. To link your accounts, sign in now using your Your Platform Name Here password.
</p>
</div>
</div>
@@ -21,26 +21,33 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
`;
exports[`ThirdPartyAuthAlert should match register page third party auth alert message snapshot 1`] = `
<div
className="fade alert-content alert-success mt-n2 alert show"
id="tpa-alert"
role="alert"
>
Array [
<div
className="pgn__alert-message-wrapper"
className="fade alert-content alert-success mt-n2 mb-5 alert show"
id="tpa-alert"
role="alert"
>
<div
className="alert-message-content"
className="pgn__alert-message-wrapper"
>
<div
className="alert-heading h4"
className="alert-message-content"
>
Almost done!
<div
className="alert-heading h4"
>
Almost done!
</div>
<p>
You've successfully signed into Google! We just need a little more information before you start learning with Your Platform Name Here.
</p>
</div>
<p>
You've successfully signed into Google! We just need a little more information before you start learning with edX.
</p>
</div>
</div>
</div>
</div>,
<h4
className="mt-4 mb-4"
>
Finish creating your account
</h4>,
]
`;

View File

@@ -0,0 +1,54 @@
// 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 {
"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,
},
}
}
/>
`;

29
src/config/index.js Normal file
View File

@@ -0,0 +1,29 @@
const configuration = {
// Cookies related configs
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null,
// Features
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
ENABLE_COOKIE_POLICY_BANNER: process.env.ENABLE_COOKIE_POLICY_BANNER || false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
ENABLE_PERSONALIZED_RECOMMENDATIONS: process.env.ENABLE_PERSONALIZED_RECOMMENDATIONS || false,
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
// Links
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK || null,
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
TOS_LINK: process.env.TOS_LINK || null,
// Miscellaneous
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
INFO_EMAIL: process.env.INFO_EMAIL || '',
ZENDESK_KEY: process.env.ZENDESK_KEY,
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
};
export default configuration;

View File

@@ -1,9 +1,9 @@
import { getConfig } from '@edx/frontend-platform';
import { applyMiddleware, createStore, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import { composeWithDevTools } from '@redux-devtools/extension';
import { applyMiddleware, compose, createStore } from 'redux';
import { createLogger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import thunkMiddleware from 'redux-thunk';
import createRootReducer from './reducers';
import rootSaga from './sagas';

View File

@@ -2,8 +2,9 @@
export const LOGIN_PAGE = '/login';
export const REGISTER_PAGE = '/register';
export const RESET_PAGE = '/reset';
export const WELCOME_PAGE = '/welcome';
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
export const DEFAULT_REDIRECT_URL = '/dashboard';
export const RECOMMENDATIONS = '/recommendations';
export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/';
export const PAGE_NOT_FOUND = '/notfound';
export const ENTERPRISE_LOGIN_URL = '/enterprise/login';
@@ -12,13 +13,16 @@ export const ENTERPRISE_LOGIN_URL = '/enterprise/login';
export const SUPPORTED_ICON_CLASSES = ['apple', 'facebook', 'google', 'microsoft'];
// Error Codes
export const FORM_SUBMISSION_ERROR = 'form-submission-error';
export const INTERNAL_SERVER_ERROR = 'internal-server-error';
export const API_RATELIMIT_ERROR = 'api-ratelimit-error';
// States
// Common States
export const DEFAULT_STATE = 'default';
export const PENDING_STATE = 'pending';
export const COMPLETE_STATE = 'complete';
export const FAILURE_STATE = 'failure';
export const FORBIDDEN_STATE = 'forbidden';
// Regex
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
@@ -27,7 +31,8 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
export const LETTER_REGEX = /[a-zA-Z]/;
export const NUMBER_REGEX = /\d/;
export const 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'];
export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'save_for_later', 'register_for_free', 'track', 'is_account_recovery'];

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

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

View File

@@ -1,13 +1,5 @@
import { combineReducers } from 'redux';
import {
reducer as loginReducer,
storeName as loginStoreName,
} from '../login';
import {
reducer as registerReducer,
storeName as registerStoreName,
} from '../register';
import {
reducer as commonComponentsReducer,
storeName as commonComponentsStoreName,
@@ -16,22 +8,29 @@ import {
reducer as forgotPasswordReducer,
storeName as forgotPasswordStoreName,
} from '../forgot-password';
import {
reducer as loginReducer,
storeName as loginStoreName,
} from '../login';
import {
reducer as authnProgressiveProfilingReducers,
storeName as authnProgressiveProfilingStoreName,
} from '../progressive-profiling';
import {
reducer as registerReducer,
storeName as registerStoreName,
} from '../register';
import {
reducer as resetPasswordReducer,
storeName as resetPasswordStoreName,
} from '../reset-password';
import {
reducer as welcomePageReducers,
storeName as welcomePageStoreName,
} from '../welcome';
const createRootReducer = () => combineReducers({
[loginStoreName]: loginReducer,
[registerStoreName]: registerReducer,
[commonComponentsStoreName]: commonComponentsReducer,
[forgotPasswordStoreName]: forgotPasswordReducer,
[resetPasswordStoreName]: resetPasswordReducer,
[welcomePageStoreName]: welcomePageReducers,
[authnProgressiveProfilingStoreName]: authnProgressiveProfilingReducers,
});
export default createRootReducer;

View File

@@ -1,11 +1,11 @@
import { all } from 'redux-saga/effects';
import { saga as registrationSaga } from '../register';
import { saga as loginSaga } from '../login';
import { saga as commonComponentsSaga } from '../common-components';
import { saga as forgotPasswordSaga } from '../forgot-password';
import { saga as loginSaga } from '../login';
import { saga as authnProgressiveProfilingSaga } from '../progressive-profiling';
import { saga as registrationSaga } from '../register';
import { saga as resetPasswordSaga } from '../reset-password';
import { saga as welcomePageSaga } from '../welcome';
export default function* rootSaga() {
yield all([
@@ -14,6 +14,6 @@ export default function* rootSaga() {
commonComponentsSaga(),
forgotPasswordSaga(),
resetPasswordSaga(),
welcomePageSaga(),
authnProgressiveProfilingSaga(),
]);
}

View File

@@ -1,9 +1,9 @@
import Cookies from 'universal-cookie';
import { getConfig } from '@edx/frontend-platform';
import Cookies from 'universal-cookie';
export function setCookie(cookieName, cookieValue, cookieExpiry) {
const cookies = new Cookies();
const options = { domain: getConfig().COOKIE_DOMAIN, path: '/' };
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
if (cookieExpiry) {
options.expires = cookieExpiry;
}

View File

@@ -1,15 +1,7 @@
// Utility functions
import * as QueryString from 'query-string';
import { AUTH_PARAMS } from '../constants';
export default function processLink(link) {
let matches;
link.replace(/(.*?)<a href=["']([^"']*).*?>([^<]+)<\/a>(.*)/g, function () { // eslint-disable-line func-names
matches = Array.prototype.slice.call(arguments, 1, 5); // eslint-disable-line prefer-rest-params
});
return matches;
}
import { AUTH_PARAMS } from '../constants';
export const getTpaProvider = (tpaHintProvider, primaryProviders, secondaryProviders) => {
let tpaProvider = null;
@@ -57,8 +49,8 @@ export const updatePathWithQueryParams = (path) => {
return `${path}${queryParams}`;
};
export const getAllPossibleQueryParam = () => {
const urlParams = QueryString.parse(window.location.search);
export const getAllPossibleQueryParams = (locationURl = null) => {
const urlParams = locationURl ? QueryString.parseUrl(locationURl).query : QueryString.parse(window.location.search);
const params = {};
Object.entries(urlParams).forEach(([key, value]) => {
if (AUTH_PARAMS.indexOf(key) > -1) {

View File

@@ -1,18 +1,5 @@
import { LOGIN_PAGE } from '../constants';
import processLink, { updatePathWithQueryParams } from './dataUtils';
describe('processLink', () => {
it('should use the provided processLink function to', () => {
const expectedHref = 'http://test.server.com/';
const expectedText = 'test link';
const link = `<a href="${expectedHref}">${expectedText}</a>`;
const matches = processLink(link);
expect(matches[1]).toEqual(expectedHref);
expect(matches[2]).toEqual(expectedText);
});
});
import { updatePathWithQueryParams } from './dataUtils';
describe('updatePathWithQueryParams', () => {
it('should append query params into the path', () => {

View File

@@ -1,9 +1,8 @@
export {
default,
getTpaProvider,
getTpaHint,
updatePathWithQueryParams,
getAllPossibleQueryParam,
getAllPossibleQueryParams,
getActivationStatus,
windowScrollTo,
} from './dataUtils';

View File

@@ -0,0 +1,24 @@
import { useEffect, useState } from 'react';
import { breakpoints } from '@edx/paragon';
/**
* A react hook used to determine if the current window is mobile or not.
* returns true if the window is of mobile size.
* Code source: https://github.com/edx/prospectus/blob/master/src/utils/useMobileResponsive.js
*/
const useMobileResponsive = (breakpoint) => {
const [isMobileWindow, setIsMobileWindow] = useState();
const checkForMobile = () => {
setIsMobileWindow(window.matchMedia(`(max-width: ${breakpoint || breakpoints.small.maxWidth}px)`).matches);
};
useEffect(() => {
checkForMobile();
window.addEventListener('resize', checkForMobile);
// return this function here to clean up the event listener
return () => window.removeEventListener('resize', checkForMobile);
});
return isMobileWindow;
};
export default useMobileResponsive;

View File

@@ -0,0 +1,158 @@
import React from 'react';
import { Form, Icon } from '@edx/paragon';
import { ExpandMore } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
const FormFieldRenderer = (props) => {
let formField = null;
const {
className, errorMessage, fieldData, onChangeHandler, isRequired, value,
} = props;
const handleFocus = (e) => {
if (props.handleFocus) { props.handleFocus(e); }
};
const handleOnBlur = (e) => {
if (props.handleBlur) { props.handleBlur(e); }
};
switch (fieldData.type) {
case 'select': {
if (!fieldData.options) {
return null;
}
formField = (
<Form.Group controlId={fieldData.name} isInvalid={!!(isRequired && errorMessage)}>
<Form.Control
className={className}
as="select"
name={fieldData.name}
value={value}
aria-invalid={isRequired && Boolean(errorMessage)}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore} />}
floatingLabel={fieldData.label}
onBlur={handleOnBlur}
onFocus={handleFocus}
>
<option key="default" value="">{fieldData.label}</option>
{fieldData.options.map(option => (
<option className="data-hj-suppress" key={option[0]} value={option[0]}>{option[1]}</option>
))}
</Form.Control>
{isRequired && errorMessage && (
<Form.Control.Feedback id={`${fieldData.name}-error`} type="invalid" className="form-text-size" hasIcon={false}>
{errorMessage}
</Form.Control.Feedback>
)}
</Form.Group>
);
break;
}
case 'textarea': {
formField = (
<Form.Group controlId={fieldData.name} isInvalid={!!(isRequired && errorMessage)}>
<Form.Control
className={className}
as="textarea"
name={fieldData.name}
value={value}
aria-invalid={isRequired && Boolean(errorMessage)}
onChange={(e) => onChangeHandler(e)}
floatingLabel={fieldData.label}
onBlur={handleOnBlur}
onFocus={handleFocus}
/>
{isRequired && errorMessage && (
<Form.Control.Feedback id={`${fieldData.name}-error`} type="invalid" className="form-text-size" hasIcon={false}>
{errorMessage}
</Form.Control.Feedback>
)}
</Form.Group>
);
break;
}
case 'text': {
formField = (
<Form.Group controlId={fieldData.name} isInvalid={!!(isRequired && errorMessage)}>
<Form.Control
className={className}
name={fieldData.name}
value={value}
aria-invalid={isRequired && Boolean(errorMessage)}
onChange={(e) => onChangeHandler(e)}
floatingLabel={fieldData.label}
onBlur={handleOnBlur}
onFocus={handleFocus}
/>
{isRequired && errorMessage && (
<Form.Control.Feedback id={`${fieldData.name}-error`} type="invalid" className="form-text-size" hasIcon={false}>
{errorMessage}
</Form.Control.Feedback>
)}
</Form.Group>
);
break;
}
case 'checkbox': {
formField = (
<Form.Group isInvalid={!!(isRequired && errorMessage)}>
<Form.Checkbox
className={className}
id={fieldData.name}
checked={!!value}
name={fieldData.name}
value={value}
aria-invalid={isRequired && Boolean(errorMessage)}
onChange={(e) => onChangeHandler(e)}
onBlur={handleOnBlur}
onFocus={handleFocus}
>
{fieldData.label}
</Form.Checkbox>
{isRequired && errorMessage && (
<Form.Control.Feedback id={`${fieldData.name}-error`} type="invalid" className="form-text-size" hasIcon={false}>
{errorMessage}
</Form.Control.Feedback>
)}
</Form.Group>
);
break;
}
default:
break;
}
return formField;
};
FormFieldRenderer.defaultProps = {
className: '',
value: '',
handleBlur: null,
handleFocus: null,
errorMessage: '',
isRequired: false,
};
FormFieldRenderer.propTypes = {
className: PropTypes.string,
fieldData: PropTypes.shape({
type: PropTypes.string,
label: PropTypes.string,
name: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
}).isRequired,
onChangeHandler: PropTypes.func.isRequired,
handleBlur: PropTypes.func,
handleFocus: PropTypes.func,
errorMessage: PropTypes.string,
isRequired: PropTypes.bool,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
};
export default FormFieldRenderer;

View File

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

View File

@@ -0,0 +1,199 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { mount } from 'enzyme';
import FieldRenderer from '../FieldRenderer';
describe('FieldRendererTests', () => {
let value = '';
const changeHandler = (e) => {
if (e.target.type === 'checkbox') {
value = e.target.checked;
} else {
value = e.target.value;
}
};
beforeEach(() => {
value = '';
});
it('should render select field type', () => {
const fieldData = {
type: 'select',
label: 'Year of Birth',
name: 'yob-field',
options: [['1997', 1997], ['1998', 1998]],
};
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
const field = fieldRenderer.find('select#yob-field');
field.simulate('change', { target: { value: 1997 } });
expect(field.type()).toEqual('select');
expect(fieldRenderer.find('label').text()).toEqual('Year of Birth');
expect(value).toEqual(1997);
});
it('should return null if no options are provided for select field', () => {
const fieldData = {
type: 'select',
label: 'Year of Birth',
name: 'yob-field',
};
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
expect(fieldRenderer.html()).toBeNull();
});
it('should render textarea field', () => {
const fieldData = {
type: 'textarea',
label: 'Why do you want to join this platform?',
name: 'goals-field',
};
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
const field = fieldRenderer.find('#goals-field').last();
field.simulate('change', { target: { value: 'These are my goals.' } });
expect(field.type()).toEqual('textarea');
expect(fieldRenderer.find('label').text()).toEqual('Why do you want to join this platform?');
expect(value).toEqual('These are my goals.');
});
it('should render an input field', () => {
const fieldData = {
type: 'text',
label: 'Company',
name: 'company-field',
};
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
const field = fieldRenderer.find('#company-field').last();
field.simulate('change', { target: { value: 'ABC' } });
expect(field.type()).toEqual('input');
expect(fieldRenderer.find('label').text()).toEqual('Company');
expect(value).toEqual('ABC');
});
it('should render checkbox field', () => {
const fieldData = {
type: 'checkbox',
label: `I agree that ${getConfig().SITE_NAME} may send me marketing messages.`,
name: 'marketing-emails-opt-in-field',
};
const fieldRenderer = mount(<FieldRenderer value={value} fieldData={fieldData} onChangeHandler={changeHandler} />);
const field = fieldRenderer.find('input#marketing-emails-opt-in-field');
field.simulate('change', { target: { checked: true, type: 'checkbox' } });
expect(field.prop('type')).toEqual('checkbox');
expect(fieldRenderer.find('label').text()).toEqual(fieldData.label);
expect(value).toEqual(true);
});
it('should return null if field type is unknown', () => {
const fieldData = {
type: 'unknown',
};
const fieldRenderer = mount(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
expect(fieldRenderer.html()).toBeNull();
});
it('should run onBlur and onFocus functions for a field if given', () => {
const fieldData = { type: 'text', label: 'Test', name: 'test-field' };
let functionValue = '';
const onBlur = (e) => {
functionValue = `${e.target.name} blurred`;
};
const onFocus = (e) => {
functionValue = `${e.target.name} focussed`;
};
const fieldRenderer = mount(
<FieldRenderer
handleFocus={onFocus}
handleBlur={onBlur}
value={value}
fieldData={fieldData}
onChangeHandler={changeHandler}
/>,
);
const field = fieldRenderer.find('#test-field').last();
field.simulate('focus');
expect(functionValue).toEqual('test-field focussed');
field.simulate('blur');
expect(functionValue).toEqual('test-field blurred');
});
it('should render error message for required text fields', () => {
const fieldData = { type: 'text', label: 'First Name', name: 'first-name-field' };
const fieldRenderer = mount(
<FieldRenderer
isRequired
fieldData={fieldData}
onChangeHandler={changeHandler}
errorMessage="Enter your first name"
/>,
);
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Enter your first name');
});
it('should render error message for required select fields', () => {
const fieldData = {
type: 'select', label: 'Preference', name: 'preference-field', options: [['a', 'Opt 1'], ['b', 'Opt 2']],
};
const fieldRenderer = mount(
<FieldRenderer
isRequired
fieldData={fieldData}
onChangeHandler={changeHandler}
errorMessage="Select your preference"
/>,
);
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Select your preference');
});
it('should render error message for required textarea fields', () => {
const fieldData = { type: 'textarea', label: 'Goals', name: 'goals-field' };
const fieldRenderer = mount(
<FieldRenderer
isRequired
fieldData={fieldData}
onChangeHandler={changeHandler}
errorMessage="Tell us your goals"
/>,
);
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('Tell us your goals');
});
it('should render error message for required checkbox fields', () => {
const fieldData = { type: 'checkbox', label: 'Honor Code', name: 'honor-code-field' };
const fieldRenderer = mount(
<FieldRenderer
isRequired
fieldData={fieldData}
onChangeHandler={changeHandler}
errorMessage="You must agree to our Honor Code"
/>,
);
expect(fieldRenderer.find('.form-text-size').last().text()).toEqual('You must agree to our Honor Code');
});
});

View File

@@ -1,29 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } 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 { INTERNAL_SERVER_ERROR } from '../data/constants';
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 = formatMessage(messages['forgot.password.error.alert.title']);
let { status } = props;
if (emailError) {
status = 'form-submission-error';
status = FORM_SUBMISSION_ERROR;
}
let message = '';
let heading = intl.formatMessage(messages['forgot.password.error.alert.title']);
const supportUrl = getConfig().PASSWORD_RESET_SUPPORT_LINK;
switch (status) {
case 'complete':
heading = intl.formatMessage(messages['confirmation.message.title']);
case COMPLETE_STATE:
heading = formatMessage(messages['confirmation.message.title']);
message = (
<FormattedMessage
id="forgot.password.confirmation.message"
@@ -34,15 +36,8 @@ const ForgotPasswordAlert = (props) => {
values={{
email: <span className="data-hj-suppress">{email}</span>,
supportLink: (
<Alert.Link
className="alert-link"
href={supportUrl}
onClick={e => {
e.preventDefault();
window.open(supportUrl, '_blank');
}}
>
{intl.formatMessage(messages['confirmation.support.link'])}
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK} target="_blank">
{formatMessage(messages['confirmation.support.link'])}
</Alert.Link>
),
}}
@@ -50,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':
heading = intl.formatMessage(messages['forgot.password.error.message.title']);
message = intl.formatMessage(messages['forgot.password.request.in.progress.message']);
case FORBIDDEN_STATE:
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 });
case FORM_SUBMISSION_ERROR:
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;
@@ -79,7 +74,7 @@ const ForgotPasswordAlert = (props) => {
return (
<Alert
id="validation-errors"
className="mb-5"
className="mb-4"
variant={`${status === 'complete' ? 'success' : 'danger'}`}
icon={status === 'complete' ? CheckCircle : Error}
>
@@ -99,8 +94,7 @@ ForgotPasswordAlert.defaultProps = {
ForgotPasswordAlert.propTypes = {
status: PropTypes.string.isRequired,
email: PropTypes.string,
intl: intlShape.isRequired,
emailError: PropTypes.string,
};
export default injectIntl(ForgotPasswordAlert);
export default ForgotPasswordAlert;

View File

@@ -1,149 +1,166 @@
import React, { useState, useEffect } from 'react';
import { Formik } from 'formik';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { Redirect } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form,
StatefulButton,
Hyperlink,
Tabs,
Tab,
Icon,
StatefulButton,
Tab,
Tabs,
} from '@edx/paragon';
import { ChevronLeft } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { Redirect } from 'react-router-dom';
import { forgotPassword } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import messages from './messages';
import { BaseComponent } from '../base-component';
import { FormGroup } from '../common-components';
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import ForgotPasswordAlert from './ForgotPasswordAlert';
import BaseComponent from '../base-component';
import messages from './messages';
const ForgotPasswordPage = (props) => {
const { intl, status, submitState } = props;
const platformName = getConfig().SITE_NAME;
const regex = new RegExp(VALID_EMAIL_REGEX, 'i');
const [validationError, setValidationError] = useState('');
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
const {
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 supportUrl = getConfig().LOGIN_ISSUE_SUPPORT_LINK;
useEffect(() => {
sendPageEvent('login_and_registration', 'reset');
sendTrackEvent('edx.bi.password_reset_form.viewed', { category: 'user-engagement' });
}, []);
const getValidationMessage = (email) => {
useEffect(() => {
setValidationError(emailValidationError);
}, [emailValidationError]);
useEffect(() => {
if (status === 'complete') {
setEmail('');
}
}, [status]);
const getValidationMessage = (value) => {
let error = '';
if (email === '') {
error = intl.formatMessage(messages['forgot.password.empty.email.field.error']);
} else if (!regex.test(email)) {
error = intl.formatMessage(messages['forgot.password.page.invalid.email.message']);
if (value === '') {
error = formatMessage(messages['forgot.password.empty.email.field.error']);
} else if (!emailRegex.test(value)) {
error = formatMessage(messages['forgot.password.page.invalid.email.message']);
}
setValidationError(error);
return error;
};
const handleBlur = () => {
props.setForgotPasswordFormData({ email, emailValidationError: getValidationMessage(email) });
};
const handleFocus = () => props.setForgotPasswordFormData({ emailValidationError: '' });
const handleSubmit = (e) => {
e.preventDefault();
setBannerEmail(email);
const error = getValidationMessage(email);
if (error) {
setFormErrors(error);
props.setForgotPasswordFormData({ email, emailValidationError: error });
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
} else {
props.forgotPassword(email);
}
};
const tabTitle = (
<div className="d-flex">
<Icon src={ChevronLeft} className="arrow-back-icon" />
<span className="ml-2">{intl.formatMessage(messages['sign.in.text'])}</span>
<div className="d-inline-flex flex-wrap align-items-center">
<Icon src={ChevronLeft} />
<span className="ml-2">{formatMessage(messages['sign.in.text'])}</span>
</div>
);
return (
<BaseComponent>
<Helmet>
<title>{formatMessage(messages['forgot.password.page.title'],
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<div>
<Tabs activeKey="" id="controlled-tab-example" onSelect={(k) => setKey(k)}>
<Tabs activeKey="" id="controlled-tab" onSelect={(k) => setKey(k)}>
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
</Tabs>
{ key && (
<Redirect to={updatePathWithQueryParams(key)} />
)}
<div id="main-content" className="main-content">
<Formik
initialValues={{ email: '' }}
validateOnChange={false}
validate={(values) => {
const validationMessage = getValidationMessage(values.email);
if (validationMessage !== '') {
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
return { email: validationMessage };
}
return {};
}}
onSubmit={(values) => { props.forgotPassword(values.email); }}
>
{({
errors, handleSubmit, setFieldValue, values,
}) => (
<>
<Helmet>
<title>{intl.formatMessage(messages['forgot.password.page.title'],
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<Form className="mw-xs">
<ForgotPasswordAlert email={props.email} emailError={errors.email} status={status} />
<h4>
{intl.formatMessage(messages['forgot.password.page.heading'])}
</h4>
<p className="mb-4">
{intl.formatMessage(messages['forgot.password.page.instructions'])}
</p>
<FormGroup
floatingLabel={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
name="email"
errorMessage={validationError}
value={values.email}
handleBlur={() => getValidationMessage(values.email)}
handleChange={e => setFieldValue('email', e.target.value)}
handleFocus={() => setValidationError('')}
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
/>
<StatefulButton
type="submit"
variant="brand"
className="login-button-width"
state={submitState}
labels={{
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
pending: '',
}}
onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
<Hyperlink
id="forgot-password"
className="ml-4 font-weight-500 text-body"
destination={supportUrl}
onClick={e => {
e.preventDefault();
window.open(supportUrl, '_blank');
}}
>{intl.formatMessage(messages['need.help.sign.in.text'])}
</Hyperlink>
<p className="mt-5 one-rem-font">{intl.formatMessage(messages['additional.help.text'])}
<span><Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink></span>
</p>
</Form>
</>
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
<h2 className="h4">
{formatMessage(messages['forgot.password.page.heading'])}
</h2>
<p className="mb-4">
{formatMessage(messages['forgot.password.page.instructions'])}
</p>
<FormGroup
floatingLabel={formatMessage(messages['forgot.password.page.email.field.label'])}
name="email"
value={email}
autoComplete="on"
errorMessage={validationError}
handleChange={(e) => setEmail(e.target.value)}
handleBlur={handleBlur}
handleFocus={handleFocus}
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"
state={submitState}
labels={{
default: formatMessage(messages['forgot.password.page.submit.button']),
pending: '',
}}
onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
{(getConfig().LOGIN_ISSUE_SUPPORT_LINK) && (
<Hyperlink
id="forgot-password"
name="forgot-password"
className="ml-4 font-weight-500 text-body"
destination={getConfig().LOGIN_ISSUE_SUPPORT_LINK}
target="_blank"
showLaunchIcon={false}
>
{formatMessage(messages['need.help.sign.in.text'])}
</Hyperlink>
)}
</Formik>
<p className="mt-5.5 small text-gray-700">
{formatMessage(messages['additional.help.text'], { platformName })}
<span>
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
</span>
</p>
</Form>
</div>
</div>
</BaseComponent>
@@ -151,15 +168,17 @@ const ForgotPasswordPage = (props) => {
};
ForgotPasswordPage.propTypes = {
intl: intlShape.isRequired,
email: PropTypes.string,
emailValidationError: PropTypes.string,
forgotPassword: PropTypes.func.isRequired,
setForgotPasswordFormData: PropTypes.func.isRequired,
status: PropTypes.string,
submitState: PropTypes.string,
};
ForgotPasswordPage.defaultProps = {
email: '',
emailValidationError: '',
status: null,
submitState: DEFAULT_STATE,
};
@@ -168,5 +187,6 @@ export default connect(
forgotPasswordResultSelector,
{
forgotPassword,
setForgotPasswordFormData,
},
)(injectIntl(ForgotPasswordPage));
)(ForgotPasswordPage);

View File

@@ -1,6 +1,7 @@
import { AsyncActionType } from '../../data/utils';
export const FORGOT_PASSWORD = new AsyncActionType('FORGOT', 'PASSWORD');
export const FORGOT_PASSWORD_PERSIST_FORM_DATA = 'FORGOT_PASSWORD_PERSIST_FORM_DATA';
// Forgot Password
export const forgotPassword = email => ({
@@ -24,3 +25,8 @@ export const forgotPasswordForbidden = () => ({
export const forgotPasswordServerError = () => ({
type: FORGOT_PASSWORD.FAILURE,
});
export const setForgotPasswordFormData = (forgotPasswordFormData) => ({
type: FORGOT_PASSWORD_PERSIST_FORM_DATA,
payload: { forgotPasswordFormData },
});

View File

@@ -1,11 +1,12 @@
import { FORGOT_PASSWORD } 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: '',
submitState: '',
email: '',
emailValidationError: '',
};
const reducer = (state = defaultState, action = null) => {
@@ -13,28 +14,42 @@ const reducer = (state = defaultState, action = null) => {
switch (action.type) {
case FORGOT_PASSWORD.BEGIN:
return {
email: state.email,
status: 'pending',
submitState: PENDING_STATE,
};
case FORGOT_PASSWORD.SUCCESS:
return {
...action.payload,
...defaultState,
status: 'complete',
};
case FORGOT_PASSWORD.FORBIDDEN:
return {
email: state.email,
status: 'forbidden',
};
case FORGOT_PASSWORD.FAILURE:
return {
email: state.email,
status: INTERNAL_SERVER_ERROR,
};
case PASSWORD_RESET_FAILURE:
return {
status: action.payload.errorCode,
};
case FORGOT_PASSWORD_PERSIST_FORM_DATA: {
const { forgotPasswordFormData } = action.payload;
return {
...state,
...forgotPasswordFormData,
};
}
default:
return defaultState;
return {
...defaultState,
email: state.email,
emailValidationError: state.emailValidationError,
};
}
}
return state;

View File

@@ -5,11 +5,10 @@ import { call, put, takeEvery } from 'redux-saga/effects';
import {
FORGOT_PASSWORD,
forgotPasswordBegin,
forgotPasswordSuccess,
forgotPasswordForbidden,
forgotPasswordServerError,
forgotPasswordSuccess,
} from './actions';
import { forgotPassword } from './service';
// Services

View File

@@ -0,0 +1,34 @@
import {
FORGOT_PASSWORD_PERSIST_FORM_DATA,
} from '../actions';
import reducer from '../reducers';
describe('forgot password reducer', () => {
it('should set email and emailValidationError', () => {
const state = {
status: '',
submitState: '',
email: '',
emailValidationError: '',
};
const forgotPasswordFormData = {
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
};
const action = {
type: FORGOT_PASSWORD_PERSIST_FORM_DATA,
payload: { forgotPasswordFormData },
};
expect(
reducer(state, action),
).toEqual(
{
status: '',
submitState: '',
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
},
);
});
});

View File

@@ -1,16 +1,16 @@
import { runSaga } from 'redux-saga';
import initializeMockLogging from '../../../setupTest';
import * as actions from '../actions';
import { handleForgotPassword } from '../sagas';
import * as api from '../service';
import initializeMockLogging from '../../../setupTest';
const { loggingService } = initializeMockLogging();
describe('handleForgotPassword', () => {
const params = {
payload: {
formData: {
forgotPasswordFormData: {
email: 'test@test.com',
},
},

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

@@ -51,16 +51,6 @@ const messages = defineMessages({
defaultMessage: 'Enter your email',
description: 'Error message that appears when user tries to submit empty email field',
},
'forgot.password.invalid.email.heading': {
id: 'forgot.password.invalid.email',
defaultMessage: 'An error occurred.',
description: 'heading for invalid email',
},
'forgot.password.invalid.email.message': {
id: 'forgot.password.invalid.email.message',
defaultMessage: "The email address you've provided isn't formatted correctly.",
description: 'message for invalid email',
},
'forgot.password.email.help.text': {
id: 'forgot.password.email.help.text',
defaultMessage: 'The email address you used to register with {platformName}',
@@ -84,7 +74,7 @@ const messages = defineMessages({
},
'additional.help.text': {
id: 'additional.help.text',
defaultMessage: 'For additional help, contact edX support at ',
defaultMessage: 'For additional help, contact {platformName} support at ',
description: 'additional help text on forgot password page',
},
'sign.in.text': {
@@ -134,10 +124,5 @@ const messages = defineMessages({
defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.',
description: 'Error message that appears when server responds with 500 error code',
},
'rate.limit.error': {
id: 'rate.limit.error',
defaultMessage: 'An error has occurred because of too many requests. Please try again after some time.',
description: 'Error message that appears when server responds with 429 error code',
},
});
export default messages;

View File

@@ -1,21 +1,21 @@
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { Provider } from 'react-redux';
import { MemoryRouter, Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { createMemoryHistory } from 'history';
import * as analytics from '@edx/frontend-platform/analytics';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { mergeConfig } from '@edx/frontend-platform';
import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n';
import * as analytics from '@edx/frontend-platform/analytics';
import * as auth from '@edx/frontend-platform/auth';
import ForgotPasswordPage from '../ForgotPasswordPage';
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 configureStore from 'redux-mock-store';
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
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');
@@ -51,7 +51,7 @@ describe('ForgotPasswordPage', () => {
beforeEach(() => {
store = mockStore(initialState);
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'edX' }));
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'test-user' }));
configure({
loggingService: { logError: jest.fn() },
config: {
@@ -66,7 +66,15 @@ describe('ForgotPasswordPage', () => {
};
});
it('not should display need other help signing in button', () => {
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
expect(wrapper.find('#forgot-password').exists()).toBeFalsy();
});
it('should display need other help signing in button', () => {
mergeConfig({
LOGIN_ISSUE_SUPPORT_LINK: '/support',
});
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
expect(wrapper.find('#forgot-password').first().text()).toEqual('Need help signing in?');
});
@@ -125,28 +133,61 @@ describe('ForgotPasswordPage', () => {
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
});
it('should display error message on blur event', async () => {
const validationMessage = 'Enter your email';
it('should set error in redux store on onBlur', () => {
const forgotPasswordFormData = {
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
};
props = {
...props,
email: 'test@gmail',
emailValidationError: '',
};
store.dispatch = jest.fn(store.dispatch);
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const emailInput = forgotPasswordPage.find('input#email');
await act(async () => {
await emailInput.simulate('blur', { target: { value: '', name: 'email' } });
});
forgotPasswordPage.find('input#email').simulate('blur');
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
});
it('should display error message if available in props', async () => {
const validationMessage = 'Enter your email';
props = {
...props,
emailValidationError: validationMessage,
email: '',
};
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
forgotPasswordPage.update();
expect(forgotPasswordPage.find('.pgn__form-text-invalid').text()).toEqual(validationMessage);
});
it('should clear error message on focus event', async () => {
const validationMessage = 'Enter your email';
it('should clear error in redux store on onFocus', () => {
const forgotPasswordFormData = {
emailValidationError: '',
};
props = {
...props,
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
};
store.dispatch = jest.fn(store.dispatch);
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
await act(async () => { await forgotPasswordPage.find('button.btn-brand').simulate('click'); });
forgotPasswordPage.update();
expect(forgotPasswordPage.find('.pgn__form-text-invalid').text()).toEqual(validationMessage);
forgotPasswordPage.find('input#email').simulate('focus');
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
});
it('should clear error message when cleared in props on focus', async () => {
props = {
...props,
emailValidationError: '',
email: '',
};
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
forgotPasswordPage.update();
expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
});

View File

@@ -1,17 +1,21 @@
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 zhcnMessages from './messages/zh_CN.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';
const messages = {
ar: arMessages,
@@ -19,6 +23,9 @@ const messages = {
'es-419': es419Messages,
fr: frMessages,
'zh-cn': zhcnMessages,
'it-it': ititMessages,
'pt-pt': ptptMessages,
'de-de': dedeMessages,
ca: caMessages,
he: heMessages,
id: idMessages,
@@ -28,6 +35,7 @@ const messages = {
ru: ruMessages,
th: thMessages,
uk: ukMessages,
hi: hiMessages,
};
export default messages;

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