Compare commits

...

234 Commits

Author SHA1 Message Date
Jason Wesson
d598cf1e4b fix: replace injected Plugins directory with dependency 2023-11-08 20:12:46 +00:00
Jason Wesson
d5949f55c2 Merge master into PluggablePOCFeature (#908)
---------

Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-11-07 20:03:44 -08:00
Jason Wesson
cd28310937 fix: hyperlink in plugin page should redirect to user profile (#897)
* add user's full name to plugn page

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-10-24 13:04:55 -07:00
Jason Wesson
b75347ad06 Merge master into PluginPOCFeature (#896) 2023-10-24 09:51:12 -07:00
Chris Deery
bd931338d8 Merge pull request #885 from openedx/PluginPOC/addTag
feat: add little tag to identify POC
2023-10-19 12:08:44 -04:00
Chris Deery
f03d5afa0d fix: update snapshot 2023-10-19 15:58:39 +00:00
Chris Deery
910e17f75d feat: add little tag to identify POC 2023-10-19 15:34:04 +00:00
Jason Wesson
2fa5cadf22 fix: rebase pluginPOCFeature with master branch (#884)
* fix(deps): update dependency @edx/paragon to v20.46.3

* chore(deps): update commitlint monorepo to v17.8.0

* fix(deps): update dependency @edx/frontend-component-footer to v12.4.0

* chore: bump frontend-platform (#869)

* fix: Add ID attribute to the main content (#845)

* chore: update browserslist DB (#871)

* fix(deps): update dependency @edx/frontend-platform to v5.6.1 (#875)

* build(deps): bump @babel/traverse from 7.22.5 to 7.23.2 (#879)

* bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.5 to 7.23.2.

* fix(deps): update dependency core-js to v3.33.0 (#876)
2023-10-18 11:58:21 -07:00
Jason Wesson
bd8221997e Move changes from ProfilePluginPOC to aperture/PluginPOCFeature (#883)
* build: create profile plugin page
* build: add plugins folder to Profile
* build: wrap Profile Plugin Page with Plugin

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-10-18 11:43:43 -07:00
Deborah Kaplan
43f485d841 Merge pull request #850 from openedx/update-browserslist-db
Update browserslist DB
2023-10-10 11:06:24 -04:00
renovate[bot]
b892ba763e fix(deps): update dependency @edx/frontend-component-header to v4.7.1 2023-10-09 16:53:45 +00:00
renovate[bot]
896905b457 fix(deps): update dependency @edx/frontend-component-footer to v12.3.0 2023-10-09 10:34:37 +00:00
jsnwesson
095b91c8cb chore: update browserslist DB 2023-10-09 00:09:13 +00:00
renovate[bot]
a6e63a8686 chore(deps): update dependency glob to v10.3.10 2023-10-02 17:03:40 +00:00
renovate[bot]
67c8d79aa2 fix(deps): update dependency @edx/frontend-component-header to v4.6.1 2023-10-02 13:24:41 +00:00
renovate[bot]
f76185d57d chore(deps): update dependency @edx/frontend-build to v12.9.17 2023-10-02 09:40:44 +00:00
renovate[bot]
f0678ca94c chore(deps): update dependency @commitlint/cli to v17.7.2 2023-10-02 06:30:56 +00:00
Mashal Malik
e73b646263 refactor: add openedx in renovate automate configuration (#849) 2023-09-28 12:21:18 -04:00
renovate[bot]
ddb8494471 fix(deps): update dependency @edx/frontend-platform to v5.4.0 2023-09-25 15:09:40 +00:00
edX requirements bot
a576bdf98b chore: update browserslist DB (#846)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-09-25 09:01:20 -04:00
renovate[bot]
21dcadba5b chore(deps): update dependency glob to v10.3.7 2023-09-25 09:21:59 +00:00
Deborah Kaplan
e770101e4e Merge pull request #842 from openedx/update-browserslist-db
Update browserslist DB
2023-09-18 12:49:47 -04:00
renovate[bot]
8ca5ea5809 fix(deps): update react-router monorepo to v6.16.0 2023-09-18 10:09:02 +00:00
renovate[bot]
d687ea30cb fix(deps): update dependency regenerator-runtime to v0.14.0 2023-09-18 07:58:45 +00:00
jsnwesson
ecda751786 chore: update browserslist DB 2023-09-18 00:08:53 +00:00
edX requirements bot
e58b174c9e chore: update browserslist DB (#839)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-09-15 14:37:58 -07:00
edx-transifex-bot
6c82805c7a chore(i18n): update translations (#838)
Co-authored-by: Jenkins <sre+jenkins@edx.org>
2023-09-15 14:37:47 -07:00
renovate[bot]
d1d98794ab fix(deps): update dependency @edx/frontend-platform to v5.3.2 2023-09-11 16:58:08 +00:00
renovate[bot]
3c7baaa91b fix(deps): update dependency core-js to v3.32.2 2023-09-11 09:39:16 +00:00
Syed Ali Abbas Zaidi
fe800f2ee9 feat: upgrade react router to v6 (#716)
* feat: upgrade react router to v6
* refactor: add hoc to use params
* fix: lint issue
2023-09-05 14:55:23 -04:00
Mashal Malik
e1d4e9b474 refactor: update lock file version (#834) 2023-09-05 14:49:02 -04:00
edX requirements bot
cf7568bcfb chore: update browserslist DB (#829)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-09-05 14:46:18 -04:00
renovate[bot]
a0fd863bc4 fix(deps): update dependency @edx/frontend-component-footer to v12.2.1 2023-09-04 09:53:06 +00:00
renovate[bot]
b63341fe99 chore(deps): update dependency glob to v10.3.4 2023-09-04 08:36:20 +00:00
renovate[bot]
1887167d0e fix(deps): update dependency core-js to v3.32.1 2023-08-21 13:43:20 +00:00
renovate[bot]
57de2b4156 chore(deps): update dependency @edx/frontend-build to v12.9.10 2023-08-21 10:09:12 +00:00
renovate[bot]
aaf6935577 fix(deps): update dependency @edx/frontend-component-header to v4.5.2 2023-08-14 16:30:15 +00:00
renovate[bot]
03b7859b20 chore(deps): update commitlint monorepo 2023-08-14 09:24:21 +00:00
edX requirements bot
2800e89f02 chore: update browserslist DB (#825)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-08-10 07:40:49 -04:00
Emad Rad
0cc03e5fec chore: translations updated (#828) 2023-08-07 14:02:54 -07:00
renovate[bot]
44f1a5f0cd fix(deps): update dependency @edx/frontend-platform to v4.6.1 2023-08-07 09:09:08 +00:00
renovate[bot]
2694f6f754 chore(deps): update dependency @edx/frontend-build to v12.9.4 2023-08-07 08:28:02 +00:00
renovate[bot]
18afe62590 fix(deps): update dependency core-js to v3.32.0 2023-07-31 10:26:36 +00:00
renovate[bot]
0b6d3dc9ac chore(deps): update dependency @edx/frontend-build to v12.9.3 2023-07-31 06:56:55 +00:00
edX requirements bot
bd5984f27b chore: update browserslist DB (#813)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-07-25 14:48:37 -04:00
Omar Al-Ithawi
f899727f8d feat: include paragon in atlas pull (#811)
This pull request is part of the [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) which is sparked by the [Translation Infrastructure update OEP-58](https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0058-arch-translations-management.html#specification).
2023-07-25 11:22:15 -04:00
renovate[bot]
803a2d5572 fix(deps): update dependency core-js to v3.31.1 2023-07-24 13:49:10 +00:00
renovate[bot]
93b0b1b9da fix(deps): update dependency @edx/frontend-component-header to v4.4.4 2023-07-24 10:27:39 +00:00
renovate[bot]
22b01f347d chore(deps): update commitlint monorepo to v17.6.7 2023-07-24 07:53:18 +00:00
Mashal Malik
b537f1f82d chore: add paragon messages (#798) 2023-07-21 11:08:29 +05:00
Deborah Kaplan
c42e339051 Merge pull request #800 from openedx/update-browserslist-db
Update browserslist DB
2023-07-20 11:15:12 -04:00
Deborah Kaplan
528bbcbad8 Merge pull request #805 from openedx/dependabot/npm_and_yarn/semver-5.7.2
build(deps): bump semver from 5.7.1 to 5.7.2
2023-07-20 11:02:08 -04:00
renovate[bot]
fcc5ce77e7 fix(deps): update dependency @edx/paragon to v20.45.2 2023-07-17 17:19:14 +00:00
renovate[bot]
ff4cd80ff2 fix(deps): update dependency @edx/frontend-component-header to v4.4.3 2023-07-17 12:24:56 +00:00
dependabot[bot]
25a6093a78 build(deps): bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 09:41:10 +00:00
renovate[bot]
d3a4b4b62d chore(deps): update dependency @edx/frontend-build to v12.8.66 2023-07-17 09:39:54 +00:00
renovate[bot]
fd52682b5c fix(deps): update dependency @edx/frontend-component-footer to v12.1.2 2023-07-17 06:41:39 +00:00
jsnwesson
a6eb5f75d1 chore: update browserslist DB 2023-07-17 00:10:44 +00:00
Emad Rad
6803fb5199 Feature: Persian language (#620)
* feat: Persian translations added

* feat: persian language added to messages

* feat: fa_IR added to transifex_langs
2023-07-12 10:07:50 -07:00
Swayam Rana
f05db64d49 Merge pull request #790 from openedx/swayamrana-pact
feat: Pact Consumer Contract Testing on LMS Account API
2023-07-11 11:22:08 -04:00
Swayam Rana
038c2a845f feat: Pact Contract Consumer testing with the Account API, and implement stub script 2023-07-11 15:15:33 +00:00
renovate[bot]
105486a44a fix(deps): update dependency @edx/frontend-component-header to v4.4.0 2023-07-10 15:51:59 +00:00
renovate[bot]
7b44687db9 fix(deps): update dependency @edx/frontend-component-footer to v12.1.1 2023-07-10 14:36:42 +00:00
renovate[bot]
7aeac7be3b chore(deps): update dependency glob to v10.3.3 2023-07-10 11:08:03 +00:00
renovate[bot]
d4665797a1 chore(deps): update dependency @edx/frontend-build to v12.8.60 2023-07-10 07:14:11 +00:00
Bilal Qamar
4a02f0ef6d feat: update react & react-dom to v17 (#769)
* feat: update react & react-dom to v17
* build: update lock file and paragon
* Merge branch 'master' of github.com:openedx/frontend-app-profile into bilalqamar95/react-upgrade-to-v17
* refactor: updated edx packages
---------

Co-authored-by: mashal-m <mashal.malik@arbisoft.com>
2023-07-07 10:17:53 -04:00
Jason Wesson
b0b8e96025 build: add catalog-info yaml and update README (#797)
* These updates and additions aim to meet the Maintainership Pilot Phase 1 requirements

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-07-05 12:08:06 -07:00
renovate[bot]
6d5c150d2f chore(deps): update dependency glob to v10 (#793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-03 10:55:50 -07:00
Eugene Dyudyunov
549d51509b fix: error when trying to save 'other education' (#667)
When user selects "Other Education" and clicks save - error occurs.

It's caused by the wrong API call payload value `'o'`
Fix: use the `'other'` payload value instead (the valid one).
2023-07-03 10:22:13 -07:00
Yoiber
ba74aef631 chore(i18n): add more languages (#689)
* chore(i18n): add more languages
2023-07-03 10:20:05 -07:00
edX requirements bot
941652aba2 chore: update browserslist DB (#781)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-07-03 10:15:59 -07:00
renovate[bot]
3b68201748 fix(deps): update dependency @edx/frontend-component-header to v4.2.3 (#743)
* fix(deps): update dependency @edx/frontend-component-header to v4.2.3

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-07-03 10:13:11 -07:00
renovate[bot]
2b8f5a8b3d fix(deps): update dependency history to v5 (#794)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-03 10:04:22 -07:00
Maxwell Frank
235644e570 Merge pull request #791 from openedx/mfrank/remove-skills-builder-master
refactor: remove Skills Builder
2023-07-03 09:11:34 -04:00
Maxwell Frank
5e0eef68e3 refactor: remove Skills Builder 2023-07-03 13:00:47 +00:00
renovate[bot]
4692477738 fix(deps): update dependency react-instantsearch-hooks-web to v6.45.0 2023-07-03 06:15:35 +00:00
renovate[bot]
465d3b481a fix(deps): update dependency algoliasearch to v4.18.0 2023-06-26 15:57:05 +00:00
renovate[bot]
79e29c46c5 fix(deps): update dependency @edx/paragon to v20.45.0 2023-06-26 13:32:44 +00:00
renovate[bot]
97e70fa4ab chore(deps): update dependency @edx/frontend-build to v12.8.57 2023-06-26 10:16:08 +00:00
renovate[bot]
60fec4152f chore(deps): update commitlint monorepo to v17.6.6 2023-06-26 07:16:12 +00:00
Maxwell Frank
26a82ec109 Merge pull request #776 from openedx/update-browserslist-db
Update browserslist DB
2023-06-21 13:09:03 -04:00
renovate[bot]
bf03f8b5ef fix(deps): update dependency react-instantsearch-hooks-web to v6.44.3 2023-06-19 17:51:20 +00:00
renovate[bot]
2a4addbb38 fix(deps): update dependency core-js to v3.31.0 2023-06-19 14:19:55 +00:00
renovate[bot]
e2b9ef7a2f fix(deps): update dependency @edx/paragon to v20.44.0 2023-06-19 11:34:11 +00:00
renovate[bot]
a6bcc9c054 chore(deps): update dependency @edx/frontend-build to v12.8.54 2023-06-19 06:49:52 +00:00
jsnwesson
31d2c07c5b chore: update browserslist DB 2023-06-19 00:08:41 +00:00
Jason Wesson
03e352208f fix: switch out class overwrite in SCSS for an aria-label selector (#771)
* fix: switch out class overwrite in SCSS for an aria-label selector

---------

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-06-15 11:22:46 -07:00
Maxwell Frank
ac7285dac5 Merge pull request #775 from openedx/mfrank/switch-for-lob
Query string to act as switch for lines of business
2023-06-15 10:01:36 -04:00
Maxwell Frank
60fe9cff9a feat: adding lines of business 2023-06-15 13:27:10 +00:00
edX requirements bot
93f757f3d7 chore: update browserslist DB (#699)
Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-06-14 13:36:48 -07:00
renovate[bot]
6740ad3672 fix(deps): update dependency algoliasearch to v4.17.2 2023-06-12 16:25:54 +00:00
renovate[bot]
ca14d3d279 chore(deps): update dependency @edx/frontend-build to v12.8.51 2023-06-12 12:00:55 +00:00
Jenkins
9dbe58b10f chore(i18n): update translations 2023-06-11 16:40:53 -04:00
Jason Wesson
50d80ef614 fix: adjust header height and content when window is medium in width or less (#756)
* fix: adjust header height and content when window is medium in width or less

* feat: convert stepper header's contents to compact view for medium windows (or less)

---------

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-06-06 11:10:00 -07:00
renovate[bot]
fe6b76da7e fix(deps): update dependency @edx/frontend-platform to v4.5.1 2023-06-05 12:46:05 +00:00
renovate[bot]
7fff13137d chore(deps): update dependency @edx/frontend-build to v12.8.40 2023-06-05 10:26:23 +00:00
renovate[bot]
91282cff74 chore(deps): update commitlint monorepo to v17.6.5 2023-06-05 07:50:24 +00:00
renovate[bot]
45be830f18 fix(deps): update dependency core-js to v3.30.2 2023-05-29 12:15:11 +00:00
renovate[bot]
122affbb6d fix(deps): update dependency @edx/frontend-platform to v4.5.0 2023-05-29 11:07:30 +00:00
renovate[bot]
48a97b769f fix(deps): update dependency algoliasearch to v4.17.1 2023-05-29 08:24:04 +00:00
renovate[bot]
bdcc09f6ba chore(deps): update dependency @edx/frontend-build to v12.8.38 2023-05-29 06:05:47 +00:00
Maxwell Frank
ac4fb6a340 Merge pull request #757 from openedx/mfrank/fallback-img-typo
fix: fallback src typo for course cards
2023-05-25 13:01:59 -04:00
Maxwell Frank
409d365125 fix: fallback src typo for course cards 2023-05-25 16:15:20 +00:00
Jason Wesson
53985e94d8 fix: disable 'select a goal' from goal dropdown when a goal has been selected (#755)
* hide the second and third portion of skills form if currentGoal is false
2023-05-23 07:57:25 -07:00
renovate[bot]
0d9a39afd7 fix(deps): update dependency @edx/frontend-platform to v4.4.0 2023-05-23 04:18:26 +00:00
renovate[bot]
cbb860bb16 chore(deps): update dependency @edx/reactifex to v2.2.0 2023-05-23 01:34:22 +00:00
Justin Hynes
695df9aa0b fix: filter out non-English courses from recommendations (#752)
[APER-2444]

This PR updates the `getProductRecommendations` utiltiy function, adding a filter to only include English courses in our recommendations.
2023-05-22 13:13:43 -04:00
Maxwell Frank
603304b799 Merge pull request #751 from openedx/mfrank/fix-close-exit-buttons
fix: close and exit buttons
2023-05-18 13:58:42 -04:00
Maxwell Frank
d3e5931d05 fix: close and exit buttons 2023-05-18 17:52:40 +00:00
renovate[bot]
6804f7e127 fix(deps): update dependency algoliasearch to v4.17.0 2023-05-17 22:25:05 +00:00
Maxwell Frank
4b16673780 Merge pull request #750 from openedx/mfrank/reduce-event-payload
fix: reduce event payload
2023-05-17 11:12:10 -04:00
Maxwell Frank
6674025bd4 fix: reduce event payload 2023-05-17 14:38:15 +00:00
renovate[bot]
0dab2d03eb chore(deps): update commitlint monorepo to v17.6.3 2023-05-15 09:41:55 +00:00
Jenkins
df1a84feb7 chore(i18n): update translations 2023-05-14 16:40:48 -04:00
Bilal Qamar
334a9b090e feat: upgraded to node v18, added .nvmrc and updated workflows (#712)
* feat: upgraded to node v18, added .nvmrc and updated workflows

* build: updated frontend-build, frontend-platform, component-footer & component-header packages

* refactor: updated snapshots

* refactor: updated snapshots
2023-05-12 14:09:48 -04:00
Maxwell Frank
5d06276838 Merge pull request #745 from openedx/mfrank/adding-configure-component
fix: remove filtering from current job select
2023-05-11 09:20:43 -04:00
Maxwell Frank
e391e427f1 fix: remove filtering from current job select 2023-05-11 13:05:04 +00:00
Maxwell Frank
b71328fd3f Merge pull request #744 from openedx/mfrank/adding-configure-component
fix: adding configure component to filter jobs
2023-05-10 16:40:11 -04:00
Maxwell Frank
3b9b3f8840 fix: adding configure component to filter jobs 2023-05-10 20:34:29 +00:00
Maxwell Frank
30e837306f Merge pull request #739 from openedx/mfrank/segement-events
APER-2333: Dispatch segment events from JS components
2023-05-10 15:45:35 -04:00
Maxwell Frank
c7a0c1d799 feat: adding analytics 2023-05-09 13:29:38 +00:00
renovate[bot]
337c97e3a0 chore(deps): update dependency @edx/frontend-build to v12.8.27 2023-05-08 09:32:26 +00:00
renovate[bot]
0de4496953 fix(deps): update dependency @edx/paragon to v20.32.3 2023-05-01 10:06:44 +00:00
renovate[bot]
359ae7f1fb chore(deps): update dependency @edx/frontend-build to v12.8.16 2023-05-01 07:10:23 +00:00
Omar Al-Ithawi
8d467f01dc feat: use atlas in make pull_translations (#732)
- Bump frontend-platform to bring intl-imports.js script
 - Move all i18n imports into `src/i18n/index.js` so intl-imports.js can override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL` environment variable is set.

This pull request is part of the [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) which is sparked by the [Translation Infrastructure update OEP-58](https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0058-arch-translations-management.html#specification).
2023-04-25 09:33:22 -04:00
renovate[bot]
20debcd79e fix(deps): update dependency reselect to v4.1.8 2023-04-24 13:13:42 +00:00
renovate[bot]
6a7cbf88df fix(deps): update dependency @edx/frontend-component-footer to v11.7.4 2023-04-24 09:34:43 +00:00
Jenkins
1b3880ee1b chore(i18n): update translations 2023-04-23 16:40:44 -04:00
renovate[bot]
79cebaf6df fix(deps): update dependency @edx/frontend-component-footer to v11.7.2 2023-04-17 15:00:56 +00:00
renovate[bot]
8686af563e chore(deps): update dependency @edx/frontend-build to v12.8.10 2023-04-17 10:24:54 +00:00
renovate[bot]
85d85007d2 fix(deps): update dependency @edx/frontend-component-footer to v11.7.1 2023-04-10 13:30:36 +00:00
renovate[bot]
9276fe25ad chore(deps): update dependency @edx/frontend-build to v12.8.6 2023-04-10 09:32:52 +00:00
Jenkins
9c2dd68752 chore(i18n): update translations 2023-04-02 16:40:41 -04:00
Maxwell Frank
e4a9045e89 feat: course recommendations for Skills Builder with fix 2023-03-30 15:34:43 -04:00
Maxwell Frank
c1bbbe488a feat: skills builder course recommendations 2023-03-30 19:26:29 +00:00
Maxwell Frank
45ab2f8175 Merge pull request #725 from openedx/revert-721-mfrank/course-recommendations
Revert "feat: course recommendations for Skills Builder"
2023-03-28 14:34:51 -04:00
Maxwell Frank
d9c7096fd7 Revert "feat: course recommendations for Skills Builder" 2023-03-28 14:05:18 -04:00
Maxwell Frank
c6825393c6 feat: course recommendations for Skills Builder
feat: course recommendations for Skills Builder
2023-03-28 09:37:59 -04:00
renovate[bot]
9354f11a99 fix(deps): update dependency @edx/frontend-component-header to v3.6.5 2023-03-28 12:04:23 +00:00
renovate[bot]
e7fc8f52fb chore(deps): update dependency @commitlint/cli to v17.5.1 2023-03-28 10:16:30 +00:00
Maxwell Frank
d8c8f5d7bd feat: course recommendations for Skills Builder 2023-03-27 19:28:46 +00:00
renovate[bot]
e5355e7ac8 chore(deps): update dependency @edx/frontend-build to v12.6.2 2023-03-24 09:14:55 +00:00
Maxwell Frank
99a80d3e66 Merge pull request #718 from openedx/mfrank/authenticated-page-route
fix: authenticated page route
2023-03-22 10:56:11 -04:00
Maxwell Frank
abf9860f62 fix: authenticated page route 2023-03-20 13:39:01 +00:00
renovate[bot]
ccc62a0e48 fix(deps): update dependency redux-saga to v1.2.3 2023-03-20 10:39:41 +00:00
renovate[bot]
650d3d469f fix(deps): update dependency @edx/paragon to v20.28.5 2023-03-20 08:06:56 +00:00
renovate[bot]
ab80fd7671 fix(deps): update dependency @edx/frontend-component-header to v3.6.4 2023-03-13 13:09:42 +00:00
Mashal Malik
f55b304732 refactor: remove unused tranisfex v2 url (#704) 2023-03-13 10:45:28 +05:00
Jenkins
65971820d4 chore(i18n): update translations 2023-03-12 16:40:39 -04:00
Maxwell Frank
7da386264b Merge pull request #706 from openedx/mfrank/retrieving-job-skills-data
APER-2187 Render jobs and related skills for Skills Builder
2023-03-08 14:03:46 -05:00
Maxwell Frank
b1fe21cded feat: SkillsBuilder jobs with related skills 2023-03-07 19:07:10 +00:00
Jenkins
40225d7db3 chore(i18n): update translations 2023-03-05 15:40:37 -05:00
Maxwell Frank
b4ba5276ae Merge pull request #702 from openedx/mfrank/adding-career-interests
feat: SkillsBuilder career interests selection
2023-02-28 14:55:57 -05:00
Maxwell Frank
ddff5364ce feat: SkillsBuilder career interests selection 2023-02-28 19:51:16 +00:00
David Joy
f57f5c4725 docs: update the maintaining group to openedx/2u-aperture (#703)
The @edx/arch-fed group no longer exists - this repo is maintained by the @openedx/2u-aperture team.
2023-02-28 13:46:03 -05:00
Feanil Patel
e6feef00eb Merge pull request #696 from openedx/repo_checks/ensure_workflows
Update standard workflow files.
2023-02-27 22:36:12 -05:00
Maxwell Frank
87487e37d7 Merge pull request #693 from openedx/mfrank/goals-and-job-select-v2
APER-2186: adding goal and job title selection
2023-02-27 09:16:06 -05:00
renovate[bot]
6b451a4437 fix(deps): update dependency @edx/frontend-component-footer to v11.6.3 2023-02-27 12:33:52 +00:00
renovate[bot]
b10b31860d chore(deps): update commitlint monorepo to v17.4.4 2023-02-27 09:29:59 +00:00
Jenkins
83e2b66c77 chore(i18n): update translations 2023-02-26 15:40:39 -05:00
dependabot[bot]
6fa5681e91 build(deps): bump cookiejar from 2.1.3 to 2.1.4 (#674)
Bumps [cookiejar](https://github.com/bmeck/node-cookiejar) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/bmeck/node-cookiejar/releases)
- [Commits](https://github.com/bmeck/node-cookiejar/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-24 13:07:48 -08:00
dependabot[bot]
e9e48e4eb0 build(deps): bump json5 from 1.0.1 to 1.0.2 (#661)
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-24 13:03:26 -08:00
renovate[bot]
17b4933278 fix(deps): update dependency @edx/frontend-platform to v3 (#660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-24 12:52:53 -08:00
Maxwell Frank
68dc8a1045 feat: first two questions of Skills Builder 2023-02-24 19:26:53 +00:00
Justin Hynes
21a3e9259d Merge pull request #695 from openedx/jhynes/aper-2262
feat: add utility function to retrieve product recommendations based on job skills
2023-02-24 09:06:26 -05:00
Justin Hynes
a6086fd4bf feat: add utility function to retrieve product recommendations based on job skills
[APER-2262]

- add a utility function to retrieve product recommendations based on skills from a job a learner is interested in
- add additional tests and coverage around new utility functions in `search.jsx`
2023-02-24 08:57:37 -05:00
edX requirements bot
0c427cf5e3 chore: update browserslist DB (#656) 2023-02-23 13:44:15 -08:00
Feanil Patel
75ea8bc207 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-23 14:01:50 -05:00
Feanil Patel
47c06c0f5d 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-23 14:01:49 -05:00
Feanil Patel
8e0ab6db4d 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-23 14:01:48 -05:00
Jenkins
20159f140e chore(i18n): update translations 2023-02-19 15:40:37 -05:00
Justin Hynes
2d05de92af Merge pull request #691 from openedx/jhynes/aper-2261_algolia-utility-functions
feat: Add utility functions for querying job related data from Algolia
2023-02-17 14:12:54 -05:00
Justin Hynes
10f93420f4 feat: Add utility functions for querying job related data from Algolia
[APER-2261]
* rename `hooks.jsx` to `search.jsx` as this is more of a collection of utilities for working with Algolia
* add a utiltiy function for returning job info (from our "job" Algolia index) based on a list of jobs a learner is interested in
* add a utility function for formatting job names based on the syntax Algolia expects
2023-02-17 14:08:11 -05:00
Maxwell Frank
a12f91f7a5 Merge pull request #685 from openedx/mfrank/building-out-reducer
feat: adding actions and reducer for Skills Builder
2023-02-15 13:34:06 -05:00
Maxwell Frank
8f42e6fbfb feat: adding actions and reducer 2023-02-15 17:09:12 +00:00
Justin Hynes
4ecdf583ea Merge pull request #690 from openedx/jhynes/aper-2261
feat: Add Algolia support to Profile MFE for Skills Builder
2023-02-15 11:37:46 -05:00
Justin Hynes
e75864b860 feat: Add Algolia support to Profile MFE
[APER-2261]

This PR adds Algolia support to the Profile MFE and the upcoming Skills Builder feature.

* Adds new dependency for the `algoliasearch` package
* Add new config to support Algolia
* Update MFE configuration so we can access the new configuration variables
* Add hook to initialize Algolia client and return job and product Algolia indexes (based on settings)
* Update SkillsBuilderModal to add test code that displays the results of querying Algolia
2023-02-15 09:56:52 -05:00
Jenkins
a697e3c543 chore(i18n): update translations 2023-02-12 15:40:35 -05:00
Jason Wesson
04607dba1d Feat: display Learning Goal in profile (#676)
* feat: add unit tests for LearningGoal component

* feat: add LearningGoals component to Profile

---------

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-02-08 13:54:47 -08:00
Justin Hynes
6a4c8d9138 Merge pull request #682 from openedx/jhynes/aper-2258
feat: add Skills Builder Header component, refactor to use ModalDialog
2023-02-06 14:45:31 -05:00
Justin Hynes
dbf716eef5 feat: add Skills Builder Header component, refactor to use ModalDialog
[APER-2258]

This PR adds the SkillsBuilderHeader component to the profile MFE.

There was also some additional refactoring work required to display the header in the modal. The FullscreenModal component is not flexible enough to allow custom header designs to be displayed (only text). We have to pivot to using the `ModalDialog` family of components instead.
2023-02-06 13:34:04 -05:00
renovate[bot]
5eaab4f07d fix(deps): update dependency redux to v4.2.1 2023-02-06 13:31:33 +00:00
renovate[bot]
a139c2f71d fix(deps): update dependency @edx/frontend-component-footer to v11.6.2 2023-02-06 11:47:01 +00:00
Maxwell Frank
9e34fdd68d Merge pull request #680 from openedx/mfrank/adding-skills-context
feat: added skills context
2023-02-01 13:31:52 -05:00
Justin Hynes
109c5d437d Merge pull request #681 from openedx/jhynes/aper-2240-cleanup
fix: remove LR MFE URL building logic
2023-01-31 13:48:51 -05:00
Justin Hynes
25d0ecb531 fix: remove learner record MFE URL building logic
[APER-2240]

The Profile MFE no longer needs to understand anything about the Learner Record MFE. The Credentials IDA now has logic to determine if a request should be redirected to the Learner Record MFE so we can remove these changes from the Profile MFE.
2023-01-30 14:00:13 -05:00
Maxwell Frank
767af3c40b feat: added skills context 2023-01-30 15:46:15 +00:00
renovate[bot]
6a05552969 fix(deps): update dependency core-js to v3.27.2 2023-01-30 15:02:41 +00:00
renovate[bot]
628914dce3 fix(deps): update dependency @edx/frontend-component-header to v3.6.1 2023-01-30 10:28:08 +00:00
Jenkins
be7e204c91 chore(i18n): update translations 2023-01-29 15:40:36 -05:00
Bilal Qamar
c1d4c36a65 chore: update dependency @edx/frontend-build to v12.4.19 (#675) 2023-01-26 12:20:35 +05:00
renovate[bot]
ce04a04c36 fix(deps): update dependency @edx/frontend-component-footer to v11.6.1 2023-01-23 14:58:40 +00:00
renovate[bot]
346c08e5d6 chore(deps): update dependency @edx/frontend-build to v12.4.16 2023-01-23 11:27:47 +00:00
Jenkins
fe61237464 chore(i18n): update translations 2023-01-22 15:40:35 -05:00
renovate[bot]
c89285f0e8 chore(deps): update dependency glob to v8.1.0 2023-01-16 15:28:14 +00:00
renovate[bot]
7523a1edb3 chore(deps): update commitlint monorepo to v17.4.2 2023-01-16 11:27:14 +00:00
Jenkins
9f1c16a599 chore(i18n): update translations 2023-01-15 15:40:34 -05:00
Maxwell Frank
fdcef0edc8 Merge pull request #664 from openedx/mfrank/adding-skills-route
feat: set up skills builder route
2023-01-11 13:53:03 -05:00
Maxwell Frank
1f056dfac7 feat: set up skills builder route 2023-01-11 18:45:06 +00:00
renovate[bot]
2814349f37 fix(deps): update dependency @edx/brand to v1.2.0 2023-01-09 11:50:01 +00:00
renovate[bot]
a4327a98e4 chore(deps): update commitlint monorepo to v17.4.0 2023-01-09 08:15:56 +00:00
renovate[bot]
50fe0ecb6f fix(deps): update dependency core-js to v3.27.1 2023-01-02 08:58:39 +00:00
renovate[bot]
8e011fdf7b fix(deps): update dependency core-js to v3.27.0 2022-12-26 08:57:37 +00:00
dependabot[bot]
a6a35f3762 build(deps): bump loader-utils from 1.4.0 to 1.4.2 (#655)
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-22 12:54:32 -08:00
renovate[bot]
259f4b2a5e chore(deps): update dependency @edx/frontend-build to v12.4.15 (#625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-22 12:34:58 -08:00
Muhammad Abdullah Waheed
65a4091e78 Automate Browserlist DB Update (#615)
* feat: added cron github action to auto update brwoserlist DB periodically

* refactor: used a shared script to update broswerslist DB, create PR and automerge it
2022-12-22 12:06:26 -08:00
Jason Wesson
e5ee7894b0 refactor: updated frontend-build & resolved eslint issues (#654)
Co-authored-by: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com>
2022-12-22 12:00:53 -08:00
renovate[bot]
8f781ea867 chore(deps): update dependency glob to v8 (#622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-20 13:16:24 -08:00
dependabot[bot]
7d208a91ac build(deps): bump loader-utils from 1.4.0 to 1.4.2 (#635)
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
...

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 13:07:00 -08:00
dependabot[bot]
80599a617f build(deps): bump minimatch and recursive-readdir (#642)
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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 12:59:41 -08:00
dependabot[bot]
652559b157 build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#644)
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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 12:53:22 -08:00
renovate[bot]
5363663170 fix(deps): update dependency universal-cookie to v4 (#279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-20 12:44:44 -08:00
renovate[bot]
b34041f090 fix(deps): update dependency core-js to v3.26.1 2022-12-19 10:22:55 +00:00
renovate[bot]
a82e3e9918 fix(deps): update dependency @edx/frontend-component-header to v3.6.0 2022-12-19 08:03:15 +00:00
mashal-m
6c15c2a0fd fix: remove is-es6 check 2022-12-15 12:43:57 +00:00
mashal-m
da5bf2f533 build: use shared browserslist configuration and upgrade paragon version 2022-12-15 12:43:57 +00:00
renovate[bot]
e507548d48 fix(deps): update dependency @edx/frontend-component-footer to v11.6.0 2022-12-12 11:18:25 +00:00
renovate[bot]
ff6c63c86b fix(deps): update dependency redux-saga to v1.2.2 2022-12-12 08:31:07 +00:00
renovate[bot]
6e24a48570 fix(deps): update dependency @edx/frontend-component-header to v3.5.0 2022-12-05 13:32:14 +00:00
renovate[bot]
42a0d27b47 fix(deps): update dependency @edx/frontend-component-footer to v11.5.2 2022-12-05 11:02:32 +00:00
edX requirements bot
6a79462567 fix: -t flag added in pull translation command (#639) 2022-11-30 16:43:21 +05:00
renovate[bot]
afe39e8b9e fix(deps): update dependency @edx/frontend-component-header to v3.4.1 2022-11-28 12:53:13 +00:00
renovate[bot]
38afcb8a5a fix(deps): update dependency @edx/frontend-component-footer to v11.5.1 2022-11-28 09:16:22 +00:00
renovate[bot]
5bf65de9e4 chore(deps): update commitlint monorepo to v17.3.0 2022-11-28 09:09:07 +00:00
renovate[bot]
28423c261d fix(deps): update dependency regenerator-runtime to v0.13.11 2022-11-21 09:04:15 +00:00
renovate[bot]
0986fd05ab chore(deps): update commitlint monorepo to v17.2.0 2022-11-14 08:32:19 +00:00
renovate[bot]
21adb70478 fix(deps): update dependency reselect to v4.1.7 2022-11-14 08:25:41 +00:00
renovate[bot]
daca35ffbe fix(deps): update dependency redux-thunk to v2.4.2 2022-11-07 10:07:44 +00:00
renovate[bot]
9fe9164bb2 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.7 2022-11-07 07:58:45 +00:00
Jenkins
09e010443d chore(i18n): update translations 2022-10-30 16:40:23 -04:00
renovate[bot]
87377a1443 fix(deps): pin dependency react-helmet to 6.1.0 2022-10-24 08:47:44 +00:00
Jenkins
5f89049506 chore(i18n): update translations 2022-10-23 16:40:27 -04:00
Diana Olarte
4f00bc43b9 feat: allow runtieme configuration (#586)
Allows frontend-app-profile to be configured at
runtime using the LMS's new MFE Configuration API.

Part of https://github.com/openedx/frontend-wg/issues/103
2022-10-17 12:13:59 -04:00
renovate[bot]
f732fa7ffc fix(deps): update dependency regenerator-runtime to v0.13.10 2022-10-17 09:48:16 +00:00
81 changed files with 12082 additions and 23908 deletions

6
.env
View File

@@ -22,6 +22,8 @@ LOGO_URL=''
LOGO_TRADEMARK_URL=''
LOGO_WHITE_URL=''
FAVICON_URL=''
ENABLE_LEARNER_RECORD_MFE=''
LEARNER_RECORD_MFE_BASE_URL=''
COLLECT_YEAR_OF_BIRTH=true
APP_ID=''
MFE_CONFIG_API_URL=''
SEARCH_CATALOG_URL=''
ENABLE_SKILLS_BUILDER_PROFILE=''

View File

@@ -23,6 +23,8 @@ LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
ENABLE_LEARNER_RECORD_MFE=''
LEARNER_RECORD_MFE_BASE_URL='http://localhost:1990'
COLLECT_YEAR_OF_BIRTH=true
APP_ID=''
MFE_CONFIG_API_URL=''
SEARCH_CATALOG_URL='http://localhost:18000/courses'
ENABLE_SKILLS_BUILDER_PROFILE=''

View File

@@ -18,5 +18,8 @@ LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
ENABLE_LEARNER_RECORD_MFE=''
ENABLE_SKILLS_BUILDER_PROFILE=''
LEARNER_RECORD_MFE_BASE_URL='http://localhost:1990'
COLLECT_YEAR_OF_BIRTH=true
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -16,4 +16,4 @@ jobs:
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 }}
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

View File

@@ -11,16 +11,15 @@ jobs:
matrix:
npm-test:
- i18n_extract
- is-es5
- lint
- test
node: [16]
steps:
- uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install -g npm@8.x.x
node-version: ${{ env.NODE_VER }}
- run: make requirements
- run: make test NPM_TESTS=build
- run: make test NPM_TESTS=${{ matrix.npm-test }}

View File

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

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

View File

@@ -0,0 +1,12 @@
name: Update Browserslist DB
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
jobs:
update-browserslist:
uses: openedx/.github/.github/workflows/update-browserslist-db.yml@master
secrets:
requirements_bot_github_token: ${{ secrets.requirements_bot_github_token }}

2
.nvmrc
View File

@@ -1 +1 @@
v16
18

27
Makefile Executable file → Normal file
View File

@@ -1,17 +1,15 @@
export TRANSIFEX_RESOURCE = frontend-app-profile
transifex_resource = frontend-app-profile
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA"
transifex_langs = "ar,de,de_DE,es_419,fa_IR,fr,fr_CA,hi,it,it_IT,pt,pt_PT,ru,uk,zh_CN"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
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
transifex_temp = ./temp/babel-plugin-formatjs
NPM_TESTS=build i18n_extract lint test is-es5
NPM_TESTS=build i18n_extract lint test
.PHONY: test
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
@@ -52,9 +50,24 @@ push_translations:
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --languages=$(transifex_langs)
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-app-profile/src/i18n/messages:frontend-app-profile
$(intl_imports) paragon frontend-component-header frontend-component-footer frontend-app-profile
endif
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:

View File

@@ -1,57 +1,147 @@
|Build Status| |Codecov| |license|
#####################
frontend-app-profile
====================
#####################
This is a micro-frontend application responsible for the display and updating of user profiles. Please tag **@edx/arch-fed** on any PRs or issues.
|license-badge| |status-badge| |ci-badge| |codecov-badge|
.. |license-badge| image:: https://img.shields.io/github/license/openedx/frontend-app-profile.svg
:target: https://github.com/openedx/frontend-app-profile/blob/main/LICENSE
:alt: License
.. |status-badge| image:: https://img.shields.io/badge/Status-Maintained-brightgreen
.. |ci-badge| image:: https://github.com/openedx/frontend-app-profile/actions/workflows/ci.yml/badge.svg
:target: https://github.com/openedx/frontend-app-profile/actions/workflows/ci.yml
:alt: Continuous Integration
.. |codecov-badge| image:: https://codecov.io/github/openedx/frontend-app-profile/coverage.svg?branch=main
:target: https://codecov.io/github/openedx/frontend-app-profile?branch=main
:alt: Codecov
********
Purpose
********
This is a micro-frontend application responsible for the display and updating of user profiles.
When a user views their own profile, they're given fields to edit their full name, location, primary spoken language, education, social links, and bio. Each field also has a dropdown to select the visibility of that field - i.e., whether it can be viewed by other learners.
When a user views someone else's profile, they see all those fields that that user set as public.
----------
***************
Getting Started
***************
Development
-----------
Installation
============
Start Devstack
^^^^^^^^^^^^^^
Follow these steps to provision, run, and enable an instance of the
Profile MFE for local development via the `devstack`_.
To use this application `devstack <https://github.com/openedx/devstack>`__ must be running and you must be logged into it.
.. _devstack: https://github.com/openedx/devstack#getting-started
- Start devstack
- Log in (http://localhost:18000/login)
#. To use this application, `devstack <https://github.com/openedx/devstack>`__ must be running and you must be logged into it.
Start the development server
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Start devstack
* Log in (http://localhost:18000/login)
In this project, install requirements and start the development server by running:
#. To run Profile, install requirements and start the development server by running:
.. code:: bash
.. code-block::
npm install
npm start # The server will run on port 1995
1. Clone your new repo:
Once the dev server is up visit http://localhost:1995/u/staff.
``git clone https://github.com/openedx/frontend-app-profile.git``
----------
2. Use node v18.x.
Configuration and Deployment
----------------------------
The current version of the micro-frontend build scripts support node 18.
Using other major versions of node *may* work, but this is unsupported. For
convenience, this repository includes an .nvmrc file to help in setting the
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
3. Install npm dependencies:
``cd frontend-app-profile && npm ci``
4. Start the dev server:
``npm start``
The server will run on port 1995
Once the dev server is up, visit http://localhost:1995/u/staff.
Configuration
=============
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:
.. code:: bash
.. code-block::
NODE_ENV=production ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
Getting Help
============
For more information see the document: `Micro-frontend applications in Open
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/micro-frontends-in-open-edx.html>`__.
If you're having trouble, we have discussion forums at
https://discuss.openedx.org where you can connect with others in the community.
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-profile.svg?branch=master
:target: https://travis-ci.org/edx/frontend-app-profile
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-profile
:target: https://codecov.io/gh/edx/frontend-app-profile
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-profile.svg
:target: @edx/frontend-app-profile
Our real-time conversations are on Slack. You can request a `Slack
invitation`_, then join our `community Slack workspace`_. Because this is a
frontend repository, the best place to discuss it would be in the `#wg-frontend
channel`_.
For anything non-trivial, the best path is to open an issue in this repository
with as many details about the issue you are facing as you can provide. Please tag **@openedx/2u-aperture** on any PRs or issues.
https://github.com/openedx/frontend-app-profile/issues
For more information about these options, see the `Getting Help`_ page.
.. _Slack invitation: https://openedx.org/slack
.. _community Slack workspace: https://openedx.slack.com/
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
.. _Getting Help: https://openedx.org/getting-help
License
=======
The code in this repository is licensed under the AGPLv3 unless otherwise
noted.
Please see `LICENSE <LICENSE>`_ for details.
Contributing
============
Contributions are very welcome. Please read `How To Contribute`_ for details.
.. _How To Contribute: https://openedx.org/r/how-to-contribute
This project is currently accepting all types of contributions, bug fixes,
security fixes, maintenance work, or new features. However, please make sure
to have a discussion about your new feature idea with the maintainers prior to
beginning development to maximize the chances of your change being accepted.
You can start a conversation by creating a new issue on this repo summarizing
your idea.
The Open edX Code of Conduct
============================
All community members are expected to follow the `Open edX Code of Conduct`_.
.. _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`_. Backstage pulls this data from the ``catalog-info.yaml``
file in this repo.
.. _Backstage: https://backstage.herokuapp.com/catalog/default/component/frontend-app-profile
Reporting Security Issues
=========================
Please do not report security issues in public. Email security@openedx.org instead.

24
catalog-info.yaml Normal file
View File

@@ -0,0 +1,24 @@
# 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: 'Profile'
description: 'This is a micro-frontend application responsible for the display and updating of user profiles.'
links:
- url: 'https://github.com/openedx/frontend-app-profile/blob/master/README.rst'
title: 'Documentation'
icon: 'Article'
annotations:
# (Optional) Annotation keys and values can be whatever you want.
# We use it in Open edX repos to have a comma-separated list of GitHub user
# names that might be interested in changes to the architecture of this
# component.
openedx.org/arch-interest-groups: ""
# This can be multiple comma-separated projects.
openedx.org/add-to-projects: "openedx:23"
spec:
type: 'service'
lifecycle: 'production'
owner: 2U-aperture
# (Optional) An array of different components or resources.

33142
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,12 @@
},
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"is-es5": "es-check es5 ./dist/*.js",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests"
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests",
"stubs": "pact-stub-service ./src/pacts/frontend-app-profile-edx-platform.json --port 18000"
},
"bugs": {
"url": "https://github.com/openedx/frontend-app-profile/issues"
@@ -25,52 +25,57 @@
"access": "public"
},
"browserslist": [
"last 2 versions",
"ie 11"
"extends @edx/browserslist-config"
],
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.3.0",
"@edx/frontend-component-header": "2.6.1",
"@edx/frontend-platform": "1.15.6",
"@edx/paragon": "19.25.3",
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "12.5.1",
"@edx/frontend-component-header": "4.8.0",
"@edx/frontend-platform": "5.6.1",
"@edx/frontend-plugin-framework": "openedx/frontend-plugin-framework#jwesson/install-plugins",
"@edx/paragon": "^20.44.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.2.0",
"@pact-foundation/pact": "^11.0.2",
"classnames": "2.3.2",
"core-js": "3.25.5",
"core-js": "3.33.1",
"history": "5.3.0",
"lodash.camelcase": "4.3.0",
"lodash.get": "4.4.2",
"lodash.pick": "4.4.0",
"lodash.snakecase": "4.1.1",
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-error-boundary": "^4.0.11",
"react-helmet": "6.1.0",
"react-redux": "7.2.9",
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
"redux": "4.2.0",
"react-router": "6.16.0",
"react-router-dom": "6.16.0",
"redux": "4.2.1",
"redux-devtools-extension": "2.13.9",
"redux-logger": "3.0.6",
"redux-saga": "1.2.1",
"redux-thunk": "2.4.1",
"regenerator-runtime": "0.13.9",
"reselect": "4.1.6",
"universal-cookie": "3.1.0"
"redux-saga": "1.2.3",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.0",
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@commitlint/cli": "17.1.2",
"@commitlint/config-angular": "17.1.0",
"@edx/reactifex": "2.1.1",
"@edx/frontend-build": "12.0.6",
"@commitlint/cli": "17.8.1",
"@commitlint/config-angular": "17.8.1",
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "13.0.4",
"@edx/reactifex": "2.2.0",
"@testing-library/react": "12.1.5",
"@wojtekmaj/enzyme-adapter-react-17": "0.8.0",
"codecov": "3.8.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"es-check": "5.2.4",
"glob": "7.2.3",
"react-test-renderer": "16.14.0",
"glob": "10.3.10",
"react-test-renderer": "17.0.2",
"reactifex": "1.1.1",
"redux-mock-store": "1.5.4"
}

93
plugins/Plugin.jsx Normal file
View File

@@ -0,0 +1,93 @@
'use client';
import React, {
useEffect, useMemo, useState,
} from 'react';
import PropTypes from 'prop-types';
import { ErrorBoundary } from 'react-error-boundary';
import { logError } from '@edx/frontend-platform/logging';
import {
dispatchMountedEvent, dispatchReadyEvent, dispatchUnmountedEvent, useHostEvent,
} from './data/hooks';
import { PLUGIN_RESIZE } from './data/constants';
// see example-plugin-app/src/PluginOne.jsx for example of customizing errorFallback
function errorFallbackDefault() {
return (
<div>
<h2>
Oops! An error occurred. Please refresh the screen to try again.
</h2>
</div>
);
}
// eslint-disable-next-line react/function-component-definition
export default function Plugin({
children, className, style, ready, errorFallbackProp,
}) {
const [dimensions, setDimensions] = useState({
width: null,
height: null,
});
const finalStyle = useMemo(() => ({
...dimensions,
...style,
}), [dimensions, style]);
const errorFallback = errorFallbackProp || errorFallbackDefault;
// Error logging function
// Need to confirm: When an error is caught here, the logging will be sent to the child MFE's logging service
const logErrorToService = (error, info) => {
logError(error, { stack: info.componentStack });
};
useHostEvent(PLUGIN_RESIZE, ({ payload }) => {
setDimensions({
width: payload.width,
height: payload.height,
});
});
useEffect(() => {
dispatchMountedEvent();
return () => {
dispatchUnmountedEvent();
};
}, []);
useEffect(() => {
if (ready) {
dispatchReadyEvent();
}
}, [ready]);
return (
<div className={className} style={finalStyle}>
<ErrorBoundary
FallbackComponent={errorFallback}
onError={logErrorToService}
>
{children}
</ErrorBoundary>
</div>
);
}
Plugin.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
errorFallbackProp: PropTypes.func,
ready: PropTypes.bool,
style: PropTypes.object, // eslint-disable-line
};
Plugin.defaultProps = {
className: null,
errorFallbackProp: null,
style: {},
ready: true,
};

View File

@@ -0,0 +1,42 @@
'use client';
import React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import PluginContainerIframe from './PluginContainerIframe';
import {
IFRAME_PLUGIN,
} from './data/constants';
import { pluginConfigShape } from './data/shapes';
// eslint-disable-next-line react/function-component-definition
export default function PluginContainer({ config, ...props }) {
if (config === null) {
return null;
}
// this will allow for future plugin types to be inserted in the PluginErrorBoundary
let renderer = null;
switch (config.type) {
case IFRAME_PLUGIN:
renderer = (
<PluginContainerIframe config={config} {...props} />
);
break;
// istanbul ignore next: default isn't meaningful, just satisfying linter
default:
}
return (
renderer
);
}
PluginContainer.propTypes = {
config: pluginConfigShape,
};
PluginContainer.defaultProps = {
config: null,
};

View File

@@ -0,0 +1,99 @@
import React, {
useEffect, useState,
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {
PLUGIN_MOUNTED,
PLUGIN_READY,
PLUGIN_RESIZE,
} from './data/constants';
import {
dispatchPluginEvent,
useElementSize,
usePluginEvent,
} from './data/hooks';
import { pluginConfigShape } from './data/shapes';
/**
* Feature policy for iframe, allowing access to certain courseware-related media.
*
* We must use the wildcard (*) origin for each feature, as courseware content
* may be embedded in external iframes. Notably, xblock-lti-consumer is a popular
* block that iframes external course content.
* This policy was selected in conference with the edX Security Working Group.
* Changes to it should be vetted by them (security@edx.org).
*/
export const IFRAME_FEATURE_POLICY = (
'fullscreen; microphone *; camera *; midi *; geolocation *; encrypted-media *'
);
// eslint-disable-next-line react/function-component-definition
export default function PluginContainerIframe({
config, fallback, className, ...props
}) {
const { url } = config;
const { title, scrolling } = props;
const [mounted, setMounted] = useState(false);
const [ready, setReady] = useState(false);
const [iframeRef, iframeElement, width, height] = useElementSize();
useEffect(() => {
if (mounted) {
dispatchPluginEvent(iframeElement, {
type: PLUGIN_RESIZE,
payload: {
width,
height,
},
}, url);
}
}, [iframeElement, mounted, width, height, url]);
usePluginEvent(iframeElement, PLUGIN_MOUNTED, () => {
setMounted(true);
});
usePluginEvent(iframeElement, PLUGIN_READY, () => {
setReady(true);
});
return (
<>
<iframe
ref={iframeRef}
title={title}
src={url}
allow={IFRAME_FEATURE_POLICY}
scrolling={scrolling}
referrerPolicy="origin" // The sent referrer will be limited to the origin of the referring page: its scheme, host, and port.
className={classNames(
'border border-0',
{ 'd-none': !ready },
className,
)}
{...props}
/>
{!ready && fallback}
</>
);
}
PluginContainerIframe.propTypes = {
config: pluginConfigShape,
fallback: PropTypes.node,
scrolling: PropTypes.oneOf(['auto', 'yes', 'no']),
title: PropTypes.string,
className: PropTypes.string,
};
PluginContainerIframe.defaultProps = {
config: null,
fallback: null,
scrolling: 'auto',
title: null,
className: null,
};

View File

@@ -0,0 +1,45 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
// eslint-disable-next-line import/no-extraneous-dependencies
import { FormattedMessage } from 'react-intl';
import { logError } from '@edx/frontend-platform/logging';
export default class PluginErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, { stack: info.componentStack });
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<FormattedMessage
id="plugin.load.failure.text"
defaultMessage="This content failed to load."
description="error message when an unexpected error occurs"
/>
);
}
return this.props.children;
}
}
PluginErrorBoundary.propTypes = {
children: PropTypes.node,
};
PluginErrorBoundary.defaultProps = {
children: null,
};

75
plugins/PluginSlot.jsx Normal file
View File

@@ -0,0 +1,75 @@
/* eslint-disable no-unused-vars */
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import { Spinner } from '@edx/paragon';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
// import { usePluginSlot } from './data/hooks';
import PluginContainer from './PluginContainer';
const PluginSlot = forwardRef(({
as, id, intl, pluginProps, children, ...props
}, ref) => {
/* the plugins below are obtained by the id passed into PluginSlot by the Host MFE. See example/src/PluginsPage.jsx
for an example of how PluginSlot is populated, and example/src/index.jsx for a dummy JS config that holds all plugins
*/
// const { plugins, keepDefault } = usePluginSlot(id);
const { fallback } = pluginProps;
// TODO: Add internationalization to the "Loading" text on the spinner.
let finalFallback = (
<div className={classNames(pluginProps.className, 'd-flex justify-content-center align-items-center')}>
<Spinner animation="border" screenReaderText="Loading" />
</div>
);
if (fallback !== undefined) {
finalFallback = fallback;
}
let finalChildren = [];
// if (plugins.length > 0) {
// if (keepDefault) {
// finalChildren.push(children);
// }
// plugins.forEach((pluginConfig) => {
// finalChildren.push(
// <PluginContainer
// key={pluginConfig.url}
// config={pluginConfig}
// fallback={finalFallback}
// {...pluginProps}
// />,
// );
// });
// } else {
finalChildren = children;
// }
return React.createElement(
as,
{
...props,
ref,
},
finalChildren,
);
});
export default injectIntl(PluginSlot);
PluginSlot.propTypes = {
as: PropTypes.elementType,
children: PropTypes.node,
id: PropTypes.string.isRequired,
intl: intlShape.isRequired,
pluginProps: PropTypes.object, // eslint-disable-line
};
PluginSlot.defaultProps = {
as: 'div',
children: null,
pluginProps: {},
};

View File

@@ -0,0 +1,8 @@
// TODO: We expect other plugin types to be added here, such as LTI_PLUGIN and BUILD_TIME_PLUGIN.
export const IFRAME_PLUGIN = 'IFRAME_PLUGIN'; // loads iframe at the URL, rather than loading a JS file.
// Plugin lifecycle events
export const PLUGIN_MOUNTED = 'PLUGIN_MOUNTED';
export const PLUGIN_READY = 'PLUGIN_READY';
export const PLUGIN_UNMOUNTED = 'PLUGIN_UNMOUNTED';
export const PLUGIN_RESIZE = 'PLUGIN_RESIZE';

96
plugins/data/hooks.js Normal file
View File

@@ -0,0 +1,96 @@
import {
useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from 'react';
import { PLUGIN_MOUNTED, PLUGIN_READY, PLUGIN_UNMOUNTED } from './constants';
export function useMessageEvent(srcWindow, type, callback) {
useLayoutEffect(() => {
const listener = (event) => {
// Filter messages to those from our source window.
if (event.source === srcWindow) {
if (event.data.type === type) {
callback({ type, payload: event.data.payload });
}
}
};
if (srcWindow !== null) {
global.addEventListener('message', listener);
}
return () => {
global.removeEventListener('message', listener);
};
}, [srcWindow, type, callback]);
}
export function useHostEvent(type, callback) {
useMessageEvent(global.parent, type, callback);
}
export function usePluginEvent(iframeElement, type, callback) {
const contentWindow = iframeElement ? iframeElement.contentWindow : null;
useMessageEvent(contentWindow, type, callback);
}
export function dispatchMessageEvent(targetWindow, message, targetOrigin) {
// Checking targetOrigin falsiness here since '', null or undefined would all be reasons not to
// try to post a message to the origin.
if (targetOrigin) {
targetWindow.postMessage(message, targetOrigin);
}
}
export function dispatchPluginEvent(iframeElement, message, targetOrigin) {
dispatchMessageEvent(iframeElement.contentWindow, message, targetOrigin);
}
export function dispatchHostEvent(message) {
dispatchMessageEvent(global.parent, message, global.document.referrer);
}
export function dispatchReadyEvent() {
dispatchHostEvent({ type: PLUGIN_READY });
}
export function dispatchMountedEvent() {
dispatchHostEvent({ type: PLUGIN_MOUNTED });
}
export function dispatchUnmountedEvent() {
dispatchHostEvent({ type: PLUGIN_UNMOUNTED });
}
export function useElementSize() {
const observerRef = useRef();
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [element, setElement] = useState(null);
const measuredRef = useCallback(_element => {
setElement(_element);
}, []);
useEffect(() => {
observerRef.current = new ResizeObserver(() => {
if (element) {
setDimensions({
width: element.clientWidth,
height: element.clientHeight,
});
setOffset({
x: element.offsetLeft,
y: element.offsetTop,
});
}
});
if (element) {
observerRef.current.observe(element);
}
}, [element]);
return useMemo(
() => ([measuredRef, element, dimensions.width, dimensions.height, offset.x, offset.y]),
[measuredRef, element, dimensions, offset],
);
}

10
plugins/data/shapes.js Normal file
View File

@@ -0,0 +1,10 @@
/* eslint-disable import/prefer-default-export */
import PropTypes from 'prop-types';
import { IFRAME_PLUGIN } from './constants';
export const pluginConfigShape = PropTypes.shape({
url: PropTypes.string.isRequired,
type: PropTypes.oneOf([IFRAME_PLUGIN]).isRequired,
// This is a place for us to put any generic props we want to pass to the component. We need it.
props: PropTypes.object, // eslint-disable-line react/forbid-prop-types
});

18
plugins/index.js Normal file
View File

@@ -0,0 +1,18 @@
// export {
// usePluginSlot,
// } from './data/hooks';
export {
default as Plugin,
} from './Plugin';
export {
default as PluginContainer,
} from './PluginContainer';
export {
default as PluginSlot,
} from './PluginSlot';
export {
IFRAME_PLUGIN,
} from './data/constants';
export {
default as PluginErrorBoundary,
} from './PluginErrorBoundary';

View File

@@ -24,7 +24,7 @@
"automerge": true
},
{
"matchPackagePatterns": ["@edx"],
"matchPackagePatterns": ["@edx", "@openedx"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}

21
src/head/Head.jsx Normal file
View File

@@ -0,0 +1,21 @@
import React from 'react';
import { Helmet } from 'react-helmet';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
const Head = ({ intl }) => (
<Helmet>
<title>
{intl.formatMessage(messages['profile.page.title'], { siteName: getConfig().SITE_NAME })}
</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
);
Head.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(Head);

17
src/head/Head.test.jsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { Helmet } from 'react-helmet';
import { mount } from 'enzyme';
import { getConfig } from '@edx/frontend-platform';
import Head from './Head';
describe('Head', () => {
const props = {};
it('should match render title tag and favicon with the site configuration values', () => {
mount(<IntlProvider locale="en"><Head {...props} /></IntlProvider>);
const helmet = Helmet.peek();
expect(helmet.title).toEqual(`Profile | ${getConfig().SITE_NAME}`);
expect(helmet.linkTags[0].rel).toEqual('shortcut icon');
expect(helmet.linkTags[0].href).toEqual(getConfig().FAVICON_URL);
});
});

11
src/head/messages.js Normal file
View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.page.title': {
id: 'profile.page.title',
defaultMessage: 'Profile | {siteName}',
description: 'Title tag',
},
});
export default messages;

View File

@@ -1,19 +1,27 @@
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as paragonMessages } from '@edx/paragon';
import arMessages from './messages/ar.json';
import frMessages from './messages/fr.json';
import es419Messages from './messages/es_419.json';
import zhcnMessages from './messages/zh_CN.json';
import ptMessages from './messages/pt.json';
import itMessages from './messages/it.json';
import ukMessages from './messages/uk.json';
import deMessages from './messages/de.json';
import ruMessages from './messages/ru.json';
import hiMessages from './messages/hi.json';
import dedeCAMessages from './messages/de_DE.json';
import es419Messages from './messages/es_419.json';
import faIRMessages from './messages/fa_IR.json';
import frCAMessages from './messages/fr_CA.json';
import itMessages from './messages/it.json';
import ititCAMessages from './messages/it_IT.json';
import frMessages from './messages/fr.json';
import hiMessages from './messages/hi.json';
import ptMessages from './messages/pt.json';
import ptptCAMessages from './messages/pt_PT.json';
import ruMessages from './messages/ru.json';
import ukMessages from './messages/uk.json';
import zhcnMessages from './messages/zh_CN.json';
// no need to import en messages-- they are in the defaultMessage field
const messages = {
const appMessages = {
ar: arMessages,
'es-419': es419Messages,
'fa-ir': faIRMessages,
fr: frMessages,
'zh-cn': zhcnMessages,
pt: ptMessages,
@@ -23,6 +31,14 @@ const messages = {
'fr-ca': frCAMessages,
ru: ruMessages,
uk: ukMessages,
'de-de': dedeCAMessages,
'it-it': ititCAMessages,
'pt-pt': ptptCAMessages,
};
export default messages;
export default [
headerMessages,
footerMessages,
paragonMessages,
appMessages,
];

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "الملف الشخصي | {siteName}",
"profile.age.details": "لمشاركة ملفك الشخصي مع بقية متعلمي {siteName}، يجب أن تؤكد أنك عمرك يفوق 13 عامًا.",
"profile.age.set.date": "ضيط تاريخ ميلادك",
"profile.datejoined.member.since": "عضو منذ {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "تم الحفظ",
"profile.visibility.who.just.me": "أنا فقط",
"profile.visibility.who.everyone": "جميع من على {siteName}",
"profile.learningGoal.learningGoal": "هدف التعلم",
"profile.learningGoal.options.start_career": "أريد أن أبدأ مسيرتي المهنية",
"profile.learningGoal.options.advance_career": "أريد أن ارتقي في مسيرتي المهنية",
"profile.learningGoal.options.learn_something_new": "أريد أن أتعلم شيئًا جديدًا",
"profile.learningGoal.options.something_else": "شيء آخر",
"profile.name.full.name": "الاسم الكامل",
"profile.name.details": "هذا هو الاسم الذي يظهر في حسابك وفي شهاداتك",
"profile.name.empty": "إضافة الاسم",

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -0,0 +1,57 @@
{
"profile.page.title": "Profil | {siteName}",
"profile.age.details": "Um Ihr Profil mit anderen {siteName}-Lernern zu teilen, müssen Sie bestätigen, dass Sie über 13 Jahre alt sind.",
"profile.age.set.date": "Legen Sie Ihr Geburtsdatum fest",
"profile.datejoined.member.since": "Mitglied seit {year}",
"profile.bio.empty": "Fügen Sie Ihre Kurzbiografie hinzu",
"profile.bio.about.me": "Über mich",
"profile.certificate.organization.label": "Von",
"profile.certificate.completion.date.label": "Abgeschlossen am {date}",
"profile.no.certificates": "Sie haben bisher keine Zertifikate erhalten.",
"profile.certificates.my.certificates": "Meine Zertifikate",
"profile.certificates.view.certificate": "Zertifikat anschauen",
"profile.certificates.types.verified": "Beglaubigtes Zertifikat ",
"profile.certificates.types.professional": "Professional Certificate",
"profile.certificates.types.unknown": "Zertifikat",
"profile.country.label": "Ort",
"profile.country.empty": "Standort hinzufügen",
"profile.education.empty": "Ausbildung hinzufügen",
"profile.education.education": "Bildung",
"profile.education.levels.p": "Doktortitel",
"profile.education.levels.m": "Master oder gleichwertiger akademischer Bildungsgrad",
"profile.education.levels.b": "Bachelor",
"profile.education.levels.a": "Allgemeine Hochschulreife oder gleichwertiger Abschluss",
"profile.education.levels.hs": "Mittlere Reife",
"profile.education.levels.jhs": "Hauptschule",
"profile.education.levels.el": "Grundschule",
"profile.education.levels.none": "Keinen Bildungsabschluss",
"profile.education.levels.o": "Sonstige Bildung",
"profile.editbutton.edit": "Bearbeiten",
"profile.formcontrols.who.can.see": "Wer kann das sehen:",
"profile.formcontrols.button.cancel": "Abbrechen",
"profile.formcontrols.button.save": "Speichern",
"profile.formcontrols.button.saving": "Speichert",
"profile.formcontrols.button.saved": "Gespeichert",
"profile.visibility.who.just.me": "Nur ich",
"profile.visibility.who.everyone": "Alle auf {siteName}",
"profile.learningGoal.learningGoal": "Lernziel",
"profile.learningGoal.options.start_career": "Ich möchte meine Karriere starten",
"profile.learningGoal.options.advance_career": "Ich möchte mich beruflich weiterentwickeln",
"profile.learningGoal.options.learn_something_new": "Ich möchte etwas Neues lernen",
"profile.learningGoal.options.something_else": "Etwas anderes",
"profile.name.full.name": "Vollständiger Name",
"profile.name.details": "Dies ist der Name, der in Ihrem Konto und auf Ihren Zertifikaten erscheint.",
"profile.name.empty": "Name hinzufügen",
"profile.preferredlanguage.empty": "Sprache hinzufügen",
"profile.preferredlanguage.label": "Gesprochene Primärsprache ",
"profile.profileavatar.upload-button": "Foto hochladen",
"profile.profileavatar.remove.button": "Entfernen",
"profile.image.alt.attribute": "Profil Avatar",
"profile.profileavatar.change-button": "Ändern",
"profile.sociallinks.add": "{network} hinzufügen",
"profile.sociallinks.social.links": "Soziale Netzwerke",
"profile.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
"profile.viewMyRecords": "Meine Aufzeichnungen anzeigen",
"profile.loading": "Profil lädt...",
"profile.username.description": "Ihre Profilinformationen sind nur für Sie sichtbar. Nur Ihr Benutzername ist für andere auf {siteName} sichtbar."
}

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Perfil | {siteName}",
"profile.age.details": "Para compartir el perfil con otros {siteName} estudiantes, debe confirmar que es mayor de 13 años.",
"profile.age.set.date": "Establece tu fecha de nacimiento",
"profile.datejoined.member.since": "Miembro desde {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Guardado",
"profile.visibility.who.just.me": "Solo yo",
"profile.visibility.who.everyone": "Todos en {siteName}",
"profile.learningGoal.learningGoal": "Objetivo de aprendizaje",
"profile.learningGoal.options.start_career": "quiero empezar mi carrera",
"profile.learningGoal.options.advance_career": "Quiero avanzar en mi carrera",
"profile.learningGoal.options.learn_something_new": "quiero aprender algo nuevo",
"profile.learningGoal.options.something_else": "Algo más",
"profile.name.full.name": "Nombre completo",
"profile.name.details": "Este es el nombre que aparecerá en tu cuenta y en tus certificados.",
"profile.name.empty": "Añade nombre",

View File

@@ -0,0 +1,57 @@
{
"profile.page.title": "پرونده کاربری {siteName}",
"profile.age.details": "برای اشتراک‌گذاری پرونده کاربری خود با سایر یادگیرندگان {siteName}، باید تأیید کنید که بیش از 13 سال سن دارید.",
"profile.age.set.date": "تنظیم تاریخ تولد",
"profile.datejoined.member.since": "عضو شده از {year}",
"profile.bio.empty": "بیوگرافی کوتاهی اضافه کنید",
"profile.bio.about.me": "درباره من",
"profile.certificate.organization.label": "از",
"profile.certificate.completion.date.label": "تکمیل شده در {date}",
"profile.no.certificates": "شما هنوز هیچ گواهی ندارید.",
"profile.certificates.my.certificates": "گواهی‌های من",
"profile.certificates.view.certificate": "نمایش گواهی",
"profile.certificates.types.verified": "گواهی تأییدشده",
"profile.certificates.types.professional": "گواهی حرفه‌ای",
"profile.certificates.types.unknown": "گواهی",
"profile.country.label": "مکان",
"profile.country.empty": "افزودن مکان",
"profile.education.empty": "افزودن تحصیلات",
"profile.education.education": "تحصیلات",
"profile.education.levels.p": "درجه دکتری",
"profile.education.levels.m": "کارشناسی ارشد یا مدرک حرفه‌ای",
"profile.education.levels.b": "مدرک کارشناسی",
"profile.education.levels.a": "مدرک کاردانی",
"profile.education.levels.hs": "متوسطه/دبیرستان",
"profile.education.levels.jhs": "مدرسه متوسطه دوره اول/ راهنمایی",
"profile.education.levels.el": "مدرسه ابتدایی",
"profile.education.levels.none": "بدون تحصیلات رسمی",
"profile.education.levels.o": "تحصیلات متفرقه",
"profile.editbutton.edit": " ویرایش",
"profile.formcontrols.who.can.see": "کسانی که می‌توانند این را ببینند:",
"profile.formcontrols.button.cancel": "لغو‌",
"profile.formcontrols.button.save": "ذخیره",
"profile.formcontrols.button.saving": "در حال ذخیره",
"profile.formcontrols.button.saved": "ذخیره شد",
"profile.visibility.who.just.me": "فقط من",
"profile.visibility.who.everyone": "هرکسی در {siteName}",
"profile.learningGoal.learningGoal": "هدف یادگیری",
"profile.learningGoal.options.start_career": "من می خواهم کارم را شروع کنم",
"profile.learningGoal.options.advance_career": "من می خواهم حرفه ام را ارتقا دهم",
"profile.learningGoal.options.learn_something_new": "می‌خواهم چیز جدیدی یاد بگیرم",
"profile.learningGoal.options.something_else": "یک چیز دیگر",
"profile.name.full.name": "نام و نام خانوادگی",
"profile.name.details": "این همان نامی است که در حساب کاربری و گواهی‌های شما درج می‌شود.",
"profile.name.empty": "افزودن نام",
"profile.preferredlanguage.empty": "افزودن زبان",
"profile.preferredlanguage.label": "زبان اصلی صحبت شده",
"profile.profileavatar.upload-button": "بارگذاری عکس",
"profile.profileavatar.remove.button": "حذف",
"profile.image.alt.attribute": "چهرک پرونده کاربری",
"profile.profileavatar.change-button": "تغییر",
"profile.sociallinks.add": "افزودن {network}",
"profile.sociallinks.social.links": "پیوندهای رسانه اجتماعی",
"profile.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی آن وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره امتحان کنید.",
"profile.viewMyRecords": "مشاهده سوابق من",
"profile.loading": "در حال بارگذاری پرونده کاربری...",
"profile.username.description": "اطلاعات پرونده کاربری فقط برای شما قابل مشاهده است. سایرین فقط نام کاربری شما را در {siteName} می‌توانند ببینند."
}

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "Pour partager votre profil avec d'autres étudiants {siteName}, vous devez confirmer que vous avez plus de 13 ans.",
"profile.age.set.date": "Définissez votre date de naissance",
"profile.datejoined.member.since": "Membre depuis {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Enregistré",
"profile.visibility.who.just.me": "Juste moi",
"profile.visibility.who.everyone": "Tout le monde sur {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Nom complet",
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos certificats.",
"profile.name.empty": "Ajouter un nom",

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profil | {siteName}",
"profile.age.details": "Pour partager votre profil avec d'autres apprenants {siteName}, vous devez confirmer que vous avez plus de 13 ans.",
"profile.age.set.date": "Entrez votre date de naissance",
"profile.datejoined.member.since": "Membre depuis {year}",
@@ -15,17 +16,17 @@
"profile.country.label": "Adresse",
"profile.country.empty": "Ajouter un emplacement",
"profile.education.empty": "Ajouter formation",
"profile.education.education": "Education",
"profile.education.education": "Formation",
"profile.education.levels.p": "Doctorat",
"profile.education.levels.m": "Maitrise ou diplôme professionnel",
"profile.education.levels.m": "Maîtrise ou diplôme professionnel",
"profile.education.levels.b": "Diplôme de baccalauréat",
"profile.education.levels.a": "Diplôme d'associé",
"profile.education.levels.hs": "Lycée / enseignement secondaire",
"profile.education.levels.jhs": "Collège / enseignement secondaire inférieur",
"profile.education.levels.el": "Enseignement primaire",
"profile.education.levels.none": "Sans diplôme",
"profile.education.levels.o": "Autre niveau d'étude",
"profile.editbutton.edit": "Modifier",
"profile.education.levels.none": "Sans formation formelle",
"profile.education.levels.o": "Autre niveau de formation",
"profile.editbutton.edit": "Éditer",
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
"profile.formcontrols.button.cancel": "Annuler",
"profile.formcontrols.button.save": "Sauvegarder",
@@ -33,14 +34,19 @@
"profile.formcontrols.button.saved": "Sauvegardé",
"profile.visibility.who.just.me": "Juste moi",
"profile.visibility.who.everyone": "Tout le monde sur {siteName}",
"profile.learningGoal.learningGoal": "Objectif d'apprentissage",
"profile.learningGoal.options.start_career": "Je veux commencer ma carrière",
"profile.learningGoal.options.advance_career": "Je veux faire progresser ma carrière",
"profile.learningGoal.options.learn_something_new": "Je veux apprendre quelque chose de nouveau",
"profile.learningGoal.options.something_else": "Autre chose",
"profile.name.full.name": "Nom complet",
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos attestations.",
"profile.name.empty": "Ajouter un nom",
"profile.preferredlanguage.empty": "Ajouter une langue",
"profile.preferredlanguage.label": "Langue principale parlée",
"profile.profileavatar.upload-button": "Téléverser une photo",
"profile.profileavatar.remove.button": "Supprimer",
"profile.image.alt.attribute": "Avatar de profil",
"profile.profileavatar.remove.button": "Retirer",
"profile.image.alt.attribute": "avatar de profil",
"profile.profileavatar.change-button": "Modifier",
"profile.sociallinks.add": "Ajouter {network}",
"profile.sociallinks.social.links": "Liens vers les réseaux sociaux",

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -0,0 +1,57 @@
{
"profile.page.title": "Profilo | {siteName}",
"profile.age.details": "Per condividere il tuo profilo con altri studenti di {siteName}, devi confermare di avere più di 13 anni.",
"profile.age.set.date": "Imposta la data di nascita ",
"profile.datejoined.member.since": "Membro da {year}",
"profile.bio.empty": "Aggiungi una breve biografia ",
"profile.bio.about.me": "Su di me",
"profile.certificate.organization.label": "Da ",
"profile.certificate.completion.date.label": "Completato il {date}",
"profile.no.certificates": "Non si dispone ancora di alcun certificato. ",
"profile.certificates.my.certificates": "Certificati personali ",
"profile.certificates.view.certificate": "Visualizza il certificato",
"profile.certificates.types.verified": "Certificato Verificato",
"profile.certificates.types.professional": "Certificato professionale ",
"profile.certificates.types.unknown": "Certificato ",
"profile.country.label": "Posizione",
"profile.country.empty": "Aggiungi posizione ",
"profile.education.empty": "Aggiungi titolo di studio ",
"profile.education.education": "Educazione",
"profile.education.levels.p": "Dottorato",
"profile.education.levels.m": "Laurea magistrale o titolo accademico professionale",
"profile.education.levels.b": "Laurea di primo livello ",
"profile.education.levels.a": "Diploma Professionale",
"profile.education.levels.hs": "Scuole superiori/liceo",
"profile.education.levels.jhs": "Scuole Medie",
"profile.education.levels.el": "Scuola Primaria/Elementare",
"profile.education.levels.none": "Nessun livello educativo formale",
"profile.education.levels.o": "Altro livello educativo",
"profile.editbutton.edit": "Modifica",
"profile.formcontrols.who.can.see": "Chi può visualizzare: ",
"profile.formcontrols.button.cancel": "Annulla",
"profile.formcontrols.button.save": "Salva",
"profile.formcontrols.button.saving": "Salvataggio in corso",
"profile.formcontrols.button.saved": "Salvato",
"profile.visibility.who.just.me": "Solo io ",
"profile.visibility.who.everyone": "Tutti su {siteName}",
"profile.learningGoal.learningGoal": "Obiettivo di apprendimento",
"profile.learningGoal.options.start_career": "Voglio iniziare il mio percorso",
"profile.learningGoal.options.advance_career": "Voglio avanzare nel mio percorso",
"profile.learningGoal.options.learn_something_new": "Voglio imparare qualcosa di nuovo",
"profile.learningGoal.options.something_else": "Qualcos'altro",
"profile.name.full.name": "Nome e Cognome",
"profile.name.details": "Questo è il nome visualizzato nel proprio account e nei propri certificati. ",
"profile.name.empty": "Aggiungi nome ",
"profile.preferredlanguage.empty": "Aggiungi lingua",
"profile.preferredlanguage.label": "Lingua principale ",
"profile.profileavatar.upload-button": "Carica foto",
"profile.profileavatar.remove.button": "Rimuovi",
"profile.image.alt.attribute": "avatar del profilo ",
"profile.profileavatar.change-button": "Cambia",
"profile.sociallinks.add": "Aggiungi {network}",
"profile.sociallinks.social.links": "Link social ",
"profile.notfound.message": "La pagina ricercata non è disponibile oppure è presente un errore nell'URL. Controllare l'URL e riprovare. ",
"profile.viewMyRecords": "Visualizza record personali ",
"profile.loading": "Caricamento del profilo... ",
"profile.username.description": "Le informazioni del tuo profilo sono visibili solo a te. Solo il tuo nome utente è visibile agli altri su {siteName}."
}

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -0,0 +1,57 @@
{
"profile.page.title": "Perfil | {siteName}",
"profile.age.details": "Para partilhar o seu perfil com outros estudantes da plataforma {siteName}, tem de confirmar que tem mais de 13 anos de idade.",
"profile.age.set.date": "Indique a sua data de nascimento",
"profile.datejoined.member.since": "Utilizador desde {year}",
"profile.bio.empty": "Adicionar uma breve biografia",
"profile.bio.about.me": "Sobre Mim",
"profile.certificate.organization.label": "De",
"profile.certificate.completion.date.label": "Concluído a {date}",
"profile.no.certificates": "Ainda não tem certificados.",
"profile.certificates.my.certificates": "Os Meus Certificados",
"profile.certificates.view.certificate": "Ver Certificado",
"profile.certificates.types.verified": "Certificado Validado",
"profile.certificates.types.professional": "Certificado Profissional",
"profile.certificates.types.unknown": "Certificado",
"profile.country.label": "Localização",
"profile.country.empty": "Adicionar localização",
"profile.education.empty": "Adicionar grau de escolaridade",
"profile.education.education": "Educação",
"profile.education.levels.p": "Doutoramento",
"profile.education.levels.m": "Mestrado ou Grau Profissional",
"profile.education.levels.b": "Licenciatura",
"profile.education.levels.a": "Pós-graduação",
"profile.education.levels.hs": "Secundário",
"profile.education.levels.jhs": "2ªciclo/3ºciclo",
"profile.education.levels.el": "Primária",
"profile.education.levels.none": "Sem estudos",
"profile.education.levels.o": "Outra formação",
"profile.editbutton.edit": "Editar",
"profile.formcontrols.who.can.see": "Quem pode ver isto:",
"profile.formcontrols.button.cancel": "Cancelar",
"profile.formcontrols.button.save": "Guardar",
"profile.formcontrols.button.saving": "A Guardar",
"profile.formcontrols.button.saved": "Guardado",
"profile.visibility.who.just.me": "Apenas eu",
"profile.visibility.who.everyone": "Toda a gente em {siteName}",
"profile.learningGoal.learningGoal": "Objectivo de aprendizagem",
"profile.learningGoal.options.start_career": "Quero começar a minha carreira",
"profile.learningGoal.options.advance_career": "Quero progredir na minha carreira",
"profile.learningGoal.options.learn_something_new": "Quero aprender algo novo",
"profile.learningGoal.options.something_else": "Outra coisa",
"profile.name.full.name": "Nome Completo",
"profile.name.details": "Este é o nome que aparece na sua conta e nos seus certificados.",
"profile.name.empty": "Adicionar nome",
"profile.preferredlanguage.empty": "Adicionar idioma",
"profile.preferredlanguage.label": "Língua Materna",
"profile.profileavatar.upload-button": "Carregar Fotografia",
"profile.profileavatar.remove.button": "Eliminar",
"profile.image.alt.attribute": "Icon de perfil",
"profile.profileavatar.change-button": "Alterar",
"profile.sociallinks.add": "Adicionar {network}",
"profile.sociallinks.social.links": "Links de Redes Sociais",
"profile.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
"profile.viewMyRecords": "Ver os Meus Registos",
"profile.loading": "A carregar perfil...",
"profile.username.description": "As informações do seu perfil só são visíveis para si. Apenas o seu nome de utilizador é visível para outros em {siteName}."
}

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -7,7 +8,7 @@
"profile.certificate.organization.label": "From",
"profile.certificate.completion.date.label": "Completed on {date}",
"profile.no.certificates": "You don't have any certificates yet.",
"profile.certificates.my.certificates": "My Certificates",
"profile.certificates.my.certificates": "Мої сертифікати",
"profile.certificates.view.certificate": "View Certificate",
"profile.certificates.types.verified": "Verified Certificate",
"profile.certificates.types.professional": "Professional Certificate",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -1,4 +1,5 @@
{
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
@@ -33,6 +34,11 @@
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",

View File

@@ -15,29 +15,38 @@ import {
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import Header, { messages as headerMessages } from '@edx/frontend-component-header';
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer';
import Header from '@edx/frontend-component-header';
import Footer from '@edx/frontend-component-footer';
import appMessages from './i18n';
import { ProfilePage, NotFoundPage } from './profile';
import messages from './i18n';
import configureStore from './data/configureStore';
import './index.scss';
import Head from './head/Head';
import AppRoutes from './routes/AppRoutes';
const RenderFooter = () => {
const location = useLocation();
return location.pathname.includes('/plugin') ? null : <Footer />;
};
const RenderHeader = () => {
const location = useLocation();
return location.pathname.includes('/plugin') ? null : <Header />;
};
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider store={configureStore()}>
<Header />
<main>
<Switch>
<Route path="/u/:username" component={ProfilePage} />
<Route path="/notfound" component={NotFoundPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
<Head />
<RenderHeader />
<main id="main">
<AppRoutes />
</main>
<Footer />
<RenderFooter />
</AppProvider>,
document.getElementById('root'),
);
@@ -48,19 +57,13 @@ subscribe(APP_INIT_ERROR, (error) => {
});
initialize({
messages: [
appMessages,
headerMessages,
footerMessages,
],
requireAuthenticatedUser: true,
messages,
hydrateAuthenticatedUser: true,
handlers: {
config: () => {
mergeConfig({
ENABLE_LEARNER_RECORD_MFE: (process.env.ENABLE_LEARNER_RECORD_MFE || false),
LEARNER_RECORD_MFE_BASE_URL: process.env.LEARNER_RECORD_MFE_BASE_URL,
COLLECT_YEAR_OF_BIRTH: process.env.COLLECT_YEAR_OF_BIRTH,
ENABLE_SKILLS_BUILDER_PROFILE: process.env.ENABLE_SKILLS_BUILDER_PROFILE,
}, 'App loadConfig override handler');
},
},

View File

@@ -0,0 +1,77 @@
{
"consumer": {
"name": "frontend-app-profile"
},
"interactions": [
{
"description": "A request for user's basic information",
"providerStates": [
{
"name": "Account and user's information does not exist"
}
],
"request": {
"method": "GET",
"path": "/api/user/v1/accounts/staff_not_found"
},
"response": {
"status": 404
}
},
{
"description": "A request for user's basic information",
"providerStates": [
{
"name": "I have a user's basic information"
}
],
"request": {
"method": "GET",
"path": "/api/user/v1/accounts/staff"
},
"response": {
"body": {
"bio": "This is my bio",
"country": "ME",
"dateJoined": "2017-06-07T00:44:23Z",
"email": "staff@example.com",
"isActive": true,
"name": "Lemon Seltzer",
"username": "staff",
"yearOfBirth": 1901
},
"headers": {
"Content-Type": "application/json"
},
"matchingRules": {
"body": {
"$": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
}
}
},
"status": 200
}
}
],
"metadata": {
"pact-js": {
"version": "11.0.2"
},
"pactRust": {
"ffi": "0.4.0",
"models": "1.0.4"
},
"pactSpecification": {
"version": "3.0.0"
}
},
"provider": {
"name": "edx-platform"
}
}

View File

@@ -4,35 +4,33 @@ import { Alert } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
function AgeMessage({ accountSettingsUrl }) {
return (
<Alert
variant="info"
dismissible={false}
show
>
<Alert.Heading id="profile.age.headline">
Your profile cannot be shared.
</Alert.Heading>
const AgeMessage = ({ accountSettingsUrl }) => (
<Alert
variant="info"
dismissible={false}
show
>
<Alert.Heading id="profile.age.headline">
Your profile cannot be shared.
</Alert.Heading>
<FormattedMessage
id="profile.age.details"
defaultMessage="To share your profile with other {siteName} learners, you must confirm that you are over the age of 13."
description="Error message"
tagName="p"
values={{
siteName: getConfig().SITE_NAME,
}}
/>
<Alert.Link href={accountSettingsUrl}>
<FormattedMessage
id="profile.age.details"
defaultMessage="To share your profile with other {siteName} learners, you must confirm that you are over the age of 13."
description="Error message"
tagName="p"
values={{
siteName: getConfig().SITE_NAME,
}}
id="profile.age.set.date"
defaultMessage="Set your date of birth"
description="Label on a link to set birthday"
/>
<Alert.Link href={accountSettingsUrl}>
<FormattedMessage
id="profile.age.set.date"
defaultMessage="Set your date of birth"
description="Label on a link to set birthday"
/>
</Alert.Link>
</Alert>
);
}
</Alert.Link>
</Alert>
);
AgeMessage.propTypes = {
accountSettingsUrl: PropTypes.string.isRequired,

View File

@@ -1,7 +1,5 @@
import React from 'react';
function Banner() {
return <div className="profile-page-bg-banner bg-primary d-none d-md-block p-relative" />;
}
const Banner = () => <div className="profile-page-bg-banner bg-primary d-none d-md-block p-relative" />;
export default Banner;

View File

@@ -2,11 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
function DateJoined({ date }) {
const DateJoined = ({ date }) => {
if (date == null) {
return null;
}
return (
<p className="mb-0">
<FormattedMessage
@@ -19,7 +18,7 @@ function DateJoined({ date }) {
/>
</p>
);
}
};
DateJoined.propTypes = {
date: PropTypes.string,

View File

@@ -1,16 +1,16 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
export default function NotFoundPage() {
return (
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
<FormattedMessage
id="profile.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" style={{ maxWidth: '32em' }}>
<FormattedMessage
id="profile.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

@@ -33,6 +33,7 @@ import DateJoined from './DateJoined';
import UsernameDescription from './UsernameDescription';
import PageLoading from './PageLoading';
import Banner from './Banner';
import LearningGoal from './forms/LearningGoal';
// Selectors
import { profilePageSelector } from './data/selectors';
@@ -40,16 +41,18 @@ import { profilePageSelector } from './data/selectors';
// i18n
import messages from './ProfilePage.messages';
import withParams from '../utils/hoc';
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
class ProfilePage extends React.Component {
constructor(props, context) {
super(props, context);
const recordsUrl = this.getRecordsUrl(context);
const credentialsBaseUrl = context.config.CREDENTIALS_BASE_URL;
this.state = {
viewMyRecordsUrl: recordsUrl,
viewMyRecordsUrl: credentialsBaseUrl ? `${credentialsBaseUrl}/records` : null,
accountSettingsUrl: `${context.config.LMS_BASE_URL}/account/settings`,
};
@@ -62,9 +65,9 @@ class ProfilePage extends React.Component {
}
componentDidMount() {
this.props.fetchProfile(this.props.match.params.username);
this.props.fetchProfile(this.props.params.username);
sendTrackingLogEvent('edx.profile.viewed', {
username: this.props.match.params.username,
username: this.props.params.username,
});
}
@@ -92,19 +95,6 @@ class ProfilePage extends React.Component {
this.props.updateDraft(name, value);
}
getRecordsUrl(context) {
let recordsUrl = null;
if (getConfig().ENABLE_LEARNER_RECORD_MFE) {
recordsUrl = getConfig().LEARNER_RECORD_MFE_BASE_URL;
} else {
const credentialsBaseUrl = context.config.CREDENTIALS_BASE_URL;
recordsUrl = credentialsBaseUrl ? `${credentialsBaseUrl}/records` : null;
}
return recordsUrl;
}
isYOBDisabled() {
const { yearOfBirth } = this.props;
const currentYear = new Date().getFullYear();
@@ -114,7 +104,7 @@ class ProfilePage extends React.Component {
}
isAuthenticatedUserProfile() {
return this.props.match.params.username === this.context.authenticatedUser.username;
return this.props.params.username === this.context.authenticatedUser.username;
}
// Inserted into the DOM in two places (for responsive layout)
@@ -136,7 +126,7 @@ class ProfilePage extends React.Component {
return (
<span data-hj-suppress>
<h1 className="h2 mb-0 font-weight-bold">{this.props.match.params.username}</h1>
<h1 className="h2 mb-0 font-weight-bold">{this.props.params.username}</h1>
<DateJoined date={dateJoined} />
{this.isYOBDisabled() && <UsernameDescription />}
<hr className="d-none d-md-block" />
@@ -184,6 +174,8 @@ class ProfilePage extends React.Component {
socialLinks,
draftSocialLinksByPlatform,
visibilitySocialLinks,
learningGoal,
visibilityLearningGoal,
languageProficiencies,
visibilityLanguageProficiencies,
visibilityCourseCertificates,
@@ -220,6 +212,7 @@ class ProfilePage extends React.Component {
/>
</div>
</div>
<div>PluginPOC</div>
<div className="col pl-0">
<div className="d-md-none">
{this.renderHeadingLockup()}
@@ -278,6 +271,14 @@ class ProfilePage extends React.Component {
formId="bio"
{...commonFormProps}
/>
{getConfig().ENABLE_SKILLS_BUILDER_PROFILE && (
<LearningGoal
learningGoal={learningGoal}
visibilityLearningGoal={visibilityLearningGoal}
formId="learningGoal"
{...commonFormProps}
/>
)}
<Certificates
visibilityCourseCertificates={visibilityCourseCertificates}
formId="certificates"
@@ -346,6 +347,10 @@ ProfilePage.propTypes = {
})),
visibilitySocialLinks: PropTypes.string.isRequired,
// Learning Goal form data
learningGoal: PropTypes.string,
visibilityLearningGoal: PropTypes.string.isRequired,
// Other data we need
profileImage: PropTypes.shape({
src: PropTypes.string,
@@ -368,10 +373,8 @@ ProfilePage.propTypes = {
updateDraft: PropTypes.func.isRequired,
// Router
match: PropTypes.shape({
params: PropTypes.shape({
username: PropTypes.string.isRequired,
}).isRequired,
params: PropTypes.shape({
username: PropTypes.string.isRequired,
}).isRequired,
// i18n
@@ -390,6 +393,7 @@ ProfilePage.defaultProps = {
socialLinks: [],
draftSocialLinksByPlatform: {},
bio: null,
learningGoal: null,
languageProficiencies: [],
courseCertificates: null,
requiresParentalConsent: null,
@@ -407,4 +411,4 @@ export default connect(
closeForm,
updateDraft,
},
)(injectIntl(ProfilePage));
)(injectIntl(withParams(ProfilePage)));

View File

@@ -29,7 +29,7 @@ const requiredProfilePageProps = {
deleteProfilePhoto: () => {},
openField: () => {},
closeField: () => {},
match: { params: { username: 'staff' } },
params: { username: 'staff' },
};
// Mock language cookie
@@ -66,32 +66,29 @@ beforeEach(() => {
analytics.sendTrackingLogEvent.mockReset();
});
function ProfilePageWrapper({
contextValue, store, match, requiresParentalConsent,
}) {
return (
<AppContext.Provider
value={contextValue}
>
<IntlProvider locale="en">
<Provider store={store}>
<ProfilePage {...requiredProfilePageProps} match={match} requiresParentalConsent={requiresParentalConsent} />
</Provider>
</IntlProvider>
</AppContext.Provider>
);
}
const ProfilePageWrapper = ({
contextValue, store, params, requiresParentalConsent,
}) => (
<AppContext.Provider
value={contextValue}
>
<IntlProvider locale="en">
<Provider store={store}>
<ProfilePage {...requiredProfilePageProps} params={params} requiresParentalConsent={requiresParentalConsent} />
</Provider>
</IntlProvider>
</AppContext.Provider>
);
ProfilePageWrapper.defaultProps = {
match: { params: { username: 'staff' } },
params: { username: 'staff' },
requiresParentalConsent: null,
};
ProfilePageWrapper.propTypes = {
contextValue: PropTypes.shape({}).isRequired,
store: PropTypes.shape({}).isRequired,
match: PropTypes.shape({}),
params: PropTypes.shape({}),
requiresParentalConsent: PropTypes.bool,
};
@@ -126,7 +123,7 @@ describe('<ProfilePage />', () => {
<ProfilePageWrapper
contextValue={contextValue}
store={mockStore(storeMocks.viewOtherProfile)}
match={{ params: { username: 'verified' } }} // Override default match
params={{ username: 'verified' }} // Override default params
/>
);
const tree = renderer.create(component).toJSON();
@@ -281,7 +278,7 @@ describe('<ProfilePage />', () => {
<ProfilePageWrapper
contextValue={contextValue}
store={mockStore(storeMocks.loadingApp)}
match={{ params: { username: 'test-username' } }}
params={{ username: 'test-username' }}
/>
);
const wrapper = mount(component);

View File

@@ -0,0 +1,219 @@
/* eslint-disable react/prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { ensureConfig } from '@edx/frontend-platform';
import { AppContext } from '@edx/frontend-platform/react';
import { injectIntl, intlShape, FormattedDate } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTwitter, faFacebook, faLinkedin } from '@fortawesome/free-brands-svg-icons';
import {
ActionRow, Avatar, Card, Hyperlink, Icon,
} from '@edx/paragon';
import { HistoryEdu, VerifiedUser } from '@edx/paragon/icons';
import get from 'lodash.get';
import { Plugin } from '@edx/frontend-plugin-framework/src/plugins';
import PluginCountry from './forms/PluginCountry';
// Actions
import {
fetchProfile,
} from './data/actions';
// Components
import PageLoading from './PageLoading';
// Selectors
import { profilePageSelector } from './data/selectors';
// i18n
import messages from './ProfilePage.messages';
import eduMessages from './forms/Education.messages';
import withParams from '../utils/hoc';
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
// eslint-disable-next-line react/function-component-definition
function Fallback() {
return (
<div>this is broken as all get</div>
);
}
const platformDisplayInfo = {
facebook: {
icon: faFacebook,
name: '',
},
twitter: {
icon: faTwitter,
name: '',
},
linkedin: {
icon: faLinkedin,
name: '',
},
};
class ProfilePluginPage extends React.Component {
componentDidMount() {
this.props.fetchProfile(this.props.params.username);
}
renderContent() {
const {
profileImage,
country,
levelOfEducation,
socialLinks,
isLoadingProfile,
dateJoined,
name,
intl,
} = this.props;
if (isLoadingProfile) {
return <PageLoading srMessage={this.props.intl.formatMessage(messages['profile.loading'])} />;
}
return (
<Plugin fallbackComponent={<Fallback />}>
<Card className="mb-2">
<Card.Header
className="pb-5"
subtitle={(
<Hyperlink destination={`/u/${this.props.params.username}`}>
View public profile
</Hyperlink>
)}
actions={
(
<ActionRow className="mt-3">
{socialLinks
.filter(({ socialLink }) => Boolean(socialLink))
.map(({ platform, socialLink }) => (
<StaticListItem
key={platform}
name={platformDisplayInfo[platform].name}
url={socialLink}
platform={platform}
/>
))}
</ActionRow>
)
}
/>
<Card.Section className="text-center" muted>
<Avatar
size="xl"
className="profile-plugin-avatar"
src={profileImage.src}
alt="Profile image"
/>
<p className="h2 mb-0 font-weight-bold">{name}</p>
<p className="h3 mb-0 font-weight-bold">{this.props.params.username}</p>
<PluginCountry
country={country}
/>
</Card.Section>
<Card.Footer className="p-0">
<Card.Section className="pgn-icons-cell-vertical">
<Icon src={VerifiedUser} />
<p>
since <FormattedDate value={new Date(dateJoined)} year="numeric" />
</p>
</Card.Section>
<Card.Section className="pgn-icons-cell-vertical">
<Icon src={HistoryEdu} />
<p>
{intl.formatMessage(get(
eduMessages,
`profile.education.levels.${levelOfEducation}`,
eduMessages['profile.education.levels.o'],
))}
</p>
</Card.Section>
</Card.Footer>
</Card>
</Plugin>
);
}
render() {
return (
<div className="profile-page">
{this.renderContent()}
</div>
);
}
}
const SocialLink = ({ url, name, platform }) => (
<a href={url} className="font-weight-bold">
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
{name}
</a>
);
const StaticListItem = ({ url, name, platform }) => (
<ul className="list-inline">
<SocialLink name={name} url={url} platform={platform} />
</ul>
);
ProfilePluginPage.contextType = AppContext;
ProfilePluginPage.propTypes = {
// Account data
dateJoined: PropTypes.string,
// Country form data
country: PropTypes.string,
// Education form data
levelOfEducation: PropTypes.string,
// Social links form data
socialLinks: PropTypes.arrayOf(PropTypes.shape({
platform: PropTypes.string,
socialLink: PropTypes.string,
})),
// Other data we need
profileImage: PropTypes.shape({
src: PropTypes.string,
isDefault: PropTypes.bool,
}),
isLoadingProfile: PropTypes.bool.isRequired,
// Actions
fetchProfile: PropTypes.func.isRequired,
// Router
params: PropTypes.shape({
username: PropTypes.string.isRequired,
}).isRequired,
// i18n
intl: intlShape.isRequired,
};
ProfilePluginPage.defaultProps = {
profileImage: {},
levelOfEducation: null,
country: null,
socialLinks: [],
dateJoined: null,
};
export default connect(
profilePageSelector,
{
fetchProfile,
},
)(injectIntl(withParams(ProfilePluginPage)));

View File

@@ -4,22 +4,20 @@ import { VisibilityOff } from '@edx/paragon/icons';
import { Icon } from '@edx/paragon';
import { getConfig } from '@edx/frontend-platform';
function UsernameDescription() {
return (
<div className="d-flex align-items-center mt-3 mb-2rem">
<Icon src={VisibilityOff} className="icon-visibility-off" />
<div className="username-description">
<FormattedMessage
id="profile.username.description"
defaultMessage="Your profile information is only visible to you. Only your username is visible to others on {siteName}."
description="A description of the username field"
values={{
siteName: getConfig().SITE_NAME,
}}
/>
</div>
const UsernameDescription = () => (
<div className="d-flex align-items-center mt-3 mb-2rem">
<Icon src={VisibilityOff} className="icon-visibility-off" />
<div className="username-description">
<FormattedMessage
id="profile.username.description"
defaultMessage="Your profile information is only visible to you. Only your username is visible to others on {siteName}."
description="A description of the username field"
values={{
siteName: getConfig().SITE_NAME,
}}
/>
</div>
);
}
</div>
);
export default UsernameDescription;

View File

@@ -12,7 +12,8 @@ module.exports = {
imageUrlMedium: null,
imageUrlLarge: null
},
levelOfEducation: null
levelOfEducation: null,
learningGoal: null
},
profilePage: {
errors: {},

View File

@@ -42,7 +42,8 @@ module.exports = {
secondaryEmail: null,
timeZone: null,
gender: null,
accountPrivacy: 'custom'
accountPrivacy: 'custom',
learningGoal: null,
},
profilePage: {
errors: {},
@@ -91,7 +92,8 @@ module.exports = {
timeZone: null,
levelOfEducation: 'el',
gender: null,
accountPrivacy: 'custom'
accountPrivacy: 'custom',
learningGoal: null,
},
preferences: {
visibilityUserLocation: 'all_users',
@@ -104,7 +106,8 @@ module.exports = {
visibilityName: 'private',
visibilityLanguageProficiencies: 'all_users',
visibilityCountry: 'all_users',
accountPrivacy: 'custom'
accountPrivacy: 'custom',
visibilityLearningGoal: 'private',
},
courseCertificates: [
{

View File

@@ -42,7 +42,8 @@ module.exports = {
secondaryEmail: null,
timeZone: null,
gender: null,
accountPrivacy: 'custom'
accountPrivacy: 'custom',
learningGoal: 'advance_career',
},
profilePage: {
errors: {},
@@ -83,7 +84,8 @@ module.exports = {
preferences: {},
courseCertificates: [],
drafts: {},
isLoadingProfile: false
isLoadingProfile: false,
learningGoal: 'advance_career',
},
router: {
location: {

View File

@@ -42,7 +42,8 @@ module.exports = {
secondaryEmail: null,
timeZone: null,
gender: null,
accountPrivacy: 'custom'
accountPrivacy: 'custom',
learningGoal: 'advance_career'
},
profilePage: {
errors: {},
@@ -91,7 +92,8 @@ module.exports = {
timeZone: null,
levelOfEducation: 'el',
gender: null,
accountPrivacy: 'custom'
accountPrivacy: 'custom',
learningGoal: 'advance_career'
},
preferences: {
visibilityUserLocation: 'all_users',
@@ -104,7 +106,8 @@ module.exports = {
visibilityName: 'private',
visibilityLanguageProficiencies: 'all_users',
visibilityCountry: 'all_users',
accountPrivacy: 'custom'
accountPrivacy: 'custom',
visibilityLearningGoal: 'private',
},
courseCertificates: [
{

View File

@@ -60,7 +60,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -103,6 +103,9 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -120,12 +123,8 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -167,7 +166,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -202,12 +201,8 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -249,7 +244,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -1657,7 +1652,9 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
id="country-2"
>
country error
<div>
country error
</div>
</div>
</div>
<div
@@ -1753,7 +1750,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor"
/>
</svg>
@@ -2346,9 +2343,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -2361,12 +2356,8 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -2401,7 +2392,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -2455,7 +2446,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -2498,6 +2489,9 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -2515,12 +2509,8 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -2562,7 +2552,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -2597,12 +2587,8 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -2644,7 +2630,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -3017,7 +3003,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
No formal education
</option>
<option
value="o"
value="other"
>
Other education
</option>
@@ -3026,7 +3012,9 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
id="levelOfEducation-3"
>
education error
<div>
education error
</div>
</div>
</div>
<div
@@ -3122,7 +3110,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor"
/>
</svg>
@@ -3535,9 +3523,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -3550,12 +3536,8 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -3590,7 +3572,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -3644,7 +3626,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -3687,6 +3669,9 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -3704,12 +3689,8 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -3751,7 +3732,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -3786,12 +3767,8 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -3833,7 +3810,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -5000,7 +4977,9 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
id="languageProficiencies-4"
>
preferred language error
<div>
preferred language error
</div>
</div>
</div>
<div
@@ -5096,7 +5075,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor"
/>
</svg>
@@ -5599,9 +5578,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -5614,12 +5591,8 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -5654,7 +5627,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -5728,6 +5701,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -5745,12 +5721,8 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<div
className="d-flex align-items-center mt-3 mb-2rem"
@@ -5769,7 +5741,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM3.42 2.45L2.01 3.87l2.68 2.68A11.738 11.738 0 001 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33a2.97 2.97 0 00-2.64-2.64l2.64 2.64z"
d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24ZM3.42 2.45 2.01 3.87l2.68 2.68A11.738 11.738 0 0 0 1 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45ZM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5Zm2.97-5.33a2.97 2.97 0 0 0-2.64-2.64l2.64 2.64Z"
fill="currentColor"
/>
</svg>
@@ -5777,9 +5749,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
<div
className="username-description"
>
<span>
Your profile information is only visible to you. Only your username is visible to others on localhost.
</span>
Your profile information is only visible to you. Only your username is visible to others on localhost.
</div>
</div>
<hr
@@ -5812,12 +5782,8 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<div
className="d-flex align-items-center mt-3 mb-2rem"
@@ -5836,7 +5802,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM3.42 2.45L2.01 3.87l2.68 2.68A11.738 11.738 0 001 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33a2.97 2.97 0 00-2.64-2.64l2.64 2.64z"
d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24ZM3.42 2.45 2.01 3.87l2.68 2.68A11.738 11.738 0 0 0 1 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45ZM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5Zm2.97-5.33a2.97 2.97 0 0 0-2.64-2.64l2.64 2.64Z"
fill="currentColor"
/>
</svg>
@@ -5844,9 +5810,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
<div
className="username-description"
>
<span>
Your profile information is only visible to you. Only your username is visible to others on localhost.
</span>
Your profile information is only visible to you. Only your username is visible to others on localhost.
</div>
</div>
<hr
@@ -5952,7 +5916,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -5995,6 +5959,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -6012,12 +5979,8 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -6059,7 +6022,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -6094,12 +6057,8 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -6141,7 +6100,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -6907,9 +6866,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -6922,12 +6879,8 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -6962,7 +6915,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -7016,7 +6969,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -7059,6 +7012,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -7076,12 +7032,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -7123,7 +7075,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -7158,12 +7110,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -7205,7 +7153,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -7894,7 +7842,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor"
/>
</svg>
@@ -8038,9 +7986,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -8053,12 +7999,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -8093,7 +8035,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -8147,7 +8089,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -8190,6 +8132,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -8207,12 +8152,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -8254,7 +8195,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -8289,12 +8230,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -8336,7 +8273,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -8935,7 +8872,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
id="bio-1"
>
bio error
<div>
bio error
</div>
</div>
</div>
<div
@@ -9031,7 +8970,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor"
/>
</svg>
@@ -9175,9 +9114,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -9190,12 +9127,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -9230,7 +9163,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
@@ -9284,7 +9217,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
className="profile-avatar-menu-container"
>
<div
className="dropdown"
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
@@ -9327,6 +9260,9 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col pl-0"
>
@@ -9344,12 +9280,8 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -9381,12 +9313,8 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
<p
className="mb-0"
>
<span>
Member since
<span>
2017
</span>
</span>
Member since
2017
</p>
<hr
className="d-none d-md-block"
@@ -10149,9 +10077,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
<p
className="small mb-0"
>
<span>
From
</span>
From
</p>
<p
className="h6 mb-4"
@@ -10164,12 +10090,8 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
<p
className="small mb-2"
>
<span>
Completed on
<span>
3/4/2019
</span>
</span>
Completed on
3/4/2019
</p>
<div>
<a
@@ -10204,7 +10126,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>

View File

@@ -7,7 +7,7 @@ const EDUCATION_LEVELS = [
'jhs',
'el',
'none',
'o',
'other',
];
const SOCIAL = {

View File

@@ -0,0 +1,7 @@
const mockData = {
learningGoal: 'advance_career',
editMode: 'static',
visibilityLearningGoal: 'private',
};
export default mockData;

View File

@@ -0,0 +1,80 @@
// This test file simply creates a contract that defines
// expectations and correct responses from the Pact stub server.
import path from 'path';
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { initializeMockApp, getConfig, setConfig } from '@edx/frontend-platform';
import { getAccount } from './services';
const expectedUserInfo200 = {
username: 'staff',
email: 'staff@example.com',
bio: 'This is my bio',
name: 'Lemon Seltzer',
country: 'ME',
dateJoined: '2017-06-07T00:44:23Z',
isActive: true,
yearOfBirth: 1901,
};
const provider = new PactV3({
log: path.resolve(process.cwd(), 'src/pact-logs/pact.log'),
dir: path.resolve(process.cwd(), 'src/pacts'),
consumer: 'frontend-app-profile',
provider: 'edx-platform',
});
describe('getAccount for one username', () => {
beforeAll(async () => {
initializeMockApp();
});
it('returns a HTTP 200 and user information', async () => {
const username200 = 'staff';
await provider.addInteraction({
states: [{ description: "I have a user's basic information" }],
uponReceiving: "A request for user's basic information",
withRequest: {
method: 'GET',
path: `/api/user/v1/accounts/${username200}`,
headers: {},
},
willRespondWith: {
status: 200,
headers: {},
body: MatchersV3.like(expectedUserInfo200),
},
});
return provider.executeTest(async (mockserver) => {
setConfig({
...getConfig(),
LMS_BASE_URL: mockserver.url,
});
const response = await getAccount(username200);
expect(response).toEqual(expectedUserInfo200);
});
});
it('Account does not exist', async () => {
const username404 = 'staff_not_found';
await provider.addInteraction({
states: [{ description: "Account and user's information does not exist" }],
uponReceiving: "A request for user's basic information",
withRequest: {
method: 'GET',
path: `/api/user/v1/accounts/${username404}`,
},
willRespondWith: {
status: 404,
},
});
await provider.executeTest(async (mockserver) => {
setConfig({
...getConfig(),
LMS_BASE_URL: mockserver.url,
});
await expect(getAccount(username404).then((response) => response.data)).rejects.toThrow('Request failed with status code 404');
});
});
});

View File

@@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import get from 'lodash.get';
// Mock Data
import mockData from '../data/mock_data';
import messages from './LearningGoal.messages';
// Components
import EditableItemHeader from './elements/EditableItemHeader';
import SwitchContent from './elements/SwitchContent';
// Selectors
import { editableFormSelector } from '../data/selectors';
const LearningGoal = (props) => {
let { learningGoal, editMode, visibilityLearningGoal } = props;
const { intl } = props;
if (!learningGoal) {
learningGoal = mockData.learningGoal;
}
if (!editMode || editMode === 'empty') { // editMode defaults to 'empty', not sure why yet
editMode = mockData.editMode;
}
if (!visibilityLearningGoal) {
visibilityLearningGoal = mockData.visibilityLearningGoal;
}
return (
<SwitchContent
className="mb-5"
expression={editMode}
cases={{
editable: (
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.learningGoal.learningGoal'])}
showVisibility={visibilityLearningGoal !== null}
visibility={visibilityLearningGoal}
/>
<p data-hj-suppress className="lead">
{intl.formatMessage(get(
messages,
`profile.learningGoal.options.${learningGoal}`,
messages['profile.learningGoal.options.something_else'],
))}
</p>
</>
),
static: (
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.learningGoal.learningGoal'])} />
<p data-hj-suppress className="lead">
{intl.formatMessage(get(
messages,
`profile.learningGoal.options.${learningGoal}`,
messages['profile.learningGoal.options.something_else'],
))}
</p>
</>
),
}}
/>
);
};
LearningGoal.propTypes = {
// From Selector
learningGoal: PropTypes.oneOf(['advance_career', 'start_career', 'learn_something_new', 'something_else']),
visibilityLearningGoal: PropTypes.oneOf(['private', 'all_users']),
editMode: PropTypes.oneOf(['editable', 'static']),
// i18n
intl: intlShape.isRequired,
};
LearningGoal.defaultProps = {
editMode: 'static',
learningGoal: null,
visibilityLearningGoal: 'private',
};
export default connect(
editableFormSelector,
{},
)(injectIntl(LearningGoal));

View File

@@ -0,0 +1,31 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.learningGoal.learningGoal': {
id: 'profile.learningGoal.learningGoal',
defaultMessage: 'Learning Goal',
description: 'A section of a user profile that displays their current learning goal.',
},
'profile.learningGoal.options.start_career': {
id: 'profile.learningGoal.options.start_career',
defaultMessage: 'I want to start my career',
description: 'Selected by user if their goal is to start their career.',
},
'profile.learningGoal.options.advance_career': {
id: 'profile.learningGoal.options.advance_career',
defaultMessage: 'I want to advance my career',
description: 'Selected by user if their goal is to advance their career.',
},
'profile.learningGoal.options.learn_something_new': {
id: 'profile.learningGoal.options.learn_something_new',
defaultMessage: 'I want to learn something new',
description: 'Selected by user if their goal is to learn something new.',
},
'profile.learningGoal.options.something_else': {
id: 'profile.learningGoal.options.something_else',
defaultMessage: 'Something else',
description: 'Selected by user if their goal is not described by the other choices.',
},
});
export default messages;

View File

@@ -0,0 +1,122 @@
import PropTypes from 'prop-types';
import React, { useMemo } from 'react';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { configure as configureI18n, IntlProvider } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { AppContext } from '@edx/frontend-platform/react';
import messages from '../../i18n';
import viewOwnProfileMockStore from '../__mocks__/viewOwnProfile.mockStore';
import savingEditedBioMockStore from '../__mocks__/savingEditedBio.mockStore';
import LearningGoal from './LearningGoal';
const mockStore = configureMockStore([thunk]);
// props to be passed down to LearningGoal component
const requiredLearningGoalProps = {
formId: 'learningGoal',
learningGoal: 'advance_career',
drafts: {},
visibilityLearningGoal: 'private',
editMode: 'static',
saveState: null,
error: null,
openHandler: jest.fn(),
};
configureI18n({
loggingService: { logError: jest.fn() },
config: {
ENVIRONMENT: 'production',
LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum',
},
messages,
});
const LearningGoalWrapper = (props) => {
const contextValue = useMemo(() => ({
authenticatedUser: { userId: null, username: null, administrator: false },
config: getConfig(),
}), []);
return (
<AppContext.Provider
value={contextValue}
>
<IntlProvider locale="en">
<Provider store={props.store}>
<LearningGoal {...props} />
</Provider>
</IntlProvider>
</AppContext.Provider>
);
};
LearningGoalWrapper.defaultProps = {
store: mockStore(viewOwnProfileMockStore),
};
LearningGoalWrapper.propTypes = {
store: PropTypes.shape({}),
};
const LearningGoalWrapperWithStore = ({ store }) => {
const contextValue = useMemo(() => ({
authenticatedUser: { userId: null, username: null, administrator: false },
config: getConfig(),
}), []);
return (
<AppContext.Provider
value={contextValue}
>
<IntlProvider locale="en">
<Provider store={mockStore(store)}>
<LearningGoal {...requiredLearningGoalProps} formId="learningGoal" />
</Provider>
</IntlProvider>
</AppContext.Provider>
);
};
LearningGoalWrapperWithStore.defaultProps = {
store: mockStore(savingEditedBioMockStore),
};
LearningGoalWrapperWithStore.propTypes = {
store: PropTypes.shape({}),
};
describe('<LearningGoal />', () => {
describe('renders the current learning goal', () => {
it('renders "I want to advance my career"', () => {
const learningGoalRenderer = renderer.create(
<LearningGoalWrapper
{...requiredLearningGoalProps}
formId="learningGoal"
/>,
);
const learningGoalInstance = learningGoalRenderer.root;
expect(learningGoalInstance.findByProps({ className: 'lead' }).children).toEqual(['I want to advance my career']);
});
it('renders "Something else"', () => {
requiredLearningGoalProps.learningGoal = 'something_else';
const learningGoalRenderer = renderer.create(
<LearningGoalWrapper
{...requiredLearningGoalProps}
formId="learningGoal"
/>,
);
const learningGoalInstance = learningGoalRenderer.root;
expect(learningGoalInstance.findByProps({ className: 'lead' }).children).toEqual(['Something else']);
});
});
});

View File

@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import { LocationOn } from '@edx/paragon/icons';
// Selectors
import { countrySelector } from '../data/selectors';
// eslint-disable-next-line react/prefer-stateless-function
class PluginCountry extends React.Component {
render() {
const {
country,
countryMessages,
} = this.props;
return (
<div className="pgn-icons-cell-horizontal">
<Icon src={LocationOn} />
<p className="h5 mt-1 ml-1">{countryMessages[country]}</p>
</div>
);
}
}
PluginCountry.propTypes = {
country: PropTypes.string,
countryMessages: PropTypes.objectOf(PropTypes.string).isRequired,
};
PluginCountry.defaultProps = {
country: null,
};
export default connect(
countrySelector,
{},
)(injectIntl(PluginCountry));

View File

@@ -244,14 +244,12 @@ export default connect(
{},
)(injectIntl(SocialLinks));
function SocialLink({ url, name, platform }) {
return (
<a href={url} className="font-weight-bold">
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
{name}
</a>
);
}
const SocialLink = ({ url, name, platform }) => (
<a href={url} className="font-weight-bold">
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
{name}
</a>
);
SocialLink.propTypes = {
url: PropTypes.string.isRequired,
@@ -259,9 +257,9 @@ SocialLink.propTypes = {
name: PropTypes.string.isRequired,
};
function EditableListItem({
const EditableListItem = ({
url, platform, onClickEmptyContent, name,
}) {
}) => {
const linkDisplay = url ? (
<SocialLink name={name} url={url} platform={platform} />
) : (
@@ -269,7 +267,7 @@ function EditableListItem({
);
return <li className="form-group">{linkDisplay}</li>;
}
};
EditableListItem.propTypes = {
url: PropTypes.string,
@@ -282,24 +280,22 @@ EditableListItem.defaultProps = {
onClickEmptyContent: null,
};
function EditingListItem({
const EditingListItem = ({
platform, name, value, onChange, error,
}) {
return (
<li className="form-group">
<label htmlFor={`social-${platform}`}>{name}</label>
<input
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
type="text"
id={`social-${platform}`}
name={platform}
value={value || ''}
onChange={onChange}
aria-describedby="social-error-feedback"
/>
</li>
);
}
}) => (
<li className="form-group">
<label htmlFor={`social-${platform}`}>{name}</label>
<input
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
type="text"
id={`social-${platform}`}
name={platform}
value={value || ''}
onChange={onChange}
aria-describedby="social-error-feedback"
/>
</li>
);
EditingListItem.propTypes = {
platform: PropTypes.string.isRequired,
@@ -314,35 +310,31 @@ EditingListItem.defaultProps = {
error: null,
};
function EmptyListItem({ onClick, name }) {
return (
<li className="mb-4">
<EmptyContent onClick={onClick}>
<FormattedMessage
id="profile.sociallinks.add"
defaultMessage="Add {network}"
values={{
network: name,
}}
description="{network} is the name of a social network such as Facebook or Twitter"
/>
</EmptyContent>
</li>
);
}
const EmptyListItem = ({ onClick, name }) => (
<li className="mb-4">
<EmptyContent onClick={onClick}>
<FormattedMessage
id="profile.sociallinks.add"
defaultMessage="Add {network}"
values={{
network: name,
}}
description="{network} is the name of a social network such as Facebook or Twitter"
/>
</EmptyContent>
</li>
);
EmptyListItem.propTypes = {
name: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
function StaticListItem({ name, url, platform }) {
return (
<li className="mb-2">
<SocialLink name={name} url={url} platform={platform} />
</li>
);
}
const StaticListItem = ({ name, url, platform }) => (
<li className="mb-2">
<SocialLink name={name} url={url} platform={platform} />
</li>
);
StaticListItem.propTypes = {
name: PropTypes.string.isRequired,

View File

@@ -47,7 +47,7 @@ configureI18n({
messages,
});
function SocialLinksWrapper(props) {
const SocialLinksWrapper = (props) => {
const contextValue = useMemo(() => ({
authenticatedUser: { userId: null, username: null, administrator: false },
config: getConfig(),
@@ -63,7 +63,7 @@ function SocialLinksWrapper(props) {
</IntlProvider>
</AppContext.Provider>
);
}
};
SocialLinksWrapper.defaultProps = {
store: mockStore(savingEditedBio),
@@ -73,7 +73,7 @@ SocialLinksWrapper.propTypes = {
store: PropTypes.shape({}),
};
function SocialLinksWrapperWithStore({ store }) {
const SocialLinksWrapperWithStore = ({ store }) => {
const contextValue = useMemo(() => ({
authenticatedUser: { userId: null, username: null, administrator: false },
config: getConfig(),
@@ -89,7 +89,7 @@ function SocialLinksWrapperWithStore({ store }) {
</IntlProvider>
</AppContext.Provider>
);
}
};
SocialLinksWrapperWithStore.defaultProps = {
store: mockStore(savingEditedBio),

View File

@@ -170,7 +170,7 @@ exports[`<SocialLinks /> calls social links with edit mode bio 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor"
/>
</svg>
@@ -399,9 +399,7 @@ exports[`<SocialLinks /> calls social links with edit mode goals 1`] = `
style={Object {}}
/>
</svg>
<span>
Add Facebook
</span>
Add Facebook
</button>
</div>
</li>
@@ -433,9 +431,7 @@ exports[`<SocialLinks /> calls social links with edit mode goals 1`] = `
style={Object {}}
/>
</svg>
<span>
Add Twitter
</span>
Add Twitter
</button>
</div>
</li>

View File

@@ -7,22 +7,20 @@ import { Button } from '@edx/paragon';
import messages from './EditButton.messages';
function EditButton({
const EditButton = ({
onClick, className, style, intl,
}) {
return (
<Button
variant="link"
size="sm"
className={className}
onClick={onClick}
style={style}
>
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />
{intl.formatMessage(messages['profile.editbutton.edit'])}
</Button>
);
}
}) => (
<Button
variant="link"
size="sm"
className={className}
onClick={onClick}
style={style}
>
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />
{intl.formatMessage(messages['profile.editbutton.edit'])}
</Button>
);
export default injectIntl(EditButton);

View File

@@ -4,24 +4,22 @@ import PropTypes from 'prop-types';
import EditButton from './EditButton';
import { Visibility } from './Visibility';
function EditableItemHeader({
const EditableItemHeader = ({
content,
showVisibility,
visibility,
showEditButton,
onClickEdit,
headingId,
}) {
return (
<div className="editable-item-header mb-2">
<h2 className="edit-section-header" id={headingId}>
{content}
{showEditButton ? <EditButton style={{ marginTop: '-.35rem' }} className="float-right px-0" onClick={onClickEdit} /> : null}
</h2>
{showVisibility ? <p className="mb-0"><Visibility to={visibility} /></p> : null}
</div>
);
}
}) => (
<div className="editable-item-header mb-2">
<h2 className="edit-section-header" id={headingId}>
{content}
{showEditButton ? <EditButton style={{ marginTop: '-.35rem' }} className="float-right px-0" onClick={onClickEdit} /> : null}
</h2>
{showVisibility ? <p className="mb-0"><Visibility to={visibility} /></p> : null}
</div>
);
export default EditableItemHeader;

View File

@@ -3,24 +3,22 @@ import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
function EmptyContent({ children, onClick, showPlusIcon }) {
return (
<div>
{onClick ? (
<button
type="button"
className="pl-0 text-left btn btn-link"
onClick={onClick}
onKeyDown={(e) => { if (e.key === 'Enter') { onClick(); } }}
tabIndex={0}
>
{showPlusIcon ? <FontAwesomeIcon size="xs" className="mr-2" icon={faPlus} /> : null}
{children}
</button>
) : children}
</div>
);
}
const EmptyContent = ({ children, onClick, showPlusIcon }) => (
<div>
{onClick ? (
<button
type="button"
className="pl-0 text-left btn btn-link"
onClick={onClick}
onKeyDown={(e) => { if (e.key === 'Enter') { onClick(); } }}
tabIndex={0}
>
{showPlusIcon ? <FontAwesomeIcon size="xs" className="mr-2" icon={faPlus} /> : null}
{children}
</button>
) : children}
</div>
);
export default EmptyContent;

View File

@@ -7,9 +7,9 @@ import messages from './FormControls.messages';
import { VisibilitySelect } from './Visibility';
function FormControls({
const FormControls = ({
cancelHandler, changeHandler, visibility, visibilityId, saveState, intl,
}) {
}) => {
// Eliminate error/failed state for save button
const buttonState = saveState === 'error' ? null : saveState;
@@ -57,7 +57,7 @@ function FormControls({
</div>
</div>
);
}
};
export default injectIntl(FormControls);

View File

@@ -22,7 +22,7 @@ const onChildExit = (htmlNode) => {
}
};
function SwitchContent({ expression, cases, className }) {
const SwitchContent = ({ expression, cases, className }) => {
const getContent = (caseKey) => {
if (cases[caseKey]) {
if (typeof cases[caseKey] === 'string') {
@@ -48,7 +48,7 @@ function SwitchContent({ expression, cases, className }) {
{getContent(expression)}
</TransitionReplace>
);
}
};
SwitchContent.propTypes = {
expression: PropTypes.string,

View File

@@ -7,7 +7,7 @@ import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons';
import messages from './Visibility.messages';
function Visibility({ to, intl }) {
const Visibility = ({ to, intl }) => {
const icon = to === 'private' ? faEyeSlash : faEye;
const label = to === 'private'
? intl.formatMessage(messages['profile.visibility.who.just.me'])
@@ -18,7 +18,7 @@ function Visibility({ to, intl }) {
<FontAwesomeIcon icon={icon} /> {label}
</span>
);
}
};
Visibility.propTypes = {
to: PropTypes.oneOf(['private', 'all_users']),
@@ -30,7 +30,7 @@ Visibility.defaultProps = {
to: 'private',
};
function VisibilitySelect({ intl, className, ...props }) {
const VisibilitySelect = ({ intl, className, ...props }) => {
const { value } = props;
const icon = value === 'private' ? faEyeSlash : faEye;
@@ -49,7 +49,7 @@ function VisibilitySelect({ intl, className, ...props }) {
</select>
</span>
);
}
};
VisibilitySelect.propTypes = {
id: PropTypes.string,

View File

@@ -3,3 +3,4 @@ export { default as saga } from './data/sagas';
export { default as ProfilePage } from './ProfilePage';
export { default as NotFoundPage } from './NotFoundPage';
export { default as messages } from './ProfilePage.messages';
export { default as ProfilePluginPage } from './ProfilePluginPage';

View File

@@ -162,4 +162,28 @@
position: relative;
}
}
.pgn-icons-cell-vertical {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin: 1px;
}
.pgn-icons-cell-horizontal {
display: flex;
flex-direction: row;
justify-content: center;
margin: 1px;
}
.profile-plugin-avatar {
@include media-breakpoint-up(xs) {
max-width: 12rem;
margin-right: 0;
margin-top: -4rem;
margin-bottom: 1rem;
}
}
}

18
src/routes/AppRoutes.jsx Normal file
View File

@@ -0,0 +1,18 @@
import React from 'react';
import {
AuthenticatedPageRoute,
PageWrap,
} from '@edx/frontend-platform/react';
import { Routes, Route } from 'react-router-dom';
import { ProfilePage, NotFoundPage, ProfilePluginPage } from '../profile';
const AppRoutes = () => (
<Routes>
<Route path="/u/:username" element={<AuthenticatedPageRoute><ProfilePage /></AuthenticatedPageRoute>} />
<Route path="/u/:username/plugin" element={<AuthenticatedPageRoute><ProfilePluginPage /></AuthenticatedPageRoute>} />
<Route path="/notfound" element={<PageWrap><NotFoundPage /></PageWrap>} />
<Route path="*" element={<PageWrap><NotFoundPage /></PageWrap>} />
</Routes>
);
export default AppRoutes;

View File

@@ -0,0 +1,80 @@
import React from 'react';
import { AppContext } from '@edx/frontend-platform/react';
import { getConfig } from '@edx/frontend-platform';
import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
import AppRoutes from './AppRoutes';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/auth', () => ({
getLoginRedirectUrl: jest.fn(),
}));
jest.mock('../profile', () => ({
ProfilePage: () => (<div>Profile page</div>),
NotFoundPage: () => (<div>Not found page</div>),
ProfilePluginPage: () => (<div>Plugin page</div>),
}));
const RoutesWithProvider = (context, path) => (
<AppContext.Provider value={context}>
<Router initialEntries={[`${path}`]}>
<AppRoutes />
</Router>
</AppContext.Provider>
);
const unauthenticatedUser = {
authenticatedUser: null,
config: getConfig(),
};
describe('routes', () => {
test('Profile page should redirect for unauthenticated users', () => {
render(
RoutesWithProvider(unauthenticatedUser, '/u/edx'),
);
expect(getLoginRedirectUrl).toHaveBeenCalled();
});
test('Profile page should be accessible for authenticated users', () => {
render(
RoutesWithProvider(
{
authenticatedUser: {
username: 'edx',
email: 'edx@example.com',
},
config: getConfig(),
},
'/u/edx',
),
);
expect(screen.getByText('Profile page')).toBeTruthy();
});
test('Profile Plugin page should be accessible for authenticated users', () => {
render(
RoutesWithProvider(
{
authenticatedUser: {
username: 'edx',
email: 'edx@example.com',
},
config: getConfig(),
},
'/u/edx/plugin',
),
);
expect(screen.getByText('Plugin page')).toBeTruthy();
});
test('should show NotFound page for a bad route', () => {
render(
RoutesWithProvider(unauthenticatedUser, '/nonMatchingRoute'),
);
expect(screen.getByText('Not found page')).toBeTruthy();
});
});

View File

@@ -2,6 +2,6 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
Enzyme.configure({ adapter: new Adapter() });

10
src/utils/hoc.jsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react';
import { useParams } from 'react-router-dom';
const withParams = (WrappedComponent) => {
const WithParamsComponent = (props) => <WrappedComponent params={useParams()} {...props} />;
return WithParamsComponent;
};
export default withParams;